TensorFlow : コード解説 : CNN – 畳み込み ニューラルネットワーク for CIFAR-10
* TensorFlow : Tutorials : 畳み込み ニューラルネットワーク (翻訳/解説) に、数式は排除/コード重視の方針で詳細な解説を加筆したものです。
CIFAR-10 分類は機械学習の共通のベンチマーク問題です。問題は RGB 32×32 ピクセル画像を 10 カテゴリーに渡って分類するものです : 飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船そしてトラック。

詳細については CIFAR-10 ページ と Alex Krizhevsky による Tech Report (pdf) を参照してください。
目標
目標は画像認識のために比較的小さい 畳み込みニューラルネットワーク (convolutional neural network (CNN)) を構築することです。その過程において :
- ネットワーク、訓練、そして評価のための標準的な構成を明らかにする、
- より大きく洗練されたモデルの構築のためのテンプレートを提供する。
CIFAR-10 を選択した理由は、大きなモデルにスケールするために、TesnorFlow の能力の多くを練習するために十分に複雑なことです。
同時に、迅速に訓練するために十分に小さいことです。これは新しいアイデアを試し新しい技法を使って実験するために理想的です。
ハイライト
TensorFlow でより大規模でより洗練されたモデルを設計するために必要な、
幾つかの重要な構成要素をデモします :
- 核となる数学コンポーネントとして、畳み込み、ReLU、最大プーリング、そして 局所反応正規化 (local response normalization) を含みます。
- 入力画像、損失そして活性化と勾配の分布を含む、訓練中のネットワーク活動の 視覚化。
- 学習したパラメータの 移動平均 の計算をして、そして予測性能を高めるために評価中にこれらの平均を使うためのルーチン。(モーメンタム法?)
- システマチックに時間をかけて減少する、学習率スケジュール の実装。
- ディスク遅延と高コストな画像前処理からモデルを分離する、入力データのための キュー の先読み。
またモデルのマルチ-GPU 版も提供します、これは以下を実証します :
- 複数の GPU カードに渡って並列に訓練するモデルを構成します。
- 複数の GPU の中で変数を共有し更新します。
TensorFlow 上のビジョン・タスクのためのより大きな CNN を構築するための起点を提供します。
モデル・アーキテクチャ
この CIFAR-10 チュートリアルのモデルは交互の畳み込みと非線形から成る多層アーキテクチャです。これらの層には softmax 分類につながる全結合 (fully connected) 層が続きます。モデルは Alex Krizhevsky により記述されているアーキテクチャを、トップの2、3層で少しの違いはありますが、追随しています。
このモデルは GPU 上の 2, 3 時間の訓練時間内に約 86% の精度の最高性能を達成しています。詳細は後述の “モデルを評価する” とコードを見てください。モデルは 1,068,298 個の学習可能なパラメータから成り、そして一つの画像上の推論を計算するために 19.5 M の乗算-加算演算を必要とします。
コード構成
このチュートリアルのコードは tensorflow/models/image/cifar10/ にあります。
ファイル | 目的 |
---|---|
cifar10_input.py | native の CIFAR-10 バイナリ・ファイル・フォーマットを読む。 |
cifar10.py | CIFAR-10 モデルの構築。 |
cifar10_train.py | CPU または GPU で CIFAR-10 モデルを訓練する。 |
cifar10_multi_gpu_train.py | 複数の GPU で CIFAR-10 モデルを訓練する。 |
cifar10_eval.py | CIFAR-10 モデルの予測性能を評価する。 |
CIFAR-10 モデル
CIFAR-10 ネットワークの大部分は cifar10.py に含まれています。完全な訓練グラフは約 765 演算を含みます。グラフを次のモジュールで構築することでコードを最大限再利用可能にしてあります。
- モデル入力 : inputs() と distorted_inputs() は評価と訓練のための CIFAR 画像を、それぞれ、読みそして前処理する演算を追加します。
- モデル予測 : inference() は提供された画像について 推論、すなわち分類 を行なう演算を追加します。
- モデル訓練 : loss() と train() は損失、勾配、変数更新と視覚化要約を計算する演算を追加します。
モデル入力
モデルの入力パートは CIFAR-10 バイナリ・データファイルから画像を読む、
inputs() と distorted_inputs() 関数で構築されます。
エントリは cifar10.py に含まれていますが、実装は cifar10_input.py にあります。
def distorted_inputs(): """Construct distorted input for CIFAR training using the Reader ops. Returns: images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. labels: Labels. 1D tensor of [batch_size] size. """ if not FLAGS.data_dir: raise ValueError('Please supply a data_dir') data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin') return cifar10_input.distorted_inputs(data_dir=data_dir, batch_size=FLAGS.batch_size) def inputs(eval_data): """Construct input for CIFAR evaluation using the Reader ops. Args: eval_data: bool, indicating if one should use the train or eval data set. Returns: images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. labels: Labels. 1D tensor of [batch_size] size. """ if not FLAGS.data_dir: raise ValueError('Please supply a data_dir') data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin') return cifar10_input.inputs(eval_data=eval_data, data_dir=data_dir, batch_size=FLAGS.batch_size)
IMAGE_SIZE = 24 def distorted_inputs(data_dir, batch_size): """Construct distorted input for CIFAR training using the Reader ops. Args: data_dir: Path to the CIFAR-10 data directory. batch_size: Number of images per batch. Returns: images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. labels: Labels. 1D tensor of [batch_size] size. """ filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i) for i in xrange(1, 6)] for f in filenames: if not tf.gfile.Exists(f): raise ValueError('Failed to find file: ' + f) # 読むためのファイル名を生成するキューを作成する。 filename_queue = tf.train.string_input_producer(filenames) # ファイル名キューにあるファイルからサンプルを読む。 read_input = read_cifar10(filename_queue) reshaped_image = tf.cast(read_input.uint8image, tf.float32) height = IMAGE_SIZE width = IMAGE_SIZE # ネットワーク訓練のための画像処理。 # 多くのランダムな歪みが画像に適用されます。 # 画像の [height, width] セクションをランダムにクロップする。 distorted_image = tf.random_crop(reshaped_image, [height, width, 3]) # ランダムに画像を水平方向にフリップします。 distorted_image = tf.image.random_flip_left_right(distorted_image) # これらの操作に可換性はないので、 # 操作の順序をランダム化することを考えます。 distorted_image = tf.image.random_brightness(distorted_image, max_delta=63) distorted_image = tf.image.random_contrast(distorted_image, lower=0.2, upper=1.8) # Subtract off the mean and divide by the variance of the pixels. float_image = tf.image.per_image_whitening(distorted_image) # Ensure that the random shuffling has good mixing properties. min_fraction_of_examples_in_queue = 0.4 min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN * min_fraction_of_examples_in_queue) print ('Filling queue with %d CIFAR images before starting to train. ' 'This will take a few minutes.' % min_queue_examples) # Generate a batch of images and labels by building up a queue of examples. return _generate_image_and_label_batch(float_image, read_input.label, min_queue_examples, batch_size) def inputs(eval_data, data_dir, batch_size): """Construct input for CIFAR evaluation using the Reader ops. Args: eval_data: bool, indicating if one should use the train or eval data set. data_dir: Path to the CIFAR-10 data directory. batch_size: Number of images per batch. Returns: images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. labels: Labels. 1D tensor of [batch_size] size. """ if not eval_data: filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i) for i in xrange(1, 6)] num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN else: filenames = [os.path.join(data_dir, 'test_batch.bin')] num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_EVAL for f in filenames: if not tf.gfile.Exists(f): raise ValueError('Failed to find file: ' + f) # 読むためのファイル名を生成するキューを作成する。 filename_queue = tf.train.string_input_producer(filenames) # ファイル名キューにあるファイルからサンプルを読む。 read_input = read_cifar10(filename_queue) reshaped_image = tf.cast(read_input.uint8image, tf.float32) height = IMAGE_SIZE width = IMAGE_SIZE # 評価のための画像処理。 # 画像の中央の [height, width] をクロップします。 resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, width, height) # Subtract off the mean and divide by the variance of the pixels. float_image = tf.image.per_image_whitening(resized_image) # Ensure that the random shuffling has good mixing properties. min_fraction_of_examples_in_queue = 0.4 min_queue_examples = int(num_examples_per_epoch * min_fraction_of_examples_in_queue) # Generate a batch of images and labels by building up a queue of examples. return _generate_image_and_label_batch(float_image, read_input.label, min_queue_examples, batch_size)
実際には read_cifar10() がファイルからデータを読みます。
CIFAR-10 バイナリ・データファイルは固定バイト長レコードを含みますので、tf.FixedLengthRecordReader を使用しています。
def read_cifar10(filename_queue): """Reads and parses examples from CIFAR10 data files. Recommendation: if you want N-way read parallelism, call this function N times. This will give you N independent Readers reading different files & positions within those files, which will give better mixing of examples. Args: filename_queue: A queue of strings with the filenames to read from. Returns: An object representing a single example, with the following fields: height: number of rows in the result (32) width: number of columns in the result (32) depth: number of color channels in the result (3) key: a scalar string Tensor describing the filename & record number for this example. label: an int32 Tensor with the label in the range 0..9. uint8image: a [height, width, depth] uint8 Tensor with the image data """ class CIFAR10Record(object): pass result = CIFAR10Record() # Dimensions of the images in the CIFAR-10 dataset. # See http://www.cs.toronto.edu/~kriz/cifar.html for a description of the # input format. label_bytes = 1 # 2 for CIFAR-100 result.height = 32 result.width = 32 result.depth = 3 image_bytes = result.height * result.width * result.depth # Every record consists of a label followed by the image, with a # fixed number of bytes for each. record_bytes = label_bytes + image_bytes # filename_queue からファイル名を取得してレコードを読みます。 # CIFAR-10 フォーマットにはヘッダやフッタはないので、 # header_bytes と footer_bytes はデフォルトの 0 のままにしておきます。 reader = tf.FixedLengthRecordReader(record_bytes=record_bytes) result.key, value = reader.read(filename_queue) # Convert from a string to a vector of uint8 that is record_bytes long. record_bytes = tf.decode_raw(value, tf.uint8) # The first bytes represent the label, which we convert from uint8->int32. result.label = tf.cast( tf.slice(record_bytes, [0], [label_bytes]), tf.int32) # The remaining bytes after the label represent the image, which we reshape # from [depth * height * width] to [depth, height, width]. depth_major = tf.reshape(tf.slice(record_bytes, [label_bytes], [image_bytes]), [result.depth, result.height, result.width]) # Convert from [depth, height, width] to [height, width, depth]. result.uint8image = tf.transpose(depth_major, [1, 2, 0]) return result
Reader クラスの動作についての詳細は データを読む を参照してください。。
画像は次のように処理されます:
- 24 x 24 ピクセルにクロップ(切り出)されます。
評価目的では中央を、訓練のためには ランダム にクロップします。resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, width, height) distorted_image = tf.random_crop(reshaped_image, [height, width, 3])
- モデルがダイナミックレンジに過敏にならないように おおよそ白色化 (approximately whitened) されます。
float_image = tf.image.per_image_whitening(resized_image)
訓練のため、人工的にデータセットのサイズを増やすために一連のランダムな歪みを追加で適用します:
- ランダムに左から右へ画像を 水平にフリップ(ひっくり返) します。
- ランダムに 画像ブライトネス を歪めます。
- ランダムに 画像コントラスト を歪めます。
distorted_image = tf.image.random_flip_left_right(distorted_image) distorted_image = tf.image.random_brightness(distorted_image, max_delta=63) distorted_image = tf.image.random_contrast(distorted_image, lower=0.2, upper=1.8)
利用可能な歪みのリストは Images ページを見てください。
それから image_summary を画像にアタッチします。
def _generate_image_and_label_batch(image, label, min_queue_examples, batch_size): """Construct a queued batch of images and labels. Args: image: 3-D Tensor of [height, width, 3] of type.float32. label: 1-D Tensor of type.int32 min_queue_examples: int32, minimum number of samples to retain in the queue that provides of batches of examples. batch_size: Number of images per batch. Returns: images: Images. 4D tensor of [batch_size, height, width, 3] size. labels: Labels. 1D tensor of [batch_size] size. """ # Create a queue that shuffles the examples, and then # read 'batch_size' images + labels from the example queue. num_preprocess_threads = 16 images, label_batch = tf.train.shuffle_batch( [image, label], batch_size=batch_size, num_threads=num_preprocess_threads, capacity=min_queue_examples + 3 * batch_size, min_after_dequeue=min_queue_examples) # TensorBoard で訓練画像を表示する。 tf.image_summary('images', images) return images, tf.reshape(label_batch, [batch_size])
TensorBoard でそれらを視覚化できます。入力が正しく組み立てられているか検証するためには良い実践です。

