0%

Classifying Chest X-ray Images with Deep Learning By Using Sagemaker (Notebook)

Deep learning is one of the family members of the machine learning based on artificial neural networks. With the neural networks, images can be classified well.In medical image science deep learning can be used to classify the X-ray images.In this project Chest X-rays are used to classify whether Pneumonia exists. With the model that can classify whether the patient has Pneumonia, doctors can reducetime to read X-ray images. The model will give the possibility of the existence of Pneumonia, which can be used as a auxiliary diagnosis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import matplotlib.pyplot as plt
import torch
from torchvision import datasets,transforms, models
from collections import OrderedDict
import json
import shutil
import numpy as np
import pandas as pd
import time
from torch import nn
from torch import optim
import seaborn as sns
from PIL import Image
from tqdm import tqdm
import glob
import boto3
import sagemaker
from sagemaker.amazon.amazon_estimator import get_image_uri
import os

Introduction

Deep learning is one of the family members of the machine learning based on artificial neural networks. With the neural networks, images can be classified well.In medical image science deep learning can be used to classify the X-ray images.In this project Chest X-rays are used to classify whether Pneumonia exists. With the model that can classify whether the patient has Pneumonia, doctors can reducetime to read X-ray images. The model will give the possibility of the existence of Pneumonia, which can be used as a auxiliary diagnosis.

The problem is to create a model that can classify the chest x-ray image in termsof whether Pneumonia exists. A deep learning method will be used, and AmazonSageMaker will be utilized. A high level method for SageMaker training will bedeployed in building the model

The dataset is retrieved from Kaggle: https://www.kaggle.com/paultimothymooney/chest-xray-pneumonia.

The dataset is organized into 3 folders (train, test, val) and contains subfolders foreach image category (0/1). 0 represents Normal and 1 represents Pneumonia. Thereare 5,863 X-Ray images (JPEG).

The benchmark model can be found in Kaggle. The best model in Kaggle Kernelsright now has recall ratio 0.98 and precision ratio 0.79. My goal is to get the similarresult based on the same test dataset.

Accuracy ratio, recall ratio and precision ratio can be used to evaluate the modelperformance. In this case recall ratio should be focused on since the number ofFalse Negatives should be as low as possible but accuracy ratio should also be considered. Thus the trade off between recall and precision ratio will be considered.

1. Explore the dataset and data preprocessing

1
2
3
4
5
# Load the data
data_dir = 'chest_xray'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'
test_dir = data_dir + '/test'

First we should get the list of the picture names and label the pictures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File route
train_normal = train_dir + '/0'
train_pneumonia = train_dir + '/1'

# Get the list of all the images
train_normal_list = glob.glob(train_normal + '/*.jpeg')
train_pneumonia_list = glob.glob(train_pneumonia + '/*.jpeg')

# label the train data
df1 = pd.DataFrame(train_normal_list)
df1['pneumonia'] = np.zeros(len(df1))
df2 = pd.DataFrame(train_pneumonia_list)
df2['pneumonia'] = np.ones(len(df2))
df_train = df1.append(df2).reset_index(drop = True)
df_train.columns = ('Pic','pneumonia')
df_train = df_train.sample(frac=1, random_state=2020).reset_index(drop=True) # shuffle the dataset
df_train.head()

Pic pneumonia
0 chest_xray/train/1/person1079_bacteria_3019.jpeg 1.0
1 chest_xray/train/1/person586_bacteria_2420.jpeg 1.0
2 chest_xray/train/0/NORMAL2-IM-1008-0001.jpeg 0.0
3 chest_xray/train/1/person1604_virus_2782.jpeg 1.0
4 chest_xray/train/1/person28_bacteria_143.jpeg 1.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Do the same thing to the validation and the test dataset
# File route
valid_normal = valid_dir + '/0'
valid_pneumonia = valid_dir + '/1'

# Get the list of all the images
valid_normal_list = glob.glob(valid_normal + '/*.jpeg')
valid_pneumonia_list = glob.glob(valid_pneumonia + '/*.jpeg')

# label the train data
df1 = pd.DataFrame(valid_normal_list)
df1['pneumonia'] = np.zeros(len(df1))
df2 = pd.DataFrame(valid_pneumonia_list)
df2['pneumonia'] = np.ones(len(df2))
df_valid = df1.append(df2).reset_index(drop = True)
df_valid.columns = ('Pic','pneumonia')
df_valid = df_valid.sample(frac=1, random_state=20).reset_index(drop=True) # shuffle the dataset
df_valid.head()

Pic pneumonia
0 chest_xray/valid/0/IM-0129-0001.jpeg 0.0
1 chest_xray/valid/0/IM-0156-0001.jpeg 0.0
2 chest_xray/valid/0/IM-0127-0001.jpeg 0.0
3 chest_xray/valid/1/person124_virus_238.jpeg 1.0
4 chest_xray/valid/1/person122_virus_229.jpeg 1.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File route
test_normal = test_dir + '/0'
test_pneumonia = test_dir + '/1'

# Get the list of all the images
test_normal_list = glob.glob(test_normal + '/*.jpeg')
test_pneumonia_list = glob.glob(test_pneumonia + '/*.jpeg')

