Raspberry Pi でエッジAI Simulinkでのリソースと性能のトレードオフ探索

## この記事について
一生懸命アルゴリズムを作ってみたけどハードウエアリソースの制約からデバイスに実装できないということはよく起こります。メモリが小さくなるような構成を工夫することは当然必要ですが、それでもリソースの制約に収まらない場合は、構成要素個別にリソース削減の調整をします。本記事では、Simulinkを開発プラットフォームとしたRaspberry PiへのAI搭載を例にとり、信号波形からの分類の際に良く用いられるLSTMネットワークのサイズを削減する手法について述べます。

## 題材
題材は、前回記事"Raspberry PiとSimulinkでエッジAI"と同じく、音声信号からの予知保全とします。省リソースになるような構成上の工夫としては以下のようなものがあります。
1. Wavelet変換で特徴量抽出 (LSTMネットワークの前処理)
2. 入力音声信号のサブサンプリング(データーの間引き)
3. Wavelet変換後のサブサンプリング(特徴量の間引き)

1については前回記事に述べたように、生のデータをそのまま扱うよりWavelet変換で抽出した特徴量をLSTMネットワークの入力とする方が、LSTMネットワークのサイズを大幅に削減でき、かつ分類精度も向上するという構成上の工夫になります。また2と3については、AIの精度と所要リソースのトレードオフ点を探るための試行錯誤となります。リソースを削れる分、わりとダイレクトに精度に影響します。試行錯誤は以下のような手順になるでしょう。

・シミュレーションでAIの精度を確認
・(可能なら)静的解析でメモリ消費リソースを確認 <- Embeded Coderのライセンスが必要
・実機実行して動的解析、およびサンプル時間内での処理完了を確認

一方本記事では、上記のようなトレードオフではなく、あまり精度を犠牲にせずにネットワークに必要なメモリサイズを削減する方法について述べます。

## ネットワークサイズの圧縮
ネットワーク圧縮の代表的な方法としては以下が良く用いられています。MATLABではこれらの調整が関数の形で提供されていて、専用ツールを援用したり、パラメータ指定のみで実施することができます。

https://jp.mathworks.com/help/deeplearning/quantization.html?s_tid=CRUX_lftnav

![reduction_network_size.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/ac92ca8e-5791-bf30-c737-563439f40183.png)

・プルーニング  　使われてない重みを枝刈り
・投影     　　LSTMネットワークの方法を変えて特定条件でのQを保存することで容量減
・量子化　ネットワークの重みについて、例えば32bit->8bとして重みデータそのものを削減

"蒸留"などの手法もありますが、本記事では、ワークフローがより体系化されている(=関数一発でリサイズ可)、"投影"について述べます。

## 圧縮のためのMATLABコード
Simulinkモデル、学習用データ、学習用MATLABコードは前回記事と同じく以下を参照

https://github.com/kureta125/raspi_air_compressor_wavelet_scattering

・Raspi_LSTM.slx  ハードウエア動作用　Simulinkモデル
・Raspi_LSTM_Sim.slx  シミュレーション用　Simulinkモデル
・AirCompressorDatadet_ALSA  学習用データ　(再度　録音したもの)
・nefProjectedFinetuned.mat  学習済みLSTMネットワーク
・waveletScatteringLSTM_projection_HW　学習・サイズ調整用MATLABライブスクリプト

このうち、ネットワークを投影してサイズを圧縮するのに使用したスクリプトはwaveletScatteringLSTM_projection_HW、その結果生成されたネットワークはnefProjectedFinetuned.matです。SimulinkモデルRaspi_LSTM.slxからはこの圧縮後のネットワークを呼び出しています。
![model_AI_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/b8b8540e-1e87-fd6a-6e13-19cff07d370c.png)

圧縮に用いるMATLAB関数は compressNetworkUsingProjection()　です。詳細は下記リンクに記載がありますが、

https://jp.mathworks.com/help/audio/ug/compress-machine-fault-recognition-neural-network-using-projection.html

使い方としては元々のネットワークとしてnetOriginalというネットワークとmbqTrainという学習データがあった場合に、まず主成分分析の関数neuronPCA() でネットワークの活性化を分析して、

npca = neuronPCA(netOriginal,mbqTrain);

以下のようにcompressNetworkUsingProjection関数を適用すると新しく投影された容量の削減されたネットワーク netProjected が生成されます。ReductionGoalはどの程度圧縮を掛けるかを指定するパラメータです。

netProjected = compressNetworkUsingProjection(netOriginal, npca, ReductionGoal)

今回の例では大体3分の1ぐらいに圧縮をしています。圧縮すると精度が落ちますが、この圧縮されたネットワークについて再学習をかけると、ほぼ精度は回復しています。
![reduction_by_projection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/65dcc682-be73-cafe-84a2-b6bd29bc5246.png)

なお投影に関しては以下のページに詳細な説明があります。

https://jp.mathworks.com/company/newsletters/articles/compressing-neural-networks-using-network-projection.html?s_tid=srchtitle_support_results_3_compressNetworkUsingProjection


## 開発の早い時期にリソースの検証まで
Raspberry PiでエッジAIを作る動機として、ラピッドプロトでコンセプト実証したいという方も多いと思いますが、開発の早い時期にリソースの消費まで大体あたりを付けておくことは重要だと思います。最悪トレードオフポイントが見つからず、コンセプトを修正する必要性が後から分かるというのはとても徒労感が大きいでしょう。その点、シミュレーションと実機動作を容易に切り替えられる環境は、とても便利です。