2015年8月22日土曜日

Deep Learning のフレームワーク Chainer を使った画像分類 その4

前回記事: 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)『深層学習 』(機械学習プロフェッショナルシリーズ)講談社

2015年8月9日日曜日

Deep Learning のフレームワーク Chainer を使った画像分類 その3

前回記事: Deep Learning のフレームワーク Chainer を使った画像分類 その2

最初から: Deep Learning のフレームワーク Chainer を使った画像分類 その1

ネットワーク内伝搬手順の定義

train_mnist.py 51-56行目

# Neural net architecture
def forward(x_data, y_data, train=True):
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h1 = F.dropout(F.relu(model.l1(x)),  train=train)
    h2 = F.dropout(F.relu(model.l2(h1)), train=train)
    y = model.l3(h2)

    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

このforward関数はこの後、train_mnist.py内82, 102行目で直接参照し実行される。実行時、第一引数 x_data には画像データが指定され、第二引数 y_data には分類の答えのデータが指定される。

forward関数内ではまず、引数 x_data と y_data 両方をchainer.Variable()で包み込み、追跡可能としている(→chainer.Variable)。x_data はl1〜l3層を通り、最終的に変数 y となる。l1〜l2を通る際、relu関数を使った活性化とドロップアウトがなされる。

活性化関数とは、ユニットが受け取る入力u=w1x1+w2x2+w3x3+w4x4+bより出力zを生成する関数f。z=f(u)。活性化関数は古くは出力が0~1に収まる、ロジスティックシグモイド関数あるいはロジスティック関数と呼ばれるが使われてきたが、近年では入力が0以上であれば出力もそれに従って増加する(0以下の場合は0となる)正規化線形関数(Rectified linear function)がより良い結果が得られ、また計算量が小さいためよく用いられている1。この関数をもつユニットはReLU(Rectified Linear Unit)と呼ばれる1

ドロップアウトとは学習時にネットワークの自由度を強制的に小さくし、過適合を避ける方法の1つ。具体的には学習時に出力層以外の層のユニットについて決まった割合pで選出しその他を無効化する。pは層ごとに異なる値を指定しても構わない2。過適合(=過剰適合, 過学習)とは訓練後にネットワークの性能をテストした際に、パラメータ更新回数を増やすにつれ、テストの誤差が大きくなってしまう状態3

forward関数は最終的に、多クラス分類の誤差関数(損失関数, loss function, とも呼ばれる4)である交差エントロピー式の算出結果と、このミニバッチの分類の正確さを返却(return)する。

1参考: 岡谷2015 p.10-11, 2岡谷2015 p.31, 3岡谷2015 p.28, 4岡谷2015 p.15

次回、Optimizerとは何か、学習と学習結果の評価手順を紐解く。→Deep Learning のフレームワーク Chainer を使った画像分類 その3

参考書籍

  • 岡谷貴之(2015)『深層学習』(機械学習プロフェッショナルシリーズ)講談社