『アソシアトロン』中野馨著の第4章概念形成の計算

表題の著作の第4章、4.1「概念形成」にかかれているサンプルを、プログラムして計算してみた。

まず、ベイズ定理を用いた計算は、次のようなプログラムだ。g++の一番新しいもので、linuxマシンでコンパイルすればいいと思う。私は、ubuntu 18.04 のg++でコンパイルしている。他のものの場合は、変更が必要になるかもしれない。(なお、その後知ったことだが、これは単純ベイズ分類器 Naive Bayes Classifier というものである。後の記事でもっと詳しく説明するだろう)

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

double pxAB(int x[], double pq[]);

int main(){

    int x[16][4] = {
        {  0,  0,  0,  0}, //0  
        {  0,  0,  0,  1}, //1  
        {  0,  0,  1,  0}, //2  
        {  0,  0,  1,  1}, //3  
        {  0,  1,  0,  0}, //4  
        {  0,  1,  0,  1}, //5  
        {  0,  1,  1,  0}, //6  
        {  0,  1,  1,  1}, //7  
        {  1,  0,  0,  0}, //8  
        {  1,  0,  0,  1}, //9  
        {  1,  0,  1,  0}, //10 
        {  1,  0,  1,  1}, //11 
        {  1,  1,  0,  0}, //12 
        {  1,  1,  0,  1}, //13 
        {  1,  1,  1,  0}, //14 
        {  1,  1,  1,  1}  //15 
        };
        
    double PA = 0.5;
    double PB = 0.5;
    double p[4] = {0.6, 0.8, 0.8, 0.6};
    double q[4] = {0.4, 0.2, 0.2, 0.2};    
    
    for(int d=0;d<16;d++){
        double PxA = pxAB(x[d],p); 
        double PxB = pxAB(x[d],q); 
        double probBx = (PxB*PA)/(PxA*PA+PxB*PB); 
        double probAx = 1-probBx; 
        string AB; 
        if(probAx > probBx) 
            AB = " ==> A";
        else 
            AB = " ==> B";
        cout << "No." << d << " PAx = " << probAx << " PBx = " << probBx << AB <<endl;
    }
    
}

double pxAB(int x[], double pq[]){
    double prob = 1;
    for(int i=0;i<4;i++){
        prob = prob*pow(pq[i],x[i])*pow(1-pq[i],1-x[i]);
    }
    return prob;
}

以下のように、上記の本に書かれているとおりの結果が出力された。

No.0 PAx = 0.0204082 PBx = 0.979592 ==> B
No.1 PAx = 0.111111 PBx = 0.888889 ==> B
No.2 PAx = 0.25 PBx = 0.75 ==> B
No.3 PAx = 0.666667 PBx = 0.333333 ==> A
No.4 PAx = 0.25 PBx = 0.75 ==> B
No.5 PAx = 0.666667 PBx = 0.333333 ==> A
No.6 PAx = 0.842105 PBx = 0.157895 ==> A
No.7 PAx = 0.969697 PBx = 0.030303 ==> A
No.8 PAx = 0.0447761 PBx = 0.955224 ==> B
No.9 PAx = 0.219512 PBx = 0.780488 ==> B
No.10 PAx = 0.428571 PBx = 0.571429 ==> B
No.11 PAx = 0.818182 PBx = 0.181818 ==> A
No.12 PAx = 0.428571 PBx = 0.571429 ==> B
No.13 PAx = 0.818182 PBx = 0.181818 ==> A
No.14 PAx = 0.923077 PBx = 0.0769231 ==> A
No.15 PAx = 0.986301 PBx = 0.0136986 ==> A

しかし、アソシアトロンのほうがうまく行かない。原因がわかったかたは、ツイッターの @wassiisg まで教えていただきたい。色々テスト、計算過程の検証をしたが、3個のデータについて、ベイズ推定の場合と一致しない。プログラムは、以下のようなものである。

#include <iostream>
#include <string>
#include <cmath>

using namespace std;

void estimation(int v[], int data[][4]);

