前回記事: Deep Learning のフレームワーク Chainer を使った画像分類 その3
最初から: Deep Learning のフレームワーク Chainer を使った画像分類 その1
Optimizer
train_mnist.py 58-60行目
# Setup optimizer
optimizer = optimizers.Adam()
optimizer.setup(model.collect_parameters())
optimizerはこの後、訓練中に前述の関数 forward より返却された F.softmax_cross_entropy() の戻り値(オブジェクト)に対し backward() された(83行目)後に optimizer.update() される(関数名末尾の括弧は関数の実行を示す)。Optimizerはパラメータと勾配からなりたち、update()を実行するたびに、対応する勾配にもとづき、パラメータを更新する。
勾配について、学習のゴールは、誤差関数 E(w) に対し最小値を与える重み w を求める事だが、E(w) は一般に凸関数ではなく、大域的な最小解を直接得るのは通常不可能1。
代わりに E(w) の局所的な極小点 w を考えるが、E(w) の極小点は一般に多数存在するので、たまたまみつけた局所的な極小点が E(w) の大域的な極小解とは限らない1。それでも E(w) の値がある程度小さいなら、クラス分類の問題をそれなりにうまく解決できるという様に考える1。このとき、勾配とは
というベクトル(Mはwの成分数)1。
今回、train_mnist.py は Optimizer として Adam法を使用する。理由、その他理論については不明。なお、Chainer のチュートリアルにおいてはSGD(確率的勾配降下法)が利用されている。
setup 関数は与えられたパラメータ/勾配に対し状態(?)を準備する。Optimizerはパラメータと勾配を実際に管理している関数を知らないので、一度パラメータと勾配を指定したら、forword と backward を通して同じパラメータと勾配を使用する必要がある。
1岡谷 2015 p.24
エポックの走査(パラメータの更新)・訓練・訓練結果のテスト
訓練
train_mnist.py 62-90行目
# Learning loop
for epoch in six.moves.range(1, n_epoch + 1):
print('epoch', epoch)
# training
perm = np.random.permutation(N)
sum_accuracy = 0
sum_loss = 0
for i in six.moves.range(0, N, batchsize):
x_batch = x_train[perm[i:i + batchsize]]
y_batch = y_train[perm[i:i + batchsize]]
if args.gpu >= 0:
x_batch = cuda.to_gpu(x_batch)
y_batch = cuda.to_gpu(y_batch)
optimizer.zero_grads()
loss, acc = forward(x_batch, y_batch)
loss.backward()
optimizer.update()
sum_loss += float(cuda.to_cpu(loss.data)) * batchsize
sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize
print('train mean loss={}, accuracy={}'.format(
sum_loss / N, sum_accuracy / N))
train_mnist.py 24行目で指定されたエポック回数(20回)分、訓練と訓練の評価を繰り返す(forループ)。np.random.permutation 関数を使い毎回N(=60000← train_mnist.py 35行目より, =訓練用データの量)までの数値をランダムに並び替えた順列を生成し、その順列 perm を使い x_train および y_train よりデータセットを選択しているため、毎回対象となるデータが異なる。エポックとは岡谷 2015 p.28 によると「パラメータ更新回数」と同義の様に読み取れる。
np.random.permutation(N) より訓練が開始される。
訓練の結果のネットワークの性能を代入する sum_accuracy および sum_loss 変数の0による初期化に続き、さらに内側の forループが開始される。このループは0〜N(=60000)までを batchsize (=100) で刻んだ分だけ繰り返し、i には繰り返すごとに、0, 100, 200, .. , 59000 までの値が代入される。
訓練用データ x_train、y_train から x_batch および y_batch にランダムに batchsize (=100) 分のデータが代入される。GPU(一部のPCに入っている並列計算が得意なCPU)を使う場合は、データをGPUに転送する。
optimizer.zero_grad 関数で optimizer が管理する勾配のベクトルをゼロで初期化し、あらかじめ train_mnist.py の中で定義した forward 関数を実行する。
forward 関数では、その第一戻り値として F.softmax_cross_entropy の実行時の戻り値を指定した。そのため、変数 loss は関数 F.softmax_cross_entropy が返却する chainer.Variable型となる。loss.backward()では chainer.Variable が提供する関数 chainer.Variable.backward が実行される。この backward 関数は誤差逆伝搬を行う。「誤差逆伝搬」とは「順伝播」型ネットワークよりも効率的にネットワークの重みとバイアスに関する誤差関数の微分を計算する方法の1つ1。この前に forward 関数を使い順伝播を行っているにも関わらず、あらためて「逆」伝搬を行う事に違和感も感じるが、「順伝播(各層における入力 u = Wx + B、出力 z = f(u) の計算)を行った後に最終層の出力を使い、逆方向に(デルタを使い2)各層のパラメータに関する微分の式を求める」のが、誤差逆伝搬による誤差勾配の計算手順3。
その後 optimizer に対し、関数 update を実行し、対応する勾配を使いすべてのパラメータと状態(?)を更新する。
内包されたforループの最後の処理として、今回の損失と正確さを変数、sum_loss と sum_accuray にあらかじめ代入されていた値に加算する形で記録する。
1参考: 岡谷 2015 p.41, 2岡谷 2015 p.46, 3岡谷 2015 p.48
訓練結果のテスト
train_mnist.py 92-100行目
# forループ内手続き宣言の続き
# evaluation
sum_accuracy = 0
sum_loss = 0
for i in six.moves.range(0, N_test, batchsize):
x_batch = x_test[i:i + batchsize]
y_batch = y_test[i:i + batchsize]
if args.gpu >= 0:
x_batch = cuda.to_gpu(x_batch)
y_batch = cuda.to_gpu(y_batch)
loss, acc = forward(x_batch, y_batch, train=False)
sum_loss += float(cuda.to_cpu(loss.data)) * batchsize
sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize
print('test mean loss={}, accuracy={}'.format(
sum_loss / N_test, sum_accuracy / N_test))
訓練で調整されたパラメータを持つネットワークを使い、テスト用データでネットワークの性能のチェックを行う。forward関数を実行する際に、訓練時とは異なり train 引数の値として False を指定する。Falseを指定した場合、関数 forward の内部にて関数 F.dropout の引数 train の値がFalseとなり、ドロップアウトが無効化される。
参考書籍
- 岡谷貴之(2015)『深層学習
』(機械学習プロフェッショナルシリーズ)講談社