ORNEW

TensorFlowによる熟練者のための Deep MNIST

Share on Facebook
Pocket

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

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

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


熟練者のための Deep MNIST

TensorFlowは大規模な数値演算を行うための強力なライブラリです。優れていることの1つとして、深層ニューラルネットワーク(DNN: Deep Neural Networks)の実装と訓練があります。このチュートリアルでは深層畳み込み(Deep Convolutional) MNIST分類器を構築しながら、TensorFlowモデルの基本的な構成要素を学んでいきます。

このチュートリアルでは、ニューラルネットワークとMNISTデータセットに精通している方を前提としています。もしあなたがそれらについてよくわかっていない場合は、初心者のためのチュートリアルをチェックアウトしてください。また、事前にTensorFlowのインストールを済ませておいてください。

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

前半は、TensorFlowモデルの基本的な実装であるmnist_softmax.pyのコードについて、何が起きているのか説明していきます。後半では、精度を改善するための幾つかの方法を示します。

あなたはこのチュートリアルのコードスニペットをPython環境にコピー&ペーストすることもできますし、コードを読むだけでも構いません。

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

セットアップ

モデルを作る前に、最初に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プログラムの一般的な使い方として、まずグラフを作成してからセッションを起動します。

ここでは代わりに便利なInteractiveSessionクラスを使用します。これにより、TensorFlowはコードの構造をより柔軟にします。これは、グラフを実行する演算グラフを作成する動作をインターリーブすることができます。これはIPythonのような対話的コンテキストの中で作業する時に特に便利です。InteractiveSessionを使用していない場合は、セッションを開始してグラフを起動する前に、計算グラフ全体を構築する必要があります。

import tensorflow as tf
sess = tf.InteractiveSession()

演算グラフ

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

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

Pythonコードの役割は、この外部演算グラフを作成し、計算グラフのどの部分を実行するか指示することとなります。詳細については「基本的な使用法」の「計算グラフ」セクションを参照してください。

ソフトマックス回帰モデルを構築する

このセクションでは、単一の線形層を持つソフトマックス回帰モデルを構築します。次のセクションでは、これを多層畳み込みニューラルネットワークを使用するソフトマックス回帰に拡張します。

プレースホルダ

入力画像と目標となる出力のためのノードを作成するところから計算グラフを構築していきましょう。

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

xy_は特定の値ではありません。むしろ、それらはそれぞれただのプレースホルダ(場所を保持するもの=値そのものではない)です。これは、TensorFlowに計算を実行をさせようとする時に入力する値を保持する場所です。

入力画像xは浮動小数点数の2次元Tensorで構成されます。 ここでは、[None、784]の形状を割り当てます。ここで、784は単一の平坦化された28×28ピクセルのMNIST画像の次元数であり、Noneはバッチサイズに対応する最初の次元が任意のサイズであることを示します。 ターゲット出力クラスy_は、2次元Tensorから構成され、各行は、対応するMNIST画像がどの数字クラス(0-9)に属するかを示す10次元のOHV(One-Hot Vector)です。

プレースホルダのshape引数の指定は任意ですが、TensorFlowはTensorの形状の不一致から生じるバグを自動的に補足することができるようになります。

変数

モデルの重みWとバイアスbを定義します。これらを追加の入力のように扱うと想像されるかもしれませんが、TensorFlowではこれを処理するためのより良い方法として、Variable(変数)を使います。変数は、TensorFlowの計算グラフ上に存在する値です。それは計算に使用したり、修正したりすることができます。機械学習アプリケーションでは、一般的に、モデルパラメータを変数で保持します。

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

tf.Variableの呼び出しで各パラメータの初期値を渡します。この場合、Wbをどちらも全てのパラメータを0で初期化します。Wは784×10の行列です(784個の入力と、10個の出力を想定しているため)。bは10次元のベクトルです(クラスが10個であるため)。

セッション内で変数を使用するには、そのセッションを使用して変数を初期化する必要があります。このステップでは、既に指定されている初期値(この場合はzero-fill)を割り当てます。これは全ての変数に同時に実行できます。

sess.run(tf.initialize_all_variables())

クラスの予測と損失関数

回帰モデルを実装します。それはたったの一行です。ベクトル化された入力画像xに重み行列Wを乗算し、バイアスbを加算します。

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

損失関数は簡単に指定できます。損失は、モデルの予測が1つのサンプルについてどれほど悪いかを示します。この損失が最小になるように、すべてのサンプルを訓練していきます。損失関数は、目標と、ソフトマックス活性化関数が適用されたモデルの予測との間の交差エントロピーを使います。初心者向けのチュートリアルと同様に、安定した演算を使用します。

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y, y_))

tf.nn.softmax_cross_entropy_with_logitsは内部的にモデルの非正規化モデル予測としてsoftmaxを適用し、それをすべてのクラスにわたって合計し、更にtf.reduce_meanがそれらの合計に対して平均をとることに注意してください。

モデルの訓練

