Tensorflow 2.0 Classification of Medical Images (Image Diagnosis of X-ray Chest Pneumonia)

1. Datasets

The dataset is a kaggle chest radiograph dataset, divided into three folders (training, testing, validation) and contains subfolders for each image category (pneumonia/normal).There are 5,863 X-ray images (in JPEG format).All chest X-ray images of the dataset were obtained from patients aged 1 to 5 years at the Guangzhou Women and Children's Medical Center.

Baidu Disk Address:
Links: https://pan.baidu.com/s/1rvev6AWv3yC_5Zn_Kg0k2w
Extraction code: 7591

Picture samples:

1. Normal lungs:

2. Bacterial pneumonia:

3. Viral pneumonia:

From the above three pictures, we can see that the normal person's lung X-ray is very transparent, there are no impurities, the bacterial pneumonia's lung X-ray is not very clear, there is a feeling of being covered by flocculent, the virus pneumonia's lung X-ray is not very clear, the most prominent feature is that there are some spring-like objects on the left.For simplicity, the study was divided into two categories: normal subjects and infected pneumonia (classifying bacterial pneumonia and viral pneumonia into one category).

2. Code

2.1 Import the appropriate library

from tensorflow.keras.callbacks import ReduceLROnPlateau,ModelCheckpoint
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import tensorflow as tf
from PIL import Image
import numpy as np
import itertools
import os

2.2 Initial setup

The width and height of the picture are set to 512, batch_size is set to 32, a total of 10 epoch s are trained. There are also model save paths, training sets, validation sets, test set paths. Because there are fewer pictures in the valid folder of the dataset, pictures in the test folder are used as validation sets and pictures in the valid folder are used as test sets.

im_height = 512 
im_width = 512
batch_size = 32
epochs = 10


if not os.path.exists("save_weights"):
    os.makedirs("save_weights")


image_path = "../input/chest-xray-pneumonia/chest_xray/"
train_dir = image_path + "train"
validation_dir = image_path + "test"
test_dir = image_path + "valid"

2.3 Data Preprocessing

Data enhancement for training set pictures, normalization for validation set and test set only

train_image_generator = ImageDataGenerator( rescale=1./255,
                                            shear_range=0.2,
                                            zoom_range=0.2,                                    
                                            horizontal_flip=True)
validation_image_generator = ImageDataGenerator(rescale=1./255)
test_image_generator = ImageDataGenerator(rescale=1./255)

2.4 Generating data

The training set data is shuffled, and the training set and validation set data are not shuffled, using one-hot encoding mode

train_data_gen = train_image_generator.flow_from_directory(directory=train_dir,
                                                           batch_size=batch_size,
                                                           shuffle=True,
                                                           target_size=(im_height, im_width),
                                                           class_mode='categorical')
    
total_train = train_data_gen.n




val_data_gen = validation_image_generator.flow_from_directory(directory=validation_dir,
                                                              batch_size=batch_size,
                                                              shuffle=False,
                                                              target_size=(im_height, im_width),
                                                              class_mode='categorical')
    
total_val = val_data_gen.n





test_data_gen = test_image_generator.flow_from_directory( directory=test_dir,
                                                          batch_size=batch_size,
                                                          shuffle=False,
                                                          target_size=(im_height, im_width),
                                                          class_mode='categorical')
    
total_test = test_data_gen.n

Result:

Found 5216 images belonging to 2 classes.
Found 624 images belonging to 2 classes.
Found 16 images belonging to 2 classes.

There were 5216 pictures in the training set, 624 pictures in the validation set, and 16 pictures in the test set, totaling 2 categories

2.5 Modeling

This uses tensorflow's built-in enseNet201 model, then freezes the pre-training model to the global average pooling, Dropout, and output layers

covn_base = tf.keras.applications.DenseNet201(weights='imagenet', include_top = False,input_shape=(im_height,im_width,3))
covn_base.trainable = False

model = tf.keras.Sequential()
model.add(covn_base)
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dropout(rate=0.2)) 
model.add(tf.keras.layers.Dense(2, activation='softmax'))
model.summary()   

Result:

Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/densenet/densenet201_weights_tf_dim_ordering_tf_kernels_notop.h5
74842112/74836368 [==============================] - 1s 0us/step
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
densenet201 (Model)          (None, 16, 16, 1920)      18321984  
_________________________________________________________________
flatten (Flatten)            (None, 491520)            0         
_________________________________________________________________
dropout (Dropout)            (None, 491520)            0         
_________________________________________________________________
dense (Dense)                (None, 2)                 983042    
=================================================================
Total params: 19,305,026
Trainable params: 983,042
Non-trainable params: 18,321,984
_________________________________________________________________

You can see that the Densenet201 model has a total of 19,305,026 parameters, but we only need to train 983,042 parameters.

