FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

フルスクラッチからC言語で作るニューラルネットワーク (6. MNIST 誤差逆伝搬法の実装)

前回まででデータ処理部のバッチ処理化まで完了したので、学習のための誤差逆伝搬法をC言語で実装する。 今回も、Pythonでの実装を参考にしながら、逐次データを抽出して、計算結果を合わせ込んで実装していく。

まずは、各処理についてどのような行列演算が行われているのかまとめておこう。

MNISTデータパス

今回はバッチサイズ100のニューラルネットワークを構築し、以下の4つの処理を逐次配置して実装した。

  1. Affine1処理
  2. ReLU活性化
  3. Affine2処理
  4. Softmax処理

それぞれ、行列演算の形を見ながらどのような計算をしているのかまとめておく。

f:id:msyksphinz:20170630020540p:plain

  1. Affine1は、28x28の画像をバッチサイズ分だけ並べて、 (28\times 28) のサイズの重み行列  W0 と掛け合わせた上で、重みベクトル  b0 のバイアスを加算する。
  2. ReLUはデータの形は変えずに、全ての要素に対してReLU関数を適用する。
  3. Affine2も同様。ReLUの出力行列に対して重み行列  W1 の行列積を求めたうえで、重みベクトル  b1 のバイアスを加算する。
  4. Softmaxもデータの形を変えずに、すべての要素に対してSoftMax関数を適用する。

ここまでで、バッチサイズ分のMNISTの処理が完了する。この結果に対して、最終的な教師データとの差分を計算し、誤差を逆方向に伝搬させていく。 具体的には、上記の式で出てきた  W0, b0, W1, b1 の勾配を計算して、元の重み行列を修正していくことになる。

MNIST誤差逆伝搬のデータパス

MNISTの誤差逆伝搬のためには、以下の4つの処理を実行する。上記のMNISTデータパスを逆方向にたどっていく。

  1. Softmaxの逆伝搬
  2. Affine2の逆伝搬
  3. ReLUの逆伝搬
  4. Affine1の逆伝搬

とする。例としてAffine1の誤差逆伝搬についてここでは取り上げる。

f:id:msyksphinz:20170630022526p:plain

基本的な考え方としては、前のニューロンから生成された入力値の差分、前の順方向ネットワークの計算結果を利用して、現在のニューロンに設定されている重みを更新し、ひとつ前のニューロンへ伝える誤差値を計算する。それぞれ、

として、Pythonのコードを参考にしながらC言語で実装すると以下のようになる。

github.com

double affine_backward (const int output_size,
                        const int hidden_size,
                        const int batch_size,
                        double dx[batch_size][hidden_size],
                        double db[output_size],
                        double dw[hidden_size][output_size],
                        const double dout[batch_size][output_size],
                        const double w[hidden_size][output_size],
                        const double x[batch_size][hidden_size])
{
  for (int b = 0; b < batch_size; b++) {
    for (int y = 0; y < hidden_size; y++) {
      dx[b][y] = 0.0;
      for (int x = 0;x < output_size; x++) {
        dx[b][y] += (dout[b][x] * w[y][x]);  // w is Transpose
      }
    }
  }
  for (int h = 0; h < hidden_size; h++) {
    for (int o = 0; o < output_size; o++) {
      for (int b = 0; b < batch_size; b++) {
        dw[h][o] += (x[b][h] * dout[b][o]);
      }
    }
  }

  for (int o = 0; o < output_size; o++) {
    db[o] = 0.0;
    for (int b = 0; b < batch_size; b++) {
      db[o] += dout[b][o];
    }
  }
}

これで、とりあえず一回分の学習について、Pythonのコードと同様の結果が得られるようになった。次に、この学習処理を何度も実行してMNISTの学習処理を進めていく。