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]
# MNIST images are 28x28 pixels, and have one color channel
inputs = tf.reshape(inputs, [-1, 28, 28, 1])
if data_format is None: def __init__(self, data_format):
# When running on GPU, transpose the data from channels_last (NHWC) to """Creates a model for classifying a hand-written digit.
# channels_first (NCHW) to improve performance.
# See https://www.tensorflow.org/performance/performance_guide#data_formats
data_format = ('channels_first'
if tf.test.is_built_with_cuda() else 'channels_last')
Args:
data_format: Either 'channels_first' or 'channels_last'.
'channels_first' is typically faster on GPUs while 'channels_last' is
typically faster on CPUs. See
https://www.tensorflow.org/performance/performance_guide#data_formats
"""
if data_format == 'channels_first': if data_format == 'channels_first':
inputs = tf.transpose(inputs, [0, 3, 1, 2]) self._input_shape = [-1, 1, 28, 28]
else:
# Convolutional Layer #1 assert data_format == 'channels_last'
# Computes 32 features using a 5x5 filter with ReLU activation. self._input_shape = [-1, 28, 28, 1]
# Padding is added to preserve width and height.
# Input Tensor Shape: [batch_size, 28, 28, 1] self.conv1 = tf.layers.Conv2D(
# Output Tensor Shape: [batch_size, 28, 28, 32] 32, 5, padding='same', data_format=data_format, activation=tf.nn.relu)
conv1 = tf.layers.conv2d( self.conv2 = tf.layers.Conv2D(
inputs=inputs, 64, 5, padding='same', data_format=data_format, activation=tf.nn.relu)
filters=32, self.fc1 = tf.layers.Dense(1024, activation=tf.nn.relu)
kernel_size=[5, 5], self.fc2 = tf.layers.Dense(10)
padding='same', self.dropout = tf.layers.Dropout(0.5)
activation=tf.nn.relu, self.max_pool2d = tf.layers.MaxPooling2D(
data_format=data_format) (2, 2), (2, 2), padding='same', data_format=data_format)
# Pooling Layer #1 def __call__(self, inputs, training):
# First max pooling layer with a 2x2 filter and stride of 2 """Add operations to classify a batch of input images.
# Input Tensor Shape: [batch_size, 28, 28, 32]
# Output Tensor Shape: [batch_size, 14, 14, 32] Args:
pool1 = tf.layers.max_pooling2d( inputs: A Tensor representing a batch of input images.
inputs=conv1, pool_size=[2, 2], strides=2, data_format=data_format) training: A boolean. Set to True to add operations required only when
training the classifier.
# Convolutional Layer #2
# Computes 64 features using a 5x5 filter. Returns:
# Padding is added to preserve width and height. A logits Tensor with shape [<batch_size>, 10].
# Input Tensor Shape: [batch_size, 14, 14, 32] """
# Output Tensor Shape: [batch_size, 14, 14, 64] y = tf.reshape(inputs, self._input_shape)
conv2 = tf.layers.conv2d( y = self.conv1(y)
inputs=pool1, y = self.max_pool2d(y)
filters=64, y = self.conv2(y)
kernel_size=[5, 5], y = self.max_pool2d(y)
padding='same', y = tf.layers.flatten(y)
activation=tf.nn.relu, y = self.fc1(y)
data_format=data_format) y = self.dropout(y, training=training)
return self.fc2(y)
# Pooling Layer #2
# Second max pooling layer with a 2x2 filter and stride of 2
# Input Tensor Shape: [batch_size, 14, 14, 64] def predict_spec(model, image):
# Output Tensor Shape: [batch_size, 7, 7, 64] """EstimatorSpec for predictions."""
pool2 = tf.layers.max_pooling2d( if isinstance(image, dict):
inputs=conv2, pool_size=[2, 2], strides=2, data_format=data_format) image = image['image']
logits = model(image, training=False)
# 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)
loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)
# Configure the training op def train_spec(model, image, labels):
if mode == tf.estimator.ModeKeys.TRAIN: """EstimatorSpec for training."""
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4) optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)
train_op = optimizer.minimize(loss, tf.train.get_or_create_global_step()) logits = model(image, training=True)
else: loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)
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