Skip to content

Commit

Permalink
Merge pull request #545 from KhelmholtzR/staging
Browse files Browse the repository at this point in the history
ISIC Scene Segmentation with Proglearn NN
  • Loading branch information
jdey4 committed May 18, 2022
2 parents acec608 + af84f50 commit ca15f3b
Show file tree
Hide file tree
Showing 8 changed files with 1,220 additions and 295 deletions.
1 change: 1 addition & 0 deletions docs/experiments.rst
Expand Up @@ -22,3 +22,4 @@ The following experiments illustrate specific tests using the ``ProgLearn`` pack
experiments/xor_rxor_with_unaware
experiments/xor_xnor_exp
experiments/double_descent_RF
experiments/isic_proglearn_nn
106 changes: 106 additions & 0 deletions docs/experiments/functions/isic_proglearn_nn_functions.py
@@ -0,0 +1,106 @@
from tensorflow import keras
import tensorflow as tf
import cv2
import os
import numpy as np


def get_images(data_path, ids, image_size):
images = np.zeros((len(ids), image_size, image_size, 3))
masks = np.zeros((len(ids), image_size, image_size, 1))

for i, id_name in enumerate(ids):
image_path = (
os.path.join(data_path, "ISIC2018_Task1-2_Training_Input", id_name) + ".jpg"
)
mask_path = (
os.path.join(data_path, "ISIC2018_Task1_Training_GroundTruth", id_name)
+ "_segmentation.png"
)
image = cv2.imread(image_path, 1)
image = cv2.resize(image, (image_size, image_size))
mask = np.zeros((image_size, image_size, 1))
_mask = cv2.imread(mask_path, -1)
_mask = cv2.resize(_mask, (image_size, image_size))
_mask = np.expand_dims(_mask, axis=-1)
mask = np.maximum(mask, _mask)

## Normalizaing
images[i, :, :, :] = image / 255.0
masks[i, :, :, :] = np.rint(mask / 255.0)
return images, masks


def dice(seg, gt):
"""
Compute Dice Score between segmentation and groud truth
"""
intersection = tf.reduce_sum(tf.cast(tf.equal(seg, gt), tf.float32))
return intersection * 2.0 / tf.cast(tf.size(seg) + tf.size(gt), tf.float32)


def down_block(x, filters, kernel_size=(3, 3), padding="same", strides=1):
"""
Down Block of UNet
"""
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(x)
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(c)
p = keras.layers.MaxPool2D((2, 2), (2, 2))(c)
return c, p


def up_block(x, skip, filters, kernel_size=(3, 3), padding="same", strides=1):
"""
Up Block of UNet
"""
us = keras.layers.UpSampling2D((2, 2))(x)
concat = keras.layers.Concatenate()([us, skip])
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(concat)
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(c)
return c


def bottleneck(x, filters, kernel_size=(3, 3), padding="same", strides=1):
"""
Bottleneck of UNet
"""
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(x)
c = keras.layers.Conv2D(
filters, kernel_size, padding=padding, strides=strides, activation="relu"
)(c)
return c


def UNet():
"""
UNet Neural network
"""
f = [16, 32, 64, 128, 256]
inputs = keras.layers.Input((image_size, image_size, 3))

p0 = inputs
c1, p1 = down_block(p0, f[0])
c2, p2 = down_block(p1, f[1])
c3, p3 = down_block(p2, f[2])
c4, p4 = down_block(p3, f[3])

bn = bottleneck(p4, f[4])

u1 = up_block(bn, c4, f[3]) # 8 -> 16
u2 = up_block(u1, c3, f[2]) # 16 -> 32
u3 = up_block(u2, c2, f[1]) # 32 -> 64
u4 = up_block(u3, c1, f[0]) # 64 -> 128

