Jovian
⭐️
Sign In

Handwritten Digit Recognition

Objective: Classify handwritten digits from the MNIST dataset by training a convolutional neural network (CNN) using the Keras deep learning library.

Data Preparation

We begin by downloading the data and creating training & validation sets. Keras has inbuilt helper functions to do this.

In [1]:
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
Using TensorFlow backend.
In [ ]:
import jovian

jovian.log_dataset('data/mnist')

jovian.log_dataset({
    'path': 'data/mnist',
    'hash': 'sfsfsfasdfsdf23423',
    'size': '100MB'
})

Each sample is a 28px x 28 px image, flattened out a vector of length 784 i.e. 28x28.

In [2]:
train_images[0].shape, train_labels[0]
Out[2]:
((28, 28), 5)

Let's take a look at some sample images from the training set, by plotting them in a grid.

In [3]:
%matplotlib inline
import matplotlib.pyplot as plt

grid_size = 6
f, axarr = plt.subplots(grid_size, grid_size)
for i in range(grid_size):
    for j in range(grid_size):
        ax = axarr[i, j]
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)
        ax.imshow(train_images[i * grid_size + j], cmap='gray')
Notebook Image

We're going to apply the following preprocessing steps:

  1. Reshape the image vectors into 28x28 matrices
  2. Separate 20% of the training set to create a vaidation set
  3. Conver the numeric labels into one-hot vectors
In [4]:
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

from keras.utils import to_categorical

partial_train_images = train_images[:45000]
partial_train_labels = train_labels[:45000]

validation_images = train_images[45000:]
validation_labels = train_labels[45000:]

partial_train_labels = to_categorical(partial_train_labels)
validation_labels = to_categorical(validation_labels)
test_labels = to_categorical(test_labels)

Model & Training

Now we're ready to define a simple CNN model.

In [5]:
input_shape = (28,28,1)
num_classes = 10
In [6]:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28,28,1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(num_classes, activation='softmax'))

model.summary()
WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/backend/tensorflow_backend.py:66: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead. WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/backend/tensorflow_backend.py:541: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead. WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/backend/tensorflow_backend.py:4432: The name tf.random_uniform is deprecated. Please use tf.random.uniform instead. WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/backend/tensorflow_backend.py:4267: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead. Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 26, 26, 32) 320 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 5408) 0 _________________________________________________________________ dense_1 (Dense) (None, 10) 54090 ================================================================= Total params: 54,410 Trainable params: 54,410 Non-trainable params: 0 _________________________________________________________________
In [7]:
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/optimizers.py:793: The name tf.train.Optimizer is deprecated. Please use tf.compat.v1.train.Optimizer instead. WARNING:tensorflow:From /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages/keras/backend/tensorflow_backend.py:3576: The name tf.log is deprecated. Please use tf.math.log instead.
In [24]:
from jovian.callbacks.keras import JovianKerasCallback
In [25]:
model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 26, 26, 32) 320 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 5408) 0 _________________________________________________________________ dense_1 (Dense) (None, 10) 54090 ================================================================= Total params: 54,410 Trainable params: 54,410 Non-trainable params: 0 _________________________________________________________________
In [36]:
history = model.fit(
    partial_train_images, 
    partial_train_labels, 
    epochs=2, 
    callbacks=[JovianKerasCallback(notify=True)],
    batch_size=128, 
    validation_data=(validation_images, validation_labels))
Train on 45000 samples, validate on 15000 samples Epoch 1/2 45000/45000 [==============================] - 24s 544us/step - loss: 0.0543 - acc: 0.9846 - val_loss: 0.0756 - val_acc: 0.9780 Epoch 2/2 45000/45000 [==============================] - 23s 504us/step - loss: 0.0498 - acc: 0.9859 - val_loss: 0.0721 - val_acc: 0.9781
In [18]:
from helpers.utils import plot_history

plot_history(history)
Notebook Image
Notebook Image

Model Evaluation

In [28]:
test_loss, test_acc = model.evaluate(test_images, test_labels)
10000/10000 [==============================] - 1s 139us/step
In [29]:
print('Test loss:', test_loss)
print('Test acc:', test_acc)
Test loss: 0.06612131999330595 Test acc: 0.9787
In [30]:
jovian.log_metrics({
    'test_loss': 0.0885,
    'test_acc': 0.974
})
[jovian] Metrics logged.

We can also save the trained model's weights to disk, so we won't need to train it again.

In [31]:
model.save('mnist-cnn.h5')

Save & Commit

In [13]:
!pip install jovian --upgrade
Collecting jovian Downloading https://files.pythonhosted.org/packages/b9/59/9093fe15fcc874bd160a13563d2861afd374de9ea3fdbe26886c538dc429/jovian-0.1.74.tar.gz Requirement already satisfied, skipping upgrade: requests in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from jovian) (2.22.0) Requirement already satisfied, skipping upgrade: uuid in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from jovian) (1.30) Requirement already satisfied, skipping upgrade: pyyaml in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from jovian) (5.1.2) Requirement already satisfied, skipping upgrade: chardet<3.1.0,>=3.0.2 in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from requests->jovian) (3.0.4) Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from requests->jovian) (2019.6.16) Requirement already satisfied, skipping upgrade: idna<2.9,>=2.5 in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from requests->jovian) (2.8) Requirement already satisfied, skipping upgrade: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/anaconda3/envs/keras-mnist-sep/lib/python3.7/site-packages (from requests->jovian) (1.25.3) Building wheels for collected packages: jovian Building wheel for jovian (setup.py) ... done Created wheel for jovian: filename=jovian-0.1.74-cp37-none-any.whl size=37333 sha256=737565a7a3f8f9b061d49b38804e387ab16f348eef2a0dd760102efe0f5136a9 Stored in directory: /Users/aakashns/Library/Caches/pip/wheels/9e/80/95/3eb6eafdb1609b823e44e7875caa15415c5571e19cf9dc4f41 Successfully built jovian Installing collected packages: jovian Successfully installed jovian-0.1.74
In [14]:
import jovian
In [ ]:
jovian.commit(files=['helpers'], artifacts=['mnist-cnn.h5'])
[jovian] Saving notebook..
In [35]:
jovian.notify('Training complete', verbose=True)
[jovian] Error: Slack not connected. To connect Slack, go to: https://jvn.io/settings/integrations
In [ ]: