前回のチュートリアル編ではEdge Impulse の後追いをやりつつ、Appendix で参考ページを見ていましたが、今回はそれらをまとめて、通しで実践してみました。
全体の構成
Raspberry Pi 4 Model B を母艦として、Pico がセンサー(MPU 6050)から得たデータやTinyML モデルの推論結果の表示に使います。
Edge Impulse にアカウントを作成しておきます。
アカウントを作成したら、新しくプロジェクトも作成しておきます。
適当なプロジェクト名を入力して作成
Raspberry Pi 4 Model B に環境設定
OSイメージは、Raspberry Pi OS (64-bit)Desktop にします。
ここにPico のファームウェア作成(C/C++)用にPico SDK をインストールしておきます。
次に、ラズパイ4にArduino IDE をインストールしておきます。
Arduino IDE はメインの開発環境ではありません。センサーのデータをシリアルポートに流すプログラムを作るためだけに使います。他に手段があればそっちを使っても可。
また、Arduino IDE にPico のボード情報をインストールしておきます。
Arduino IDE でPi Pico Wの開発用にEarle Philhower版を使う
シリアルポートのデータをモニタするためにminicomもインストールしておきましょう。
Raspberry Pi 4 Model B , Raspberry Pi Pico とMPU 6050 でデータ収集の準備
Pico とラズパイの結線
SDA -> GP4
SDL -> GP5
Pico のモーションデータをシリアル経由で取得します
Aduino IDE に以下のコードを記述(Gyaroのデータはコメントアウト)して実行。
実行時にどこに保存するか聞いてくるので、適当にフォルダーを作って保存しておきます。
(例:~/Arduino/MPU6050-PICO)
ここではdelay()を入れて、送信を50Hz前後に調整しています。
動作確認はminicomを使ってみましょう。
このままではIDE がしょっちゅう必要になるので、ファームウェアにしてPico にインストールしておきます。
IDEのメニュ -> スケッチ -> コンパイルしたバイナリを出力
uf2ファイルが上記でコードを保存したフォルダーに保存されます。
PicoのBOOTSELボタンを押しながらMicroUSBケーブルでラズパイに接続。
Pico がストレージとして認識されてフォルダーが開くので、作成したuf2ファイルをドラッグ・ドロップします。Pico は再起動してコードが実行され、シリアルポートにデータが流されます。
どういうモーションにするか決めておく
こういう3つの動作と何もしない状態(Idle)の4つのモーションにしてみます。
センサーは常に地面に対して垂直にしておきます。センサーのちょっとした傾き状態もモーションのデータとして保存されてしまいます。
相互に異なる正確なデータであるために余計な要素は極力排除します。
ただ、Updown やLeftright とCircleのようなモーション は明確には区別しずらいということはそれとなく分かります。
これら以外のモーションについては、ページ最後尾のAppendix に追記しています。
ローカルで採取されているモーションデータをクラウドのEdge Impulse Studio に転送準備
Edge Impulse CLI をラズパイにインストールします。
これはローカルのラズパイ-Pico とクラウドの Edge Impulse Studio を接続するために使います。
● Node.js v14 以上をインストール
最新のRaspberry Pi OS (64-bit)の場合、Node はVersion 15 以上が必要なようです。
例えばバージョンが以下のように表示されます。
v15.14.0
/usr
もし/usr/local/ が返された場合は、次のコマンドを実行して npm のデフォルト ディレクトリを変更します。
● CLI toolsをインストール
データ転送にEdge Impulse Data forwarderを使用
Data forwarderは、シリアル経由で任意のデバイスから Edge Impulse にデータを簡単に中継するために使用されます。
デバイスはシリアル接続経由でセンサー値を書き込み、Data forwarderがデータを収集し、データに署名し、取り込みサービスにデータを送信します。
ターミナルで次を実行します。
minicom が実行中なら停止しておきます(Ctrl + A)。
フルパス
~/.npm-global/bin/edge-impulse-data-forwarder
アカウント作成時のユーザー名(or メールアドレス)とパスワードを聞いてきます。パスワードは入力時何も反映されません。
どのプロジェクトを使うか聞いてきた場合は、矢印キーで指定します。
3つの軸を何と呼ぶのか聞いてきます。適当にカンマで区切って名前を入力(例:x,y,z)。またデバイス名を何とするのか聞いてきます。ここも適当に(例:rpi-pico)。
注:この軸に与える名称は需要なようです。後でEdge Impulse Studio でモデルを再利用したりデータを追加しようとする場合、再接続で軸の名称を間違えるとデバイスを認識してくれないようです。
うまくいくと、あなたが作ったプロジェクトに紐づけられます。
表示されたURLを控えておきます。
上記のURLにアクセスして、Edge Impulse Studio にログインします。
データを採取
labelに上記の4つのモーション名(idle、updown、leftright、forwardback)を入れて、Start sampling をクリックし、それぞれのデータを採取します。
10秒(10000ms)間モーションを繰り返して採取します。目安は大体10回から15回くらい。
labelを入力ミスすると新しいモーションとみなされて追加されてしまいます。その場合は、選択して右端の3点アイコンをクリックし、Deleteで削除するか、Edit labelで修正します(Rename ではありません)。
適当なところでデータを見てみます。赤い三角のアイコンをクリック。
こんな感じでよさげな量サンプリングされると、データを学習用とテスト用に分割するよう促してきます。
分割します。
確認してくるので「perform split」と入力して分割実行。
無事分割されるとグリーンの円グラフに変わっています。
分割を実行しても1部のデータが赤い線の表示のままで分割されないことがあります。そのモーションのみサンプリングを追加しながらsplitしてください。ほどよい量になったら分割されます。
再度データ確認。こんな感じで、だいたい8:2くらいの割合で分割されています。
もう少しデータは採取したほうがいいのかもしれませんが、この辺りでデータの前処理とモデル作成に移行します。
Create impulse でパイプラインを作成
メニュのCreate impulse をクリック。左のメインメニュが出ていない場合はページ左上のバーガーアイコンをクリックすれば出てきます。
こんな風に見やすいメニュでprocessing block とlearning block を追加するように聞いてきます。
左の枠をクリックしてprocessing block 追加メニュを開きます。
なにも考えずにSpectral Analysisを追加。この環境ではFFTを使った変換をやっているようです…多分。
「加速度センサーからのデータなど、反復的な動きの分析に最適です。 時間の経過に伴う信号の周波数および電力特性を抽出します」……だそうです。
右のlearning block には2つ追加します。
なんでAnomaly Detection が必要なのか?
どうもデータの大きな外れ値を排除するのに使われるっぽい?実はよく分かっていません。ただ、これがあるとAccuracy は大幅に改善されます。
「新しいデータで外れ値を見つけます。 未知の状態を認識し、分類器を補完するのに適しています。 スペクトル特徴ブロックの出力などの低次元特徴で最もよく機能します」…….だそうです。
追加し終えたら、Classification のName は「NN Classifier」に変更して、Save impulseを実行。
名前の変更に深い意味はないと思います、どうも皆さんそうしているみたいなので……。
以上でパイプラインの作成は終了。
トレーニング
Save impulseを実行するとメニュが以下の様に追加されています。以降、順番に実行します。
全部やり終えないとStudioに文句を言われてDeployment にたどり着きません。終了するとグレーの丸がグリーンの丸に変わります。
まずはSpectral features です。
パラメータは変更したほうがいいのかもしれませんが、とりあえずデフォルトで実行。
explorerにデータがバラついて表示されているので、いい感じです。
分類(Neural Network Classifier)実行
Number of training cycles というのはエポックという名称で知られている数です。デフォルト30で実行してみます。
Accuracy が97%でした。悪くない数字ですが、エポックを上げながらもう少しやってみます。
なんとエポック50で100%になってしまいました。大丈夫か?と思ってしまいますが折角なのでこれを採用してみます。
最後に異常値検出です。これはどうもデータに存在するあり得なさそうな数値を排除するのにつかわれるのでしょうか?良くわかりませんが必要です。
以下のX,Y,Zの3つのRMSの項にチェックを入れて最終トレーニングを実行してモデルを作成します。
テスト
生成されたモデルで実際のデータを使って推論を実行してみます。
10秒間、モーション(updown , leftright , forwardback)をやって分類できているかテストします。
leftright を実行してみました。
結構な正確さで分類されています。
デプロイ用パッケージ作成
成果物をC++Liburary としてアーカイブしてローカルにダウンロードします。
オプションはデフォルトのままでビルドします。
終了。
ローカルPCのダウンロードフォルダーにZIPファイルが落とされています。
中身はこんな感じ。
必要なのは3つのフォルダーで、これをラズパイに移行します。
デプロイ実行
ラズパイに最初にPico SDK をインストールしたpico ディレクトリにプロジェクト用のフォルダーを作成します。
このフォルダーに必要なフォルダーやファイルをコピーしていきます。
最終的にはこんな構造になります(LICENSEやREADME.mdなどは端折っています)。
~/pico/
├── motion-multicore-inferencing-pico/
│ ├─ edge-impulse-sdk/
│ ├─ model-parameters/
│ ├─ tflite-model/
│ ├─ source/
│ ├─ build/
│ ├─ CMakeLists.txt
│ └─ pico_sdk_import.cmake
│
├── pico-sdk/
│ ├── cmake
まず、上記でダウンロードしたZIPファイルの中の3つのフォルダー(edge-impulse-sdk、model-parameters、 tflite-model)を~/pico/motion-multicore-inferencing-picoにコピーします。
次に、Multicore Pico Example repository をダウンロードするかクローンしておきます。
これはファームウェアのひな型のようなものです。
or
この中から以下のフォルダーとファイルを上で作成したmotion-multicore-inferencing-picoフォルダーにコピーしておきます。
sourceフォルダー
CMakeLists.txt
pico_sdk_import.cmake
次に3つのファイルを編集します。
● CMakeLists.txtを開いて、example_multicoreをmotion_multicore に一括置換(名前を合わせてるだけ)。
● source/accelerometer.cppを開いて以下の様に変更(最初の結線の形に合わせているだけ)。
#define I2C_INTERFACE i2c0
#define I2C_SDA_PIN 4
#define I2C_SCL_PIN 5
● source/ei_classifier_porting.cppを開いて以下のように2つの関数をコメントアウト(リンクエラーを防ぐため)。
/*
uint64_t ei_read_timer_ms()
{
return to_ms_since_boot(get_absolute_time());
}
uint64_t ei_read_timer_us()
{
return to_us_since_boot(get_absolute_time());
}
*/
実行用ファームウェアをビルドして作成します。
buildフォルダーにmotion_multicore.uf2が生成されているのを確認。
PicoのBOOTSELボタンを押しながらMicroUSBケーブルでラズパイに接続。
Pico がストレージとして認識されてフォルダーが開くので、作成したuf2ファイルをドラッグ・ドロップします。Pico は再起動してコードが実行されます。
推論実行
minicomを開いてシリアルポートをモニターします。
最初は何もしていない状態なので、idleに高い数値が出ています。
leftright の動作を実行すると、その動きは以下の様に推論されます。
この結果表示のコードは、source/main.cpp です。
注:source/core1_thread.cppにあるようにLEDは25pin にアサインされています。したがってPico ではLEDが点滅しますがPico W ではしません。W ではWL_GPIO0 を指定します。
Appendix
他のモーションについて
以下のようなモーションを試してみました。
1・
サンプリング数:各10秒を10回
Accuracy : 99.3 % (epoch 60)
この3つのモーションはかなり正確に認識されました。
2.
サンプリング数:各10秒を10回
Accuracy : 93.5 % (epoch 60)
これらのモーションについてはUpdown とCircle は区別されましたが、
Circle がどちらなのかはあいまいでした。つまり、サンプリングする場合、どちらもCircle としてデータを取っておいて問題ないかな……と思います。あるいは、サンプリング数を増やせば、circle-aとcircle-bはもう少しはっきりと区別されるかもしれません。
* サンプル数を増やすとAccuracy はやはり上がります。ただcircle-aとcircle-bだけ増やすとAccuracyはかえって下がります。やはりバランスが大事。全部を10s x 6増やすだけで、Accuracy は94.6% (epoch 50)でcircle-aとcircle-bも差別化がはっきりするようになりました。
Leave a Reply