TensorFlow : Get Started : ML 初心者向けの MNIST (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時: 04/13, 03/19/2017; 07/14, 06/26/2016
作成日時 : 01/26/2016
* 本家サイトのドキュメント構成の変更に伴い、本ページは以下のページをベースにするよう変更し、
また原文の加筆や変更に合わせて翻訳文も更新しました (03/19/2017) :
https://www.tensorflow.org/get_started/mnist/beginners
* (obsolete) 本ページは、TensorFlow の本家サイトの Tutorials – MNIST For ML Beginners を翻訳した上で
適宜、補足説明したものです:
(リンク切れ) https://www.tensorflow.org/versions/master/tutorials/mnist/beginners/index.html#mnist-for-ml-beginners
* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
このチュートリアルは機械学習と TensorFlow の初心者である読者を想定しています。もし貴方が MNIST が何であるか、そして softmax(多項ロジスティック)回帰が何であるか既に知っているのであれば、より 早いペースのチュートリアル が良いかもしれません。
どのようにプログラムをするか学ぶ時、最初に行なうことは “Hello World.” をプリントするという慣例があります。プログラミングに “Hello World” があるように、機械学習には “MNIST” があります。
MNIST は簡単なコンピュータ・ビジョンのデータセットで、このような手書き数字の画像から成ります :

データセットはまた、どの数字かを教えてくれる各画像へのラベルを含みます。例えば、上の画像へのラベルは 5, 0, 4 そして 1 です。
このチュートリアルでは、画像を見てどの数字であるか予測できるようにモデルを訓練していきます。我々の目的は最先端のパフォーマンスを達成できるような本当に手の込んだモデルを訓練することではありません — 後でそれを行なうコードを与えますが! — しかしそれよりも TensorFlow の利用に足を踏み入れることです。そういったわけで、非常に簡単なモデルから始めます、これは Softmax 回帰と呼ばれます。
このチュートリアルの実際のコードは非常に短く、全ての興味深いことはわずか3行の内に発生します。しかし、背後のアイデアを理解することは非常に重要です: どのように TensorFlow が動作するか、そして中心的な機械学習の概念です。それ故に、注意深くコードに取り組んでいきます。
このチュートリアルについて
このチュートリアルは mnist_softmax.py コードにおいて何が起きているのかの、1行ずつの説明です。
このチュートリアルを2、3の異なった方法で利用可能です、それは :
- 各行の説明を読み通したら各コード・スニペットを1行ずつ python 環境にコピー&ペーストします。
- 説明を通読する前なり後で mnist_softmax.py Python ファイル全体を実行し、そしてこのチュートリアルを貴方にとってクリアでないコードの行を理解するために使用します。
このチュートリアルで達成することは :
- MNIST データと softmax 回帰について学習します。
- 画像の全てのピクセルを見ることを基に、数字を認識するためのモデルである関数を作成します。
- 数千のサンプルを “見させる” ことで数字を認識するように TensorFlow を使ってモデルをトレーニングします (そしてそれを行なうように最初の TensorFlow session を実行します)。
- テストデータでモデル精度をチェックします。
MNIST データ
MNIST データは Yann LeCun’s website にホストされています。もし貴方がこのチュートリアルからのコードにおいてコピー&ペーストしているならば、データを自動的にダウンロードして読み込むこの2行のコードから始めましょう :
from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
ダウンロードされたデータは3つのパートに分かれます、訓練データの 55,000 データ・ポイント (mnist.train)、テスト・データの 10,000 ポイント (mnist.test) そして検証データの 5,000 ポイント (mnist.validation) です。この分割は非常に重要です : 機械学習において学習には使わない隔離データを持つことは本質的です。これにより学習したものが実際に一般化されていることを確かなものにできます!
先に言及したように、全ての MNIST データポイントは2つのパートを持ちます : 手書き数字の画像と該当ラベルです。画像を “x” そしてラベルを “y” と呼ぶことにします。訓練セットとテストセットは画像と該当するラベルを含みます; 例えば訓練イメージは mnist.train.images で訓練ラベルは mnist.train.labels です。
各画像は 28 ピクセル x 28 ピクセルです。これは数値の大きな配列と解釈することができます。

この配列を 28 x 28 = 784 数値のベクタに平坦化できます。画像間で一貫していればどのように配列を平坦化するかは問題ではありません、この見地からは、MNIST 画像は very rich structure (訳注: 計算集約型視覚化)を持つ、784-次元のベクタ空間のたくさんのポイントになります。
データの平坦化は画像の 2D 構造についての情報を捨てています。これはまずくないでしょうか? そうですね、ベストなコンピュータ・ビジョンの方法ではこの構造を利用しますので、後のチュートリアルでそうしましょう。しかしここで使う単純な方法 – softmax 回帰 – では利用しません。
結果は mnist.train.images は [55000, 784] の shape(形状)を持つテンソルになります。最初の次元は画像へのインデックスで2つめの次元は各画像のピクセルへのインデックスです。テンソルの各要素は特定の画像の特定のピクセルのための 0 と 1 の間のピクセル濃度です。

MNIST の該当するラベルは 0 から 9 の間の数字で、与えられた画像がどの数字であるかを示します。
このチュートリアルの目的のためにはラベルは “one-hot ベクタ” であることを望みます。one-hot ベクタはほとんどの次元で 0 で、一つの次元で 1 であるベクタです。この場合、n 番目の数字は n 番目の次元が 1 のベクタとして表されます。例えば、3 は [0,0,0,1,0,0,0,0,0,0] になります。結果的に、mnist.train.labels は float の [55000, 10] 配列になります。

今、我々のモデルを実際に作成する準備ができました!
Softmax 回帰
MNIST の全ての画像が、それが 0 であろうが 9 であろうが、数字であることを我々は知っています。我々は画像を見て(その画像に)各々の数字である確率を与えることができるようにしたいのです。例えば、我々のモデルは 9 の画像を見て、その画像が 9 であることが 80 % 確かであり、しかし((数字の形状の)上部のループゆえに) 8 である 5 % の可能性を与え、そして確信が持てないために全ての他の数字にも少しの可能性を与えます。
これは softmax 回帰が自然で簡単なモデルとなる古典的なケースです。幾つかの異なるものの一つであるオブジェクトに確率を割り当てたいならば、softmax がこれを行なってくれます。何故ならば softmax が(合計すると 1 になる) 0 と 1 の間の値のリストを与えてくれるからです。後に、より洗練されたモデルを訓練する時にさえも、最終ステップは softmax 層になります。
softmax 回帰は2つのステップを持ちます : 一つは、入力の、確かと考えるクラスに属するための証拠 (evidence) を集計すること、そして証拠を確率に変換します。
与えられた画像がある特定のクラスに属す証拠を計算するために、ピクセル強度の加重和(重みつき和)を取ります。高い強度を持つピクセルが画像があるクラスに属することへの反証となるのであれば重みはネガティブ(負値)です。そして補強する証拠であればポジティブ(正値)です。
次の図は一つのモデルがこれらのクラスについて学習した重みを示しています。赤色はネガティブな重みを表し、青色はポジティブな重みを表しています。

更に、バイアスと呼ばれる、特別な証拠も追加します。基本的に、入力からは独立的にある事象が起きやすいと言いたいがためです。結果的に入力 \(x\) が与えられた時のクラス \(i\) の証拠は :
\[\text{evidence}_i = \sum_j W_{i,~ j} x_j + b_i\]
ここで \(W_i\) は重みで \(b_i\) はクラス \(i\) のためのバイアス、そして \(j\) は入力画像 \(x\) のピクセルについて集計するためのインデックスです。そして証拠の合計を “softmax” 関数を使って予測確率 \(y\) に変換します :
\[y = \text{softmax}(\text{evidence})\]
ここで softmax は “活性化” あるいは “リンク” 関数として機能し、線形関数の出力を望ましい形式に整形します – この場合は 10 ケースの確率分布です。これは証拠の合計を、入力が各クラスに属する確率に変換するものとして考えることができます。次のように定義されます :
\[\text{softmax}(x) = \text{normalize}(\exp(x))\]
この式を展開すれば、次を得ます :
\[\text{softmax}(x)_i = \frac{\exp(x_i)}{\sum_j \exp(x_j)}\]
しかし softmax を最初の方法で考えることはしばしば有用です : 入力のべき乗(指数関数)を求めて正規化します。べき乗は、証拠の更なる1単位が任意の仮説に与えられた重みを乗法的に増加することを意味します。逆に、証拠の1単位の減少は、仮説に以前の重みのわずかな減少を与えられることを意味します。どの仮説も 0 や負の重みは決して持ちません。Softmax はそしてこれらの重みを正規化し、その結果、総計は 1 になり、有効な確率分布を形成します。(softmax 関数についてより直感的な知識を得たいのであれば、対話的な視覚化を完備する、Michael Nieslen の本のそれについての セクション を詳細に参照してください。)
softmax 回帰は次のような感じで図示できます、より多くの x を持ちますが。各出力に対して、x の重み合計を計算し、バイアスを加え、そして softmax を適用します。

数式で書くならば、以下を得ます :

この手続きは、行列の乗算とベクタの加算に変換することにより “ベクタ化” できます。これは計算効率のために有用です。(考えるにもまた良い方法です。)

よりコンパクトに単に :
\[y = \text{softmax}(Wx + b)\]
と書けます。
さてこれを TensorFlow が利用できるようなものにしましょう。
回帰を実装する
Python における効率的な数値計算を行なうためには、通常は NumPy のようなライブラリを用います。これらは他の言語で実装された高く効率化されたコードを用い、Python の外で行列の乗算のようなコスト高な演算を実行します。不運なことに、全ての演算において依然として Python へのスイッチバックからたくさんのオーバーヘッドがあります。このオーバーヘッドは、データ転送に高いコストがかかる、GPU 上や分散手法で計算を実行することを望む場合、特に悪いです。
TensorFlow もまた重たい演算を Python の外に持ち出しますが、このオーバーヘッドを回避するために物事を一歩先に進めています。Python から独立的に単一の高価な演算を実行する代わりに、TensorFlow は Python の外で完全に走る相互作用する演算のグラフの記述を可能にします。(このようなアプローチは2、3の機械学習ライブラリで見られます。)
TensorFlow を使うためには、import する必要があります。
import tensorflow as tf
シンボリックな変数を操作することによりこれらの相互作用する演算を記述します。一つ作成してみましょう :
x = tf.placeholder(tf.float32, [None, 784])
x は特定な値ではありません。これはプレースホルダーです。TensorFlow に計算の実行を依頼する時に入力する値です。我々は各々 784-次元ベクタに平坦化された MNIST 画像の任意の個数を入力できることを望みます。これを shape [None, 784] を持つ浮動小数点数値の 2-D テンソルとして表します。(ここでは None は次元が任意の長さを取りうることを意味しています。)
我々のモデルには重みとバイアスもまた必要です。これらを追加の入力のように扱うことも考えられますが、TensorFlow はそれを処理するにより良い方法を持っています : 変数 (Variable) です。変数は変更可能なテンソルで、相互作用の演算の TensorFlow のグラフの中で有効です。計算により利用可能で変更可能でさえもあります。機械学習のアプリケーションは、一般に、モデルパラメータを変数として持ちます。
W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10]))
変数の初期値を tf.Variable に与えることによってこれらの変数を作成します : この場合、全てゼロのテンソルとして W も b も初期化します。W と b を学習しますので、最初にどのような値であるかはそれほど重要ではありません。
W は [784, 10] の shape(形状)を持つことに注意してください。何故なら、それを 784-次元画像ベクタに乗算することによって異なるクラスのための証拠の 10-次元ベクタを生成することになるからです。b は [10] の shape を持ち、出力に加算できます。
ここにおいて我々のモデルが実装可能です。それを定義するためにわずか1行だけしかかかりません!
y = tf.nn.softmax(tf.matmul(x, W) + b)
最初に tf.matmul(x, W) という式で x に W を乗算します。この式では、\(Wx\) であった、先の我々の式でそれらを乗算した時とは(順序が)ひっくり返されていますが、これは x を複数の入力をもつ 2D テンソルとして扱うための小さなトリックです。それから b を加算し、最後に tf.nn.softmax を適用します。
これで全部です。短い数行のセットアップ後、我々のモデルを定義するのに1行しかかかりません。これは、TensorFlow が softmax 回帰を特に簡単にするように設計されているからではありません : 機械学習モデルから物理シミュレーションまで、数値計算の多くの種類を記述するに柔軟だからです。そして一度定義されれば、我々のモデルは異なるデバイスで実行されます : 貴方のコンピュータの CPU、GPU そしてスマホでさえもです!
訓練
我々のモデルを訓練するためには、モデルが良いとは何を意味するかを定義する必要があります。実際には、機械学習ではモデルが悪いとは何を意味するかを通常は定義します。これをコスト、あるいは損失と呼び、そしてそれは私たちのモデルが望ましい結果からどの程度遠く離れているかを表しています。エラーを最小化しようとします、そしてエラー・マージンが小さくなればモデルもより良くなります。
非常に一般的で良いコスト関数の一つは “交差(クロス)エントロピー”です。交差エントロピーは情報理論において情報圧縮コード (information compressing codes) について考えることから発生しています。しかし、それはギャンブルから機械学習まで、幅広い領域で重要なアイデアとなる結果になっています。それは以下で定義されます :
\[H_{y'}(y) = -\sum_i y'_i \log(y_i)\]
ここで \(y\) は我々が予測した確率分布で、\(y'\) は(入力する one-hot ベクタの)真の分布です。大雑把な意味では、交差エントロピー我々の予測が真実 (truth) を記述するにどのくらい非効率であるかをを計測します。交差エントロピーについてより詳細に踏み込むのはこのチュートリアルの範囲を超えていますが、理解する 価値は十分にあります。
交差エントロピーを実装するには、正解を入力するための新しいプレースホルダーの追加が最初に必要です :
y_ = tf.placeholder(tf.float32, [None, 10])
これにより交差エントロピー \(-\sum y'\log(y)\) が実装できます :
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
最初に、tf.log は y の各要素の対数を計算します。次に、_y の各要素と tf.log(y) の該当する要素を乗算します。それから tf.reduce_sum が reduction_indices=[1] パラメータによって、y の2番目の次元における要素を加算します。最後に、tf.reduce_mean がバッチの全てのサンプルに渡り mean を計算します。
これは一つの予測と正解の交差エントロピーではなく、我々が見ていた全ての画像のための交差エントロピーの合計である点に注意してください。この例で言えば、各バッチに 100 の画像を持ちます : 100 のデータポイント上でどの程度上手くやっているかは、一つのデータポイントよりも、我々のモデルがどの程度良いかを表すはるかに良い記述となります。
ソースコードで、この式を使っていないことに注意してください、何故ならばそれは数値的に不安定だからです。代わりに、正規化されていないロジット上で tf.nn.softmax_cross_entropy_with_logits を適用しています (e.g., tf.matmul(x, W) + b 上で softmax_cross_entropy_with_logits を呼び出します)、何故ならばこの数値的により安定的な関数が内部的に softmax 活性を計算するからです。貴方のコードでは、代わりに tf.nn.softmax_cross_entropy_with_logits を使用することを考えましょう。
我々のモデルに何をすることを望むかを知った今、TensorFlow にそれをするために訓練させることは非常に容易です。TensorFlow は貴方の計算のグラフの全体を知っていますので、変数がどのように(最小化したい)コストに影響を与えるか効率的に決定するために、自動的に バックプロパゲーション・アルゴリズム を使用することができます。そして最適化アルゴリズムの貴方の選択を適用して変数を変更しコストを減じます。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
この例では、0.01 の学習率で勾配降下法アルゴリズムを使って TensorFlow に交差エントロピーを最小化させることを依頼します。勾配降下法は単純な手続きで、ここでは TensorFlow はコストを減少させる方向に各変数をほんの少し単にシフトします。しかし、TensorFlow はまた 多くの他の最適化アルゴリズム を提供しています : 一つを使用することは一行調整する程度に簡単です。
ここで TensorFlow が裏で実際に行なうことは、バックプロパゲーションと勾配降下法をを実装する新しい演算をグラフに追加することです。そして TensorFlow は一つの演算を返します。これは、実行時に、勾配降下訓練のステップを行ない、コストを減少させるために変数を微調整するものです。
これで InteractiveSession でモデルを launch できます :
sess = tf.InteractiveSession()
最初に作成した変数を初期化するための演算を作成しなければなりません :
tf.global_variables_initializer().run()
訓練しましょう — 訓練ステップ 1000 回を実行します!
for _ in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
ループの各ステップで、訓練セットから 100 のランダムなデータポイントの “バッチ” を取得します。プレースフォルダーを置き換えるバッチデータを供給 (feed) して train_step を実行します。
ランダム・データの小さなバッチを使用することを確率的訓練 – この例では、確率的勾配降下法と呼称します。理想的には、訓練の全てのステップで全てのデータを使用したいのです。何故ならそれは何をなすべきかのより良いセンスを与えてくれるからです。しかしこれは高くつきます。そこで代わりに、毎回異なるサブセットを使用します。これを行なうのは安価で、かつ同様な多くの恩恵があります。
モデルを評価する
我々のモデルはどの程度上手くやるでしょうか?
そうですね、最初に正解のラベルを予測した場所を把握しましょうか。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))
最後に、テストデータ上で精度を求めます。
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
これは約 92 % になるでしょう。
これは良いでしょうか?う~ん、そうでもないですね。事実、これはかなり悪いです。これは我々が非常に単純なモデルを使っているからです。幾つかの小さな変更で、97% にまで到達できます。ベストなモデルは 99.7% を超えた精度に到達可能です!(更なる情報は、結果のリスト を見てください。)
重要なのはこのモデルから学んだことです。依然として、これら結果について少しがっかりしているのであれば、次のチュートリアル をチェックしてください。ここでは多くの改良を行ない、TensorFlow を使ったより洗練されたモデルを構築する方法を学びます!
以上