Classifying Images¶

Using the CIFAR10 dataset built in to the tensorflow library

Note: The accuracy of this model is poor, but I wanted to practice. I could have used Data Augmentation (ImageDataGenerator) to increase diversity of the training data, increased the number of epochs, as well as several other adjustments.

Import Libraries¶

In [1]:
import numpy as np
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization
from tensorflow.keras.models import Sequential
from tensorflow.keras import datasets

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import random

import warnings

warnings.filterwarnings('ignore')

Set Up Test and Training Data¶

In [2]:
(training_images, training_labels), (test_images, test_labels) = datasets.cifar10.load_data()
In [3]:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']

View the First 20 Training Images¶

In [4]:
random_indices = np.random.choice(training_images.shape[0], 20, replace=False)

for i, index in enumerate(random_indices):
    plt.subplot(4, 5, i + 1)
    plt.imshow(training_images[index])
    plt.title(class_names[training_labels[index][0]])
    plt.axis('off')
plt.tight_layout()
plt.show()

Normalise the Data¶

In [5]:
training_images = training_images/255

Design, Compile and Fit Model¶

In [6]:
model = Sequential([
    Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
    Conv2D(32, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    BatchNormalization(),
    Dropout(0.25),
    Conv2D(64, (3, 3), activation='relu'),
    Conv2D(64, (3, 3), activation='relu'),
    MaxPooling2D((2, 2)),
    BatchNormalization(),
    Dropout(0.25),
    Conv2D(128, (3, 3), activation='relu'),
    Conv2D(128, (3, 3), activation='relu'),
    BatchNormalization(),
    Flatten(),
    Dense(512, activation='relu'),
    Dropout(0.5),
    Dense(256, activation='relu'),
    Dropout(0.5),
    Dense(10, activation='softmax')
])

model.summary()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 30, 30, 32)     │           896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 28, 28, 32)     │         9,248 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 14, 14, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization             │ (None, 14, 14, 32)     │           128 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 14, 14, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 12, 12, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 10, 10, 64)     │        36,928 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 5, 5, 64)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_1           │ (None, 5, 5, 64)       │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_1 (Dropout)             │ (None, 5, 5, 64)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_4 (Conv2D)               │ (None, 3, 3, 128)      │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 1, 1, 128)      │       147,584 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_2           │ (None, 1, 1, 128)      │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten (Flatten)               │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 512)            │        66,048 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 512)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 256)            │       131,328 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_3 (Dropout)             │ (None, 256)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 10)             │         2,570 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 487,850 (1.86 MB)
 Trainable params: 487,402 (1.86 MB)
 Non-trainable params: 448 (1.75 KB)
In [7]:
model.compile(optimizer = 'adam', loss ='sparse_categorical_crossentropy', metrics =['accuracy'])
In [8]:
model.fit(training_images, training_labels, batch_size = 512, epochs=5)
Epoch 1/5
98/98 ━━━━━━━━━━━━━━━━━━━━ 26s 252ms/step - accuracy: 0.2329 - loss: 2.1257
Epoch 2/5
98/98 ━━━━━━━━━━━━━━━━━━━━ 25s 254ms/step - accuracy: 0.4825 - loss: 1.4211
Epoch 3/5
98/98 ━━━━━━━━━━━━━━━━━━━━ 25s 253ms/step - accuracy: 0.5750 - loss: 1.1918
Epoch 4/5
98/98 ━━━━━━━━━━━━━━━━━━━━ 25s 256ms/step - accuracy: 0.6311 - loss: 1.0451
Epoch 5/5
98/98 ━━━━━━━━━━━━━━━━━━━━ 25s 259ms/step - accuracy: 0.6769 - loss: 0.9315
Out[8]:
<keras.src.callbacks.history.History at 0x30955a8d0>

Evaluate Model Using Test Data¶

Note: This poor accuracy (compared to the training data) suggests heavy overfitting.

In [9]:
model.evaluate(test_images, test_labels)
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.3016 - loss: 65.2209
Out[9]:
[64.67840576171875, 0.2994999885559082]

View The Classification on a Sample Set of Test Data¶

In [10]:
random_indices = random.sample(range(test_images.shape[0]), 16)

predictions = model.predict(training_images[random_indices])

for i in range(16):
    plt.subplot(4,4,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(training_images[random_indices[i]], cmap=plt.cm.binary)
    predicted_label = np.argmax(predictions[i])
    true_label = training_labels[random_indices[i]]
    if predicted_label == true_label:
        color = 'green'
    else:
        color = 'red'
    plt.xlabel("{} ({})".format(class_names[predicted_label], class_names[true_label[0]]), color=color)
plt.tight_layout()
plt.show()
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 55ms/step

A Heatmap of Predictions¶

In [11]:
predicted_classes = model.predict(test_images)
313/313 ━━━━━━━━━━━━━━━━━━━━ 4s 11ms/step
In [12]:
predicted_classes = predicted_classes.argmax(1)
In [14]:
from sklearn.metrics import confusion_matrix
import seaborn as sns

cm = confusion_matrix(predicted_classes, test_labels)

plt.figure(figsize=(10, 10))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=[str(i) for i in range(10)], 
            yticklabels=[str(i) for i in range(10)])
plt.xlabel('Predicted Labels')
plt.ylabel('True Labels')
plt.show()
In [ ]: