TensorFlow : Get Started : 専門家のための深層 MNIST(翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時: 04/26, 03/20/2017
更新日時: 07/28, 07/15, 06/26/2016
作成日時 : 02/14/2016
* 本家サイトのドキュメント構成の変更に伴い、本ページは以下のページをベースにするよう変更し、
また原文の加筆や変更に合わせて翻訳文も更新しました (03/20/2017) :
https://www.tensorflow.org/get_started/mnist/pros
* (obsolete) 本ページは、TensorFlow の本家サイトの Tutorials – Deep MNIST for Experts を翻訳した上で
適宜、補足説明したものです:
(リンク切れ) https://www.tensorflow.org/versions/master/tutorials/mnist/pros/index.html#deep-mnist-for-experts
* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
TensorFlow は大規模な数値計算を行なうためのパワフルなライブラリです。優れているタスクの一つは深層ニューラルネットワークを実装し訓練することです。このチュートリアルでは TensorFlow モデルの基本的なビルディング・ブロックを学ぶ一方で、深層畳み込み MNIST 分類器を構築します。
このイントロはニューラルネットワークと MNIST データセットに馴染みがあることを仮定しています。もし貴方がそれらの背景がないのであれば、初心者向けのイントロ をチェックしてください。
このチュートリアルについて
このチュートリアルの最初のパートは mnist_softmax.py コードで何が起きているのかを説明します、これは TensorFlow モデルの基本的な実装です。2番目のパートは精度を改善するための幾つかの方法を示します。
貴方はこのチュートリアルから各コード・スニペットを Python 環境にコピー & ペーストすることができますし、あるいはコードを読み通すだけという選択もできます。
このチュートリアルで達成することは :
- 画像の全てのピクセルを見ることを元に、数字を認識するためのモデルである softmax 回帰関数を作成します。
- 数千のサンプルを “見させる” ことで数字を認識するように TensorFlow を使ってモデルをトレーニングします (そしてそれを行なうように最初の TensorFlow session を実行します)。
- テストデータでモデル精度をチェックします。
- 結果を改善するために多層畳込みニューラルネットワークを構築し、トレーニングし、そしてテストします。
セットアップ
我々のモデルを作成する前に、最初に MNIST データセットをロードし、そして TensorFlow セッションを開始します。
MNIST データのロード
もし貴方がこのチュートリアルからのコードにおいてコピー&ペーストしているならば、データを自動的にダウンロードして読み込むこの2行のコードから始めましょう :
from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
ここで mnist は軽量のクラスで、訓練、検証そしてテスト・セットを NumPy 配列としてストアしています。これはまたデータ・ミニバッチを通して反復するための関数も提供し、これは下で使います。
TensorFlow InteractiveSession を開始する
TensorFlow は計算を行なうために高度に効率的な C++ バックエンドに頼っています。このバックエンドへの接続はセッションと呼ばれています。TensorFlow プログラムの共通する利用法は最初にグラフを作成してそしてセッションの中に launch することです。
ここでは代わりに便利な InteractiveSession クラスを使います、これはどのようにコードを構成するかについて TensorFlow をより柔軟にします。これはグラフを実行する演算とともに 計算グラフ を構築する不連続な操作を可能にします。これは IPython のような対話的なコンテキストで作業する時に特に便利です。もし InteractiveSession を使わないのであれば、セッションを開始して グラフを launch する前に全体の計算グラフを構築すべきです。
import tensorflow as tf sess = tf.InteractiveSession()
計算グラフ
Python で効率的な数値計算を行なうために、私たちは典型的には NumPy のようなライブラリを用います。これらは他の言語で実装された高度に効率的なコードを用い、Python の外で行列乗算のような高価な演算を行ないます。不幸なことに全ての演算について Python へのスイッチバックから依然として多くのオーバヘッドがあります。これらのオーバヘッドは、データ転送に高いコストが存在し得る GPU 上や分散マナーでの計算を実行したい場合は特に悪いです。
TensorFlow もまた Python の外へ重い持ち出しをしますが、このオーバーヘッドを避けるためにもう一歩先に進んでいます。Python から独立的に単独の高価な演算を実行する代わりに、TensorFlow は Python の外で完全に動作する、相互に作用する演算のグラフを記述することを可能にします。このアプローチは Theano や Torch で使われているものと類似のものです。
従って Python コードの役割は、この外部計算グラフを構築し、そして計算グラフのどのパートが実行されるべきかを命じることにあります。より詳細は 基本的な使い方 の計算グラフのセクションを見てください。
Softmax 回帰モデルを構築する
このセクションでは単一の線形層で softmax 回帰モデルを構築します。次のセクションでは、これを多層の畳み込みネットワークで softmax 回帰の例に拡張します。
プレースホルダー
入力画像と目的出力クラスのためのノードを作成することにより計算グラフの組み立てを開始します。
x = tf.placeholder(tf.float32, shape=[None, 784]) y_ = tf.placeholder(tf.float32, shape=[None, 10])
ここで x と y_ は特定の値ではありません。むしろ、個別のプレースホルダーです — TensorFlow に計算を実行することを依頼する時に入力する値です。
入力画像 x は浮動小数点の 2d テンソルからなります。ここで shape [None, 784] を割り当てます、784 は一つの平坦化した MNIST 画像の次元数で、None は、バッチサイズに相当する最初の次元が任意のサイズを取りうることを示しています。目的出力クラス y_ はまた 2d テンソルからなり、各行は one-ホット 10-次元ベクタで該当の MNIST 画像がどの数字クラス (0 から9 まで) に属するかを示しています。
プレースホルダーへの shape (形状)引数はオプションですが、しかしそれは TensorFlow が矛盾するテンソル形状に由来するバグを自動的に捉えることを可能にします。
変数
今私たちのモデルのために重み W とバイアス b を定義します。これらを追加入力のように扱うことも考えられますが、TensorFlow はこれらを扱うよりもより良い方法持ちます : 変数です。変数は TensorFlow の計算グラフで生存する値です。計算により使用できて修正さえ可能です。機械学習アプリケーションにおいて、一般に持つモデル・パラメータは変数となります。
W = tf.Variable(tf.zeros([784,10])) b = tf.Variable(tf.zeros([10]))
tf.Variable への呼び出しで各パラメータに初期値を渡します。この場合、W と b を全て 0 のテンソルとして初期化します。W は 784 x 10 行列(何故なら 784 入力特徴と 10 出力を持つから)で b は 10-次元ベクタ(何故なら 10 クラスあるから)です。
変数がセッションで使用可能になる前に、これらはそのセッションを使って初期化されなければなりません。このステップは既に指定されている初期値(この場合はテンソルは全て 0)を取り、そして各変数にそれらを割り当てます。これは全ての変数について一度で行なわれます。
sess.run(tf.global_variables_initializer())
予測クラスとコスト関数
今我々の回帰モデルが実装できます。わずか1行だけです! ベクタ化された入力画像 x を重み行列 W で乗算し、バイアス b を加算します。
y = tf.matmul(x,W) + b
損失関数は簡単に指定できます。損失は単一のサンプル上でモデルの予測がどの程度悪いかを示します; 全てのサンプルに渡りトレーニングの間にそれを最小化しよとします。ここで、損失関数はターゲットとモデルの予測に適用される softmax 活性化関数の間のクロス・エントロピーです。初心者チュートリアル内のように、安定した式を使用します :
cross_entropy = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
tf.nn.softmax_cross_entropy_with_logits は内部的にはモデルの正規化されていないモデル予測上で softmax を適用して全てのクラスに渡り合計し、そして tf.reduce_mean がそれらの合計の平均を取ります。
モデルを訓練する
我々のモデルと訓練するコスト関数を定義した今、TensorFlow を使って訓練するだけです。TensorFlow は全体の計算グラフを知っているので、各変数に関連してコストの勾配を見つけるために自動微分が使用できます。TensorFlow は様々な 組み込みの最適化アルゴリズム を持ちます。この例では、交差エントロピーを降るために 0.5 のステップ長で最急勾配降下を用います。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
TensorFlow がその単一の行で実際に行なったことは計算グラフに新しい演算を追加したことです。これらの演算は勾配を計算し、パラメータ更新ステップを計算して更新ステップをパラメータに適用するためのものを含みます。
戻り値の演算 train_step は、実行時、勾配降下更新をパラメータに適用します。モデルの訓練は従って train_step を繰り返し実行することにより達成されます。
for _ in range(1000): batch = mnist.train.next_batch(100) train_step.run(feed_dict={x: batch[0], y_: batch[1]})
各訓練反復で 100 訓練サンプルをロードします。そして プレースホルダー・テンソル x と y_ を訓練サンプルで置き換えるために feed_dict を用いて train_step 演算を実行します。feed_dict を用いて計算グラフの任意のテンソルを置き換えられることに注意してください — プレースホルダーのみに制限されているわけではありません。
モデルを評価する
我々のモデルはどの程度上手くやれたでしょうか?
最初にどこで正しいラベルを予測したかをはっきりさせましょう。tf.argmax は非常に有用な関数である軸に沿ってテンソルのもっとも高いエントリのインデックスを与えます。例えば、tf.argmax(y,1) が各入力に対して我々のモデルがもっともそれらしいと考えるラベルで、一方で tf.argmax(y_,1) は真のラベルです。予測が真の値にマッチしているか確認するためには tf.equal が使用できます。
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
それはブール値のリストを与えます。どの程度の分数が正しいか決定するために、浮動小数点数値にキャストしてから平均を取ります。例えば、[True, False, True, True] は [1,0,1,1] となりこれは 0.75 となります。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
最後に、テストデータ上の精度を評価できます。これは約 92% 正しくなるはずです。
print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
多層畳み込みネットワークを構築する
MNIST 上の 92 % の精度の取得は悪いです。ほとんど当惑するほど悪いです。このセクションでは、これを解決します、非常に簡単なモデルから適度に洗練されたもの: 小さい畳み込みニューラルネットワークへとジャンプします。これは 99.2% 程度の精度へ導いてくれます — 最先端のものではありませんが、立派なものです。
重みの初期化
このモデルを作成するためには、多くの重みとバイアスを作成していく必要があります。一般に対称性の破れのため、そして 0 の勾配を防ぐために、微量のノイズとともに重みを初期化すべきです。ReLU ニューロンを使用していますので、”死んだニューロン” を回避するためにわずかに正の初期バイアスでそれらを初期化することは良い実践です。モデルを構築する間に繰り返しこれを行なう代わりに、それを行なう2つの便利な関数を作成しましょう。
def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial)
*訳注 : tf.truncated_normal は、切断正規分布から乱数値を出力します。
畳み込みとプーリング
TensorFlow はまた、畳み込みとプーリング処理について多くの柔軟性を与えてくれます。境界をどのように処理しましょうか? ストライドの間隔はどのくらい?この例では、我々はいつも普通の版を選択していきます。我々の畳み込みは 1 のストライドを用い、0 でパディングされますので出力は入力と同じサイズになります。プーリングは 2×2 ブロックの分かりやすい古典的な最大プーリングです。コードをきれいに保つため、これらの演算もまた関数に抽象化しましょう。
def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
第1畳み込み層
今私たちは第1層の実装ができます。畳み込み(層)と、それに続く最大プーリング(層)から成り立ちます。畳み込みは各 5×5 パッチのために 32 の特徴を計算します。その重みテンソルは [5, 5, 1, 32] の形状を持ちます。最初の2次元はパッチ・サイズで、次が入力チャネルの数、そして最後が出力チャネルの数です。各出力チャネルのための成分を持つバイアス・ベクタをまた持ちます。
W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32])
この層を適用するために、第2と3の次元は画像の幅と高さに相当し、そして最後の次元はカラー・チャネル数に相当するように、(入力)x を 4d テンソルに最初に reshape します。
x_image = tf.reshape(x, [-1,28,28,1])
そして x_image を重みテンソルで畳み込み、バイアスを加算し、ReLU 関数を適用し、そして最後に最大プールします。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) h_pool1 = max_pool_2x2(h_conv1)
第2畳み込み層
深層ネットワークを構築するために、このタイプの幾つかの層を積み重ねます。第2層は各 5×5 パッチに対して 64 の特徴を持ちます。
W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) h_pool2 = max_pool_2x2(h_conv2)
密結合された層(Densely Connected Layer, 全結合層)
画像サイズが 7×7 まで減じられた今、画像全体の処理を可能にするために 1024 ニューロンの全結合層を追加します。プーリング層からのテンソルをベクタのバッチに reshape し、重み行列を乗算し、バイアスを加算し、ReLU を適用します。
W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
ドロップアウト
過学習を減じるために、読み出し層 (readout layer) の前にドロップアウトを適用します。確率のためにプレースホルダーを作成します、これはニューロンの出力をドロップアウトの間保持するものです。これは訓練の間はドロップアウトを有効にし、テストの間には無効にすることを可能にしてくれます。TensorFlow の tf.nn.dropout OP はニューロン出力のマスクに加えてスケーリングを自動的に処理します、ドロップアウトは追加のスケーリングなしに動作します。(注釈 1)
keep_prob = tf.placeholder(tf.float32) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
読み出し層 (Readout Layer)
最後に、上述の softmax 回帰の一つの層のように、softmax 層を追加します。
W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
モデルを訓練し評価する
このモデルはどの程度上手くやるでしょう? それを訓練し評価するために上述の簡単な単層 SoftMax ネットワークのものとほぼ同一のコードを用います。
違いは次のようなものです :
- 最急勾配降下オプティマイザーをより洗練された ADAM オプティマイザーで置き換えます。
- ドロップアウト率を制御するために feed_dict の追加パラメータ keep_prob を含めます。
- 訓練プロセスにおいて 100 番目の反復毎にロギングを追加します。
続けてこのコードを実行してください、しかしそれは 20,000 訓練反復を行ない、
貴方のプロセッサに依存して、しばらくの時間がかかるでしょう (おそらく半時間まで)。
cross_entropy = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv)) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) sess.run(tf.global_variables_initializer()) for i in range(20000): batch = mnist.train.next_batch(50) if i%100 == 0: train_accuracy = accuracy.eval(feed_dict={ x:batch[0], y_: batch[1], keep_prob: 1.0}) print("step %d, training accuracy %g"%(i, train_accuracy)) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) print("test accuracy %g"%accuracy.eval(feed_dict={ x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
このコードを実行した後の最終的なテストセットの精度は約 99.2 % になるでしょう。
訳注: 実際に丁度 99.2 % の精度になりました。
我々は TensorFlow を使って、どのように素早く簡単にかなり洗練された深層学習モデルを構築し、訓練しそして評価するかを学びました。
注釈 1: この小さな畳込みネットワークに対しては、性能は実際には dropout があってもなくても殆ど同じです。dropout はしばしば過学習を減じるのに有効ですが、それは非常に巨大なニューラルネットワークをトレーニングするときに最も有用です。
以上