ORNEW

TensorFlowによる機械学習の初心者のためのMNIST

Facebook にシェア
Pocket

この記事はTensorFlow公式チュートリアル、「MNIST For ML Beginners」を初心者のために翻訳したものである。純粋な翻訳ではなく、初心者のためのチュートリアルの解説を目的としている。故に、基本的に意訳を用いている点と、原文にはない部分や噛み砕いている部分があることをご了承いただきたい。

もし優秀な翻訳があればこの記事を書く必要はなかったが、事前に他の翻訳に目を通したところ、機械翻訳を整形した程度の誤った翻訳ばかりが存在していたため、改めて翻訳を行うこととした。

2016年末から2017年始にかけて企画しているクラウドMLに関するハンズオンの資料として利用する予定である。この企画については詳細が決まり次第追記する。


TensorFlowによる機械学習の初心者のためのMNIST

このチュートリアルは、機械学習とTensorFlowの初心者の読者を対象としています。もしあなたが既にMNISTやソフトマックス(マルチロジスティック)回帰について知っているのであれば、こちらのペースの速いチュートリアルのほうが良いかもしれません。いずれのチュートリアルを開始する場合も、事前にTensorFlowをインストールしておいてください。

プログラムを学ぶとき最初に行う伝統として、「Hello World」の出力があります。プログラミングがHello Worldを持っているように、機械学習にはMNISTが用いられます。

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

5041

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

このチュートリアルでは、画像を見て、示されている数字が何であるかを予測するモデルを訓練するつもりです。私達の目的は最先端の性能を実現し、本当に精巧なモデルを訓練することではありません。むしろTensorFlowを試しに始める程度です。よって、私たちはソフトマックス回帰と呼ばれる非常に単純なモデルから始めるつもりです。

このチュートリアルのための実際のコードは非常に短く、すべての興味深い部分はたった3行です。しかし、その背景にある「TensorFlowはどのように動作するのか、機械学習のコンセプトは何なのか」という点を理解することが非常に重要となります。よって、私たちはとても慎重にコード見ていきます。

このチュートリアルの概要

このチュートリアルでは、mnist_softmax.pyのコードについて行単位で何が起こっているか説明していきます。

あなたはこのチュートリアルを幾つかの方法で使うことができます。

このチュートリアルで達成するのことは以下の通りです。

MNISTデータ

MNISTデータはYann LeCun’sのWebサイトにホストされています。もしあなたがこのチュートリアルのコードをコピー&ペーストしている場合は、以下の2行のコードで自動的にダウンロードと読み込みを行います。

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

