Detecting Pneumonia Using CNNs In TensorFlow

Ashley <3
Artificial Intelligence in Plain English
7 min readDec 22, 2020

--

Image from: Cell

It’s insane how you can detect pneumonia in images using deep learning models, saving an individual’s life. According to the World Health Organization, pneumonia kills around 2 million children under the age of 5 years and is repeatedly estimated as the single leading cause of child mortality. This means that pneumonia kills more children than HIV, malaria, and measles combined.

Additionally, the World Health Organization states that 95 % of childhood pneumonia occurs in developing countries, more specifically in the regions of Southeast Asia and Africa. What causes pneumonia? Bacterial and viral pathogens are the leading causes.

Bacterial and viral pneumonia are treated in different manners. In terms of bacterial pneumonia, urgent care is necessary along with antibiotic treatment, in contrast to viral pneumonia which is treated through supportive care.

Thus, accurate and prompt diagnosis of pneumonia is important, and by building a CNN, we are able to analyze chest x-rays to detect pneumonia.

Dataset Information

This particular dataset is from cell.com, and you can also access the dataset from Kaggle.

Dataset features:

  • 5,232 chest x-ray images from children
  • 2,583 are bacterial
  • 1, 345 are viral
  • 1,349 are normal
  • A total of 5, 857 patients

We can use a convolutional neural network to feed our data through and predict which chest x-rays have pneumonia and which don’t. In this article, I’m going to walk you through how to make this in Keras, TensorFlow.

If you don’t already know what CNNs are or need a review of the concept, check out this article I wrote:

Building the Model

Let’s start off with our imports:

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Dropout, Conv2D, MaxPooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt

For this particular CNN, we will be using Keras TensorFlow.

From Keras, we’ll need to import the Sequential model.

For the layers of our model, we’ll need to import: Flatten, Dropout, Conv2D, and MaxPooling2D.

In this particular model, we will need to use an ImageDataGenerator which we will import from Keras’ preprocessing.

Lastly, we will need matplotlib for our visualization of the data.

After our imports, we will initialize our directories for this project.

train_path = '/Users/ashleyc/Deeplearning/chest_xray/train'
test_path = '/Users/ashleyc/Deeplearning/chest_xray/test'
valid_path = '/Users/ashleyc/Deeplearning/chest_xray/val'

As you can see, we’ve stored our training, testing, and validation path’s in their own variables. We need to do this for our ImageDataGenorator which is in the next step.

BATCH_SIZE = 10

Additionally, we’ll create a batch size variable to store our batch size number. In your ImageDataGenorator, you could include the number in the batch_size parameter, rather than creating a variable for this, however, I prefer creating a variable for readability, and so I don’t have to search through my neural network if I wanted to change the number.

train_batches = ImageDataGenerator(
preprocessing_function=tf.keras.applications.vgg16.preprocess_input, rescale=1/255.
).flow_from_directory(
directory=train_path,
target_size=(224, 224),
classes=['NORMAL', 'PNEUMONIA'],
batch_size=BATCH_SIZE,
class_mode='binary'
)

test_batches = ImageDataGenerator(
preprocessing_function=tf.keras.applications.vgg16.preprocess_input, rescale=1/255.
).flow_from_directory(
directory=test_path,
target_size=(224, 224),
classes=['NORMAL', 'PNEUMONIA'],
batch_size=BATCH_SIZE,
class_mode='binary',
shuffle=False
)

valid_batches = ImageDataGenerator(
preprocessing_function=tf.keras.applications.vgg16.preprocess_input, rescale=1/255.
).flow_from_directory(
directory=valid_path,
target_size=(224, 224),
classes=['NORMAL', 'PNEUMONIA'],
batch_size=BATCH_SIZE,
class_mode='binary'
)

A lot is going on here, but don’t feel overwhelmed. Essentially we are creating ImageDataGenerators for our training, testing, and validation paths. To simplify things, we use ImageDataGenerators to prepare and process our images for our CNN.

All the batches have the same ImageDataGenerators, with the exception of each batch having its own directory, and the testing batch shuffles being set to false (we never want to shuffle our data in the testing stages).

Due to the similarity of the generators, I’ll break down what is happening in the train_batches ImageDataGenerator.

train_batches = ImageDataGenerator(
preprocessing_function=tf.keras.applications.vgg16.preprocess_input, rescale=1/255.
).flow_from_directory(
directory=train_path,
target_size=(224, 224),
classes=['NORMAL', 'PNEUMONIA'],
batch_size=BATCH_SIZE,
class_mode='binary'
)

The first step of our ImageDataGenerator includes applying a preprocessing_function, which will be: tf.keras.applications.vgg16.preprocess_input.

