TensorFlow : Get Started : TensorFlow 技法 101 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 09/12/2017; 07/29/2016
作成日時 : 01/23/2016
* 本ページは、TensorFlow 本家サイトの Get Started – TensorFlow Mechanics 101 を翻訳した上で
適宜、補足説明したものです:
* (obsolete, リンク切れ) 本ページは、TensorFlow の本家サイトの Tutorials – TensorFlow Mechanics 101 を翻訳した上で
適宜、補足説明したものです:
- https://www.tensorflow.org/versions/master/tutorials/mnist/tf/index.html
* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
コード : tensorflow/examples/tutorials/mnist/
このチュートリアルの目的は、(古典的な)MNIST データセットを利用した手書き数字分類のための単純な feed-forward(順伝播型)ニューラルネットワークを学習させて評価するためにどのように TensorFlow を利用するかを示すことです。このチュートリアルの想定読者は TensorFlow を利用することに興味がある、機械学習の経験があるユーザです。
これらのチュートリアルは機械学習一般を教えることは意図していません。
チュートリアルのファイル
このチュートリアルでは次のファイルを参照します :
(ファイル : 目的)
mnist.py : このコードは full-connected(全結合)な MNIST モデルを構築します。
fully_connected_feed.py : 構築された MNIST モデルをダウンロードしたデータセットに対して feed 辞書を使って学習させるためのメイン・コードです。
学習を始めるには fully_connected_feed.py ファイルを単に直接実行するだけです :
$ python fully_connected_feed.py
データの準備
MNIST は機械学習の古典的な問題です。手書き数字のグレースケールの 28 x 28 ピクセル画像を見て画像が 0 から 9 までのどの数字を表しているかを決定する問題です。
より詳細な情報は、Yann LeCun’s MNIST page または Chris Olah’s visualizations of MNIST を参照してください。
ダウンロード
run_training() メソッドの冒頭では、input_data.read_data_sets() 関数が正しいデータがローカル学習フォルダにダウンロードされたかを保証し、それからデータセットのインスタンスの辞書を返すためにデータを取り出します。
data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)
注意: fake_data フラグはユニットテスト目的で使われますので reader で無視しても安全です。
データセット | 目的 |
data_sets.train : | 55000 イメージとラベル、主な学習用。 |
data_sets.validation : | 5000 イメージとラベル、学習中の正確さの反復 (iterative) バリデーション(検証)のため。 |
data_sets.test : | 10000 イメージとラベル、学習した正確性の最終的なテストのため。 |
データについての更なる情報は、MNIST データ・ダウンロード チュートリアルを読んでください。
入力とプレースホルダー
placeholder_inputs() 関数は残りのグラフに対して batch_size を含む入力の shape(形状)を定義する、2つの tf.placeholder OP を作成します。そしてそこに実際の学習サンプルが供給 (feed) されます。
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size, IMAGE_PIXELS)) labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
更に(コードを)進むと、学習ループの中で、全体の画像とラベルのデータセットは各ステップのために batch_size に適合するようにスライスされ、これらのプレースホルダー OP に合わせられ、そして feed_dict パラメータを使って sess.run() 関数の中に渡されます。
グラフの構築
データのためのプレースホルダーを作成した後、3-ステージ・パターン : inference()、loss() そして training() に従って mnisty.py からグラフが構築されます。
- inference()(= 推論) – 予測するためにネットワークを前向きに実行するために必要な限り(推論)グラフを構築する。
- loss() – 推論グラフに損失を生成するために必要な OP を追加する。
- training() – 勾配を計算して適用するために必要な OP を損失グラフに追加する。

