機械学習からディープラーニングまでをちゃんと理解して、FPGAに持ち込みたい

機械学習からディープラーニングまで、ちゃんと理解したい。

そう思いました。
思ったので、まず何をしたかというと、ChatGPTに聞きました。

ディープラーニングを理解したいのに、いきなりディープラーニングっぽいものに聞いている。

もうこの時点でだいぶ負けています。

しかし、現代人なので仕方ありません。
火を起こす前にライターを使うようなものです。

チャッピーに、
「機械学習からディープラーニングへの進化を追いながら学びたい」
と聞いたところ、なかなかそれっぽい学習計画を出してきました。
悔しいですが、そこそこ良さそうでした。ただし、そのまま鵜呑みにすると、
「AIを勉強するためにAIに聞いた内容をそのまま実行する人」になってしまいます。

それはちょっと癪です。

なので今回は、チャッピー案をベースにしつつ、FPGA屋さん目線で都合よく解釈し直していきます。
前提として、画像系の処理扱いたいです。

FPGA屋さんにとって、古典手法はまだまだ強い

FPGAなどの資源が限られたハードウェアを触っていると、しばしば思うことがあります。

古典的な手法、普通に強くない?

ソフトウェアの世界では「今ならライブラリ関数を呼べば終わり」と思われがちな処理でも、FPGAや組込みの世界では、昔からあるハードウェア向きの考え方が今でも普通に効きます。

たとえば、かなり分かりやすいのが CORDIC です。

sin、cos、atan、ベクトルの大きさや角度の計算などをしたいとき、ソフトウェアならだいたい数学関数を呼べば終わりです。

sin(x);
cos(x);
atan2(y, x);

便利です。

人類は甘やかされています。

しかしFPGAで同じことを考えると、急に話が変わります。

浮動小数点で三角関数をそのまま計算するのか。
LUTで持つのか。
補間するのか。
DSPを使うのか。
レイテンシは何クロック許せるのか。
固定小数点で足りるのか。

一気に現実が襲ってきます。

そこで出てくるのが武器の1つがCORDICです。

CORDICは、ざっくり言えば、三角関数や座標変換を シフトと加算を繰り返して近似する古典的なアルゴリズム です。

派手さはありません。

でも、FPGA目線ではかなりうれしいです。

乗算器をあまり使わずに済む。
固定小数点で組みやすい。
パイプライン化しやすい。
レイテンシを見積もりやすい。
リソース量も読みやすい。

つまり、ハードウェアに優しい。

最新AIの論文タイトルみたいな強さはありませんが、実装する側からするとかなり頼もしい存在です。

チャッピーに「三角関数を計算したいです」と聞いたら、たぶんPythonやNumPyの話を始めます。

でもFPGA屋さんはそこで、

「そのsin、本当にそのまま計算するんですか?」
「LUTですか?」
「CORDICですか?」
「固定小数点のbit幅は?」
「補正どうします?」
「パイプライン段数いくつにします?」

みたいなことを考え始めます。

めんどくさいですね。

でも、これがハードウェアです。

いきなり最新モデルに行くと、たぶん雰囲気だけになる

機械学習やディープラーニングを勉強しようとすると、つい最新のモデルを見たくなります。

Transformer。
Diffusion。
Vision Transformer。
なんとかNet。
なんとかGaussian。

名前が強い。

でも、いきなりそこへ行くと、

「なんか層がいっぱいあって、なんか学習して、なんか精度が出る」

という理解になりそうです。

それはそれで使うだけならよいのかもしれません。

でもFPGAに持っていきたい場合、それでは足りません。

FPGAでやりたいのは、

「このモデルすごいですね」

で終わることではなく、

「この演算はどこに置く?」
「このバッファは何ライン必要?」
「この重みはBRAMに乗る?」
「この中間特徴マップ、外部メモリに出した瞬間に負けでは?」
「INT8でいける? もっと削れる?」

みたいな話です。

つまり、モデルをありがたがるだけではなく、分解して、流して、削って、詰める必要があります。

しんどいですね。

でも、そこが楽しいところでもあります。

たぶん。

今回の学習方針

今回やりたいのは、

機械学習からディープラーニングへの進化を、流れで理解する

というものです。

完全に歴史を勉強したいわけではありません。

目的はあくまで、

FPGAや組込みシステムに、機械学習・ディープラーニングを賢く持ち込めるようになること

です。

なので、

「最新AIえぐしゅぎーー!」

で終わらずに、

「なぜ古典的特徴量が必要だったのか」
「なぜCNNが出てきたのか」
「なぜ量子化しても意外と動くのか」
「なぜエッジAIではメモリ帯域がつらいのか」
「なぜ最終的にFPGA屋さんはメモリの話を始めるのか」

