(2)次に、りんご1個100円。個数2個。に加えて、みかん1個150円。個数3個。

税率1.1倍の場合の順方向&逆方向計算は、こんな感じのスクリプトになります。

 

ap =100        // リンゴ1個の値段
an =2        // リンゴの数
op =150        // みかん1個の値段
on =3        // みかんの数
tax=1.1        // 税倍率

a_layer = dup(&mullayer())        // リンゴ計算のレイヤ
o_layer = dup(&mullayer())        // みかん計算のレイヤ
f_layer = dup(&addlayer())        // 果物計算のレイヤ
t_layer = dup(&mullayer())        // 税金計算のレイヤ

### Forward ###
at = a_layer('F',ap,an )        // リンゴ全部の値段
ot = o_layer('F',op,on )        // みかん全部の値段
ft = f_layer('F',at,ot )        // 果物全部の値段
pr = t_layer('F',ft,tax)        // 最終価格
print("---Forward---\n%f\n",pr)

### Backward ###
dpr=1
(dft,dtax) = t_layer('B',dpr)    // {果物全部の値段、税倍率            }の微分
(dat,dot ) = f_layer('B',dft)    // {リンゴ全部の値段、みかん全部の値段}の微分
(dap,dan ) = a_layer('B',dat)    // {リンゴ1個の値段、リンゴの数}の微分
(dop,don ) = o_layer('B',dot)    // {みかん1個の値段、みかんの数}の微分
print("---Backward---\n%f\t%f\t%f\t%f\t%f\n",dap,dan,dop,don,dtax)

 

こちらも、黄色で表示した関数の複製の部分が、ポイントになります。

では、実行してみます。

 

% tt apple+orange
---Forward---
715.000000
---Backward---
2.200000    110.000000    3.300000    165.000000    650.000000

 

となりました。

つまり、合計の金額は(順方向計算より)715円。

それぞれの微分は(逆方向計算より)

リンゴの値段=2.2、リンゴの個数=110、

みかんの値段=3.3、みかんの個数=165、

税倍率=200。となりました。

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単な計算グラフを計算&実行してみたいと思います。

 

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

 

まずは、乗算レイヤですが、こんな感じとなります。

乗算レイヤでは、順方向出力結果を保持する必要があるので、静的変数

利用しています。ただし、気をつけないといけないのは、りんごの乗算と

税率の乗算では、別の状態を保持しなければならないということです。

なので、使用時には  dup() 関数を利用して関数の複製を行います。

 

def mullayer(direct,v1,v2){
static sx=0,sy=0
    if(direct=='F'){
        sx=v1
        sy=v2
        out=sx*sy
        retn(out)
    }
    else{
        dout=v1
        dx=sy*dout
        dy=sx*dout
        retn(dx,dy)
    }
}

 

一方で、加算レイヤは単純です。内部状態を持たないので、静的変数は必要

ありません。又、関数の複製も必要ありません。

(もちろん、複製して使用しても問題ありません。)

 

def addlayer(direct,v1,v2){
    if(direct=='F'){
        out=v1+v2
        retn(out)
    }
    else{
        dout=v1
        retn(dout,dout)
    }
}

 

こうやって関数を定義しておけば、

(1)りんご1個100円。個数2個。税率1.1倍の場合の順方向&逆方向

計算は、こんな感じのスクリプトになります。

 

ap =100        // リンゴ1個の値段
an =2        // リンゴの数
tax=1.1        // 税倍率

a_layer = dup(&mullayer())
t_layer = dup(&mullayer())

### Forward ###
at = a_layer('F',ap,an )        // リンゴ全部の値段
pr = t_layer('F',at,tax)        // 最終価格
print("---Forward---\n%f\n",pr)

### Backward ###
dpr=1
(dat,dtax) = t_layer('B',dpr)    // {リンゴ全部の値段、税倍率    }の微分
(dap,dan ) = a_layer('B',dat)    // {リンゴ1個の値段、リンゴの数}の微分
print("---Backward---\n%f\t%f\t%f\n",dap,dan,dtax)

黄色で表示した複製の部分が、ポイントになります。

では、実行してみます。

 

% tt apple
---Forward---
220.000000
---Backward---
2.200000    110.000000    200.000000

 

となりました。

つまり、合計の金額は(順方向計算より)220円。

それぞれの微分は(逆方向計算より)リンゴの値段=2.2、

リンゴの個数=110、税倍率=200。となりました。

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

 

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

 

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

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

 

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

 

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

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

 

global SETSIZE    =100            /* 学習データのセット数                */
global CNO        =10                /* 学習データの桁数(10社分)        */
global GENMAX    =10000            /* 試行回数                            */
global SEED        =32767            /* 乱数のシード                        */

