German Road Signs Classifier


In [1]:
# Imports
import pickle
import tensorflow as tf
from sklearn.utils import shuffle
import os.path
import numpy as np

In [2]:
# Load data augmented training data

with open('./train_data/aug_train_features_ready2.pickle', mode='rb') as f:
    X_train = pickle.load(f)
with open('./train_data/aug_train_labels_ready2.pickle', mode='rb') as f:
    y_train = pickle.load(f)
with open('./train_data/aug_valid_features_ready2.pickle', mode='rb') as f:
    X_valid = pickle.load(f)
with open('./train_data/aug_valid_labels_ready2.pickle', mode='rb') as f:
    y_valid = pickle.load(f)
with open('./train_data/aug_test_features_ready2.pickle', mode='rb') as f:
    X_test = pickle.load(f)
with open('./train_data/aug_test_labels_ready2.pickle', mode='rb') as f:
    y_test = pickle.load(f)

In [3]:
# Check dimensions
assert (len(X_train) == len(y_train))
assert (len(X_valid) == len(y_valid))
assert (len(X_test) == len(y_test))

In [4]:
# Shuffle everything
X_train, y_train = shuffle(X_train, y_train)

In [5]:
def convnet(x_input):
    # Parameters used for tf.truncated_normal
    mu = 0
    sigma = 0.1

    # Layer 1: Convolutional. Input = 32x32x1. Output = 28x28x6.
    with tf.name_scope("conv1"):
        conv1_W = tf.Variable(tf.truncated_normal(shape=(5, 5, 1, 6), mean=mu, stddev=sigma), name='Weights')
        conv1_b = tf.Variable(tf.constant(0.1, shape=[6]), name='Bias')
        conv1_2d = tf.nn.conv2d(x_input, conv1_W, strides=[1, 1, 1, 1], padding='VALID') + conv1_b
        act1 = tf.nn.tanh(conv1_2d)
        conv1 = tf.nn.max_pool(act1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID',name="conv1")
        tf.summary.histogram("weights", conv1_W)
        tf.summary.histogram("biases", conv1_b)
        tf.summary.histogram("activations", act1)

    # Layer 2: Convolutional. Output = 10x10x16.
    with tf.name_scope("conv2"):
        conv2_W = tf.Variable(tf.truncated_normal(shape=(5, 5, 6, 16), mean=mu, stddev=sigma), name='Weights')
        conv2_b = tf.Variable(tf.constant(0.1, shape=[16]), name='Bias')
        conv2_2d = tf.nn.conv2d(conv1, conv2_W, strides=[1, 1, 1, 1], padding='VALID') + conv2_b
        act2 = tf.nn.tanh(conv2_2d)
        conv2 = tf.nn.max_pool(act2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
        tf.summary.histogram("weights", conv2_W)
        tf.summary.histogram("biases", conv2_b)
        tf.summary.histogram("activations", act2)

    # Flatten. Input = 10x10x16. Output = 400.
    with tf.name_scope("flat"):
        fc0 = tf.contrib.layers.flatten(conv2)

    # Layer 3: Fully Connected. Input = 400. Output = 120.
    with tf.name_scope("fc1"):
        fc1_W = tf.Variable(tf.truncated_normal(shape=(400, 120), mean=mu, stddev=sigma, name='Weights'))
        fc1_b = tf.Variable(tf.constant(0.1, shape=[120]), name='Bias')
        fc1_1 = tf.matmul(fc0, fc1_W) + fc1_b
        fc1 = tf.nn.tanh(fc1_1)
        tf.summary.histogram("weights", fc1_W)
        tf.summary.histogram("biases", fc1_b)
        tf.summary.histogram("activations", fc1)

    with tf.name_scope("Dropout1"):
        fcd1 = tf.nn.dropout(fc1, keep_prob, name='Dropout1')

    # Layer 4: Fully Connected. Input = 120. Output = 84.
    with tf.name_scope("fc2"):
        fc2_W = tf.Variable(tf.truncated_normal(shape=(120, 84), mean=mu, stddev=sigma, name="W"))
        fc2_b = tf.Variable(tf.constant(0.1, shape=[84]), name='Bias')
        fc2_1 = tf.matmul(fcd1, fc2_W) + fc2_b
        fc2 = tf.nn.tanh(fc2_1)
        tf.summary.histogram("weights", fc2_W)
        tf.summary.histogram("biases", fc2_b)
        tf.summary.histogram("activations", fc2)

    with tf.name_scope("Dropout2"):
        fcd2 = tf.nn.dropout(fc2, keep_prob, name='Dropout2')

    # Layer 5: Fully Connected. Input = 84. Output = 43.
    with tf.name_scope("fc3"):
        fc3_W = tf.Variable(tf.truncated_normal(shape=(84, 43), mean=mu, stddev=sigma, name='Weights'))
        fc3_b = tf.Variable(tf.constant(0.1, shape=[43]), name='Bias')
        sign_class = tf.matmul(fcd2, fc3_W, name="fc3") + fc3_b
        tf.summary.histogram("weights", fc3_W)
        tf.summary.histogram("biases", fc3_b)
        tf.summary.histogram("activations", sign_class)

    return sign_class

In [6]:
# Global parameters
LOGDIR = "./support/tensorboard_logs/"
EPOCHS = 100
BATCH_SIZE = 128
rate = 0.0005

In [7]:
with tf.name_scope('Input'):
    x = tf.placeholder(tf.float32, (None, 32, 32, 1), name='InputData')    
    y = tf.placeholder(tf.int32, (None), name='LabelData')
    keep_prob = tf.placeholder(tf.float32)
    tf.summary.image('input', x, 3)
    one_hot_y = tf.one_hot(y, 43)

In [8]:
# Call CNN
logits = convnet(x)

In [9]:
# xent or cost function
with tf.name_scope("xent"):
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=one_hot_y, logits=logits)
    loss_operation = tf.reduce_mean(cross_entropy, name="xent")
    tf.summary.scalar("xent", loss_operation)

In [10]:
with tf.name_scope("train"):
    optimizer = tf.train.AdamOptimizer(learning_rate = rate)
    training_operation = optimizer.minimize(loss_operation)

In [11]:
with tf.name_scope("accuracy"):
    correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(one_hot_y, 1))
    accuracy_operation = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    tf.summary.scalar("accuracy", accuracy_operation)