モデルと訓練のための損失関数を定義したので、TenosrFlowを使って訓練を行うのは簡単です。TensorFlowは計算グラフ全体を把握しているため、自動微分を使用して各変数に対する損失の勾配を見つけることができます。TensorFlowにはさまざまな組み込みの最適化アルゴリズムがあります。 この例では、交差エントロピーを降下させるために、最急降下勾配(Steepest Gradient Descent)をステップ長0.5で使用します。

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

この一行でTensorFlowが実際に行うのは、新たな演算を演算グラフに追加することです。これらの操作には、勾配を計算し、パラメータ更新ステップを計算し、パラメータに更新ステップを適用する操作が含まれています。

返されたtrain_stepが実行されると、勾配降下の更新がパラメータに適用されます。したがって、train_stepを反復実行することで、モデルの訓練を行うことができます。

for i in range(1000):
  batch = mnist.train.next_batch(100)
  train_step.run(feed_dict={x: batch[0], y_: batch[1]})

各反復において、100個の訓練サンプルを読み込みます。次にfeed_dictを使用してプレースホルダxy_を訓練用に置き換えるtrain_stepオペレーションを実行します。このようにfeed_dictを使用して計算グラフのテンソルを置き換えることができます。これはプレースホルダだけに限定されません。

モデルの評価

モデルの出来はどうでしょう?

最初に、予測した正しいラベルはどれなのかを特定しましょう。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))

最後に、テストデータの精度を評価することができます。これは約92%ほどの正答率になるはずです。

多層畳み込みネットワークの構築

MNISTでの92%の精度は悪い結果です。それはもう恥ずかしいくらい悪いです。このセクションでは、非常に単純なモデルから、やや洗練されたもの、つまりは小さな畳み込みニューラルネットワークに修正します。これにより、約99.2%の精度を得ることができます。これは最先端の結果ではありませんが、立派といえるレベルでしょう。

重みの初期化

このモデルを作成するには、たくさんの重みとバイアスを作る必要があります。一般に、対象性を壊すために僅かなノイズで重みを初期化し、勾配が0になるのを防止する必要があります。私たちはReLU(Rectified Linear Unit)ニューロンを使用しているため、「死んだニューロン」を避けるために、僅かな正のバイアスで初期化するのも良いことでしょう。モデルを構築する時にこれを繰り返し行うのではなく、自分たちのために便利な関数を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)

畳み込みと集約(Convolution and Pooling)

TensorFlowは、畳み込みと集約の操作に多くの柔軟性をもたらします。境界をどう扱うか?ストライド長はどうするか?この例では、常にバニラを使用します。畳み込みは1のストライドを使用し、出力と入力が同じサイズになるようにゼロパディングされます。集約では、2×2ブロック以上の普通の最大値集約(Max Pooling)を用います。コードを見通しよく保つために、これらの操作を関数として抽象化しておきましょう。

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')

第一畳み込み層

これで、最初の層を実装できます。畳み込みとそれに続く最大値集約から構成されます。畳み込みは5×5パッチごとに32個の特徴を計算します。その重みのTensorの形状は[5, 5, 1, 32]です。最初の2つの次元はパッチサイズ、次が入力チャネル数、最後が出力チャネル数を示しています。また、出力チャネルごとにコンポーネントを持つバイアスのベクトルも用意します。

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

層を適用するには、最初にxを、第2次元と第3次元が画像の幅と高さ、最後の次元がカラーチャネルに対応した4次元Tensorに再構築します。

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)

第二畳み込み層

深層ニューラルネットワークを構築するために、同型の複数の層をスタックします。第二層では、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)

密結合層

画像サイズが7×7に縮小されたため、画像全体を処理できるように1024個のニューロンを備えた総結合層(fully-connected layer)を追加します。Tensorを集約層からベクトルのバッチに再構築し、重みを乗算し、バイアスを加算し、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)

ドロップアウト

過学習(Overfitting)を減らすために、読み出し層の前にドロップアウトを適用します。ドロップアウト中にニューロンの出力が保持される確率を示すプレースホルダを作成します。これにより、訓練中はドロップアウトをオンに、テスト中にはオフにすることができるようになります。TensorFlowのtf.nn.dropoutオペレーションは、マスキングに加えてスケジューリングニューロンの出力を自動的に処理するので、ドロップアウトは追加のスケーリングなしに動作します1

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

読み出し層

最後に、前半のソフトマックス回帰と同様に、層を追加します。

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

モデルの訓練と評価

このモデルの精度はどうなったでしょうか?それを訓練して評価するために、前半の一層型ソフトマックスネットワークとほぼ同じコードを使用します。

違いは次のとおりです。

このコードは自由に実行してもらって構いませんが、2万回の訓練の反復に結構時間がかかると思います(おそらく最大30分)。プロセッサに依ります。

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv, y_))
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.initialize_all_variables())
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%になるはずです。

私たちは、TensorFlowを使ってかなり洗練された深層学習モデルを素早く簡単に構築、訓練、評価する方法を学びました。


  1. この小さな畳み込みネットワークでは、パフォーマンスはドロップアウトの有無にかかわらず事実上ほぼ同じです。ドロップアウトは多くの場合で過学習を減らすのに非常に効果的ですが、特に非常に大きなニューラルネットワークを訓練する時に最も便利です。