The vgg16 is a transfer learning model. Essentially transfer learning can be used in machine learning, and what transfer learning does is reuse a pre-trained model on a new problem. In all essence, the model exploits its knowledge from a prior task to improve generalization on a future task.

Next, we will need to rescale. If you don’t rescale your images could look like this (this actually happened when I didn’t rescale the image):

I accidentally burned the lungs, please rescale your image to avoid this.

We rescale to 1/255. to transform every pixel in the range 0,255 to 0,1. Rescaling will treat all the images in the same matter since images will have varying pixel ranges.

Next, we will call the flow_from_directory() method on our ImageDataGenerator. Flow_from_directory() takes a directory path and generates batches of augmented data.

Within this method we will do the following:

directory=train_path,
target_size=(224, 224),
classes=['NORMAL', 'PNEUMONIA'],
batch_size=BATCH_SIZE,
class_mode='binary',

Firstly, we must initialize our directory, which is our train_path.

Next, we will target_size, resize our images to have the same dimensions.

Additionally, we will declare our classes for the model, which include NORMAL and PNEUMONIA.

Then we will set our batch_size to our BATCH_SIZE variable that we defined earlier. Our batch size determines the number of samples that will be fed through our network.

Lastly, our class_mode will be set to binary, as a result of the program only having 2 classes.

Before we make our model, we will want to do some data visualization on our augmented data.

images, labels = next(train_batches)
def plot(img):
fig, axes = plt.subplots(1, 10, figsize=(20,20))
axes = axes.flatten()
for imgs, ax, in zip(img, axes):
ax.imshow(imgs)
ax.axis('off')
plt.tight_layout()
plt.show()
plot(images)
print(labels)

Running our visualization will give us these images of the chest x-rays.

They are a little tiny, so let's take a closer look at the normal vs pneumonia x-rays.

If a chest x-ray is normal, it should look like this:

If a chest x-ray has pneumonia, it should look something similar to this:

Now it is time to build our CNN.

model = Sequential()
model.add(Conv2D(32, (3, 3),activation=('relu'),input_shape=(224,224,3)))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3),activation=('relu')))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3),activation=('relu')))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))

model.add(Flatten())
model.add(Dense(64,activation=('relu')))
model.add(Dropout(0.2))
model.add(Dense(1,activation=('sigmoid')))

model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])
model.fit(test_batches, epochs=17) #notice we are compiling our model on the test_batches, not the train ones. We want to see how the model performs on data it has not seen before.

Since our CNN is using classification, we will use Sequential(). Using this model allows us to create a deep learning model that has layers.

model.add(Conv2D(32, (3, 3),activation=('relu'),input_shape=(224,224,3)))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3),activation=('relu')))
model.add(MaxPooling2D(pool_size=(2, 2)))

Now we will add 2 Conv2D layers with max-pooling after each layer. We’ll initialize the input_shape as 224, 224 for the dimensions of our image and 3 for the channels. Note that we use the relu activation for our layers.

model.add(Conv2D(64, (3, 3),activation=('relu')))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))

model.add(Flatten())
model.add(Dense(64,activation=('relu')))
model.add(Dropout(0.2))
model.add(Dense(1,activation=('sigmoid')))

In the last stages of our model, we will add another Conv2D layer with max-pooling, but instead, add a dropout of 0.2. Essentially our dropout will reduce overfitting.

Now we need to flatten our layers, so the data can be fit into our dense layer. We will also add a dropout after.

Lastly, we will add one more dense layer, and make our activation sigmoid. Since our output is for binary classification, our choice of the activation function is sigmoid. This is because sigmoid does non-linear transformations on the input, increasing the input’s learning capabilities and allowing it to perform complex tasks.

Before we test our model, we will compile and fit our CNN.

model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])
model.fit(test_batches, epochs=17)

We will use adam for our optimizer, and set metrics to accuracy. We will also fit in our test_batches because we want to see how well our CNN will perform on the test data. Our epochs will be set to 17, and this was done as a result of getting the highest accuracy and a decent loss with this specific size.

Now it’s time to run the model.

On your last epoch, you should get a result similar to this.

Congratulations, you just built a CNN to detect pneumonia!

Contact me for any inquiries 🚀

Please note that all code within this article is my own code. If you would like to use or reference this code, go to my Github, where the repository is public.

Hi, I’m Ashley, a 16-year-old coding nerd and A.I. enthusiast!

I hope you enjoyed reading my article, and if you did, feel free to check out some of my other pieces on Medium :)

If you have any questions, would like to learn more about me, or want resources for anything A.I. or programming related, you can contact me by:

💫Email: ashleycinquires@gmail.com

💫Github

--

--

computer scientist, dog lover, peanut butter enthusiast, and probably a little too ambitious