1. Qiita
  2. 投稿
  3. 機械学習

TensorFlowチュートリアル - ML初心者のためのMNIST(翻訳)

  • 169
    いいね
  • 0
    コメント

TensorFlowのチュートリアル(MNIST For ML Beginners)
https://www.tensorflow.org/versions/master/tutorials/mnist/beginners
の翻訳です。
翻訳の誤りなどあればご指摘お待ちしております。


このチュートリアルは、機械学習と TensorFlow に不慣れな読者を対象とします。MNIST が何であるか、ソフトマックス(多項ロジスティック)回帰が何であるかを知っている場合は、よりテンポの速いチュートリアルを好むかもしれません。チュートリアルを開始する前にTensorFlow をインストールしてください。

プログラミングを学ぶとき、最初にすることが「Hello World.」をプリントすることであるという伝統があります。 プログラミングには Hello World があるように、機械学習には MNIST があります。

MNIST は、単純なコンピュータビジョンのデータセットです。それは、以下のような手書きの数字のイメージで構成されています:

図

また、それぞれの画像にはそれがどの数字かを示す、ラベルが含まれています。例えば、上記の画像のラベルは 5、0、4、1 です。

このチュートリアルでは、画像を見て、それが何の数字かを予測するモデルをトレーニングするつもりです。私たちの目標は最先端の性能を実現する本当に精巧なモデルを訓練することではなく(後でそうするためにコードを提示します!)、TensorFlow の使用に慣れることにあります。このように、私たちはソフトマックス回帰と呼ばれる非常に単純なモデルから始めるつもりです。

このチュートリアルの実際のコードは非常に短く、すべての面白いものはわずか3行で起こります。しかし、その背後にある考え方を理解することはとても重要です:TensorFlow がどのように動作し、機械学習の中心概念は何か。このため、とても慎重にコードを見ていきます。

MNISTデータ

MNIST データは Yann LeCun のウェブサイト上でホストされています。便宜のために、データを自動的にダウンロードしてインストールする、いくつかの Python コードを含めました。コードをダウンロードし、以下のようにそれをインポート、または単にコピー&ペーストすることができます。

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

ダウンロードされたデータは、二つの部分、訓練データ(mnist.train)の 55,000 データ・ポイントとテストデータの 5,000 データ・ポイント(mnist.test)に分かれます。この分割はとても重要です:学習内容が実際に一般化されていることを確認するために、学習しないデータを別に持つことが機械学習には不可欠なのです!

前述したように、すべての MNIST データ・ポイントは、2つの部分を持っています:手書き数字の画像と、それに対応するラベルです。私たちは、画像を「xs」、ラベルを「ys」と呼びます。訓練セットとテストセットのどちらも、xs と ys を含みます。例えば、訓練画像は mnist.train.images であり、訓練ラベルは mnist.train.labels です。

各画像は 28 ピクセル × 28 ピクセルです。私たちは、これを数値の大きな配列と解釈することができます:

図

私たちは、28x28=784 の数値のベクトルにこの配列をフラット化することができます。画像間で一貫している限り、配列をフラットにする方法は重要ではありません。このような観点から、MNIST 画像は、非常にリッチな構造(警告:計算的に集約的な視覚化)を持った、784 次元のベクトル空間の一連の点です。

データをフラットにすることは、画像の2次元構造に関する情報を捨てます。それは悪いことではありませんか?最高のコンピュータビジョンの方法では、この構造を活用し、後のチュートリアルではそうします。しかし、ここで採用する単純な方法、ソフトマックス回帰ではそうしません。

結果として mnist.train.images は [55000, 784] の形状を有するテンソル(n次元配列)です。最初の次元は画像のインデックスで、2番目の次元は各画像のピクセルのインデックスです。テンソルの各要素は、特定の画像の特定のピクセルのための、0 と 1 の間のピクセル強度です。

図

MNIST で対応するラベルは、与えられた画像がどの数字かを記す、0 から 9 までの数字です。このチュートリアルの目的のため、ラベルを「1-ホットベクトル」として必要とします。1-ホットベクトルは、ほとんどの次元で 0 であり、1つの次元でのみ 1 であるベクトルです。この場合、$n$ 番目の数字は $n$ 次元目が 1 であるベクトルとして表現されます。例えば、3 は、$[0,0,0,1,0,0,0,0,0,0]$ です。結果的に、mnist.train.labels は、float の [55000, 10] 配列です。

図

これで、実際にモデルを作るための準備が整いました!

ソフトマックス回帰

MNIST 内のすべての画像は、0 なのか 9 なのか分かりませんが、数字であることを私たちは知っています。私たちは、画像を見て、それが各々の数字である確率を与えることができるようにしたいです。例えば、モデルは 9 の絵を見て、それが 80% の確信度で 9 である、しかし、5% の機会でそれは 8 である(上部にループがあるため)、他のすべての数字は、確かではないため、確率はわずかである、というように。

