diff options
Diffstat (limited to 'tensorflow/models/image/mnist/convolutional.py')
-rw-r--r-- | tensorflow/models/image/mnist/convolutional.py | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/tensorflow/models/image/mnist/convolutional.py b/tensorflow/models/image/mnist/convolutional.py new file mode 100644 index 0000000000..8fb0e4dfb4 --- /dev/null +++ b/tensorflow/models/image/mnist/convolutional.py @@ -0,0 +1,270 @@ +"""Simple, end-to-end, LeNet-5-like convolutional MNIST model example. + +This should achieve a test error of 0.8%. Please keep this model as simple and +linear as possible, it is meant as a tutorial for simple convolutional models. +Run with --self_test on the command line to exectute a short self-test. +""" +import gzip +import os +import sys +import urllib + +import tensorflow.python.platform + +import numpy +import tensorflow as tf + +SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/' +WORK_DIRECTORY = 'data' +IMAGE_SIZE = 28 +NUM_CHANNELS = 1 +PIXEL_DEPTH = 255 +NUM_LABELS = 10 +VALIDATION_SIZE = 5000 # Size of the validation set. +SEED = 66478 # Set to None for random seed. +BATCH_SIZE = 64 +NUM_EPOCHS = 10 + + +tf.app.flags.DEFINE_boolean("self_test", False, "True if running a self test.") +FLAGS = tf.app.flags.FLAGS + + +def maybe_download(filename): + """Download the data from Yann's website, unless it's already here.""" + if not os.path.exists(WORK_DIRECTORY): + os.mkdir(WORK_DIRECTORY) + filepath = os.path.join(WORK_DIRECTORY, filename) + if not os.path.exists(filepath): + filepath, _ = urllib.urlretrieve(SOURCE_URL + filename, filepath) + statinfo = os.stat(filepath) + print 'Succesfully downloaded', filename, statinfo.st_size, 'bytes.' + return filepath + + +def extract_data(filename, num_images): + """Extract the images into a 4D tensor [image index, y, x, channels]. + + Values are rescaled from [0, 255] down to [-0.5, 0.5]. + """ + print 'Extracting', filename + with gzip.open(filename) as bytestream: + bytestream.read(16) + buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images) + data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32) + data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH + data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, 1) + return data + + +def extract_labels(filename, num_images): + """Extract the labels into a 1-hot matrix [image index, label index].""" + print 'Extracting', filename + with gzip.open(filename) as bytestream: + bytestream.read(8) + buf = bytestream.read(1 * num_images) + labels = numpy.frombuffer(buf, dtype=numpy.uint8) + # Convert to dense 1-hot representation. + return (numpy.arange(NUM_LABELS) == labels[:, None]).astype(numpy.float32) + + +def fake_data(num_images): + """Generate a fake dataset that matches the dimensions of MNIST.""" + data = numpy.ndarray( + shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS), + dtype=numpy.float32) + labels = numpy.zeros(shape=(num_images, NUM_LABELS), dtype=numpy.float32) + for image in xrange(num_images): + label = image % 2 + data[image, :, :, 0] = label - 0.5 + labels[image, label] = 1.0 + return data, labels + + +def error_rate(predictions, labels): + """Return the error rate based on dense predictions and 1-hot labels.""" + return 100.0 - ( + 100.0 * + numpy.sum(numpy.argmax(predictions, 1) == numpy.argmax(labels, 1)) / + predictions.shape[0]) + + +def main(argv=None): # pylint: disable=unused-argument + if FLAGS.self_test: + print 'Running self-test.' + train_data, train_labels = fake_data(256) + validation_data, validation_labels = fake_data(16) + test_data, test_labels = fake_data(256) + num_epochs = 1 + else: + # Get the data. + train_data_filename = maybe_download('train-images-idx3-ubyte.gz') + train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz') + test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz') + test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz') + + # Extract it into numpy arrays. + train_data = extract_data(train_data_filename, 60000) + train_labels = extract_labels(train_labels_filename, 60000) + test_data = extract_data(test_data_filename, 10000) + test_labels = extract_labels(test_labels_filename, 10000) + + # Generate a validation set. + validation_data = train_data[:VALIDATION_SIZE, :, :, :] + validation_labels = train_labels[:VALIDATION_SIZE] + train_data = train_data[VALIDATION_SIZE:, :, :, :] + train_labels = train_labels[VALIDATION_SIZE:] + num_epochs = NUM_EPOCHS + train_size = train_labels.shape[0] + + # This is where training samples and labels are fed to the graph. + # These placeholder nodes will be fed a batch of training data at each + # training step using the {feed_dict} argument to the Run() call below. + train_data_node = tf.placeholder( + tf.float32, + shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)) + train_labels_node = tf.placeholder(tf.float32, + shape=(BATCH_SIZE, NUM_LABELS)) + # For the validation and test data, we'll just hold the entire dataset in + # one constant node. + validation_data_node = tf.constant(validation_data) + test_data_node = tf.constant(test_data) + + # The variables below hold all the trainable weights. They are passed an + # initial value which will be assigned when when we call: + # {tf.initialize_all_variables().run()} + conv1_weights = tf.Variable( + tf.truncated_normal([5, 5, NUM_CHANNELS, 32], # 5x5 filter, depth 32. + stddev=0.1, + seed=SEED)) + conv1_biases = tf.Variable(tf.zeros([32])) + conv2_weights = tf.Variable( + tf.truncated_normal([5, 5, 32, 64], + stddev=0.1, + seed=SEED)) + conv2_biases = tf.Variable(tf.constant(0.1, shape=[64])) + fc1_weights = tf.Variable( # fully connected, depth 512. + tf.truncated_normal([IMAGE_SIZE / 4 * IMAGE_SIZE / 4 * 64, 512], + stddev=0.1, + seed=SEED)) + fc1_biases = tf.Variable(tf.constant(0.1, shape=[512])) + fc2_weights = tf.Variable( + tf.truncated_normal([512, NUM_LABELS], + stddev=0.1, + seed=SEED)) + fc2_biases = tf.Variable(tf.constant(0.1, shape=[NUM_LABELS])) + + # We will replicate the model structure for the training subgraph, as well + # as the evaluation subgraphs, while sharing the trainable parameters. + def model(data, train=False): + """The Model definition.""" + # 2D convolution, with 'SAME' padding (i.e. the output feature map has + # the same size as the input). Note that {strides} is a 4D array whose + # shape matches the data layout: [image index, y, x, depth]. + conv = tf.nn.conv2d(data, + conv1_weights, + strides=[1, 1, 1, 1], + padding='SAME') + # Bias and rectified linear non-linearity. + relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases)) + # Max pooling. The kernel size spec {ksize} also follows the layout of + # the data. Here we have a pooling window of 2, and a stride of 2. + pool = tf.nn.max_pool(relu, + ksize=[1, 2, 2, 1], + strides=[1, 2, 2, 1], + padding='SAME') + conv = tf.nn.conv2d(pool, + conv2_weights, + strides=[1, 1, 1, 1], + padding='SAME') + relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases)) + pool = tf.nn.max_pool(relu, + ksize=[1, 2, 2, 1], + strides=[1, 2, 2, 1], + padding='SAME') + # Reshape the feature map cuboid into a 2D matrix to feed it to the + # fully connected layers. + pool_shape = pool.get_shape().as_list() + reshape = tf.reshape( + pool, + [pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]]) + # Fully connected layer. Note that the '+' operation automatically + # broadcasts the biases. + hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases) + # Add a 50% dropout during training only. Dropout also scales + # activations such that no rescaling is needed at evaluation time. + if train: + hidden = tf.nn.dropout(hidden, 0.5, seed=SEED) + return tf.matmul(hidden, fc2_weights) + fc2_biases + + # Training computation: logits + cross-entropy loss. + logits = model(train_data_node, True) + loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits( + logits, train_labels_node)) + + # L2 regularization for the fully connected parameters. + regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) + + tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases)) + # Add the regularization term to the loss. + loss += 5e-4 * regularizers + + # Optimizer: set up a variable that's incremented once per batch and + # controls the learning rate decay. + batch = tf.Variable(0) + # Decay once per epoch, using an exponential schedule starting at 0.01. + learning_rate = tf.train.exponential_decay( + 0.01, # Base learning rate. + batch * BATCH_SIZE, # Current index into the dataset. + train_size, # Decay step. + 0.95, # Decay rate. + staircase=True) + # Use simple momentum for the optimization. + optimizer = tf.train.MomentumOptimizer(learning_rate, + 0.9).minimize(loss, + global_step=batch) + + # Predictions for the minibatch, validation set and test set. + train_prediction = tf.nn.softmax(logits) + # We'll compute them only once in a while by calling their {eval()} method. + validation_prediction = tf.nn.softmax(model(validation_data_node)) + test_prediction = tf.nn.softmax(model(test_data_node)) + + # Create a local session to run this computation. + with tf.Session() as s: + # Run all the initializers to prepare the trainable parameters. + tf.initialize_all_variables().run() + print 'Initialized!' + # Loop through training steps. + for step in xrange(int(num_epochs * train_size / BATCH_SIZE)): + # Compute the offset of the current minibatch in the data. + # Note that we could use better randomization across epochs. + offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE) + batch_data = train_data[offset:(offset + BATCH_SIZE), :, :, :] + batch_labels = train_labels[offset:(offset + BATCH_SIZE)] + # This dictionary maps the batch data (as a numpy array) to the + # node in the graph is should be fed to. + feed_dict = {train_data_node: batch_data, + train_labels_node: batch_labels} + # Run the graph and fetch some of the nodes. + _, l, lr, predictions = s.run( + [optimizer, loss, learning_rate, train_prediction], + feed_dict=feed_dict) + if step % 100 == 0: + print 'Epoch %.2f' % (float(step) * BATCH_SIZE / train_size) + print 'Minibatch loss: %.3f, learning rate: %.6f' % (l, lr) + print 'Minibatch error: %.1f%%' % error_rate(predictions, + batch_labels) + print 'Validation error: %.1f%%' % error_rate( + validation_prediction.eval(), validation_labels) + sys.stdout.flush() + # Finally print the result! + test_error = error_rate(test_prediction.eval(), test_labels) + print 'Test error: %.1f%%' % test_error + if FLAGS.self_test: + print 'test_error', test_error + assert test_error == 0.0, 'expected 0.0 test_error, got %.2f' % ( + test_error,) + + +if __name__ == '__main__': + tf.app.run() |