スクリプト言語 ツインテールdeエンジェルモード!! で、簡単なXORニューラルネットのシミュレーションを実行してみたいと思います。

 

これは、オーム社「機械学習と深層学習(C言語によるシミュレーション)」(小高知宏著)にて説明されている、単純な階層型ニューラルネットプログラムを、スクリプト言語 ツインテールdeエンジェルモード!! でも、作成してみようというものです。

 

そして、「ソース規模」「実行速度」の比較もしてみたいと思います。

(プログラム自体の説明については、本書をご覧ください。)

 

スクリプトの導入部分とメインプログラムはこんな感じになります。

(以下、インラインでコメント挿入)

 

>C言語のプリプロセッサ(マクロ)に相当する機能はないので、グローバル変数

>で似たようなことを実現してみました。

 

global    INPUTNO =    2                /* 入力層のニューロン数                */
global    HIDDENNO=2                    /* 隠れ層のニューロン数                */
global    DATANO  =100                /* データセットの最大数                */

/* 重みと閾値の初期化&データの読み込み */
wh = initwh()
wo = initwo()
(n_of_e,e) = getdata(e)
print("Numbe of Data Set = %d\n",n_of_e)
/* メインループ */
for( i=0 ; i<n_of_e ; i++ ){        // 全ての入力データについて...
    print("[%d] ",i)
    for( j=0 ; j<INPUTNO ; j++ )
        print("%f ",e[i][j])
    o = forward(wh,wo,e[i])
    print("%f\n",o)
}

 

重みと閾値の初期化をして、データを読み込んで、

順方向計算をする。という、とてもシンプルなものです。

前回やった、人工ニューロンのシミュレーションと大変似ています。

 

次に、初期化関数はこんな感じです。パラメーターは固定値となっています。

 

def initwh(){
    wh[0][0] = -2
    wh[0][1] = +3
    wh[0][2] = -1
    wh[1][0] = -2
    wh[1][1] = +1
    wh[1][2] = +0.5
    retn(wh)
}

def initwo(){
    wo = {-60,+94,-1}
    retn(wo)
}

データの読み込み関数ですが、これは前回と全く同じなので省略します。

最後に、順方向計算関数です。

入力データを重みと掛け合わして、その結果を足し合せ、

最後に閾値を引いています。そして、それを2層に渡って計算しています。

なお、活性化関数は、ステップ関数となっています。

 

def forward(wh,wo,e){
/* z の計算 */
    for( i=0 ; i<HIDDENNO ; i++ ){
        u = 0.0
        for( j=0 ; j<INPUTNO ; j++ )
            u += wh[i][j]*e[j]
        u -= wh[i][j]                // 閾値
        z[i] = (u>=0?1.0:0.0)
    }
/* o の計算 */
    o = 0.0
    for( i=0 ; i<HIDDENNO ; i++ )
        o += wo[i]*z[i]
    o -= wo[i]                        // 閾値
    retn( o>=0?1.0:0.0 )
}

では、実際に実行してみて、実行時間を測定してみます。

 

% time tt nn.tt < ch4/data24.txt
Numbe of Data Set = 4
[0] 0.000000 0.000000 0.000000
[1] 0.000000 1.000000 1.000000
[2] 1.000000 0.000000 1.000000
[3] 1.000000 1.000000 0.000000

real    0m0.010s
user    0m0.006s
sys    0m0.003s


単純な計算だけなので、あまり時間が掛かっていません。

なお、プログラムはXOR回路となりました。

 

次に、C言語によるバージョンで速度を測定してみます。

(といっても、ダウンロードバージョンではなく、

独自にC言語で作ったプログラムでの測定となります。)

 

% gcc -Wall nn.c
% time a.out < ch4/data24.txt
Numbe of Data Set = 4
[0] 0.000000 0.000000 0.000000
[1] 0.000000 1.000000 1.000000
[2] 1.000000 0.000000 1.000000
[3] 1.000000 1.000000 0.000000

real    0m0.001s
user    0m0.000s
sys    0m0.000s


流石C言語という感じですね!

丁度10倍の速度で処理が終わっています。

 

最後に、ソース規模(ソース行数)の比較です。

書籍では、154行(C言語)ですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、

 

% wc -l nn.tt
70 nn.tt

70行で済みました。あと、数字では表しにくいのですが、プログラムの記述の

しやすさも、スクリプト言語の方が、かなりお気楽でした。

 

この辺り(お気楽さ)は、軽量言語の得意分野ですね!!

用途に合わせた、適材適所な選択がベストな選択だと思います。

 

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な人工ニューロンのシミュレーションを実行してみたいと思います。

 

これは、オーム社「機械学習と深層学習(C言語によるシミュレーション)」(小高知宏著)にて説明されている、単体の人工ニューロンプログラムを、スクリプト言語 ツインテールdeエンジェルモード!! でも、作成してみようというものです。

 

そして、「ソース規模」「実行速度」の比較もしてみたいと思います。

(プログラム自体の説明については、本書をご覧ください。)

 