これは、ソフトマックス回帰が自然で単純なモデルである、古典的なケースです。いくつかの異なるものの一つであるオブジェクトに確率を割り当てたい場合は、ソフトマックスを使用すべきです。後に、より洗練されたモデルを訓練する場合でも、最後のステップは、ソフトマックスのレイヤーになります。

ソフトマックス回帰には、2 つのステップがあります:最初に、入力がある特定のクラスに含まれる証拠を足し合わせ、次に、この証拠を確率に変換します。

与えられた画像が特定のクラスに含まれる証拠を合計するために、ピクセル強度の加重和を行います。クラスに含まれる画像に反して、ピクセルが高い強度を持つ場合は重みは負であり、支持する証拠である場合には正です。

次の図は、あるモデルが、これらのクラスのそれぞれについて学習した重みを示しています。赤色が負の重みを表し、青色が正の重みを表します。

図

また、バイアスと呼ばれるいくつかの余分な証拠を追加します。基本的に、いくつかのものは、入力に関わらず、可能性が高いと言うことができるようにしたいです。結果的に、与えられた入力 $x$ がクラス $i$ であるための証拠は:

\text{evidence}_i = \sum_j W_{i,~ j} x_j + b_i

ここで、$W_i$ は重み、$b_i$ はクラス $i$ のバイアス、$j$ は入力画像 $x$ 内のピクセルを加算するためのインデックスです。そして、「ソフトマックス」関数を使って証拠の合計を予測確率 $y$ に変換します:

y = \text{softmax}(\text{evidence})

ここでソフトマックスは、線形関数の出力を望みの形に整形する、「活性化」または「リンク」関数として提供されています、このケースでは、10 例の確率分布です。証拠の合計を入力が各クラスに含まれる確率に変換すると考えることができます。それは次のように定義されます:

\text{softmax}(x) = \text{normalize}(\exp(x))

式を展開すると、次のようになります:

\text{softmax}(x)_i = \frac{\exp(x_i)}{\sum_j \exp(x_j)}

しかし、しばしばソフトマックスを最初の方法で考える方がより有益です:入力を累乗して、正規化。累乗は、1単位の証拠の増加が、すべての仮説に与えられる重みを乗法的に増加させることを意味します。そして逆に、1単位の証拠の減少が、仮説の重みのある割合を減少させることを意味します。どの仮説もゼロまたは負の重みを持っていません。ソフトマックスはこれらの重みを正規化します、それらの合計は1になり、有効な確率分布を形成します。 (ソフトマックス関数についてより多くの直感を得るためには、インタラクティブ視覚化を完備した、Michael Nieslen の本の関連するセクションをチェックしてください。)

ソフトマックス回帰は次のような図で表すことができますが、より多くの $x$ を持ちます。各出力には、$x$ の加重和を計算し、バイアス加え、ソフトマックスを適用します。

図

方程式は:

図

行列の乗算とベクトルの加算に変えることにより、この手順を「ベクトル化」することができます。これは、計算効率のために有効です。 (また、考えるためにも有用です。)

図

よりコンパクトに:

y = \text{softmax}(Wx + b)

回帰の実装

Python で効率的な数値計算を行うためには、通常、行列の乗算などの高コストな操作を、別の言語で実装された非常に効率的なコードを用いて Python の外で行う、NumPy のようなライブラリを使用します。残念ながら、まだすべての操作を Python からスイッチ・バックするには多くのオーバーヘッドがある場合があります。GPU、または分散環境で計算を実行したい場合、データ転送が高コストの場合には、このオーバーヘッドは特に悪いです。

TensorFlow も Python の外に重い処理を持ち出しますが、このオーバーヘッドを一歩遠く回避する方法を取ります。単一の高コストな操作を Python から独立して実行する代わりに、TensorFlow では、完全に Python の外で実行する操作を相互作用のグラフとして記述することができます。 (これは、いくつかの機械学習ライブラリーに見られるようなアプローチです)。

TensorFlow を使用するには、インポートする必要があります。

import tensorflow as tf

シンボリック変数を操作することによって、これらの相互作用の操作について説明します。 1 つ作成してみましょう:

x = tf.placeholder(tf.float32, [None, 784])

x は特定の値ではありません。これは、プレースホルダー(TensorFlow に計算を実行するよう依頼するとき、私たちが入力する値)です。任意の数の MNIST 画像を入力することができるようにしたいです、それぞれは 784 次元ベクトルにフラット化します。これを浮動小数点数の2次元テンソルとして、形状 [None, 784] で表します。(ここで None は、次元が任意の長さをとることができることを意味します。)

また、モデルの重みとバイアスを必要としています。これらを追加の入力のように扱うと想像されるかもしれませんが、TensorFlow ではそれを処理するためのより良い方法があります:変数です。変数は、TensorFlow の相互作用のグラフ内にある、変更可能なテンソルです。それは計算により使用され、変更することもできます。機械学習アプリケーションでは、一般的に、モデル・パラメータは変数として持ちます。

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