2.6 Compile Model

The adam optimizer is chosen, the initial learning rate is set to 0.0001, and the loss function is the cross-entropy loss function.

model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
    loss = 'categorical_crossentropy',
    metrics=['accuracy']
)

2.7 Start training

checkpoint: Set the model save path and save it according to val_acc saves the optimal model
reduce_lr: Monitor'val_Change in loss, if the learning rate decreases to 1/10 of the original for two invariant rounds

reduce_lr = ReduceLROnPlateau(
                                monitor='val_loss', 
                                factor=0.1, 
                                patience=2, 
                                mode='auto',
                                verbose=1
                             )


checkpoint = ModelCheckpoint(
                                filepath='./save_weights/DenseNet201.ckpt',
                                monitor='val_acc', 
                                save_weights_only=False, 
                                save_best_only=True, 
                                mode='auto',
                                period=1
                            )

history = model.fit(x=train_data_gen,
                    steps_per_epoch=total_train // batch_size,
                    epochs=epochs,
                    validation_data=val_data_gen,
                    validation_steps=total_val // batch_size,
                    callbacks=[checkpoint, reduce_lr])

Result:

Epoch 1/10
163/163 [==============================] - 479s 3s/step - loss: 0.2023 - accuracy: 0.9408 - val_loss: 0.4601 - val_accuracy: 0.9079 - lr: 1.0000e-04
Epoch 2/10
163/163 [==============================] - 447s 3s/step - loss: 0.1367 - accuracy: 0.9638 - val_loss: 0.3181 - val_accuracy: 0.9293 - lr: 1.0000e-04
Epoch 3/10
163/163 [==============================] - 443s 3s/step - loss: 0.0801 - accuracy: 0.9766 - val_loss: 0.5200 - val_accuracy: 0.9243 - lr: 1.0000e-04
Epoch 4/10
163/163 [==============================] - ETA: 0s - loss: 0.0823 - accuracy: 0.9785
Epoch 00004: ReduceLROnPlateau reducing learning rate to 9.999999747378752e-06.
163/163 [==============================] - 441s 3s/step - loss: 0.0823 - accuracy: 0.9785 - val_loss: 0.7227 - val_accuracy: 0.8914 - lr: 1.0000e-04
Epoch 5/10
163/163 [==============================] - 440s 3s/step - loss: 0.0413 - accuracy: 0.9873 - val_loss: 0.3969 - val_accuracy: 0.9260 - lr: 1.0000e-05
Epoch 6/10
163/163 [==============================] - ETA: 0s - loss: 0.0387 - accuracy: 0.9883
Epoch 00006: ReduceLROnPlateau reducing learning rate to 9.999999747378752e-07.
163/163 [==============================] - 450s 3s/step - loss: 0.0387 - accuracy: 0.9883 - val_loss: 0.4794 - val_accuracy: 0.9112 - lr: 1.0000e-05
Epoch 7/10
163/163 [==============================] - 462s 3s/step - loss: 0.0328 - accuracy: 0.9883 - val_loss: 0.4039 - val_accuracy: 0.9260 - lr: 1.0000e-06
Epoch 8/10
163/163 [==============================] - ETA: 0s - loss: 0.0288 - accuracy: 0.9912
Epoch 00008: ReduceLROnPlateau reducing learning rate to 9.999999974752428e-08.
163/163 [==============================] - 466s 3s/step - loss: 0.0288 - accuracy: 0.9912 - val_loss: 0.3938 - val_accuracy: 0.9309 - lr: 1.0000e-06
Epoch 9/10
163/163 [==============================] - 464s 3s/step - loss: 0.0259 - accuracy: 0.9919 - val_loss: 0.3957 - val_accuracy: 0.9309 - lr: 1.0000e-07
Epoch 10/10
163/163 [==============================] - ETA: 0s - loss: 0.0314 - accuracy: 0.9889
Epoch 00010: ReduceLROnPlateau reducing learning rate to 1.0000000116860975e-08.
163/163 [==============================] - 443s 3s/step - loss: 0.0314 - accuracy: 0.9889 - val_loss: 0.3935 - val_accuracy: 0.9309 - lr: 1.0000e-07

From the training process, it can be seen that the convergence rate of training using the migration learning model is very fast, and the accuracy rate reaches 93% after training 10 epoch s.

2.8 Save Model

Save the model in.ckpt format with only the weights

model.save_weights('./save_weights/DenseNet201.ckpt',save_format='tf')

2.9 Draw accuracy and loss curves

history_dict = history.history
train_loss = history_dict["loss"]
train_accuracy = history_dict["accuracy"]
val_loss = history_dict["val_loss"]
val_accuracy = history_dict["val_accuracy"]