# label the train data
df1 = pd.DataFrame(test_normal_list)
df1['pneumonia'] = np.zeros(len(df1))
df2 = pd.DataFrame(test_pneumonia_list)
df2['pneumonia'] = np.ones(len(df2))
df_test = df1.append(df2).reset_index(drop = True)
df_test.columns = ('Pic','pneumonia')
df_test = df_test.sample(frac=1, random_state=20).reset_index(drop=True) # shuffle the dataset
df_test.head()

Pic pneumonia
0 chest_xray/test/1/person103_bacteria_489.jpeg 1.0
1 chest_xray/test/0/NORMAL2-IM-0030-0001.jpeg 0.0
2 chest_xray/test/1/person83_bacteria_412.jpeg 1.0
3 chest_xray/test/1/person57_virus_113.jpeg 1.0
4 chest_xray/test/0/IM-0017-0001.jpeg 0.0
1
2
3
4
5
6
7
8
# Then we can see the distribution of our train dataset
counts = df_train.pneumonia.value_counts()
sns.barplot(x=counts.index, y= counts.values)
plt.title('The distribution of the dataset')
plt.xlabel('Pneumonia or not')
plt.ylabel('Counts')
plt.text(0,counts.values[1], '%.0f' % counts.values[1], ha='center', va= 'bottom',fontsize=11)
plt.text(1,counts.values[0], '%.0f' % counts.values[0], ha='center', va= 'bottom',fontsize=11)
Text(1, 3861, '3861')

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# It is also a good idea to look some examples of the X-ray pictures.

pic_list = df_train[df_train['pneumonia']==0].head()['Pic']
pic_list = pic_list.append(df_train[df_train['pneumonia']==1].head()['Pic'])
pic_list = pic_list.reset_index(drop=True)

fig, ax = plt.subplots(2,5, figsize=(60,20))
for i in range(1,11):
img = Image.open(pic_list[i-1])
if i <= 5:
ax[0,i-1].imshow(img, cmap='gray')
ax[0,i-1].set_title('Normal',fontsize=40)
else:
ax[1,i-6].imshow(img, cmap='gray')
ax[1,i-6].set_title('Pneumonia',fontsize=40)
plt.xticks([])
plt.yticks([])
fig.suptitle('Example of X-ray pictures', fontsize=80)
plt.show()

png

Next we should preprocessing our dataset in order to fit the requirements of the model. In this case, we should create dictionaries such that each file name corresponds to the correct label.

1
2
3
4
5
6
7
8
9
# create annotations for train 
train_annotations = []
for each in df_train['Pic']:
each = each.strip('chest_xray/train/1/')
each = each.strip('chest_xray/train/0/')
train_annotations.append(each)
train_annotations = pd.DataFrame([train_annotations,df_train['pneumonia']]).T
train_annotations = dict(train_annotations.values.tolist())
len(train_annotations)
5186
1
2
3
4
5
6
7
8
9
# create annotations for validation
valid_annotations = []
for each in df_valid['Pic']:
each = each.strip('chest_xray/valid/1/')
each = each.strip('chest_xray/valid/0/')
valid_annotations.append(each)
valid_annotations = pd.DataFrame([valid_annotations,df_valid['pneumonia']]).T
valid_annotations = dict(valid_annotations.values.tolist())
len(valid_annotations)
46
1
2
3
4
5
6
7
all_annotations = {}

for key, value in train_annotations.items():
all_annotations[key] = value
for key, value in valid_annotations.items():
all_annotations[key] = value
len(all_annotations)
5232
1
2
3
4
5
6
classes = list(all_annotations.values())

classes = list(set(classes))

print(classes)
print('\nNum of classes:', len(classes))
[0.0, 1.0]

Num of classes: 2

We have 5232 training images and 46 validation images with two classes.

2. Upload the training data to S3

1
2
3
4
5
6
# session and role
sess = sagemaker.Session()
role = sagemaker.get_execution_role()
bucket = sess.default_bucket()
container = get_image_uri(boto3.Session().region_name, 'image-classification')
print(container)
825641698319.dkr.ecr.us-east-2.amazonaws.com/image-classification:1

Then we should prepare the dataset in specific folders so that these data can be uploaded to S3

1
2
3
4
5
6
folders = ['train', 'train_lst', 'validation', 'validation_lst']

for folder in folders:
if os.path.isdir(folder):
shutil.rmtree(folder)
os.mkdir(folder)

After four folders are created we should transfer data in each folder and create annotation files with .lst

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import re 
def prepare_data_train(annotations, key='train'):
images = list(annotations.keys())
f = open(os.path.join(key + '_lst', key + '.lst'), 'w')
with tqdm(total=len(images)) as pbar:
for i, image in enumerate(images):
match = re.match(r'person', image)
if match:
shutil.copy(os.path.join('chest_xray/train/1/', image), os.path.join(key, image))
else:
shutil.copy(os.path.join('chest_xray/train/0/', image), os.path.join(key, image))
class_id = classes.index(annotations[image])
f.write('{}\t{}\t{}\n'.format(i, class_id, image))
pbar.update(1)
f.close()

def prepare_data_valid(annotations, key='validation'):
images = list(annotations.keys())
f = open(os.path.join(key + '_lst', key + '.lst'), 'w')
with tqdm(total=len(images)) as pbar:
for i, image in enumerate(images):
match = re.match(r'person', image)
if match:
shutil.copy(os.path.join('chest_xray/valid/1/', image), os.path.join(key, image))
else:
shutil.copy(os.path.join('chest_xray/valid/0/', image), os.path.join(key, image))
class_id = classes.index(annotations[image])
f.write('{}\t{}\t{}\n'.format(i, class_id, image))
pbar.update(1)
f.close()
1
prepare_data_train(train_annotations, 'train')
100%|██████████| 5186/5186 [03:01<00:00, 28.63it/s] 
1
prepare_data_valid(valid_annotations, 'validation')
100%|██████████| 46/46 [00:02<00:00, 22.23it/s]