スクリプトの導入部分とメインプログラムはこんな感じになります。

(以下、インラインでコメント挿入)

 

>C言語のプリプロセッサ(マクロ)に相当する機能はないので、グローバル変数

>で似たようなことを実現してみました。

 

global    INPUTNO=2                    /* 1ニューロンの入力数                */
global    DATANO =100                    /* データセットの最大数                */

 

/* 重みと閾値の初期化&データの読み込み */
w = initw()
(n_of_e,e) = getdata()
print("Numbe of Data Set = %d\n",n_of_e)
/* メインループ */
for( i=0 ; i<n_of_e ; i++ ){        // 全ての入力データについて...
    print("[%d] ",i)
    for( j=0 ; j<INPUTNO ; j++ )
        print("%f ",e[i][j])
    o = forward(w,e[i])
    print("%f\n",o)
}
retn(0)

 

重みと閾値の初期化をして、

データを読み込んで、

順方向計算をする。という、とてもシンプルなものです。

 

次に、初期化関数はこんな感じです。固定値となっています。

閾値の変更によって、ANDとORの機能が選べるようになっています。

 

def initw(){
    w[0] = 1.0
    w[1] = 1.0
    w[2] = 1.5
//    w[2] = 0.5
    retn(w)
}

続いて、データの読み込み関数です。

読み込んだデータは(配列に分解したデータも)、文字列であることに注意が

必要です。この値は、後で計算に用いるので整数化しておきます。

 

def getdata(){
    n_of_e=0
    while(s=gets()){
        e[n_of_e][0]=int(psplit(s,' ',0))
        e[n_of_e][1]=int(psplit(s,' ',1))
        n_of_e++
    }
    retn(n_of_e,e)
}

 

最後に、順方向計算関数です。

入力データを重みと掛け合わして、その結果を足し合せ、

最後に閾値を引いています。

活性化関数は、ステップ関数となっています。

 

def forward(w,e){
    u = 0.0;
    for( i=0 ; i<INPUTNO ; i++ )
        u += w[i]*e[i]
    u -= w[i]                        // 閾値
    o = (u>=0)?1.0:0.0                // Step関数
    retn(o)
}

では、実際に実行してみて、実行時間を測定してみます。

 

% time tt neuron.tt < ch4/data24.txt
Numbe of Data Set = 4
[0] 0.000000 0.000000 0.000000
[1] 0.000000 1.000000 0.000000
[2] 1.000000 0.000000 0.000000
[3] 1.000000 1.000000 1.000000

real    0m0.009s
user    0m0.002s
sys    0m0.008s

単純な計算だけなので、ほとんど時間が掛かっていません。

なお、プログラムはAND回路となりました。

 

次に、閾値を変更して実行しています。

変更する箇所は、initw() 関数のここだけです。

 

//  w[2] = 1.5
    w[2] = 0.5
 

% tt neuron.tt < ch4/data24.txt
Numbe of Data Set = 4
[0] 0.000000 0.000000 0.000000
[1] 0.000000 1.000000 1.000000
[2] 1.000000 0.000000 1.000000
[3] 1.000000 1.000000 1.000000

この閾値では、OR回路になりました。

 

次に、C言語によるバージョンで速度を測定してみます。

(といっても、ダウンロードバージョンではなく、

独自にC言語で作ったプログラムでの測定となります。)

 

% gcc -Wall neuron.c
% time a.out < ch4/data24.txt
Numbe of Data Set = 4
[0] 0.000000 0.000000 0.000000
[1] 0.000000 1.000000 1.000000
[2] 1.000000 0.000000 1.000000
[3] 1.000000 1.000000 1.000000

real    0m0.000s
user    0m0.000s
sys    0m0.000s

流石C言語という感じですね!

測定限界を下回っているようです。

 

最後に、ソース規模(ソース行数)の比較です。

書籍では、123行(C言語)ですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、

 

% wc -l neuron.tt
52 neuron.tt

52行で済みました。あと、数字では表しにくいのですが、プログラムの記述の

しやすさも、スクリプト言語の方が、かなりお気楽でした。

 

この辺り(お気楽さ)は、軽量言語の得意分野ですね!!

用途に合わせた、適材適所な選択がベストな選択なんでしょうね。

先程、スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な遺伝的アルゴリズムのシミュレーションプログラムを作成してみたのですが、それの実行して結果をプロットをしてみたいと思います。

 

まずは、実行と結果の取得から。

リダイレクトを使って、こんな感じで取得します。

 

% tt ga.tt <  ch3/data.txt | grep Best > log

 

logの内容はこんな感じです。

 

% head log
Best = 788    Ave = 673.466667
Best = 905    Ave = 713.866667
Best = 1014    Ave = 738.700000
Best = 949    Ave = 742.133333
Best = 852    Ave = 727.333333
Best = 889    Ave = 747.433333
Best = 883    Ave = 736.800000
Best = 922    Ave = 765.433333
Best = 892    Ave = 740.833333
Best = 892    Ave = 787.133333

 

