2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CNNをFPGAでやってみた

Last updated at Posted at 2023-12-08

この記事は、EEIC Advent Calendar 2023 の記事として書かれています

はじめまして

 東京大学工学部電子情報・電気電子工学科(通称、EEIC)のものです。先日から始まったEEICアドベントカレンダーなるものが2枚目に突入したのですが、記事がスッカスカで寂しいうえに、学科のSlackでも「書け」という圧があったので、書きました。

 こういう記事とか今まで書いてこなかったので、文章下手かもですが、許してください。

畳み込みニューラルネットワークをFPGAに実装する

 EEICの「FPGAを用いたアルゴリズム実装」という学生実験でやったことについて書いていきます。私は、畳み込みニューラルネットワークで画像認識してみる、というのに挑戦しました。

 畳み込みニューラルネットワークの推論で行われる畳み込み演算や行列演算は、並列性が高いためハードウェア化との相性が良かろう、ということでやってみました。(あと、内部で多少バグっててもそれっぽい出力をしてくれればバレないので)

 モデルの学習はPytorchで行い、Verilogで推論のコードを書きました。高位合成とか知らないです。

 ソースコードはGithubにあります。物好きな人はどうぞ。

リソース足りない問題

 ニューラルネットをハードウェア実装しようと思った時に懸念したのが、リソース足りない問題です。仮に、パラメータが 32bit float で、数が 1,000,000 とすると、4MBのメモリが必要になってくるわけです。これLUT足りなくないか、ってなりました。1

 そんなわけで、

      1. モデルをシンプルにする
      2. 重み、活性値の2値化(バイナリニューラルネットワークというやつ)

 というのをやってみました。もはや「精度なんぞどうでもよろしい」という感じです。実際、出来上がったものの精度は MNISTの分類で 90% 程度という...。

バイナリニューラルネットについて簡単に

 バイナリニューラルネットワークはあまり馴染みのない話かもしれないので、簡単に説明させてください。BinaryNetという論文を参考にしました。

 超ざっくりいうと、重みと活性値を1と−1の2値にしようということです。これをするとメモリ量が大幅に削減できるのはもちろんのこと、1/ −1 同士の乗算結果もまた1/ −1なので、乗算器が単なる NXOR ゲートで済むというわけです。 

a b a × b
1 1 1
1 -1 -1
-1 1 -1
-1 -1 1

 なんともハードウェア化に優しいですね。

 では、どうやってパラメータを学習させていくのかという話ですが、実はパラメータは32bit float としてもっており、順伝播・逆伝播の際はそのパラメータを0を閾値として2値化したものを用いて計算し、パラメータの更新時のみ float の値をいじるというやり方になっています。

 それと、各層の活性値も2値化するため、活性化関数は符号関数を用います。

Sign(x) = \left\{
\begin{array}{}
1 & (x \geq 0) \\\
-1 & (x \lt 0)
\end{array}
\right.

 ただ、このままだと勾配が計算できないので、逆伝播時は、tanh 関数として勾配を計算してみました。2
 ついでに、入力値であるMNISTのグレースケール画像も2値化しました。

 Q. こんなことやって精度は落ちないの?

 A. 落ちます。

 誤解のないよう補足しておくと、上の論文で紹介されていたやり方ならあまり落ちないけども、私が実装したものはいろいろはしょりすぎて良くなかったみたいです。
 また後で、ちゃんとやります、後でね。

畳み込み演算の並列度はすごい

 モデルの学習が済んだら、今度はVerilogと格闘する時間です。意外とVerilog書くの面白いんですけどね、やっぱり大変です。

 畳み込み演算は、畳み込みフィルターの1つの値に対して、入力画像の各ピクセルとの乗算&出力への加算を並列的に行います。なんと、24×24=576並列 !  畳み込み演算が25クロックで終わります。

bcnn01.png


 行列演算も同じような感じで並列化しますが、パラメータが圧倒的に多いので、時間はまあまあかかります。

実装結果

 結局作ったモデルはこんな感じ

Layer output size parameter
入力層 28×28 -
畳み込み層 24×24 25
全結合層 128 73,856
出力層 10 1290

 Vivadoでの合成結果

周波数 LUT FF
100MHz 6400 4400

画像1枚あたり 15us くらいで推論できます。悪くないんじゃなかろうか...? 気が向いたら改良します。

そんなことより

 ブルアカのアニメが待ち遠しいです

  1. 当時は、分散RAMとブロックRAMの違いを全然知らず、全部LUTでやるもんだと思ってた。今思えば、余裕だった。

  2. BinaryNetの論文では、ハードtanhというものが使われてます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?