In [12]:
def evaluate(X_data, y_data):
    num_examples = len(X_data)
    total_accuracy = 0
    total_loss = 0
    sess = tf.get_default_session()
    for offset in range(0, num_examples, BATCH_SIZE):
        batch_x, batch_y = X_data[offset:offset + BATCH_SIZE], y_data[offset:offset + BATCH_SIZE]
        loss, accuracy = sess.run([loss_operation, accuracy_operation], feed_dict={x: batch_x, y: batch_y, keep_prob: 1.0})
        total_accuracy += (accuracy * len(batch_x))
        total_loss += (loss * len(batch_x))
    return total_loss/num_examples, total_accuracy/num_examples

In [13]:
merged_summary = tf.summary.merge_all()
saver = tf.train.Saver()

In [14]:
with tf.Session() as sess:
    # variables need to be initialized before we can use them
    sess.run(tf.global_variables_initializer())
    writer = tf.summary.FileWriter(LOGDIR)
    writer.add_graph(sess.graph)

    num_examples = len(X_train)

    # perform training cycles
    print("Training...")
    print()
    for i in range(EPOCHS):
        X_train, y_train = shuffle(X_train, y_train)
        for offset in range(0, num_examples, BATCH_SIZE):
            end = offset + BATCH_SIZE
            batch_x, batch_y = X_train[offset:end], y_train[offset:end]
            summary = sess.run(training_operation, feed_dict={x: batch_x, y: batch_y, keep_prob: 0.5})

        [validation_loss, validation_accuracy] = evaluate(X_valid, y_valid)
        summary, acc = sess.run([merged_summary, accuracy_operation], feed_dict={x: batch_x, y: batch_y, keep_prob: 1.0})
        writer.add_summary(summary, i)  # Write summary
        saver.save(sess, os.path.join(LOGDIR, "model.ckpt"), i)
        print("EPOCH {} ...".format(i + 1))
        print("Validation Accuracy = {:.3f}".format(validation_accuracy))
        #print("Validation Loss = {:.3f}".format(validation_loss))
        print()
    
    with tf.name_scope("accuracy"):
        [test_loss, test_accuracy] = evaluate(X_test, y_test)
        print("Test Accuracy = {:.3f}".format(test_accuracy))
        print("Test Loss = {:.3f}".format(test_loss))
    
    saver = tf.train.Saver()
    saver.save(sess, './model/train.data')
    print("Model saved!")