Finally we can upload our prepared files to the S3.

1
2
3
4
5
6
7
8
9
10
%%time

s3_train_path = sess.upload_data(path='train',bucket=bucket, key_prefix='train')
print('Training images uploaded')
s3_train_lst_path = sess.upload_data(path='train_lst',bucket=bucket, key_prefix='train_lst')
print('Training list uploaded')
s3_validation_path = sess.upload_data(path='validation',bucket=bucket, key_prefix='validation')
print('validation images uploaded')
s3_validation_lst_path = sess.upload_data(path='validation_lst',bucket=bucket, key_prefix='validation_lst')
print('Validation list uploaded')
Training images uploaded
Training list uploaded
validation images uploaded
Validation list uploaded
CPU times: user 36.4 s, sys: 3.31 s, total: 39.7 s
Wall time: 5min 10s

SageMaker Estimator

Then we should set up our model with parameters and hyperparameters.

1
2
3
4
5
6
7
8
9
10
11
12
prefix = 'output'
output_path = 's3://{}/{}'.format(bucket, prefix)
model = sagemaker.estimator.Estimator(
container,
role=role,
train_instance_count=1,
train_instance_type='ml.p2.xlarge',
train_max_run=36000,
input_mode='File',
output_path=output_path,
sagemaker_session=sess
)
1
2
3
4
5
6
7
8
9
10
11
12
13
# Hyperparameters
model.set_hyperparameters(
num_layers=18,
use_pretrained_model=1,
image_shape='3,224,224',
num_classes=2,
mini_batch_size=32,
resize=224,
epochs=10,
learning_rate=0.001,
num_training_samples=5186,
augmentation_type='crop_color_transform'
)
1
2
3
4
5
6
7
8
9
# Data channels
train_data = sagemaker.session.s3_input(s3_train_path, distribution='FullyReplicated',
content_type='application/x-image',s3_data_type='S3Prefix')
validation_data = sagemaker.session.s3_input(s3_validation_path, distribution='FullyReplicated',
content_type='application/x-image',s3_data_type='S3Prefix')
train_lst_data = sagemaker.session.s3_input(s3_train_lst_path, distribution='FullyReplicated',
content_type='application/x-image',s3_data_type='S3Prefix')
validation_lst_data = sagemaker.session.s3_input(s3_validation_lst_path, distribution='FullyReplicated',
content_type='application/x-image',s3_data_type='S3Prefix')
1
2
3
4
5
6
data_channels = {
'train': train_data,
'train_lst': train_lst_data,
'validation': validation_data,
'validation_lst': validation_lst_data
}

Model training

1
model.fit(inputs=data_channels,logs=True)
2020-03-19 21:39:31 Starting - Starting the training job...
2020-03-19 21:39:32 Starting - Launching requested ML instances...
2020-03-19 21:40:27 Starting - Preparing the instances for training...............
2020-03-19 21:42:38 Downloading - Downloading input data......
2020-03-19 21:43:52 Training - Downloading the training image..Docker entrypoint called with argument(s): train
[03/19/2020 21:44:16 INFO 139731408856896] Reading default configuration from /opt/amazon/lib/python2.7/site-packages/image_classification/default-input.json: {u'beta_1': 0.9, u'gamma': 0.9, u'beta_2': 0.999, u'optimizer': u'sgd', u'use_pretrained_model': 0, u'eps': 1e-08, u'epochs': 30, u'lr_scheduler_factor': 0.1, u'num_layers': 152, u'image_shape': u'3,224,224', u'precision_dtype': u'float32', u'mini_batch_size': 32, u'weight_decay': 0.0001, u'learning_rate': 0.1, u'momentum': 0}
[03/19/2020 21:44:16 INFO 139731408856896] Merging with provided configuration from /opt/ml/input/config/hyperparameters.json: {u'learning_rate': u'0.001', u'epochs': u'10', u'use_pretrained_model': u'1', u'augmentation_type': u'crop_color_transform', u'num_classes': u'2', u'num_layers': u'18', u'mini_batch_size': u'32', u'image_shape': u'3,224,224', u'resize': u'224', u'num_training_samples': u'5186'}
[03/19/2020 21:44:16 INFO 139731408856896] Final configuration: {u'optimizer': u'sgd', u'learning_rate': u'0.001', u'epochs': u'10', u'resize': u'224', u'lr_scheduler_factor': 0.1, u'num_layers': u'18', u'num_classes': u'2', u'precision_dtype': u'float32', u'mini_batch_size': u'32', u'augmentation_type': u'crop_color_transform', u'beta_1': 0.9, u'beta_2': 0.999, u'use_pretrained_model': u'1', u'eps': 1e-08, u'weight_decay': 0.0001, u'momentum': 0, u'image_shape': u'3,224,224', u'gamma': 0.9, u'num_training_samples': u'5186'}
[03/19/2020 21:44:16 INFO 139731408856896] Searching for .lst files in /opt/ml/input/data/train_lst.
[03/19/2020 21:44:16 INFO 139731408856896] Creating record files for train.lst

