Mathematicaでパーセプトロンとバックプロパゲーション

拙作のリバーシプログラムViglaは高校時代に作ったものだが、 評価関数は手の広さと辺の形を適当に数値化したものであるためにあまり強いプログラムにはできなかった。

当時から強いリバーシプログラムは辺や隅のパターンを評価していることは知ってはいたものの、理解出来ずじまいだった。

今回もう一度挑戦してみようと思い、まずはMathematicaでパーセプトロンとバックプロパゲーションによる学習を実装してみた。

簡単のため、出力関数としてはシグモイド関数のみに限定した(一般の関数にできるようにするには、各層のネット値を別に保存する必要があり、煩雑だったため)。 しかしながら、任意の隠し層の数やニューロン数に対応できるように実装した。

というわけで、まずはシグモイド関数と順方向演算を定義。

f[x_] := 1/(1 + Exp[-x]);
perceptron[w : {__}][x_] := FoldList[(f /@ (Append[#1, 1].#2)) &, x, w];

wが各層の重み行列のリストで、MathematicaならFoldListを使って1行で順方向演算が定義できる。 Mathematicaでは添字が1から始まるので、通常wi0で表されることが多いバイアス成分は一番最後に置いた。

次に、重み行列をランダムに初期化したパーセプトロンを返す関数として、以下のcreatePerceptronを定義。

createPerceptron[dimensions_, range_] := perceptron[MapThread[
   RandomReal[range, {#1, #2}] &, {Most[dimensions] + 1, Rest[dimensions]}]]

dimensionsは各層のニューロン数で、乱数の範囲をrangeで指定する。

例えば、入力層のニューロン数が2、隠し層のニューロン数が2、出力層のニューロン数が1の3層パーセプトロンで、 重みが-0.2から0.2までの乱数となるパーセプトロンは、以下のようにして得られる。

p = createPerceptron[{2, 2, 1}, {-0.2, 0.2}]

この初期状態で、入力層に{0, 0}を与えた時の出力は、

p[{0, 0}]
{{0, 0}, {0.512507, 0.510116}, {0.497566}}

となる。2番目の要素が隠し層の値で、最後の{0.497566}が出力層の値である。

次に、バックプロパゲーションによる学習を実装する。

backPropagation[pct : perceptron[w : {__}], x_List, teach_List, rate_: 1] := Module[{out, \[Delta], \[Delta]n, \[CapitalDelta]w},
  out = pct[x];
  \[Delta]n = With[{y = Last[out]}, (teach - y) y (1 - y)];
  \[Delta] = Reverse@FoldList[(Most[#2[[2]]].#1) #2[[1]] (1 - #2[[1]]) &, \[Delta]n,
     Reverse@Rest@Transpose[{Most[out], w}]];
  \[CapitalDelta]w = MapThread[Outer[Times, Append[#1, 1], #2] &, {Most@out, \[Delta]}];
  perceptron[w + rate \[CapitalDelta]w]
  ]

第1引数は学習させるパーセプトロンで、第2引数に入力層の値、第3引数に教師信号を与えると、学習後のパーセプトロンを返す関数とした。 δを求める式はもう少しスマートに書けるかもしれない。

例としてXORを学習させる場合は、以下のようにNestFoldを用いて学習後のパーセプトロンp2を得る。

p2 = Nest[Fold[backPropagation[#1, Sequence @@ #2] &, #,
       {{{0, 0}, {0}}, {{0, 1}, {1}}, {{1, 0}, {1}}, {{1, 1}, {0}}}] &,
  p, 2000];
Last[p2[#]] & /@ {{0, 0}, {0, 1}, {1, 0}, {1, 1}}
{{0.0317529}, {0.965119}, {0.9649}, {0.0442608}}

うまく学習できたようだ。 いろいろな文献を調べたが、微妙に文字や添え字に誤植があったりして苦労した。

以上のように、パーセプトロンとバックプロパゲーションもMathematicaを使えば少ない行数でスマートに書ける。