jesliu
jesliu

Reputation: 71

Keras CNN prediction output is only predicting within a range of numbers

I am trying to predict 3D medical brain images based on their assessment score using CNN, however the accuracy scores I have received were within a range of numbers (ex: there are 7 possible test scores: 1, 1.5, 2, 2.5, 3, 4, 5 and the output only gives predictions within 1-1.5 range)

I have resized, normalized, and separated the images into train (66 imgs), test (22 imgs), and validation (22 imgs) sets. Because there are so few images, I have added a custom 3D image augmentation (from github) so the total number of images increased to 10x what I originally had.

I have tried changing most, if not all, the parameters (batch size, optimizer, learning rate, simpler/complex nn's, activation, loss, etc) within my network to no avail. I have also looked online for similar problems in hope that someone has had the same issue and solved it.

Here is an example image that I am using:

image description

the size of this image is (96, 96, 96) and a strip of its array value is (after normalizing them):

[0.54124768 0.59549533 0.61464823 0.59833751 0.50441322 0.33578409
 0.40528049 0.4359369  0.39544678 0.32074109 0.20008253 0.28538722
 0.27870766 0.37098099 0.13504691 0.2372147  0.4171059  0.56398624
 0.38187722 0.71896363 0.44387385 0.41523819 0.31195372 0.10586056
 0.12634818 0.13454185 0.57811427 0.6465261  0.61814963 0.61493715]

After preprocessing steps, I fed this into my CNN model:

batch_size = 3

model = Sequential()

model.add(Conv3D(32, [3, 3, 3], padding='same', activation='relu',
                 input_shape=input_size))
model.add(Conv3D(32, [3, 3, 3], padding='same', activation='relu'))
model.add(MaxPooling3D(pool_size=(2, 2, 2), padding='same'))

model.add(Conv3D(64, [3, 3, 3], padding='same', activation='relu'))
model.add(Conv3D(64, [3, 3, 3], padding='same', activation='relu'))
model.add(MaxPooling3D(pool_size=(2, 2, 2), padding='same'))
model.add(Dropout(0.5))

model.add(Conv3D(128, [3, 3, 3], padding='same', activation='relu'))
model.add(Conv3D(128, [3, 3, 3], padding='same', activation='relu'))
model.add(Conv3D(128, [3, 3, 3], padding='same', activation='relu'))
model.add(MaxPooling3D(pool_size=(2, 2, 2), padding='same'))

model.add(Conv3D(256, [3, 3, 3], padding='same', activation='relu'))
model.add(Conv3D(256, [3, 3, 3], padding='same', activation='relu'))
model.add(Conv3D(256, [3, 3, 3], padding='same', activation='relu'))
model.add(MaxPooling3D(pool_size=(2, 2, 2), padding='same'))
model.add(Dropout(0.5))

model.add(Flatten())
model.add(Dense(1024, activation='relu'))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='linear'))

opt = optimizers.Adam(lr=1e-6)
model.compile(loss='mean_squared_logarithmic_error', optimizer=opt, metrics=['accuracy'])

train_datagen = customImageDataGenerator(shear_range=0.2,
                                         zoom_range=0.2,
                                         horizontal_flip=True)

val_datagen = customImageDataGenerator()

test_datagen = customImageDataGenerator()



training_set = train_datagen.flow(x_train, y_train, batch_size=batch_size, shuffle=True)

validation_set = val_datagen.flow(x_val, y_val, batch_size=batch_size, shuffle=False)

testing_set = test_datagen.flow(x_test, y_test, batch_size=batch_size, shuffle=False)



earlystop = EarlyStopping(monitor='val_loss', patience=30)

history = model.fit(
                    training_set,
                    steps_per_epoch = len(x_train)//batch_size,
                    epochs = 50,
                    #callbacks = [earlystop],
                    validation_data = validation_set,
                    validation_steps = len(x_val)//batch_size
                    )

I created a custom accuracy check to visualize the outputs which are:

Predicted score: [1.8405123] True score: 3.0
Predicted score: [1.4033222] True score: 3.0
Predicted score: [1.4690828] True score: 1.0
Predicted score: [1.5127727] True score: 3.0
Predicted score: [1.6159409] True score: 1.0
Predicted score: [1.4333361] True score: 1.5
Predicted score: [1.7470968] True score: 3.0
Predicted score: [1.2196972] True score: 1.5
Predicted score: [1.5940914] True score: 4.0
Predicted score: [1.4052064] True score: 1.0
Predicted score: [1.5127727] True score: 1.0
Predicted score: [1.4584785] True score: 1.0
Predicted score: [1.7860543] True score: 3.0
Predicted score: [1.4752649] True score: 2.5
Predicted score: [1.8568267] True score: 1.0
Predicted score: [1.4793051] True score: 3.0
Predicted score: [1.395096] True score: 2.5
Predicted score: [1.6011616] True score: 4.0
Predicted score: [1.9094267] True score: 1.0
Predicted score: [1.6322718] True score: 1.0
Predicted score: [1.7284409] True score: 4.0
Predicted score: [1.5262214] True score: 1.5
Out: 0.09090909090909091

As you can see, the predicted values fall within the 1-2 range even though there are test scores at the 2.5, 3, 4, and 5 range.

print(y_pred.min(), y_pred.max())
1.2196972 1.9094267

Finally, here are my graphs:

enter image description here enter image description here

As you can see, the loss decreases beautifully, but the accuracy freezes midway and I am not sure what the cause may be.

Sorry for the long post, but I would appreciate any answers, thank you!

Upvotes: 2

Views: 1320

Answers (1)

Aramakus
Aramakus

Reputation: 1920

There are several things that may help with the accuracy of your model.

  1. Add BatchNomalization after each Conv3D layer, it improves the convergence.
  2. Try scaling scores into a range [0, 1]. This is usually not a problem when you have enough data, but may become a problem if your dataset is small. This will also require a softmax activation in your last layer.
  3. Dropout of 0.5 may be a bit extreme. Try adding a smaller dropout after each Conv3D. It can be conveniently combined with proposition 1) using the following wrapper:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Layer, Conv3D, BatchNormalization, Dropout, InputLayer


class ConvBlock(Layer):
  def __init__(self, filters=32, kernel_size = [3, 3, 3], 
               padding='same', activation='relu', dropout=0.1):
    super(ConvBlock, self).__init__()
    self.conv = Conv3D(filters=filters, 
                       kernel_size=kernel_size, 
                       activation=activation, 
                       padding=padding)
    self.norm = BatchNormalization()
    self.dropout = Dropout(dropout)

  def call(self, input, training=False):
    x = self.conv(input)
    x = self.norm(x)
    x = self.dropout(x, training=training)
    return x
  
  
model = Sequential()

model.add(InputLayer((96, 96, 96, 1)))
model.add(ConvBlock())

model.summary()

Hope this helps to improve it at least a little bit.

Upvotes: 1

Related Questions