Training...

EPOCH 1 ...
Validation Accuracy = 0.791

EPOCH 2 ...
Validation Accuracy = 0.872

EPOCH 3 ...
Validation Accuracy = 0.895

EPOCH 4 ...
Validation Accuracy = 0.913

EPOCH 5 ...
Validation Accuracy = 0.919

EPOCH 6 ...
Validation Accuracy = 0.925

EPOCH 7 ...
Validation Accuracy = 0.938

EPOCH 8 ...
Validation Accuracy = 0.940

EPOCH 9 ...
Validation Accuracy = 0.930

EPOCH 10 ...
Validation Accuracy = 0.936

EPOCH 11 ...
Validation Accuracy = 0.941

EPOCH 12 ...
Validation Accuracy = 0.938

EPOCH 13 ...
Validation Accuracy = 0.944

EPOCH 14 ...
Validation Accuracy = 0.939

EPOCH 15 ...
Validation Accuracy = 0.937

EPOCH 16 ...
Validation Accuracy = 0.948

EPOCH 17 ...
Validation Accuracy = 0.941

EPOCH 18 ...
Validation Accuracy = 0.941

EPOCH 19 ...
Validation Accuracy = 0.942

EPOCH 20 ...
Validation Accuracy = 0.943

EPOCH 21 ...
Validation Accuracy = 0.943

EPOCH 22 ...
Validation Accuracy = 0.944

EPOCH 23 ...
Validation Accuracy = 0.945

EPOCH 24 ...
Validation Accuracy = 0.940

EPOCH 25 ...
Validation Accuracy = 0.945

EPOCH 26 ...
Validation Accuracy = 0.943

EPOCH 27 ...
Validation Accuracy = 0.942

EPOCH 28 ...
Validation Accuracy = 0.948

EPOCH 29 ...
Validation Accuracy = 0.942

EPOCH 30 ...
Validation Accuracy = 0.944

EPOCH 31 ...
Validation Accuracy = 0.945

EPOCH 32 ...
Validation Accuracy = 0.942

EPOCH 33 ...
Validation Accuracy = 0.946

EPOCH 34 ...
Validation Accuracy = 0.943

EPOCH 35 ...
Validation Accuracy = 0.946

EPOCH 36 ...
Validation Accuracy = 0.941

EPOCH 37 ...
Validation Accuracy = 0.944

EPOCH 38 ...
Validation Accuracy = 0.939

EPOCH 39 ...
Validation Accuracy = 0.939

EPOCH 40 ...
Validation Accuracy = 0.941

EPOCH 41 ...
Validation Accuracy = 0.941

EPOCH 42 ...
Validation Accuracy = 0.949

EPOCH 43 ...
Validation Accuracy = 0.945

EPOCH 44 ...
Validation Accuracy = 0.949

EPOCH 45 ...
Validation Accuracy = 0.950

EPOCH 46 ...
Validation Accuracy = 0.956

EPOCH 47 ...
Validation Accuracy = 0.951

EPOCH 48 ...
Validation Accuracy = 0.952

EPOCH 49 ...
Validation Accuracy = 0.946

EPOCH 50 ...
Validation Accuracy = 0.957

EPOCH 51 ...
Validation Accuracy = 0.949

EPOCH 52 ...
Validation Accuracy = 0.948

EPOCH 53 ...
Validation Accuracy = 0.952

EPOCH 54 ...
Validation Accuracy = 0.951

EPOCH 55 ...
Validation Accuracy = 0.958

EPOCH 56 ...
Validation Accuracy = 0.953

EPOCH 57 ...
Validation Accuracy = 0.956

EPOCH 58 ...
Validation Accuracy = 0.955

EPOCH 59 ...
Validation Accuracy = 0.947

EPOCH 60 ...
Validation Accuracy = 0.956

EPOCH 61 ...
Validation Accuracy = 0.945

EPOCH 62 ...
Validation Accuracy = 0.950

EPOCH 63 ...
Validation Accuracy = 0.956

EPOCH 64 ...
Validation Accuracy = 0.955