ディスクから画像を読んでそれらを歪ませることは、処理時間の自明ではない総量を使用する可能性があります。これらの操作が訓練を遅延させることを防ぐために、継続的に TensorFlow キュー を満たす、16 の分離したスレッドの内部でそれらを実行します。
モデル予測
モデルの予測パートは inference() で構築されます。これは予測のロジットを計算するための演算を追加します。モデルのそのパートは以下のように体系化されます :
層名 | 説明 |
conv1 | 畳み込み と ReLU 活性化。 |
pool1 | 最大プーリング。 |
norm1 | 局所反応正規化 (local response normalization)。 |
conv2 | 畳み込みと ReLU 活性化。 |
norm2 | 局所反応正規化 (local response normalization)。 |
pool2 | 最大プーリング。 |
local3 | 全結合 with ReLU 活性化。 |
local4 | 全結合 with ReLU 活性化。 |
softmax_linear | ロジット生成のための線形変換。 |
def inference(images): """Build the CIFAR-10 model. Args: images: Images returned from distorted_inputs() or inputs(). Returns: Logits. """ # We instantiate all variables using tf.get_variable() instead of # tf.Variable() in order to share variables across multiple GPU training runs. # If we only ran this model on a single GPU, we could simplify this function # by replacing all instances of tf.get_variable() with tf.Variable(). # # conv1 : 畳み込みと ReLU 活性化 with tf.variable_scope('conv1') as scope: kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64], stddev=1e-4, wd=0.0) conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME') biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0)) bias = tf.nn.bias_add(conv, biases) conv1 = tf.nn.relu(bias, name=scope.name) _activation_summary(conv1) # pool1 : 最大プーリング pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool1') # norm1 : 局所反応正規化 (local response normalization)。 norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='norm1') # conv2 : 畳み込みと ReLU 活性化 with tf.variable_scope('conv2') as scope: kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64], stddev=1e-4, wd=0.0) conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME') biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1)) bias = tf.nn.bias_add(conv, biases) conv2 = tf.nn.relu(bias, name=scope.name) _activation_summary(conv2) # norm2 : 局所反応正規化 (local response normalization) norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='norm2') # pool2 : 最大プーリング pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME', name='pool2') # local3 : 全結合 with ReLU 活性化 with tf.variable_scope('local3') as scope: # Move everything into depth so we can perform a single matrix multiply. dim = 1 for d in pool2.get_shape()[1:].as_list(): dim *= d reshape = tf.reshape(pool2, [FLAGS.batch_size, dim]) weights = _variable_with_weight_decay('weights', shape=[dim, 384], stddev=0.04, wd=0.004) biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1)) local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name) _activation_summary(local3) # local4 : 全結合 with ReLU 活性化 with tf.variable_scope('local4') as scope: weights = _variable_with_weight_decay('weights', shape=[384, 192], stddev=0.04, wd=0.004) biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1)) local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name) _activation_summary(local4) # softmax, i.e. softmax(WX + b) with tf.variable_scope('softmax_linear') as scope: weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES], stddev=1/192.0, wd=0.0) biases = _variable_on_cpu('biases', [NUM_CLASSES], tf.constant_initializer(0.0)) softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name) _activation_summary(softmax_linear) return softmax_linear
次に推論演算を記述する TensorBoard から生成されたグラフを示します:

課題: 推論の出力は正規化されていないロジットです。tf.softmax() を使って正規化された予測を返すようにネットワークアーキテクチャを編集してみましょう。
課題: inference() のモデル・アーキテクチャは cuda-convnet で指定されている CIFAR-10 モデルと少し違います。特に、Alex のオリジナル・モデルのトップ層は局所的に接続され全結合されません。トップ層における局所的に接続されたアーキテクチャを正確に再現するためにアーキテクチャを編集してみましょう。
inputs() と inference() 関数はモデル上の評価を遂行するために必要な全てのコンポーネントを提供しています。
そこで焦点をモデルの訓練のための演算の構築にシフトします。
モデル訓練
N-項(多項)分類ネットワークを訓練する通常のメソッドは 多項ロジスティック回帰、別名 softmax 回帰です。
Softmax 回帰は出力に softmax 非線形性を適用して正規化された (normalized) 予測とラベルの 1-hot エンコーディング 間の 交差エントロピー を計算します。
また正則化 (regularization) のためには、全ての学習された変数に 荷重減衰 (weight decay) 損失を適用します。
loss() 関数によって返される、モデルに対する目的関数は交差エントロピー損失とこれらの荷重減衰の全ての項の合計です。
def loss(logits, labels): """Add L2Loss to all the trainable variables. Add summary for for "Loss" and "Loss/avg". Args: logits: Logits from inference(). labels: Labels from distorted_inputs or inputs(). 1-D tensor of shape [batch_size] Returns: Loss tensor of type float. """ # バッチに渡る平均交差エントロピー損失を計算します。 labels = tf.cast(labels, tf.int64) cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( logits, labels, name='cross_entropy_per_example') cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') tf.add_to_collection('losses', cross_entropy_mean) # 損失総計は、交差エントロピー損失に全ての荷重減衰項 (L2 loss) を加算したものとして定義されます。 return tf.add_n(tf.get_collection('losses'), name='total_loss')
scalar_summary を使って TensorBoard で視覚化することができます。
cifar10.train() に埋め込まれています。
def train(total_loss, global_step): ... tf.scalar_summary('learning_rate', lr) loss_averages_op = _add_loss_summaries(total_loss) def _add_loss_summaries(total_loss): loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') losses = tf.get_collection('losses') loss_averages_op = loss_averages.apply(losses + [total_loss]) for l in losses + [total_loss]: tf.scalar_summary(l.op.name +' (raw)', l) tf.scalar_summary(l.op.name, loss_averages.average(l)) return loss_averages_op
時間とともに 指数的に減衰する 学習率で標準的な 勾配降下 アルゴリズム(他のメソッドについては Training を見てください)を用いてモデルを訓練します。
cifar10#train() 関数は、勾配を計算して学習された変数を更新することにより目的(関数)を最小化するに必要な演算を追加します(詳細は GradientDescentOptimizer を参照)。
画像の一つのバッチのためにモデルを訓練し更新するために必要な全ての計算を実行する演算を返します。
def train(total_loss, global_step): """Train CIFAR-10 model. Create an optimizer and apply to all trainable variables. Add moving average for all trainable variables. Args: total_loss: Total loss from loss(). global_step: Integer Variable counting the number of training steps processed. Returns: train_op: op for training. """ # Variables that affect learning rate. num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY) # Decay the learning rate exponentially based on the number of steps. lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE, global_step, decay_steps, LEARNING_RATE_DECAY_FACTOR, staircase=True) tf.scalar_summary('learning_rate', lr) # Generate moving averages of all losses and associated summaries. loss_averages_op = _add_loss_summaries(total_loss) # 勾配を計算します。 with tf.control_dependencies([loss_averages_op]): opt = tf.train.GradientDescentOptimizer(lr) grads = opt.compute_gradients(total_loss) # 勾配を適用します。 apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) # Add histograms for trainable variables. for var in tf.trainable_variables(): tf.histogram_summary(var.op.name, var) # Add histograms for gradients. for grad, var in grads: if grad: tf.histogram_summary(var.op.name + '/gradients', grad) # Track the moving averages of all trainable variables. variable_averages = tf.train.ExponentialMovingAverage( MOVING_AVERAGE_DECAY, global_step) variables_averages_op = variable_averages.apply(tf.trainable_variables()) with tf.control_dependencies([apply_gradient_op, variables_averages_op]): train_op = tf.no_op(name='train') return train_op
モデルの Launching と訓練
モデルを構築したので、スクリプト cifar10_train.py でそれを launch して訓練演算を実行しましょう。
python cifar10_train.py
注意: 最初に CIFAR-10 チュートリアルの任意のターゲットを実行する時、CIFAR-10 データセットは自動的にダウンロードされます。データセットは ~ 160MB あり、最初の実行ではコーヒーでも飲みたくなるかもしれません。
出力は以下のようなものです:
Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes. 2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch) 2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch) 2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch) 2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch) 2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch) 2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch) ...
スクリプトは、10 ステップ毎の損失の総計と最後のバッチが処理された速度を報告します。
for step in xrange(FLAGS.max_steps): start_time = time.time() _, loss_value = sess.run([train_op, loss]) duration = time.time() - start_time assert not np.isnan(loss_value), 'Model diverged with loss = NaN' if step % 10 == 0: num_examples_per_step = FLAGS.batch_size examples_per_sec = num_examples_per_step / duration sec_per_batch = float(duration) format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f ' 'sec/batch)') print (format_str % (datetime.now(), step, loss_value, examples_per_sec, sec_per_batch)) if step % 100 == 0: summary_str = sess.run(summary_op) summary_writer.add_summary(summary_str, step) # Save the model checkpoint periodically. if step % 1000 == 0 or (step + 1) == FLAGS.max_steps: checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt') saver.save(sess, checkpoint_path, global_step=step)
2、3コメントがあります:
- データの最初のバッチは(例えば数分間)過度に遅いかもしれません、これは前処理するスレッドが 20,000 の前処理された CIFAR 画像でシャッフリング・キューを満たすためです。
- 報告された損失はもっとも最近のバッチの平均損失です。この損失は交差エントロピーと全ての荷重減衰項の合計であることを覚えていてください。
- バッチの処理速度に目を向けてください。上で表示された数値は Tesla K40c 上で得られたものです。もし貴方が CPU 上で実行しているのであれば、より遅いパフォーマンスが予想されます。
課題: 実験時、最初の訓練ステップが非常に長くかかり時に煩わしいかもしれません。初期化時にキューを満たす画像の数を減らしてみてください。cifar10.py の NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN を検索してください。
IMAGE_SIZE = 24 NUM_CLASSES = 10 NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000 NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000IMAGE_SIZE = cifar10_input.IMAGE_SIZE NUM_CLASSES = cifar10_input.NUM_CLASSES NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL
cifar10_train.py は定期的(1000 ステップ毎)に チェックポイント・ファイル に全てのモデルパラメータを 保存 しますが、しかしモデルを評価はしません。
# Save the model checkpoint periodically. if step % 1000 == 0 or (step + 1) == FLAGS.max_steps: checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt') saver.save(sess, checkpoint_path, global_step=step)
チェックポイント・ファイルは予測性能を計測するためには cifar10_eval.py で使用されます。(後述の「モデルを評価する」を見てください。)
もしこれまでのステップを追ったのであれば、今貴方は CIFAR-10 モデルの訓練が始められました。おめでとう!
cifar10_train.py から返される端末テキストはどのようにモデルが訓練されるか最少限の考察を提供しますが、訓練時のモデルへの更なる洞察を望むこともあるでしょう:
- 損失は本当に減少しているのか、あるいは単なるノイズか?
- モデルには妥当な画像が提供されているか?
- 勾配は、活性化は、そして重みは妥当か?
- 学習率は現在幾つか?
TensorBoard はこれらの機能を提供します、SummaryWriter を通して cifar10_train.py から定期的にエクスポートされたデータを表示します。
例えば、訓練時の local3 特徴における活性化の分布と疎性の程度がどのように進むかを見ることができます:


個々の損失関数は、総計の損失と同様に、時間を追って追跡するのに特に興味深いです。けれども、損失は、訓練で使用される小さいバッチサイズゆえにかなりの量のノイズを見せます。実際に移動平均を生の値に加えて視覚化することは非常に有用であることを見出すでしょう。スクリプトがこの目的のために ExponentialMovingAverage をどのように使うかを見てください。
def _add_loss_summaries(total_loss): """Add summaries for losses in CIFAR-10 model. Generates moving average for all losses and associated summaries for visualizing the performance of the network. Args: total_loss: Total loss from loss(). Returns: loss_averages_op: op for generating moving averages of losses. """ # Compute the moving average of all individual losses and the total loss. loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') losses = tf.get_collection('losses') loss_averages_op = loss_averages.apply(losses + [total_loss]) # Attach a scalar summary to all individual losses and the total loss; do the # same for the averaged version of the losses. for l in losses + [total_loss]: # Name each loss as '(raw)' and name the moving average version of the loss # as the original loss name. tf.scalar_summary(l.op.name +' (raw)', l) tf.scalar_summary(l.op.name, loss_averages.average(l)) return loss_averages_op
モデルを評価する
訓練されたモデルが取っておいたデータセット上でどのように上手くやるか評価してみましょう。モデルはスクリプト cifar10_eval.py で評価されます。これは inference() 関数でモデルを構築し、CIFAR-10 の評価セットの 10,000 画像全てを使います。それは 1 における正確性 (precision at 1): どのくらいの頻度で(1番)トップの予測が画像の真のラベルにマッチするかを計算します。
def evaluate(): """Eval CIFAR-10 for a number of steps.""" with tf.Graph().as_default(): # Get images and labels for CIFAR-10. eval_data = FLAGS.eval_data == 'test' images, labels = cifar10.inputs(eval_data=eval_data) # Build a Graph that computes the logits predictions from the # inference model. logits = cifar10.inference(images) # Calculate predictions. top_k_op = tf.nn.in_top_k(logits, labels, 1) # Restore the moving average version of the learned variables for eval. variable_averages = tf.train.ExponentialMovingAverage( cifar10.MOVING_AVERAGE_DECAY) variables_to_restore = variable_averages.variables_to_restore() saver = tf.train.Saver(variables_to_restore) # Build the summary operation based on the TF collection of Summaries. summary_op = tf.merge_all_summaries() graph_def = tf.get_default_graph().as_graph_def() summary_writer = tf.train.SummaryWriter(FLAGS.eval_dir, graph_def=graph_def) while True: eval_once(saver, summary_writer, top_k_op, summary_op) if FLAGS.run_once: break time.sleep(FLAGS.eval_interval_secs)
訓練時にモデルがどのように改善していくかをモニタするために、cifar10_train.py で作成された最新のチェックポイント・ファイル上で定期的に評価スクリプトが動作します。
python cifar10_eval.py
同じ GPU 上で評価と訓練バイナリを実行しないように注意してください。さもないとメモリ不足になるでしょう。もし利用可能であれば別の GPU 上で評価を実行するか、あるいは同じ GPU 上で評価を実行する間は訓練バイナリを中断することを考えてください。
次のような出力が見られるはずです:
2015-11-06 08:30:44.391206: precision @ 1 = 0.860 ...
スクリプトは単に “precision(精度) @ 1” を定期的に返すだけです — この場合は 86 % の精度を返しています。
cifar10_eval.py はまた要約をエクスポートします、これは TensorBoard で視覚化されることができます。これらの要約は評価時のモデルへの追加の洞察を提供します。
訓練スクリプトは全ての学習された変数全ての 移動平均 バージョンを計算します。評価スクリプトは学習されたモデルパラメータ全てを移動平均バージョンで代用します。この代用は評価時のモデル性能を押し上げます。
課題: 平均化されたパラメータを使用すると precision @ 1 で計測した時予測性能を約 3% 押し上げることができます。モデルに対する平均化されたパラメータを使用しないように cifar10_eval.py を編集して予測性能が落ちることを確認しましょう。
複数の GPU カードを使ってモデルを訓練する
現代的なワークステーションは科学計算のために複数の GPU を装備できますが、TensorFlow は訓練演算を複数の GPU カードに渡って同時に実行することでこの環境が利用できます。
並列的・分散的なマナーによるモデル訓練は訓練プロセスのコーディネートが必要となります。便宜上、データのサブセット上で訓練したモデルの一つのコピーをモデル・レプリカと呼称します。
モデル・パラメータの非同期更新を単純なやりかたで行なうことは最適以下の訓練性能につながります、何故なら個々のモデル・レプリカはモデル・パラメータの最新ではないコピーの上で訓練されるかもしれないからです。
逆に、完全に同期した更新を行なう場合はもっとも遅いモデル・レプリカ並に遅いことになります。
複数の GPU カードを装備するワークステーションでは、各 GPU は同種の速度を持ち CIFAR-10 モデル全体を実行する十分なメモリを持つでしょう。そこで、私たちの訓練システムを次のような方法で設計することを選択します :
- 各 GPU に個々のモデル・レプリカを配置する。
- モデル・パラメータは同期的に、全ての GPU がデータのバッチ処理を終了するまで待ってから更新する。
ここにこのモデルのダイアグラムを示します :