2020-03-19 21:44:13 Training - Training image download completed. Training in progress.[03/19/2020 21:45:18 INFO 139731408856896] Done creating record files...
[03/19/2020 21:45:18 INFO 139731408856896] Searching for .lst files in /opt/ml/input/data/validation_lst.
[03/19/2020 21:45:18 INFO 139731408856896] Creating record files for validation.lst
[03/19/2020 21:45:18 INFO 139731408856896] Done creating record files...
[03/19/2020 21:45:18 INFO 139731408856896] use_pretrained_model: 1
[03/19/2020 21:45:18 INFO 139731408856896] multi_label: 0
[03/19/2020 21:45:18 INFO 139731408856896] Using pretrained model for initializing weights and transfer learning.
[03/19/2020 21:45:18 INFO 139731408856896] ---- Parameters ----
[03/19/2020 21:45:18 INFO 139731408856896] num_layers: 18
[03/19/2020 21:45:18 INFO 139731408856896] data type: <type 'numpy.float32'>
[03/19/2020 21:45:18 INFO 139731408856896] epochs: 10
[03/19/2020 21:45:18 INFO 139731408856896] image resize size: 224
[03/19/2020 21:45:18 INFO 139731408856896] optimizer: sgd
[03/19/2020 21:45:18 INFO 139731408856896] momentum: 0.9
[03/19/2020 21:45:18 INFO 139731408856896] weight_decay: 0.0001
[03/19/2020 21:45:18 INFO 139731408856896] learning_rate: 0.001
[03/19/2020 21:45:18 INFO 139731408856896] num_training_samples: 5186
[03/19/2020 21:45:18 INFO 139731408856896] mini_batch_size: 32
[03/19/2020 21:45:18 INFO 139731408856896] image_shape: 3,224,224
[03/19/2020 21:45:18 INFO 139731408856896] num_classes: 2
[03/19/2020 21:45:18 INFO 139731408856896] augmentation_type: crop_color_transform
[03/19/2020 21:45:18 INFO 139731408856896] kv_store: device
[03/19/2020 21:45:18 INFO 139731408856896] checkpoint_frequency not set, will store the best model
[03/19/2020 21:45:18 INFO 139731408856896] --------------------
[21:45:18] /opt/brazil-pkg-cache/packages/AIAlgorithmsMXNet/AIAlgorithmsMXNet-1.3.x_ecl_Cuda_10.1.x.2564.0/AL2012/generic-flavor/src/src/nnvm/legacy_json_util.cc:209: Loading symbol saved by previous version v0.8.0. Attempting to upgrade...
[21:45:18] /opt/brazil-pkg-cache/packages/AIAlgorithmsMXNet/AIAlgorithmsMXNet-1.3.x_ecl_Cuda_10.1.x.2564.0/AL2012/generic-flavor/src/src/nnvm/legacy_json_util.cc:217: Symbol successfully upgraded!
[03/19/2020 21:45:19 INFO 139731408856896] Setting number of threads: 3
[21:45:27] /opt/brazil-pkg-cache/packages/AIAlgorithmsMXNet/AIAlgorithmsMXNet-1.3.x_ecl_Cuda_10.1.x.2564.0/AL2012/generic-flavor/src/src/operator/nn/./cudnn/./cudnn_algoreg-inl.h:97: Running performance tests to find the best convolution algorithm, this can take a while... (setting env variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable)
[03/19/2020 21:45:33 INFO 139731408856896] Epoch[0] Batch [20]#011Speed: 87.326 samples/sec#011accuracy=0.787202
[03/19/2020 21:45:37 INFO 139731408856896] Epoch[0] Batch [40]#011Speed: 113.065 samples/sec#011accuracy=0.844512
[03/19/2020 21:45:41 INFO 139731408856896] Epoch[0] Batch [60]#011Speed: 125.234 samples/sec#011accuracy=0.877561
[03/19/2020 21:45:45 INFO 139731408856896] Epoch[0] Batch [80]#011Speed: 132.430 samples/sec#011accuracy=0.889660
[03/19/2020 21:45:49 INFO 139731408856896] Epoch[0] Batch [100]#011Speed: 137.065 samples/sec#011accuracy=0.902228
[03/19/2020 21:45:53 INFO 139731408856896] Epoch[0] Batch [120]#011Speed: 140.315 samples/sec#011accuracy=0.912448
[03/19/2020 21:45:57 INFO 139731408856896] Epoch[0] Batch [140]#011Speed: 142.653 samples/sec#011accuracy=0.918883
[03/19/2020 21:46:01 INFO 139731408856896] Epoch[0] Batch [160]#011Speed: 144.469 samples/sec#011accuracy=0.921196
[03/19/2020 21:46:02 INFO 139731408856896] Epoch[0] Train-accuracy=0.921682
[03/19/2020 21:46:02 INFO 139731408856896] Epoch[0] Time cost=35.642
[03/19/2020 21:46:02 INFO 139731408856896] Epoch[0] Validation-accuracy=1.000000
[03/19/2020 21:46:02 INFO 139731408856896] Storing the best model with validation accuracy: 1.000000
[03/19/2020 21:46:02 INFO 139731408856896] Saved checkpoint to "/opt/ml/model/image-classification-0001.params"
[03/19/2020 21:46:06 INFO 139731408856896] Epoch[1] Batch [20]#011Speed: 154.748 samples/sec#011accuracy=0.944940
[03/19/2020 21:46:10 INFO 139731408856896] Epoch[1] Batch [40]#011Speed: 156.555 samples/sec#011accuracy=0.947409
[03/19/2020 21:46:14 INFO 139731408856896] Epoch[1] Batch [60]#011Speed: 157.096 samples/sec#011accuracy=0.953381
[03/19/2020 21:46:18 INFO 139731408856896] Epoch[1] Batch [80]#011Speed: 157.231 samples/sec#011accuracy=0.960262
[03/19/2020 21:46:22 INFO 139731408856896] Epoch[1] Batch [100]#011Speed: 157.317 samples/sec#011accuracy=0.954517
[03/19/2020 21:46:26 INFO 139731408856896] Epoch[1] Batch [120]#011Speed: 157.328 samples/sec#011accuracy=0.958161
[03/19/2020 21:46:31 INFO 139731408856896] Epoch[1] Batch [140]#011Speed: 157.330 samples/sec#011accuracy=0.959663
[03/19/2020 21:46:35 INFO 139731408856896] Epoch[1] Batch [160]#011Speed: 157.375 samples/sec#011accuracy=0.960792
[03/19/2020 21:46:35 INFO 139731408856896] Epoch[1] Train-accuracy=0.960648
[03/19/2020 21:46:35 INFO 139731408856896] Epoch[1] Time cost=32.738
[03/19/2020 21:46:35 INFO 139731408856896] Epoch[1] Validation-accuracy=0.968750
[03/19/2020 21:46:39 INFO 139731408856896] Epoch[2] Batch [20]#011Speed: 154.851 samples/sec#011accuracy=0.968750
[03/19/2020 21:46:43 INFO 139731408856896] Epoch[2] Batch [40]#011Speed: 156.146 samples/sec#011accuracy=0.963415
[03/19/2020 21:46:48 INFO 139731408856896] Epoch[2] Batch [60]#011Speed: 156.423 samples/sec#011accuracy=0.969262
[03/19/2020 21:46:52 INFO 139731408856896] Epoch[2] Batch [80]#011Speed: 156.532 samples/sec#011accuracy=0.968750
[03/19/2020 21:46:56 INFO 139731408856896] Epoch[2] Batch [100]#011Speed: 156.637 samples/sec#011accuracy=0.968750
[03/19/2020 21:47:00 INFO 139731408856896] Epoch[2] Batch [120]#011Speed: 156.685 samples/sec#011accuracy=0.968750
[03/19/2020 21:47:04 INFO 139731408856896] Epoch[2] Batch [140]#011Speed: 156.680 samples/sec#011accuracy=0.968972
[03/19/2020 21:47:08 INFO 139731408856896] Epoch[2] Batch [160]#011Speed: 156.667 samples/sec#011accuracy=0.968750
[03/19/2020 21:47:08 INFO 139731408856896] Epoch[2] Train-accuracy=0.968943
[03/19/2020 21:47:08 INFO 139731408856896] Epoch[2] Time cost=32.884
[03/19/2020 21:47:08 INFO 139731408856896] Epoch[2] Validation-accuracy=1.000000
[03/19/2020 21:47:13 INFO 139731408856896] Epoch[3] Batch [20]#011Speed: 153.472 samples/sec#011accuracy=0.964286
[03/19/2020 21:47:17 INFO 139731408856896] Epoch[3] Batch [40]#011Speed: 155.076 samples/sec#011accuracy=0.971037
[03/19/2020 21:47:21 INFO 139731408856896] Epoch[3] Batch [60]#011Speed: 155.574 samples/sec#011accuracy=0.973361
[03/19/2020 21:47:25 INFO 139731408856896] Epoch[3] Batch [80]#011Speed: 155.840 samples/sec#011accuracy=0.966435
[03/19/2020 21:47:29 INFO 139731408856896] Epoch[3] Batch [100]#011Speed: 155.884 samples/sec#011accuracy=0.966584
[03/19/2020 21:47:33 INFO 139731408856896] Epoch[3] Batch [120]#011Speed: 155.888 samples/sec#011accuracy=0.967717
[03/19/2020 21:47:37 INFO 139731408856896] Epoch[3] Batch [140]#011Speed: 155.917 samples/sec#011accuracy=0.969193
[03/19/2020 21:47:41 INFO 139731408856896] Epoch[3] Batch [160]#011Speed: 156.010 samples/sec#011accuracy=0.970885
[03/19/2020 21:47:42 INFO 139731408856896] Epoch[3] Train-accuracy=0.970872
[03/19/2020 21:47:42 INFO 139731408856896] Epoch[3] Time cost=33.022
[03/19/2020 21:47:42 INFO 139731408856896] Epoch[3] Validation-accuracy=0.968750
[03/19/2020 21:47:46 INFO 139731408856896] Epoch[4] Batch [20]#011Speed: 153.395 samples/sec#011accuracy=0.979167
[03/19/2020 21:47:50 INFO 139731408856896] Epoch[4] Batch [40]#011Speed: 154.704 samples/sec#011accuracy=0.977896
[03/19/2020 21:47:55 INFO 139731408856896] Epoch[4] Batch [60]#011Speed: 155.226 samples/sec#011accuracy=0.979508
[03/19/2020 21:47:59 INFO 139731408856896] Epoch[4] Batch [80]#011Speed: 155.545 samples/sec#011accuracy=0.978395
[03/19/2020 21:48:03 INFO 139731408856896] Epoch[4] Batch [100]#011Speed: 155.689 samples/sec#011accuracy=0.979579
[03/19/2020 21:48:07 INFO 139731408856896] Epoch[4] Batch [120]#011Speed: 155.736 samples/sec#011accuracy=0.978822
[03/19/2020 21:48:11 INFO 139731408856896] Epoch[4] Batch [140]#011Speed: 155.788 samples/sec#011accuracy=0.980496
[03/19/2020 21:48:15 INFO 139731408856896] Epoch[4] Batch [160]#011Speed: 155.848 samples/sec#011accuracy=0.979425
[03/19/2020 21:48:15 INFO 139731408856896] Epoch[4] Train-accuracy=0.979360
[03/19/2020 21:48:15 INFO 139731408856896] Epoch[4] Time cost=33.056
[03/19/2020 21:48:15 INFO 139731408856896] Epoch[4] Validation-accuracy=1.000000
[03/19/2020 21:48:20 INFO 139731408856896] Epoch[5] Batch [20]#011Speed: 152.767 samples/sec#011accuracy=0.973214
[03/19/2020 21:48:24 INFO 139731408856896] Epoch[5] Batch [40]#011Speed: 154.224 samples/sec#011accuracy=0.972561
[03/19/2020 21:48:28 INFO 139731408856896] Epoch[5] Batch [60]#011Speed: 154.832 samples/sec#011accuracy=0.976434
[03/19/2020 21:48:32 INFO 139731408856896] Epoch[5] Batch [80]#011Speed: 155.123 samples/sec#011accuracy=0.974923
[03/19/2020 21:48:36 INFO 139731408856896] Epoch[5] Batch [100]#011Speed: 155.217 samples/sec#011accuracy=0.974319
[03/19/2020 21:48:40 INFO 139731408856896] Epoch[5] Batch [120]#011Speed: 155.293 samples/sec#011accuracy=0.975465
[03/19/2020 21:48:44 INFO 139731408856896] Epoch[5] Batch [140]#011Speed: 155.389 samples/sec#011accuracy=0.976285
[03/19/2020 21:48:49 INFO 139731408856896] Epoch[5] Batch [160]#011Speed: 155.443 samples/sec#011accuracy=0.975543
[03/19/2020 21:48:49 INFO 139731408856896] Epoch[5] Train-accuracy=0.975502
[03/19/2020 21:48:49 INFO 139731408856896] Epoch[5] Time cost=33.144
[03/19/2020 21:48:49 INFO 139731408856896] Epoch[5] Validation-accuracy=0.968750
[03/19/2020 21:48:53 INFO 139731408856896] Epoch[6] Batch [20]#011Speed: 152.969 samples/sec#011accuracy=0.973214
[03/19/2020 21:48:58 INFO 139731408856896] Epoch[6] Batch [40]#011Speed: 154.372 samples/sec#011accuracy=0.976372
[03/19/2020 21:49:02 INFO 139731408856896] Epoch[6] Batch [60]#011Speed: 154.792 samples/sec#011accuracy=0.973361
[03/19/2020 21:49:06 INFO 139731408856896] Epoch[6] Batch [80]#011Speed: 155.024 samples/sec#011accuracy=0.975694
[03/19/2020 21:49:10 INFO 139731408856896] Epoch[6] Batch [100]#011Speed: 155.127 samples/sec#011accuracy=0.975557
[03/19/2020 21:49:14 INFO 139731408856896] Epoch[6] Batch [120]#011Speed: 155.212 samples/sec#011accuracy=0.977531
[03/19/2020 21:49:18 INFO 139731408856896] Epoch[6] Batch [140]#011Speed: 155.250 samples/sec#011accuracy=0.977615
[03/19/2020 21:49:22 INFO 139731408856896] Epoch[6] Batch [160]#011Speed: 155.251 samples/sec#011accuracy=0.977873
[03/19/2020 21:49:22 INFO 139731408856896] Epoch[6] Train-accuracy=0.978009
[03/19/2020 21:49:22 INFO 139731408856896] Epoch[6] Time cost=33.185
[03/19/2020 21:49:23 INFO 139731408856896] Epoch[6] Validation-accuracy=1.000000
[03/19/2020 21:49:27 INFO 139731408856896] Epoch[7] Batch [20]#011Speed: 152.235 samples/sec#011accuracy=0.979167
[03/19/2020 21:49:31 INFO 139731408856896] Epoch[7] Batch [40]#011Speed: 153.754 samples/sec#011accuracy=0.980183
[03/19/2020 21:49:35 INFO 139731408856896] Epoch[7] Batch [60]#011Speed: 154.330 samples/sec#011accuracy=0.983094
[03/19/2020 21:49:39 INFO 139731408856896] Epoch[7] Batch [80]#011Speed: 154.580 samples/sec#011accuracy=0.983410
[03/19/2020 21:49:44 INFO 139731408856896] Epoch[7] Batch [100]#011Speed: 154.669 samples/sec#011accuracy=0.980507
[03/19/2020 21:49:48 INFO 139731408856896] Epoch[7] Batch [120]#011Speed: 154.828 samples/sec#011accuracy=0.979081
[03/19/2020 21:49:52 INFO 139731408856896] Epoch[7] Batch [140]#011Speed: 154.877 samples/sec#011accuracy=0.980053
[03/19/2020 21:49:56 INFO 139731408856896] Epoch[7] Batch [160]#011Speed: 154.953 samples/sec#011accuracy=0.980590
[03/19/2020 21:49:56 INFO 139731408856896] Epoch[7] Train-accuracy=0.980710
[03/19/2020 21:49:56 INFO 139731408856896] Epoch[7] Time cost=33.247
[03/19/2020 21:49:56 INFO 139731408856896] Epoch[7] Validation-accuracy=0.968750
[03/19/2020 21:50:01 INFO 139731408856896] Epoch[8] Batch [20]#011Speed: 152.881 samples/sec#011accuracy=0.985119
[03/19/2020 21:50:05 INFO 139731408856896] Epoch[8] Batch [40]#011Speed: 154.101 samples/sec#011accuracy=0.986280
[03/19/2020 21:50:09 INFO 139731408856896] Epoch[8] Batch [60]#011Speed: 154.410 samples/sec#011accuracy=0.985143
[03/19/2020 21:50:13 INFO 139731408856896] Epoch[8] Batch [80]#011Speed: 154.530 samples/sec#011accuracy=0.983796
[03/19/2020 21:50:17 INFO 139731408856896] Epoch[8] Batch [100]#011Speed: 154.613 samples/sec#011accuracy=0.983911
[03/19/2020 21:50:21 INFO 139731408856896] Epoch[8] Batch [120]#011Speed: 154.723 samples/sec#011accuracy=0.983471
[03/19/2020 21:50:26 INFO 139731408856896] Epoch[8] Batch [140]#011Speed: 154.797 samples/sec#011accuracy=0.983156
[03/19/2020 21:50:30 INFO 139731408856896] Epoch[8] Batch [160]#011Speed: 154.821 samples/sec#011accuracy=0.983307
[03/19/2020 21:50:30 INFO 139731408856896] Epoch[8] Train-accuracy=0.983410
[03/19/2020 21:50:30 INFO 139731408856896] Epoch[8] Time cost=33.277
[03/19/2020 21:50:30 INFO 139731408856896] Epoch[8] Validation-accuracy=0.968750
[03/19/2020 21:50:35 INFO 139731408856896] Epoch[9] Batch [20]#011Speed: 152.028 samples/sec#011accuracy=0.980655
[03/19/2020 21:50:39 INFO 139731408856896] Epoch[9] Batch [40]#011Speed: 153.382 samples/sec#011accuracy=0.977896
[03/19/2020 21:50:43 INFO 139731408856896] Epoch[9] Batch [60]#011Speed: 154.004 samples/sec#011accuracy=0.983094
[03/19/2020 21:50:47 INFO 139731408856896] Epoch[9] Batch [80]#011Speed: 154.290 samples/sec#011accuracy=0.984954
[03/19/2020 21:50:51 INFO 139731408856896] Epoch[9] Batch [100]#011Speed: 154.418 samples/sec#011accuracy=0.984839
[03/19/2020 21:50:55 INFO 139731408856896] Epoch[9] Batch [120]#011Speed: 154.560 samples/sec#011accuracy=0.985279
[03/19/2020 21:50:59 INFO 139731408856896] Epoch[9] Batch [140]#011Speed: 154.655 samples/sec#011accuracy=0.985594
[03/19/2020 21:51:03 INFO 139731408856896] Epoch[9] Batch [160]#011Speed: 154.721 samples/sec#011accuracy=0.986219
[03/19/2020 21:51:04 INFO 139731408856896] Epoch[9] Train-accuracy=0.986111
[03/19/2020 21:51:04 INFO 139731408856896] Epoch[9] Time cost=33.299
[03/19/2020 21:51:04 INFO 139731408856896] Epoch[9] Validation-accuracy=1.000000