MNISTデータは3つのパートに分割されます。

  1. 訓練用データ 55,000個(mnist.train
  2. テスト用データ 10,000個(mnist.test
  3. 検証用データ 5,000個(mnist.validation

これらの分割は非常に重要です。機械学習において、私達が学ばせたことが実際に一般化されていることを確認するためにはデータが別々であることが不可欠なのです。訓練に使った画像をテストデータに使ってしまっては、そのモデルが汎用的であるかは確認できません。

前述の通り、すべてのMNISTデータは2つのパーツから成り立っています。手書き数字の画像と、対応するラベルです。私たちはここで画像をx、ラベルをyと呼ぶことにします。訓練データセットとテストデータセットは、画像及び対応するラベルを含んでいます。例えば、訓練画像はmnist.train.imagesであり、訓練ラベルはmnist.train.labelsです。

各画像は28ピクセル平方です。私たちは、数値の配列としてこれを解釈します。

この28×28の2次元配列は、28×28=784個の数字からなるフラットな(1次元)配列としても捉えられます。全ての画像で変換方法が一致している限りは、配列をフラットにする方法は重要ではありません。捉え方として、MNIST画像は(計算集約的な視覚化において)非常にリッチな構造を持った、784次元のベクトル空間の一連の点として考えられるということです。これは、画像がTensorで表現できるということを暗に示しています。

データをフラットにするということは、画像の2次元構造に関する情報(空間的相関など)を捨てるということです。これは悪いことではないでしょうか?はい、そのとおりです。最高のコンピュータビジョンの方法では、この構造を活用します。それは後々玄人のためのチュートリアルで行います。ここで使うソフトマックス回帰(後ほど定義します)という簡単な方法ではそんなことはしません。

何が言いたいのかというと、mnist.train.images[55000, 784]の形をしたTensor($n$次元配列)であるということです。最初の次元は画像のリストへのインデクスであり、第2の次元は各画像の各ピクセルへのインデクスに対応しています。Tensorの各エントリは特定の画像内の特定のピクセルの強度(0-1の間)です。数字が描かれている部分のピクセルはピクセル強度が強いということであり、何も描かれていないところはピクセル強度が弱いということを、0-1の間の数値でピクセルごとに表現しているということです。

MNISTの各画像は、対応するラベルとして画像に描かれた数字を表す0から9までの番号を持っています。

このチュートリアルの目的のために、OHV(one-hot vectors)で表されるラベルが必要になります。(厳密には、ソフトマックス関数のためにOHV形式が必要となります。)

OHVとは、全ての要素の内、ほとんどの次元の値が0であり、唯一1つの次元だけが値として1を持つようなベクトルのことです。今回の場合、$n$番目の数字は$n$次元目が1であるベクトルとして表現されます。例えば、3というラベルは、OHVだと$[0,0,0,1,0,0,0,0,0,0]$と表現されます。結果的に、mnist.train.labelsは、float型の[55000, 10]配列です。

これで、モデルを作る準備が整いました!

ソフトマックス回帰(Softmax Regressions)

私たちはMNIST内の全ての画像が0から9の間の手書き数字であることを知っています。ですから、与えられた画像には10種類の可能性があります。私たちは、画像を見て、それぞれの数字ごとに、その数字である確率を出力できるようにしたいです(非決定的であるということです)。例えば、私たちは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}) $$

ソフトマックスは私達が望む形に線形関数(ここでは$\text{evidence}$のこと)の出力を整形する「活性化」または「リンク」として機能しています。望む形とはこの場合、10種類のクラスの確率分布です。あなたは各クラスの証拠の集計を変換していると考えることができます。

$$ \text{softmax}(x) = \text{normalize}(\exp(x)) \tag{1} \label{softmax1} $$

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

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

しかし、ソフトマックスは式$\eqref{softmax1}$の形で考える方が多くの場合でより便利でしょう。入力を冪乗し、正規化するということです。冪乗は、1以上の証拠が仮説に与えられる重みを乗法的に増加させることを意味します。逆に、証拠が1未満であれば、仮説の重みを奪うことになります。どの仮説もゼロまたは負の重みにはなりません。ソフトマックスはこれらの重みを正規化し、それらの合計は1になり、有効な確率分布を形成します。(ソフトマックス関数についてより詳しい感覚を得るためには、インタラクティブ視覚化を完備した、Michael Nieslenの本の関連するセクションを確認してください。)

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

方程式のようにこのグラフを表現すると以下のようになります。

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

よりコンパクトに書くなら、こうなります。

$$ y = \text{softmax}(Wx + b) \tag{2} \label{y} $$

回帰の実装

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

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

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

import tensorflow as tf

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

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

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

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

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

変数の初期値をtf.Variableに与えることにより、変数を作成できます。このケースでは、Wbをどちらも全ての要素を0で初期化した変数を作成します。私たちはこのWbを学習しようとしているので、それらが最初何であるかはあまり重要ではありません。

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

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

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

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

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

訓練

モデルを訓練するために、良いモデルとは何かを定義する必要があります。まあ、実際のところは、悪いモデルとは何かを定義するのが一般的です。私たちはこれをコストや損失と呼び、望ましい結果からどれだけかけ離れているかを表します。間違いを最小にするために、そのかけ離れ具合を最小にすることが、モデルの訓練のベターな方法です。

モデルの損失を決定するための非常に一般的で優秀な関数に「交差エントロピー(cross-entropy)」と呼ばれるものがあります。交差エントロピーは情報理論における圧縮符号から生まれた考えですが、ギャンブルから機械学習まで多くの分野で重要となるアイデアとなりました。交差エントロピーは以下のように定義されます。

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

ここで、$y$は予測確率分布で、$y’$はOHVで示されるラベルに相当する正答を示す分布(true distribution)です。大雑把に言うと、交差エントロピーは、予測が真実に対してどのくらい非効率か(かけ離れているか)を測ります。交差エントロピーに関する詳しい説明は、このチュートリアルの範囲を超えていますが、それを理解することは十分に価値があります。

正答を入力するために、まず新しいプレースホルダを追加する必要があります。

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は、バッチ内のすべてのサンプルにわたる平均を計算します。

(ソースコードで、この式は数値的に不安定であるため使用されないことに注意してください。代わりに非正規化logitsにtf.nn.softmax_cross_entropy_with_logitsを使います。このより数値的に安定した関数は、内部でソフトマックス活性化を計算するためです。例えば、tf.matmul(x, W) + bsoftmax_cross_entropy_with_logitsを適用します。あなたのコードでは、tf.nn.(sparse_)softmax_cross_entropy_with_logitsを代わりに使用することを検討してください。詳しくは、APIリファレンスを読んで下さい。)

今、私達がモデルに何をさせたいのかが自明なので、TensorFlowにそれを訓練させるのはとても簡単です。TensorFlowは計算グラフの全体を知っているので、自動的かつ効率的に損失を最小にするために変数がどう影響するかを誤差逆伝播法(backpropagation algorithm)を用いて決定することができます。そして、変数を変更して損失を低減させるための、最適化アルゴリズムの選択を適用することができます。

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

ここでは、勾配降下法(gradient descent algorithm)を使用して学習率0.5でcross_entropyを最小化するようにTensorFlowに依頼します。勾配降下は単純な手順です。単に損失を削減する方向に各変数を少しシフトします。しかしTensorFlowは、他にも多くの最適化アルゴリズムを提供します。どれを使用するにしても、1行修正する程度で簡単です。

TensorFlowは実際にはこの舞台裏で、勾配降下法による誤差逆伝播法を、あなたのグラフに新しいオペレーションとして追加しています。そして「勾配降下訓練のステップとして損失を減らすために変数を僅かに修正する」という単一のオペレーションを返します。

ここで、訓練のためにモデルをセットアップします。それを起動する前に最後に1つ、今まで作成してきた変数を初期化するオペレーションを作成する必要があります。ここで、オペレーションは作成されただけであり、実行はされていないことに注意してください。

オペレーションとは何かについては、補足をここに追記するとともに詳細を別記事として書き起こします。

init = tf.initialize_all_variables()

モデルはSessionの中で起動することができ、オペレーションもセッションを通して実行できます。セッションの作成と変数を初期化するinitオペレーションの実行は以下のようになります。

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はいくつかの軸に沿ったTensorで最も高いエントリのインデクスを取得する非常に便利な関数です。例えば、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によってより洗練されたモデルを構築する方法を学ぶことができます!