% tail log
Best = 1180    Ave = 1073.400000
Best = 1180    Ave = 1057.000000
Best = 1153    Ave = 1026.900000
Best = 1153    Ave = 1024.133333
Best = 1170    Ave = 1028.133333
Best = 1170    Ave = 996.100000
Best = 1170    Ave = 998.766667
Best = 1170    Ave = 1020.666667
Best = 1170    Ave = 1025.000000
Best = 1157    Ave = 989.533333

 

それでは、gnuplot を利用してプロットしています。

 

% gnuplot

    G N U P L O T
    Version 5.0 patchlevel 5    last modified 2016-10-02

    Copyright (C) 1986-1993, 1998, 2004, 2007-2016
    Thomas Williams, Colin Kelley and many others

    gnuplot home:     http://www.gnuplot.info
    faq, bugs, etc:   type "help FAQ"
    immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'x11'
gnuplot> set grid
gnuplot> plot [][600:] "log" u:3 w lp t "Best", "" u:6 w lp t "Ave"

Best & Ave ともに、まずまず改善していっている感じでしょうか?

Best は 30 世代前ぐらいで頭打ちとなってしまっています。

 

次に、実行時間を測定してみます。まずは、普通バージョン

 

% time tt ga.tt <  ch3/data.txt | grep Best > log

real    0m10.716s
user    0m10.674s
sys    0m0.113s

 

次は、 -O3 を指定してコンパイルした最適化バージョン

 

% time tt ga.tt <  ch3/data.txt | grep Best > log

real    0m8.288s
user    0m8.246s
sys    0m0.105s

 

gccのオプチマイザー ( -O3 ) は凄いですね!!

ざっと20%も速くなりました!!

 

では、C言語によるバージョンで速度を測定してみます。

(といっても、ダウンロードバージョンではなく、

独自にC言語で作ったプログラムでの測定となります。)

 

% time a.out < ch3/data.txt > log

real    0m0.015s
user    0m0.014s
sys    0m0.001s

流石C言語という感じですね!

まさに、桁違いのスピードです。(2〜3桁違います!)

 

最後に、ソース規模(ソース行数)の比較です。

書籍では、300行(C言語)ですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、

 

% wc -l ga.tt
146 ga.tt

146行で済みました。あと、数字では表しにくいのですが、プログラムの記述の

しやすさも、スクリプト言語の方が、かなりお気楽でした。

 

この辺り(お気楽さ)は、軽量言語の得意分野ですね!!

用途に合わせた、適材適所な選択がベストな選択なんでしょうね。

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な遺伝的アルゴリズムのシミュレーションを実行してみたいと思います。

 

これは、オーム社「機械学習と深層学習(C言語によるシミュレーション)」(小高知宏著)にて説明されている、ナップサック問題の遺伝的アルゴリズムによる解法プログラムを、スクリプト言語 ツインテールdeエンジェルモード!! でも、作成してみようというものです。

 

そして、「ソース規模」「実行速度」の比較もしてみたいと思います。

(プログラム自体の説明については、本書をご覧ください。)

 

スクリプトの導入部分とメインプログラムはこんな感じになります。

(以下、インラインでコメント挿入)

 

>C言語のプリプロセッサ(マクロ)に相当する機能はないので、グローバル変数

>で似たようなことを実現してみました。

 

global    MAXVALUE    =100                /* 重さ&価値の最大値                */
global    N            =30                    /* 荷物の個数                        */
global    WEIGHTLIMIT    =(N*MAXVALUE/4)        /* 重量制限(全体の1/4)            */
global    POOLSIZE    =30                    /* 遺伝子のプールサイズ                */
global    LASTG        =50                    /* 打ち切り世代                        */
global    MRATE        =0.01                /* 突然変異の確率                    */

global parcel                            /* 荷物(重量+価値)                */
global pool,ngpool

/* 荷物と染色体の初期化 */
srand(0)
initparcel()
initpool()

/* メインループ */
for( gene=0 ; gene<LASTG ; gene++ ){
    print("%d世代\n",gene)
    mating()
    mutation()
    selectng()
    printp()
}
retn(0)

特に、むつかしいところはありません。

初期化関数はこんな感じです。荷物データは、標準入力から読み込みます。

 

def initparcel(){
    for( i=0 ; s=gets() ; i++ ){
        parcel[i,0]=int(psplit(s,' ',0))
        parcel[i,1]=int(psplit(s,' ',1))
    }
}

def initpool(){
    for( i=0 ; i<POOLSIZE ; i++ )
        for( j=0 ; j<N ; j++ )
            pool[i][j]=rand(2)
    for( i=0 ; i<POOLSIZE*2 ; i++ )
        for( j=0 ; j<N ; j++ )
            ngpool[i][j]=0
}

 

遺伝子の評価関数と、遺伝子の表示関数です。

特に説明は必要ないと思います。

 