2020-03-19 21:51:19 Uploading - Uploading generated training model
2020-03-19 21:51:19 Completed - Training job completed
Training seconds: 521
Billable seconds: 521

Deploy the model

1
2
deployed_model = model.deploy(initial_instance_count=1,instance_type='ml.t2.medium')
print('\nModel deployed!')
-----------------!
Model deployed!

Predictions

1
2
3
image_dir = 'chest_xray/test/0'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
print(len(images))
234
1
deployed_model.content_type = 'image/jpeg'
1
2
3
4
5
6
7
8
9
index = 233

image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())

result = deployed_model.predict(b)
result = json.loads(result)
print(result)
[0.9960546493530273, 0.003945335745811462]
1
2
3
4
5
6
7
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
langs = ['0','1']
ax.bar(langs,result)
plt.text(0,result[0], '%f' % result[0], ha='center', va= 'bottom',fontsize=11)
plt.text(1,result[1], '%f' % result[1], ha='center', va= 'bottom',fontsize=11)
plt.show()

png

The result above returns a possibility distribution of zero and one we can simply choose the highest one as the predicted result.

1
classes[np.argmax(result)]
0.0
1
2
3
plt.imshow(plt.imread(image_path), cmap='gray')
plt.xlabel(images[index])
plt.show()

