Malaria Detection¶
Problem Definition¶
The context:¶
Malaria remains one of the most significant global health challenges, particularly in tropical and subtropical regions. According to the World Health Organization (WHO), malaria caused approximately 249 million cases and 608,000 deaths in 2022, with the majority of fatalities occurring in young children and pregnant women in sub-Saharan Africa. Early and accurate detection of malaria is critical for effective treatment, reducing mortality rates, and preventing the disease's spread.
Traditional malaria diagnosis primarily relies on microscopic examination of blood smears, which requires skilled technicians and can be time-consuming and prone to human error. While rapid diagnostic tests (RDTs) exist, they may have limited sensitivity and are not always accessible in remote areas. Given these challenges, leveraging automated machine learning models to assist in malaria detection from microscopic blood images offers a promising solution. A reliable and efficient deep learning model could significantly enhance diagnostic accuracy, reduce the burden on healthcare systems, and enable faster intervention—ultimately saving lives.
The objectives:¶
- Develop an Image Classification Model – Build a CNN-based model capable of accurately distinguishing between parasitized and uninfected blood cell images.
- Minimize False Negatives – Prioritize reducing false negatives to ensure that parasitized blood cells are correctly identified, minimizing the risk of undiagnosed malaria cases.
- Optimize Model Performance – Achieve a high level of accuracy, precision, recall, and F1-score, with a focus on recall for the parasitized class.
- Preprocess and Augment Data – Implement preprocessing techniques such as normalization, resizing, and data augmentation to improve model robustness and generalization.
- Evaluate and Validate – Use appropriate evaluation metrics, including confusion matrices and classification reports, to assess model performance and ensure reliable predictions.
The key questions:¶
- How accurately can the model distinguish between parasitized and uninfected blood cell images?
- What is the false negative rate, and how can it be minimized to ensure infected cases are not missed?
- What are the optimal preprocessing techniques to improve model performance and generalization?
- How well does the model generalize to unseen test data, and what steps can be taken to reduce overfitting?
- What is the best balance between sensitivity (recall) and specificity to achieve reliable malaria detection?
- Can the model perform efficiently enough for potential real-world deployment in medical settings?
- How does the model compare to existing malaria detection methods in terms of accuracy and reliability?
- What additional features or improvements (e.g., different CNN architectures, hyperparameter tuning) can enhance the model’s performance?
The problem formulation:¶
This project aims to develop an automated and reliable deep learning model for malaria detection using microscopic images of blood cells. The goal is to classify images as either parasitized or uninfected with high accuracy while prioritizing the reduction of false negatives to avoid misdiagnosing infected cases.
Data Description ¶
There are a total of 24,958 train and 2,600 test images (colored) that we have taken from microscopic images. These images are of the following categories:
Parasitized: The parasitized cells contain the Plasmodium parasite which causes malaria
Uninfected: The uninfected cells are free of the Plasmodium parasites
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
print('Not connected to a GPU')
else:
print(gpu_info)
Mon Feb 24 21:04:30 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |
| N/A 78C P0 44W / 70W | 0MiB / 15360MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
from psutil import virtual_memory
ram_gb = virtual_memory().total / 1e9
print('Your runtime has {:.1f} gigabytes of available RAM\n'.format(ram_gb))
if ram_gb < 20:
print('Not using a high-RAM runtime')
else:
print('You are using a high-RAM runtime!')
Your runtime has 54.8 gigabytes of available RAM
You are using a high-RAM runtime!
Mount the Drive
# mount google drive
from google.colab import drive
drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
Loading libraries¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = "plotly_white"
import warnings
import os
import zipfile
import cv2
import tensorflow as tf
import keras
from tensorflow.keras.models import Sequential, Model, load_model
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, BatchNormalization, Activation, Input, LeakyReLU, Flatten, Dropout
from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img
from tensorflow.keras import backend
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.optimizers import Adam, SGD, RMSprop
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
from tensorflow.keras import losses, optimizers
from google.colab.patches import cv2_imshow
warnings.filterwarnings('ignore')
Let us load the data¶
Note:
- You must download the dataset from the link provided on Olympus and upload the same to your Google Drive. Then unzip the folder.
pathZip = '/content/drive/MyDrive/Python Course/Capstone Project/cell_images.zip'
# Comment out after extracting
#with zipfile.ZipFile(pathZip, 'r') as zip_ref:
#zip_ref.extractall('/content/drive/MyDrive/Python Course/Capstone Project')
The extracted folder has different folders for train and test data will contain the different sizes of images for parasitized and uninfected cells within the respective folder name.
The size of all images must be the same and should be converted to 4D arrays so that they can be used as an input for the convolutional neural network. Also, we need to create the labels for both types of images to be able to train and test the model.
Let's do the same for the training data first and then we will use the same code for the test data as well.
# Assign path and category
DATADIR_TRAIN = '/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train'
DATADIR_TEST = '/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test'
CATEGORIES = ['parasitized', 'uninfected']
IMG_SIZE = 128
# Function to extract the images from the directory
training_data = []
image_sizes = []
def create_training_data():
for category in CATEGORIES:
path = os.path.join(DATADIR_TRAIN, category)
class_num = category
for img in os.listdir(path):
img_array = cv2.imread(os.path.join(path, img))
if img_array is not None:
# Store image dimensions
h, w, _ = img_array.shape
image_sizes.append((w, h))
# Create new array with resized images
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
training_data.append([new_array, class_num])
create_training_data()
# Comment out after first run
#original_training_data = training_data.copy()
testing_data = []
def create_testing_data():
for category in CATEGORIES:
path = os.path.join(DATADIR_TEST, category)
class_num = category
for img in os.listdir(path):
img_array = cv2.imread(os.path.join(path, img))
if img_array is not None:
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
testing_data.append([new_array, class_num])
create_testing_data()
# Comment out after first run
# original_testing_data = testing_data.copy()
Check the shape of train and test images
# Create two different lists to store the Numpy arrays and the corresponding labels
x_train = []
y_train = []
# Shuffle data to reduce variance
np.random.shuffle(training_data)
# Iterate over data and append images and labels
for features, label in training_data:
x_train.append(features)
y_train.append(label)
# Create two different lists to store the Numpy arrays and the corresponding labels
x_test = []
y_test = []
# Shuffle data to reduce variance
np.random.shuffle(testing_data)
# Iterate over data and append images and labels
for features, label in testing_data:
x_test.append(features)
y_test.append(label)
# Converting the pixel values into Numpy array
x_train = np.array(x_train)
x_test = np.array(x_test)
x_train.shape, x_test.shape
((24957, 128, 128, 3), (2600, 128, 128, 3))
print(x_train.shape[0])
print(x_test.shape[0])
24957
2600
Check the shape of train and test labels
# Convert list to DataFrames
y_train = pd.DataFrame(y_train, columns=['Label'], dtype=object)
y_test = pd.DataFrame(y_test, columns=['Label'], dtype=object)
# Check shape of train and test Labels
y_train.Label.shape, y_test.Label.shape
((24957,), (2600,))
# Check label proportions
y_train.Label.value_counts(normalize=True)
| proportion | |
|---|---|
| Label | |
| parasitized | 0.504107 |
| uninfected | 0.495893 |
# Check label proportions
y_test.Label.value_counts(normalize=True)
| proportion | |
|---|---|
| Label | |
| uninfected | 0.5 |
| parasitized | 0.5 |
Observations and insights: _____
Check the minimum and maximum range of pixel values for train and test images
import numpy as np
# Assuming x_train is a numpy array with shape (num_samples, IMG_SIZE, IMG_SIZE, 3)
min_height = np.inf
max_height = -np.inf
min_width = np.inf
max_width = -np.inf
# Loop through the images to get the min and max dimensions
for img in x_train:
if img is not None: # Only process valid images
height, width, _ = img.shape # Get the height, width, and channels (color)
# Update min and max dimensions
min_height = min(min_height, height)
max_height = max(max_height, height)
min_width = min(min_width, width)
max_width = max(max_width, width)
print(f"Min Height: {min_height}")
print(f"Max Height: {max_height}")
print(f"Min Width: {min_width}")
print(f"Max Width: {max_width}")
Min Height: 40
Max Height: 385
Min Width: 46
Max Width: 394
Observations and insights: _____
Count the number of values in both uninfected and parasitized
# Printing the value counts of y_train labels
count = y_train.Label.value_counts()
print(count)
print('*'*10)
count = y_train.Label.value_counts(normalize = True)
print(count)
Label
parasitized 12581
uninfected 12376
Name: count, dtype: int64
**********
Label
parasitized 0.504107
uninfected 0.495893
Name: proportion, dtype: float64
# Printing the value counts of y_train labels
count = y_test.Label.value_counts()
print(count)
print('*'*10)
count = y_test.Label.value_counts(normalize = True)
print(count)
Label
uninfected 1300
parasitized 1300
Name: count, dtype: int64
**********
Label
uninfected 0.5
parasitized 0.5
Name: proportion, dtype: float64
Normalize the images
# Normalizing the image data
x_train = x_train/255.0
x_test = x_test/255.0
Observations and insights:¶
The data is imbalanced in the train dataset with "parasitized" having 12582 images and "uninfected" having 12376 images, a ratio of:
parasitized 0.504127 uninfected 0.495873
The test dataset is balanced at 1300 images for each label.
Plot to check if the data is balanced
# Get the counts
num_x_train = x_train.shape[0]
num_y_train = len(y_train)
num_x_test = x_test.shape[0]
num_y_test = len(y_test)
# Create subplots
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Training set histogram
bars1 = axes[0].bar(['x_train (Images)', 'y_train (Labels)'], [num_x_train, num_y_train], color=['blue', 'orange'])
axes[0].set_title('Comparison of x_train Images vs y_train Labels')
axes[0].set_ylabel('Count')
# Testing set histogram
bars2 = axes[1].bar(['x_test (Images)', 'y_test (Labels)'], [num_x_test, num_y_test], color=['green', 'red'])
axes[1].set_title('Comparison of x_test Images vs y_test Labels')
# Add text labels on bars
for ax in axes:
for bar in ax.patches:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2, height, f'{int(height)}',
ha='center', va='bottom', fontsize=12, fontweight='bold')
# Show plot
plt.tight_layout()
plt.show()
Observations and insights: _____
Data Exploration¶
Let's visualize the images from the train data
Visualize the images with subplot(6, 6) and figsize = (12, 12)
# Ensure y_train is an np array
y_train_array = np.array(y_train)
# Select 36 random images from x_train
num_images = 36
fig, axes = plt.subplots(6, 6, figsize=(12, 12))
# Loop to display images and corresponding labels
for i, ax in enumerate(axes.flat):
if i < num_images:
ax.imshow(x_train[i]) # Display the image
ax.axis('off') # Hide axis
ax.set_title(f'Label: {y_train_array[i]}') # Display corresponding label
plt.tight_layout()
plt.show()
Observations and insights: ¶
Uninfected cells tend to be more circular in shape, though not always. The one distinguishing factor is that all parasitized cells have one or more purple or pink spots which are not visible in the uninfected cells.
Plotting the mean images for parasitized and uninfected
# Specify the image size
IMG_SIZE = 128 # Resize images to 128x128
# Paths to the parasitized and uninfected directories
DATADIR_TRAIN = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train" # Set the actual path
CATEGORIES = ["parasitized", "uninfected"]
# Load parasitized and uninfected images
parasitized_images = []
uninfected_images = []
for category in CATEGORIES:
path = os.path.join(DATADIR_TRAIN, category)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img_array = cv2.imread(img_path)
if img_array is None:
print(f"Warning: Unable to load image {img_path}") # Debugging message
continue # Skip this image
img_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE)) # Resize to uniform size
if category == "parasitized":
parasitized_images.append(img_array)
else:
uninfected_images.append(img_array)
# Convert lists to numpy arrays
parasitized_images = np.array(parasitized_images)
uninfected_images = np.array(uninfected_images)
# Compute the mean images
mean_parasitized = np.mean(parasitized_images, axis=0)
mean_uninfected = np.mean(uninfected_images, axis=0)
# Plot the mean images
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
# Plot parasitized mean image
axes[0].imshow(mean_parasitized.astype(np.uint8))
axes[0].axis('off')
axes[0].set_title('Mean Parasitized Image')
# Plot uninfected mean image
axes[1].imshow(mean_uninfected.astype(np.uint8))
axes[1].axis('off')
axes[1].set_title('Mean Uninfected Image')
plt.tight_layout()
plt.show()
Observations and insights:
The two images look nearly identical in size, shape, and color. It's possible that the parasitized image is slightly darker than the uninfected image but it is not a stark contrast.
Converting RGB to HSV of Images using OpenCV
Converting the train data
# Define paths
DATADIR_TEST = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train"
CATEGORIES = ["parasitized", "uninfected"]
IMG_SIZE = 128 # Resize for consistency
# Initialize lists for each category
hsv_parasitized = []
hsv_uninfected = []
# Process images
for category in CATEGORIES:
path = os.path.join(DATADIR_TRAIN, category)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img = cv2.imread(img_path)
if img is not None:
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # Resize
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV) # Convert to HSV
# Store images in corresponding category list
if category == "parasitized":
hsv_parasitized.append(img_hsv)
else:
hsv_uninfected.append(img_hsv)
# Convert lists to numpy arrays
hsv_parasitized = np.array(hsv_parasitized)
hsv_uninfected = np.array(hsv_uninfected)
# Compute mean images
mean_parasitized = np.mean(hsv_parasitized, axis=0).astype(np.uint8)
mean_uninfected = np.mean(hsv_uninfected, axis=0).astype(np.uint8)
# Convert mean images back to RGB for visualization
mean_parasitized_rgb = cv2.cvtColor(mean_parasitized, cv2.COLOR_HSV2RGB)
mean_uninfected_rgb = cv2.cvtColor(mean_uninfected, cv2.COLOR_HSV2RGB)
# Plot the mean images
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
# Plot mean parasitized image
axes[0].imshow(mean_parasitized_rgb)
axes[0].axis("off")
axes[0].set_title("Mean Parasitized Image")
# Plot mean uninfected image
axes[1].imshow(mean_uninfected_rgb)
axes[1].axis("off")
axes[1].set_title("Mean Uninfected Image")
plt.tight_layout()
plt.show()
Converting the test data
# Define paths
DATADIR_TEST = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
CATEGORIES = ["parasitized", "uninfected"]
IMG_SIZE = 128 # Resize for consistency
# Initialize lists for each category
hsv_parasitized = []
hsv_uninfected = []
# Process images
for category in CATEGORIES:
path = os.path.join(DATADIR_TEST, category)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img = cv2.imread(img_path)
if img is not None:
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # Resize
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV) # Convert to HSV
# Store images in corresponding category list
if category == "parasitized":
hsv_parasitized.append(img_hsv)
else:
hsv_uninfected.append(img_hsv)
# Convert lists to numpy arrays
hsv_parasitized = np.array(hsv_parasitized)
hsv_uninfected = np.array(hsv_uninfected)
# Compute mean images
mean_parasitized = np.mean(hsv_parasitized, axis=0).astype(np.uint8)
mean_uninfected = np.mean(hsv_uninfected, axis=0).astype(np.uint8)
# Convert mean images back to RGB for visualization
mean_parasitized_rgb = cv2.cvtColor(mean_parasitized, cv2.COLOR_HSV2RGB)
mean_uninfected_rgb = cv2.cvtColor(mean_uninfected, cv2.COLOR_HSV2RGB)
# Plot the mean images
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
# Plot mean parasitized image
axes[0].imshow(mean_parasitized_rgb)
axes[0].axis("off")
axes[0].set_title("Mean Parasitized Image")
# Plot mean uninfected image
axes[1].imshow(mean_uninfected_rgb)
axes[1].axis("off")
axes[1].set_title("Mean Uninfected Image")
plt.tight_layout()
plt.show()
Observations and insights:
The parasitized image has a neon green center and a neon green outer ring. Separating these two areas is a light purple, semi-transparent ring. By comparison, the uninfected image does not have any purple, is primarily neon green/yellow with a yellowish-green center.
Processing Images using Gaussian Blurring
Gaussian Blurring on train data
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Define paths
DATADIR_TRAIN = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train"
CATEGORIES = ["parasitized", "uninfected"]
IMG_SIZE = 128 # Resize for consistency
# Initialize dictionary to store one blurred image per category
blurred_samples = {}
# Process images
for category in CATEGORIES:
path = os.path.join(DATADIR_TRAIN, category)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img = cv2.imread(img_path)
if img is not None:
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # Resize
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB
img_blur = cv2.GaussianBlur(img_rgb, (5, 5), 0) # Apply Gaussian Blur
# Store the first image found for each category
if category not in blurred_samples:
blurred_samples[category] = img_blur
break # Stop after finding the first image in each category
# Plot blurred images
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
for i, category in enumerate(CATEGORIES):
axes[i].imshow(blurred_samples[category])
axes[i].set_title(f"Blurred {category.capitalize()} Image")
axes[i].axis("off")
plt.tight_layout()
plt.show()
Gaussian Blurring on test data
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Define paths
DATADIR_TEST = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
CATEGORIES = ["parasitized", "uninfected"]
IMG_SIZE = 128 # Resize for consistency
# Initialize dictionary to store one blurred image per category
blurred_samples = {}
# Process images
for category in CATEGORIES:
path = os.path.join(DATADIR_TEST, category)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img = cv2.imread(img_path)
if img is not None:
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # Resize
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB
img_blur = cv2.GaussianBlur(img_rgb, (5, 5), 0) # Apply Gaussian Blur
# Store the first image found for each category
if category not in blurred_samples:
blurred_samples[category] = img_blur
break # Stop after finding the first image in each category
# Plot blurred images
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
for i, category in enumerate(CATEGORIES):
axes[i].imshow(blurred_samples[category])
axes[i].set_title(f"Blurred {category.capitalize()} Image")
axes[i].axis("off")
plt.tight_layout()
plt.show()
Observations and insights:
The parasitized cell has noticable purple spot(s) while the uninfected sell does not.
Think About It: Would blurring help us for this problem statement in any way? What else can we try?
Blurring can help in reducing high-frequency noise so that the model can focus on more important patterns in the images. Also, as seen in the image above, the blurring has helped to bring out a larger-scale structural difference. In this way, the model will be able to passover irrelevant details which will help it to generalize better and avoid overfitting. Additionally, if we were to use Sobel filter or Canny edge detection, we may be able to improve edge detection through use of the blurred images.
Model Building¶
Base Model¶
Note: The Base Model has been fully built and evaluated with all outputs shown to give an idea about the process of the creation and evaluation of the performance of a CNN architecture. A similar process can be followed in iterating to build better-performing CNN architectures.
Importing the required libraries for building and training our Model
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = "plotly_white"
import warnings
import os
import zipfile
import cv2
import tensorflow as tf
import keras
from tensorflow.keras.models import Sequential, Model, load_model
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, GlobalAveragePooling2D, BatchNormalization, Activation, Input, LeakyReLU, Flatten, Dropout
from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img
from tensorflow.keras import backend
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.optimizers import Adam, SGD, RMSprop
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
from tensorflow.keras import losses, optimizers
from google.colab.patches import cv2_imshow
from sklearn.preprocessing import LabelEncoder
One Hot Encoding the train and test labels
# Convert DataFrame column to NumPy array
y_train = np.array(y_train).ravel() # Convert to a 1D NumPy array
y_test = np.array(y_test).ravel()
# Apply Label Encoding
label_encoder = LabelEncoder()
y_train_int = label_encoder.fit_transform(y_train)
y_test_int = label_encoder.transform(y_test)
# One-hot encode the labels
y_train_encoded = to_categorical(y_train_int)
y_test_encoded = to_categorical(y_test_int)
# Print class mapping and check encoding
print("Class Mapping:", dict(zip(label_encoder.classes_, range(len(label_encoder.classes_)))))
print("y_train_encoded shape:", y_train_encoded.shape)
print("y_test_encoded shape:", y_test_encoded.shape)
print("First 5 encoded labels in y_train:\n", y_train_encoded[:5])
Class Mapping: {'parasitized': 0, 'uninfected': 1}
y_train_encoded shape: (24957, 2)
y_test_encoded shape: (2600, 2)
First 5 encoded labels in y_train:
[[0. 1.]
[1. 0.]
[1. 0.]
[0. 1.]
[1. 0.]]
CNN MODEL 1:
Building the model
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_1():
# Intializing a sequential model
model = Sequential()
# Adding first conv layer with 64 filters and kernel size 3x3, padding 'same' provides the output size same as the input size
# The input_shape denotes input image dimension
model.add(Conv2D(64, (3, 3), activation = 'relu', padding = "same", input_shape = (128, 128, 3)))
# Adding max pooling to reduce the size of output of first conv layer
model.add(MaxPooling2D((2, 2), padding = 'same'))
# Adding second conv layer with 32 filters and kernel size 3x3, padding 'same' followed by a Maxpooling2D layer
model.add(Conv2D(32, (3, 3), activation = 'relu', padding = "same"))
model.add(MaxPooling2D((2, 2), padding = 'same'))
# Add third conv layer with 32 filters and kernel size 3x3, padding 'same' followed by a Maxpooling2D layer
model.add(Conv2D(32, (3, 3), activation='relu', padding="same"))
model.add(MaxPooling2D((2, 2), padding = 'same'))
# Flattening the output of the conv layer after max pooling to make it ready for creating dense connections
model.add(Flatten())
# Adding a fully connected dense layer with 100 neurons
model.add(Dense(100, activation = 'relu'))
# Adding the output layer with 2 neurons and activation functions as softmax since this is a multi-class classification problem
model.add(Dense(2, activation = 'softmax'))
return model
model_1 = cnn_model_1()
Compiling the model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
# Using Adam Optimizer
opt = Adam(learning_rate=0.001)
# Compiling the model
model_1.compile(optimizer = opt, loss = 'categorical_crossentropy', metrics = ['accuracy'])
# Generating the summary of the model
model_1.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 128, 128, 64) │ 1,792 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 64, 64, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D) │ (None, 64, 64, 32) │ 18,464 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 32, 32, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D) │ (None, 32, 32, 32) │ 9,248 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_2 (MaxPooling2D) │ (None, 16, 16, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten) │ (None, 8192) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 100) │ 819,300 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 2) │ 202 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 849,006 (3.24 MB)
Trainable params: 849,006 (3.24 MB)
Non-trainable params: 0 (0.00 B)
Observations:
- There are 849,006 total parameters (3.24 MB) all of which are trainable.
Using Callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and train our Model
# Fitting the model with 30 epochs and validation_split of 20%
history=model_1.fit(x_train,
y_train_encoded,
epochs = 30,
batch_size= 32, validation_split = 0.20, callbacks = [es, mc])
Epoch 1/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 21ms/step - accuracy: 0.6913 - loss: 0.5315
Epoch 1: val_accuracy improved from -inf to 0.95773, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 21s 27ms/step - accuracy: 0.6915 - loss: 0.5312 - val_accuracy: 0.9577 - val_loss: 0.1353
Epoch 2/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9584 - loss: 0.1342
Epoch 2: val_accuracy improved from 0.95773 to 0.96895, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9585 - loss: 0.1342 - val_accuracy: 0.9690 - val_loss: 0.0942
Epoch 3/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9731 - loss: 0.0842
Epoch 3: val_accuracy improved from 0.96895 to 0.97476, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9731 - loss: 0.0842 - val_accuracy: 0.9748 - val_loss: 0.0830
Epoch 4/30
623/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9774 - loss: 0.0712
Epoch 4: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9774 - loss: 0.0712 - val_accuracy: 0.9685 - val_loss: 0.1014
Epoch 5/30
621/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9839 - loss: 0.0526
Epoch 5: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9839 - loss: 0.0526 - val_accuracy: 0.9625 - val_loss: 0.1216
Epoch 6/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9849 - loss: 0.0458
Epoch 6: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9849 - loss: 0.0457 - val_accuracy: 0.9724 - val_loss: 0.1029
Epoch 7/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9881 - loss: 0.0380
Epoch 7: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9881 - loss: 0.0380 - val_accuracy: 0.9724 - val_loss: 0.1029
Epoch 8/30
623/624 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.9874 - loss: 0.0357
Epoch 8: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 12s 19ms/step - accuracy: 0.9874 - loss: 0.0357 - val_accuracy: 0.9671 - val_loss: 0.1243
Epoch 8: early stopping
Evaluating the model on test data
model_1.evaluate(x_test, (y_test_encoded))
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 14ms/step - accuracy: 0.9731 - loss: 0.1205
[0.12279212474822998, 0.9700000286102295]
Plotting the confusion matrix
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_1.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step precision recall f1-score support 0 0.98 0.96 0.97 1300 1 0.96 0.98 0.97 1300 accuracy 0.97 2600 macro avg 0.97 0.97 0.97 2600 weighted avg 0.97 0.97 0.97 2600
Plotting the train and validation curves
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Observations and insights:¶
- The test accuracy is 97% just slightly below the training accuracy.
- Cross entropy loss was about 0.12.
- The test and training plots track fairly closely after the first epoch, suggesting limited overfitting and good generalization of the model.
- False negatives outweighed false positives nearly 3:1,
So now let's try to build another model with few more add on layers and try to check if we can try to improve the model. Therefore try to build a model by adding few layers if required and altering the activation functions.
CNN MODEL 2:
Model 1
Trying to improve the performance of our model by adding new layers
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
Building the Model
def cnn_model_2():
# Initializing a Sequential model
model = Sequential()
# 1st Conv Block
model.add(Conv2D(32, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)))
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.25)) # Dropout for regularization
# 2nd Conv Block
model.add(Conv2D(64, (3, 3), activation='relu', padding="same"))
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.3))
# 3rd Conv Block
model.add(Conv2D(128, (3, 3), activation='relu', padding="same"))
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.4))
# 4th Conv Block
model.add(Conv2D(256, (3, 3), activation='relu', padding="same"))
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.5))
# Global Average Pooling instead of Flatten
model.add(GlobalAveragePooling2D())
# Fully Connected Layers
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.3))
# Output Layer (Softmax for multi-class classification)
model.add(Dense(2, activation='softmax'))
return model
model_2 = cnn_model_2()
Compiling the model
# Compile Model with a lower learning rate
opt = Adam(learning_rate=0.001) # Reduced learning rate for better convergence
model_2.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
# Summary of the model
model_2.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 128, 128, 32) │ 896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 64, 64, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout) │ (None, 64, 64, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D) │ (None, 64, 64, 64) │ 18,496 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 32, 32, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout) │ (None, 32, 32, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D) │ (None, 32, 32, 128) │ 73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_2 (MaxPooling2D) │ (None, 16, 16, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout) │ (None, 16, 16, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_3 (Conv2D) │ (None, 16, 16, 256) │ 295,168 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_3 (MaxPooling2D) │ (None, 8, 8, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_3 (Dropout) │ (None, 8, 8, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ global_average_pooling2d │ (None, 256) │ 0 │
│ (GlobalAveragePooling2D) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 128) │ 32,896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_4 (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 64) │ 8,256 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_5 (Dropout) │ (None, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense) │ (None, 2) │ 130 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 429,698 (1.64 MB)
Trainable params: 429,698 (1.64 MB)
Non-trainable params: 0 (0.00 B)
Using Callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and Train the model
# Fitting the model with 30 epochs and validation_split of 20%
history=model_2.fit(x_train,
y_train_encoded,
epochs = 30,
batch_size= 32, validation_split = 0.20, callbacks = [es, mc])
Epoch 1/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 29ms/step - accuracy: 0.6785 - loss: 0.5621
Epoch 1: val_accuracy improved from -inf to 0.95192, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 31s 36ms/step - accuracy: 0.6787 - loss: 0.5618 - val_accuracy: 0.9519 - val_loss: 0.1840
Epoch 2/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9682 - loss: 0.1157
Epoch 2: val_accuracy improved from 0.95192 to 0.95493, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9682 - loss: 0.1156 - val_accuracy: 0.9549 - val_loss: 0.1541
Epoch 3/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9739 - loss: 0.0835
Epoch 3: val_accuracy improved from 0.95493 to 0.97476, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9739 - loss: 0.0835 - val_accuracy: 0.9748 - val_loss: 0.0821
Epoch 4/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9722 - loss: 0.0847
Epoch 4: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9722 - loss: 0.0847 - val_accuracy: 0.9571 - val_loss: 0.1786
Epoch 5/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9720 - loss: 0.0806
Epoch 5: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9720 - loss: 0.0806 - val_accuracy: 0.9694 - val_loss: 0.0940
Epoch 6/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9776 - loss: 0.0672
Epoch 6: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9776 - loss: 0.0672 - val_accuracy: 0.9724 - val_loss: 0.0844
Epoch 7/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9767 - loss: 0.0686
Epoch 7: val_accuracy did not improve from 0.97476
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9767 - loss: 0.0686 - val_accuracy: 0.9740 - val_loss: 0.0744
Epoch 8/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9773 - loss: 0.0666
Epoch 8: val_accuracy improved from 0.97476 to 0.97596, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9773 - loss: 0.0666 - val_accuracy: 0.9760 - val_loss: 0.0681
Epoch 9/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9760 - loss: 0.0788
Epoch 9: val_accuracy improved from 0.97596 to 0.97636, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9760 - loss: 0.0788 - val_accuracy: 0.9764 - val_loss: 0.0705
Epoch 10/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9773 - loss: 0.0629
Epoch 10: val_accuracy improved from 0.97636 to 0.97736, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9773 - loss: 0.0629 - val_accuracy: 0.9774 - val_loss: 0.0616
Epoch 11/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9788 - loss: 0.0593
Epoch 11: val_accuracy did not improve from 0.97736
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9788 - loss: 0.0593 - val_accuracy: 0.9774 - val_loss: 0.0674
Epoch 12/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9793 - loss: 0.0618
Epoch 12: val_accuracy improved from 0.97736 to 0.97857, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9793 - loss: 0.0618 - val_accuracy: 0.9786 - val_loss: 0.0583
Epoch 13/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9795 - loss: 0.0582
Epoch 13: val_accuracy did not improve from 0.97857
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9795 - loss: 0.0583 - val_accuracy: 0.9738 - val_loss: 0.0820
Epoch 14/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9786 - loss: 0.0634
Epoch 14: val_accuracy did not improve from 0.97857
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9786 - loss: 0.0634 - val_accuracy: 0.9700 - val_loss: 0.0824
Epoch 15/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9792 - loss: 0.0610
Epoch 15: val_accuracy did not improve from 0.97857
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9792 - loss: 0.0610 - val_accuracy: 0.9770 - val_loss: 0.0629
Epoch 16/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9800 - loss: 0.0580
Epoch 16: val_accuracy did not improve from 0.97857
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9800 - loss: 0.0580 - val_accuracy: 0.9760 - val_loss: 0.0640
Epoch 17/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.9786 - loss: 0.0590
Epoch 17: val_accuracy did not improve from 0.97857
624/624 ━━━━━━━━━━━━━━━━━━━━ 14s 22ms/step - accuracy: 0.9786 - loss: 0.0590 - val_accuracy: 0.9764 - val_loss: 0.0686
Epoch 17: early stopping
Evaluating the model
model_2.evaluate(x_test, (y_test_encoded))
82/82 ━━━━━━━━━━━━━━━━━━━━ 2s 16ms/step - accuracy: 0.9793 - loss: 0.0555
[0.05434190109372139, 0.9815384745597839]
Plotting the confusion matrix
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_2.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step precision recall f1-score support 0 0.98 0.98 0.98 1300 1 0.98 0.98 0.98 1300 accuracy 0.98 2600 macro avg 0.98 0.98 0.98 2600 weighted avg 0.98 0.98 0.98 2600
Plotting the train and the validation curves
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Observations and insights:¶
- The test accuracy is 98% just slightly below the training accuracy.
- Cross entropy loss was about 0.05.
- The test and training plots track very closely, suggesting little overfitting and very good generalization of the model.
- False positives and false negatives both occured 24 times.
Think about it:
Now let's build a model with LeakyRelu as the activation function
- Can the model performance be improved if we change our activation function to LeakyRelu?
- Can BatchNormalization improve our model?
Let us try to build a model using BatchNormalization and using LeakyRelu as our activation function.
CNN MODEL 3:
Model 3 with Batch Normalization
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
Building the Model
def cnn_model_3():
# Initializing a Sequential model
model = Sequential()
# 1st Conv Block
model.add(Conv2D(32, (3, 3), padding="same", input_shape=(128, 128, 3)))
model.add(LeakyReLU(alpha=0.01))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.25)) # Dropout for regularization
# 2nd Conv Block
model.add(Conv2D(64, (3, 3), padding="same"))
model.add(LeakyReLU(alpha=0.01))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.3))
# 3rd Conv Block
model.add(Conv2D(128, (3, 3), padding="same"))
model.add(LeakyReLU(alpha=0.01))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.4))
# 4th Conv Block
model.add(Conv2D(256, (3, 3), padding="same"))
model.add(LeakyReLU(alpha=0.01))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2), padding='same'))
model.add(Dropout(0.5))
# Global Average Pooling instead of Flatten
model.add(GlobalAveragePooling2D())
# Fully Connected Layers
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.3))
# Output Layer (Softmax for multi-class classification)
model.add(Dense(2, activation='softmax'))
return model
model_3 = cnn_model_3()
Compiling the model
# Compile Model with a lower learning rate
opt = Adam(learning_rate=0.001) # Reduced learning rate for better convergence
model_3.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
# Summary of the model
model_3.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 128, 128, 32) │ 896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ leaky_re_lu (LeakyReLU) │ (None, 128, 128, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ batch_normalization │ (None, 128, 128, 32) │ 128 │
│ (BatchNormalization) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 64, 64, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout) │ (None, 64, 64, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D) │ (None, 64, 64, 64) │ 18,496 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ leaky_re_lu_1 (LeakyReLU) │ (None, 64, 64, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ batch_normalization_1 │ (None, 64, 64, 64) │ 256 │
│ (BatchNormalization) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 32, 32, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout) │ (None, 32, 32, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D) │ (None, 32, 32, 128) │ 73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ leaky_re_lu_2 (LeakyReLU) │ (None, 32, 32, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ batch_normalization_2 │ (None, 32, 32, 128) │ 512 │
│ (BatchNormalization) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_2 (MaxPooling2D) │ (None, 16, 16, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout) │ (None, 16, 16, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_3 (Conv2D) │ (None, 16, 16, 256) │ 295,168 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ leaky_re_lu_3 (LeakyReLU) │ (None, 16, 16, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ batch_normalization_3 │ (None, 16, 16, 256) │ 1,024 │
│ (BatchNormalization) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_3 (MaxPooling2D) │ (None, 8, 8, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_3 (Dropout) │ (None, 8, 8, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ global_average_pooling2d │ (None, 256) │ 0 │
│ (GlobalAveragePooling2D) │ │ │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 128) │ 32,896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_4 (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 64) │ 8,256 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_5 (Dropout) │ (None, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense) │ (None, 2) │ 130 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 431,618 (1.65 MB)
Trainable params: 430,658 (1.64 MB)
Non-trainable params: 960 (3.75 KB)
Using callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and train the model
# Fitting the model with 30 epochs and validation_split of 20%
history=model_3.fit(x_train,
y_train_encoded,
epochs = 30,
batch_size= 32, validation_split = 0.20, callbacks = [es, mc])
Epoch 1/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 32ms/step - accuracy: 0.7583 - loss: 0.4916
Epoch 1: val_accuracy improved from -inf to 0.69772, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 34s 39ms/step - accuracy: 0.7584 - loss: 0.4913 - val_accuracy: 0.6977 - val_loss: 1.2697
Epoch 2/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9593 - loss: 0.1236
Epoch 2: val_accuracy improved from 0.69772 to 0.94291, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9593 - loss: 0.1236 - val_accuracy: 0.9429 - val_loss: 0.1970
Epoch 3/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9717 - loss: 0.0908
Epoch 3: val_accuracy did not improve from 0.94291
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9717 - loss: 0.0908 - val_accuracy: 0.8812 - val_loss: 0.4436
Epoch 4/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9741 - loss: 0.0845
Epoch 4: val_accuracy improved from 0.94291 to 0.94551, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9741 - loss: 0.0845 - val_accuracy: 0.9455 - val_loss: 0.1965
Epoch 5/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9765 - loss: 0.0749
Epoch 5: val_accuracy improved from 0.94551 to 0.95954, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9765 - loss: 0.0749 - val_accuracy: 0.9595 - val_loss: 0.1191
Epoch 6/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9751 - loss: 0.0768
Epoch 6: val_accuracy improved from 0.95954 to 0.96254, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9751 - loss: 0.0767 - val_accuracy: 0.9625 - val_loss: 0.1307
Epoch 7/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9778 - loss: 0.0683
Epoch 7: val_accuracy did not improve from 0.96254
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9778 - loss: 0.0683 - val_accuracy: 0.9591 - val_loss: 0.1166
Epoch 8/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9779 - loss: 0.0671
Epoch 8: val_accuracy did not improve from 0.96254
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9779 - loss: 0.0671 - val_accuracy: 0.9625 - val_loss: 0.1325
Epoch 9/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9793 - loss: 0.0666
Epoch 9: val_accuracy did not improve from 0.96254
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9793 - loss: 0.0666 - val_accuracy: 0.9571 - val_loss: 0.1344
Epoch 10/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9803 - loss: 0.0605
Epoch 10: val_accuracy improved from 0.96254 to 0.97376, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9803 - loss: 0.0605 - val_accuracy: 0.9738 - val_loss: 0.0754
Epoch 11/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9788 - loss: 0.0603
Epoch 11: val_accuracy improved from 0.97376 to 0.97536, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9788 - loss: 0.0603 - val_accuracy: 0.9754 - val_loss: 0.0721
Epoch 12/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9808 - loss: 0.0589
Epoch 12: val_accuracy did not improve from 0.97536
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9808 - loss: 0.0589 - val_accuracy: 0.9706 - val_loss: 0.0984
Epoch 13/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9800 - loss: 0.0570
Epoch 13: val_accuracy improved from 0.97536 to 0.97937, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9800 - loss: 0.0570 - val_accuracy: 0.9794 - val_loss: 0.0567
Epoch 14/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9796 - loss: 0.0584
Epoch 14: val_accuracy did not improve from 0.97937
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9796 - loss: 0.0584 - val_accuracy: 0.9730 - val_loss: 0.0779
Epoch 15/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9821 - loss: 0.0549
Epoch 15: val_accuracy did not improve from 0.97937
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9821 - loss: 0.0549 - val_accuracy: 0.9685 - val_loss: 0.1186
Epoch 16/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9821 - loss: 0.0548
Epoch 16: val_accuracy did not improve from 0.97937
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9821 - loss: 0.0548 - val_accuracy: 0.9651 - val_loss: 0.1083
Epoch 17/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9811 - loss: 0.0527
Epoch 17: val_accuracy did not improve from 0.97937
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9811 - loss: 0.0527 - val_accuracy: 0.9764 - val_loss: 0.0708
Epoch 18/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 23ms/step - accuracy: 0.9815 - loss: 0.0512
Epoch 18: val_accuracy did not improve from 0.97937
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 25ms/step - accuracy: 0.9815 - loss: 0.0512 - val_accuracy: 0.9694 - val_loss: 0.1077
Epoch 18: early stopping
Plotting the train and validation accuracy
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Evaluating the model
model_3.evaluate(x_test, (y_test_encoded))
82/82 ━━━━━━━━━━━━━━━━━━━━ 2s 16ms/step - accuracy: 0.9706 - loss: 0.0993
[0.10453113168478012, 0.9700000286102295]
Observations and insights:¶
- The test accuracy is 97% just slightly below the training accuracy.
- Cross entropy loss was about 0.1.
- The test and training plots track fairly closely, suggesting some overfitting, but overall good generalization of the model.
- Changing activation to LeakyRelu and implementing Batch Normalization after each layer did not improve the accuracy of the model.
Generate the classification report and confusion matrix
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_3.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 2s 14ms/step precision recall f1-score support 0 0.99 0.95 0.97 1300 1 0.95 0.99 0.97 1300 accuracy 0.97 2600 macro avg 0.97 0.97 0.97 2600 weighted avg 0.97 0.97 0.97 2600
Observations and insights:¶
- The test accuracy is 98% just slightly below the training accuracy.
- Cross entropy loss was about 0.05.
- The test and training plots track fairly closely, suggesting limited overfitting and good generalization of the model.
Think About It :
- Can we improve the model with Image Data Augmentation?
- References to image data augmentation can be seen below:
CNN MODEL 4:
Model 4 with Data Augmentation
# Import Data Augmentation package
from tensorflow.keras.preprocessing.image import ImageDataGenerator
Use image data generator
import os
import cv2
from PIL import Image
import shutil
# Paths
train_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train"
test_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
# Remove corrupt or missing images as this as been an issue
def clean_invalid_images(directory):
"""Checks each image in a directory and removes any that are corrupt, unreadable, or missing."""
for class_folder in os.listdir(directory):
class_path = os.path.join(directory, class_folder)
if not os.path.isdir(class_path):
continue # Skip if it's not a directory
for filename in os.listdir(class_path):
file_path = os.path.join(class_path, filename)
try:
# Verify with PIL
img = Image.open(file_path)
img.verify()
# Verify with OpenCV
img = cv2.imread(file_path)
if img is None:
print(f"Removing unreadable image: {file_path}")
os.remove(file_path)
except Exception as e:
print(f"Removing corrupt/missing file: {file_path} ({e})")
os.remove(file_path)
# Clean train & test directories
clean_invalid_images(train_dir)
clean_invalid_images(test_dir)
# Now create data generators
datagen = ImageDataGenerator(
rescale=1.0/255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
train_generator = datagen.flow_from_directory(
train_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
validation_generator = ImageDataGenerator(rescale=1.0/255).flow_from_directory(
test_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
Found 24957 images belonging to 2 classes.
Found 2600 images belonging to 2 classes.
Think About It :
- Check if the performance of the model can be improved by changing different parameters in the ImageDataGenerator.
Visualizing Augmented images
# Get a batch of images and labels
images, labels = next(train_generator) # Fetch the next batch of images
# Get class indices from the generator
class_indices = train_generator.class_indices # {'parasitized': 0, 'uninfected': 1}
class_labels = {v: k for k, v in class_indices.items()} # Reverse mapping {0: 'parasitized', 1: 'uninfected'}
# Set up the plot
fig, axes = plt.subplots(6, 6, figsize=(12, 12)) # Display 36 images in a 6x6 grid
# Loop through and display images with labels
for i, ax in enumerate(axes.flat):
if i < len(images):
ax.imshow(images[i]) # Show image
ax.axis('off') # Hide axes
# Get class label (argmax to find the highest probability in one-hot encoding)
label_index = np.argmax(labels[i])
ax.set_title(class_labels[label_index], fontsize=12, color='blue') # Display label
plt.tight_layout()
plt.show()
Observations and insights:¶
- The height and width shift along with the rotation and added zoom have revealed the outer edges of the images while obstucting certain portions of the cell.
- The predominant defining characteristic if one or more purple/pink spots on parasitized cells.
- There appears to be more noise inthe images due to the bars visible coming out of the cells. Not sure what these are.
Building the Model
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_4():
model = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model
model_4 = cnn_model_4()
model_4.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
model_4.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 128, 128, 64) │ 1,792 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 64, 64, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D) │ (None, 64, 64, 64) │ 36,928 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 32, 32, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D) │ (None, 32, 32, 128) │ 73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_2 (MaxPooling2D) │ (None, 16, 16, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten) │ (None, 32768) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 128) │ 4,194,432 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 2) │ 258 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 4,307,266 (16.43 MB)
Trainable params: 4,307,266 (16.43 MB)
Non-trainable params: 0 (0.00 B)
Using Callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and Train the model
history = model_4.fit(
train_generator,
epochs=30,
validation_data=validation_generator,
callbacks=[es, mc]
)
Epoch 1/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 385ms/step - accuracy: 0.5468 - loss: 0.6920
Epoch 1: val_accuracy improved from -inf to 0.76115, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 313s 397ms/step - accuracy: 0.5468 - loss: 0.6920 - val_accuracy: 0.7612 - val_loss: 0.5855
Epoch 2/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 199ms/step - accuracy: 0.8024 - loss: 0.4574
Epoch 2: val_accuracy improved from 0.76115 to 0.94154, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 208ms/step - accuracy: 0.8025 - loss: 0.4574 - val_accuracy: 0.9415 - val_loss: 0.1795
Epoch 3/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 198ms/step - accuracy: 0.9305 - loss: 0.2155
Epoch 3: val_accuracy improved from 0.94154 to 0.96154, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 162s 208ms/step - accuracy: 0.9305 - loss: 0.2154 - val_accuracy: 0.9615 - val_loss: 0.1231
Epoch 4/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 198ms/step - accuracy: 0.9444 - loss: 0.1694
Epoch 4: val_accuracy improved from 0.96154 to 0.98115, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 209ms/step - accuracy: 0.9444 - loss: 0.1694 - val_accuracy: 0.9812 - val_loss: 0.0774
Epoch 5/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 199ms/step - accuracy: 0.9534 - loss: 0.1481
Epoch 5: val_accuracy improved from 0.98115 to 0.98654, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 209ms/step - accuracy: 0.9534 - loss: 0.1481 - val_accuracy: 0.9865 - val_loss: 0.0524
Epoch 6/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 204ms/step - accuracy: 0.9582 - loss: 0.1350
Epoch 6: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 167s 214ms/step - accuracy: 0.9582 - loss: 0.1350 - val_accuracy: 0.9827 - val_loss: 0.0541
Epoch 7/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 200ms/step - accuracy: 0.9573 - loss: 0.1401
Epoch 7: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 164s 210ms/step - accuracy: 0.9573 - loss: 0.1401 - val_accuracy: 0.9831 - val_loss: 0.0556
Epoch 8/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 201ms/step - accuracy: 0.9609 - loss: 0.1236
Epoch 8: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 164s 211ms/step - accuracy: 0.9609 - loss: 0.1236 - val_accuracy: 0.9838 - val_loss: 0.0447
Epoch 9/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 199ms/step - accuracy: 0.9573 - loss: 0.1326
Epoch 9: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 209ms/step - accuracy: 0.9573 - loss: 0.1326 - val_accuracy: 0.9862 - val_loss: 0.0512
Epoch 10/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 200ms/step - accuracy: 0.9591 - loss: 0.1305
Epoch 10: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 164s 210ms/step - accuracy: 0.9591 - loss: 0.1305 - val_accuracy: 0.9846 - val_loss: 0.0485
Epoch 11/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 201ms/step - accuracy: 0.9570 - loss: 0.1319
Epoch 11: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 165s 212ms/step - accuracy: 0.9570 - loss: 0.1319 - val_accuracy: 0.9858 - val_loss: 0.0470
Epoch 12/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 199ms/step - accuracy: 0.9600 - loss: 0.1250
Epoch 12: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 209ms/step - accuracy: 0.9600 - loss: 0.1250 - val_accuracy: 0.9858 - val_loss: 0.0521
Epoch 13/30
780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 197ms/step - accuracy: 0.9611 - loss: 0.1220
Epoch 13: val_accuracy did not improve from 0.98654
780/780 ━━━━━━━━━━━━━━━━━━━━ 161s 207ms/step - accuracy: 0.9611 - loss: 0.1220 - val_accuracy: 0.9850 - val_loss: 0.0589
Epoch 13: early stopping
Evaluating the model
model_4.evaluate(validation_generator)
82/82 ━━━━━━━━━━━━━━━━━━━━ 8s 100ms/step - accuracy: 0.9835 - loss: 0.0686
[0.058934930711984634, 0.9850000143051147]
Plot the train and validation accuracy
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Plotting the classification report and confusion matrix
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_4.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 12ms/step precision recall f1-score support 0 0.98 0.99 0.99 1300 1 0.99 0.98 0.98 1300 accuracy 0.98 2600 macro avg 0.99 0.99 0.98 2600 weighted avg 0.99 0.98 0.98 2600
Now, let us try to use a pretrained model like VGG16 and check how it performs on our data.
CNN MODEL 5:
Pre-trained model (VGG16)¶
- Import VGG16 network upto any layer you choose
- Add Fully Connected Layers on top of it
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
Compiling the model
from tensorflow.keras.applications import VGG16
def cnn_model_5():
# Load pre-trained VGG16 model (excluding the top classification layers)
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(128, 128, 3))
# Freeze more layers of VGG16 (first 18 layers)
for layer in base_model.layers[:18]: # Freeze most of the layers
layer.trainable = False
# Add Fully Connected Layers on top of VGG16
x = Flatten()(base_model.output) # Flatten the output
x = Dense(64, activation='relu')(x) # Reduced size of fully connected layer
x = Dropout(0.5)(x) # Dropout to prevent overfitting
x = Dense(32, activation='relu')(x) # Further reduction in size
x = Dropout(0.3)(x)
output_layer = Dense(2, activation='softmax')(x) # Output layer for binary classification
# Create the new model
model = Model(inputs=base_model.input, outputs=output_layer)
return model
model_5 = cnn_model_5()
# Compile the model
model_5.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# Print model summary to check the reduced number of parameters
model_5.summary()
Model: "functional"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer) │ (None, 128, 128, 3) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_conv1 (Conv2D) │ (None, 128, 128, 64) │ 1,792 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_conv2 (Conv2D) │ (None, 128, 128, 64) │ 36,928 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block1_pool (MaxPooling2D) │ (None, 64, 64, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_conv1 (Conv2D) │ (None, 64, 64, 128) │ 73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_conv2 (Conv2D) │ (None, 64, 64, 128) │ 147,584 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block2_pool (MaxPooling2D) │ (None, 32, 32, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv1 (Conv2D) │ (None, 32, 32, 256) │ 295,168 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv2 (Conv2D) │ (None, 32, 32, 256) │ 590,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_conv3 (Conv2D) │ (None, 32, 32, 256) │ 590,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block3_pool (MaxPooling2D) │ (None, 16, 16, 256) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv1 (Conv2D) │ (None, 16, 16, 512) │ 1,180,160 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv2 (Conv2D) │ (None, 16, 16, 512) │ 2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_conv3 (Conv2D) │ (None, 16, 16, 512) │ 2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block4_pool (MaxPooling2D) │ (None, 8, 8, 512) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv1 (Conv2D) │ (None, 8, 8, 512) │ 2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv2 (Conv2D) │ (None, 8, 8, 512) │ 2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_conv3 (Conv2D) │ (None, 8, 8, 512) │ 2,359,808 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ block5_pool (MaxPooling2D) │ (None, 4, 4, 512) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten) │ (None, 8192) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 64) │ 524,352 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout) │ (None, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 32) │ 2,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout) │ (None, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense) │ (None, 2) │ 66 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 15,241,186 (58.14 MB)
Trainable params: 526,498 (2.01 MB)
Non-trainable params: 14,714,688 (56.13 MB)
using callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and Train the model
# Fitting the model with 30 epochs and validation_split of 20%
history=model_5.fit(x_train,
y_train_encoded,
epochs = 30,
batch_size= 32, validation_split = 0.20, callbacks = [es, mc])
Epoch 1/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 75ms/step - accuracy: 0.7234 - loss: 0.5269
Epoch 1: val_accuracy improved from -inf to 0.90405, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 69s 95ms/step - accuracy: 0.7235 - loss: 0.5268 - val_accuracy: 0.9040 - val_loss: 0.2319
Epoch 2/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.8605 - loss: 0.3409
Epoch 2: val_accuracy improved from 0.90405 to 0.93790, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 50s 79ms/step - accuracy: 0.8605 - loss: 0.3409 - val_accuracy: 0.9379 - val_loss: 0.2260
Epoch 3/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8721 - loss: 0.3280
Epoch 3: val_accuracy did not improve from 0.93790
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 78ms/step - accuracy: 0.8721 - loss: 0.3279 - val_accuracy: 0.9301 - val_loss: 0.1933
Epoch 4/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8876 - loss: 0.2910
Epoch 4: val_accuracy improved from 0.93790 to 0.94651, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8876 - loss: 0.2910 - val_accuracy: 0.9465 - val_loss: 0.1743
Epoch 5/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8893 - loss: 0.2728
Epoch 5: val_accuracy improved from 0.94651 to 0.95212, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8893 - loss: 0.2728 - val_accuracy: 0.9521 - val_loss: 0.1669
Epoch 6/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8915 - loss: 0.2761
Epoch 6: val_accuracy did not improve from 0.95212
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8915 - loss: 0.2762 - val_accuracy: 0.9507 - val_loss: 0.1851
Epoch 7/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8880 - loss: 0.2758
Epoch 7: val_accuracy did not improve from 0.95212
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8880 - loss: 0.2757 - val_accuracy: 0.9445 - val_loss: 0.1551
Epoch 8/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.8964 - loss: 0.2666
Epoch 8: val_accuracy did not improve from 0.95212
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8964 - loss: 0.2666 - val_accuracy: 0.9499 - val_loss: 0.1479
Epoch 9/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.8958 - loss: 0.2614
Epoch 9: val_accuracy did not improve from 0.95212
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8958 - loss: 0.2614 - val_accuracy: 0.9379 - val_loss: 0.1630
Epoch 10/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8946 - loss: 0.2554
Epoch 10: val_accuracy did not improve from 0.95212
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8946 - loss: 0.2554 - val_accuracy: 0.9423 - val_loss: 0.1544
Epoch 11/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8951 - loss: 0.2569
Epoch 11: val_accuracy improved from 0.95212 to 0.95252, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8951 - loss: 0.2569 - val_accuracy: 0.9525 - val_loss: 0.1501
Epoch 12/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9000 - loss: 0.2446
Epoch 12: val_accuracy did not improve from 0.95252
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9000 - loss: 0.2447 - val_accuracy: 0.9191 - val_loss: 0.1890
Epoch 13/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8937 - loss: 0.2528
Epoch 13: val_accuracy improved from 0.95252 to 0.95493, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8937 - loss: 0.2528 - val_accuracy: 0.9549 - val_loss: 0.1397
Epoch 14/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9005 - loss: 0.2415
Epoch 14: val_accuracy did not improve from 0.95493
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9005 - loss: 0.2415 - val_accuracy: 0.9485 - val_loss: 0.1555
Epoch 15/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.8974 - loss: 0.2417
Epoch 15: val_accuracy did not improve from 0.95493
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8974 - loss: 0.2417 - val_accuracy: 0.9457 - val_loss: 0.1553
Epoch 16/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.8995 - loss: 0.2447
Epoch 16: val_accuracy improved from 0.95493 to 0.95573, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8995 - loss: 0.2447 - val_accuracy: 0.9557 - val_loss: 0.1309
Epoch 17/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9026 - loss: 0.2446
Epoch 17: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9026 - loss: 0.2446 - val_accuracy: 0.9539 - val_loss: 0.1293
Epoch 18/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9038 - loss: 0.2382
Epoch 18: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9038 - loss: 0.2382 - val_accuracy: 0.9501 - val_loss: 0.1430
Epoch 19/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9050 - loss: 0.2317
Epoch 19: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9050 - loss: 0.2317 - val_accuracy: 0.9333 - val_loss: 0.1672
Epoch 20/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 63ms/step - accuracy: 0.9014 - loss: 0.2362
Epoch 20: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9014 - loss: 0.2363 - val_accuracy: 0.9535 - val_loss: 0.1355
Epoch 21/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.9000 - loss: 0.2383
Epoch 21: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.9000 - loss: 0.2383 - val_accuracy: 0.9277 - val_loss: 0.1753
Epoch 22/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 64ms/step - accuracy: 0.8996 - loss: 0.2366
Epoch 22: val_accuracy did not improve from 0.95573
624/624 ━━━━━━━━━━━━━━━━━━━━ 49s 79ms/step - accuracy: 0.8996 - loss: 0.2366 - val_accuracy: 0.9425 - val_loss: 0.1502
Epoch 22: early stopping
Plot the train and validation accuracy
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Observations and insights:¶
- The model overfit to the training dataset and therefore is not well generalized.
Evaluating the model
model_5.evaluate(x_test, (y_test_encoded))
82/82 ━━━━━━━━━━━━━━━━━━━━ 9s 100ms/step - accuracy: 0.9524 - loss: 0.1359
[0.15787282586097717, 0.9399999976158142]
Plotting the classification report and confusion matrix
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_5.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 6s 70ms/step precision recall f1-score support 0 0.99 0.89 0.94 1300 1 0.90 0.99 0.94 1300 accuracy 0.94 2600 macro avg 0.95 0.94 0.94 2600 weighted avg 0.95 0.94 0.94 2600
Think about it:¶
- What observations and insights can be drawn from the confusion matrix and classification report?
- The model did far better at avoiding false negatives and was more inclined to give a false positive. We see that it performed much better on parasitized images. Given the greater cost of diagnosing someone with Malaria as not having Malaria (a false negative) we'd obviously rather a model give weighting such that false negatives are punished more heavily than false positives.
- Choose the model with the best accuracy scores from all the above models and save it as a final model.
model_4.save('final_model.h5')
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
Observations and Conclusions drawn from the final model: ¶
The final model (cnn_model_4)
- The test accuracy is over 98% just slightly below the training accuracy.
- Cross entropy loss was about 0.069.
- The test and training plots track very closely, suggesting little overfitting and very good generalization of the model.
- False positives and false negatives both occured the fewest times of any model, 20 and 19 times respectively.
Improvements that can be done:
- Can the model performance be improved using other pre-trained models or different CNN architecture?
- You can try to build a model using these HSV images and compare them with your other models.
CNN MODEL 6:
Model 6 with Swish Activation
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
# Model Using Swish Activation on the Conv Layers
from tensorflow.keras.activations import swish
def cnn_model_swish():
model = Sequential([
Conv2D(64, (3, 3), activation='swish', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='swish', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='swish', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model
model_swish = cnn_model_swish()
# Compile the model
model_swish.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
# Fitting the model with 30 epochs and validation_split of 20%
history=model_swish.fit(x_train,
y_train_encoded,
epochs = 30,
batch_size= 32, validation_split = 0.20, callbacks = [es, mc])
Epoch 1/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 27ms/step - accuracy: 0.6614 - loss: 0.5966
Epoch 1: val_accuracy improved from -inf to 0.94792, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 23s 32ms/step - accuracy: 0.6616 - loss: 0.5964 - val_accuracy: 0.9479 - val_loss: 0.1456
Epoch 2/30
623/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9501 - loss: 0.1471
Epoch 2: val_accuracy improved from 0.94792 to 0.96615, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9501 - loss: 0.1471 - val_accuracy: 0.9661 - val_loss: 0.1057
Epoch 3/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9646 - loss: 0.1096
Epoch 3: val_accuracy improved from 0.96615 to 0.96835, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9646 - loss: 0.1096 - val_accuracy: 0.9683 - val_loss: 0.1097
Epoch 4/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9697 - loss: 0.1001
Epoch 4: val_accuracy did not improve from 0.96835
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9697 - loss: 0.1001 - val_accuracy: 0.9677 - val_loss: 0.1345
Epoch 5/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9696 - loss: 0.1005
Epoch 5: val_accuracy improved from 0.96835 to 0.97095, saving model to best_model.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9695 - loss: 0.1005 - val_accuracy: 0.9710 - val_loss: 0.1358
Epoch 6/30
622/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9733 - loss: 0.0887
Epoch 6: val_accuracy did not improve from 0.97095
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9733 - loss: 0.0887 - val_accuracy: 0.9635 - val_loss: 0.1421
Epoch 7/30
624/624 ━━━━━━━━━━━━━━━━━━━━ 0s 24ms/step - accuracy: 0.9745 - loss: 0.0867
Epoch 7: val_accuracy did not improve from 0.97095
624/624 ━━━━━━━━━━━━━━━━━━━━ 16s 26ms/step - accuracy: 0.9745 - loss: 0.0867 - val_accuracy: 0.9589 - val_loss: 0.1540
Epoch 7: early stopping
model_swish.evaluate(x_test, (y_test_encoded))
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - accuracy: 0.9607 - loss: 0.1422
[0.15161262452602386, 0.9576923251152039]
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_5.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 5s 63ms/step precision recall f1-score support 0 0.99 0.89 0.94 1300 1 0.90 0.99 0.94 1300 accuracy 0.94 2600 macro avg 0.95 0.94 0.94 2600 weighted avg 0.95 0.94 0.94 2600
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
Observations:
- Swish activation didn't improve cnn_model_4
Model using HSV images
CNN MODEL 7:
Model 7 with HSV Images
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
# Model using HSV images
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
# Define paths
DATADIR_TEST = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
CATEGORIES = ["parasitized", "uninfected"]
IMG_SIZE = 128 # Resize for consistency
# Initialize lists
X = [] # Image data
y = [] # Labels
# Process images
for category in CATEGORIES:
path = os.path.join(DATADIR_TEST, category)
label = CATEGORIES.index(category) # Assign label (0: parasitized, 1: uninfected)
for img_name in os.listdir(path):
img_path = os.path.join(path, img_name)
img = cv2.imread(img_path)
if img is not None:
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # Resize
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV) # Convert to HSV
X.append(img_hsv)
y.append(label)
# Convert lists to numpy arrays
X = np.array(X) / 255.0 # Normalize pixel values
y = np.array(y)
# One-hot encode labels (since it's a binary classification)
y = to_categorical(y, num_classes=2)
# Split into training and test sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Define CNN model
model = Sequential([
# First convolutional layer
Conv2D(32, (3,3), activation='relu', input_shape=(IMG_SIZE, IMG_SIZE, 3)),
MaxPooling2D(pool_size=(2,2)),
Dropout(0.25),
# Second convolutional layer
Conv2D(64, (3,3), activation='relu'),
MaxPooling2D(pool_size=(2,2)),
Dropout(0.25),
# Third convolutional layer
Conv2D(128, (3,3), activation='relu'),
MaxPooling2D(pool_size=(2,2)),
Dropout(0.25),
# Flatten before fully connected layers
Flatten(),
# Fully connected layer
Dense(128, activation='relu'),
Dropout(0.5),
# Output layer (binary classification)
Dense(2, activation='softmax') # Two classes: parasitized, uninfected
])
# Compile the model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# Print model summary
model.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 126, 126, 32) │ 896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 63, 63, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout) │ (None, 63, 63, 32) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D) │ (None, 61, 61, 64) │ 18,496 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 30, 30, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout) │ (None, 30, 30, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D) │ (None, 28, 28, 128) │ 73,856 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_2 (MaxPooling2D) │ (None, 14, 14, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout) │ (None, 14, 14, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten (Flatten) │ (None, 25088) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense) │ (None, 128) │ 3,211,392 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_3 (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 2) │ 258 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 3,304,898 (12.61 MB)
Trainable params: 3,304,898 (12.61 MB)
Non-trainable params: 0 (0.00 B)
history = model.fit(
X_train, y_train,
epochs=30,
batch_size=32,
validation_data=(X_test, y_test)
)
Epoch 1/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 7s 33ms/step - accuracy: 0.5170 - loss: 0.7864 - val_accuracy: 0.6596 - val_loss: 0.6582
Epoch 2/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.6009 - loss: 0.6716 - val_accuracy: 0.6808 - val_loss: 0.5923
Epoch 3/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.7157 - loss: 0.5653 - val_accuracy: 0.9288 - val_loss: 0.3150
Epoch 4/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9083 - loss: 0.3016 - val_accuracy: 0.9577 - val_loss: 0.1732
Epoch 5/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9394 - loss: 0.1804 - val_accuracy: 0.9596 - val_loss: 0.1617
Epoch 6/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9526 - loss: 0.1581 - val_accuracy: 0.9577 - val_loss: 0.1185
Epoch 7/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9562 - loss: 0.1313 - val_accuracy: 0.9750 - val_loss: 0.0920
Epoch 8/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9650 - loss: 0.1036 - val_accuracy: 0.9712 - val_loss: 0.0899
Epoch 9/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9778 - loss: 0.0811 - val_accuracy: 0.9788 - val_loss: 0.0952
Epoch 10/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9767 - loss: 0.0846 - val_accuracy: 0.9769 - val_loss: 0.0791
Epoch 11/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9869 - loss: 0.0465 - val_accuracy: 0.9750 - val_loss: 0.0886
Epoch 12/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9819 - loss: 0.0499 - val_accuracy: 0.9731 - val_loss: 0.1188
Epoch 13/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9876 - loss: 0.0472 - val_accuracy: 0.9712 - val_loss: 0.1262
Epoch 14/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9897 - loss: 0.0377 - val_accuracy: 0.9788 - val_loss: 0.0905
Epoch 15/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9903 - loss: 0.0323 - val_accuracy: 0.9731 - val_loss: 0.1206
Epoch 16/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9899 - loss: 0.0448 - val_accuracy: 0.9731 - val_loss: 0.1194
Epoch 17/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9902 - loss: 0.0388 - val_accuracy: 0.9750 - val_loss: 0.1020
Epoch 18/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9884 - loss: 0.0420 - val_accuracy: 0.9692 - val_loss: 0.1390
Epoch 19/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9862 - loss: 0.0354 - val_accuracy: 0.9827 - val_loss: 0.0533
Epoch 20/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9964 - loss: 0.0180 - val_accuracy: 0.9788 - val_loss: 0.0714
Epoch 21/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9930 - loss: 0.0216 - val_accuracy: 0.9846 - val_loss: 0.0607
Epoch 22/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9955 - loss: 0.0148 - val_accuracy: 0.9769 - val_loss: 0.0940
Epoch 23/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9939 - loss: 0.0167 - val_accuracy: 0.9808 - val_loss: 0.0663
Epoch 24/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9993 - loss: 0.0056 - val_accuracy: 0.9769 - val_loss: 0.0826
Epoch 25/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9972 - loss: 0.0115 - val_accuracy: 0.9808 - val_loss: 0.0748
Epoch 26/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9987 - loss: 0.0055 - val_accuracy: 0.9808 - val_loss: 0.1243
Epoch 27/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.9963 - loss: 0.0090 - val_accuracy: 0.9846 - val_loss: 0.0703
Epoch 28/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9960 - loss: 0.0156 - val_accuracy: 0.9808 - val_loss: 0.0793
Epoch 29/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9993 - loss: 0.0046 - val_accuracy: 0.9788 - val_loss: 0.0948
Epoch 30/30
65/65 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.9991 - loss: 0.0052 - val_accuracy: 0.9846 - val_loss: 0.0936
model.evaluate(X_test, y_test)
17/17 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.9755 - loss: 0.1381
[0.09360247105360031, 0.9846153855323792]
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
pred = model_5.predict(x_test)
pred = np.argmax(pred, axis = 1)
y_true = np.argmax(y_test_encoded, axis = 1)
# Printing the classification report
print(classification_report(y_true, pred))
# Plotting the heatmap using confusion matrix
cm = confusion_matrix(y_true, pred)
plt.figure(figsize = (8, 5))
sns.heatmap(cm, annot = True, fmt = '.0f', xticklabels = ['parasitized', 'uninfected'], yticklabels= ['parasitized', 'uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 5s 57ms/step precision recall f1-score support 0 0.99 0.89 0.94 1300 1 0.90 0.99 0.94 1300 accuracy 0.94 2600 macro avg 0.95 0.94 0.94 2600 weighted avg 0.95 0.94 0.94 2600
Insights¶
Refined insights:¶
- What are the most meaningful insights from the data relevant to the problem?
- Models are very accurate at classifying Malaria in blood cells
- False negatives often are more common than false positives, so some weighting against false negatives might be important to add.
- Data augmentation doesn't hurt the models and may improve them with the right values of distortion.
- The HSV performed very well which I will look at investigating further in the next part of the capstone.
Comparison of various techniques and their relative performance:¶
- How do different techniques perform? Which one is performing relatively better? Is there scope to improve the performance further?
- All techniques performed relatively the same. Relu activation tended to work the best when used with 3 convulsional layers employing max pooling, 2 dense layers and a flattening layer, as seen with Model 4 (cnn_model_4). Potential for improvement is still there as average pooling can be used in certain scenarios instead of flatenning before the final fully connected layers. Using the HSV images will be part of the final solution.
Proposal for the final solution design:¶
- What model do you propose to be adopted? Why is this the best solution to adopt?
- I propose to continue to work on Model 4 (cnn_model_4) as it was the most accurate and produced the lowest ratio of FNs to FPs. However, weighting should be added to reduce the degree of false negatives. Expanding upon Model 4 by giving it more layers and varied activations may be worth of try. It may also make sense to add some of the augmented data to the original training data as the augmented data also performed well. Also, there may be more augmentation combinations that can be tried to improve the model further. Lastly, using HSV images with Model 4 may prove to be the best solution as the HSV images performed very well at the end of this milestone project.
from google.colab import files
# Convert to HTML
!jupyter nbconvert /content/drive/MyDrive/Colab Notebooks/Reference_Notebook_Malaria_Detection_Full_Code.ipynb --to html
[NbConvertApp] WARNING | pattern '/content/drive/MyDrive/Colab' matched no files
[NbConvertApp] WARNING | pattern 'Notebooks/Reference_Notebook_Malaria_Detection_Full_Code.ipynb' matched no files
This application is used to convert notebook files (*.ipynb)
to various other formats.
WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.
Options
=======
The options below are convenience aliases to configurable class-options,
as listed in the "Equivalent to" description-line of the aliases.
To see all configurable class-options for some <cmd>, use:
<cmd> --help-all
--debug
set log level to logging.DEBUG (maximize logging output)
Equivalent to: [--Application.log_level=10]
--show-config
Show the application's configuration (human-readable format)
Equivalent to: [--Application.show_config=True]
--show-config-json
Show the application's configuration (json format)
Equivalent to: [--Application.show_config_json=True]
--generate-config
generate default config file
Equivalent to: [--JupyterApp.generate_config=True]
-y
Answer yes to any questions instead of prompting.
Equivalent to: [--JupyterApp.answer_yes=True]
--execute
Execute the notebook prior to export.
Equivalent to: [--ExecutePreprocessor.enabled=True]
--allow-errors
Continue notebook execution even if one of the cells throws an error and include the error message in the cell output (the default behaviour is to abort conversion). This flag is only relevant if '--execute' was specified, too.
Equivalent to: [--ExecutePreprocessor.allow_errors=True]
--stdin
read a single notebook file from stdin. Write the resulting notebook with default basename 'notebook.*'
Equivalent to: [--NbConvertApp.from_stdin=True]
--stdout
Write notebook output to stdout instead of files.
Equivalent to: [--NbConvertApp.writer_class=StdoutWriter]
--inplace
Run nbconvert in place, overwriting the existing notebook (only
relevant when converting to notebook format)
Equivalent to: [--NbConvertApp.use_output_suffix=False --NbConvertApp.export_format=notebook --FilesWriter.build_directory=]
--clear-output
Clear output of current file and save in place,
overwriting the existing notebook.
Equivalent to: [--NbConvertApp.use_output_suffix=False --NbConvertApp.export_format=notebook --FilesWriter.build_directory= --ClearOutputPreprocessor.enabled=True]
--coalesce-streams
Coalesce consecutive stdout and stderr outputs into one stream (within each cell).
Equivalent to: [--NbConvertApp.use_output_suffix=False --NbConvertApp.export_format=notebook --FilesWriter.build_directory= --CoalesceStreamsPreprocessor.enabled=True]
--no-prompt
Exclude input and output prompts from converted document.
Equivalent to: [--TemplateExporter.exclude_input_prompt=True --TemplateExporter.exclude_output_prompt=True]
--no-input
Exclude input cells and output prompts from converted document.
This mode is ideal for generating code-free reports.
Equivalent to: [--TemplateExporter.exclude_output_prompt=True --TemplateExporter.exclude_input=True --TemplateExporter.exclude_input_prompt=True]
--allow-chromium-download
Whether to allow downloading chromium if no suitable version is found on the system.
Equivalent to: [--WebPDFExporter.allow_chromium_download=True]
--disable-chromium-sandbox
Disable chromium security sandbox when converting to PDF..
Equivalent to: [--WebPDFExporter.disable_sandbox=True]
--show-input
Shows code input. This flag is only useful for dejavu users.
Equivalent to: [--TemplateExporter.exclude_input=False]
--embed-images
Embed the images as base64 dataurls in the output. This flag is only useful for the HTML/WebPDF/Slides exports.
Equivalent to: [--HTMLExporter.embed_images=True]
--sanitize-html
Whether the HTML in Markdown cells and cell outputs should be sanitized..
Equivalent to: [--HTMLExporter.sanitize_html=True]
--log-level=<Enum>
Set the log level by value or name.
Choices: any of [0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL']
Default: 30
Equivalent to: [--Application.log_level]
--config=<Unicode>
Full path of a config file.
Default: ''
Equivalent to: [--JupyterApp.config_file]
--to=<Unicode>
The export format to be used, either one of the built-in formats
['asciidoc', 'custom', 'html', 'latex', 'markdown', 'notebook', 'pdf', 'python', 'qtpdf', 'qtpng', 'rst', 'script', 'slides', 'webpdf']
or a dotted object name that represents the import path for an
``Exporter`` class
Default: ''
Equivalent to: [--NbConvertApp.export_format]
--template=<Unicode>
Name of the template to use
Default: ''
Equivalent to: [--TemplateExporter.template_name]
--template-file=<Unicode>
Name of the template file to use
Default: None
Equivalent to: [--TemplateExporter.template_file]
--theme=<Unicode>
Template specific theme(e.g. the name of a JupyterLab CSS theme distributed
as prebuilt extension for the lab template)
Default: 'light'
Equivalent to: [--HTMLExporter.theme]
--sanitize_html=<Bool>
Whether the HTML in Markdown cells and cell outputs should be sanitized.This
should be set to True by nbviewer or similar tools.
Default: False
Equivalent to: [--HTMLExporter.sanitize_html]
--writer=<DottedObjectName>
Writer class used to write the
results of the conversion
Default: 'FilesWriter'
Equivalent to: [--NbConvertApp.writer_class]
--post=<DottedOrNone>
PostProcessor class used to write the
results of the conversion
Default: ''
Equivalent to: [--NbConvertApp.postprocessor_class]
--output=<Unicode>
Overwrite base name use for output files.
Supports pattern replacements '{notebook_name}'.
Default: '{notebook_name}'
Equivalent to: [--NbConvertApp.output_base]
--output-dir=<Unicode>
Directory to write output(s) to. Defaults
to output to the directory of each notebook. To recover
previous default behaviour (outputting to the current
working directory) use . as the flag value.
Default: ''
Equivalent to: [--FilesWriter.build_directory]
--reveal-prefix=<Unicode>
The URL prefix for reveal.js (version 3.x).
This defaults to the reveal CDN, but can be any url pointing to a copy
of reveal.js.
For speaker notes to work, this must be a relative path to a local
copy of reveal.js: e.g., "reveal.js".
If a relative path is given, it must be a subdirectory of the
current directory (from which the server is run).
See the usage documentation
(https://nbconvert.readthedocs.io/en/latest/usage.html#reveal-js-html-slideshow)
for more details.
Default: ''
Equivalent to: [--SlidesExporter.reveal_url_prefix]
--nbformat=<Enum>
The nbformat version to write.
Use this to downgrade notebooks.
Choices: any of [1, 2, 3, 4]
Default: 4
Equivalent to: [--NotebookExporter.nbformat_version]
Examples
--------
The simplest way to use nbconvert is
> jupyter nbconvert mynotebook.ipynb --to html
Options include ['asciidoc', 'custom', 'html', 'latex', 'markdown', 'notebook', 'pdf', 'python', 'qtpdf', 'qtpng', 'rst', 'script', 'slides', 'webpdf'].
> jupyter nbconvert --to latex mynotebook.ipynb
Both HTML and LaTeX support multiple output templates. LaTeX includes
'base', 'article' and 'report'. HTML includes 'basic', 'lab' and
'classic'. You can specify the flavor of the format used.
> jupyter nbconvert --to html --template lab mynotebook.ipynb
You can also pipe the output to stdout, rather than a file
> jupyter nbconvert mynotebook.ipynb --stdout
PDF is generated via latex
> jupyter nbconvert mynotebook.ipynb --to pdf
You can get (and serve) a Reveal.js-powered slideshow
> jupyter nbconvert myslides.ipynb --to slides --post serve
Multiple notebooks can be given at the command line in a couple of
different ways:
> jupyter nbconvert notebook*.ipynb
> jupyter nbconvert notebook1.ipynb notebook2.ipynb
or you can specify the notebooks list in a config file, containing::
c.NbConvertApp.notebooks = ["my_notebook.ipynb"]
> jupyter nbconvert --config mycfg.py
To see all available configurables, use `--help-all`.
Malaria Detection¶
Mount the Drive
# mount google drive
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
Loading libraries¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = "plotly_white"
import warnings
import os
import zipfile
import cv2
import tensorflow as tf
import keras
from tensorflow.keras.models import Sequential, Model, load_model
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, BatchNormalization, Activation, Input, LeakyReLU, Flatten, Dropout
from tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img
from tensorflow.keras import backend
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.optimizers import Adam, SGD, RMSprop
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
from tensorflow.keras import losses, optimizers
from google.colab.patches import cv2_imshow
warnings.filterwarnings('ignore')
# Import Data Augmentation package
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.preprocessing import LabelEncoder
Data Augmentation Model¶
import os
import cv2
from PIL import Image
import shutil
# Paths
train_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train"
test_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
# Remove corrupt or missing images as this as been an issue
def clean_invalid_images(directory):
"""Checks each image in a directory and removes any that are corrupt, unreadable, or missing."""
for class_folder in os.listdir(directory):
class_path = os.path.join(directory, class_folder)
if not os.path.isdir(class_path):
continue # Skip if it's not a directory
for filename in os.listdir(class_path):
file_path = os.path.join(class_path, filename)
try:
# Verify with PIL
img = Image.open(file_path)
img.verify()
# Verify with OpenCV
img = cv2.imread(file_path)
if img is None:
print(f"Removing unreadable image: {file_path}")
os.remove(file_path)
except Exception as e:
print(f"Removing corrupt/missing file: {file_path} ({e})")
os.remove(file_path)
# Clean train & test directories
clean_invalid_images(train_dir)
clean_invalid_images(test_dir)
# Now create data generators
datagen = ImageDataGenerator(
rescale=1.0/255, # Normalize pixel values to the range [0,1]
rotation_range=45, # Randomly rotate images up to 20 degrees
width_shift_range=0.2, # Randomly shift images horizontally by 20% of the width
height_shift_range=0.2, # Randomly shift images vertically by 20% of the height
shear_range=0.4, # Apply shearing transformations
zoom_range=0.3, # Randomly zoom in/out up to 20%
horizontal_flip=True, # Flip images horizontally at random
fill_mode='nearest' # Fill in missing pixels after transformations using the nearest pixels
)
train_generator = datagen.flow_from_directory(
train_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
validation_generator = ImageDataGenerator(rescale=1.0/255).flow_from_directory(
test_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
# Print class indices for debugging
print("Class Indices:", validation_generator.class_indices)
Found 24957 images belonging to 2 classes.
Found 2600 images belonging to 2 classes.
Class Indices: {'parasitized': 0, 'uninfected': 1}
datagen2 = ImageDataGenerator(
rescale=1.0/255, # Normalize pixel values to the range [0,1]
rotation_range=180, # Randomly rotate images up to 20 degrees
width_shift_range=0.2, # Randomly shift images horizontally by 20% of the width
height_shift_range=0.2, # Randomly shift images vertically by 20% of the height
shear_range=0.2, # Apply shearing transformations
zoom_range=0.5, # Randomly zoom in/out up to 20%
horizontal_flip=True, # Flip images horizontally at random
fill_mode='nearest' # Fill in missing pixels after transformations using the nearest pixels
)
train_generator2 = datagen2.flow_from_directory(
train_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
Found 24957 images belonging to 2 classes.
datagen3 = ImageDataGenerator(
rescale=1.0/255, # Normalize pixel values to the range [0,1]
rotation_range=90, # Randomly rotate images up to 20 degrees
width_shift_range=0.2, # Randomly shift images horizontally by 20% of the width
height_shift_range=0.2, # Randomly shift images vertically by 20% of the height
shear_range=0.5, # Apply shearing transformations
zoom_range=0.2, # Randomly zoom in/out up to 20%
horizontal_flip=True, # Flip images horizontally at random
fill_mode='nearest' # Fill in missing pixels after transformations using the nearest pixels
)
train_generator3 = datagen3.flow_from_directory(
train_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
Found 24957 images belonging to 2 classes.
datagen4 = ImageDataGenerator(
rescale=1.0/255, # Normalize pixel values to the range [0,1]
rotation_range=25, # Randomly rotate images up to 20 degrees
width_shift_range=0.25, # Randomly shift images horizontally by 20% of the width
height_shift_range=0.25, # Randomly shift images vertically by 20% of the height
shear_range=0.25, # Apply shearing transformations
zoom_range=0.25, # Randomly zoom in/out up to 20%
horizontal_flip=True, # Flip images horizontally at random
fill_mode='nearest' # Fill in missing pixels after transformations using the nearest pixels
)
train_generator4 = datagen4.flow_from_directory(
train_dir,
target_size=(128, 128),
batch_size=32,
class_mode='categorical'
)
Found 24957 images belonging to 2 classes.
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model():
model = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model
model = cnn_model()
model.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_2():
model_2 = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model_2
model_2 = cnn_model_2()
model_2.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_3():
model_3 = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model_3
model_3 = cnn_model_3()
model_3.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_4():
model_4 = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model_4
model_4 = cnn_model_4()
model_4.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
Using Callbacks
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model_1.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc2 = ModelCheckpoint('best_model_2.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc3 = ModelCheckpoint('best_model_3.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc4 = ModelCheckpoint('best_model_4.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
Fit and Train the model
history = model.fit(
train_generator,
epochs=20,
validation_data=validation_generator,
callbacks=[es, mc]
)
Epoch 1/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 349ms/step - accuracy: 0.5397 - loss: 0.6927 Epoch 1: val_accuracy improved from -inf to 0.87231, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 287s 361ms/step - accuracy: 0.5397 - loss: 0.6926 - val_accuracy: 0.8723 - val_loss: 0.3688 Epoch 2/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 196ms/step - accuracy: 0.8560 - loss: 0.4010 Epoch 2: val_accuracy improved from 0.87231 to 0.87769, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.8560 - loss: 0.4009 - val_accuracy: 0.8777 - val_loss: 0.3394 Epoch 3/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9015 - loss: 0.2858 Epoch 3: val_accuracy improved from 0.87769 to 0.96923, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 203ms/step - accuracy: 0.9016 - loss: 0.2858 - val_accuracy: 0.9692 - val_loss: 0.0996 Epoch 4/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9466 - loss: 0.1651 Epoch 4: val_accuracy improved from 0.96923 to 0.98115, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.9466 - loss: 0.1651 - val_accuracy: 0.9812 - val_loss: 0.0771 Epoch 5/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9470 - loss: 0.1619 Epoch 5: val_accuracy did not improve from 0.98115 780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.9470 - loss: 0.1619 - val_accuracy: 0.9769 - val_loss: 0.0833 Epoch 6/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.9491 - loss: 0.1639 Epoch 6: val_accuracy improved from 0.98115 to 0.98423, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.9491 - loss: 0.1639 - val_accuracy: 0.9842 - val_loss: 0.0617 Epoch 7/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9504 - loss: 0.1496 Epoch 7: val_accuracy improved from 0.98423 to 0.98500, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 203ms/step - accuracy: 0.9504 - loss: 0.1496 - val_accuracy: 0.9850 - val_loss: 0.0610 Epoch 8/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 192ms/step - accuracy: 0.9502 - loss: 0.1530 Epoch 8: val_accuracy did not improve from 0.98500 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9502 - loss: 0.1530 - val_accuracy: 0.9850 - val_loss: 0.0638 Epoch 9/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 192ms/step - accuracy: 0.9549 - loss: 0.1418 Epoch 9: val_accuracy did not improve from 0.98500 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9549 - loss: 0.1419 - val_accuracy: 0.9842 - val_loss: 0.0563 Epoch 10/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9531 - loss: 0.1481 Epoch 10: val_accuracy did not improve from 0.98500 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9531 - loss: 0.1481 - val_accuracy: 0.9723 - val_loss: 0.0696 Epoch 11/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9515 - loss: 0.1478 Epoch 11: val_accuracy improved from 0.98500 to 0.98692, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 203ms/step - accuracy: 0.9515 - loss: 0.1478 - val_accuracy: 0.9869 - val_loss: 0.0465 Epoch 12/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9555 - loss: 0.1383 Epoch 12: val_accuracy did not improve from 0.98692 780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 203ms/step - accuracy: 0.9555 - loss: 0.1383 - val_accuracy: 0.9792 - val_loss: 0.0544 Epoch 13/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9563 - loss: 0.1355 Epoch 13: val_accuracy improved from 0.98692 to 0.98731, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9563 - loss: 0.1355 - val_accuracy: 0.9873 - val_loss: 0.0473 Epoch 14/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9531 - loss: 0.1418 Epoch 14: val_accuracy did not improve from 0.98731 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9532 - loss: 0.1418 - val_accuracy: 0.9865 - val_loss: 0.0494 Epoch 15/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9561 - loss: 0.1351 Epoch 15: val_accuracy did not improve from 0.98731 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9561 - loss: 0.1351 - val_accuracy: 0.9831 - val_loss: 0.0552 Epoch 16/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.9550 - loss: 0.1358 Epoch 16: val_accuracy improved from 0.98731 to 0.98769, saving model to best_model_1.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.9550 - loss: 0.1358 - val_accuracy: 0.9877 - val_loss: 0.0532 Epoch 16: early stopping
history2 = model.fit(
train_generator2,
epochs=20,
validation_data=validation_generator,
callbacks=[es, mc2]
)
Epoch 1/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.9433 - loss: 0.1629 Epoch 1: val_accuracy improved from -inf to 0.98769, saving model to best_model_2.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.9433 - loss: 0.1629 - val_accuracy: 0.9877 - val_loss: 0.0421 Epoch 2/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9376 - loss: 0.1755 Epoch 2: val_accuracy did not improve from 0.98769 780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.9376 - loss: 0.1755 - val_accuracy: 0.9850 - val_loss: 0.0458 Epoch 3/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 196ms/step - accuracy: 0.9385 - loss: 0.1747 Epoch 3: val_accuracy did not improve from 0.98769 780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.9385 - loss: 0.1747 - val_accuracy: 0.9846 - val_loss: 0.0620 Epoch 4/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 196ms/step - accuracy: 0.9414 - loss: 0.1690 Epoch 4: val_accuracy did not improve from 0.98769 780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.9414 - loss: 0.1690 - val_accuracy: 0.9835 - val_loss: 0.0533 Epoch 5/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9380 - loss: 0.1770 Epoch 5: val_accuracy did not improve from 0.98769 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9380 - loss: 0.1770 - val_accuracy: 0.9862 - val_loss: 0.0458 Epoch 6/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.9410 - loss: 0.1647 Epoch 6: val_accuracy did not improve from 0.98769 780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.9410 - loss: 0.1647 - val_accuracy: 0.9685 - val_loss: 0.0841 Epoch 6: early stopping
history3 = model_3.fit(
train_generator3,
epochs=20,
validation_data=validation_generator,
callbacks=[es, mc3]
)
Epoch 1/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.6369 - loss: 0.6182 Epoch 1: val_accuracy improved from -inf to 0.92962, saving model to best_model_3.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 161s 203ms/step - accuracy: 0.6371 - loss: 0.6180 - val_accuracy: 0.9296 - val_loss: 0.2855 Epoch 2/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.8960 - loss: 0.3124 Epoch 2: val_accuracy improved from 0.92962 to 0.98269, saving model to best_model_3.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.8961 - loss: 0.3123 - val_accuracy: 0.9827 - val_loss: 0.0920 Epoch 3/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 192ms/step - accuracy: 0.9467 - loss: 0.1630 Epoch 3: val_accuracy did not improve from 0.98269 780/780 ━━━━━━━━━━━━━━━━━━━━ 157s 201ms/step - accuracy: 0.9467 - loss: 0.1630 - val_accuracy: 0.9812 - val_loss: 0.0744 Epoch 4/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 198ms/step - accuracy: 0.9566 - loss: 0.1458 Epoch 4: val_accuracy improved from 0.98269 to 0.98423, saving model to best_model_3.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 162s 207ms/step - accuracy: 0.9566 - loss: 0.1458 - val_accuracy: 0.9842 - val_loss: 0.0698 Epoch 5/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.9565 - loss: 0.1387 Epoch 5: val_accuracy did not improve from 0.98423 780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.9565 - loss: 0.1387 - val_accuracy: 0.9827 - val_loss: 0.0628 Epoch 6/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 197ms/step - accuracy: 0.9568 - loss: 0.1318 Epoch 6: val_accuracy improved from 0.98423 to 0.98538, saving model to best_model_3.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 161s 206ms/step - accuracy: 0.9568 - loss: 0.1318 - val_accuracy: 0.9854 - val_loss: 0.0476 Epoch 7/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 197ms/step - accuracy: 0.9571 - loss: 0.1335 Epoch 7: val_accuracy did not improve from 0.98538 780/780 ━━━━━━━━━━━━━━━━━━━━ 161s 207ms/step - accuracy: 0.9571 - loss: 0.1335 - val_accuracy: 0.9592 - val_loss: 0.0956 Epoch 8/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 197ms/step - accuracy: 0.9566 - loss: 0.1367 Epoch 8: val_accuracy did not improve from 0.98538 780/780 ━━━━━━━━━━━━━━━━━━━━ 161s 207ms/step - accuracy: 0.9566 - loss: 0.1367 - val_accuracy: 0.9846 - val_loss: 0.0494 Epoch 9/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 196ms/step - accuracy: 0.9573 - loss: 0.1269 Epoch 9: val_accuracy did not improve from 0.98538 780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 206ms/step - accuracy: 0.9573 - loss: 0.1269 - val_accuracy: 0.9854 - val_loss: 0.0493 Epoch 10/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9594 - loss: 0.1261 Epoch 10: val_accuracy did not improve from 0.98538 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 202ms/step - accuracy: 0.9594 - loss: 0.1261 - val_accuracy: 0.9835 - val_loss: 0.0514 Epoch 11/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.9617 - loss: 0.1197 Epoch 11: val_accuracy did not improve from 0.98538 780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 203ms/step - accuracy: 0.9617 - loss: 0.1197 - val_accuracy: 0.9788 - val_loss: 0.0563 Epoch 11: early stopping
history4 = model_4.fit(
train_generator4,
epochs=20,
validation_data=validation_generator,
callbacks=[es, mc4]
)
Epoch 1/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.5368 - loss: 0.6961 Epoch 1: val_accuracy improved from -inf to 0.52000, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 163s 206ms/step - accuracy: 0.5368 - loss: 0.6961 - val_accuracy: 0.5200 - val_loss: 0.6883 Epoch 2/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.5999 - loss: 0.6636 Epoch 2: val_accuracy improved from 0.52000 to 0.53885, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.5999 - loss: 0.6636 - val_accuracy: 0.5388 - val_loss: 0.6721 Epoch 3/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 195ms/step - accuracy: 0.6439 - loss: 0.6454 Epoch 3: val_accuracy improved from 0.53885 to 0.89192, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 160s 205ms/step - accuracy: 0.6440 - loss: 0.6453 - val_accuracy: 0.8919 - val_loss: 0.3426 Epoch 4/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 194ms/step - accuracy: 0.8741 - loss: 0.3379 Epoch 4: val_accuracy improved from 0.89192 to 0.97423, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 159s 204ms/step - accuracy: 0.8741 - loss: 0.3378 - val_accuracy: 0.9742 - val_loss: 0.1284 Epoch 5/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 193ms/step - accuracy: 0.9159 - loss: 0.2406 Epoch 5: val_accuracy did not improve from 0.97423 780/780 ━━━━━━━━━━━━━━━━━━━━ 158s 203ms/step - accuracy: 0.9159 - loss: 0.2406 - val_accuracy: 0.9642 - val_loss: 0.1190 Epoch 6/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 189ms/step - accuracy: 0.9267 - loss: 0.2119 Epoch 6: val_accuracy did not improve from 0.97423 780/780 ━━━━━━━━━━━━━━━━━━━━ 155s 199ms/step - accuracy: 0.9267 - loss: 0.2119 - val_accuracy: 0.9569 - val_loss: 0.1355 Epoch 7/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 187ms/step - accuracy: 0.9195 - loss: 0.2186 Epoch 7: val_accuracy did not improve from 0.97423 780/780 ━━━━━━━━━━━━━━━━━━━━ 154s 197ms/step - accuracy: 0.9195 - loss: 0.2186 - val_accuracy: 0.9731 - val_loss: 0.0971 Epoch 8/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 190ms/step - accuracy: 0.9282 - loss: 0.2015 Epoch 8: val_accuracy did not improve from 0.97423 780/780 ━━━━━━━━━━━━━━━━━━━━ 156s 200ms/step - accuracy: 0.9282 - loss: 0.2015 - val_accuracy: 0.9738 - val_loss: 0.1017 Epoch 9/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 189ms/step - accuracy: 0.9338 - loss: 0.1868 Epoch 9: val_accuracy did not improve from 0.97423 780/780 ━━━━━━━━━━━━━━━━━━━━ 155s 198ms/step - accuracy: 0.9338 - loss: 0.1869 - val_accuracy: 0.9454 - val_loss: 0.1373 Epoch 10/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 192ms/step - accuracy: 0.9316 - loss: 0.1883 Epoch 10: val_accuracy improved from 0.97423 to 0.98038, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 157s 201ms/step - accuracy: 0.9316 - loss: 0.1883 - val_accuracy: 0.9804 - val_loss: 0.0767 Epoch 11/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 191ms/step - accuracy: 0.9349 - loss: 0.1872 Epoch 11: val_accuracy did not improve from 0.98038 780/780 ━━━━━━━━━━━━━━━━━━━━ 157s 201ms/step - accuracy: 0.9349 - loss: 0.1872 - val_accuracy: 0.9800 - val_loss: 0.0844 Epoch 12/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 191ms/step - accuracy: 0.9374 - loss: 0.1839 Epoch 12: val_accuracy did not improve from 0.98038 780/780 ━━━━━━━━━━━━━━━━━━━━ 157s 201ms/step - accuracy: 0.9374 - loss: 0.1839 - val_accuracy: 0.9796 - val_loss: 0.0875 Epoch 13/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 192ms/step - accuracy: 0.9414 - loss: 0.1747 Epoch 13: val_accuracy did not improve from 0.98038 780/780 ━━━━━━━━━━━━━━━━━━━━ 157s 201ms/step - accuracy: 0.9414 - loss: 0.1747 - val_accuracy: 0.9796 - val_loss: 0.0708 Epoch 14/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 189ms/step - accuracy: 0.9436 - loss: 0.1653 Epoch 14: val_accuracy improved from 0.98038 to 0.98115, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 154s 198ms/step - accuracy: 0.9436 - loss: 0.1653 - val_accuracy: 0.9812 - val_loss: 0.0758 Epoch 15/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 190ms/step - accuracy: 0.9352 - loss: 0.1849 Epoch 15: val_accuracy improved from 0.98115 to 0.98346, saving model to best_model_4.h5
WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`.
780/780 ━━━━━━━━━━━━━━━━━━━━ 156s 200ms/step - accuracy: 0.9352 - loss: 0.1849 - val_accuracy: 0.9835 - val_loss: 0.0765 Epoch 16/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 191ms/step - accuracy: 0.9392 - loss: 0.1746 Epoch 16: val_accuracy did not improve from 0.98346 780/780 ━━━━━━━━━━━━━━━━━━━━ 156s 200ms/step - accuracy: 0.9392 - loss: 0.1746 - val_accuracy: 0.9827 - val_loss: 0.0671 Epoch 17/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 191ms/step - accuracy: 0.9456 - loss: 0.1678 Epoch 17: val_accuracy did not improve from 0.98346 780/780 ━━━━━━━━━━━━━━━━━━━━ 156s 200ms/step - accuracy: 0.9456 - loss: 0.1678 - val_accuracy: 0.9831 - val_loss: 0.0649 Epoch 18/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 189ms/step - accuracy: 0.9445 - loss: 0.1693 Epoch 18: val_accuracy did not improve from 0.98346 780/780 ━━━━━━━━━━━━━━━━━━━━ 155s 198ms/step - accuracy: 0.9445 - loss: 0.1693 - val_accuracy: 0.9531 - val_loss: 0.1234 Epoch 19/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 187ms/step - accuracy: 0.9414 - loss: 0.1716 Epoch 19: val_accuracy did not improve from 0.98346 780/780 ━━━━━━━━━━━━━━━━━━━━ 153s 197ms/step - accuracy: 0.9414 - loss: 0.1716 - val_accuracy: 0.9835 - val_loss: 0.0762 Epoch 20/20 780/780 ━━━━━━━━━━━━━━━━━━━━ 0s 188ms/step - accuracy: 0.9435 - loss: 0.1726 Epoch 20: val_accuracy did not improve from 0.98346 780/780 ━━━━━━━━━━━━━━━━━━━━ 154s 197ms/step - accuracy: 0.9435 - loss: 0.1726 - val_accuracy: 0.9819 - val_loss: 0.0707
Evaluating the model
# Evaluate the model on the test dataset from datagen
model.evaluate(validation_generator)
82/82 ━━━━━━━━━━━━━━━━━━━━ 7s 86ms/step - accuracy: 0.9703 - loss: 0.0856
[0.08412174880504608, 0.9684615135192871]
model_2.evaluate(validation_generator)
82/82 ━━━━━━━━━━━━━━━━━━━━ 8s 91ms/step - accuracy: 0.5044 - loss: 0.6935
[0.6932710409164429, 0.5026922821998596]
model_3.evaluate(validation_generator)
82/82 ━━━━━━━━━━━━━━━━━━━━ 7s 90ms/step - accuracy: 0.9815 - loss: 0.0546
[0.05625525116920471, 0.9788461327552795]
model_4.evaluate(validation_generator)
82/82 ━━━━━━━━━━━━━━━━━━━━ 7s 87ms/step - accuracy: 0.9833 - loss: 0.0676
[0.07073481380939484, 0.9819231033325195]
Plot the train and validation accuracy
# Set test image labels
import os
import cv2
import numpy as np
from tensorflow.keras.utils import to_categorical
# Define the test dataset directory
DATADIR_TEST = '/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test'
def load_images_and_labels(directory, target_size=(128, 128)):
"""Loads images from a directory and one-hot encodes labels."""
images, labels = [], []
class_labels = {class_name: idx for idx, class_name in enumerate(os.listdir(directory))}
for class_name, class_idx in class_labels.items():
class_path = os.path.join(directory, class_name)
if not os.path.isdir(class_path):
continue # Skip if not a directory
for filename in os.listdir(class_path):
file_path = os.path.join(class_path, filename)
img = cv2.imread(file_path)
if img is not None:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert BGR to RGB
img = cv2.resize(img, target_size) # Resize to model input size
images.append(img)
labels.append(class_idx) # Store class index
# Convert to NumPy arrays
X = np.array(images) / 255.0 # Normalize images
y = to_categorical(labels, num_classes=len(class_labels)) # One-hot encode labels
return X, y, class_labels # Return class mapping too
# Load test images and labels
X_test, y_test, class_labels = load_images_and_labels(DATADIR_TEST)
# Print the shapes to verify
print(f"Shape of X_test: {X_test.shape}, y_test: {y_test.shape}")
print(f"Class Labels Mapping: {class_labels}") # Shows { 'Parasitized': 0, 'Uninfected': 1 }
Shape of X_test: (2600, 128, 128, 3), y_test: (2600, 2)
Class Labels Mapping: {'parasitized': 0, 'uninfected': 1}
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# Generate predictions (batch-wise, avoiding RAM overload)
pred = model.predict(X_test, verbose=1) # Use validation set!
pred = np.argmax(pred, axis=1) # Convert probabilities to class labels
# Get true labels correctly
y_true = y_test.argmax(axis=1) # Convert one-hot encoded labels to class labels
# Printing the classification report
print(classification_report(y_true, pred, target_names=['Parasitized', 'Uninfected']))
# Confusion Matrix
cm = confusion_matrix(y_true, pred)
# Plot Confusion Matrix as Heatmap
plt.figure(figsize=(8, 5))
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues',
xticklabels=['Parasitized', 'Uninfected'],
yticklabels=['Parasitized', 'Uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step precision recall f1-score support Parasitized 0.98 0.96 0.97 1300 Uninfected 0.96 0.98 0.97 1300 accuracy 0.97 2600 macro avg 0.97 0.97 0.97 2600 weighted avg 0.97 0.97 0.97 2600
# Plotting the training and validation accuracies for each epoch
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# Generate predictions (batch-wise, avoiding RAM overload)
pred = model_2.predict(X_test, verbose=1) # Use validation set!
pred = np.argmax(pred, axis=1) # Convert probabilities to class labels
# Get true labels correctly
y_true = y_test.argmax(axis=1) # Convert one-hot encoded labels to class labels
# Printing the classification report
print(classification_report(y_true, pred, target_names=['Parasitized', 'Uninfected']))
# Confusion Matrix
cm = confusion_matrix(y_true, pred)
# Plot Confusion Matrix as Heatmap
plt.figure(figsize=(8, 5))
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues',
xticklabels=['Parasitized', 'Uninfected'],
yticklabels=['Parasitized', 'Uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step precision recall f1-score support Parasitized 0.49 0.52 0.50 1300 Uninfected 0.49 0.45 0.47 1300 accuracy 0.49 2600 macro avg 0.49 0.49 0.49 2600 weighted avg 0.49 0.49 0.49 2600
# Plotting the training and validation accuracies for each epoch
plt.plot(history2.history['accuracy'])
plt.plot(history2.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# Generate predictions (batch-wise, avoiding RAM overload)
pred = model_3.predict(X_test, verbose=1) # Use validation set!
pred = np.argmax(pred, axis=1) # Convert probabilities to class labels
# Get true labels correctly
y_true = y_test.argmax(axis=1) # Convert one-hot encoded labels to class labels
# Printing the classification report
print(classification_report(y_true, pred, target_names=['Parasitized', 'Uninfected']))
# Confusion Matrix
cm = confusion_matrix(y_true, pred)
# Plot Confusion Matrix as Heatmap
plt.figure(figsize=(8, 5))
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues',
xticklabels=['Parasitized', 'Uninfected'],
yticklabels=['Parasitized', 'Uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step precision recall f1-score support Parasitized 0.97 0.99 0.98 1300 Uninfected 0.99 0.97 0.98 1300 accuracy 0.98 2600 macro avg 0.98 0.98 0.98 2600 weighted avg 0.98 0.98 0.98 2600
# Plotting the training and validation accuracies for each epoch
plt.plot(history3.history['accuracy'])
plt.plot(history3.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# Generate predictions (batch-wise, avoiding RAM overload)
pred = model_4.predict(X_test, verbose=1) # Use validation set!
pred = np.argmax(pred, axis=1) # Convert probabilities to class labels
# Get true labels correctly
y_true = y_test.argmax(axis=1) # Convert one-hot encoded labels to class labels
# Printing the classification report
print(classification_report(y_true, pred, target_names=['Parasitized', 'Uninfected']))
# Confusion Matrix
cm = confusion_matrix(y_true, pred)
# Plot Confusion Matrix as Heatmap
plt.figure(figsize=(8, 5))
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues',
xticklabels=['Parasitized', 'Uninfected'],
yticklabels=['Parasitized', 'Uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
82/82 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step precision recall f1-score support Parasitized 0.98 0.98 0.98 1300 Uninfected 0.98 0.98 0.98 1300 accuracy 0.98 2600 macro avg 0.98 0.98 0.98 2600 weighted avg 0.98 0.98 0.98 2600
# Plotting the training and validation accuracies for each epoch
plt.plot(history4.history['accuracy'])
plt.plot(history4.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')
plt.show()
import matplotlib.pyplot as plt
# Extract accuracy values from history objects
epochs = range(1, len(history.history['accuracy']) + 1)
# Training Accuracy
train_acc_1 = history.history['accuracy']
train_acc_2 = history2.history['accuracy']
train_acc_3 = history3.history['accuracy']
train_acc_4 = history4.history['accuracy']
# Validation Accuracy
val_acc_1 = history.history['val_accuracy']
val_acc_2 = history2.history['val_accuracy']
val_acc_3 = history3.history['val_accuracy']
val_acc_4 = history4.history['val_accuracy']
# Plot Training Accuracy
plt.figure(figsize=(10, 5))
plt.plot(epochs, train_acc_1, 'o-', label='Model 1 Train')
plt.plot(epochs, train_acc_2, 's-', label='Model 2 Train')
plt.plot(epochs, train_acc_3, '^-', label='Model 3 Train')
plt.plot(epochs, train_acc_4, 'd-', label='Model 4 Train')
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.title("Training Accuracy Comparison")
plt.legend()
plt.grid()
plt.show()
# Plot Validation Accuracy
plt.figure(figsize=(10, 5))
plt.plot(epochs, val_acc_1, 'o-', label='Model 1 Val')
plt.plot(epochs, val_acc_2, 's-', label='Model 2 Val')
plt.plot(epochs, val_acc_3, '^-', label='Model 3 Val')
plt.plot(epochs, val_acc_4, 'd-', label='Model 4 Val')
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.title("Validation Accuracy Comparison")
plt.legend()
plt.grid()
plt.show()
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-37-d073c22730ea> in <cell line: 0>() 19 plt.figure(figsize=(10, 5)) 20 plt.plot(epochs, train_acc_1, 'o-', label='Model 1 Train') ---> 21 plt.plot(epochs, train_acc_2, 's-', label='Model 2 Train') 22 plt.plot(epochs, train_acc_3, '^-', label='Model 3 Train') 23 plt.plot(epochs, train_acc_4, 'd-', label='Model 4 Train') /usr/local/lib/python3.11/dist-packages/matplotlib/pyplot.py in plot(scalex, scaley, data, *args, **kwargs) 3827 **kwargs, 3828 ) -> list[Line2D]: -> 3829 return gca().plot( 3830 *args, 3831 scalex=scalex, /usr/local/lib/python3.11/dist-packages/matplotlib/axes/_axes.py in plot(self, scalex, scaley, data, *args, **kwargs) 1775 """ 1776 kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D) -> 1777 lines = [*self._get_lines(self, *args, data=data, **kwargs)] 1778 for line in lines: 1779 self.add_line(line) /usr/local/lib/python3.11/dist-packages/matplotlib/axes/_base.py in __call__(self, axes, data, return_kwargs, *args, **kwargs) 295 this += args[0], 296 args = args[1:] --> 297 yield from self._plot_args( 298 axes, this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey, 299 return_kwargs=return_kwargs /usr/local/lib/python3.11/dist-packages/matplotlib/axes/_base.py in _plot_args(self, axes, tup, kwargs, return_kwargs, ambiguous_fmt_datakey) 492 493 if x.shape[0] != y.shape[0]: --> 494 raise ValueError(f"x and y must have same first dimension, but " 495 f"have shapes {x.shape} and {y.shape}") 496 if x.ndim > 2 or y.ndim > 2: ValueError: x and y must have same first dimension, but have shapes (16,) and (6,)
Think about it:¶
- What observations and insights can be drawn from the confusion matrix and classification report?
- The model did far better at avoiding false negatives and was more inclined to give a false positive. We see that it performed much better on parasitized images. Given the greater cost of diagnosing someone with Malaria as not having Malaria (a false negative) we'd obviously rather a model give weighting such that false negatives are punished more heavily than false positives.
- Choose the model with the best accuracy scores from all the above models and save it as a final model.
Improvements that can be done:
- Can the model performance be improved using other pre-trained models or different CNN architecture?
- You can try to build a model using these HSV images and compare them with your other models.
Model using HSV images
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
import os
import cv2
import numpy as np
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
from PIL import Image
# Paths
train_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/train"
test_dir = "/content/drive/MyDrive/Python Course/Capstone Project/cell_images/test"
# Function to remove corrupt or missing images
def clean_invalid_images(directory):
"""Checks each image in a directory and removes any that are corrupt, unreadable, or missing."""
for class_folder in os.listdir(directory):
class_path = os.path.join(directory, class_folder)
if not os.path.isdir(class_path):
continue # Skip if it's not a directory
for filename in os.listdir(class_path):
file_path = os.path.join(class_path, filename)
try:
# Verify with PIL
img = Image.open(file_path)
img.verify()
# Verify with OpenCV
img = cv2.imread(file_path)
if img is None:
print(f"Removing unreadable image: {file_path}")
os.remove(file_path)
except Exception as e:
print(f"Removing corrupt/missing file: {file_path} ({e})")
os.remove(file_path)
# Clean train & test directories
clean_invalid_images(train_dir)
clean_invalid_images(test_dir)
# Function to load images & convert to HSV
def load_images_hsv(directory, target_size=(224, 224)):
"""Loads images from a directory and converts them to HSV format."""
images, labels = [], []
class_labels = {class_name: idx for idx, class_name in enumerate(os.listdir(directory))}
for class_name, class_idx in class_labels.items():
class_path = os.path.join(directory, class_name)
if not os.path.isdir(class_path):
continue # Skip if not a directory
for filename in os.listdir(class_path):
file_path = os.path.join(class_path, filename)
img = cv2.imread(file_path)
if img is not None:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Convert to RGB (OpenCV loads as BGR)
img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) # Convert to HSV
img = cv2.resize(img, target_size) # Resize to match input size
images.append(img)
labels.append(class_idx)
return np.array(images) / 255.0, to_categorical(labels) # Normalize and one-hot encode labels
# Load train & validation data
X_train, y_train = load_images_hsv(train_dir)
X_test, y_test = load_images_hsv(test_dir)
# Define ImageDataGenerator (without color transformations, since images are already in HSV)
datagen = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
# Create augmented dataset
train_generator = datagen.flow(X_train, y_train, batch_size=32)
test_generator = datagen.flow(X_test, y_test, batch_size=32)
# Verify the output
x_batch, y_batch = next(train_generator)
print("Batch Shape:", x_batch.shape) # Expected: (batch_size, img_height, img_width, 3)
print("Label Shape:", y_batch.shape)
backend.clear_session()
# Fixing the seed for random number generators so that we can ensure we receive the same output everytime
np.random.seed(42)
import random
random.seed(42)
tf.random.set_seed(42)
def cnn_model_hsv():
model_hsv = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model_hsv
model_hsv = cnn_model_hsv()
model_hsv.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
# The following lines of code saves the best model's parameters if training accuracy goes down on further training
es = EarlyStopping(monitor = 'val_loss', mode = 'min', verbose = 1, patience = 5)
mc = ModelCheckpoint('best_model_hsv.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)
history_hsv = model_hsv.fit(
train_generator,
epochs=20,
validation_data=test_generator,
callbacks=[es, mc]
)
model_hsv.evaluate(test_generator)
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, confusion_matrix
# Generate predictions (batch-wise, avoiding RAM overload)
pred = model_hsv.predict(X_test, verbose=1) # Use validation set!
pred = np.argmax(pred, axis=1) # Convert probabilities to class labels
# Get true labels correctly
y_true = y_test.argmax(axis=1) # Convert one-hot encoded labels to class labels
# Printing the classification report
print(classification_report(y_true, pred, target_names=['Parasitized', 'Uninfected']))
# Confusion Matrix
cm = confusion_matrix(y_true, pred)
# Plot Confusion Matrix as Heatmap
plt.figure(figsize=(8, 5))
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues',
xticklabels=['Parasitized', 'Uninfected'],
yticklabels=['Parasitized', 'Uninfected'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
Insights¶
Refined insights:¶
- What are the most meaningful insights from the data relevant to the problem?
- Models are very accurate at classifying Malaria in blood cells
- False negatives often are more common than false positives, so some weighting against false negatives might be important to add.
- Data augmentation doesn't hurt the models and may improve them with the right values of distortion.
- The HSV performed very well which I will look at investigating further in the next part of the capstone.
Comparison of various techniques and their relative performance:¶
- How do different techniques perform? Which one is performing relatively better? Is there scope to improve the performance further?
- All techniques performed relatively the same. Relu activation tended to work the best when used with 3 convulsional layers employing max pooling, 2 dense layers and a flattening layer, as seen with Model 4 (cnn_model_4). Potential for improvement is still there as average pooling can be used in certain scenarios instead of flatenning before the final fully connected layers. Using the HSV images will be part of the final solution.
Proposal for the final solution design:¶
- What model do you propose to be adopted? Why is this the best solution to adopt?
- I propose to continue to work on Model 4 (cnn_model_4) as it was the most accurate and produced the lowest ratio of FNs to FPs. However, weighting should be added to reduce the degree of false negatives. Expanding upon Model 4 by giving it more layers and varied activations may be worth of try. It may also make sense to add some of the augmented data to the original training data as the augmented data also performed well. Also, there may be more augmentation combinations that can be tried to improve the model further. Lastly, using HSV images with Model 4 may prove to be the best solution as the HSV images performed very well at the end of this milestone project.
from google.colab import files
# Convert to HTML
!jupyter nbconvert "/content/drive/MyDrive/Colab Notebooks/Reference_Notebook_Malaria_Detection_Full_Code.ipynb" --to html
Executive Summary:¶
While all models performed well some did a better job at avoiding false negatives, which in the use case of detecting malaria, is important so a person with the disease is not diagnosed as uninfected. CNN Model 4, the model that used augmented data, was not only the most accurate (over 98% on the validation set), it also had the lowest categorical cross entropy loss, and the highest precision, recall, and f1-score of any of the models. Below is a look back at the structure of the CNN model itself:
def cnn_model_4():
model = Sequential([
Conv2D(64, (3, 3), activation='relu', padding="same", input_shape=(128, 128, 3)),
MaxPooling2D(2, 2),
Conv2D(64, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Conv2D(128, (3, 3), activation='relu', padding="same"),
MaxPooling2D(2, 2),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # Regularization to prevent overfitting
Dense(2, activation='softmax') # Adjust output neurons based on your classes
])
return model
model_4 = cnn_model_4()
MODEL SUMMARY:
The model is a sequential CNN built using Keras' Sequential API. It processes input images of size 128x128 with 3 color channels (RGB). It consists of three convolutional layers with ReLU activation and same padding to maintain spatial dimensions. Each convolutional layer is followed by a MaxPooling layer to reduce spatial dimensions and extract features. The extracted features are flattened into a 1D vector. A fully connected Dense layer with 128 neurons applies ReLU activation for learning complex patterns. A Dropout layer (0.5) is used for regularization to prevent overfitting. The final output layer has 2 neurons with softmax activation, indicating that this model is likely used for binary classification (e.g., distinguishing between two classes such as "parasitized" vs. "uninfected" blood cells).
LAYER BREAKDOWN:
Conv2D(64, (3,3), activation='relu', padding="same", input_shape=(128,128,3))
Extracts features using 64 filters of size 3x3 with ReLU activation. same padding ensures the output has the same dimensions as the input. MaxPooling2D(2,2)
Reduces spatial dimensions by half using a 2x2 pooling window. Conv2D(64, (3,3), activation='relu', padding="same")
Another convolutional layer with the same structure as the first one. MaxPooling2D(2,2)
Further reduces spatial dimensions. Conv2D(128, (3,3), activation='relu', padding="same")
Increases filter size to 128, allowing it to capture more complex patterns. MaxPooling2D(2,2)
Further downsamples the feature maps. Flatten()
Converts the extracted 2D features into a 1D vector for the dense layers. Dense(128, activation='relu')
Fully connected layer with 128 neurons. Dropout(0.5)
Randomly drops 50% of neurons during training to prevent overfitting. Dense(2, activation='softmax')
Output layer with 2 neurons, using softmax activation to return class probabilities. Use Case: This model is well-suited for binary image classification, such as distinguishing between parasitized and uninfected blood cells.
DATA AUGMENTATION:
This ImageDataGenerator applies real-time data augmentation to prevent overfitting by generating variations of images during training. The effects introduce slight modifications (rotation, shift, shear, zoom, flip) to the training images, making the model more robust to variations in the dataset. Below is the code with comments showing the augmentation that was made to the images using ImageDataGenerator:
datagen = ImageDataGenerator(
rescale=1.0/255, # Normalize pixel values to the range [0,1]
rotation_range=20, # Randomly rotate images up to 20 degrees
width_shift_range=0.2, # Randomly shift images horizontally by 20% of the width
height_shift_range=0.2, # Randomly shift images vertically by 20% of the height
shear_range=0.2, # Apply shearing transformations
zoom_range=0.2, # Randomly zoom in/out up to 20%
horizontal_flip=True, # Flip images horizontally at random
fill_mode='nearest' # Fill in missing pixels after transformations using the nearest pixels
)
The images we loaded from the training dataset and the transformation as defined above was applied before preparing them in batches for model training. Finally, a validation data generator was used which similar took the images from the test dataset as they were and applied no data augmentation in order to ascertain the models real-world performance.