/* 乱数シードの設定&データの読み込み */
srand(SEED)
(data,teacher)=readdata()

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

 

>書籍と同じで、単純にスコアを計算して、よりよい結果が得られたら、

>それを保持するようなループとなっています。

 

/* メインループ */
bestscore=0
for( i=0 ; i<GENMAX ; i++ ){    // 全ての試行回数について...
    /* >解候補の生成 */
    for ( j=0 ; j<CNO ; j++ )
            answer[j] = rand(3)
    /* >スコアの計算 */
    score = calcscore(data,teacher,answer)
    /* >最良値の更新 */
    if(bestscore<score){
        bestanswer = dup(answer)
        bestscore  = score
        for ( j=0 ; j<CNO ; j++ )
            print("%d ",bestanswer[j])
        print(":score=%d\n",bestscore)
    }
}

次に、個別の関数です。

まずは、スコアを計算する calcscore() 関数です。

 

>書籍では、パターンマッチの程度を表す変数名が point となっていて、

>すこし、理解に時間がかかりました。(スクリプトでも同じ名前を利用しています。)

 

def calcscore(data,teacher,answer){
    score=0
    for( i=0 ; i<SETSIZE ; i++ ){    // 全ての学習データのセットについて...
    /* >一致桁数の計算 */
        point=0;
        for( j=0 ; j<CNO ; j++ ){
            if  (answer[j]==2        )    point++;    /* WildCard    */
            elif(answer[j]==data[i,j])    point++;    /* 一致        */
        }
    /* >スコアの計算 */
        if  ( point==CNO && teacher[i]==1 )    score++;
        elif( point!=CNO && teacher[i]==0 )    score++;
    }
    retn(score);
}

そして、学習データと教師データを読み込む readdata() 関数です。

 

>読み込んだデータは文字(列)なので、整数化するのをわすれないように

>して下さい。(私は、これで15分程悩みました。)

 

def readdata(){
    for( i=0 ; i<SETSIZE ; i++ ){    // 全ての学習データのセットについて...
        line=gets()
        arry=psplit(line," ")
        for( j=0 ; j<CNO ; j++ )
            data[i,j]=int(arry[j])
        teacher[i]=int(arry[j])
    }
    retn(data,teacher)
}

では、実行してみます。

なお、最後の「最良解の表示」部分ですが、これは不要なのでカットしています。

あと、サンプルデータ ch2/ldata.txt は、オーム社のダウンロードサイトから

取得したものです。

(書籍では、アドレスが間違っているようなので、検索した方がよいようです。)

 

% tt learnstock.tt < ch2/ldata.txt
0 2 1 1 0 0 0 0 2 1 :score=74
1 0 0 0 1 1 1 1 0 2 :score=76
0 2 0 0 0 0 2 0 2 0 :score=77
0 2 0 2 2 1 1 1 2 2 :score=78
2 2 0 1 2 1 2 2 0 2 :score=84
2 2 0 1 2 2 2 2 0 2 :score=88

 

書籍とは、実行結果がやや異なりますが、これはOSとかの実行環境の違いに

よるものだと思います。(乱数のシードに固定値を設定している関係上、何度

やっても同じ答えが得られます。)

 

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

 

% time tt learnstock.tt < ch2/ldata.txt
0 2 1 1 0 0 0 0 2 1 :score=74
1 0 0 0 1 1 1 1 0 2 :score=76
0 2 0 0 0 0 2 0 2 0 :score=77
0 2 0 2 2 1 1 1 2 2 :score=78
2 2 0 1 2 1 2 2 0 2 :score=84
2 2 0 1 2 2 2 2 0 2 :score=88

real    0m22.920s
user    0m22.904s
sys    0m0.015s

約23秒程度かかりました。まぁ、1万回ループしてますので、こんなもんでしょう。

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

 

% time tt learnstock.tt < ch2/ldata.txt
0 2 1 1 0 0 0 0 2 1 :score=74
1 0 0 0 1 1 1 1 0 2 :score=76
0 2 0 0 0 0 2 0 2 0 :score=77
0 2 0 2 2 1 1 1 2 2 :score=78
2 2 0 1 2 1 2 2 0 2 :score=84
2 2 0 1 2 2 2 2 0 2 :score=88

real    0m17.291s
user    0m17.274s
sys    0m0.016s

17秒程で終了しました。 gccのオプチマイザー ( -O3 ) は凄いですね!!

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

 

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