def evalfit(g){
    weight=0.0
    value =0.0
    for( pos=0 ; pos<N ; pos++ ){
        weight += parcel[pos,0]*g[pos]
        value  += parcel[pos,1]*g[pos]
    }
    if( weight>=WEIGHTLIMIT) value=0
    retn(value)
}

def printp(){
    fit                                // 適応度
    totalfit=0.0                    // 適応度の合計値
    bestfit=0
    for( i=0 ; i<POOLSIZE ; i++ ){
        for( j=0 ; j<N ; j++ )
            print("%d",pool[i][j])
        fit=evalfit(pool[i])
        print("\t%d\n",fit)
        if(fit>bestfit)
            bestfit=fit
        totalfit+=fit
    }
    print("Best = %d\tAve = %f\n",bestfit,totalfit/POOLSIZE)
}

 

遺伝子の交配を実行する関数と親を選択する関数です。

選択時において、ルーレット選択アルゴリズムを利用しています。

効率的ではないかもしれませんが、なかなか面白いやり方です。

 

美男美女ほど早く相手を見付けやすいということでしょうか?

 

def crossing(m,p,c1,c2){
    cp=rand(N)
/* 前半のコピー */
    for( j=0 ; j<cp ; j++ ){
        c1[j] = m[j]
        c2[j] = p[j]
    }
/* 後半のコピー */
    for(     ; j<N  ; j++ ){
        c2[j] = m[j]
        c1[j] = p[j]
    }
}

def selectp(roulette,totalfit){
    ball=rand(totalfit)
    acc =0
    for( i=0 ; i<POOLSIZE ; i++ ){
        acc += roulette[i]
        if(acc>ball) break
    }
    retn(i)
}

 

def mating(){
/* ルーレットの作成 */
    totalfit=0
    for( i=0 ; i<POOLSIZE ; i++ ){
        roulette[i] = evalfit(pool[i])
        totalfit += roulette[i]
    }
/* 親の選択と交配 */
    for( i=0 ; i<POOLSIZE ; i++ ){
        do_while(mama==papa){
            mama = selectp(roulette,totalfit)
            papa = selectp(roulette,totalfit)
        }
        crossing(pool[mama],pool[papa],ngpool[i*2],ngpool[i*2+1])
    }
}

 

突然変異の発生関数と、次世代遺伝子を選択する関数です。

こちらの選択でも、ルーレット選択アルゴリズムを利用しています。

 

def mutation(){
    for( i=0 ; i<POOLSIZE*2 ; i++ )
        for( j=0 ; j<N ; j++ )
            if(rand(100)/100.0<=MRATE)
                ngpool[i][j] = !ngpool[i][j];
}

def selectng(){
    for( i=0 ; i<POOLSIZE ; i++ ){    // 全ての染色体について...
    /* >ルーレットの作成 */
        totalfit=0
        for( c=0 ; c<POOLSIZE*2 ; c++ ){
            roulette[c] = evalfit(ngpool[c])
            totalfit += roulette[c]
        }
    /* >染色体を1つ選ぶ */
        ball=rand(totalfit)
        acc =0
        for( c=0 ; c<POOLSIZE*2 ; c++ ){
            acc += roulette[c]
            if(acc>ball) break
        }
    /* >染色体のコピー */
        for( j=0 ; j<N ; j++ )
            pool[i][j] = ngpool[c][j]
    }
}

 

 

 

実行時間自体は一瞬なんですが、C言語と比べると実に100倍以上の時間差が

発生しています。コンパイラー言語と、軽量スクリプト言語の差でしょうね。

 

先程、スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な群知能のシミュレーションを実行してみたのですが、それの結果をプロットをしてみたいと思います。

 

まずは、結果の取得から。

リダイレクトを使って、こんな感じで取得します。

 

% tt ant.tt > log

 

内容の確認をしてみます。まずは、最初(0回目)の部分です。

 

% head -15 log
0:
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
*mstep
1 0 0 0 0 0 0 1 1 1
0 0 0 1 1 0 0 1 1 0
1 0 0 0 0 0 0 0 0 1
1 0 1 0 1 0 1 1 0 1
1 0 1 0 0 1 1 0 1 1
1 1 1 0 0 1 1 0 0 1
1 0 1 0 0 1 0 0 1 0
0 1 0 1 1 1 1 0 0 1
0 0 1 0 1 1 0 1 1 1
1 0 1 1 1 0 1 1 0 0
AVE = 30.000000

 

蟻は、ほとんどばらばらに動いていて、平均移動距離も 30 と大きいです。

次に、最後(49回目)の部分です。

 

% tail -15 log
49:
1.44 8.97 9.24 9.35 9.16 9.14 8.94 8.93 9.14 9.04
8.23 0.70 0.42 0.32 0.51 0.53 0.73 0.74 0.52 0.63
*mstep
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 1
1 0 1 0 0 0 0 0 0 0
1 0 1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0
AVE = 15.600000

蟻は、ほとんど 0 のステップを選択するようになりました。

平均移動距離も 15.6 と小くなっています。

 

最後に、蟻の平均移動距離をプロットしてみます。

