Commit 1a83863d authored by Asim Shankar's avatar Asim Shankar
Browse files

[mnist]: Updates

- Use the object-oriented tf.layers API instead of the functional one.
  The object-oriented API is particularly useful when using the model
  with eager execution.
- Update unittest to train, evaluate, and predict using the model.
- Add a micro-benchmark for measuring step-time.
  The parameters (batch_size, num_steps etc.) have NOT been tuned,
  the purpose with this code is mostly to illustrate how model
  benchmarks may be written.

These changes are made as a step towards consolidating model definitions
for different TensorFlow features (like eager execution and support for
TPUs in
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/mnist
and
https://github.com/tensorflow/tpu-demos/tree/master/cloud_tpu/models/mnist
parent 5a1faffd
...@@ -21,6 +21,13 @@ python mnist.py ...@@ -21,6 +21,13 @@ python mnist.py
The model will begin training and will automatically evaluate itself on the The model will begin training and will automatically evaluate itself on the
validation data. validation data.
Illustrative unittests and benchmarks can be run with:
```
python mnist_test.py
python mnist_test.py --benchmarks=.
```
## Exporting the model ## Exporting the model
You can export the model into Tensorflow [SavedModel](https://www.tensorflow.org/programmers_guide/saved_model) format by using the argument `--export_dir`: You can export the model into Tensorflow [SavedModel](https://www.tensorflow.org/programmers_guide/saved_model) format by using the argument `--export_dir`:
......
...@@ -63,6 +63,7 @@ parser.add_argument( ...@@ -63,6 +63,7 @@ parser.add_argument(
type=str, type=str,
help='The directory where the exported SavedModel will be stored.') help='The directory where the exported SavedModel will be stored.')
def train_dataset(data_dir): def train_dataset(data_dir):
"""Returns a tf.data.Dataset yielding (image, label) pairs for training.""" """Returns a tf.data.Dataset yielding (image, label) pairs for training."""
data = input_data.read_data_sets(data_dir, one_hot=True).train data = input_data.read_data_sets(data_dir, one_hot=True).train
...@@ -75,141 +76,128 @@ def eval_dataset(data_dir): ...@@ -75,141 +76,128 @@ def eval_dataset(data_dir):
return tf.data.Dataset.from_tensors((data.images, data.labels)) return tf.data.Dataset.from_tensors((data.images, data.labels))
def mnist_model(inputs, mode, data_format): class Model(object):
"""Takes the MNIST inputs and mode and outputs a tensor of logits.""" """Class that defines a graph to recognize digits in the MNIST dataset."""
# Input Layer
# Reshape X to 4-D tensor: [batch_size, width, height, channels] def __init__(self, data_format):
# MNIST images are 28x28 pixels, and have one color channel """Creates a model for classifying a hand-written digit.
inputs = tf.reshape(inputs, [-1, 28, 28, 1])
Args:
if data_format is None: data_format: Either 'channels_first' or 'channels_last'.
# When running on GPU, transpose the data from channels_last (NHWC) to 'channels_first' is typically faster on GPUs while 'channels_last' is
# channels_first (NCHW) to improve performance. typically faster on CPUs. See
# See https://www.tensorflow.org/performance/performance_guide#data_formats https://www.tensorflow.org/performance/performance_guide#data_formats
data_format = ('channels_first' """
if tf.test.is_built_with_cuda() else 'channels_last') if data_format == 'channels_first':
self._input_shape = [-1, 1, 28, 28]
if data_format == 'channels_first': else:
inputs = tf.transpose(inputs, [0, 3, 1, 2]) assert data_format == 'channels_last'
self._input_shape = [-1, 28, 28, 1]
# Convolutional Layer #1
# Computes 32 features using a 5x5 filter with ReLU activation. self.conv1 = tf.layers.Conv2D(
# Padding is added to preserve width and height. 32, 5, padding='same', data_format=data_format, activation=tf.nn.relu)
# Input Tensor Shape: [batch_size, 28, 28, 1] self.conv2 = tf.layers.Conv2D(
# Output Tensor Shape: [batch_size, 28, 28, 32] 64, 5, padding='same', data_format=data_format, activation=tf.nn.relu)
conv1 = tf.layers.conv2d( self.fc1 = tf.layers.Dense(1024, activation=tf.nn.relu)
inputs=inputs, self.fc2 = tf.layers.Dense(10)
filters=32, self.dropout = tf.layers.Dropout(0.5)
kernel_size=[5, 5], self.max_pool2d = tf.layers.MaxPooling2D(
padding='same', (2, 2), (2, 2), padding='same', data_format=data_format)
activation=tf.nn.relu,
data_format=data_format) def __call__(self, inputs, training):
"""Add operations to classify a batch of input images.
# Pooling Layer #1
# First max pooling layer with a 2x2 filter and stride of 2 Args:
# Input Tensor Shape: [batch_size, 28, 28, 32] inputs: A Tensor representing a batch of input images.
# Output Tensor Shape: [batch_size, 14, 14, 32] training: A boolean. Set to True to add operations required only when
pool1 = tf.layers.max_pooling2d( training the classifier.
inputs=conv1, pool_size=[2, 2], strides=2, data_format=data_format)
Returns:
# Convolutional Layer #2 A logits Tensor with shape [<batch_size>, 10].
# Computes 64 features using a 5x5 filter. """
# Padding is added to preserve width and height. y = tf.reshape(inputs, self._input_shape)
# Input Tensor Shape: [batch_size, 14, 14, 32] y = self.conv1(y)
# Output Tensor Shape: [batch_size, 14, 14, 64] y = self.max_pool2d(y)
conv2 = tf.layers.conv2d( y = self.conv2(y)
inputs=pool1, y = self.max_pool2d(y)
filters=64, y = tf.layers.flatten(y)
kernel_size=[5, 5], y = self.fc1(y)
padding='same', y = self.dropout(y, training=training)
activation=tf.nn.relu, return self.fc2(y)
data_format=data_format)
# Pooling Layer #2 def predict_spec(model, image):
# Second max pooling layer with a 2x2 filter and stride of 2 """EstimatorSpec for predictions."""
# Input Tensor Shape: [batch_size, 14, 14, 64] if isinstance(image, dict):
# Output Tensor Shape: [batch_size, 7, 7, 64] image = image['image']
pool2 = tf.layers.max_pooling2d( logits = model(image, training=False)
inputs=conv2, pool_size=[2, 2], strides=2, data_format=data_format)
# Flatten tensor into a batch of vectors
# Input Tensor Shape: [batch_size, 7, 7, 64]
# Output Tensor Shape: [batch_size, 7 * 7 * 64]
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
# Dense Layer
# Densely connected layer with 1024 neurons
# Input Tensor Shape: [batch_size, 7 * 7 * 64]
# Output Tensor Shape: [batch_size, 1024]
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
# Add dropout operation; 0.6 probability that element will be kept
dropout = tf.layers.dropout(
inputs=dense, rate=0.4, training=(mode == tf.estimator.ModeKeys.TRAIN))
# Logits layer
# Input Tensor Shape: [batch_size, 1024]
# Output Tensor Shape: [batch_size, 10]
logits = tf.layers.dense(inputs=dropout, units=10)
return logits
def mnist_model_fn(features, labels, mode, params):
"""Model function for MNIST."""
if mode == tf.estimator.ModeKeys.PREDICT and isinstance(features,dict):
features = features['image_raw']
logits = mnist_model(features, mode, params['data_format'])
predictions = { predictions = {
'classes': tf.argmax(input=logits, axis=1), 'classes': tf.argmax(logits, axis=1),
'probabilities': tf.nn.softmax(logits, name='softmax_tensor') 'probabilities': tf.nn.softmax(logits),
} }
return tf.estimator.EstimatorSpec(
mode=tf.estimator.ModeKeys.PREDICT,
predictions=predictions,
export_outputs={
'classify': tf.estimator.export.PredictOutput(predictions)
})
if mode == tf.estimator.ModeKeys.PREDICT:
export_outputs={'classify': tf.estimator.export.PredictOutput(predictions)}
return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions,
export_outputs=export_outputs)
def train_spec(model, image, labels):
"""EstimatorSpec for training."""
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)
logits = model(image, training=True)
loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits) loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)
# Configure the training op
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)
train_op = optimizer.minimize(loss, tf.train.get_or_create_global_step())
else:
train_op = None
accuracy = tf.metrics.accuracy( accuracy = tf.metrics.accuracy(
tf.argmax(labels, axis=1), predictions['classes']) labels=tf.argmax(labels, axis=1), predictions=tf.argmax(logits, axis=1))
metrics = {'accuracy': accuracy} # Name the accuracy tensor 'train_accuracy' to demonstrate the
# LoggingTensorHook.
# Create a tensor named train_accuracy for logging purposes
tf.identity(accuracy[1], name='train_accuracy') tf.identity(accuracy[1], name='train_accuracy')
tf.summary.scalar('train_accuracy', accuracy[1]) tf.summary.scalar('train_accuracy', accuracy[1])
return tf.estimator.EstimatorSpec(
mode=tf.estimator.ModeKeys.TRAIN,
loss=loss,
train_op=optimizer.minimize(loss, tf.train.get_or_create_global_step()))
def eval_spec(model, image, labels):
"""EstimatorSpec for evaluation."""
logits = model(image, training=False)
loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)
return tf.estimator.EstimatorSpec( return tf.estimator.EstimatorSpec(
mode=mode, mode=tf.estimator.ModeKeys.EVAL,
predictions=predictions,
loss=loss, loss=loss,
train_op=train_op, eval_metric_ops={
eval_metric_ops=metrics) 'accuracy':
tf.metrics.accuracy(
labels=tf.argmax(labels, axis=1),
predictions=tf.argmax(logits, axis=1)),
})
def model_fn(features, labels, mode, params):
"""The model_fn argument for creating an Estimator."""
model = Model(params['data_format'])
if mode == tf.estimator.ModeKeys.PREDICT:
return predict_spec(model, features)
if mode == tf.estimator.ModeKeys.TRAIN:
return train_spec(model, features, labels)
if mode == tf.estimator.ModeKeys.EVAL:
return eval_spec(model, features, labels)
def main(unused_argv): def main(unused_argv):
# Create the Estimator data_format = FLAGS.data_format
if data_format is None:
data_format = ('channels_first'
if tf.test.is_built_with_cuda() else 'channels_last')
mnist_classifier = tf.estimator.Estimator( mnist_classifier = tf.estimator.Estimator(
model_fn=mnist_model_fn, model_fn=model_fn,
model_dir=FLAGS.model_dir, model_dir=FLAGS.model_dir,
params={ params={
'data_format': FLAGS.data_format 'data_format': data_format
}) })
# Set up training hook that logs the training accuracy every 100 steps.
tensors_to_log = {'train_accuracy': 'train_accuracy'}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=100)
# Train the model # Train the model
def train_input_fn(): def train_input_fn():
# When choosing shuffle buffer sizes, larger sizes result in better # When choosing shuffle buffer sizes, larger sizes result in better
...@@ -221,6 +209,10 @@ def main(unused_argv): ...@@ -221,6 +209,10 @@ def main(unused_argv):
(images, labels) = dataset.make_one_shot_iterator().get_next() (images, labels) = dataset.make_one_shot_iterator().get_next()
return (images, labels) return (images, labels)
# Set up training hook that logs the training accuracy every 100 steps.
tensors_to_log = {'train_accuracy': 'train_accuracy'}
logging_hook = tf.train.LoggingTensorHook(
tensors=tensors_to_log, every_n_iter=100)
mnist_classifier.train(input_fn=train_input_fn, hooks=[logging_hook]) mnist_classifier.train(input_fn=train_input_fn, hooks=[logging_hook])
# Evaluate the model and print results # Evaluate the model and print results
...@@ -233,10 +225,11 @@ def main(unused_argv): ...@@ -233,10 +225,11 @@ def main(unused_argv):
# Export the model # Export the model
if FLAGS.export_dir is not None: if FLAGS.export_dir is not None:
image = tf.placeholder(tf.float32,[None, 28, 28]) image = tf.placeholder(tf.float32, [None, 28, 28])
serving_input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn( input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({
{"image_raw":image}) 'image': tf.placeholder(tf.float32, [None, 28, 28])
mnist_classifier.export_savedmodel(FLAGS.export_dir, serving_input_fn) })
mnist_classifier.export_savedmodel(FLAGS.export_dir, input_fn)
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -18,52 +18,76 @@ from __future__ import division ...@@ -18,52 +18,76 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
import tensorflow as tf import tensorflow as tf
import time
import mnist import mnist
tf.logging.set_verbosity(tf.logging.ERROR) BATCH_SIZE = 100
class BaseTest(tf.test.TestCase): def dummy_input_fn():
image = tf.random_uniform([BATCH_SIZE, 784])
labels = tf.random_uniform([BATCH_SIZE], maxval=9, dtype=tf.int32)
return image, tf.one_hot(labels, 10)
def input_fn(self):
features = tf.random_uniform([55000, 784])
labels = tf.random_uniform([55000], maxval=9, dtype=tf.int32)
return features, tf.one_hot(labels, 10)
def mnist_model_fn_helper(self, mode): def make_estimator():
features, labels = self.input_fn() data_format = 'channels_last'
image_count = features.shape[0] if tf.test.is_built_with_cuda():
spec = mnist.mnist_model_fn( data_format = 'channels_first'
features, labels, mode, {'data_format': 'channels_last'}) return tf.estimator.Estimator(
model_fn=mnist.model_fn, params={
'data_format': data_format
})
predictions = spec.predictions
self.assertAllEqual(predictions['probabilities'].shape, (image_count, 10))
self.assertEqual(predictions['probabilities'].dtype, tf.float32)
self.assertAllEqual(predictions['classes'].shape, (image_count,))
self.assertEqual(predictions['classes'].dtype, tf.int64)
if mode != tf.estimator.ModeKeys.PREDICT: class Tests(tf.test.TestCase):
loss = spec.loss
self.assertAllEqual(loss.shape, ())
self.assertEqual(loss.dtype, tf.float32)
if mode == tf.estimator.ModeKeys.EVAL: def test_mnist(self):
eval_metric_ops = spec.eval_metric_ops classifier = make_estimator()
self.assertAllEqual(eval_metric_ops['accuracy'][0].shape, ()) classifier.train(input_fn=dummy_input_fn, steps=2)
self.assertAllEqual(eval_metric_ops['accuracy'][1].shape, ()) eval_results = classifier.evaluate(input_fn=dummy_input_fn, steps=1)
self.assertEqual(eval_metric_ops['accuracy'][0].dtype, tf.float32)
self.assertEqual(eval_metric_ops['accuracy'][1].dtype, tf.float32)
def test_mnist_model_fn_train_mode(self): loss = eval_results['loss']
self.mnist_model_fn_helper(tf.estimator.ModeKeys.TRAIN) global_step = eval_results['global_step']
accuracy = eval_results['accuracy']
self.assertEqual(loss.shape, ())
self.assertEqual(2, global_step)
self.assertEqual(accuracy.shape, ())
def test_mnist_model_fn_eval_mode(self): input_fn = lambda: tf.random_uniform([3, 784])
self.mnist_model_fn_helper(tf.estimator.ModeKeys.EVAL) predictions_generator = classifier.predict(input_fn)
for i in range(3):
predictions = next(predictions_generator)
self.assertEqual(predictions['probabilities'].shape, (10,))
self.assertEqual(predictions['classes'].shape, ())
def test_mnist_model_fn_predict_mode(self):
self.mnist_model_fn_helper(tf.estimator.ModeKeys.PREDICT) class Benchmarks(tf.test.Benchmark):
def benchmark_train_step_time(self):
classifier = make_estimator()
# Run one step to warmup any use of the GPU.
classifier.train(input_fn=dummy_input_fn, steps=1)
have_gpu = tf.test.is_gpu_available()
num_steps = 1000 if have_gpu else 100
name = 'train_step_time_%s' % ('gpu' if have_gpu else 'cpu')
start = time.time()
classifier.train(input_fn=dummy_input_fn, steps=num_steps)
end = time.time()
wall_time = (end - start) / num_steps
self.report_benchmark(
iters=num_steps,
wall_time=wall_time,
name=name,
extras={
'examples_per_sec': BATCH_SIZE / wall_time
})
if __name__ == '__main__': if __name__ == '__main__':
tf.logging.set_verbosity(tf.logging.ERROR)
tf.test.main() tf.test.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment