About Dataset
The definitive diagnosis of Acute Lymphoblastic Leukemia (ALL), as a highly prevalent cancer, requires invasive, expensive, and time-consuming diagnostic tests. ALL diagnosis using peripheral blood smear (PBS) images plays a vital role in the initial screening of cancer from non-cancer cases. The examination of these PBS images by laboratory users is riddled with problems such as diagnostic error because the non-specific nature of ALL signs and symptoms often leads to misdiagnosis.
The images of this dataset were prepared in the bone marrow laboratory of Taleqani Hospital (Tehran, Iran). This dataset consisted of 3242 PBS images from 89 patients suspected of ALL, whose blood samples were prepared and stained by skilled laboratory staff. This dataset is divided into two classes benign and malignant. The former comprises hematogenous, and the latter is the ALL group with three subtypes of malignant lymphoblasts: Early Pre-B, Pre-B, and Pro-B ALL. All the images were taken by using a Zeiss camera in a microscope with a 100x magnification and saved as JPG files. A specialist using the flow cytometry tool made the definitive determination of the types and subtypes of these cells.
🔵 At the first step, Install requred python librasries with pip install
command.
# ! pip install -q split-folders
🔵 Then import nesseccary libraries with import
command.
import os # To work with main operating system commands
import gc # Its a 'Garbage collector' , to freeup spaces
import shutil # To copy and move files
import numpy as np # To work with arrays
import cv2 # Powerfull library to work with images
import random # To generate random number and random choices
import matplotlib.pyplot as plt # To visualization
import seaborn as sns # To visualization
import splitfolders # To splite images to [train, validation, test]
from PIL import Image # To read images
from tqdm.notebook import tqdm # Beautifull progress-bar
from termcolor import colored # To colorfull output
from warnings import filterwarnings # to avoid python warnings
import torch # Pytorch framework
import torchvision.transforms as transforms # to apply some functions befor create a dataset
from torchvision.datasets import ImageFolder # To create dataset from images on local drive
from torch.utils.data import DataLoader # Create DataLoader
from torchvision.models import googlenet, GoogLeNet_Weights # Pre-trained model with its weights
import torch.nn as nn # Neural-Networs function
from datetime import datetime # To calculate time and duration
from sklearn.metrics import confusion_matrix, classification_report # To calculate and plot Confusion Matrix
🔵 Apply above libraries configs to better performances.
# Add a style to seaborn plots for better visualization
sns.set_style('darkgrid')
# To avoide Python warniongs
filterwarnings('ignore')
# Initialization values
img_size = (128, 128)
batch_size = 64
num_epochs = 30
# Show all colors used in this notebook
colors_dark = ['#1d3461', '#eef1fb', '#ade8f4', 'red', 'black', 'orange', 'navy', '#fbf8cc']
sns.palplot(colors_dark)
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
if device.type == 'cuda' :
print(colored(' GPU is available ', 'green', 'on_white', attrs=['bold']))
else :
print(colored(' You are using CPU ', 'red', 'on_white', attrs=['bold']))
🔵 Set path of Dataset on kaggle or your local drive.
# Path of main dataset
base_dir = 'C:\\envs\\DataSets\\Blood cell Cancer [ALL]'
# Path of working directory
working_dir = 'C:\\envs\\Working\\Blood_Cell_Cancer'
🔵 Base in above diagram, we should do below steps :
# Create 'Image' folder in working directory (step 1)
Images = os.path.join(working_dir, 'Images')
if not os.path.exists(Images) :
os.mkdir(Images)
# For each class, create a folder in Images folder (step 2)
Benign = os.path.join(Images, 'Benign')
Early_Pre_B = os.path.join(Images, 'Early_Pre_B')
Pre_B = os.path.join(Images, 'Pre_B')
Pro_B = os.path.join(Images, 'Pro_B')
os.mkdir(Benign)
os.mkdir(Early_Pre_B)
os.mkdir(Pre_B)
os.mkdir(Pro_B)
# Copy images from dataset to working-dir/Images
for folder in os.listdir(base_dir) :
folder_path = os.path.join(base_dir, folder)
for img in tqdm(os.listdir(folder_path)) :
src = os.path.join(folder_path, img)
match folder :
case 'Benign' :
shutil.copy(src, os.path.join(Benign, img))
case '[Malignant] early Pre-B' :
shutil.copy(src, os.path.join(Early_Pre_B, img))
case '[Malignant] Pre-B' :
shutil.copy(src, os.path.join(Pre_B, img))
case '[Malignant] Pro-B' :
shutil.copy(src, os.path.join(Pro_B, img))
print(colored('All images copied to working directory', 'green'))
# Read and show classes
classes = os.listdir(Images)
num_classes = len(classes)
print(classes)
print(f'Number of classes : {num_classes}')
🔵 Show a number of samples in each class by countplot .
# A variable to store values
counts = []
# Loop over class names
for class_name in classes :
class_path = os.path.join(Images, class_name)
counts.append(len(os.listdir(class_path)))
# Plot the result
plt.figure(figsize=(13, 4), dpi=400)
ax = sns.barplot(x=counts, y=classes, palette='Set1', hue=classes)
for i in range(len(classes)) :
ax.bar_label(ax.containers[i])
plt.title('Number of images in each class', fontsize=20, fontweight='bold', c='navy')
ax.set_xlim(0, 1200)
ax.set_xlabel('Counts', fontweight='bold')
ax.set_ylabel('Classes', fontweight='bold')
plt.show()
🔵 Now plot some images in each class
# A loop to iterate below codes for each class
for class_name in classes :
# To create a plot with 1 row and 6 column
fig, ax = plt.subplots(1, 6, figsize=(15, 2))
# Define a variable for each class_name's path by joining base_directory and each class_name
class_path = os.path.join(Images, class_name)
# Files is a list of all image names in each folder (class)
files = os.listdir(class_path)
# Choose 6 random image from each class to show in plot
random_images = random.choices(files, k=6)
# A loop to iterate in each 6 random images
for i in range(6) :
# print class_name as suptitle for each class
plt.suptitle(class_name, fontsize=20, fontweight='bold')
# variable img is path of image, by joining class_path and image file name
img = os.path.join(class_path ,random_images[i])
# load image in img variable using keras.utils.load_img(image_path)
img = Image.open(img)
# Plot image
ax[i].imshow(img)
# Turn axis off
ax[i].axis('off')
# Make plots to become nearer to each other
plt.tight_layout()
🔵 In this step, split images to 3 part, Train, Validation and Test by ratio 70%, 15%, 15% of whole images.
# create folder for train and validation and test
train_valid = os.path.join(working_dir, 'train_valid')
splitfolders.ratio(
input=Images, output=train_valid, seed=42, ratio=(0.7, 0.15, 0.15)
)
print(colored(f' All images splited to TRAIN / VALIDATION / TEST folders. ', 'white', 'on_green', attrs=['bold']))
🔵 Count Images in each folder
# list of folders
folders = os.listdir(train_valid)
print(colored('Number of samples in each folder : ', 'green', attrs=['bold']))
for folder in folders :
# A variable to store count of images in each part
counts = 0
folder_path = os.path.join(train_valid, folder)
for class_name in os.listdir(folder_path) :
class_path = os.path.join(folder_path, class_name)
counts += len(os.listdir(class_path))
print(colored(f'{folder} : {counts}', 'blue',attrs=['bold']))
🔵 Data augmentation is the process of artificially generating new data from existing data, primarily to train new machine learning (ML) models. Data augmentation can address a variety of challenges when training a CNN model, such as limited or imbalanced data, overfitting, and variation and complexity. This technique can increase the size of the dataset and balance the classes by applying different transformations
🔵 Here, choose a sample image to plot with each Augmentation function to represent changes.
sample_image = os.path.join(Benign, 'Sap_013 (1).jpg')
🔵 Blurring an image is a process that makes the image less sharp and reduces its level of detail. It distorts the detail of an image which makes it less clear. The most common use of image blurriness is to remove noise from the image; the other is to get the most detailed part of the image and smooth out the less detailed ones. Image blur is also called image smoothing.
🔵 We use 3 kind of bluring :
def Blure_Filter(img, filter_type ="blur", kernel=13):
'''
### Filtering ###
img: image
filter_type: {blur: blur, gaussian: gaussian, median: median}
'''
if filter_type == "blur":
return cv2.blur(img,(kernel,kernel))
elif filter_type == "gaussian":
return cv2.GaussianBlur(img, (kernel, kernel), 0)
elif filter_type == "median":
return cv2.medianBlur(img, kernel)
🔵 Represent blur function on sample image.
plt.figure(figsize=(10, 2.25), dpi=400)
plt.suptitle('Blured samples', fontweight='bold', fontsize=15)
# Original image
plt.subplot(1, 4, 1)
img = cv2.imread(sample_image)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off')
plt.title('Original', fontweight='bold')
# Blurs
# List of filters
filters = ['blur', 'gaussian', 'median']
for filter in filters :
indx = filters.index(filter)
plt.subplot(1, 4, indx+2)
filtered_img = Blure_Filter(img, filter_type=filter, kernel=13)
plt.imshow(filtered_img)
plt.axis('off')
plt.title(filter, fontweight='bold')
🔵 Noise is deliberately altering pixels to be different than what they may should have represented. Old-fashioned films are famous for having speckles black and white pixels present where they should not be. This is noise! Noise is one kind of imperfection that can be particularly frustrating for machines versus human understanding. While humans can easily ignore noise (or fit it within appropriate context), algorithms struggle. This is the root of so-called adversarial attacks where small, human-imperceptible pixel changes can dramatically alter a neural network's ability to make an accurate prediction.
🔵 We use 3 kind of Noise adding :
def Add_Noise(img, noise_type="gauss"):
'''
### Adding Noise ###
img: image
cj_type: {gauss: gaussian, sp: salt & pepper}
'''
if noise_type == "gauss":
mean=0
st=0.5
gauss = np.random.normal(mean,st,img.shape)
gauss = gauss.astype('uint8')
image = cv2.add(img,gauss)
return image
elif noise_type == "sp":
prob = 0.01
black = np.array([0, 0, 0], dtype='uint8')
white = np.array([255, 255, 255], dtype='uint8')
probs = np.random.random(img.shape[:2])
img[probs < (prob / 2)] = black
img[probs > 1 - (prob / 2)] = white
return img
🔵 Represent Noise adding function on sample image.
plt.figure(figsize=(10, 2.75), dpi=400)
plt.suptitle('Noised samples', fontweight='bold', fontsize=15)
plt.subplot(1, 3, 1)
img = cv2.imread(sample_image)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off')
plt.title('Original', fontweight='bold')
noises = ['gauss', 'sp']
for noise in noises :
indx = noises.index(noise)
plt.subplot(1, 3, indx+2)
noised_img = Add_Noise(img, noise_type=noise)
plt.imshow(noised_img)
plt.axis('off')
plt.title(noise, fontweight='bold')
🔵 Flipping an image (and its annotations) is a deceivingly simple technique that can improve model performance in substantial ways. Our models are learning what collection of pixels and the relationship between those collections of pixels denote an object is in-frame. But machine learning models (like convolutional neural networks) have a tendency to be quite brittle: they might memorize a specific ordering of pixels describes an object, but if that same object is mirrored across the image, our models may struggle to recognize it. Consider the orientation of your face when you are taking a selfie versus using the backwards lens on your camera: one interpretation may be mirrored while the other is not, yet they are still both your face. This mirroring of orientation is what we call flipping an image. By creating several versions of our images in various orientations, we give our deep learning model more information to learn from without having to go through the time consuming process of collecting and labeling more training data.
🔵 We use 3 kind of Fliping :
def Flip(img, flip_code) :
flipped_img = cv2.flip(img, flip_code)
return flipped_img
🔵 Represent Flip function on sample image.
plt.figure(figsize=(10, 2.75), dpi=400)
plt.suptitle('Flip a sample', fontweight='bold', fontsize=15)
plt.subplot(1, 4, 1)
img = cv2.imread(sample_image)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.axis('off')
plt.title('Original', fontweight='bold')
plt.subplot(1, 4, 2)
fliped = Flip(img, flip_code=0)
plt.imshow(fliped)
plt.axis('off')
plt.title('Horizontal Flip', fontweight='bold')
plt.subplot(1, 4, 3)
fliped = Flip(img, flip_code=1)
plt.imshow(fliped)
plt.axis('off')
plt.title('Vertical Flip', fontweight='bold')
plt.subplot(1, 4, 4)
fliped = Flip(img, flip_code=-1)
plt.imshow(fliped)
plt.axis('off')
plt.title('X&Y Flip', fontweight='bold')
plt.show()
🔵 OK ! Its time to apply above functions to Train images . Do this by define a function to choose randomly between 3 kind of augs and apply them to images. At last return a dictionary with key of new image name and value of augmented images.
def Apply_Augmentations(img) :
''' Apply random choice of augmentation functions on images '''
returned_augs = dict()
AUGS = ['Blure', 'Noise', 'Flip']
# How many of Augs choosen ?
random_num = random.randint(1, 3)
random_choice = random.choices(AUGS, k=random_num)
# To avoid repeatations :
random_choice = list(set(random_choice))
for choice in random_choice :
if choice == 'Blure' :
filters = ['blur', 'gaussian', 'median']
kernels = [5, 7, 9, 11]
random_filter = random.choices(filters, k=1)[0]
random_kernel = random.choices(kernels, k=1)[0]
blured_img = Blure_Filter(img, filter_type=random_filter, kernel=random_kernel)
new_name = '_blured'
returned_augs[new_name] = blured_img
elif choice == 'Noise' :
noises = ['gauss', 'sp']
random_noise = random.choices(noises, k=1)[0]
noised_img = Add_Noise(img, noise_type=random_noise)
new_name = '_noised'
returned_augs[new_name] = noised_img
elif choice == 'Flip' :
flip_codes = [-1, 0, 1]
random_code = random.choices(flip_codes, k=1)[0]
flipped_img = Flip(img, flip_code=random_code)
new_name = '_fliped'
returned_augs[new_name] = flipped_img
return returned_augs
🔵 Count images in train folder beforeand after of augmentation to find out how many images added to train folder.
train_dir = os.path.join(train_valid, 'train')
num_samples_befor_aug = 0
for folder in os.listdir(train_dir) :
folder_path = os.path.join(train_dir, folder)
num_samples_befor_aug += len(os.listdir(folder_path))
print(colored(f' Number of samples in TRAIN folder befor Augmentation : {num_samples_befor_aug} ', 'black', 'on_white', attrs=['bold']))
for folder in os.listdir(train_dir) :
folder_path = os.path.join(train_dir, folder)
for img_name in tqdm(os.listdir(folder_path)) :
img_path = os.path.join(folder_path, img_name)
img = cv2.imread(img_path)
returned = Apply_Augmentations(img)
for exported_name, exported_image in returned.items() :
# 1_left.jpg ---TO---> 1_lef_blured.jpg
new_name = img_name.split('.')[0] + exported_name + '.' + img_name.split('.')[-1]
new_path = os.path.join(folder_path, new_name)
# Save new image
cv2.imwrite(new_path, exported_image)
print(colored(f' Augmentation Completed. ', 'white', 'on_green', attrs=['bold']))
num_samples_after_aug = 0
for folder in os.listdir(train_dir) :
folder_path = os.path.join(train_dir, folder)
num_samples_after_aug += len(os.listdir(folder_path))
print(colored(f' Number of samples in TRAIN folder after Augmentation : {num_samples_after_aug} ', 'black', 'on_white', attrs=['bold']))
print(colored(f' {num_samples_after_aug-num_samples_befor_aug} images added to train directory. ', 'white', 'on_blue', attrs=['bold']))
🔵 Now, its time to create a dataset of images by some transforms and after that create DataLoader for each dataset. ## ## 4.1 | Create Datasets and DataLoaders
🔵 Torchvision supports common computer vision transformations in the torchvision.transforms and torchvision.transforms.v2 modules. Transforms can be used to transform or augment data for training or inference of different tasks (image classification, detection, segmentation, video classification).
transform = transforms.Compose(
[
transforms.Resize(img_size),
transforms.ToTensor()
]
)
############################# TRAIN #############################
# Dataset
train_ds = ImageFolder(root=os.path.join(train_valid, 'train'), transform=transform)
# DataLoader
train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
print(colored(f'TRAIN Folder :\n', 'green', attrs=['bold']))
print(train_ds)
############################# VALIDATION #############################
# Dataset
valid_ds = ImageFolder(root=os.path.join(train_valid, 'val'), transform=transform)
# DataLoader
valid_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=True)
print(colored(f'VALID Folder :\n', 'green', attrs=['bold']))
print(valid_ds)
############################# TEST #############################
# Dataset
test_ds = ImageFolder(root=os.path.join(train_valid, 'test'), transform=transform)
# DataLoader
test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=True)
print(colored(f'TEST Folder :\n', 'green', attrs=['bold']))
print(test_ds)
🔵 Read a batch of data from each loaders(train_loader, valid_loader, test_loader), to represet shape of bach and its data type.
# print shape of dataset for each set
for key, value in {'Train': train_loader, "Validation": valid_loader, 'Test': test_loader}.items():
for X, y in value:
print(colored(f'{key}:', 'white','on_green', attrs=['bold']))
print(f"Shape of images [Batch_size, Channels, Height, Width]: {X.shape}")
print(f"Shape of y: {y.shape} {y.dtype}\n")
print('-'*45)
break
🔵 Because of defining lots of variables and functions, our RAM may filled with unneccessary data and our GPU may be filled too. In this part by Deleting unneccessary variabels and using gc.collect
function for RAM and torch.cuda
for GPU cache we can free up some space to better performances.
##
## 5.1 | RAM
del [ax, base_dir, Benign, class_name, class_path, counts, colors_dark, exported_image, exported_name, Early_Pre_B, fig, files, filter, filtered_img, filters, fliped, folder, folder_path]
del [folders, i, Images, img, indx, key, noise, noised_img, noises, num_classes, num_samples_after_aug, num_samples_befor_aug, Pre_B, Pro_B, random_images]
del [sample_image, train_dir, value, working_dir, X, y, returned, src]
del [img_name, img_path, img_size, new_name, new_path, ]
gc.collect()
torch.cuda.empty_cache()
🔵 Instead of define a new model form scatch , i prefer to use a Pre-Trained model, GoogleNet with its trained wights GoogLeNet_Weights .
Google Net (or Inception V1) was proposed by research at Google (with the collaboration of various universities) in 2014 in the research paper titled “Going Deeper with Convolutions”. This architecture was the winner at the ILSVRC 2014 image classification challenge. It has provided a significant decrease in error rate as compared to previous winners AlexNet (Winner of ILSVRC 2012) and ZF-Net (Winner of ILSVRC 2013) and significantly less error rate than VGG (2014 runner up). This architecture uses techniques such as 1×1 convolutions in the middle of the architecture and global average pooling.
##
## 6.1 | PreTrained Model
model = googlenet(weights=GoogLeNet_Weights)
model
🔵 Out-feature of GoogleNet has 1000 Neuron, but in this case, our model should have 4 neuron, Length of classes. So change fc part of GoogleNet and replace it with a Sequential of fully connected network.
model.fc = nn.Sequential(
nn.Linear(in_features=1024, out_features=512),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(in_features=512, out_features=128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(in_features=128, out_features=64),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(in_features=64, out_features=4)
)
🔵 Its time to first use of GPU ! Move the model to GPU to accelerate process.
model.to(device)
🔵 As the first step in this part, define some functions to make output more beautifull and better undrestand.
def DeltaTime(dt) :
'''A Function to apply strftime manualy on delta.datetime class'''
h = dt.seconds // 3600
dh = dt.seconds % 3600
m = dh // 60
s = dh % 60
if h<10 : h='0'+str(h)
else : h = str(h)
if m<10 : m='0'+str(m)
else : m = str(m)
if s<10 : s='0'+str(s)
else : s = str(s)
return( h + ':' + m + ':' + s)
def Beauty_epoch(epoch) :
''' Return epochs in 2 digits - like (01 or 08) '''
if epoch<10 :
return '0' + str(epoch)
else :
return str(epoch)
🔵 Lets Train the model with train data and evaluate with validations.
# Create Loss_function and Optimizer
Learning_Rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=Learning_Rate)
# Some variables to store loss and accuracy to plot them
train_losses = np.zeros(num_epochs)
train_accs = np.zeros(num_epochs)
valid_losses = np.zeros(num_epochs)
valid_accs = np.zeros(num_epochs)
print(colored('Training Starts ... ', 'blue', 'on_white', attrs=['bold']))
for epoch in range(num_epochs) :
# Set the mode to TRAIN
model.train()
# Current time to calculate duration of epoch
t0 = datetime.now()
# Some variables to store data
train_loss = []
train_acc = []
valid_loss = []
valid_acc = []
n_correct = 0
n_total = 0
###############
#### Train ####
###############
# Read Images and Labels from TrainLoader
for images, labels in train_loader :
# Move Data to GPU
images = images.to(device)
labels = labels.to(device)
# Reshape labels to [Batch-Size, 1]
# labels = torch.reshape(labels, (-1, 1))
# Zero Grad Optimizer
optimizer.zero_grad()
# Forward Pass
y_pred = model(images)
loss = criterion(y_pred, labels)
# Backward pass
loss.backward()
optimizer.step()
# Train Loss
train_loss.append(loss.item())
# Train Accuracy
_, prediction = torch.max(y_pred, 1)
n_correct += (prediction==labels).sum().item()
n_total += labels.shape[0]
train_losses[epoch] = np.mean(train_loss)
train_accs[epoch] = n_correct / n_total
####################
#### Validation ####
####################
n_correct = 0
n_total = 0
# Read Images and Labels from ValidLoader
for images, labels in valid_loader :
# Move Data to GPU
images = images.to(device)
labels = labels.to(device)
# Reshape labels to [Batch-Size, 1]
# labels = torch.reshape(labels, (-1, 1))
# Forward pass
y_pred = model(images)
loss = criterion(y_pred, labels)
# Validation Loss
valid_loss.append(loss.item())
# val Accuracy
_, prediction = torch.max(y_pred, 1)
n_correct += (prediction==labels).sum().item()
n_total += labels.shape[0]
valid_losses[epoch] = np.mean(valid_loss)
valid_accs[epoch] = n_correct / n_total
############################### Duration ###############################
dt = datetime.now() - t0
############################### BEAUTIFULL OUTPUT ###############################
EPOCH = colored(f' Epoch [{Beauty_epoch(epoch+1)}/{num_epochs}] ', 'black', 'on_white', attrs=['bold'])
TRAIN_LOSS = colored(f' Train Loss:{train_losses[epoch]:.4f} ', 'white', 'on_green', attrs=['bold'])
TRAIN_ACC = colored(f' Train Acc:{train_accs[epoch]:.4f} ', 'white', 'on_blue', attrs=['bold'])
VAL_LOSS = colored(f' Val Loss:{valid_losses[epoch]:.4f} ', 'white', 'on_green', attrs=['bold'])
VAL_ACC = colored(f' Val Acc:{valid_accs[epoch]:.4f} ', 'white', 'on_blue', attrs=['bold'])
DURATION = colored(f' Duration : {DeltaTime(dt)} ', 'white', 'on_dark_grey', attrs=['bold'])
LR = colored(f' lr = {Learning_Rate} ', 'black', 'on_cyan', attrs=['bold'])
# Print the result of each epochs
print(f'{EPOCH} -> {TRAIN_LOSS}{TRAIN_ACC} {VAL_LOSS}{VAL_ACC} {DURATION} {LR}')
print(colored('Training Finished ...', 'blue', 'on_white', attrs=['bold']))
🔵 Plot the result of training.
plt.figure(figsize=(12, 3), dpi=400)
plt.subplot(1, 2, 1)
sns.lineplot(train_accs, label='Train Accuracy')
sns.lineplot(valid_accs, label='Valid Accuracy')
plt.title('Accuracy')
plt.subplot(1, 2, 2)
sns.lineplot(train_losses, label='Train Loss')
sns.lineplot(valid_losses, label='Validation Loss')
plt.title('Loss')
plt.show()
🔵 After finishing training, we should test the model with never unseen images to final evaluate the model.
with torch.no_grad() :
model.eval()
t0 = datetime.now()
test_loss = []
val_loss = []
n_correct = 0
n_total = 0
for images, labels in test_loader :
# Move input data to GPU
images = images.to(device)
labels = labels.to(device)
# Forward pass
y_pred = model(images)
loss = criterion(y_pred, labels)
# Train Loss
test_loss.append(loss.item())
# Train Accuracy
_, prediction = torch.max(y_pred, 1)
n_correct += (prediction==labels).sum().item()
n_total += labels.shape[0]
test_loss = np.mean(train_loss)
train_acc = n_correct / n_total
dt = datetime.now() - t0
print(colored(f'Loss:{test_loss:.4f}\nAccuracy:{train_acc:.4f}\nDuration:{dt}', 'green', attrs=['bold']))
🔵 And now, plot some images with real labels and predicted labels .
🔵 To do this, we should create a Dictionary called label_map, a dictionary with indexes as keys and class_names as values.
# Create a label_map to show True and Predicted labels in below plot
classes.sort()
classes
labels_map = {}
for index, label in enumerate(classes) :
labels_map[index] = label
labels_map
# Move model to CPU
cpu_model = model.cpu()
# Get 1 batch of test_loader
for imgs, labels in test_loader :
break
# Plot 1 batch of test_loader images with True and Predicted label
plt.subplots(4, 8, figsize=(16, 12))
plt.suptitle('Rice images in 1 Batch', fontsize=25, fontweight='bold')
for i in range(32) :
ax = plt.subplot(4, 8, i+1)
img = torch.permute(imgs[i], (1, 2, 0))
plt.imshow(img)
label = labels_map[int(labels[i])]
img = img[i].unsqueeze(0)
img = imgs[i].unsqueeze(0)
out = cpu_model(img)
predict = labels_map[int(out.argmax())]
plt.title(f'True :{label}\nPredict :{predict}')
plt.axis('off')
plt.show()
🔵 And the final step is ploting Confusion Matrix by sklearn
library.
# Get out 2 list include y_true and y_pred for use in confusion_matrix
model = model.to(device)
y_true = []
y_pred = []
for images, labels in test_loader:
images = images.to(device)
labels = labels.numpy()
outputs = model(images)
_, pred = torch.max(outputs.data, 1)
pred = pred.detach().cpu().numpy()
y_true = np.append(y_true, labels)
y_pred = np.append(y_pred, pred)
classes = labels_map.values()
print(classification_report(y_true, y_pred))
def plot_confusion_matrix(y_test, y_prediction):
'''Plotting Confusion Matrix'''
cm = confusion_matrix(y_true, y_pred)
ax = plt.figure(figsize=(8, 6))
ax = sns.heatmap(cm, annot=True, fmt='', cmap="Blues")
ax.set_xlabel('Prediced labels', fontsize=18)
ax.set_ylabel('True labels', fontsize=18)
ax.set_title('Confusion Matrix', fontsize=25)
ax.xaxis.set_ticklabels(classes)
ax.yaxis.set_ticklabels(classes)
plt.show()
plot_confusion_matrix(y_true, y_pred)
✅ If you like my notebook, please upvote it ✅