例によって、 gnuplot を使いたいと思います。

データの取得部分は、キーワード AVE を使って grep します。

 

% tt ant.tt | grep AVE > log
% gnuplot

    G N U P L O T
    Version 5.0 patchlevel 5    last modified 2016-10-02

    Copyright (C) 1986-1993, 1998, 2004, 2007-2016
    Thomas Williams, Colin Kelley and many others

    gnuplot home:     http://www.gnuplot.info
    faq, bugs, etc:   type "help FAQ"
    immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'x11'
gnuplot> set grid
gnuplot> plot [][0:] "log" u:3 w l
gnuplot>

set grid は、方眼紙のような点線を入れるためのものです。

(なくても問題ありません。)

 

[][0:] は、X軸はデフォルトで、Y軸は0〜デフォルトまでを表示するという設定です。

(なくても問題ありません。)

 

u:3 は 3 カラム目のデータを表示する指定で、

w l は線で表示する指定です。

 

表示されたグラフを見ると、最初は 30 ぐらいあった平均移動距離が、

シミュレーションを重ねるに従って 15 ぐらいまで落ちているのがわかります。

 

***

 

一点注意点があります。 書籍のリスト 3.1 の 118 行目の文ですが、

        abs( ... ) < 1e-9

となっていますが、C言語の関数 abs() は、整数を受け取って、整数を返しますので、

この記述は誤解を招くと思われます。

 

実質的には、(実数の整数化時には小数点以下は切り捨てられますので、)

        abs( ... ) < 1

のような感じになると思います。

 

スクリプト言語 ツインテールdeエンジェルモード!! では、実数をそのまま返すので、

結果がなかなか合わなくて少し悩みました。

        abs(pher[0,s]-pher[1,s])<1.0

と記述を変更することにより、よいシミュレーション結果が得られることとなりました。

(比較対象が 1e-9 から 1.0 に変更してあります。)

 

とりあえず、ここまで。

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な群知能のシミュレーションを実行してみたいと思います。

 

これは、オーム社「機械学習と深層学習(C言語によるシミュレーション)」(小高知宏著)にて説明されている、蟻コロニー最適化法のプログラムを、スクリプト言語 ツインテールdeエンジェルモード!! でも、作成してみようというものです。

 

そして、「ソース規模」「実行速度」の比較もしてみたいと思います。

(プログラム自体の説明については、本書をご覧ください。)

 

スクリプトの導入部分はこんな感じになります。(以下、インラインでコメント挿入)

 

>C言語のプリプロセッサ(マクロ)に相当する機能はないので、グローバル変数

>で似たようなことを実現してみました。

 

global GENMAX =50                    /* 試行回数        */
global NOA    =10                    /* 蟻の個体数        */
global STEP   =10                    /* 道程のステップ数    */
global Q      =3                    /* フェロモン更新の定数*/
global RHO    =0.8                    /* フェロモン蒸発の定数(ρ)        */
global EPSILON=0.15                    /* εーグリーディ法の確率(ε)        */
global SEED   =32767                /* 乱数のシード            */

 

次に、スクリプトのメイン処理はこんな感じになります。

 

>書籍と同じで、ε-greedy法による行動選択を基本としています。

>また、プロットの為に蟻の平均移動距離を AVE = %d と表示しています。

 

cost = {{1,1,1,1,1,1,1,1,1,1},        // 各ステップのコスト(距離)
        {5,5,5,5,5,5,5,5,5,5}}
pher = {{0,0,0,0,0,0,0,0,0,0},        // 各ステップのフェロモン量
        {0,0,0,0,0,0,0,0,0,0}}

/* メインループ */
srand(SEED)
loop( i<GENMAX ){                    // 全ての試行回数について...
/* >ループ回数&フェロモン状態の表示 */
    print ("%d:\n",i);
    printp(pher);
/* >蟻を歩かせる */
    mstep = walk(pher);
/* >フェロモンの更新 */
    update(cost,pher,mstep);
}
exit(0);

 

メイン処理で使用している関数ですが、それぞれこんな感じです。

まずは、表示関数。

 

def printmstep(mstep){
    print("*mstep\n")
    loop(i<NOA){                    // 全ての蟻について...
        loop(j<STEP)
            print("%d ",mstep[i,j])
        print("\n")
    }
}

def printp(pher){
    loop( i<2 ){                    // 全ての経路について...
        loop( j<STEP )
            print("%4.2f ",pher[i,j])
        print("\n")
    }
}

 

つづいて、アリを動かす関数。


def walk(pher){
    loop( m<NOA ){                    // 全ての蟻について...
        loop( s<STEP ){
        /* >εーグリーディ法による行動選択 */
            if( urand()<EPSILON || abs(pher[0,s]-pher[1,s])<1.0 ){    // ランダムに行動
                mstep[m,s] = rand(2)
            }
            else{                            // フェロモン濃度により選択
                if( pher[0,s]>pher[1,s] )
                    mstep[m,s] = 0
                else
                    mstep[m,s] = 1
            }
        }
    }
    printmstep(mstep)
    retn(mstep)
}

 

