[BlueLeaf1336]> PROBLEMS> NeuralNetwork>

ニューラルネットワーク > XOR問題

historyTOP

2009/12/15:作成
2009/12/16:続き
2009/12/17:続き

はじめにTOP

今、手元に 2002/02/27 に購入したパーソナルメディア刊平野廣美著「Cでつくるニューラルネットワーク」という本(以下、テキスト)があります。なんというか、突然ニューラルネットワークをやってみたくなったわけで。

先週なんですけど、平日はオフラインの上、持ってる本も別の場所にあってやってみたい欲が見事に増殖した感じです。で、週末に昔買った本を探しまわることになりました。

で、平日オフラインなんですが、携帯だけはあるのでi-modeで検索して思いを馳せているとXOR問題という言葉が見つかりました。テキストによると、初期のニューラルネットワークの限界として線形分離性という性質を持つ問題だけに対応できる(XOR演算は線形分離不可能)、という話があって、3層の階層型ネットワークとバックプロパゲーションで解決できた歴史があって、「XOR問題」がメジャーな言葉になってるようです。

ま、怪しい説明をしましたが、「XOR問題」という言葉がなんかかっこよくてますますやってみたくなったわけです。

テキストはタイトルどおりニューラルネットワークを実行できるように、どんな配列を用意するか、どんな順序で実行するか、などをかなり詳しく説明してくれています。で、ここではそれをDelphiで書くわけですが、そのままやっても面白くないのでわざわざイランことをしながら書いていく予定です。

テキストの説明TOP

テキストでは、配列をバキバキつかっています。できればこれをクラスで閉じ込めていきたいなぁと。感触としては配列の方がわかりやすそうな気もしますが、そこはあえて。基本的に頭の体操的な感じで進めようかと。

出てくる用語とかテキストに出てくる配列とか

判断方向(入力層⇒出力層)の信号伝播 (入力層⇒隠れ層、隠れ層⇒出力層 の2段階)

バックプロパゲーション(教師付き学習)

ただし、実際には、学習パターン(入力層に与える値と望ましい結果の対)は複数(XOR問題なら4種類)あるので、

実は、この最後の話がしっくりきません。直接「入力層に与える値と望ましい結果の対」を保持した方がよさそうな気がする。...多分浅知恵

Thinking TimeTOP

重みは、ユニット単体では決定できない。間。ユニットをクラスにすると、重みの調整がめんどくさくなる気がする。やっぱ神様視点(?)で配列かなぁ。

出力側につながる重みをユニットに持たせるにしても、入力側の重みをユニットに持たせるにしても、判断モードと学習モードでどっちも見ないといけない。

じゃ、両方持たすか。

ユニットの試作TOP

まだ中身は書いてませんが、こんな感じで進めてみます。ひょっとするとテキストどおりに配列使った方がシンプルな気がしてきたり。

uses
    SysUtils, Classes, Contnrs;

type
    //  値を更新したときに前回値との変更量を記録する
    TDeltaValue = class
    public
        Value: Double;                  //  値
        Delta: Double;                  //  前回値からの変化量
        procedure Update(const NewValue: Double; const KeepDelta: Boolean = False);
    end;

    //  ニューラルネットワークのユニット
    TNeuron = class
    private
        FPushWeight: TObjectBucketList; //  出力側との結合の重み
        FPullWeight: TObjectBucketList; //  入力との結合の重み
        FThreshold: TDeltaValue;        //  閾値(初期値ゼロ、かな)
        FScore: Double;                 //  学習信号(評価点数という意味で)
        FOutput: Double;                //  出力値
        //  入力側のユニットとの重みを設定する
        //  SetPushWeightが呼び出されたら反射的に中から呼び出す
        procedure SetPullWeight(const Target: TNeuron);
    public
        //  出力側のユニットとの重みを設定する
        procedure SetPushWeight(const Target: TNeuron; const Weight: Double);
    public
        procedure EvalOutput();         //  出力値計算
        procedure EvalScore();          //  学習信号計算
        procedure UpdateWeight();       //  入力側との結合の重みを調整
        procedure UpdateThreshold();    //  閾値を調整
    public
        constructor Create();
        destructor Destroy(); override;
    end;

中身を書く前に、こういうクラスがあると妄想してネットワークを作成する流れを確認してみます。

シャドウネットワーキングTOP

テキストをぱらぱらとめくってみると、まんなか位に汎用的なネットワークを作成できるプログラムが紹介されています。読んでみると、もうめんどくさいぐらい作成できるネットワークの構造が柔軟です。ただ、今、そのレベルに踏み込むと120%の確率で挫折するので、XOR問題を解ける程度としておきます。

まず、XOR演算の入力と出力の組をテキストファイル(学習データファイル)で読ませる必要があります。で、問題は、どうやって入力と出力を区別するかです。

0,0,0
0,1,1
1,0,1
1,1,0

しない、という手もあります。もうこうなったらしないことにします。なんならテキストファイルに分けなくてもソースコードにべた書きでもいい気がしてきましたが、さすがに気が引けるので上の形式で読み込むことにします。

多分、線形分離できるんだと思うんですが、AND や OR の問題を解かせることもできるなぁと自分に言い訳して

入力1、入力2、出力

のフォーマットとします。で、ネットワークは、テキストどおり、3層(入力層、隠れ層、出力層)として層間は直列接続に決めます。後は層ごとのユニットの数ですが、これもテキストどおり、2つ、2つ、1つに決めうちで。

そして、処理の流れもテキストどおり。

  1. 学習データファイルを読み込んで、保持する。
  2. ユニットを入力層2+隠れ層2+出力層1の5つ作成する。
  3. ネットワークを組み立てる。結合の重みは、-0.3~0.3の乱数で。
  4. 学習を行う。終了判定は、すべての学習パターンに対して誤差の2乗を足しこみ平均?が設定値以下
  5. 問題を出して遊んでみる

乱数の生成TOP

今、[0,1]の範囲で乱数を生成する関数があって、[-0.3,0.3]の乱数を得たいとしたら、どうしたらいいんだろう。単純に0.3左にずらしたら、[-0.3,0.7]になってしまう。

いやいや、0.6をかけて0.3ずらしたらいいですね。

[0,1]x0.6=[0,0.6]
[0,0.6]-0.3=[-0.3,0.3]

EOFTOP