png

1
2
3
4
# Also try a pneumonia
image_dir = 'chest_xray/test/1'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
print(len(images))
390
1
2
3
4
5
6
7
8
9
deployed_model.content_type = 'image/jpeg'
index = 1
image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())

result = deployed_model.predict(b)
result = json.loads(result)
print(result)
[0.023419998586177826, 0.9765799641609192]
1
2
3
4
5
6
7
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
langs = ['0','1']
ax.bar(langs,result)
plt.text(0,result[0], '%f' % result[0], ha='center', va= 'bottom',fontsize=11)
plt.text(1,result[1], '%f' % result[1], ha='center', va= 'bottom',fontsize=11)
plt.show()

png

1
classes[np.argmax(result)]
1.0
1
2
3
plt.imshow(plt.imread(image_path), cmap='gray')
plt.xlabel(images[index])
plt.show()

png

The estimates of those examples are correct but we still need to use the remainder test set to calculate the recall and accuracy ratios in order to test our model performence.

Assess the performance

1
2
3
4
5
6
7
8
9
10
11
12
13
# All should be zeros, which means normal
image_dir = 'chest_xray/test/0'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
deployed_model.content_type = 'image/jpeg'
normals = []
for i in range(0,233):
index = i
image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())
result = deployed_model.predict(b)
result = json.loads(result)
normals.append(classes[np.argmax(result)])
1
2
false_pos = sum(normals) 
true_neg = len(normals) - false_pos
1
2
3
4
5
6
7
8
9
10
11
12
13
# All should be ones, which means pneumonia
image_dir = 'chest_xray/test/1'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
deployed_model.content_type = 'image/jpeg'
pneumonia = []
for i in range(0,389):
index = i
image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())
result = deployed_model.predict(b)
result = json.loads(result)
pneumonia.append(classes[np.argmax(result)])
1
2
true_pos = sum(pneumonia) 
false_neg = len(pneumonia) - true_pos
1
2
3
4
df = pd.DataFrame({'predict 0': [true_neg, false_neg],
'predict 1': [false_pos, true_pos],},
index=['true 0','true 1'])
df