最後に、道(ステップ)上のフェロモンをアップデートする関数です。

 

def update(cost,pher,mstep){
/* フェロモンの蒸発 */
    loop( i<2 )                        // 全ての経路について...
        loop( j<STEP )
            pher[i,j] *= RHO
/* 蟻によるフェロモンの上塗り */
    sum_lm=0.0
    loop( m<NOA ){                    // 全ての蟻について...
        lm=0
        loop( i<STEP )
            lm += cost[ mstep[m,i],i ]
        loop( i<STEP )
            pher[ mstep[m,i],i ] += Q*(1.0/lm)
        sum_lm += lm
    }
    print("AVE = %f\n",sum_lm/NOA)
}

 

二次元配列の書き方が、C言語では a[i][j] となるのですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、 a[i,j] となるので、

少し注意が必要です。(もちろん、配列の中に配列を入れることができるので、

a[i][j] といった記述も可能ですが、 a[i,j] とは別の要素となります。)

 

さて、ソース規模(ソース行数)の比較ですが、

書籍では、187行(C言語)ですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、

 

% wc -l ant.tt
90 ant.tt

90行で済みました。あと、数字では表しにくいのですが、プログラムの記述のしやすさ

も、スクリプト言語 ツインテールdeエンジェルモード!! の方が、かなりお気楽でした。

 

次に、実行時間の比較です。 まずはC言語から、

 

% time a.out > /dev/null

real    0m0.001s
user    0m0.001s
sys    0m0.000s

 

さすが、C言語ですね。ほとんど時間が掛かっていません。

次は、スクリプト言語 ツインテールdeエンジェルモード!! です。

 

まずは、普通にコンパイルしたバージョンです。

 

% time tt ant.tt > /dev/null

real    0m0.130s
user    0m0.124s
sys    0m0.006s

 

次は、最適化オプション( -O3 )付きでコンパイルしたバージョンです。

 

% time tt ant.tt > /dev/null

real    0m0.115s
user    0m0.106s
sys    0m0.009s

 

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単なAffineレイヤーSoftmax_With_Lossレイヤーを実装してみたいと思います。

 

これは、O'REILLY社「ゼロから作る Deep Learning」(斎藤康毅著)にて説明されているものを、スクリプト言語 ツインテールdeエンジェルモード!! でも、トライしてみようというものです。(詳細な内容は書籍をご参照下さい。)

 

ここでのポイントは、双方向とするために静的変数を利用するというところです。

 

早速スクリプトですが、こんな感じとなりました。

まずは、Affineレイヤーです。

関数名ですが、双方向を表すために数字の 2 をつけてみました。

 

def affine2(dir,v1,v2,v3){    # Affine関数
static sWT                    # 配列処理専用&双方向対応バージョン。
    if(dir=='F'){            # 順方向
        x = v1
        W = v2; sWT = trans( W )
        b = v3
        ans = madd( mdot(x,W) , b )
    }
    else{                    # 逆方向
        dout = v1
        dx = mdot(dout,sWT)
    }
    retn(ans)
}

 

次に、Softmax_With_Lossレイヤーです。

関数名ですが、こちらも双方向を表すために数字の 2 をつけてみました。

 

def softmaxwloss2(dir,v1,v2){    # SoftMax_With_Loss関数
static sy,st                # 配列処理専用&双方向対応バージョン。
    if(dir=='F'){            # 順方向
        sy = dup(v1)
        st = dup(v2)
        ans = ceerr(sy,st)
    }
    else{                    # 逆方向
        ans = msub(sy,dt)
    }
    retn(ans)
}

 

誤差逆伝播法の実装の時に利用してみたいと思います。

 

そういえば、ついでに、

双方向版のReLUレイヤーと双方向版のsigmoidレイヤーの関数名も、

(双方向を表すために)それぞれ数字の 2 を付加してみました。

 

とりあえず、ここまで。

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単なReLUレイヤーとsigmoidレイヤーを実装してみたいと思います。

 

これは、O'REILLY社「ゼロから作る Deep Learning」(斎藤康毅著)にて説明されているものを、スクリプト言語 ツインテールdeエンジェルモード!! でも、トライしてみようというものです。

 

これらの関数は以前にも、実装したことがあったのですが、今回は、バックプロパゲーションにも対応したものとして実装する予定です。

 

まずは、ReLUレイヤーから。入力値(又は出力値)を保持しておく必要があるので、

静的変数を使います。(なお、あまり使わないのと、コードがややこしくなるので、

スカラー処理の部分はなくしました。→ 配列処理だけの実装とします。)

 

スクリプトはこんな感じになります。引数 dir で方向を指定します。

 

def relu(dir,v){            # ReLU関数
static sx                    # 配列処理専用&双方向対応バージョン。
    if(dir=='F'){            # 順方向
        x = v
        each( k=keys(x) )
            ans[k] = sx[k] = x[k]>0 ? x[k]:0
    }
    else{                    # 逆方向
        dout = v
        each( k=keys(dout) )
            ans[k] = sx[k]>0 ? dout[k]:0
    }
    retn(ans)
}