EPOCH 65 ...
Validation Accuracy = 0.957

EPOCH 66 ...
Validation Accuracy = 0.953

EPOCH 67 ...
Validation Accuracy = 0.954

EPOCH 68 ...
Validation Accuracy = 0.955

EPOCH 69 ...
Validation Accuracy = 0.955

EPOCH 70 ...
Validation Accuracy = 0.957

EPOCH 71 ...
Validation Accuracy = 0.956

EPOCH 72 ...
Validation Accuracy = 0.957

EPOCH 73 ...
Validation Accuracy = 0.959

EPOCH 74 ...
Validation Accuracy = 0.957

EPOCH 75 ...
Validation Accuracy = 0.951

EPOCH 76 ...
Validation Accuracy = 0.960

EPOCH 77 ...
Validation Accuracy = 0.960

EPOCH 78 ...
Validation Accuracy = 0.955

EPOCH 79 ...
Validation Accuracy = 0.951

EPOCH 80 ...
Validation Accuracy = 0.951

EPOCH 81 ...
Validation Accuracy = 0.951

EPOCH 82 ...
Validation Accuracy = 0.956

EPOCH 83 ...
Validation Accuracy = 0.955

EPOCH 84 ...
Validation Accuracy = 0.948

EPOCH 85 ...
Validation Accuracy = 0.956

EPOCH 86 ...
Validation Accuracy = 0.949

EPOCH 87 ...
Validation Accuracy = 0.956

EPOCH 88 ...
Validation Accuracy = 0.953

EPOCH 89 ...
Validation Accuracy = 0.949

EPOCH 90 ...
Validation Accuracy = 0.954

EPOCH 91 ...
Validation Accuracy = 0.954

EPOCH 92 ...
Validation Accuracy = 0.957

EPOCH 93 ...
Validation Accuracy = 0.954

EPOCH 94 ...
Validation Accuracy = 0.956

EPOCH 95 ...
Validation Accuracy = 0.956

EPOCH 96 ...
Validation Accuracy = 0.958

EPOCH 97 ...
Validation Accuracy = 0.955

EPOCH 98 ...
Validation Accuracy = 0.958

EPOCH 99 ...
Validation Accuracy = 0.952

EPOCH 100 ...
Validation Accuracy = 0.958

Test Accuracy = 0.936
Test Loss = 0.247
Model saved!

In [15]:
# Load dataset of 5 internet images

with open('./test_data/test_img_features.pickle', mode='rb') as f:
    X_custom = pickle.load(f)
with open('./test_data/test_img_labels.pickle', mode='rb') as f:
    y_custom = pickle.load(f)

In [16]:
with tf.Session() as sess:
    loader = tf.train.import_meta_graph('./model/train.data.meta')
    loader.restore(sess, tf.train.latest_checkpoint('./model/'))

    [test_loss, test_accuracy] = evaluate(X_custom, y_custom)
    print("Test Images Accuracy = {:.3f}".format(test_accuracy))    
    #print("Test Images Loss = {:.3f}".format(test_loss))    
    print()
    
    softmax = sess.run(tf.nn.softmax(logits), feed_dict={x: X_custom, y: y_custom, keep_prob: 1.0})
    result = sess.run(tf.nn.top_k(softmax, k=5))
    
    np.set_printoptions(precision=2, suppress=True)
    print("Softmax probablities:")
    print(result.values)
    print()
    print("Predicted indices:")
    print(result.indices)
    print()
    print("Labels of test images:")
    print(y_custom)


Test Images Accuracy = 0.800

Softmax probablities:
[[ 0.99  0.01  0.    0.    0.  ]
 [ 0.34  0.3   0.16  0.11  0.04]
 [ 0.96  0.01  0.01  0.01  0.  ]
 [ 1.    0.    0.    0.    0.  ]
 [ 0.98  0.01  0.    0.    0.  ]]

Predicted indices:
[[13  2 38 12 35]
 [23 11 30 19 10]
 [28 27 11 36 30]
 [35 36  3 13 33]
 [17 38 12 14 40]]

Labels of test images:
[13, 23, 28, 35, 38]

An observation: I just realized, that the sign Turn right is not on the list of classes. So, the network classified it as a No entry, which is very close, despite of different colour. An RGB neural network would not do this kind of prediction, but this grayscale one does.