TensorFlow のチュートリアル(TensorFlow Mechanics 101)
https://www.tensorflow.org/versions/master/tutorials/mnist/tf/index.html#tensorflow-mechanics-101
の翻訳です。
翻訳の誤りなどあればご指摘お待ちしております。
コード: tensorflow/examples/tutorials/mnist/
このチュートリアルの目標は、(古典的)MNIST データ・セットを使用して手書き数字を分類するためのシンプルなフィードフォワード・ニューラルネットワークを訓練・評価するために TensorFlow を使用する方法を示すことです。このチュートリアルの対象読者は、TensorFlow を使用することに関心のある、機械学習の経験があるユーザーです。
これらのチュートリアルは、一般的に機械学習を教えるためのものではありません。
TensorFlow のインストール手順に従っていることを確認してください。
##チュートリアル・ファイル
このチュートリアルでは、以下のファイルを参照します:
ファイル | 目的 |
---|---|
mnist.py | 全結合 MNIST モデル構築のコード。 |
fully_connected_feed.py | ダウンロードされたデータセットに対し、フィード辞書を使用して構築された MNIST モデルを訓練するメインコード。 |
訓練を開始するには、直接 fully_connected_feed.py ファイルを実行します:
python fully_connected_feed.py
##データの準備
MNIST は、機械学習における古典的な問題です。この問題は、手書き数字のグレースケールの 28x28 ピクセルの画像を見て、画像が表す数字が0から9までのうちどの数字かを決定する、というものです。
詳細は、Yann LeCun の MNIST のページまたは Chris Olah の MNIST の視覚化を参照してください。
###ダウンロード
run_training() メソッドの始め、input_data.read_data_sets() 関数は、正しいデータがローカルの training フォルダにダウンロードされていることを確認し、データを解凍して DataSet インスタンスの辞書を返します。
data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)
注:fake_data フラグは、単体テストのために使用されるため、ここでは無視して構いません。
データセット | 目的 |
---|---|
data_sets.train | 主な訓練のための55000の画像やラベル。 |
data_sets.validation | 訓練精度の反復的検証のための5000の画像やラベル。 |
data_sets.test | 訓練精度の最終テストのための10000の画像やラベル。 |
データの詳細については、ダウンロードのチュートリアルを読んでください。
###入力とプレースホルダ―
placeholder_inputs() 関数は、グラフの残りの部分に、batch_size などの入力の形状を定義する、2つの tf.placeholder 操作を作成します。その中に実際の訓練サンプルがフィードされます。
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size,
IMAGE_PIXELS))
labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
下の訓練ループで、すべての画像およびラベルのデータセットは、各ステップにおいて batch_size にフィットするようにスライスされ、これらのプレースホルダ―に適合され、その後、feed_dict パラメータを使用して sess.run() 関数に渡されます。
##グラフの構築
データのプレースホルダ―を作成した後、グラフは mnist.py ファイルから3段階のパターンに従って構築されます:inference()、loss()、training() です。
- inference() - 予測のためにネットワークを前進実行するグラフを作成します。
- loss() - 損失を生成する操作をグラフに追加します。
- training() - 勾配を計算し適用する操作をグラフに追加します。
###推定(Inference)
inference() 関数は、出力の予測を含むテンソルを返すために、必要に応じてグラフを作成します。
この関数は、入力として画像プレースホルダーを取ります。そして、最初に、ReLu 活性化を伴う全結合層と、それに続く、出力ロジットを特定する10ノードの線形層を構築します。
各層は、一意の tf.name_scope の下に作成されます。tf.name_scope はそのスコープ内で作成されたアイテムへのプレフィックスとして機能します。
with tf.name_scope('hidden1'):
定義されたスコープ内において、各層で使用される重みおよびバイアスは、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」になります。
各変数には、そのコンストラクションの一部として初期化操作が与えられます。
上記のような最も一般的なケースでは、重みは tf.truncated_normal で初期化され、2次元テンソルの形状を持ちます。最初の次元は重みの結合元の層のユニット数、第2の次元は結合先の層のユニット数です。hidden1 という名の第1層において、重みは画像入力と hidden1 層を結合するため、次元は [IMAGE_PIXELS, hidden1_units] です。tf.truncated_normal 初期化子は、所与の平均値と標準偏差を持つランダムな分布を生成します。
そして、バイアスは、すべてゼロ値で開始することを保証するために tf.zeros で初期化されており、その形状は、単に結合先の層のユニット数です。
グラフの3つの主要な操作―隠れ層のための2つの tf.matmul をラップした tf.nn.relu と、ロジットのための tf.matmul―は、順番に、入力プレースホルダーまたは各層の出力テンソルと接続された 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() 関数は、必要な損失操作を追加することによってグラフをさらに構築します。
まず、labels_placeholder からの値が64ビット整数に変換されます。そして、labels_placeholder から1-ホットなラベルを自動的に作成し、inference() 関数からの出力ロジットとこれらの1-ホットラベルを比較するために tf.nn.sparse_softmax_cross_entropy_with_logits 操作が追加されます。
labels = tf.to_int64(labels)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits, labels, name='xentropy')
その後、総損失として、バッチ次元(最初の次元)に沿って交差エントロピー値の平均を得るために tf.reduce_mean を使用します。
loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
そして、損失値を含むテンソルを返します。
注意:交差エントロピーは、所与の真実に対し、ニューラルネットワークの予測を信じることがいかに悪いかを示す、情報理論に基づくアイデアです。詳細は、視覚情報定理のブログ記事を参照してください。(http://colah.github.io/posts/2015-09-Visual-Information/
)
###訓練(Training)
training() 関数は、勾配降下法による損失の最小化に必要な操作を追加します。
まず、loss() 関数から損失テンソルを取り、tf.scalar_summary に渡します。この操作は SummaryWriter(下記参照)と共に使用すると、イベント・ファイルに要約値を生成します。ここでは、要約が書き出されるたびに損失値のスナップショットを発行します。
tf.scalar_summary(loss.op.name, loss)
次に、tf.train.GradientDescentOptimizer をインスタンス化します。tf.train.GradientDescentOptimizer は要求された学習率による勾配を適用します。
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
そして、グローバルな訓練ステップのカウンタが入る一つの変数を生成し、minimize() 操作を使用します。minimize() 操作は、システムの訓練可能な重みを更新し、ステップをインクリメントします。慣例で、この操作は train_op として知られ、TensorFlow のセッションにより、訓練の1フルステップ(下記参照)を引き起こすために実行されるものです。
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)
訓練操作の出力を含むテンソルを返します。
##モデルの訓練
一旦グラフが構築されれば、fully_connected_feed.py のユーザーコードによって制御されるループ内で、反復的に訓練および評価できます。
###グラフ
run_training() 関数の始めは、python の with コマンドで、これは構築されたすべての操作がデフォルトのグローバルな tf.Graph インスタンスに関連づけられることを示します。
with tf.Graph().as_default():
tf.Graph は、グループとしてまとめて実行することができる操作のコレクションです。TensorFlow を使用するほとんどの場合、単一のデフォルトのグラフにのみ依存します。
複数のグラフを持つ、より複雑な使用法も可能ですが、この簡単なチュートリアルの範囲を越えています。
###セッション
構築準備がすべて完了し、必要な操作をすべて生成したら、グラフを実行するために tf.Session を作成します。
sess = tf.Session()
あるいは、スコープのための with ブロックに生成します:
with tf.Session() as sess:
Session の引数が空であることは、デフォルトのローカル・セッションにアタッチ(または作成されていない場合には作成)することを示します。
セッションが作成された直後、sess.run() を呼び出すことにより、すべての tf.Variable インスタンスは各々の初期化操作により初期化されます。
init = tf.initialize_all_variables()
sess.run(init)
sess.run() メソッドは、パラメータとして渡された操作に対応するグラフの完全なサブセットを実行します。この最初の呼び出しでは、init 操作は、変数の初期化子のみを含む tf.group です。グラフの残りの部分のいずれも、ここでは実行されません、それは、以下の訓練ループで行われます。
###訓練ループ
セッションで変数を初期化した後、訓練を開始することができます。
ユーザーコードは、ステップごとの訓練を制御します。以下に有用な訓練を行う最も単純なループを示します:
for step in xrange(FLAGS.max_steps):
sess.run(train_op)
しかし、このチュートリアルでのループは少し複雑です。各ステップにおいて、以前に生成したプレースホルダーに適合するように入力データをスライスする必要があるためです。
####グラフのフィード
各ステップにおいてフィード辞書を作成します。この辞書はその訓練ステップで使用されるサンプル集合を含み、それを表すプレースホルダ―をキーとします。
fill_feed_dict() 関数では、次の batch_size 個の画像およびラベルの集合を得るために、与えられた DataSet を照会します。プレースホルダ―に適合するテンソルが次の画像またはラベルにより満たされます。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size,
FLAGS.fake_data)
プレースホルダ―をキーとし、対応するフィード・テンソルを値として、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 は出力値のない操作であるため、返されるタプルの対応する要素は None であり、そのため破棄します。しかし、loss テンソルの値は、訓練中にモデルが発散した場合 NaN になるため、ログのためにキャプチャします。
NaN にならずに訓練が正常に実行された場合でも、ユーザーに訓練の状態を知らせるために、訓練ループは100ステップごとに簡単なステータス・テキストを出力します。
if step % 100 == 0:
print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)
####ステータスの可視化
TensorBoard で使用されるイベント・ファイルを発行するため、すべての要約(この場合は、一つだけですが)はグラフの構築フェーズ中に、単一の操作に集められます。
summary_op = tf.merge_all_summaries()
そしてセッションが作成された後、グラフ自体と要約の値を含むイベント・ファイルを書くために、tf.train.SummaryWriter をインスタンス化することができます。
summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, sess.graph)
最後に、イベント・ファイルは summary_op が実行されるたびに新しい要約値で更新され、出力はライターの 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()
訓練ループでは、すべての訓練可能な変数の現在の値を使用して training ディレクトリにチェックポイント・ファイルを書き込むために、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 に対する評価は隔離されることに注意してください。data_sets.test はかなりの量のハイパー・チューニング後にのみチェックされるためです。単純で小さな MNIST 問題のため、すべてのデータに対して評価します。
###評価グラフの構築
訓練ループに入る前に、Eval 操作を構築する必要があります。Eval 操作を構築するために、mnist.pyのevaluation() 関数を、loss() 関数と同じロジット/ラベルのパラメータで呼び出します。
eval_correct = mnist.evaluation(logits, labels_placeholder)
evaluation() 関数は、単に tf.nn.in_top_k 操作を生成します。tf.nn.in_top_k は、真のラベルが可能性の上位K番目までの予測に含まれる場合は各モデルの出力を正しいものとみなし、自動的にスコアします。ここでは、予測が真のラベルと一致する場合にのみ正しいとみなすため、Kの値を1に設定します。
eval_correct = tf.nn.in_top_k(logits, labels, 1)
###出力の評価
これで、ループを作成することができます。ループでは、feed_dict を満たし、eval_correct 操作について sess.run() を呼び出します。eval_correct 操作は与えられたデータセットに対してモデルを評価します。
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 操作が正しく判断したすべての予測を、単に集計します。精度は、単にこれをサンプル総数で割ることで計算できます。
precision = true_count / num_examples
print(' Num examples: %d Num correct: %d Precision @ 1: %0.04f' %
(num_examples, true_count, precision))