静的変数で直近の入力値を保持しておいて、それを利用して逆方向伝播の

出力を計算する感じとなっています。

 

つづいて、sigmoidレイヤーです。こちらは、出力値を保持しておく必要があります。

同じく、静的変数を使います。

(なお、こちらについても、あまり使わないのと、コードがややこしくなるので、

スカラー処理の部分はなくしました。→ 配列処理だけの実装とします。)

 

スクリプトはこんな感じになります。引数 dir で方向を指定します。

 

def sigmoid(dir,v){            # Sigmoid関数
static sy                    # 配列処理専用&双方向対応バージョン。
    if(dir=='F'){            # 順方向
        x = v
        each( k=keys(x) )
            ans[k] = sy[k] = 1.0/(1.0+exp(-x[k]))
    }
    else{                    # 逆方向
        dout = v
        each( k=keys(dout) )
            ans[k] = dout[k]*sy[k]*(1.0-sy[k])
    }
    retn(ans)
}

 

最後にサンプルデータで、試してみます。

 

x={1.0,-0.5,-2.0,3.0}
x
y=relu('F',x)
y
z=relu('B',y)
z
y=sigmoid('F',x)
y
z=sigmoid('B',y)
z

こんな感じの計算を行った所、

 

x[0]=+1.000000,x[1]=-0.500000,x[2]=-2.000000,x[3]=+3.000000
y[0]=+1.000000,y[1]=0,y[2]=0,y[3]=+3.000000
z[0]=+1.000000,z[1]=0,z[2]=0,z[3]=+3.000000
y[0]=+0.731059,y[1]=+0.377541,y[2]=+0.119203,y[3]=+0.952574
z[0]=+0.143735,z[1]=+0.088723,z[2]=+0.012516,z[3]=+0.043034

 

と出力されました。

書籍のコードでも実行して比較してみましたが、合っているようです。

先程、スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な強化学習のシミュレーションを実行してみたのですが、それのプロットをしてみたいと思います。

 

まずは、結果の取得から。

リダイレクトを使って、こんな感じで取得します。

 

% tt qlearning.tt > log

 

内容の確認をしてみます。

 

% head log
0    73    34    46    30    23    4    3    67    49    25    21    92    18    98    
0    69    34    47    30    23    4    3    67    49    25    21    92    18    98    
0    69    32    47    30    28    4    3    67    49    25    21    92    18    98    
0    69    32    47    30    34    4    3    67    49    25    21    92    18    98    
0    67    32    47    31    34    4    3    67    49    25    21    92    18    98    
0    64    32    47    32    34    4    3    67    49    25    21    92    18    98    
0    62    32    48    32    34    4    3    67    49    25    21    92    18    98    
0    60    32    49    32    34    4    3    67    49    25    21    92    18    98    
0    59    32    50    32    34    4    3    67    49    25    21    92    18    98    
0    59    31    50    32    39    4    3    67    49    25    21    92    18    98    

 

プロットは gnuplot を利用してみます。

 

% gnuplot

    G N U P L O T
    Version 5.0 patchlevel 5    last modified 2016-10-02

    Copyright (C) 1986-1993, 1998, 2004, 2007-2016
    Thomas Williams, Colin Kelley and many others

    gnuplot home:     http://www.gnuplot.info
    faq, bugs, etc:   type "help FAQ"
    immediate help:   type "help"  (plot window: hit 'h')

Terminal type set to 'x11'
gnuplot> set grid
gnuplot> plot "log" u:1,"" u:2,"" u:3,"" u:4,"" u:5,"" u:6,"" u:7,"" u:8,"" u:9,"" u:10,"" u:11,"" u:12,"" u:13,"" u:14,"" u:15

実際に入力した部分は、緑のところだけです。

グラフ出力を添付します。

 

ノード14の報酬のみのパターンと、

ノード14と11の報酬のパターンと作ってみます。

 

前者は、そのままの log をプロットしたものですが、

後者は、コメントアウトした部分を有効にして log を出力してプロットしたものです。

 

どちらも、いい感じで Q-Learning が行われているようです。

 

ちょっと気になったので、パラメーターをいじってみたのですが、

 

学習率αを変えると、グラフの上がり方が変わるようです。

→ 小くすると、ゆっくり上がるようになる。

→ 大きくすると、はやく上がるようになる。

 

割引率γを変えると、各層のQ値の落ち方が変るようです。

→ 小くすると、落ち方が大きくなる。

→ 大きくすると、落ち方が少なくなる。

 

(なんとなく、名前の意味がわかりました。)

 

ε値を変えると、グラフが上るまでの速度が変わるようです。

→ 小くすると、上がるまで時間がかかるようになりました。

→ 大きくすると、上がるまでの時間がはやくなりました。

 

