隠れ層のあるニューラルネットワークと誤差逆伝搬法
隠れ層の導入
前節でパーセプトロンとそのパラメータの決定法を見てきましたが、XOR はパーセプトロンで実現できませんでした。
しかし、XOR(a, b) = OR(NAND(a, b), NOR(A, B)) で、OR, NAND, NORはいずれもパーセプトロンで実現できますから、
パーセプトロンを縦列させればより複雑なモデルを表現できそうです。
このようにパーセプトロンを多数組み合わせてネットワーク化したシステムをニューラルネットと呼びます。
隠れ層
一般的なニューラルネットは入力に対して「中間的な」結果を計算し、それをまた入力として新たな「中間的な」結果を計算します。
最後に出力値を計算してニューラルネットの計算を終了します。
式で書くと
\[
S_{out} = N_{D-1}(N_{D-2}(\cdots N_0(x_{in}))
\]
あるいは「中間値」 \( S^k \) を導入して (\( S^{-1} = x とします。\))
\[
S^k = N_k(S^{k-1})
\]
中間層のことをニューラルネットでは「隠れ層」と呼びます。
なお、隠れ層の出力関数にはシグモイド関数を使い、出力層の出力関数にはソフトマックス関数を使うことが多いようです。(最近は ReLU を取るのがトレンドです。)
隠れ層に対するパラメータ推定
前節では単純パーセプトロンに対して w, b を推定する方法として、誤差逆伝搬法を紹介しました。
隠れ層があるニューラルネットワークでも誤差逆伝搬法を使ってパラメータ推定が可能です。
以降、\( z^d_{\alpha k} \) をサンプル \( \alpha \) の \( d \) 番目のパーセプトロンに対する線形和、 \( s^d_{\alpha k} = \sigma(z^d_{\alpha k}) \) とします。
損失関数 L が \( \bar{L} = L_0 \sum_\alpha L(s_\alpha, y_\alpha) \), \( (L_0 > 0) \)と書けている場合、( \( \sigma() \) は隠れ層ではシグモイド関数、出力層ではソフトマックス関数の出力を示すものとします)
\[
\frac{\partial \bar{L}}{\partial b^l_k} = L_0 \sum_\alpha \frac{\partial L(x_\alpha)}{\partial b^l_k}
\]
\[
\frac{\partial \bar{L}}{\partial w^l_{ik}} = L_0 \sum_\alpha \frac{\partial L(x_\alpha)}{\partial w^l_{ik}}
\]
となるので、\( L \) に対する微分を考えます。
\( d \)番目のパーセプトロンに対する w, b を \( w^d_{jk}, b^d_k \) のように書くことにすると、出力層に対しては今まで通り、
\[
\frac{\partial L(x_\alpha)}{\partial b^{D-1}_k} = L'(s^{D-1}_{\alpha k}) \sigma'(z^{D-1}_{\alpha k}) \equiv \delta^{D-1}_{\alpha k}
\]
\[
\frac{\partial L(x_\alpha)}{\partial w^{D-1}_{ik}} = L'(s^{D-1}_{\alpha k}) \sigma'(z^{D-1}_{\alpha k}) s^{D-1}_{\alpha i} = \delta^{D-2}_{\alpha k} s^{D-2}_{\alpha i}
\]
ただし、\( D \) はニューラルネットの段数を表すものとします。
隠れ層に対しては
\[
\frac{\partial L(x_\alpha)}{\partial b^d_k} = \sum_m \delta^{d+1}_{\alpha m} w^{d+1}_{km} \sigma'(z^d_{\alpha k}) \equiv \delta^d_{\alpha k}
\]
\[
\frac{\partial L(x_\alpha)}{\partial w^d_{ik}} = \delta^d_{\alpha k} s^{d-1}_{\alpha i}
\]
ただし、\( s^{-1} = x_{in} \) と書くことができます。
アルゴリズム
まず、SimpleNet の backPropagate() を隠し層に対応するように書き換えます。
// SimpleNet に隠し層を導入する
public class SimpleHiddenNet extends SimpleNet {
public SimpleHiddenNet(int samplen, int inn, int outnn, double eta) {
super(samplen, inn, outn, eta);
}
// メソッドを再定義
// deltad: delta^{d+1}
// wd: w^{d+1}_{ik}
public void backPropagate(double[][] deltad, double[][] wd, int[] samples) {
double[][] db2 = new double[samples.length][outn];
double[][] dw2 = new double[samples.length][inn][outn];
IntStream.range(0, samples.length).forEach(a->{
IntStream.range(0, inn).forEach(i->{
db2[a][i] += IntStream.range(0, outnn)
.mapToDouble(m->deltad[a][m] * wd[i][m])
.sum()
* in[samples[a]][i] * (1.0 - in[samples[a]][i];
IntStream.range(0, outn).forEach(k->{
dw2[a][i][k] = db2[a][k] * in[samples[a]][i];
});
});
});
// add for the whole samples
IntStream.range(0, inn).forEach(i->{
db[i] = IntStream.range(0, samples.length).mapToDouble(a->db2[a][i]).sum();
IntStream.range(0, outn).forEach(k->{
dw[i][k] = 0.0;
});
IntStream.range(0, outn).forEach(k->{
dw[i][k] += IntStream.range(0, outn).mapToDouble(a->dw2[a][i][k]).sum();
});
});
}
// learning process
IntStream.range(0, outn).forEach(k->{
b[k] -= eta * db[k];
IntStream.range(0, inn).forEach(i->{
w[i][k] -= eta * dw[i][k];
});
});
}
全接続型のニューラルネットワークを表すクラス NeuralNetwork を定義します。
forward() ではパーセプトロンの出力と入力を接続して計算します。
backPropagate() では、確率的最急降下法を各パーセプトロンの w, b に対して適用します。
隠れ層に対しては上記の式を適用し、出力層に近い隠れ層から値を求めます。
public class NeuralNetwork {
public SimpleNet[] nn;
public int[] layers;
// constructor
public NeuralNetwork(int[] layers, int samplen, double eta) {
this.layers = layers;
this.nn = new SimpleNet[layers.length - 1];
IntStream.range(0, layers.length - 1).forEach(i->{
if (i == layers.length - 1) {
nn[i] = new SimpleNet(samplen, layers[i], layers[i + 1], eta);
} else {
nn[i] = new SimpleHiddenNet(samplen, layers[i], layers[i + 1], eta);
}
});
}
// forward propagation
public double[][] forward(double[][] xin, int[] samples) {
double[][] tmp = xin, r = null;
IntStream.range(0, layers.length - 1).forEach(i->{
r = nn[i].fowrard(tmp, samples);
tmp = nn[i].s;
});
return r;
}
public void backPropagate(double[][] xin, double[][] xout, int count, int nset, int batchsize) {
IntStream.range(0, count).forEach(c->{
// create random number sequence for the interval [0, samplen)
int[] samples = createRandomSeq(batchsize, samplen);
IntStream.range(0, nset).forEach(n->{
IntStream.range(0, layers.length - 1).forEach(d->{
nn[d].initializeWB(samples);
});
IntStream.range(0, layers.length - 1)
.map(d -> layers.length - d - 1).forEachOrdered(d->
if (d == layers.length - 2) {
nn[d].backPropagate(nn[d].in, xout, samples);
} else {
nn[d].backPropagate(nn[d+1].b, nn[d+1].w, samples);
}
});
});
});
}
}
Copyright (c) 2017-2019 by TeqStock.tokyo