predict 0 predict 1
true 0 216.0 17.0
true 1 64.0 325.0
1
2
3
4
5
6
7
# ratios
accuracy = (true_pos+true_neg)/(true_pos+true_neg+false_pos+false_neg)
recall = true_pos/(true_pos+false_neg)
precision = true_pos/(true_pos+false_pos)
print("{:<11} {:.3f}".format('Accuracy:', accuracy))
print("{:<11} {:.3f}".format('Recall:', recall))
print("{:<11} {:.3f}".format('Precision:', precision))
Accuracy:   0.870
Recall:     0.835
Precision:  0.950

In this task, recall ratio should be as higher as possible. Based on our model predictions, the recall ratio is nearly 83.5%, which is pretty good. However, if the model will be used in reality the recall ratio should be improved such that number of the miss diagnosis will decrease. Because miss diagnosis will do harm to the patients but false postive can be double checked by the doctor. Next we should improve the recall ratio based on the cutoff values.

Improve the performance of the model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cutoff = 0.1 # set the cutoff value
# All should be zeros, which means normal
image_dir = 'chest_xray/test/0'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
deployed_model.content_type = 'image/jpeg'
normals = []
for i in range(0,233):
index = i
image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())
result = deployed_model.predict(b)
result = json.loads(result)
if result[1] > cutoff:
normals.append(1)
else:
normals.append(0)
false_pos = sum(normals)
true_neg = len(normals) - false_pos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# All should be ones, which means pneumonia
image_dir = 'chest_xray/test/1'
images = [x for x in os.listdir(image_dir) if x[-4:] == 'jpeg']
deployed_model.content_type = 'image/jpeg'
pneumonia = []
for i in range(0,389):
index = i
image_path = os.path.join(image_dir, images[index])
with open(image_path, 'rb') as f:
b = bytearray(f.read())
result = deployed_model.predict(b)
result = json.loads(result)
if result[1] > cutoff:
pneumonia.append(1)
else:
pneumonia.append(0)
true_pos = sum(pneumonia)
false_neg = len(pneumonia) - true_pos
1
2
3
4
df = pd.DataFrame({'predict 0': [true_neg, false_neg],
'predict 1': [false_pos, true_pos],},
index=['true 0','true 1'])
df

