-2

I have built a one class anomaly detection deep learning model and I am training it on one class red-blood cell images.

I have calculated a threshold that will be able to differentiate between anomalous and normal images but for some reason it doesn't identify as many abnormal images as I would like.

What are some of the hyperparameters that I can tune to make my model learn well and predict better.

Here is my code.

import tensorflow as tf
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, BatchNormalization, LayerNormalization
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l1

from tensorflow.keras.optimizers import Adam

optimizer = Adam(learning_rate=0.0001)


input_shape = (SIZE, SIZE, 3)

# Encoder
inputs = Input(shape=input_shape)
x = Conv2D(64, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(inputs)
x = MaxPooling2D((2, 2), padding='same')(x)

x = Conv2D(32, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = MaxPooling2D((2, 2), padding='same')(x)

x = Conv2D(16, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
encoded = MaxPooling2D((2, 2), padding='same')(x)

# Decoder
x = Conv2D(16, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(encoded)
x = UpSampling2D((2, 2))(x)

x = Conv2D(32, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = UpSampling2D((2, 2))(x)

x = Conv2D(64, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = UpSampling2D((2, 2))(x)

decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)

# Define the autoencoder model
autoencoder = Model(inputs, decoded)

# Compile the model
autoencoder.compile(optimizer=optimizer, loss='mean_squared_error')

# Print the model summary
autoencoder.summary()

#Fit the model. 
history = autoencoder.fit(
        train_generator,
        steps_per_epoch= 250 // batch_size,
        epochs=1000,
        validation_data=validation_generator,
        validation_steps= 250 // batch_size,
        shuffle = True)

#plot the training and validation loss at each epoch
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, 'y', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

I want to have a low reconstruction error for the normal instances and a high reconstruction error for abnormal instances. Any idea on how I can improve the performance of my model ?

Here is a snapshot of the the loss.

Epoch 1/1000
7/7 [==============================] - 31s 4s/step - loss: 0.1420 - val_loss: 0.1355
Epoch 2/1000
7/7 [==============================] - 30s 4s/step - loss: 0.1302 - val_loss: 0.1266
Epoch 3/1000
7/7 [==============================] - 30s 4s/step - loss: 0.1228 - val_loss: 0.1201
Epoch 4/1000
7/7 [==============================] - 30s 4s/step - loss: 0.1152 - val_loss: 0.1111
Epoch 5/1000
7/7 [==============================] - 31s 5s/step - loss: 0.1028 - val_loss: 0.0920
Epoch 6/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0785 - val_loss: 0.0630
Epoch 7/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0571 - val_loss: 0.0507
Epoch 8/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0470 - val_loss: 0.0430
Epoch 9/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0412 - val_loss: 0.0392
Epoch 10/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0381 - val_loss: 0.0366
Epoch 11/1000
7/7 [==============================] - 30s 5s/step - loss: 0.0361 - val_loss: 0.0347
Epoch 12/1000
7/7 [==============================] - 30s 4s/step - loss: 0.0341 - val_loss: 0.0333
Epoch 13/1000
7/7 [==============================] - 31s 5s/step - loss: 0.0330 - val_loss: 0.0320
  • How does your anomaly scores look like wrt the classes on validation set? Plot the histogram/kde of anomaly scores, colored by class. And indicate the chosen threshold with a vertical line. What are your precision/recall ? And why do mean when you say "it does not identify enough anomalous images" ? – Jon Nordby Mar 25 '23 at 13:17
  • I am trying to see how well it can identify anomalies. I am calculating the recon error threshold by getting the standard deviation of the normal images and applying it to the a set of abnormal images with normal images then getting a confusion matrix but its only able to identify few abnormal images. – Mufasatoday Mar 29 '23 at 14:37
  • The reconstruction error is not gonna be normally distributed. Usually not even symmetrical. So selecting based on standard deviation might not be a good choice. Plot the reconstruction error and set a threshold visually. Or sweep different thresholds to produce a PR/DET curve, and select the one which optimizes your preferred metric – Jon Nordby Mar 30 '23 at 10:02
  • This is what I have been doing. Correct me if I am wrong. The problem I find is not even finding a threshold that works well. The problem is that the threshold that I find is sacrificing more of my normal instances which tells me that my model isn't doing a good job at classifying the images. If the model does a good job then the threshold chosen should always give me more of abnormal images a less of normal images. My sensitivity is at 90% and my specificity is at 71%. – Mufasatoday Mar 30 '23 at 17:20

1 Answers1

0

The encoder architecture should progressively increase the number of filters in each convolution layer, and the decoder architecture should progressively decrease the number of filters in each convolution layer. Otherwise the size of the encoded bottleneck would be too small to retain the information of the original image.

The image pixel values are not binary, so try using a ReLU activation function in the final output layer. Even if you are normalizing your image pixel values to the range 0-1, ReLU will work just fine.

One more thing, your input image has 3 channels, and as your intention is to reconstruct the image, your final output layer should also contain 3 channels, unless you're reconstructing an RGB images to a Black & White image.

This would be an appropriate example:

import tensorflow as tf
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, BatchNormalization, LayerNormalization
from tensorflow.keras.models import Model
from tensorflow.keras.regularizers import l1

from tensorflow.keras.optimizers import Adam

optimizer = Adam(learning_rate=0.0001)


input_shape = (SIZE, SIZE, 3)

# Encoder
inputs = Input(shape=input_shape)
x = Conv2D(16, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(inputs)
x = MaxPooling2D((2, 2), padding='same')(x)

x = Conv2D(32, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = MaxPooling2D((2, 2), padding='same')(x)

x = Conv2D(64, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
encoded = MaxPooling2D((2, 2), padding='same')(x)

# Decoder
x = Conv2D(64, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(encoded)
x = UpSampling2D((2, 2))(x)

x = Conv2D(32, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = UpSampling2D((2, 2))(x)

x = Conv2D(16, (3, 3), activation='relu', padding='same', activity_regularizer=l1(1e-6))(x)
x = UpSampling2D((2, 2))(x)

decoded = Conv2D(3, (3, 3), activation='relu', padding='same')(x)

# Define the autoencoder model
autoencoder = Model(inputs, decoded)

# Compile the model
autoencoder.compile(optimizer=optimizer, loss='mean_squared_error')

# Print the model summary
autoencoder.summary()

#Fit the model. 
history = autoencoder.fit(
        train_generator,
        steps_per_epoch= 250 // batch_size,
        epochs=1000,
        validation_data=validation_generator,
        validation_steps= 250 // batch_size,
        shuffle = True)

#plot the training and validation loss at each epoch
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, 'y', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
MD Mushfirat Mohaimin
  • 1,966
  • 3
  • 10
  • 22
  • This doesn't still give me good results as I expected. – Mufasatoday Mar 29 '23 at 14:37
  • @Mufasatoday Is there any improvement on the loss? What is the lowest `val_loss` you're getting now? – MD Mushfirat Mohaimin Mar 29 '23 at 16:24
  • When the model is set as above yes but not significant improvement that shows that can be evidenced with the model classifying more of abnormal images and not misclassifying normal images as abnormal. The model above seems to misclassify normal images. – Mufasatoday Mar 30 '23 at 17:22