(このパラメーターは、難しいですね。極端な話、εをゼロにすると、

初期値のパターンが固定化されてしまいます。逆に、εを1にすると、

ランダムばかりが選ばれて、Q値の高いルートが優先的に選ばれる

ことがなくなります。→ これって、筋の悪いルートがいつまでも無駄な

学習を受けることになります。)

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な強化学習のシミュレーションを実行してみたいと思います。

 

これは、オーム社「機械学習と深層学習(C言語によるシミュレーション)」(小高知宏著)にて説明されている、迷路探索プログラムを、スクリプト言語 ツインテールdeエンジェルモード!! でも、作成してみようというものです。

 

そして、「ソース規模」「実行速度」の比較もしてみたいと思います。

(プログラム自体の説明については、本書をご覧ください。)

 

スクリプトの導入部分はこんな感じになります。(以下、インラインでコメント挿入)

 

>C言語のプリプロセッサ(マクロ)に相当する機能はないので、グローバル変数

>で似たようなことを実現してみました。

 

global GENMAX    =1000                /* 試行回数                            */
global NODENO    =15                    /* ノード数                            */
global ALPHA    =0.1                /* 学習係数(α)                    */
global GAMMA    =0.9                /* 割引率  (γ)                    */
global EPSILON    =0.3                /* εーグリーディ法の確率(ε)        */
global SEED        =32767                /* 乱数のシード                        */

 

次に、スクリプトのメイン処理はこんな感じになります。

 

>書籍と同じで、ε-greedy法による行動選択を基本としています。

>また、プロットの為に毎回Q値を表示しています。


/* 乱数シードの設定&Q値の初期化 */
srand(SEED)
for( i=0 ; i<NODENO ; i++ )
    qvalue[i] = rand(101)
printqvalue(qvalue)

/* メインループ */
for( i=0 ; i<GENMAX ; i++ ){    // 全ての試行回数について...
    /* >状態の初期化 */
    s = 0
    /* >行動の選択&Q値の更新 */
    for( t=0 ; t<3 ; t++ ){        // 全ての層について...
        s = selecta(s,qvalue)
        qvalue[s] = updateq(s,qvalue)
    }
    printqvalue(qvalue)
}

 

メイン処理で使用している関数ですが、それぞれこんな感じです。

C言語と違って、前方参照(関数本体の定義が後に)できるのは意外と楽です。


def selecta(olds,qvalue){
/* εーグリーディ法による行動選択 */
    if( urand()<EPSILON ){            // ランダムに行動
        if( rand(2)==0 ) s = 2*olds + 1
        else             s = 2*olds + 2
    }
    else{                            // Q値最大値を選択
        if( qvalue[olds*2+1]>qvalue[olds*2+2] ) s = 2*olds + 1
        else                                    s = 2*olds + 2
    }
/* リターン */
    retn(s)
}

 

>500の報酬部分は、とりあえずコメントアウトしています。

 

def updateq(s,qvalue){
/* 最下段 */
    if( s>6 ){
        if( s==14 )                    // 報酬の付与
            q = qvalue[s] + ALPHA*(1000-qvalue[s])
//        else if( s==11 )            // 報酬の付与
//            q = qvalue[s] + ALPHA*( 500-qvalue[s])
        else                        // 報酬無し
            q = qvalue[s]
    }
/* それ以外 */
    else{
        if( qvalue[2*s+1]>qvalue[2*s+2] ) qmax = qvalue[2*s+1]
        else                              qmax = qvalue[2*s+2]
        q = qvalue[s] + ALPHA*(GAMMA*qmax-qvalue[s])
    }
/* リターン */
    retn(q)
}

 

def printqvalue(qvalue){
    for( i=0 ; i<NODENO ; i++ )        // 全てのノード番号について...
        print("%d\t",qvalue[i])
    print("\n")
}

 

ソース規模(ソース行数)の比較ですが、

書籍では、171行(C言語)ですが、

スクリプト言語 ツインテールdeエンジェルモード!! では、

 

% wc -l qlearning.tt
70 qlearning.tt

70行で済みました。あと、数字では表しにくいのですが、プログラムの記述のしやすさ

も、スクリプト言語 ツインテールdeエンジェルモード!! の方が、かなりお気楽でした。

 

次に、実行時間の比較です。 まずはC言語から、

 

% time a.out > log

real    0m0.003s
user    0m0.003s
sys    0m0.000s

 

さすが、C言語ですね。ほとんど時間が掛かっていません。

次は、スクリプト言語 ツインテールdeエンジェルモード!! です。

 

まずは、普通にコンパイルしたバージョンです。

 

% time tt qlearning.tt > log

real    0m0.068s
user    0m0.058s
sys    0m0.010s

 

次は、最適化オプション( -O3 )付きでコンパイルしたバージョンです。

 

% time tt qlearning.tt > log

real    0m0.060s
user    0m0.050s
sys    0m0.010s
 

時間自体は一瞬なんですが、C言語と比べると約20倍以上の差が発生しています。

コンパイラー言語と、軽量スクリプト言語の差でしょうね。