int main(){
    int data[10][4] = {
        {  1,  1,  1,  1}, // A
        {  1,  1,  1,  0}, // A
        {  1,  1,  0,  1}, // A
        {  0,  1,  1,  0}, // A
        {  0,  0,  1,  1}, // A
        {  1,  0,  0,  0}, // B
        {  0,  1,  0,  0}, // B
        {  0,  0,  1,  0}, // B
        {  1,  0,  0,  1}, // B
        {  0,  0,  0,  0}  // B
        };

    int test[16][4] = {
        {  0,  0,  0,  0}, //0  B
        {  0,  0,  0,  1}, //1  NON X
        {  0,  0,  1,  0}, //2  B
        {  0,  0,  1,  1}, //3  A
        {  0,  1,  0,  0}, //4  B
        {  0,  1,  0,  1}, //5  NON
        {  0,  1,  1,  0}, //6  A
        {  0,  1,  1,  1}, //7  B
        {  1,  0,  0,  0}, //8  B
        {  1,  0,  0,  1}, //9  NON
        {  1,  0,  1,  0}, //10 NON X
        {  1,  0,  1,  1}, //11 NON
        {  1,  1,  0,  0}, //12 NON X
        {  1,  1,  0,  1}, //13 A
        {  1,  1,  1,  0}, //14 A
        {  1,  1,  1,  1}  //15 A
        };

    int M[4][4];
    for(int i=0;i<4;i++){
        for(int j=0;j<4;j++){
            M[i][j] = 0;
        }
    }
    
    for(int d=0;d<10;d++){
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                int di = data[d][i];
                int dj = data[d][j];
                if(di == 0) di = -1;
                if(dj == 0) dj = -1;
                M[i][j] = M[i][j] + (di*dj);
            }
        }
    }
    cout << "記憶行列 M " << endl;
    for(int i=0;i<4;i++){
        for(int j=0;j<4;j++){
            cout << M[i][j] << " ";
        }
        cout << endl;
    }
    
    cout << "概念形成 " << endl;
    for(int d=0;d<16;d++){
        cout << "No." << d << ":: "; 
        int v[4];
        for(int j=0;j<4;j++) v[j] = 0;
        for(int i=0;i<4;i++){
            for(int k=0;k<4;k++){ 
                int dk = test[d][k]; 
                if(dk == 0) dk = -1; 
                v[i] += M[i][k]*dk; 
            } 
            if(v[i] > 0){
                v[i] = 1;
                cout << "1 "; 
            }else{
                 v[i] = 0;
                 cout << "0 "; 
            }
        }
        estimation(v, data);
        cout << endl;
    }
    
    return 0;
}

void estimation(int v[], int data[][4]){
    double minidist = 10000;
    int minino = -1;
    for(int d=0;d<10;d++){
        double dis = 0;
        for(int j=0;j<4;j++){
            // ユークリッドの距離
            dis += (double)(data[d][j]-v[j])*(data[d][j]-v[j]);
            /*
            // Hamming の距離
            if(data[d][j]-v[j] < 0){
                dis += v[j]-data[d][j];
            }else{
                dis += data[d][j]-v[j];
            }
            */
        }
        // ユークリッドの距離
        dis = sqrt(dis); 
        // Hamming の距離
        //dis = dis/2;
        if(dis < minidist){
            minino = d;
            minidist = dis;
        }
    }
    if(minino < 5) cout << " ==> A ";
    else cout << " ==> B ";    
    cout << "( " << minino << ", " << minidist << " )";
}

出力結果は以下のようになる。

記憶行列 M 
10 2 -2 4 
2 10 2 0 
-2 2 10 0 
4 0 0 10 
概念形成 
No.0:: 0 0 0 0  ==> B ( 9, 0 )
No.1:: 0 0 0 1  ==> A ( 4, 1 )
No.2:: 0 0 1 0  ==> B ( 7, 0 )
No.3:: 0 0 1 1  ==> A ( 4, 0 )
No.4:: 0 1 0 0  ==> B ( 6, 0 )
No.5:: 0 1 0 1  ==> A ( 2, 1 )
No.6:: 0 1 1 0  ==> A ( 3, 0 )
No.7:: 0 1 1 1  ==> A ( 0, 1 )
No.8:: 1 0 0 0  ==> B ( 5, 0 )
No.9:: 1 0 0 1  ==> B ( 8, 0 )
No.10:: 1 0 1 0  ==> A ( 1, 1 )
No.11:: 1 0 1 1  ==> A ( 0, 1 )
No.12:: 1 1 0 0  ==> A ( 1, 1 )
No.13:: 1 1 0 1  ==> A ( 2, 0 )
No.14:: 1 1 1 0  ==> A ( 1, 0 )
No.15:: 1 1 1 1  ==> A ( 0, 0 )

ベイズ推定の場合と比べると3つのデータだけ違った結果を出している。

アソシアトロン でMISNT

MINSTの手書き数字のパターン認識をアソシアトロンでできないかと思っている。ディープラーニングに勝とうというわけではない。そもそも、アソシアトロン ではできない可能性が高いが、何事も挑戦である。

