リスト3は、学習を行う関数とその関数で扱う配列を示しています。
2: int links[25*25]; 42: void learn2(void){ 43: int i,j; 44: for (i=0;i<25;i++) 45: for(j=0;j<25;j++) 46: links[i*25+j]= 47: +((D[i]==D[j])?1:-1) 48: +((J[i]==J[j])?1:-1) 49: +((C[i]==C[j])?1:-1) 50: +((M[i]==M[j])?1:-1); 51: }
2行目は、学習した重み付けを格納する配列linksです。625個の要素を持つ1次元配列になっています。25×25の2次元配列を用いた方がコンピュータで処理しやすいのですが、後でこのデータを持って行くFPGAでは何かと1次元配列の方が扱いやすいこともあり、あえて1次元配列にしています。
42〜51行までで示したlearn2関数は先に示した4つの文字“D”“C”“J”“M”を学習させる関数です。forループが2重の入れ子になった形の制御構造になっています。それぞれのループが25回ずつ実行されますので、46〜50行までのコードが25の二乗回なので625回実行されることになります。
さて、この関数で最も肝になる式(46〜50行)を見ていきましょう。それぞれの文字データ(25個要素の1次元配列)を総当たりでその相関関係を調べます。文字データのそれぞれのビットが一致すればプラス1、一致しない場合はマイナス1とし、それらの値の合計をlinksに格納していきます。
linksは625個の要素を持つ配列でしたよね。このコードではlinksは整数型の配列として宣言されています。この式を見ると、取り得る値は±4の範囲に収まることが分かります。ですので、3ビットと符号ビットの4ビットあれば十分収まるというわけです。コンピュータの場合、コンパイラにもよりますが整数型は32ビット長あるいは64ビット長のメモリが割り当てられます。
PCだからこそこのような大盤振る舞いが可能なのですが、筆者が使っているFPGAは、はるかに貧弱なハードウェアリソースしかありませんので、PC上で作成したアルゴリズムをFPGAに実装するには最小限のリソースで動作するように最適化する必要があります。
あと、添え字のiとjが一致する場合は計算を行わないコードをよく見ますが、今回提示したプログラムリストではそのような処理はあえて行っていません。本連載の最終目標は、FPGAでニューラルネットワークを動かすことです。コンピュータのプログラムは実行時間を最小にするために最適化しますが、今回のニューラルネットワークのモデルではFPGAに移行する際に全ての処理の並列化が可能です。
つまり、そういったことに気を取られている余地はないのです。確かに、時間とハードウェアリソースのトレードオフを考慮する局面もありますが、そのあたりについては連載の回が進めば言及していきたいと思います。
ここからは、learn2関数で学習したデータを基に手書き文字を推論させてみます。以下のような手書き文字のサンプルを使って推論させます(図3)。
# ## # # # ##
“J”のできそこないといった感じでしょうか。手書き文字といっても実際はリスト4のようにプログラム上で値を代入します。
64: void main(void){ 65: int input[25]={ 66: -1, 1,-1, 1, 1, 67: -1,-1,-1, 1,-1, 68: -1,-1,-1, 1,-1, 69: -1,-1,-1, 1,-1, 70: -1, 1, 1,-1,-1 71: }; 72: learn2(); 73: printf("example\n"); 74: show(input); 75: printf("recognition\n"); 76: hopfield(input,links); 77: show(input); 78: }
推論させたいサンプルの手書き文字について、main関数を用いて65〜71行にあるようにinputという25個の要素を持つ配列に値を設定します。ここで1になっているところを“#”で表現したものが、先に示した「推論させる手書き文字のサンプル」となります。
72行で先ほど説明した4文字を学習させるlearn2関数が呼ばれます。74行のshow関数で推論させたい手書き文字を表示させます。表示結果は上で示した「推論させる手書き文字のサンプル」になります。
75行で推論するためのhopfield関数を呼び出し、引数として推論させたい手書き文字のサンプルの配列を渡します。もう1つは、学習させる関数learn2で生成した学習済みデータであるlinksを渡します。
77行でhopfield関数によって推論した結果を表示します。その結果が図4で、覚え込ませた4文字のうち“J”と判断されたわけです。
##### # # # # ###
リスト5は推論関数であるhopfield関数のソースコードに当たる箇所です。
53: void hopfield(int inp[], int lin[]){ 54: int sum,i,j; 55: for (i=0;i<25;i++){ 56: sum = 0; 57: for(j=0;j<25;j++)sum=sum+(inp[j])*(lin[i*25+j]); 58: inp[i]=(sum>=0)?1:-1; 59: } 60: }
先にも触れましたが、この関数の引数であるinpには推論させたい手書き文字の配列が渡ります。また、この引数は推論結果を上位関数に渡すためにも用います。次の引数linには学習済みのデータが渡ってきます。
これも学習アルゴリズム同様2重の入れ子構造のループになっています。58行目の式は都合625回実行されることになります。入力文字の各ドットと学習データとの積を取り、sumに足し込んで行きます。その積和の結果を58行目の式で判断して、推論結果である5×5のドットの±1を決定します。
いかがでしたでしょうか。現在のAI(人工知能)技術を支えるニューラルネットワークも掘り下げてみれば、統計処理などでよく使われる相関計算に他ならないんですね。AIに抱く神秘性とは程遠い現実にお気付きになられたかと思います。AIもニューラルネットワークも深層学習も恐れるに足らずですね。
また、ソースコードを見てもう気付いた方もいらっしゃるでしょうが、学習も推論も並列化が可能なアルゴリズムです。それでこれらのアルゴリズムをFPGAに実装することに勝機を見いだしたというわけです。
次回はPCで学習した学習済みデータをFPGAに移すところから始めたいと思います。お楽しみに。
Copyright © ITmedia, Inc. All Rights Reserved.