MNIST Image Classification with TensorFlow

This notebook demonstrates how to implement a simple linear image models on MNIST using tf.keras.


This companion notebook extends the basic harness of this notebook to a variety of models including DNN, CNN, dropout, pooling etc.


In [1]:
!sudo chown -R jupyter:jupyter /home/jupyter/training-data-analyst

In [2]:
import numpy as np
import shutil
import os
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
print(tf.__version__)


WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/compat/v2_compat.py:88: disable_resource_variables (from tensorflow.python.ops.variable_scope) is deprecated and will be removed in a future version.
Instructions for updating:
non-resource variables are not supported in the long term
2.1.0

Exploring the data

Let's download MNIST data and examine the shape. We will need these numbers ...


In [3]:
HEIGHT = 28
WIDTH = 28
NCLASSES = 10

In [4]:
# Get mnist data
mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()

# Scale our features between 0 and 1
x_train, x_test = x_train / 255.0, x_test / 255.0 

# Convert labels to categorical one-hot encoding
y_train = tf.keras.utils.to_categorical(y = y_train, num_classes = NCLASSES)
y_test = tf.keras.utils.to_categorical(y = y_test, num_classes = NCLASSES)

print("x_train.shape = {}".format(x_train.shape))
print("y_train.shape = {}".format(y_train.shape))
print("x_test.shape = {}".format(x_test.shape))
print("y_test.shape = {}".format(y_test.shape))


x_train.shape = (60000, 28, 28)
y_train.shape = (60000, 10)
x_test.shape = (10000, 28, 28)
y_test.shape = (10000, 10)

In [5]:
import matplotlib.pyplot as plt
IMGNO = 12
plt.imshow(x_test[IMGNO].reshape(HEIGHT, WIDTH));


Define the model.

Let's start with a very simple linear classifier. All our models will have this basic interface -- they will take an image and return probabilities.


In [6]:
# Build Keras Model Using Keras Sequential API
def linear_model():
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.InputLayer(input_shape = [HEIGHT, WIDTH], name = "image"))
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(units = NCLASSES, activation = tf.nn.softmax, name = "probabilities"))
    return model

Write Input Functions

As usual, we need to specify input functions for training, evaluation, and predicition.


In [7]:
# Create training input function
train_input_fn = tf.estimator.inputs.numpy_input_fn(
    x = {"image": x_train},
    y = y_train,
    batch_size = 100,
    num_epochs = None,
    shuffle = True,
    queue_capacity = 5000
  )

# Create evaluation input function
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
    x = {"image": x_test},
    y = y_test,
    batch_size = 100,
    num_epochs = 1,
    shuffle = False,
    queue_capacity = 5000
  )

# Create serving input function for inference
def serving_input_fn():
    placeholders = {"image": tf.placeholder(dtype = tf.float32, shape = [None, HEIGHT, WIDTH])}
    features = placeholders # as-is
    return tf.estimator.export.ServingInputReceiver(features = features, receiver_tensors = placeholders)

Create train_and_evaluate function

tf.estimator.train_and_evaluate does distributed training.


In [8]:
def train_and_evaluate(output_dir, hparams):
    # Build Keras model
    model = linear_model()
        
    # Compile Keras model with optimizer, loss function, and eval metrics
    model.compile(
        optimizer = "adam",
        loss = "categorical_crossentropy",
        metrics = ["accuracy"])
        
    # Convert Keras model to an Estimator
    estimator = tf.keras.estimator.model_to_estimator(
        keras_model = model, 
        model_dir = output_dir)

    # Set estimator's train_spec to use train_input_fn and train for so many steps
    train_spec = tf.estimator.TrainSpec(
        input_fn = train_input_fn,
        max_steps = hparams["train_steps"])

    # Create exporter that uses serving_input_fn to create saved_model for serving
    exporter = tf.estimator.LatestExporter(
        name = "exporter", 
        serving_input_receiver_fn = serving_input_fn)

    # Set estimator's eval_spec to use eval_input_fn and export saved_model
    eval_spec = tf.estimator.EvalSpec(
        input_fn = eval_input_fn,
        steps = None,
        exporters = exporter)

    # Run train_and_evaluate loop
    tf.estimator.train_and_evaluate(
        estimator = estimator, 
        train_spec = train_spec, 
        eval_spec = eval_spec)

This is the main() function


In [9]:
OUTDIR = "mnist/learned"
shutil.rmtree(OUTDIR, ignore_errors = True) # start fresh each time

hparams = {"train_steps": 1000, "learning_rate": 0.01}
train_and_evaluate(OUTDIR, hparams)