あたりを、自分の中でつなげたいです。

Phase 1:古典画像処理と古典機械学習

まずは、人間が特徴量を頑張って設計していた時代を軽く触ります。

チャッピーもここは大事と言っていました。

AIが「古典も大事」と言っているので、たぶん大事です。実際ここで事足りるなら、それまででいいです。

このフェーズで見るのは、このあたりです。

画像処理:

  • Sobel
  • Gaussian Filter
  • Laplacian
  • Harris Corner
  • Optical Flow
  • Background Subtraction

特徴量:

  • HOG
  • SIFT
  • Census Transform
  • Haar-like Feature

古典機械学習:

  • 線形回帰
  • ロジスティック回帰
  • SVM
  • PCA

このあたりを見ていくと、人間がどれだけ頑張って特徴量を作っていたかが分かります。

エッジを見る。
勾配を見る。
局所パターンを見る。
明るさ変化を見る。
背景との差分を見る。

かなり泥臭いです。

でも、この泥臭さが大事だと思います。

なぜならFPGAもだいたい泥臭いからです。

きれいな数式より、結局は、

「この画素、前のラインに残ってる?」
「このwindow、1クロックで出せる?」
「このRAM、同時に何ポート読める?」
「このpragma、ほんまに効いてる?」

みたいな世界です。

このフェーズで大事なのは、アルゴリズム名を覚えることではなく、

なぜその特徴量が効くのか

を考えることだと思います。

ここを理解しておくと、あとでCNNを見たときに、

「ああ、畳み込み層が特徴量抽出器っぽく働いているのね」

という理解につながりやすいそうです(by チャッピー先生)。

Phase 2:NumPyでニューラルネットを書く

次は、いきなりPyTorchへ行かずに、NumPyで小さなニューラルネットを書いてみます。

正直、ここはちょっと面倒そうです。

面倒そうですが、たぶん避けると後でツケが来ます。

やることはこのあたりです。

  • 全結合ニューラルネット
  • ReLU
  • Softmax
  • 損失関数
  • SGD
  • 誤差逆伝播
  • 簡単なCNN
  • Pooling

特に理解したいのは、誤差逆伝播です。

ライブラリを使えば、学習自体はできます。

でも、

「なぜ重みが更新されるのか」
「どこから勾配が流れてくるのか」
「層を増やすと何が起きるのか」
「なんで急に学習が壊れるのか」

を知らないまま使うと、何かがうまくいかなかったときに全部お祈りになります。

お祈りAI開発。

嫌な響きですね。#include <stdio.h>をオマジナイで済ませる並にクソです。

なので、一度はNumPyで小さなニューラルネットを書いておきたいです。

本としては『ゼロから作る Deep Learning』あたりが良さそうです。

チャッピーもおすすめしていましたし、内容は忘れたけど昔読み切っています。

チャッピーにおすすめされた本を読む。
そしてそのチャッピーを理解するために勉強する。AIの奴隷ですね。

Phase 3:PyTorchで実験できるようになる

内部の雰囲気をつかんだら、次はPyTorchを使います。

ここで学ぶのは、ニューラルネットの理論そのものというより、

実験を回すための道具です。

具体的には、

  • Dataset
  • DataLoader
  • augmentation
  • GPU利用
  • mixed precision
  • 学習ログ
  • モデル保存
  • 推論コード

などです。

PyTorchは便利です。

たぶん便利すぎます。

便利すぎるので、何も分からなくても何か動きます。

これは良いことでもあり、怖いことでもあります。

先輩から引き継いだシステムと使い方で、動かせるけど中身が触れない。こんな状況が見え隠れしてますね。

動く。
でも分からない。

一番怖いやつです。

なので、PyTorchは便利な実験環境として使いつつ、中身を見失わないようにしたいです。

FPGAへ持っていく場合、最終的には、

  • どの演算が重いのか
  • どの層がメモリを食うのか
  • どこを量子化できるのか
  • どこをストリーム化できるのか
  • どこでバッファが必要になるのか

を考える必要があります。

PyTorchで精度が出ました。

めでたい。

では、それをFPGAに載せましょう。

ここから急に地獄が始まる可能性があります。

Phase 4:CNNをハードウェア視点で見る

ここからが本題です。

CNNを、単なるAIモデルとしてではなく、

巨大な画像処理パイプライン

として見てみます。

畳み込み層は、FPGA目線ではだいたいこう見えます。

line buffer
↓
sliding window
↓
multiply accumulate
↓
activation

見覚えがあります。

いつものやつです。