各 GPU はデータの唯一のバッチに対して勾配だけでなく推論を計算することに注意してください。このセットアップは GPU に渡ってデータのより大きなバッチを効率的に分割することを許可します。
このセットアップは全ての GPU がモデル・パラメータを共有することを必要とします。良く知られた事実として GPU へのそして GPU からのデータ転送は非常に遅いです。このため、(GPU ではなくて)CPU 上で全てのモデル・パラメータを保持し更新することにしました。(図の緑色の枠線のボックスを見てください。)モデル・パラメータのフレッシュなセットは、データの新しいバッチが全ての GPU で処理された時に GPU に転送されます。
GPU は演算で同期されます。全ての勾配は GPU から蓄積されて平均化されます(緑色のボックスを見てください)。モデル・パラメータは、モデル・レプリカに渡って平均化された勾配で更新されます。
デバイス上に変数と演算を配置する
演算と変数のデバイス上への配置は幾つかの特別な抽象化が必要です。必要な最初の抽象化は、一つのモデル・レプリカのために推論と勾配を計算するための関数です。コードレベルではこの抽象化を “タワー” と呼んでいます。各タワーに2つの属性を設定しなければなりません:
- タワー内の全ての演算に対する一意な名前。tf.name_scope() は、スコープを追加することでこの一意の名前を提供します。例えば、最初のタワーの全ての演算は tower_0、例えば tower_0/conv1/Conv2D で付加されます。
- タワーで演算を実行するための望ましいハードウェアデバイス。tf.device() がこれを指定します。例えば、最初のタワーの全ての演算は device(‘/gpu:0’) スコープに存在しますが、これはそれらが最初の GPU で実行されるべきであることを示しています。
TOWER_NAME = 'tower'
"""A binary to train CIFAR-10 using multiple GPU's with synchronous updates. Accuracy: cifar10_multi_gpu_train.py achieves ~86% accuracy after 100K steps (256 epochs of data) as judged by cifar10_eval.py. Speed: With batch_size 128. System | Step Time (sec/batch) | Accuracy -------------------------------------------------------------------- 1 Tesla K20m | 0.35-0.60 | ~86% at 60K steps (5 hours) 1 Tesla K40m | 0.25-0.35 | ~86% at 100K steps (4 hours) 2 Tesla K20m | 0.13-0.20 | ~84% at 30K steps (2.5 hours) 3 Tesla K20m | 0.13-0.18 | ~84% at 30K steps 4 Tesla K20m | ~0.10 | ~84% at 30K steps Usage: Please see the tutorial and website for how to download the CIFAR-10 data set, compile the program and train the model. http://tensorflow.org/tutorials/deep_cnn/ """ tf.app.flags.DEFINE_integer('num_gpus', 1, """How many GPUs to use.""") def train(): with tf.Graph().as_default(), tf.device('/cpu:0'): ... # Calculate the gradients for each model tower. tower_grads = [] for i in xrange(FLAGS.num_gpus): with tf.device('/gpu:%d' % i): with tf.name_scope('%s_%d' % (cifar10.TOWER_NAME, i)) as scope: # Calculate the loss for one tower of the CIFAR model. This function # constructs the entire CIFAR model but shares the variables across # all towers. loss = tower_loss(scope) # Reuse variables for the next tower. tf.get_variable_scope().reuse_variables() # Retain the summaries from the final tower. summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope) # Calculate the gradients for the batch of data on this CIFAR tower. grads = opt.compute_gradients(loss) # Keep track of the gradients across all towers. tower_grads.append(grads)
全ての変数は CPU にとどめられそしてマルチ-GPU 版ではそれらを共有するために tf.get_variable() でアクセスされます。how-to の 変数を共有する を見てください。
def train(): with tf.Graph().as_default(), tf.device('/cpu:0'): # Create a variable to count the number of train() calls. This equals the # number of batches processed * FLAGS.num_gpus. global_step = tf.get_variable( 'global_step', [], initializer=tf.constant_initializer(0), trainable=False)
複数の GPU カード上でモデルを Launching して訓練する
もし貴方のマシン上に幾つかの GPU カードをインストールしていれるのであれば、cifar10_multi_gpu_train.py スクリプトでモデルをより速く訓練するためにそれらを使用できます。訓練スクリプトのこのバージョンは複数の GPU カードに渡ってモデルを並列化します。
python cifar10_multi_gpu_train.py --num_gpus=2
訓練スクリプトは次のように出力するはずです :
Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes. 2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch) 2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch) 2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch) 2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch) 2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch) 2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch) ...
使用される GPU カード数は 1 にデフォルト設定されています。貴方のマシン上で 1 GPU のみが利用可能であるならば、仮にそれ以上を望んだとしても全ての計算はその上に配置されます。
課題:
cifar10_train.py のデフォルト設定では 128 のバッチサイズ上で動作します。64 のバッチサイズで cifar10_multi_gpu_train.py を 2 GPU 上で実行して訓練速度を比較してみてください。
次のステップ
おめでとうございます! 貴方は CIFAR-10 チュートリアルを終えました。
もし貴方自身の画像分類システムを開発して訓練することに興味があるのであれば、このチュートリアルを fork して、貴方の画像分類問題にアドレスするためにコンポーネントを置き換えることを勧めます。
課題:
Street View House Numbers (SVHN) データセットをダウンロードしてください。CIFAR-10 チュートリアルを fork して入力データとして SVHN と交換しましょう。予測性能を改善するためにネットワークアーキテクチャを適合させてください。
以上