INFO:tensorflow:Using default config.
INFO:tensorflow:Using the Keras model provided.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/ops/init_ops.py:97: calling GlorotUniform.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/ops/init_ops.py:97: calling Zeros.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
INFO:tensorflow:Using config: {'_eval_distribute': None, '_session_creation_timeout_secs': 7200, '_save_summary_steps': 100, '_global_id_in_cluster': 0, '_service': None, '_experimental_distribute': None, '_num_ps_replicas': 0, '_cluster_spec': ClusterSpec({}), '_train_distribute': None, '_tf_random_seed': None, '_experimental_max_worker_delay_secs': None, '_protocol': None, '_log_step_count_steps': 100, '_model_dir': 'mnist/learned', '_is_chief': True, '_evaluation_master': '', '_keep_checkpoint_max': 5, '_device_fn': None, '_keep_checkpoint_every_n_hours': 10000, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_save_checkpoints_secs': 600, '_task_id': 0, '_save_checkpoints_steps': None, '_task_type': 'worker', '_master': '', '_num_worker_replicas': 1}
INFO:tensorflow:Not using Distribute Coordinator.
INFO:tensorflow:Running training and evaluation locally (non-distributed).
INFO:tensorflow:Start train and evaluate loop. The evaluate will happen after every checkpoint. Checkpoint frequency is determined based on RunConfig arguments: save_checkpoints_steps None or save_checkpoints_secs 600.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/training/training_util.py:236: Variable.initialized_value (from tensorflow.python.ops.variables) is deprecated and will be removed in a future version.
Instructions for updating:
Use Variable.read_value. Variables in 2.X are initialized automatically both in eager and graph (inside tf.defun) contexts.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_estimator/python/estimator/inputs/queues/feeding_queue_runner.py:62: QueueRunner.__init__ (from tensorflow.python.training.queue_runner_impl) is deprecated and will be removed in a future version.
Instructions for updating:
To construct input pipelines, use the `tf.data` module.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_estimator/python/estimator/inputs/queues/feeding_functions.py:500: add_queue_runner (from tensorflow.python.training.queue_runner_impl) is deprecated and will be removed in a future version.
Instructions for updating:
To construct input pipelines, use the `tf.data` module.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Warm-starting with WarmStartSettings: WarmStartSettings(ckpt_to_initialize_from='mnist/learned/keras/keras_model.ckpt', vars_to_warm_start='.*', var_name_to_vocab_info={}, var_name_to_prev_var_name={})
INFO:tensorflow:Warm-starting from: mnist/learned/keras/keras_model.ckpt
INFO:tensorflow:Warm-starting variables only in TRAINABLE_VARIABLES.
INFO:tensorflow:Warm-started 2 variables.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/training/monitored_session.py:906: start_queue_runners (from tensorflow.python.training.queue_runner_impl) is deprecated and will be removed in a future version.
Instructions for updating:
To construct input pipelines, use the `tf.data` module.
INFO:tensorflow:Saving checkpoints for 0 into mnist/learned/model.ckpt.
INFO:tensorflow:loss = 2.3702068, step = 1
INFO:tensorflow:global_step/sec: 340.867
INFO:tensorflow:loss = 0.7732622, step = 101 (0.295 sec)
INFO:tensorflow:global_step/sec: 384.292
INFO:tensorflow:loss = 0.6145685, step = 201 (0.260 sec)
INFO:tensorflow:global_step/sec: 391.351
INFO:tensorflow:loss = 0.50422275, step = 301 (0.256 sec)
INFO:tensorflow:global_step/sec: 397.311
INFO:tensorflow:loss = 0.4027025, step = 401 (0.252 sec)
INFO:tensorflow:global_step/sec: 391.875
INFO:tensorflow:loss = 0.44188076, step = 501 (0.258 sec)
INFO:tensorflow:global_step/sec: 386.557
INFO:tensorflow:loss = 0.37594798, step = 601 (0.256 sec)
INFO:tensorflow:global_step/sec: 389.035
INFO:tensorflow:loss = 0.34926274, step = 701 (0.257 sec)
INFO:tensorflow:global_step/sec: 400.38
INFO:tensorflow:loss = 0.52942824, step = 801 (0.250 sec)
INFO:tensorflow:global_step/sec: 391.907
INFO:tensorflow:loss = 0.40796912, step = 901 (0.255 sec)
INFO:tensorflow:Saving checkpoints for 1000 into mnist/learned/model.ckpt.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2020-06-02T17:25:49Z
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from mnist/learned/model.ckpt-1000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Inference Time : 0.65976s
INFO:tensorflow:Finished evaluation at 2020-06-02-17:25:50
INFO:tensorflow:Saving dict for global step 1000: acc = 0.9136, global_step = 1000, loss = 0.3200383
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 1000: mnist/learned/model.ckpt-1000
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
WARNING:tensorflow:From /home/jupyter/.local/lib/python3.5/site-packages/tensorflow_core/python/saved_model/signature_def_utils_impl.py:201: build_tensor_info (from tensorflow.python.saved_model.utils_impl) is deprecated and will be removed in a future version.
Instructions for updating:
This function will only be available through the v1 compatibility library as tf.compat.v1.saved_model.utils.build_tensor_info or tf.compat.v1.saved_model.build_tensor_info.
INFO:tensorflow:Signatures INCLUDED in export for Classify: None
INFO:tensorflow:Signatures INCLUDED in export for Train: None
INFO:tensorflow:Signatures INCLUDED in export for Regress: None
INFO:tensorflow:Signatures INCLUDED in export for Eval: None
INFO:tensorflow:Signatures INCLUDED in export for Predict: ['serving_default']
INFO:tensorflow:Restoring parameters from mnist/learned/model.ckpt-1000
INFO:tensorflow:Assets added to graph.
INFO:tensorflow:No assets to write.
INFO:tensorflow:SavedModel written to: mnist/learned/export/exporter/temp-b'1591118750'/saved_model.pb
INFO:tensorflow:Loss for final step: 0.45906192.

I got:

Saving dict for global step 1000: categorical_accuracy = 0.9112, global_step = 1000, loss = 0.32516304

In other words, we achieved 91.12% accuracy with the simple linear model!

# Copyright 2020 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.