predict 0 predict 1
true 0 182 51
true 1 12 377
1
2
3
4
5
6
7
# ratios
accuracy = (true_pos+true_neg)/(true_pos+true_neg+false_pos+false_neg)
recall = true_pos/(true_pos+false_neg)
precision = true_pos/(true_pos+false_pos)
print("{:<11} {:.3f}".format('Accuracy:', accuracy))
print("{:<11} {:.3f}".format('Recall:', recall))
print("{:<11} {:.3f}".format('Precision:', precision))
Accuracy:   0.899
Recall:     0.969
Precision:  0.881

When we set the cutoff value equals 0.2, the recall ratio improves to nearly 96.9%. What is worth to mention is that the precision ratio only decreased a little bit to 88.1%. Therefore, the overall accuracy ratio improves. It is good to choose a relatively lower cutoff value. The usual cutoff value is 0.5 but in this diagnostic problem we should choose relatively lower cutoff value in order to decrease the number of false negatives.

Compared with benchmark model in Kaggle, we have relatively simliar results.

The limitation in this analysis will be the small dataset. This dataset only has nearly 6000 pictures, which is clearly not enough to get an elaborate model. However, the methods and ideas used in this analysis is useful for further analysis when we have more data.

Delete the endpoint

1
sagemaker.Session().delete_endpoint(deployed_model.endpoint)
1
2