Inference(推論)
inference() 関数は出力予測を含むテンソルを返す必要がある限りグラフを構築します。
(inference() は)画像プレースフォルダーを入力として取り、そのトップに ReLu 活性化(関数)とともに全結合層のペアを、続いて出力ロジット(対数オッズ)を示す 10 ノードの線形層を構築します。
各層は、scope 内で作成された項目のプレフィックスとして作用するユニークな tf.name_scope の下で作成されます。
with tf.name_scope('hidden1') as scope:
定義されたスコープ内で、これらの層の個々に使用される weights(重み)と biases(バイアス)は希望する shape(形状)で tf.Variable インスタンス内に生成されます :
weights = tf.Variable( tf.truncated_normal([IMAGE_PIXELS, hidden1_units], stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))), name='weights') biases = tf.Variable(tf.zeros([hidden1_units]), name='biases')
例えば、これらが hidden1 スコープの下で作成された時は、weights 変数にはユニークな名前が与えられ “hidden1/weights” となります。
各変数は生成の一部として initializer OP に与えられます。
この最も一般的なケースでは weights は tf.truncated_normal で初期化され、2-D テンソルの shape が与えられます。最初の次元は weights の接続元の層のユニット数を表し、2つめの次元は weights の接続先の層のユニット数を表します。
hidden1 と命名された最初の層では、次元は [IMAGE_PIXELS, hidden1_units] です。何故なら weights は画像入力を hidden1 層に接続しているからです。tf.truncated_normal initializer は与えられた平均値と標準偏差でランダム分布を生成します。
それから biases は全てゼロ値で始まることを保証するために tf.zeros で初期化されます。shape は単に接続先の層のユニット数です。
そしてグラフの3つの主要な OP — 隠れ層のための tf.matmul をラッピングした2つの tf.nn.relu OP とロジットのための1つの特別な tf.matmul OP — がそれぞれ順番に、入力プレースホルダーか前の層の出力テンソルに接続される、分離した tf.Variable インスタンスと共に作成されます。
hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases) hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases) logits = tf.matmul(hidden2, weights) + biases
最終的に、出力を含むロジット・テンソルが返されます。
訳注 : 通常はロジットは対数オッズのことです。
Loss(損失)
loss() 関数は必要な loss(損失) OP を追加することでグラフを更に構築します。
最初に、labels_placeholder からの値はワン・ホット (1-hot) 値のテンソルとしてエンコードされます。例えば、クラス識別子が ‘3’ であるならば値は次のように変換されます :
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
batch_size = tf.size(labels) labels = tf.expand_dims(labels, 1) indices = tf.expand_dims(tf.range(0, batch_size, 1), 1) concated = tf.concat(1, [indices, labels]) onehot_labels = tf.sparse_to_dense( concated, tf.pack([batch_size, NUM_CLASSES]), 1.0, 0.0)
そして tf.nn.softmax_cross_entropy_with_logits OP が inference() からの出力ロジットと 1-hot ラベルを比較するために追加されます。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits, onehot_labels, name='xentropy')
そして損失の総計としてバッチ次元(最初の次元)に渡る交差エントロピー値の平均を取るために tf.reduce_mean を 使用します。
loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
[注意]
交差エントロピー (Cross-entropy) は情報理論からのアイデアです。これは、実際にどれがが真であるかを与えられるとして、ニューラルネットワークの予測を信じるにはどの程度悪いかを表現することを可能にします。
更なる情報は、ブログポスト Visual Information Theory を読んでください。
Training(訓練)
training() 関数は勾配降下法によって損失を最小化するために必要な OP を追加します。
最初に、loss() 関数からの loss テンソルを取りそしてそれを tf.scalar_summary に渡します。この OP は SummaryWriter(後述)とともに使用される時はイベントファイルに要約値を生成します。
この場合、要約が書き出されるたびに損失のスナップショット値を吐きます。
tf.scalar_summary(loss.op.name, loss)
次に、要求された学習率で勾配法を適用する責を負う tf.train.GradientDescentOptimizer をインスタンス化します。
optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)
そしてグローバルな学習ステップのためのカウンターを含む一つの変数を生成して、minimize() OP がシステムの訓練可能な weights(重み)の更新とグローバルステップの繰り上げの両方のために使用されます。
これは慣習的に train_op として知られ、(この OP は)一つの学習の完全なステップを引き起こすために TensorFlow セッションにより実行されなければなりません。(後述)
global_step = tf.Variable(0, name='global_step', trainable=False) train_op = optimizer.minimize(loss, global_step=global_step)
学習 OP の出力を含むテンソルが返されます。
モデルを訓練する
ひとたびグラフが構築されれば、fully_connected_feed.py のユーザコードにより制御されたループの中で反復的に訓練され評価されます。
グラフ
run_training() 関数の冒頭は、全ての構築された OP はデフォルトのグローバルな tf.Graph インスタンスと関連づけらることを示す python コマンドです。
with tf.Graph().as_default():
tf.Graph はグループとして一緒に実行可能な OP のコレクションです。多くの TensorFlow の利用では一つのデフォルトのグラフに依拠することだけが必要でしょう。
複数のグラフを伴うより複雑な利用法も可能ですが、この簡単なチュートリアルの範囲を超えています。
セッション
全ての構築準備が完了し、全ての必要な OP が生成されたら、グラフを実行するために tf.Session が作成されます。
sess = tf.Session()
あるいは、スコープのために with ブロック内に Session が生成されます。
with tf.Session() as sess:
session への空パラメータは、このコードがデフォルトのローカル・セッションにアタッチする(あるいはもし未作成ならば作成する)ことを示しています。
セッションを作成したら直ちに、initialization OP で sess.run() を呼び出すことにより全ての tf.Variable インスタンスが初期化されます。
init = tf.initialize_all_variables() sess.run(init)
sess.run() メソッドは、パラメータとして渡された OP に対応するグラフの完全な部分集合を実行します。この最初の呼び出しで、init OP は変数への initializer だけを含む tf.group です。グラフの残りはここでは実行されません; それは下記の学習ループで発生します。
訓練ループ
セッションで変数を初期化した後、学習が始められます。
ユーザコードはステップ毎に学習を制御し、そして有用な学習を行なうことが可能な最も単純なループは :
for step in xrange(max_steps): sess.run(train_op)
です。
けれども、このチュートリアルは、先に生成されたプレースホルダーに適合させるためにステップ毎の入力データスライスしなければならないという点で少しばかりより複雑です。
グラフに供給する
各ステップで、コードは feed 辞書を生成します。これはステップの学習をするサンプルのセットを含み、それらが表すプレースホルダー OP をキーとします。
fill_feed_dict() 関数では、与えられたデータセットに batch_size の次の画像とラベルのセットが問い合わせされます。そしてプレースホルダーにマッチするテンソルには次の画像とラベルを含むように設定されます。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size)
それから、プレースホルダーをキーとし、それを表す feed テンソルを値として持つ、python 辞書オブジェクトが生成されます。
feed_dict = { images_placeholder: images_feed, labels_placeholder: labels_feed, }
これは学習のこのステップのための入力例を提供するために、sess.run() 関数の feed_dict パラメータに渡されます。
ステータスのチェック
コードは run 呼び出しで取得するための2つの値を指定します : [train_op, loss]
for step in xrange(FLAGS.max_steps): feed_dict = fill_feed_dict(data_sets.train, images_placeholder, labels_placeholder) _, loss_value = sess.run([train_op, loss], feed_dict=feed_dict)
取得する2つの値があるので、sess.run() は2つの要素のタプルを返します。取得する値のリストの中の各テンソルは返されたタプルの中の numpy 配列に相当し、学習のこのステップにおけるそのテンソルの値が設定されています。train_op は出力値なしの OP ですから、返されたタプルの中の相当する要素は None であり、それゆえに棄てられます。しかし loss テンソルの値は学習中にモデルが発散 (diverge) した場合、NaN になるかもしれません。よって、これらの値は捕えてロギングします。
NaN なしに学習がきれいに走ると仮定すれば、学習ループはまた、ユーザに学習状況を知らせるために簡単なステータス・テキストを 100 ステップ毎に表示します。
if step % 100 == 0: print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)
ステータスの視覚化
TensorBoard で使用されるイベントファイルを出力するために、全ての要約(この場合は一つ)はグラフ構築フェイズで一つの OP に集められます。
summary_op = tf.merge_all_summaries()
そしてそれから、セッションが作成されると、tf.train.SummaryWriter はグラフ自身と要約の値の両方を含むイベントファイルを書くためにインスタンス化されます。
summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, graph_def=sess.graph_def)
最終的に、summary_op が実行されて writer の add_summary() 関数に出力が渡されるたびにイベントファイルは新しい要約値で更新されます。
summary_str = sess.run(summary_op, feed_dict=feed_dict) summary_writer.add_summary(summary_str, step)
イベントファイルが書かかれた時、要約からの値を表示するために TensorBoard が学習フォルダに対して実行されます。
[注意]
Tensorboard をどのように構築して実行するかについての更なる情報は添付のチュートリアル Tensorboard: 学習を視覚化する を参照してください。
チェックポイントの保存
更なる学習や評価のために後でモデルを復元するために使用される、チェックポイント・ファイルを出力するためには tf.train.Saver をインスタンス化します。
saver = tf.train.Saver()
学習ループでは、チェックポイントファイルを学習ディレクトリに書くために、saver.save() メソッドが全ての学習可能な変数の現在値とともに定期的に呼び出されます。
saver.save(sess, FLAGS.train_dir, global_step=step)
先々どこか後のポイントで、モデル・パラメータをリロードするために saver.restore() メソッドを使うことにより学習を再開することもあり得ます。
saver.restore(sess, FLAGS.train_dir)
モデルを評価する
1000 ステップ毎に、コードは学習用とテスト用のデータセットの両方についてモデルの評価を試みます。学習、検証そしてテスト用のデータセットのために do_eval() 関数が3度呼ばれます。
print 'Training Data Eval:' do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.train) print 'Validation Data Eval:' do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.validation) print 'Test Data Eval:' do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.test)
[注意]
より複雑な利用方法では通常は data_sets.test をハイパー・パラメータの主要な量のチューニングの後でのみチェックする目的で隔離しておく点に注意してください。しかし簡単で小さな MNIST 問題のために全てのデータに対して評価します。
評価 (Eval) グラフの構築
デフォルト・グラフをオープンする前に、テスト・データセットを掴むためにセットされたパラメータとともに get_data(train=False) 関数を呼び出すことによりテストデータが取得されるべきです。
test_all_images, test_all_labels = get_data(train=False)
学習ループに入る前に、loss() 関数と同様のロジット/ラベル・パラメータとともに mnist.py から evaluation() 関数を呼び出すことにより、評価 (Eval) OP が構築されているべきです。
eval_correct = mnist.evaluation(logits, labels_placeholder)
evaluation() 関数は、真のラベルがもっともそれらしい予測 K 個の中で見つかる場合、自動的に各モデルの出力を正しいとスコアできる tf.nn.in_top_k OP を単に生成します。今回の場合は、真のラベルの場合のみ予測が正しいと考えることにして K の値を 1 に設定します。
出力を評価する
そして与えられたデータセット上でモデルを評価するために feed_dict を埋めて eval_corrent OP に対して sess.run() を呼び出すループが作成できます。
for step in xrange(steps_per_epoch): feed_dict = fill_feed_dict(data_set, images_placeholder, labels_placeholder) true_count += sess.run(eval_correct, feed_dict=feed_dict)
true_count 変数は in_top_k OP が正しいと決定した予測の全てを集計するだけです。ここから、正確性は単にサンプルの総数で除算することで計算されます。
precision = float(true_count) / float(num_examples) print ' Num examples: %d Num correct: %d Precision @ 1: %0.02f' % ( num_examples, true_count, precision)
以上