outputs = keras.layers.Conv2D(1, (1, 1), padding="same", activation="sigmoid")(u4)
model = keras.models.Model(inputs, outputs)
return model
573 changes: 573 additions & 0 deletions docs/experiments/isic_proglearn_nn.ipynb

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions proglearn/deciders.py
Expand Up @@ -171,3 +171,55 @@ def predict(self, X, transformer_ids=None):
"""
vote_overall = self.predict_proba(X, transformer_ids=transformer_ids)
return self.classes[np.argmax(vote_overall, axis=1)]


class BinaryRoundDecider(SimpleArgmaxAverage): # pragma: no cover
"""
A class for a decider that rounds probabilities for multiple dimensions in binary classification. To be used with MLKNN voter.
Parameters
----------
classes : list, default=[]
List of final output classification labels of type obj.
Attributes
----------
transformer_id_to_transformers_ : dict
A dictionary with keys of type obj corresponding to transformer ids
and values of type obj corresponding to a transformer. This dictionary
maps transformers to a particular transformer id.
transformer_id_to_voters_ : dict
A dictionary with keys of type obj corresponding to transformer ids
and values of type obj corresponding to a voter class. This dictionary
maps voter classes to a particular transformer id.
"""

def predict(self, X, transformer_ids=None):
"""
Predicts the most likely class per input example.
Uses the predict_proba method to get the mean vote per id.
Returns the class with the highest vote.
Parameters
----------
X : ndarray
Input data matrix.
transformer_ids : list, default=None
A list with all transformer ids. Defaults to None if no transformer ids
are given.
Returns
-------
y_hat : ndarray of shape [n_samples]
predicted class label per example
Raises
------
NotFittedError
When the model is not fitted.
"""
vote_overall = self.predict_proba(X, transformer_ids=transformer_ids)
return np.around(vote_overall)
43 changes: 42 additions & 1 deletion proglearn/tests/test_voter.py
Expand Up @@ -2,7 +2,11 @@
from numpy import testing
from sklearn.utils.validation import NotFittedError

from proglearn.voters import TreeClassificationVoter, KNNClassificationVoter
from proglearn.voters import (
TreeClassificationVoter,
KNNClassificationVoter,
MLKNNClassificationVoter,
)


def generate_data(n=100):
Expand Down Expand Up @@ -61,3 +65,40 @@ def test_correct_vote(self):

# check if model predicts as expected
testing.assert_allclose(Y_test, kcv.predict_proba(X_test), atol=1e-4)

class TestMLKNNClassificationVoter:
def test_initialize(self):
MLKNNClassificationVoter()
assert True

def test_vote_without_fit(self):
# generate random data
X = np.random.randn(100, 3)
testing.assert_raises(
NotFittedError, MLKNNClassificationVoter().predict_proba, X
)

def test_correct_vote(self):
# set random seed
np.random.seed(0)

# generate training data and classes
X = np.concatenate(
(
np.random.normal(0, 0.5, (100, 100)),
np.random.normal(1, 0.5, (100, 100)),
)
)
Y = np.concatenate((np.zeros((100, 1)), np.ones((100, 1))))

# train model
mlkcv = MLKNNClassificationVoter(5)
mlkcv.fit(X, Y)

# generate testing data and class probability
X_test = np.zeros((6, 100))

Y_test = np.zeros((6, 1))

# check if model predicts as expected
testing.assert_allclose(Y_test, mlkcv.predict(X_test), atol=1e-4)
16 changes: 14 additions & 2 deletions proglearn/transformers.py
Expand Up @@ -34,6 +34,9 @@ class NeuralClassificationTransformer(BaseTransformer):
compile_kwargs : dict, default={"metrics": ["acc"]}
A dictionary containing metrics for judging network performance.
categorical: bool, default=True
A boolean used to perform one-hot encoding on labels
fit_kwargs : dict, default={
"epochs": 100,
"callbacks": [keras.callbacks.EarlyStopping(patience=5, monitor="val_acc")],
Expand All @@ -60,6 +63,7 @@ def __init__(
loss="categorical_crossentropy",
pretrained=False,
compile_kwargs={"metrics": ["acc"]},
categorical=True,
fit_kwargs={
"epochs": 100,
"callbacks": [keras.callbacks.EarlyStopping(patience=5, monitor="val_acc")],
Expand All @@ -77,6 +81,7 @@ def __init__(
self.loss = loss
self.compile_kwargs = compile_kwargs
self.fit_kwargs = fit_kwargs
self.categorical = categorical

def fit(self, X, y):
"""
Expand All @@ -95,13 +100,20 @@ def fit(self, X, y):
The object itself.
"""
check_X_y(X, y, ensure_2d=False, allow_nd=True)
_, y = np.unique(y, return_inverse=True)
_, yt = np.unique(y, return_inverse=True)

y = yt.reshape(y.shape)

self.network.compile(
loss=self.loss, optimizer=self.optimizer, **self.compile_kwargs
)

self.network.fit(X, keras.utils.to_categorical(y), **self.fit_kwargs)
if self.categorical:
self.network.fit(X, keras.utils.to_categorical(y), **self.fit_kwargs)

else: # pragma: no cover
self.network.fit(X, y, **self.fit_kwargs)

self.fitted_ = True

return self
Expand Down

0 comments on commit ca15f3b

Please sign in to comment.