『アソシアトロン』中野馨著の第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つのデータだけ違った結果を出している。

アソシアトロン の原理

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

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

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

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

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

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

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

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