1. 程式人生 > >TensorFlow 從入門到精通(二):MNIST 例程原始碼分析

TensorFlow 從入門到精通(二):MNIST 例程原始碼分析

按照上節步驟, TensorFlow 預設安裝在 /usr/lib/python/site-packages/tensorflow/ (也有可能是 /usr/local/lib……)下,檢視目錄結構:

# tree -d -L 3 /usr/lib/python2.7/site-packages/tensorflow/
/usr/lib/python2.7/site-packages/tensorflow/
├── contrib
│   ├── bayesflow
│   │   └── python
│   ├── cmake
│   ├── copy_graph
│   │   └── python
│   ├── crf
│   │   └── python
│   ├── cudnn_rnn │   │   ├── ops │   │   └── python │   ├── distributions │   │   └── python │   ├── factorization │   │   └── python │   ├── ffmpeg │   │   └── ops │   ├── framework │   │   └── python │   ├── graph_editor │   ├── grid_rnn │   │   └── python │   ├── layers │   │   ├── ops │   │   └── python
│   ├── learn │   │   └── python │   ├── linear_optimizer │   │   ├── ops │   │   └── python │   ├── lookup │   ├── losses │   │   └── python │   ├── metrics │   │   ├── ops │   │   └── python │   ├── opt │   │   └── python │   ├── quantization │   │   ├── kernels │   │   ├── ops │   │   └── python
│   ├── rnn │   │   └── python │   ├── session_bundle │   ├── slim │   │   └── python │   ├── tensorboard │   │   └── plugins │   ├── tensor_forest │   │   ├── client │   │   ├── data │   │   ├── hybrid │   │   └── python │   ├── testing │   │   └── python │   ├── training │   │   └── python │   └── util ├── core │   ├── example │   ├── framework │   ├── lib │   │   └── core │   ├── protobuf │   └── util ├── examples │   └── tutorials │   └── mnist ├── include │   ├── Eigen │   │   └── src │   ├── external │   │   └── eigen_archive │   ├── google │   │   └── protobuf │   ├── tensorflow │   │   └── core │   ├── third_party │   │   └── eigen3 │   └── unsupported │   └── Eigen ├── models │   ├── embedding │   ├── image │   │   ├── alexnet │   │   ├── cifar10 │   │   ├── imagenet │   │   └── mnist │   └── rnn │   ├── ptb │   └── translate ├── python │   ├── client │   ├── debug │   │   └── cli │   ├── framework │   ├── lib │   │   ├── core │   │   └── io │   ├── ops │   ├── platform │   ├── saved_model │   ├── summary │   │   └── impl │   ├── training │   ├── user_ops │   └── util │   └── protobuf ├── tensorboard │   ├── backend │   ├── dist │   ├── lib │   │   ├── css │   │   └── python │   └── plugins │   └── projector └── tools └── pip_package 119 directories

上節執行 MNIST 例程的命令為:
# python -m tensorflow.models.image.mnist.convolutional
對應檔案為 /usr/lib/python2.7/site-packages/tensorflow/models/image/mnist/convolutional.py
開啟例程原始碼:

# Copyright 2015 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.
# ==============================================================================

"""Simple, end-to-end, LeNet-5-like convolutional MNIST model example.

This should achieve a test error of 0.7%. 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 execute a short self-test.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import gzip
import os
import sys
import time

import numpy
from six.moves import urllib
from six.moves import xrange  # pylint: disable=redefined-builtin
import tensorflow as tf

# 資料來源
SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/'
# 工作目錄,存放下載的資料
WORK_DIRECTORY = 'data'
# MNIST 資料集特徵: 
#     影象尺寸 28x28 
IMAGE_SIZE = 28
#     黑白影象
NUM_CHANNELS = 1
#     畫素值0~255 
PIXEL_DEPTH = 255
#     標籤分10個類別
NUM_LABELS = 10
#     驗證集共 5000 個樣本
VALIDATION_SIZE = 5000  
# 隨機數種子,可設為 None 表示真的隨機
SEED = 66478 
# 批處理大小為64
BATCH_SIZE = 64
# 資料全集一共過10遍網路
NUM_EPOCHS = 10
# 驗證集批處理大小也是64
EVAL_BATCH_SIZE = 64
# 驗證時間間隔,每訓練100個批處理,做一次評估
EVAL_FREQUENCY = 100  


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 tf.gfile.Exists(WORK_DIRECTORY):
    tf.gfile.MakeDirs(WORK_DIRECTORY)
  filepath = os.path.join(WORK_DIRECTORY, filename)
  if not tf.gfile.Exists(filepath):
    filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
    with tf.gfile.GFile(filepath) as f:
      size = f.Size()
    print('Successfully downloaded', filename, size, 'bytes.')
  return filepath

# 抽取資料,變為 4維張量[影象索引,y, x, c]
# 去均值、做歸一化,範圍變到[-0.5, 0.5]
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 vector of int64 label IDs."""
  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).astype(numpy.int64)
  return labels

# 假資料,用於功能自測
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,), dtype=numpy.int64)
  for image in xrange(num_images):
    label = image % 2
    data[image, :, :, 0] = label - 0.5
    labels[image] = label
  return data, labels