基本的なアイデアはこうだ。

まず、MNSITは28X28の画像データになっている。以前、ディープラーニングでやったことがある。MNIST手書き数字データで93%の識別率

1ピクセル1バイトのデータになっていたと思う。例えば、ゼロ以上の値を持つピクセルを1とし、それ以外を-1としてアソシアトロン に乗せると、パターンのベクトル次元は、784次元しかない。一方MNISTの学習データは60000個あるのだ。784次元にこれだけのパターンを学習させるのは、つまり前のアソシアトロン の原理で説明したM行列を作成するのは、ほとんど不可能だというか、意味がない。識別不能であることがやる前から明らかだ。

アソシアトロン は、パターンの情報を分散させることでその威力は生まれているわけだから、784次元はMNISTには少なすぎるのである。

そこでまず、もともと1ピクセル1バイトだから、その値をビット化して、例えばあるピクセルのデータが77だったら、'01001101'として、のちに0を-1に変換すれば、784x8=6272次元になる。さらに、1ニューロンは、0,1,-1のいずれかなので、2ビット必要になるので、6272x2=12544ニューロンを使うことになる。具体的なやり方は、1の時は、10、0の時は、00、-1の時は11にすればいい(一つ使わないものが出るがそれは問題ない)。

これで結構増えた。

でもまだ足りない。何しろデータは60000組あるのである。ここで、少しトリッキーな操作をすることが考えられる。現時点では、あくまでも、こうしたらいいかなという程度の思いつきである。

上で示したように、1ピクセルは、8x2=16ビット(16ニューロン)で表されるのだが、この各ニューロンにランダムなゆらぎを与えよう。今、この16ニューロンは、先の作りから2ビットずつペアになっているのだが、この1ビットめに2ビット使う。この1ビットめが1の時は、新たな2ビットについて、01か10で表すのである。どちらで表すかは、データごとにランダムにxれぞれ0.5の確率で割り振る。

こんな不確実なゆらぎを与えても結果は多分変わらないだろうと思う。そうすると、パターンベクトルは、さらに8倍の大きさになる。

すなわち12544x8=100352、つまり約10万次元となるのである。これだけ増えれば、6万組の学習データの記憶が可能になるのではないか。

というのが、私のアイデアである。

ただ、この場合、一つのパターン行列が100352x100352ビットになる。すなわち約1Gになってしまう。これではちょっとコンピュータが持たない。だから、これはちょっと多すぎる。いや、できなくはないか。

アソシアトロン の原理

人工知能の分野では、ディープラーニングなどの階層的ニューラルネットワークが脚光を浴びている。確かに、驚くべき成果を挙げているのだから、それは当然のことである。しかし、それが人間の脳のニューラルネットワークをシミュレートしているかといえばそうではないだろう。ディープラーニングが、その基礎的パーツとして神経回路網的構造を持っていることは確かだが、人間の脳もそのようにシステマティックに階層化されたネットワーク層を積み重ねているとは到底思えない。

人間の脳は、もっと非構造的システムのはずだ。脳には、領域ごとに違った機能を果たしていることはわかっている。しかし、その領域そのものが莫大な冗長性を持ったものであり、漠然とした機能の瞬間的作用から、人間の意識を想像している感じなのである。

そのように考えていた時、アソシアトロン というものに出会った。実は、私が30年以上前、岩手大学にいた頃、今のディープラーニングにつながるニューラルネットワークを研究していた頃、すでにこのアソシアトロン というものは世に出されていた。名前は知っていたのだ。が、当時の、バックプロぱゲーションなどを実装した並列処理システム、ニューラルネットワークの勢いの中で、真剣に考えてみたいテーマではなかったから、具体的にどのように実装するなどというところまでは全く行かなかった。

しかし、今この時に、改めてその内容を捕まえてみると、とても興味深い。そうだ、人間の脳は、きっとこんな感じなのだと思わせる、単純で、それでいてニューラルネットワークらしい漠然として機能を有している気がしてきた。

改めて、その理論の中身を捉えてみた。それは以下にまとめておいた。

アソシアトロン の原理
アソシアトロン の原理

この原理説明のpdf文書を見ていただければ明らかなように、このアソシアトロン が必ずしもそのものではない情報から記憶を再現できるのは、パターンが、そのパターンの次元倍のネットワークの中に、パターン情報を分散させるからなのである。原理的なアイデアはこれに尽きると思う。

今日のコンピュータ機能の進化した状況の中で、この単純さと優れた機能は改めて見直されるべきだと思う。