第5回 画像認識を行う深層学習(CNN)を作成してみよう(TensorFlow編):TensorFlow入門(2/2 ページ)
ディープラーニングの代表的手法「CNN」により画像認識を行う機械学習モデルを構築してみる。CNNによる深層学習がどのようなものか体験しよう。
バッチサイズ
精度の改善を行う前に、バッチサイズの説明を行う。
コード中にmnist.train.next_batch(BATCH_SIZE)という処理がある。前後のコードも併せると、この処理ではMNISTの訓練データから複数の画像データとラベルデータを取得していることが予想できると思う。実際にその通りで、複数の画像入出力データを用いて同時に最適化を行っている。
- このようにデータを少しずつ学習させる方法をミニバッチ学習(Mini-batch learning)という
- これに対し、すべてのデータを同時に学習させる方法がバッチ学習(Batch learning)
- 1つずつ学習させる方法はオンライン学習(Online learning)という
バッチサイズとは、ミニバッチにおける同時学習データサイズのことを指す。
ドロップアウト層の追加
ディープニューラルネットワークの学習を効率化させるために、ドロップアウト(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を試しに使ってみることにしよう。
【TL;DR】CNNで行う画像認識
- MNIST: 手書き数字画像のデータセット。28×28ピクセルのグレースケール画像で各ピクセルは8 bits値。TensorFlowには読み込み関数が用意されている
- pillow: 画像データを扱うためのPythonパッケージ
- CNNのネットワーク定義: 画像サイズ変更、畳み込み、活性化関数、プーリング、全結合などの層を重ねていき、最後にソフトマック関数で確率にする
- ReLU関数: 代表的な活性化関数の一つ。0以下であれば「0」とし、入力が0より大きければ「入力通りの値」にする。ニューラルネットワークを収束させるのに有効
- 損失関数: 理想的な出力との距離を定義する関数。この関数の値が小さいほど、理想的な出力に近い。代表的な損失関数の一つに「交差エントロピー」がある
- ワンホット表現: 正解の要素のみ「1」で、他の要素は「0」となるテンソル表現
- オプティマイザー: 最適化手法を実装したクラス群で、これを使って損失関数を最小化する。最適化手法の一つに「Adam」がある。
- ミニバッチ学習: データを少しずつ学習させる方法。他には、すべて同時に学習させる「バッチ学習」や、1つずつ学習させる「オンライン学習」という方法がある
- ドロップアウト: 学習時にニューラルネットワークの一部のニューロンを、一定の確率で非活性化させるテクニック。個々のニューロンが特徴を捉えやすくなるため、精度が向上する
- モデルの保存と利用: tf.train.Saverクラスのsaveメソッドでモデルをファイルに出力して保存し、restoreメソッドでファイルからモデルを入力して利用できる
- Keras: TensorFlowなどをバックエンドとして使用できる深層学習ライブラリ。簡潔かつ直感的に深層学習をコーディングできる
Copyright© Digital Advantage Corp. All Rights Reserved.