精度の改善を行う前に、バッチサイズの説明を行う。
コード中にmnist.train.next_batch(BATCH_SIZE)という処理がある。前後のコードも併せると、この処理ではMNISTの訓練データから複数の画像データとラベルデータを取得していることが予想できると思う。実際にその通りで、複数の画像入出力データを用いて同時に最適化を行っている。
バッチサイズとは、ミニバッチにおける同時学習データサイズのことを指す。
ディープニューラルネットワークの学習を効率化させるために、ドロップアウト(Dropout)と呼ばれるテクニックがある。
ドロップアウトとは、学習時にニューラルネットワークの一部のニューロンを、一定の確率で非活性化させるテクニックである(図3)。全結合の状態では、周囲のニューロンの影響が強く、ニューロン単体が特徴量をうまく捉えられなくなる。そこで一部のニューロンを取り除くことにより、周囲のニューロンの影響が弱まり、個々のニューロンが特徴を捉えやすくなるのである。
ここでは表1の全結合レイヤー(リスト6の(6)と(8))にドロップアウト処理を追加する。TensorFlowではtf.nn.dropoutメソッドによりドロップアウトを行うことができる。そのメソッドのkeep_prob引数にノードを残す割合を指定できる。
リスト6からの変更点(ドロップアウト処理を追加した部分のみ抜粋)は次の通り。
# ドロップアウト付きの全結合
def matmul_plus_bias_with_dropout(x, w, b, p):
return tf.matmul(tf.nn.dropout(x, keep_prob=p), w) + b
# (6) 全結合
# 重みとバイアス
w_1 = tf.Variable(tf.zeros([160, 40]))
b_1 = tf.Variable([0.1] * 40)
# ドロップアウト率
p_1 = tf.placeholder(1.0, name='p_1')
# 全結合
x_6 = matmul_plus_bias_with_dropout(x_5, w_1, b_1, p_1)
# (8) 全結合
# 重みとバイアス
w_2 = tf.Variable(tf.zeros([40, 10]))
b_2 = tf.Variable([0.1] * 10)
# ドロップアウト率
p_2 = tf.placeholder(1.0, name='p_2')
# 全結合
x_8 = matmul_plus_bias_with_dropout(x_7, w_2, b_2, p_2)
# パラメーター
# バッチサイズ
BATCH_SIZE = 32
# 学習回数
NUM_TRAIN = 10_000
# 学習中の出力頻度
OUTPUT_BY = 500
# ドロップアウト率
DROPOUT_PROB_1 = 0.2
DROPOUT_PROB_2 = 0.5
# 学習の実行
sess.run(tf.global_variables_initializer())
dropout_prob = {p_1: DROPOUT_PROB_1, p_2: DROPOUT_PROB_2}
for i in range(NUM_TRAIN):
batch = mnist.train.next_batch(BATCH_SIZE)
inout = {x: batch[0], labels: batch[1]}
if i % OUTPUT_BY == 0:
train_accuracy = accuracy.eval(feed_dict={**inout, p_1: 1.0, p_2: 1.0})
print('step {:d}, accuracy {:.2f}'.format(i, train_accuracy))
optimizer.run(feed_dict={**inout, **dropout_prob})
# テストデータによる精度検証
test_accuracy = accuracy.eval(feed_dict={x: mnist.test.images, labels: mnist.test.labels, p_1: 1.0, p_2: 1.0})
print('test accuracy {:.2f}'.format(test_accuracy))
今度は90%近くまで精度が上がったと思われる。まだまだ精度を上げられるが、本稿ではここでストップする。
本稿では割愛するが、さらに工夫を重ねることで99%以上の精度まで上げることができる。本稿までに説明した内容で行える工夫としては、以下のようなものがある。
これ以外にも、入力データにノイズやゆがみを加えることで訓練データを水増しするような方法もある。WikipediaのMNISTのページには、さまざまな手法でのMNISTの精度が示されているので、その参考文献を当たってみるのもよいだろう。
リスト6やリスト7では、学習したメモリー上のモデルをそのまま利用した。しかし実際のケースでは、モデル(すなわちデータフローグラフとパラメーター)を保存しておき、必要なタイミングで読み込める方がよい。ミニバッチ学習では段階的に学習していくため、その学習過程の歴史をスナップショットとして保存するようなこともできる。
他にも、パブリッククラウド環境であれば、学習時は高パフォーマンスのVM(仮想マシン)インスタンスを利用し、学習済みモデルを用いた予測は低パフォーマンスのVMインスタンスで提供するといったこともできる。
TensorFlowでは、モデルのファイル入出力はtf.train.Saverクラスが担う。モデルをファイルに出力する場合はsaveメソッドを、ファイルからモデルを入力する場合はrestoreメソッドを利用する。リスト7のコードを再利用して、学習のセッションと、ファイル経由の学習結果を利用する方法を次に示す。
まずは学習結果の保存だ。リスト6と7からの変更点(追加部分のみ抜粋)は次の通り。
# カレントスレッドにデフォルトのグラフが残存していることがあるので(特にJupyter Notebookを使っている場合)、リセットしておく
tf.reset_default_graph()
sess = tf.InteractiveSession()
# 学習の実行
sess.run(tf.global_variables_initializer())
dropout_prob = {p_1: DROPOUT_PROB_1, p_2: DROPOUT_PROB_2}
saver = tf.train.Saver()
for i in range(NUM_TRAIN):
batch = mnist.train.next_batch(BATCH_SIZE)
inout = {x: batch[0], labels: batch[1]}
if i % OUTPUT_BY == 0:
train_accuracy = accuracy.eval(feed_dict={**inout, p_1: 1.0, p_2: 1.0})
print('step {:d}, accuracy {:.2f}'.format(i, train_accuracy))
# 過程の保存
saver.save(sess, 'models/my-model', global_step=i)
optimizer.run(feed_dict={**inout, **dropout_prob})
# 最終結果の保存
saver.save(sess, 'models/my-model')
リスト8を実行してモデルを保存すると、指定した名前に.data-00000-of-00001/.index/.metaという3種類の拡張子が付いたファイル群とcheckpointファイルが作成される。
リスト8で「過程の保存」とコメントしてある箇所では、<モデル名>-<ステップ数>.<拡張子>という名前で保存される。
「最終結果の保存」とコメントしてある箇所では、<モデル名>.<拡張子>という名前で保存される。
次に学習結果の読み込みと利用だ。こちらは少しだけ処理が多い。なお今回は、既存のコードは再利用しないので、Jupyter Notebbokで新規ノートブックを作成して試してほしい。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# MNISTデータ
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
# モデルの読み込み
saver = tf.train.import_meta_graph('models/my-model.meta')
# データフローグラフからプレースホルダーと精度測定処理ノードを取得
graph = tf.get_default_graph()
x = graph.get_tensor_by_name('x:0')
labels = graph.get_tensor_by_name('labels:0')
p_1 = graph.get_tensor_by_name('p_1:0')
p_2 = graph.get_tensor_by_name('p_2:0')
accuracy = graph.get_tensor_by_name('accuracy:0')
with tf.Session() as sess:
# 変数初期化
sess.run(tf.global_variables_initializer())
# モデル読み込み
saver.restore(sess, 'models/my-model')
# テストデータによる精度検証
test_accuracy = accuracy.eval(feed_dict={x: mnist.test.images, labels: mnist.test.labels, p_1: 1.0, p_2: 1.0})
print('test accuracy {:.2f}'.format(test_accuracy))
.metaファイルは、メタグラフというデータフローグラフ構造などの情報を保存したものである。tf.train.import_meta_graphメソッドを用いることで読み込むことができる*1。
*1 メタグラフをファイルに出力するtf.train.export_meta_graphメソッドも存在する。
tf.train.import_metagraphメソッドによりグラフ構造を読み込んだだけでは、プレースホルダーなどのテンソルをPythonの変数として利用できない。データフローグラフから名前付きテンソルを取得しているのが3〜9行目の一連の処理である。リスト6と7で(name='<変数名>'という引数指定で)一部のテンソルに名前を付けていたのはこのためである。名前の後ろに':0'という文字列が付いているが、これはTensorFlowによって自動で付加されるoutput_indexというもので、決まり文句のようなものだと思っておいて特に問題ないだろう。
メタグラフにはテンソル要素の具体的な値は含まれていない。変数を初期化し、restoreメソッドを用いることで、学習したパラメーターを読み込むことができる。これでデータフローグラフ構造とテンソルの値が読み込まれたことになるので、テンソルの評価が行えるようになる。
本稿では単純なCNNの実行と学習の方法について示した。なぜ畳み込み、プーリング、ドロップアウトといった処理がニューラルネットワークの枠組みで学習できるのか、オプティマイザーとはどのような処理を行っているのか、といったことは示していない。CNNについて深く学習したい場合は、書籍やWeb記事を参考にするとよいだろう。
また、TensorFlowのtf.kerasモジュールには、高レベルAPIと呼ばれる、学習やモデルの定義を容易にするAPI群が定義されている。もともとKerasはTensorFlowやそれ以外の計算ライブラリをバックエンドとした独立した深層学習ライブラリであったが、APIはTensorFlowに統合されている。よほど複雑なことを行うのでなければ、tf.kerasモジュールの提供するAPIを利用した方が、簡潔かつ直感的に深層学習のモデルコードを書くことができるだろう。
本稿ではTensorFlowの計算骨格を説明するためにKerasについては取り扱わなかったが、深層学習を一般的な機械学習業務で利用するに当たっては、Kerasのような高レベルAPIを用いた方が開発スピードや保守の観点で推奨される。
さて次回は、時系列データでよく使われる代表的な手法「RNN」について概観する。さらに次々回では、今回と同じにようにRNNを試しに使ってみることにしよう。
Copyright© Digital Advantage Corp. All Rights Reserved.