# 計算分類錯誤率
def error_rate(predictions, labels):
  """Return the error rate based on dense predictions and sparse labels."""
  return 100.0 - (
      100.0 *
      numpy.sum(numpy.argmax(predictions, 1) == labels) /
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(EVAL_BATCH_SIZE)
    test_data, test_labels = fake_data(EVAL_BATCH_SIZE)
    num_epochs = 1
  else:
    # 下載資料
    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')

    # 載入資料到numpy
    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)

    # 產生評測集
    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]

# 訓練樣本和標籤將從這裡送入網路。
# 每訓練迭代步,佔位符節點將被送入一個批處理資料
# 訓練資料節點
  train_data_node = tf.placeholder(
      tf.float32,
shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))
# 訓練標籤節點
  train_labels_node = tf.placeholder(tf.int64, shape=(BATCH_SIZE,))
# 評測資料節點
  eval_data = tf.placeholder(
      tf.float32,
      shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))

# 下面這些變數是網路的可訓練權值
# conv1 權值維度為 32 x channels x 5 x 5, 32 為特徵圖數目
  conv1_weights = tf.Variable(
      tf.truncated_normal([5, 5, NUM_CHANNELS, 32],  # 5x5 filter, depth 32.
                          stddev=0.1,
                          seed=SEED))
# conv1 偏置
  conv1_biases = tf.Variable(tf.zeros([32]))
# conv2 權值維度為 64 x 32 x 5 x 5 
  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 權值,神經元數目為512
  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 權值,維度與標籤類數目一致
  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]))

# 兩個網路:訓練網路和評測網路
# 它們共享權值

# 實現 LeNet-5 模型,該函式輸入為資料,輸出為fc2的響應
# 第二個引數區分訓練網路還是評測網路
  def model(data, train=False):
"""The Model definition."""
# 二維卷積,使用“不變形”補零(即輸出特徵圖與輸入尺寸一致)。
    conv = tf.nn.conv2d(data,
                        conv1_weights,
                        strides=[1, 1, 1, 1],
                        padding='SAME')
    # 加偏置、過啟用函式一塊完成
relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))
    # 最大值下采樣
    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')
# 特徵圖變形為2維矩陣,便於送入全連線層
    pool_shape = pool.get_shape().as_list()
    reshape = tf.reshape(
        pool,
        [pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]])
# 全連線層,注意“+”運算自動廣播偏置
    hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)
# 訓練階段,增加 50% dropout;而評測階段無需該操作
    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.sparse_softmax_cross_entropy_with_logits(
      logits, train_labels_node))


  # 全連線層引數進行 L2 正則化
  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))
  # 將正則項加入損失函式
  loss += 5e-4 * regularizers

  # 優化器: 設定一個變數,每個批處理遞增,控制學習速率衰減
  batch = tf.Variable(0)
  # 指數衰減
  learning_rate = tf.train.exponential_decay(
      0.01,                # 基本學習速率
      batch * BATCH_SIZE,  # 當前批處理在資料全集中的位置
      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)

  # 用softmax 計算訓練批處理的預測概率
  train_prediction = tf.nn.softmax(logits)

  # 用 softmax 計算評測批處理的預測概率
  eval_prediction = tf.nn.softmax(model(eval_data))

  # Small utility function to evaluate a dataset by feeding batches of data to
  # {eval_data} and pulling the results from {eval_predictions}.
  # Saves memory and enables this to run on smaller GPUs.
  def eval_in_batches(data, sess):
    """Get all predictions for a dataset by running it in small batches."""
    size = data.shape[0]
    if size < EVAL_BATCH_SIZE:
      raise ValueError("batch size for evals larger than dataset: %d" % size)
    predictions = numpy.ndarray(shape=(size, NUM_LABELS), dtype=numpy.float32)
    for begin in xrange(0, size, EVAL_BATCH_SIZE):
      end = begin + EVAL_BATCH_SIZE
      if end <= size:
        predictions[begin:end, :] = sess.run(
            eval_prediction,
            feed_dict={eval_data: data[begin:end, ...]})
      else:
        batch_predictions = sess.run(
            eval_prediction,
            feed_dict={eval_data: data[-EVAL_BATCH_SIZE:, ...]})
        predictions[begin:, :] = batch_predictions[begin - size:, :]
    return predictions


  # Create a local session to run the training.
  start_time = time.time()
  with tf.Session() as sess:
    # 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 it 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 = sess.run(
          [optimizer, loss, learning_rate, train_prediction],
          feed_dict=feed_dict)
      if step % EVAL_FREQUENCY == 0:
        elapsed_time = time.time() - start_time
        start_time = time.time()
        print('Step %d (epoch %.2f), %.1f ms' %
              (step, float(step) * BATCH_SIZE / train_size,
               1000 * elapsed_time / EVAL_FREQUENCY))
        print('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr))
        print('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels))
        print('Validation error: %.1f%%' % error_rate(
            eval_in_batches(validation_data, sess), validation_labels))
        sys.stdout.flush()
    # Finally print the result!
    test_error = error_rate(eval_in_batches(test_data, sess), 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()