画像処理をFPGAでやると、だいたいline bufferとsliding windowが出てきます。

CNNも、見方を変えるとかなり近い構造に見えます。

もちろん実際のCNNはチャネル数が多く、重みも多く、中間特徴マップも多く、メモリ帯域もつらいです。

つまり、ただのSobelの親戚みたいな顔をしながら、急に大量のリソースを要求してきます。

でも基本構造としては、

局所領域を取り出す
→ 重みを掛ける
→ 足し合わせる
→ 非線形変換する

という処理です。

この見方ができると、CNNが一気にハードウェアの話になります。

一般的なAI入門では、CNNはテンソル演算として説明されることが多いです。

それはそれで正しいです。

ただしFPGAへ持っていくなら、

tensor

ではなく、

データの流れ

として見たいです。

どの順番で画素が来るのか。
どこにline bufferを置くのか。
windowを何個並列に出すのか。
MACをどの程度並べるのか。
重みはどこから読むのか。
中間特徴マップは保持するのか、流すのか。
外部メモリに出した瞬間に負けなのか。

このあたりを考えると、急にFPGAの話になります。

そしてだいたいメモリの話になります。

Phase 5:FPGA実装へ持ち込む

最後に、実際にFPGAへ持っていくことを考えます。

ここで重要になるテーマはこのあたりです。

  • fixed point
  • quantization
  • INT8推論
  • streaming inference
  • line buffer
  • systolic array
  • memory bandwidth
  • DMA
  • 外部メモリアクセス
  • レイテンシ
  • スループット

特に重要なのは、たぶんメモリ帯域です。

演算器をたくさん並べれば速くなる。

そう思っていた時期が私にもありました。

実際には、データが供給できなければ演算器は遊びます。

DSPが暇そうにしている。
BRAMが足りない。
DDRが詰まる。
AXIが渋滞する。
タイミングが閉じない。

つらい。

FPGAでは、

演算すること

よりも、

データをどう流すか

のほうが支配的になることが多いです。

たとえばカメラ入力から推論までを考えると、

camera
↓
ISP / 前処理
↓
line buffer
↓
conv
↓
activation
↓
pooling
↓
後段処理

のような流れになります。

ここで、全部をフレームバッファに落としてから処理するのか、それとも可能な限りストリーミングで流すのかで、必要なメモリもレイテンシも変わります。

古典手法 + 小規模NN はかなり良さそう

今のところ、個人的に一番おもしろそうだと思っているのは、

古典画像処理 + 小規模NN

の構成です。

たとえば、

前処理
↓
軽量な特徴抽出
↓
小さなニューラルネット
↓
判定

のような構成です。

全部を巨大なネットワークに任せるのではなく、前処理や特徴量抽出の一部を固定回路として作り、最後だけ学習器に任せる。

この形は、FPGAやエッジAIではかなり現実的なのではないかと考えています。

もちろん、用途によっては大きなモデルが必要になることもあります。

一般的にはモデルが大きいほど表現力は上がりますが、例外として、入力条件が限定されている組込み用途では、小さなモデルや古典手法の組み合わせで十分な場合もあります。

たとえば、

「決まったカメラ」
「決まった照明」
「決まった距離」
「決まった対象物」
「判定クラスも少ない」

みたいな条件なら、いきなり巨大モデルを持ち込まなくてもよい可能性があります。

むしろ、

ちゃんと前処理した小さいモデル

のほうが、軽くて、速くて、説明しやすいかもしれません。

この見極めができるようになりたいです。

「AIでやりました」

ではなく、

「ここは古典処理で削って、ここだけ学習器に任せました」

と言えるようになりたい。

そのほうが頭よさそうだもん。

まとめ

機械学習やディープラーニングを学ぶとき、いきなり最新モデルから入るのも悪くはありません。

ただ、自分の場合はFPGAや組込みシステムに持っていくことが目的なので、

古典画像処理
↓
古典機械学習
↓
NumPyによるニューラルネット実装
↓
PyTorch
↓
CNNのハードウェア理解
↓
FPGA実装

という順番で学ぶのが良さそうです。

この順番なら、

なぜその技術が必要になったのか

を追いながら理解できます。

そして最終的に、

「とりあえずAIを載せました」

ではなく、

「この処理は固定回路」
「この処理は学習器」
「この部分はストリーム」
「この部分はメモリに逃がす」
「ここは量子化しても許されそう」
「ここは削ったら怒られそう」

と判断できるようになりたいです。

チャッピーに聞いた学習計画を、チャッピーに支配されないために実行する。

何を言っているのか分からなくなってきました。

良きハッピーハードウェア開発を。

コメント