#magnitude of the loss
plt.figure()
plt.plot(range(epochs), train_loss, label='train_loss')
plt.plot(range(epochs), val_loss, label='val_loss')
plt.legend()
plt.xlabel('epochs')
plt.ylabel('loss')

# accuracy rate
plt.figure()
plt.plot(range(epochs), train_accuracy, label='train_accuracy')
plt.plot(range(epochs), val_accuracy, label='val_accuracy')
plt.legend()
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.show()

Loss curve

Accuracy Curve

From the effect of training, there are still some over-fitting phenomena

2.10 Evaluation Model

2.10.1 Testing on Test Sets
scores = model.evaluate(test_data_gen, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

Result:

1/1 [==============================] - 0s 1ms/step - loss: 0.0073 - accuracy: 1.0000
Test loss: 0.007296066265553236
Test accuracy: 1.0

The accuracy on the test set is 100%, and the loss value is 0.00729

2.10.2 Draw confusion matrix
def plot_confusion_matrix(cm, target_names,title='Confusion matrix',cmap=None,normalize=False):
    accuracy = np.trace(cm) / float(np.sum(cm)) #Calculating accuracy
    misclass = 1 - accuracy #Calculation error rate
    if cmap is None:
        cmap = plt.get_cmap('Blues') #Set color to blue
    plt.figure(figsize=(10, 8)) #Set window size
    plt.imshow(cm, interpolation='nearest', cmap=cmap) #display picture
    plt.title(title) #show heading
    plt.colorbar() #Draw color bar

    if target_names is not None:
        tick_marks = np.arange(len(target_names))
        plt.xticks(tick_marks, target_names, rotation=45) #x-coordinate label rotated 45 degrees
        plt.yticks(tick_marks, target_names) #y-coordinate

    if normalize:
        cm = cm.astype('float32') / cm.sum(axis=1)
        cm = np.round(cm,2) #Keep two decimal places for numbers
        

    thresh = cm.max() / 1.5 if normalize else cm.max() / 2
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): #takeCm.shape[0],Cm.shapeElements in [1] form tuples, traversing each number in the tuple
        if normalize: #Standardization
            plt.text(j, i, "{:0.2f}".format(cm[i, j]), #Keep two decimal places
                     horizontalalignment="center",  #Number in the middle of the box
                     color="white" if cm[i, j] > thresh else "black")  #Set Font Color
        else:  #Nonstandard
            plt.text(j, i, "{:,}".format(cm[i, j]),
                     horizontalalignment="center",  #Number in the middle of the box
                     color="white" if cm[i, j] > thresh else "black") #Set Font Color

    plt.tight_layout() #Automatically adjust subgraph parameters to fill the entire image area
    plt.ylabel('True label') #Labels in the y direction
    plt.xlabel("Predicted label\naccuracy={:0.4f}\n misclass={:0.4f}".format(accuracy, misclass)) #Labels in x direction
    plt.show() #display picture

labels = ['NORMAL','PNEUMONIA']

# Overall accuracy of prediction validation set data
Y_pred = model.predict_generator(test_data_gen, total_test // batch_size + 1)
# Convert the predicted result to a one hit vector
Y_pred_classes = np.argmax(Y_pred, axis = 1)
# Computing confusion matrix
confusion_mtx = confusion_matrix(y_true = test_data_gen.classes,y_pred = Y_pred_classes)
# Draw confusion matrix
plot_confusion_matrix(confusion_mtx, normalize=True, target_names=labels)


The accuracy of both normal and pneumonia predictions in the test set was 100%, and that of the overall predictions was 100%, indicating that the results were good.

2.11 Test Model

Pick up a test set picture for prediction

#Getting category codes for datasets
class_indices = train_data_gen.class_indices 
#Save codes and corresponding categories in a dictionary
inverse_dict = dict((val, key) for key, val in class_indices.items()) 
#Load Test Picture
img = Image.open("../input/chest-xray-pneumonia/chest_xray/val/NORMAL/NORMAL2-IM-1430-0001.jpeg")
# resize the picture to 224x224 size
img = img.resize((im_width, im_height))
#Convert Grayscale Grayscale to RGB Mode
img = img.convert("RGB")
# normalization
img1 = np.array(img) / 255.
# Add a dimension to the picture to match the network model
img1 = (np.expand_dims(img1, 0))
#Converting predictions to probability values
result = np.squeeze(model.predict(img1))
predict_class = np.argmax(result)
#print(inverse_dict[int(predict_class)],result[predict_class])
#Print the predicted results on the picture
plt.title([inverse_dict[int(predict_class)],result[predict_class]])
#display picture
plt.imshow(img)


The predictions are correct and you're done!

Tags: Spring encoding network

Posted on Wed, 24 Jun 2020 21:33:32 -0400 by Journey44