Learning How To Classify Images Using Keras Part 1
I previously wrote a blog post on AlexNet, one of the most influential convolutional neural networks, and wanted to try using Keras to create my own CNN. Part of this was to gain experience and learn a new tool, but in the process I hope to help others who are wanting to learn how to use a CNN. The data set that I used was from Kaggle. It contains roughly 6,000 images of MRI segmentation of the brain regarding Alzheimer’s disease.
The file structure of this project is important for reproducibility. The data will be saved in src/data and the notebook I’m working in is Model.ipynb
├── notebooks
│ ├── exploratory
│ └── Model.ipynb
└── src
└── data
If you have a Kaggle api key you can download it to a directory fairly easy with the code below, otherwise manually download it from the website above and save it to src/data.
!kaggle datasets download -d tourist55/alzheimers-dataset-4-class-of-images -p ../../src/data
You can then unzip the file using the following code:
import zipfile
import os, shutil
import splitfolderswith zipfile.ZipFile("../../src/data/alzheimers-dataset-4-class-of-images.zip","r") as zip_ref:
zip_ref.extractall("../../src/data")
Once the files have been unzipped there should be 2 subfolders labeled train and test. Within those folders you will find 4 classes (Very Mild, Mild, Moderate, Normal Brain). From here you can start training a CNN, but I wanted to create a Validation set and also randomize the photos so I did the following lines of code.
filepath = ['MildDemented', 'ModerateDemented', 'NonDemented', 'VeryMildDemented']for path in filepath:
locals()[str(path) + str('_train_dir')] = f'../../src/data/train/{path}' #Train_directory
locals()[str(path) + str('_test_dir')] = f'../../src/data/test/{path}' #Test_directory
locals()[str(path) + str('_images')] = [file for file in os.listdir(f'../../src/data/test/{path}') if file.endswith('.jpg')]
What this is doing is creating variables for the file path of each of the train and test subfolders. Next we will combine the test images with the train images.
img_list = [MildDemented_images, ModerateDemented_images, NonDemented_images, VeryMildDemented_images]
for path, file in zip(img_list,filepath):
for img in path:
origin = os.path.join(f'../../src/data/test/{file}', img)
destination = os.path.join(f'../../src/data/train/{file}', img)
shutil.copyfile(origin, destination)
os.remove(f'../../src/data/test/{file}'+'/'+img)
Finally we can remove the empty folders and split our combined data into 3 categories: Train, Val, and Test. By running the code below the training data will be 70 percent of the photos while the validation and test will make up for 15 percent of the photos each.
os.rename('../../src/data/train', '../../src/data/combined')
os.remove('../../src/data/test')# This will split the combined folder into 3 separate folders train, val, test with the ratio shown
splitfolders.ratio("../../src/data/combined", output="../../src/data3", seed=1337, ratio=(.7, .15, .15))
If you’re curious to see how many each subfolder contains exactly you can run the following:
for split in ['val', 'train', 'test']:
for path in filepath:
print(split + ' ' + path + ': ' + str(len(os.listdir(f'../../src/data3/{split}/{path}'))))
The output should look something like:
val MildDemented: 134
val ModerateDemented: 9
val NonDemented: 480
val VeryMildDemented: 336
train MildDemented: 627
train ModerateDemented: 44
train NonDemented: 2240
train VeryMildDemented: 1568
test MildDemented: 135
test ModerateDemented: 11
test NonDemented: 480
test VeryMildDemented: 336
Next I wanted to take a look at some of the photos and see what we’re dealing with before we create a CNN.
import os, shutil
from keras.preprocessing import image
import numpy as npimport os
import PIL
import tensorflow as tfimport matplotlib.pyplot as plt
%matplotlib inline
Import all the packages above
Image_ds = tf.keras.preprocessing.image_dataset_from_directory(
'../../src/data/train',
seed=123)class_names = Image_ds.class_names
import matplotlib.pyplot as pltplt.figure(figsize=(10, 10))
for images, labels in Image_ds.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
Now for the Modeling part!
For our first simple model we are going to need to import the following packages:
from sklearn import metrics
from sklearn.metrics import confusion_matrix, classification_report, plot_confusion_matrixfrom keras import models
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
We want to use a data generator to import the images from their respective folders. For now we are just focusing on the train and validation subfolders. We do this by creating a train_generator and val_generator
batch_size = 64
target_size = (64, 64)
train_dir = '../../src/data/train'
test_dir = '../../src/data/test'dagenerator = ImageDataGenerator(rescale=1./255, validation_split=0.2)train_generator = dagenerator.flow_from_directory(directory = train_dir,
target_size = target_size,
subset = 'training',
batch_size = batch_size)val_generator = dagenerator.flow_from_directory(directory = train_dir,
target_size = target_size,
shuffle = False,
subset = 'validation',
batch_size = batch_size)
When modeling, especially with CNN, you want the first model to be simple that way you can gauge a baseline and work up from there. The nature of CNN are very computationally expensive to so you don’t want to run something for an hour only to be disappointed with the results. The first model I created only had convolutional layer, maxpooling layer and softmax layer. When creating the first Convultional layer you must make sure the input shape is the same as your target size from above. The (64,64,3) is the target size plus the 3 layers of RBG. Even though the images appear greyscale they still have a RBG layer.
model = Sequential()model.add(Conv2D(10, (3,3), activation='relu', input_shape = (64,64,3)))
model.add(MaxPooling2D((2,2)))
model.add(Flatten())
model.add(Dense(4, activation='softmax')) # last layer has to match the amount of catagories trying to classifymodel.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['acc'])history = model.fit_generator(
generator =train_generator,
validation_data = val_generator,
epochs=5)
The results of each Epoch will appear as shown below. As you can see by the 5th Epoch I was able to get an accuracy of roughly 93% on training data and 83% on validation. Not bad for the first simple model.
Epoch 1/5
411/411 [==============================] - 5s 11ms/step - loss: 0.9018 - acc: 0.5767 - val_loss: 0.8482 - val_acc: 0.5926
Epoch 2/5
411/411 [==============================] - 4s 9ms/step - loss: 0.6189 - acc: 0.7432 - val_loss: 0.5934 - val_acc: 0.7690
Epoch 3/5
411/411 [==============================] - 4s 10ms/step - loss: 0.4395 - acc: 0.8303 - val_loss: 0.4297 - val_acc: 0.8635
Epoch 4/5
411/411 [==============================] - 5s 12ms/step - loss: 0.3258 - acc: 0.8864 - val_loss: 0.3658 - val_acc: 0.8694
Epoch 5/5
411/411 [==============================] - 5s 12ms/step - loss: 0.2256 - acc: 0.9320 - val_loss: 0.3923 - val_acc: 0.8285
Part 2 will cover how to improve upon this model as well as visualize what the model is focusing on when classifying these images. Stay tuned….