tf.Variable に変数の初期値を与えることによって、これらの変数を作成します。このケースでは、W と b を共に 0 で満たされたテンソルとして初期化します。W と b を学習しようとしているので、それらが最初何であるかはあまり重要ではありません。

W は [784, 10] の形状をしていることに注意してください、これを 784 次元の画像ベクトルに乗算し、異なるクラスのための 10 次元の証拠ベクトルを生成するためです。b は [10] の形状をしていて、出力にそれを追加することができます。

これで、モデルを実装することができます。たった1行で!

y = tf.nn.softmax(tf.matmul(x, W) + b)

まず、式 tf.matmul(x,W) で x に W を掛けます。式での乗算と比べて、逆転しています(先ほどは $Wx$ でした)、これは、x を複数の入力を持つ2次元テンソルとして扱うための、小さなトリックです。それから、b を加え、最後に tf.nn.softmax を適用します。

これですべてです。モデルを定義するためのたった1行と、セットアップのための短い数行のみです。TensorFlow がソフトマックス回帰を特に容易にできるように設計されているためではありません:それは機械学習モデルから物理シミュレーションまで、多くの種類の数値計算を記述する非常に柔軟な方法です。そして一旦定義されれば、モデルを異なるデバイス上で実行することもできます:お使いのコンピュータの CPU、GPU、さらにはスマートフォンでも!

訓練

モデルを訓練するために、良いモデルとは何を意味するのかを定義する必要があります。実際には、機械学習では典型的には、悪いモデルとは何を意味するのかを定義し(コストや損失と呼びます)、悪さを最小限に抑えるようにします。しかし、これらは同じ意味です。

ひとつの非常に一般的な、とても素敵なコスト関数は、「交差エントロピー」です。驚くべきことに、交差エントロピーは、情報理論における情報圧縮コードについて考えから生まれましたが、ギャンブルから機械学習まで、多くの分野で重要なアイデアになりました。定義は:

H_{y'}(y) = -\sum_i y'_i \log(y_i)

ここで、$y$ は予測された確率分布で、$y'$ は(入力される1-ホットベクトルの)真の分布です。大雑把な意味では、交差エントロピーは、予測が真実を記述するためにどのくらい非効率かを測ります。交差エントロピーに関する詳しい説明は、このチュートリアルの範囲を超えていますが、それを理解することは十分に価値があります。

交差エントロピーを実装するには、最初に正解を入力するための新しいプレースホルダを追加する必要があります:

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_sum はテンソルのすべての要素を足します。最後に、tf.reduce_mean は、バッチ内のすべてのサンプルにわたる平均を計算します。

私たちはモデルに何をさせたいかを知っているので、TensorFlow にそれを訓練させることはとても簡単です。TensorFlow は計算のグラフ全体を知っているので、自動的にバックプロパゲーション・アルゴリズムを使用して、変数が、最小化したいコストにどのように影響するかを効率的に決定することができます。そして、変数を変更してコストを減少させるための、最適化アルゴリズムの選択を適用することができます。

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

ここでは、TensorFlow に、勾配降下アルゴリズムを使用して学習率 0.5 で cross_entropy を最小化するように依頼します。勾配降下は単純な手順です、ここでは TensorFlow は単にコストを削減する方向に各変数を少しシフトします。しかし TensorFlow はまた、多くの他の最適化アルゴリズムを提供します:1つを使用するのは、1行を微調整するのと同様に簡単です。

TensorFlow が実際にここでしていることは、舞台裏では、バックプロパゲーションと勾配降下を実装する、新しい操作をグラフに追加することです。そして、実行時に、勾配降下訓練の1ステップ、コストを減少させるために、変数の微調整を行う、単一の操作を戻します。

ここで、訓練するためのモデルを設定します。起動する前に最後に一つ、作成した変数を初期化するために操作を追加する必要があります:

init = tf.initialize_all_variables()

これでセッションでモデルを起動して、変数を初期化する操作を実行することができます:

sess = tf.Session()
sess.run(init)

訓練してみましょう、訓練ステップを 1000 回実行します。

for i 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 個のランダムなデータ・ポイントの「バッチ」を取得します。プレースホルダを置き換えるバッチ・データをフィードし、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}))

これは約 91% になるはずです。

これは良いですか?それほどでもありません。実際には、かなり悪いです。それは非常に単純なモデルを使用しているためです。いくつかの小さな変更で、97% を得ることができます。最高のモデルは、99.7% 以上の精度を得ることができます! (詳細については、結果のリストを見てください。)

重要なのは、私たちがこのモデルから学んだことです。まだ、これらの結果について少し気落ちしている場合には、次のチュートリアルをチェックアウトして下さい、そこでは TensorFlow を使用して、より良い、より洗練されたモデルを構築する方法を学ぶことができます!