(といっても、ダウンロードバージョンだとLinux環境では、表示が文字化けする

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

 

% time a.out < ch2/ldata.txt
0 2 1 1 0 0 0 0 2 1 :score=74
1 0 0 0 1 1 1 1 0 2 :score=76
0 2 0 0 0 0 2 0 2 0 :score=77
0 2 0 2 2 1 1 1 2 2 :score=78
2 2 0 1 2 1 2 2 0 2 :score=84
2 2 0 1 2 2 2 2 0 2 :score=88

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

はっ、速いです!! 一瞬で終わりました!!

通常バージョンとの比較で、約 0.3 %で終了しました!!

最適化バージョンとの比較でも、約 0.4 %で終了しています!!

 

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

ざっと、200倍以上の速度となります。

 

次は、ソース規模(ソース行数)の比較です。

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

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

 

% wc -l learnstock.tt
55 learnstock.tt

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

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

 

この辺りは、軽量言語の得意分野ですね!!

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

 

では!

スクリプト言語 ツインテールdeエンジェルモード!! で、簡単なニューラルネットワークのシミュレーションを実行してみたいと思います。(書籍でいうところの TwoLayer に相当します。)

 

具体的には、入力のXが28*28=784。 隠れ層は1層のみでノード数が10。 出力のYも10のシンプルなものです。

 

従って、重みはW1=784*10、W2=10*10、バイアスはb1=10、b2=10 となります。 あと、ハイパーパラメータとなる学習率は 0.1 に設定しておきます。

 

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

 

>シグモイド関数とか、損失関数とか、行列の演算関数とか、

>もろもろの関数を定義したスクリプトファイルを読み込んでおいて、

:r prog/deepfunc

 

>ノード数とかのパラメータを設定しておいて、
i_size=784                            # 入力ノード数
h_size=10                            # 隠れノード数
o_size=10                            # 出力ノード数
lr=0.1                                # 学習率

 

>重みを正規乱数(*)で、バイアスをオールゼロで、

>それぞれ初期値の設定をしておいて、

# 重み&バイアスの定義(初期値設定)
global W1 = ninit2d(i_size,h_size,0.01)
global b1 = zero  (h_size)
global W2 = ninit2d(h_size,o_size,0.01)
global b2 = zero  (o_size)

 

>2つの重みと2つのバイアス(合計4つ)を1つにまとめる変数 params と

>それらの勾配を入れる変数 grads を定義しておいて、

global params,grads

 

>実際にまとめます。
params["W1"]=W1
params["b1"]=b1
params["W2"]=W2
params["b2"]=b2

 

ここで、正規乱数についてですが、、、

平均が 0.00 となる実数の乱数です。(すなわち、正の値も負の値もとる。)

それでいて、出現率は 0.00 付近がおおくて、そこから離れるに従って

すくなるなります。

具体的には、その出現率が標準正規分布になるような乱数です。

(平均=0、標準偏差=1)

 

スクリプトでは prog/deepfunc の中で、こんな感じで定義しています。

 

def ninit2d(n,m,weight){
    if(!isdef(weitht)) weight=1.0
    loop(i<n)
        loop(j<m)
            a[i,j]=nrand()*weight
    retn(a)
}

もどって、重みとバイアスとかが global 変数として定義されている理由ですが、

これは、他の関数の中でそれらを使う可能性があるからです。

(もちろん、引数で渡してもいいとは思います。)

 

つづいて、

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

 

>入力データ x と、教師データ t を設定します。

まずは、一様乱数(*)で設定してみました。

x = uinit(784)
t = uinit( 10)

 

>実際の処理ループの本体です。以下、100回繰り返します。

>まずは、損失値を計算表示して、(変数名だけを書くと、値を表示してくれます。)

>そして、パラメーターの勾配 g を求めて、

>求めた、勾配に学習率(lr)を掛けて、元のパラメーターを更新します。

>each のループについては、後述します。

 

loop(i<100){
    L = loss(x,t)
    L
    g = gradall(&loss(),x,t,params)
    each( kywd=keys(params) ){
        each( indx=keys(params[kywd]) ){
            params[kywd][indx] -= lr*g[kywd][indx]
        }
    }
}

一様乱数ですが、0〜1までの間の実数乱数のことです。

出現率は0〜1まで一定です。(つまり、ランダムです。)

 

損失関数ですが、こんな感じです。

 

>入力データ x と教師データ t を受け取って、推論結果 y を出しておいて、

>結果 y と教師データ t から、交差エントロピー誤差を求めます。

 

def loss(x,t){
    y = predict(x)
    L = ceerr(y,t)
    retn(L)
}

 

推論を求める関数ですが、こんな感じです。

 

>入力データ x を受け取って、

>第1層では、重み W1 との積を計算して、バイアス b1 を加ます。

>その結果 a1 を、シグモイド関数に通して第1層の出力とします。

>出力層では、重み W2 との積を計算して、バイアス b2 を加ます。

>その結果 a2 を、softmax 関数に通して出力層の出力とします。

 

def predict(x){
    a1 = madd( mdot(x ,W1) , b1 )
    z1 = sigmoid( a1 )
    a2 = madd( mdot(z1,W2) , b2 )
    y  = softmax( a2 )
    retn(y)
}

勾配を求める関数ですが、こんな感じです。

 

>損失関数 f と入力データ x と教師データを受け取って、

>関数 f(x,t) の値 (x,t) におけるパラメータ params に対する数値勾配を求めます。

>一見複雑そうですが、パラメータの1つを微小増加させて計算した関数値 fp

>と微小減少させて計算した関数値 fn をもとめて、

>平均変化率を求めているだけです。 each のループについては、後述します。

 

def gradall(f,x,t){
    h=1e-4
    each( kywd=keys(params) ){
        each( indx=keys(params[kywd]) ){
            tmp=params[kywd][indx]
            params[kywd][indx]=tmp+h; fp=f(x,t,p)
            params[kywd][indx]=tmp-h; fn=f(x,t,p)
            grads [kywd][indx]=(fp-fn)/(2.0*h)
            params[kywd][indx]=tmp
        }
    }
    retn(grads)
}

 

上で2度出て来た each の2重ループですが、こんな形になっています。

これは、変数 params が、文字列 W1,b1,W2,b2 をインデックスとする

配列となっていて、その要素がまた別の配列になっているため、多少複雑な

形となっています。

 

    each( kywd=keys(params) ){
        each( indx=keys(params[kywd]) ){

            {何らかの処理}
        }
    }

 

まず、外側の each 文ですが、

params のキー(添字orインデックス値)を取って、

変数 kwyd に代入してますので、変数 kywd には、

最初 "W1" が代入されて、each 文の中が実行されます。

次に、"b1" が代入されて、 〃 。

次に、"W2" が代入されて、 〃 。

最後に、"b2" が代入されて、 〃 。

 

次に、内側の each 文ですが、

params[kywd] のキー(添字orインデックス値)を取りますので、

例えば、kywd="W1" の時には、

変数 indx には、 0,0 〜 783,9 までの値が次々に代入されて、

(内側の)each 文の中がそれぞれ実行されます。

 

説明が長くなってしまったので、実際に実行してみます。

 

% tt twolayer
+13.868130
+11.073620
+10.461002
+10.073896
+9.814911
...

 

とりあえず、順調に損失関数の値が減少しているのが確認できました。

 

実際には、まだまだ出力が続くのですが、

1回のループに非常に時間がかかります。

 

どうやら、数値微分を利用した勾配計算は、とても時間がかかるようです。

書籍にもその点についての指摘(注意)がありました。

 

実際のデータを利用した計算は、もっと高速な、

誤差逆伝播法を組み込んでから再チャレンジしてみたいと思います。

ツインテールdeエンジェルモード!! で、簡単なニューラルネットワークの勾配を算出してみたいと思います。(書籍でいうところの SimpleNet に相当します。)

すなわち、入力のXが2つ。隠れ層は無し。出力のYが3つのとてもシンプルなものです。重みもW=2X3のみで、バイアスは無しです。

 

ますは、NxM 行列を正規乱数で初期化する関数をこんな感じで書いておきます。

そして、ファイル prog/deepfunc に(いままでの他の関数と一緒に)まとめて

入れておきます。

 

### Deep Learning - 初期化関数の定義 ###
def init(n,m){          # 初期化関数( NxM 行列を正規乱数で初期化する。)
    loop(i<n)
        loop(j<m)
            a[i,j]=nrand()
    retn(a)
}

 

関数 nrand() が、正規乱数を発生する部分ですね。

では、推論関数と損失関数を定義しておきます。

 

% cat v1
:r prog/deepfunc

global W = init(2,3)

def predict(x){
    y = mdot(x,W)
    retn(y)
}

def loss(x,t){
    z = predict(x)
    y = softmax(z)
    loss = ceerr(y,t)
    retn(loss)
}

とはいえ、正規乱数を普通に使ってしまうと、書籍と値が異なってしまうので、

検証の意味を兼ねて、重みパラメーターの初期値は、書籍の値と同じにして

みます。

 

% cat v2
:r prog/deepfunc

#global W = init(2,3)
global W = {{ 0.47355232, 0.99773930, 0.84668094 },
                    { 0.85557411, 0.03563661, 0.69422093 }}

def predict(x){
    y = mdot(x,W)
    retn(y)
}

def loss(x,t){
    z = predict(x)
    y = softmax(z)
    loss = ceerr(y,t)
    retn(loss)
}

最初と異る部分は、重みパラメータの部分だけです。

では、この関数を使って推論と損失関数の値と勾配を求めてみます。

(簡単に、対話モードでやってみます。)

 

まずは、インタプリターを対話モードで立ち上げます。(デフォルトでOK)

% tt

 

先のファイル v2 を読み込みます。

tt> :r v2

 

重みパラメーターの値を確認します。

tt> W
W[1,0]=+0.855574,W[1,1]=+0.035637,W[1,2]=+0.694221,W[0,0]=+0.473552,W[0,1]=+0.997739,W[0,2]=+0.846681

 

入力データ X の値を設定します。

なお、 W X T の3つの値をグローバル変数にしている理由ですが、

勾配を求める時に、これらの値を参照するためです。

tt> global X = { 0.6 , 0.9 }
X[0]=+0.600000,X[1]=+0.900000

 

推論を実行します。

tt> p = predict(X)
p[0]=+1.054148,p[1]=+0.630717,p[2]=+1.132807

 

推論結果(一番高い確率のインデックス)を表示します。

tt> argmax(p)
2

 

教師データを設定します。ここでは、2を正解としてみます。
tt> global T = { 0 , 0 , 1 }
T[0]=0,T[1]=0,T[2]=1

 

損失関数の値を計算します。
tt> loss(X,T)
+0.928068

 

勾配を求めるために、ダミー関数 f(W) を定義します。

tt> def f(W){ retn(loss(X,T)) }

 

勾配を求めます。

tt> dW = ngrad(f,W)
dW[1,0]=+0.328871,dW[1,1]=+0.215344,dW[1,2]=-0.544215,dW[0,0]=+0.219248,dW[0,1]=+0.143562,dW[0,2]=-0.362810

 

結果を確認してみます。

とりあえず、いい値が出ているみたいです。

ツインテールdeエンジェルモード!! で、数値的に勾配を求める関数を記述してみたいと思います。

 

数値的に勾配を求めることを英語では numerical gradient というらしいので、

頭文字をとって ngrad() ぐらいの名前で作ってみます。

 

数値微分との違いは、対象となる関数が複数のパラメーターを持つということです。

第1引数に関数を、第2引数にパラメータの配列をとるものとします。

 

def ngrad(f,x){        # 関数 f(x[0],x[1],x[2], ... ) の x における勾配を求める。
    h=1e-4
    each( k=keys(x) ){
        tmp=x[k]
        x[k]=tmp+h; fp=f(x)
        x[k]=tmp-h; fn=f(x)
        grad[k]=(fp-fn)/(2.0*h)
        x[k]=tmp
    }
    retn(grad)
}

 

こんな感じとなりそうです。

では、関数 f(x,y) = x**2 + y**2 について、

(x,y)=(3,4) 及び (0,2) 及び (3.0) の点での勾配を求めてみたいと思います。

 

% cat v
def ngrad(f,x){        # 関数 f(x[0],x[1],x[2], ... ) の x における勾配を求める。
    h=1e-4
    each( k=keys(x) ){
        tmp=x[k]
        x[k]=tmp+h; fp=f(x)
        x[k]=tmp-h; fn=f(x)
        grad[k]=(fp-fn)/(2.0*h)
        x[k]=tmp
    }
    retn(grad)
}

def f2(x){ retn( x[0]**2.0 + x[1]**2.0 ) }

g = ngrad(f2,{3,4})
p(g)
g = ngrad(f2,{0,2})
p(g)
g = ngrad(f2,{3,0})
p(g)

 

では、実行してみます。

 

% tt v
g[0]=6.000000,g[1]=8.000000
g[0]=0.000000,g[1]=4.000000
g[0]=6.000000,g[1]=0.000000

 

となりました。

結果が解析値と一致していますので、これで良いようです。

 

次に、勾配降下法によって、先の関数の最小値を求めてみます。

スクリプトは、こんな感じです。

 

def gradd(f,x,lr,num){    # 勾配降下法により、関数 f(x[0],x[1],x[2], ... ) の最小値を求める。
                        #( x は初期値配列、lr は学習率、num は繰り返し数 )
    loop(i<num){
        grad = ngrad(f,x)
        x = msub(x,mmul(lr,grad))
    }
    retn(x)
}

 

では、初期値{-3,4}、学習率 0.1 、繰り返し 100 回で実行してみます。

 

% cat v
:r prog/matrix

def ngrad(f,x){            # 関数 f(x[0],x[1],x[2], ... ) の x における勾配を求める。
    h=1e-4
    each( k=keys(x) ){
        tmp=x[k]
        x[k]=tmp+h; fp=f(x)
        x[k]=tmp-h; fn=f(x)
        grad[k]=(fp-fn)/(2.0*h)
        x[k]=tmp
    }
    retn(grad)
}

def gradd(f,x,lr,num){    # 勾配降下法により、関数 f(x[0],x[1],x[2], ... ) の最小値を求める。
                        #( x は初期値配列、lr は学習率、num は繰り返し数 )
    loop(i<num){
        grad = ngrad(f,x)
        x = msub(x,mmul(lr,grad))
    }
    retn(x)
}

def f2(x){ retn( x[0]**2.0 + x[1]**2.0 ) }

x = gradd( f2 , {-3,4} , 0.1 , 100 )
p(x)

 

実行してみます。

 

% tt v
x[0]=-0.000000,x[1]=0.000000

 

原点に辿り着きました。うまく算出できているようです。

なお、学習率の設定がまずいと良い答えがでないそうなので、

その点も確認してみます。

 

まずは、学習率が 10 と大きすぎる場合。

以下、スクリプト v の出力 p() をコメントアウトしています。

 

% cat v2
:r v

x = gradd( f2 , {-3,4} , 10 , 100 )
print("%.10g %.10g\n",x[0],x[1])

 

% tt v2
-4.806621831e+15 6.409925701e+15

 

と、結果が発散してしまいました。

なお、 python3 での結果(書籍の値)と異なりますが、

書籍の値は 64 ビットでの計算値。 こちらは 128 ビットでの計算値。

となりますので、その違いが現れた可能性があります。

 

次は、学習率が 1e-10 と小さすぎる場合です。

 

% cat v3
:r v

x = gradd( f2 , {-3,4} , 1e-10 , 100 )
print("%.10g %.10g\n",x[0],x[1])

 

% tt v3
-2.99999994 3.99999992

 

こちらは、初期値からほとんど動いていません。

やはり、適切な学習率の設定が重要なようです。

 

とりあえず、ここまで。

ツインテールdeエンジェルモード!! で、数値微分関数を記述してみたいと思います。

 

数値微分を英語で書くと numerical differentiation となるらしいので、

頭文字をとって ndiff() ぐらいの名前で作ってみます。

 

これは、想像とは違って意外と記述が簡単です。

第1引数に関数を、第2引数に x の値をとるものとします。

 

def ndiff(f,x){
    h=1e-4
    retn( (f(x+h)-f(x-h))/(2.0*h) )
}

 

こんな感じとなりそうです。

では、関数 f(x) = 0.01 * x**2 + 0.1 * x について、

x=5 及び x=10 の点での数値微分を求めてみたいと思います。

 

% cat v
def ndiff(f,x){
    h=1e-4
    retn( (f(x+h)-f(x-h))/(2.0*h) )
}

def f(x){ retn( 0.01*x**2.0 + 0.1*x ) }

d = ndiff(f, 5)
p(d)

d = ndiff(f,10)
p(d)

では、実行してみます。

 

% tt v
0.200000
0.300000

となりました。

結果が解析値と一致していますので、これで良いようです。

ツインテールdeエンジェルモード!! で、損失関数を記述してみたいと思います。

(その2)

 

前回は、2乗和誤差を記述しましたので、

今回は、交差エントロピー誤差を記述してみます。

英語で書くと cross entropy error となるらしいので、

頭文字をとって ceerr() ぐらいで作ってみます。

 

% cat lossfunc
### Deep Learning - 損失関数の定義 ###

 

(途中略)


def ceerr(y,t){            # 交差エントロピー誤差 [ Cross Entropy Error ]
    if( type(y)=='A' ){
        ans = 0
        each( k=keys(y) )
            ans += -t[k]*log(y[k]+1e-7)
    }
    else
        ans = -t*log(y+1e-7)
    retn(ans)
}

 

こちらも、場合分けしているのは、

ベクトルだけでなくスカラーでも処理できるようにするためです。

 

2乗和誤差で使ったサンプルデータで計算&表示させてみます。

スクリプトの全体像はこんな感じです。

(先の損失関数を、ファイルで読み込んでいます。)

 

配列 t が教師データ。(one-hot 表現)

配列 y が推論出力。といったイメージです。

 

% cat v
:r lossfunc

t = { 0   , 0    , 1   , 0 , 0    , 0   , 0 , 0   , 0 , 0 }

y = { 0.1 , 0.05 , 0.6 , 0 , 0.05 , 0.1 , 0 , 0.1 , 0 , 0 }
loss = ceerr(y,t)
p(loss)

y = { 0.1 , 0.05 , 0.1 , 0 , 0.05 , 0.1 , 0 , 0.6 , 0 , 0 }
loss = ceerr(y,t)
p(loss)

 

では、実行させてみます。

前回の2乗和誤差と値は違いますが、

推論が正しいとき(最初の結果)が、出力が低くなり、

推論が正しくない時(二番目の結果)が、出力が高くなるという点は同じでした。

 

% tt v
0.510825
2.302584

 

交差エントロピー誤差でも、トレースモードで実行してみます。


% tt -t v
<File=v,Line=001> :r lossfunc
*** EOF ***
<File=v,Line=003> t = { 0   , 0    , 1   , 0 , 0    , 0   , 0 , 0   , 0 , 0 }
t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0
<File=v,Line=005> y = { 0.1 , 0.05 , 0.6 , 0 , 0.05 , 0.1 , 0 , 0.1 , 0 , 0 }
y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0
<File=v,Line=006> loss = ceerr(y,t)
Call: ceerr(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0,t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0)
<File=lossfunc,Line=015>     if( type(y)=='A' ){
Call: type(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0)
if(TRUE)
<File=lossfunc,Line=016>         ans = 0
ans=0
<File=lossfunc,Line=017>         each( k=keys(y) )
Call: keys(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0)
<File=lossfunc,Line=018>             ans += -t[k]*log(y[k]+1e-7)
Call: log(0.100000)
ans=0.000000
Call: log(0.050000)
ans=0.000000
Call: log(0.600000)
ans=0.510825
Call: log(0.000000)
ans=0.510825
Call: log(0.050000)
ans=0.510825
Call: log(0.100000)
ans=0.510825
Call: log(0.000000)
ans=0.510825
Call: log(0.100000)
ans=0.510825
Call: log(0.000000)
ans=0.510825
Call: log(0.000000)
ans=0.510825
<File=lossfunc,Line=022>     retn(ans)
loss=0.510825
<File=v,Line=007> p(loss)
Call: p(0.510825)
0.510825
<File=v,Line=009> y = { 0.1 , 0.05 , 0.1 , 0 , 0.05 , 0.1 , 0 , 0.6 , 0 , 0 }
y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0
<File=v,Line=010> loss = ceerr(y,t)
Call: ceerr(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0,t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0)
<File=lossfunc,Line=015>     if( type(y)=='A' ){
Call: type(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0)
if(TRUE)
<File=lossfunc,Line=016>         ans = 0
ans=0
<File=lossfunc,Line=017>         each( k=keys(y) )
Call: keys(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0)
<File=lossfunc,Line=018>             ans += -t[k]*log(y[k]+1e-7)
Call: log(0.100000)
ans=0.000000
Call: log(0.050000)
ans=0.000000
Call: log(0.100000)
ans=2.302584
Call: log(0.000000)
ans=2.302584
Call: log(0.050000)
ans=2.302584
Call: log(0.100000)
ans=2.302584
Call: log(0.000000)
ans=2.302584
Call: log(0.600000)
ans=2.302584
Call: log(0.000000)
ans=2.302584
Call: log(0.000000)
ans=2.302584
<File=lossfunc,Line=022>     retn(ans)
loss=2.302584
<File=v,Line=011> p(loss)
Call: p(2.302584)
2.302584

変数の変化や、関数の呼び出しなどが、追跡できました。

うまく動作しているみたいです。

ツインテールdeエンジェルモード!! で、損失関数を記述してみたいと思います。

 

まずは、2乗和誤差。英語で書くと mean squared error となるらしいので、

頭文字をとって mserr() ぐらいで作ってみます。

 

% cat lossfunc
### Deep Learning - 損失関数の定義 ###

def mserr(y,t){            # 2乗和誤差[ Mean Squared Error ]
    if( type(y)=='A' ){
        ans = 0
        each( k=keys(y) )
            ans += ((y[k]-t[k])**2.0)/2.0
    }
    else
        ans = ((y-t)**2.0)/2.0
    retn(ans)
}

 

場合分けしているのは、ベクトルだけでなくスカラーでも処理できるように

するためです。

 

サンプルデータで計算&表示させてみます。

スクリプトの全体像はこんな感じです。

(先の損失関数を、ファイルで読み込んでいます。)

 

配列 t が教師データ。(one-hot 表現)

配列 y が推論出力。といったイメージです。

 

% cat v
:r lossfunc

t = { 0   , 0    , 1   , 0 , 0    , 0   , 0 , 0   , 0 , 0 }

y = { 0.1 , 0.05 , 0.6 , 0 , 0.05 , 0.1 , 0 , 0.1 , 0 , 0 }
loss = mserr(y,t)
p(loss)

y = { 0.1 , 0.05 , 0.1 , 0 , 0.05 , 0.1 , 0 , 0.6 , 0 , 0 }
loss = mserr(y,t)
p(loss)

 

実行させてみます。

推論が正しいとき(最初の結果)が、出力が低くなり、

推論が正しくない時(二番目の結果)が、出力が高くなりました。

 

% tt v
0.097500
0.597500

 

もし、スクリプトが怪しいときは、トレースモードで試してみましょう。

オプション -t を付けるだけです。

 

% tt -t v
<File=v,Line=001> :r lossfunc
*** EOF ***
<File=v,Line=003> t = { 0   , 0    , 1   , 0 , 0    , 0   , 0 , 0   , 0 , 0 }
t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0
<File=v,Line=005> y = { 0.1 , 0.05 , 0.6 , 0 , 0.05 , 0.1 , 0 , 0.1 , 0 , 0 }
y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0
<File=v,Line=006> loss = mserr(y,t)
Call: mserr(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0,t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0)
<File=lossfunc,Line=004>     if( type(y)=='A' ){
Call: type(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0)
if(TRUE)
<File=lossfunc,Line=005>         ans = 0
ans=0
<File=lossfunc,Line=006>         each( k=keys(y) )
Call: keys(y[0]=0.100000,y[1]=0.050000,y[2]=0.600000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.100000,y[8]=0,y[9]=0)
<File=lossfunc,Line=007>             ans += ((y[k]-t[k])**2.0)/2.0
ans=0.005000
ans=0.006250
ans=0.086250
ans=0.086250
ans=0.087500
ans=0.092500
ans=0.092500
ans=0.097500
ans=0.097500
ans=0.097500
<File=lossfunc,Line=011>     retn(ans)
loss=0.097500
<File=v,Line=007> p(loss)
Call: p(0.097500)
0.097500
<File=v,Line=009> y = { 0.1 , 0.05 , 0.1 , 0 , 0.05 , 0.1 , 0 , 0.6 , 0 , 0 }
y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0
<File=v,Line=010> loss = mserr(y,t)
Call: mserr(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0,t[0]=0,t[1]=0,t[2]=1,t[3]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0)
<File=lossfunc,Line=004>     if( type(y)=='A' ){
Call: type(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0)
if(TRUE)
<File=lossfunc,Line=005>         ans = 0
ans=0
<File=lossfunc,Line=006>         each( k=keys(y) )
Call: keys(y[0]=0.100000,y[1]=0.050000,y[2]=0.100000,y[3]=0,y[4]=0.050000,y[5]=0.100000,y[6]=0,y[7]=0.600000,y[8]=0,y[9]=0)
<File=lossfunc,Line=007>             ans += ((y[k]-t[k])**2.0)/2.0
ans=0.005000
ans=0.006250
ans=0.411250
ans=0.411250
ans=0.412500
ans=0.417500
ans=0.417500
ans=0.597500
ans=0.597500
ans=0.597500
<File=lossfunc,Line=011>     retn(ans)
loss=0.597500
<File=v,Line=011> p(loss)
Call: p(0.597500)
0.597500

 

変数の変化や、関数の呼び出しなどが、追跡できます。

ツインテールdeエンジェルモード!! の、バージョンアップをしてみます。

 

まずは、ここのリンク(↓)から最新ソースファイルをダウンロードします。

https://osdn.net/projects/angelmode/downloads/67637/twintail_de_angelmode.costume175.tgz/

 

今日時点では、こすちゅーむ175が最新でした。

次に、TGZファイルを解凍します。

 

    % tar zxvf twintail_de_angelmode.costume175.tgz

 

そして、解凍してできたディレクトリに移動して、、、

 

    % cd twintail_de_angelmode

 

インタプリターをコンパイルします。

どこにも、なにも、インストールしないので安心してコマンドを打って下さい。

 

    % make

 

そして、インストールします。とはいえ、えらく単純です。

単に、コンパイルして出来上がった、インタプリター tt をコピーするだけです。

まぁ、このコピーしなくてもOKです。

 

% cp  tt  /usr/local/bin

 

では、サンプルプログラムを動作させて、インタプリターが正常にできていることを

確認してみます。

 

% tt DOCS/HelloMaster

こんにちは、ご主人様!!

こんにちは、ご主人様!!
こんにちは、ご主人様!!
こんにちは、ご主人様!!
こんにちは、ご主人様!!
こんにちは、ご主人様!!
こんにちは、ご主人様!!
こんにちは、ご主人様!!

 

(実際には、7色に表示されます。)

ついでに、バージョン番号(=こすちゅーむ番号)も確認してみます。

 

% tt -v

TwinTail_de_AngelMode!! Ver.175 ( 64[BIT] )
Copyright (C) 2011,2012,2013,2014,2015,2016,2017 "NekoMimi" <TwinTail@AngelMode.Net>

(これも、実際にはカラー表示されます。)

とりあえず、ここまで。