FPGAにニューラルネットワークを実装するプロセスを学ぶ本連載。第4回では、低価格FPGAである「Tang Nano 9K」ではこれまでうまくいかなかった文字認識AIの学習が可能になったので、その結果を紹介する。
前回は、5×5ドットの枠内で表現されたアルファベットの大文字である「D」「C」「J」「M」の4文字をPC上で学習させて、その結果をFPGAに渡して推論させるという手順になっていました。これは、「Tang Nano 9K」ではリソース不足で当初は学習までは無理だと考えていたためですが、いろいろといじっているうちに学習もできるようになってきました。今回は、その“いろいろといじった”結果をまとめて紹介します。
本連載を始める前は、「FPGAにニューラルネットワークを実装する」という話題でどうすれば連載を継続できるか、いろいろと試行錯誤していました。そもそも、Intel系の「Cyclone IV」で学習も推論もできていたので「ほぼ同スペックのTang Nano 9Kでも楽勝でしょ」って思っていたのですが、それがそうでもなく、推論は何とかなりましたが、学習でかなり苦戦しておりました。
そこで、Tang Nanoシリーズでもう少しスペックが上の「Tang Nano 20K」で試したところ問題なく学習は行えたので、ある程度連載が進めば、学習はTang Nano 20Kで行うという話にしようかと思っていました。しかしなんと、Tang Nano 9Kでも学習が可能ということが分かったここで皆さんにお伝えできるというわけです。
⇒連載「FPGAにニューラルネットワークを実装する」バックナンバーはこちら
では今回使用するコードを見てみましょう。以下のGitHubのリポジトリからソースコードの「neuro.v」を入手してください。
neuro.vの最大の目玉は、何といっても学習をFPGAで行うようにしたところです。前回は、PCで学習済みデータを生成し、ファイルとして出力したものをFPGAの開発言語であるVerilogのinclude文で取り込むという内容でした。今回は、そこをFPGA上に構築したロジックによって学習済みデータを生成します。
リスト1にあるように、ソースコードであるneuro.vの1〜3行目で今回のトピックを紹介しています。
1: // Commented out lines were erased. 2: // Noise during training was removed. 3: // Rewrote the LED matrix column process to one line.
コメントの1行目ですが、コメントアウトしたコードを削除しました。筆者は開発時に元のコードを残すためにコメントアウトします。ですが、記事を執筆する時点ではある程度ソースコードの行番号を確定したいので、このような措置をしました。
コメントの2行目ですが学習済みデータを生成する際にノイズの混入を試みたのですが、執筆時までに想定した結果を得られなかったのでこのコードは取りあえずコメントアウトしています。
コメントの3行目は、LEDマトリクスに5×5ドットの文字を映すための処理ですが、前回示したコードで5行を要していたのに対して、今回はそれを1行に書き換えました。
また、これらのコメントでは触れてないのですが、推論結果に対して再度推論してみるモードを追加しました。これもコメントにはないのですが、入力文字をランダムに設定できるモードを用意しました。
なお、リスト2に示す4行目以降のソースコード本文は、前回と同様にVerilogで記述しています。開発環境も同じです。詳しくは前回の記事をご参照ください。
4: module neuro (input rst,input clk,Abtn,Bbtn,input 5: [3:0]btn,output test1,test2,low,high,[7:0]col,[7:0]row); 5: reg signed [7:0]sum; 6: reg [24:0]counter,neuros; 7: reg signed[3:0] links[624:0]; 8: assign col = (counter[15:13]==0)*neuros[4:0]+(counter[15:13]==1)*neuros[9:5]+(counter[15:13]==2)*neuros[14:10]+(counter[15:13]==3)*neuros[19:15]+(counter[15:13]==4)*neuros[24:20]; 9: assign row = ~(1<<counter[15:13]); 10: wire [24:0]D=25'b0111010010100101001001111; 11: wire [24:0]C=25'b0011101001010000100011111; 12: wire [24:0]J=25'b1111000001000010000111110; 13: wire [24:0]M=25'b1000110001101011101110001; 14: wire [24:0]noise=counter; 15: always @(posedge(clk))counter = counter + 1; 16: 17: integer k,m; 18: always @(posedge(clk))begin 19: if (rst==0) neuros = (Abtn==0)? counter : 25'b0111010011100100001001110; 20: if (btn[0]==0)if (counter[22]==0) begin 21: for (k=0;k<25;k=k+1) begin 22: sum=0; 23: for (m=0;m<25;m=m+1) 24: sum = sum + ((neuros[m]==1)?links[(k<<4)+(k<<3)+k+m]:(-links[(k<<4)+(k<<3)+k+m])); 25: neuros[k]=(sum>0)?1:0; 26: end 27: end 28: end 29: 30: always @(posedge(counter[19]))if (rst==0) 31: for (k=0;k<25;k=k+1) 32: for(m=0;m<25;m=m+1) 33: links[k*25+m]<=((D[k]==D[m])?1:-1)+((J[k]==J[m])?1:-1)+((C[k]==C[m])?1:-1)+((M[k]==M[m])?1:-1); 34: // links[k*25+m]<=((D[k]==D[m])?1:-1)+((J[k]==J[m])?1:-1)+((C[k]==C[m])?1:-1)+((M[k]==M[m])?1:-1)+((noise[k]==noise[m])?1:-1); 35: endmodule
リスト2の6行目までは、前回と変わりませんので説明は割愛します。
7行目は、学習済みデータを格納するlinksの定義です。符号付きの4ビット長のレジスタを625個用意しています。
8行目ですが、コメントにも書きましたが5×5ドットの文字を、8×8のLEDマトリクスに表示させる処理をしています。マトリクスの左上から5×5のLEDを使って表示させます。
10〜14行目は学習させる5×5ドットの文字データ25ビットをwireで定義しています。レジスタ型で定義することもできますが、この値は定義後書き変わることはないのでwire型で定義しました。この方がFPGAのリソースの消耗が少ないのも選択の理由になっています。
17〜28行目までが推論のロジックを記述しているコードです。この部分は、前回紹介した推論コードがベースになっていますので、今回追加した部分のみの説明に留めます。
19行目は、Abtnボタンを押しながらリセットボタンを押すとランダムな入力文字が選択される内容になっています。何も押さずにリセットボタンを押すと、あらかじめ設定された入力文字になります。今回のコードでは“D”を2カ所ほどビット改変したものが入力文字となります。ランダムな入力文字が選択されると、クロック(27MHz)でカウントアップされる25ビットのcounterの値が入力文字となります。Abtnボタンの押下とリセットボタンを離すタイミングでほぼほぼランダムっぽいビット列の文字が設定されます。
なお、AbtnボタンはTang Nano 9Kの69番ピン、リセットボタンはTang Nano 9Kの4番ピンに接続されたタクトスイッチになります。
20〜27行目は、リセットボタンとは反対側のボタンを押すと実行されるコードです。ボタンが押され続けるとcounter[22]が立ち上がるたびに再推論が行われます。counter[22]の立ち上がりにしたのは、何度か推論を繰り返す度に文字が変化する様子をある程度目で追える速度を考慮したものです。counter[22]の立ち上がりがどのくらいの間隔かというと、連載「注目デバイスで組み込み開発をアップグレード」の第19回である「FPGA評価ボードに十字キーを付けてみた【後編】」で紹介したキーの反応速度の2倍くらいですかね。
なお、21〜25行目は推論のコードです。こちらの内容については前回の記事をご参照ください。
Copyright © ITmedia, Inc. All Rights Reserved.