This case study is particularly important in self-driving car applications similar to human drivers. Self-driving cars need to make decisions based on what they see using camera data. Self-driving cars need to detect objects and classify traffic signs so it can know when it stops yield and how fast it will drive 30 kilometers an hour or 50 kilometers an hour.
Problem:
Datasets
Classes are as listed below:
Sources:
J. Stallkamp, M. Schlipsing, J. Salmen, and C. Igel. The German Traffic Sign Recognition Benchmark: A multi-class classification competition. In Proceedings of the IEEE International Joint Conference on Neural Networks, pages 1453β1460. 2011.
@inproceedings{Stallkamp-IJCNN-2011, author = {Johannes Stallkamp and Marc Schlipsing and Jan Salmen and Christian Igel}, booktitle = {IEEE International Joint Conference on Neural Networks}, title = {The {G}erman {T}raffic {S}ign {R}ecognition {B}enchmark: A multi-class classification competition}, year = {2011}, pages = {1453--1460} }
# Import libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pickle
# The pickle module implements binary protocols for serializing and de-serializing a Python object structure.
# import dataset
with open("traffic-signs-data/train.p", mode='rb') as training_data:
train = pickle.load(training_data)
with open("traffic-signs-data/valid.p", mode='rb') as validation_data:
valid = pickle.load(validation_data)
with open("traffic-signs-data/test.p", mode='rb') as testing_data:
test = pickle.load(testing_data)
# Data splitting
X_train, y_train = train['features'], train['labels']
X_validate, y_validate = valid['features'], valid['labels']
X_test, y_test = test['features'], test['labels']
# Check train dataset dimension
X_train.shape, y_train.shape
# Check validation dataset dimension
X_validate.shape, y_validate.shape
# Check test dataset dimension
X_test.shape, y_test.shape
# Check train image
i = 3000
plt.imshow(X_train[i])
y_train[i]
# Check validation image
i = 4000
plt.imshow(X_validate[i])
y_validate[i]
# Check validation image
i = 5000
plt.imshow(X_test[i])
y_test[i]
#Shuffle the training dataset
from sklearn.utils import shuffle
X_train, y_train = shuffle(X_train, y_train)
# Make the image greyscale by taking it average
X_train_gray = np.sum(X_train/3, axis = 3, keepdims = True)
X_validate_gray = np.sum(X_validate/3, axis = 3, keepdims = True)
X_test_gray = np.sum(X_test/3, axis = 3, keepdims = True)
# Check the train dimension
X_train_gray.shape, X_validate_gray.shape, X_test_gray.shape
# Normalize dataset
X_train_gray_norm = (X_train_gray - 128)/128
X_validate_gray_norm = (X_validate_gray - 128)/128
X_test_gray_norm = (X_test_gray - 128)/128
# Check dataset dimension
X_train_gray.shape, X_validate_gray.shape, X_test_gray.shape
# Visualize the training dataset
i = 6000
# Original image
plt.imshow(X_train[i])
plt.figure()
plt.show()
# Greyscaled image
plt.imshow(X_train_gray[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
# Normalized image
plt.imshow(X_train_gray_norm[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
# Visualize the validation dataset
i = 1000
# Original image
plt.imshow(X_validate[i])
plt.figure()
plt.show()
# Greyscaled image
plt.imshow(X_validate_gray[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
# Normalized image
plt.imshow(X_validate_gray_norm[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
# Visualize the validation dataset
i = 2000
# Original image
plt.imshow(X_test[i])
plt.figure()
plt.show()
# Greyscaled image
plt.imshow(X_test_gray[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
# Normalized image
plt.imshow(X_test_gray_norm[i].squeeze(), cmap='gray')
plt.figure()
plt.show()
The model consists of the following layers:
STEP 1: THE FIRST CONVOLUTIONAL LAYER #1
- Input = 32x32x1
- Output = 28x28x6
- Output = (Input-filter+1)/Stride* => (32-5+1)/1=28
- Used a 5x5 Filter with input depth of 3 and output depth of 6
- Apply a RELU Activation function to the output
- pooling for input, Input = 28x28x6 and Output = 14x14x6
Stride is the amount by which the kernel is shifted when the kernel is passed over the image.
STEP 2: THE SECOND CONVOLUTIONAL LAYER #2
- Input = 14x14x6
- Output = 10x10x16
- Layer 2: Convolutional layer with Output = 10x10x16
- Output = (Input-filter+1)/strides => 10 = 14-5+1/1
- Apply a RELU Activation function to the output
- Pooling with Input = 10x10x16 and Output = 5x5x16
STEP 3: FLATTENING THE NETWORK
- Flatten the network with Input = 5x5x16 and Output = 400
STEP 4: FULLY CONNECTED LAYER
- Layer 3: Fully Connected layer with Input = 400 and Output = 120
- Apply a RELU Activation function to the output
STEP 5: ANOTHER FULLY CONNECTED LAYER
- Layer 4: Fully Connected Layer with Input = 120 and Output = 84
- Apply a RELU Activation function to the output
STEP 6: FULLY CONNECTED LAYER
- Layer 5: Fully Connected layer with Input = 84 and Output = 43
# Import Libraries
from tensorflow.keras import Sequential
from keras.layers import Conv2D, MaxPooling2D, AveragePooling2D, Dense, Flatten, Dropout
from keras.optimizers import Adam
from keras.callbacks import TensorBoard
from sklearn.model_selection import train_test_split
# Build model
cnn_model = Sequential()
# First Layer
cnn_model.add(Conv2D(filters = 6, kernel_size = (5,5), activation = 'relu', input_shape = (32,32,1)))
cnn_model.add(AveragePooling2D())
# Second layer
cnn_model.add(Conv2D(filters = 16, kernel_size = (5,5), activation = 'relu'))
cnn_model.add(AveragePooling2D())
# Flattening
cnn_model.add(Flatten())
# Connecting layer
cnn_model.add(Dense(units = 120, activation = 'relu'))
# Connecting layer
cnn_model.add(Dense(units = 84, activation = 'relu'))
# Connecting layer to output
cnn_model.add(Dense(units = 43, activation = 'softmax'))
# Compile the model
cnn_model.compile(loss ='sparse_categorical_crossentropy', optimizer=Adam(lr=0.001),metrics =['accuracy'])
# Train the model
history = cnn_model.fit(X_train_gray_norm, y_train, batch_size = 500, epochs = 50, verbose = 1, validation_data = (X_validate_gray_norm, y_validate))
# Check model accuracy
score = cnn_model.evaluate(X_test_gray_norm, y_test)
history.history.keys()
# Label the metrics
accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']
loss = history.history['val_loss']
val_loss = history.history['loss']
# Number of epochs
epochs = range(len(accuracy))
# Plot the evaluation metrics
plt.plot(epochs, accuracy, label='Training Accuracy', c = 'green')
plt.plot(epochs, val_accuracy, label='Validation Accuracy', c = 'red')
plt.title('Training and Validation accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epochs')
plt.legend()
plt.show()
# Plot the evaluation metrics
plt.plot(epochs, loss, label='Training Loss', c = 'green')
plt.plot(epochs, val_loss, label='Validation Loss', c = 'red')
plt.title('Training and Validation accuracy')
plt.ylabel('Loss')
plt.xlabel('Epochs')
plt.legend()
plt.show()
# Get the predictions for the test data
predicted_classses = np.argmax(cnn_model.predict(X_test_gray_norm), axis=-1)
#get the indices to be plotted
y_true = y_test
# Create confusion matrix
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_true, predicted_classes)
plt.figure(figsize = (25,25))
sns.heatmap(cm, annot=True)
plt.show()
# Create image subplots for image predictions
L = 7
W = 7
fig, axes = plt.subplots(L, W, figsize = (12,12))
axes = axes.ravel() #
for i in np.arange(0, L * W):
axes[i].imshow(X_test[i])
axes[i].set_title("Prediction={}\n True={}".format(predicted_classes[i], y_true[i]))
axes[i].axis('off')
plt.subplots_adjust(wspace=1)
In this case study, LeNet5 Convolutional Neural Network was implemented to classify traffic sign images and was able to achieved 86% accuracy. There are some images that are really hard to recognize even in human naked eye that possibly gave the machine difficulty in identifying the correct traffic signs. This could be further improve in many ways such as removing the unrecognizable traffic signs, adding more sample signs and tuning the parameters.