Google Colaboratoryの機械学習でM5UnitVに動物フィギュアを認識させる

# はじめに
* 先日Kendryte K210を搭載したお手軽AIカメラのM5UnitVを約2,000円（[スイッチサイエンス](https://www.switch-science.com/catalog/6212/)）で購入し、色認識を使った自動追尾に成功した。
 * 「[M5UnitVの開発環境をセットアップする](https://qiita.com/Nabeshin/items/7d49ca51cfdb10b70014) 」
 * 「[M5UnitVのカメラ映像をシリアル通信でM5Stackの液晶に表示する](https://qiita.com/Nabeshin/items/9268dc88927123319549)」
 * 「[M5UnitVのカメラでスライムベスを自動追尾してみた](https://qiita.com/Nabeshin/items/7858ca7cf1edb58ca3ca)」
* しかし2021/1/18現在、[V-Training チュートリアル](https://docs.m5stack.com/#/en/related_documents/v-training)を利用した学習モデル作成サービスが使えない。。
 * 「次はいよいよ機械学習を使った物体認識やるぞ〜！」とやる気満々で試したが、サービストラブルで学習モデルの作成サービスが数週間ほど、ずっと使えない状況が続いている。
 * M5Stack Communityに[ISSUE](https://community.m5stack.com/topic/2666/v-training-service-is-active-now)を投稿したが、年末年始で広いオフィスに引っ越したそうで対応している暇なんて無さそう。
* 幸いK210はM5Stack社のチップではないので、何か別の手段がある筈だと考えて色々検索した所、からあげ先生のブログ「[M5StickVで超お手軽エッジAI画像認識](https://karaage.hatenadiary.jp/entry/2019/08/14/073000)」に別のやり方が示されていた。
* 結果的にこちらの方法で上手くいったが、2019年発売時に作成されたコードだと2021年現在ではライブラリの組み合わせ等で動かない部分があったため、最新版の情報としてここに記録しておく。
 * 基本的にM5StickVに置き換えても同じ。

## **(2021/1/19 追記)**
* **importエラーだったGrad-CAMについても実行できるようになりました！！**
 * 詳細は「[Creating an Image Classification Model for M5StickV by Transfer Learningのコピー](https://colab.research.google.com/drive/1UdQV5nQMQy1ckJP0qdS3kS7sW6sEQyzs#scrollTo=prOt_VofnYCo)」 の[6][7]に記載してあります。
 * 内容については本文中に記載。

![IMG_3368D.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/2271ad93-1275-7843-1ade-611f297d96a3.jpeg)

# V-Trainingの機械学習サービスが使用できない・・・
* M5Stack社が運用してくれている機械学習モデル作成サービスのV-Trainingが現在使用できない状態。
 * UnitVに学習データ作成用ファームを書き込み撮影した画像を学習させ、任意の物体を検出できるようにするまでの手順が以下のV-Trainingページに記載されている。
 * https://docs.m5stack.com/#/en/related_documents/v-training
 * 自分もこれに沿って作業を進め６種類の物体データを用意し、指定された形式のZipファイルにしてV-Trainingにアップロードする所までやってみた。

![スクリーンショット 2020-12-29 11.57.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/3dd8a7c5-5e06-a864-8cef-8b9b1c51b408.png)

![スクリーンショット 2020-12-29 11.50.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/31447fd0-c11d-2679-461c-14589d4dbb0d.png)

が、以下のエラーが解消されず、学習モデルが作成できない状況が続いている。。。

* [不具合①]：誰かが先にアップロードしたファイルの処理に失敗するとTaskが削除されずにスタックする。
 * その状態で別の人がファイルをアップロードしても、ずっとTask順番が回ってこない。。。
 * 何日かするとサーバ管理者の人が気付き、Taskをキャンセルしてくれると解消する。
* [不具合②]：解消された状態を狙ってファイルをアップロードしても、以下のエラーがメールで返され失敗する。

```
CONTENT: Unexpected Error Found During Triaining, 
A target array with shape (128, 6) was passed for an output of shape (None, 7) while using as loss categorical_crossentropy.
This loss expects targets to have the same shape as the output.
```

* 内容から想像すると、（スクリプトが間違っていて）アップロードしたディレクトリ（Class）の数以上のClassを検証しようとしてエラーになっている感じ。
 * クラス数を変更しても常に「ディレクトリ数 + 1」でエラーになるので、中の人にスクリプトを修正してもらう必要がありそう。
 * （もし上手くいってる方が居たら教えて下さい。）
 * という事で、[Communityへの問い合わせ中](https://community.m5stack.com/topic/2666/v-training-service-is-active-now)



# Google ColaboratoryのNotebookの方法を試してみる
* 参考文献
 * からあげ先生@karaage0703のブログ「[M5StickVで超お手軽エッジAI画像認識](https://karaage.hatenadiary.jp/entry/2019/08/14/073000)」に別のやり方が示されている。
 * 出典元はIAMASの小林先生 @kotobuki が公開しているGoogle ColaboratoryのNotebookとのこと。
* [Creating an Image Classification Model for M5StickV by Transfer Learningのコピー](https://colab.research.google.com/drive/1UdQV5nQMQy1ckJP0qdS3kS7sW6sEQyzs#scrollTo=prOt_VofnYCo) 
 * 上記NoteBookを自身のGoogle Driveにコピーさせて頂き、2021/1/17時点で実行エラーとなる部分を回避したものを公開しました。
 * 一応、これでK210用の学習モデル生成ができるようになり、M5UnitVで任意画像を認識できるようになりました！！
 * 貴重な情報を公開して頂き、感謝いたします。

以下、手順を示します。
開発環境（MaixPy等）のインストールについては先行する記事の内容でインストール済みとします。

## 学習データの作成
* 学習データの作成はM5UnitV, M5StickV用のスクリプトを使う。
 * [M5StackのV-Training](https://docs.m5stack.com/#/en/related_documents/v-training)のサイトから学習データ収集用のファームを入手し、MaixPyで実行する。

![スクリーンショット 2021-01-17 22.52.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/d4ad8e58-d4c8-ed25-903c-f1b47328860d.png)

* こんな感じで、パシャパシャと学習データを撮影する。
 * 今回は、アニマルシリーズと言うことで、自宅に飾ってあるシュライヒの動物フィギュアを使う。
 * 象、キリン、シマウマ、牛、ホシガメ（ホシガメはガチャガチャでゲットしたやつ）。
 * M5UnitVの右側のAボタンで撮影、左側のBボタンで次のClassに切替え。
 * １つのClassあたり、色々向きを変えて50枚ほど撮影する。

![IMG_3366.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/0f31055e-0d92-81ee-9b1a-3f00e3fb2fef.jpeg)

* 撮影状況については以下のように、MaixPyのシリアルターミナルに撮影中のClass番号、撮影枚数が出力される。

![スクリーンショット 2021-01-17 16.14.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/1acde1d0-f5b8-c9c7-1535-6ca82bbee621.png)

![IMG_3362.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/86a41f75-84ff-79a0-b220-29ad5f5d2508.jpeg)

![IMG_3363.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/f6f8d581-3365-bbe2-2c6e-72e65c475835.jpeg)

## アップロード用のdataset.zipの作成と配置
* 認識させたいものが全部撮影できたら、SDカードの`train`フォルダのみをzip形式で圧縮し、`dataset.zip`というファイル名にする。
 * `vaild`(`valid`のタイポ)フォルダも作られるが、今回V-Trainingを使わないため使用しない。

![スクリーンショット 2021-01-17 23.02.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/94d8b65e-349d-f18a-786a-446919cca85f.png)

* 次に`dataset.zip`を任意のアプリからURL指定でダウンロードできる場所にアップしておく。
 * 自分は、NoteBookで記載されていたお勧めのやり方に合わせ、自身のDropBoxにアップロードした。
 * `https://www.dropbox.com/s/8e04e5k395jaap4/dataset.zip?dl=1`
 * （しばらくファイルは残しておきますが、そのうち忘れて消すかもしれません。）

## Google Colaboratory上で学習モデルの作成
* 先ほど自分のGoogle Driveにコピーした「[Creating an Image Classification Model for M5StickV by Transfer Learningのコピー](https://colab.research.google.com/drive/1UdQV5nQMQy1ckJP0qdS3kS7sW6sEQyzs#scrollTo=prOt_VofnYCo)」 をGoogle Colaboratoryで開く。
 * JupyterLabo環境がブラウザ内で立ち上がるので、上から順に「コードエリア」にカーソルを合わせて`Shift + Enter`で実行する。
 * JupyterLaboについては、自分JetsonNano&JetPackで使っていたが、Google Colaboratory使えばいきなりクラウド上に環境が持てちゃう。
 * こうやって共有もできちゃう。素晴らしいね、これ。

![スクリーンショット 2021-01-17 23.15.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/9995f312-5842-354d-58ba-ba88cca7d01e.png)

## 独自の学習データを指定
* 上から順次実行して行き、ここまで来たら`dateset_url:`の部分に先ほど学習データをアップロードしたURLを貼り付ける。
 * 今回使用するNoteBookにおいて個別に編集する箇所は、ここだけで良いはず。

![スクリーンショット 2021-01-17 23.23.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/6c970f3f-d070-ea84-761f-7c0e1d9b3460.png)

* 後は、順に実行していくだけ。
 * 途中、学習結果が表示される。

![スクリーンショット 2021-01-19 21.29.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/6001e175-78e0-bc53-b11d-515de9516647.png)


![スクリーンショット 2021-01-17 16.07.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/895d2fff-6e8a-9ca8-6a1b-dc0d6ba1c5ac.png)

* 学習モデルの評価。
 * 良く分かってないけど、きれいに予測出来てるって事かしら？(逆にこんなに綺麗に区別できちゃうものなの？？)

![スクリーンショット 2021-01-17 16.07.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/ec6e3c1a-8450-7e00-f87d-4b9924bf0ee6.png)


## Grad-CAMによる学習状況の可視化(2021/1/19 追記)
* 2019年作成時のコードには、Grad-CAMという学習モデルの推論内容をヒートマップみたいな感じで可視化する手順が含まれている。
 * [日本一詳しくGrad-CAMとGuided Grad-CAMのソースコードを解説してみる(Keras実装)](https://qiita.com/kinziro/items/69f996065b4a658c42e8)
* 検出結果が芳しくない場合にどこが良くてどこが良くないかがわかる。
 * ~~が、この１年でパッケージ構成が変更されたらしく、この部分はimportエラーになってしまう。~~
 * ~~ちょっと調べたり、いじったりしてみたが、自分では解消できなかった。。。~~
 * ~~今の自分の実力では依存関係を説き解せるほど分かってないので、ここはパスする。~~
 * ~~モデル作成には支障はないため一旦スキップし、時間が出来たらリトライする。~~

![スクリーンショット 2021-01-17 16.08.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/a8e92bfa-e72a-f8ff-cb7a-affdfd8c6e0a.png)

* **(2021/1/19 追記)**
 * **この部分についても実行できるようになりました！！**
 * 詳細は「[Creating an Image Classification Model for M5StickV by Transfer Learningのコピー](https://colab.research.google.com/drive/1UdQV5nQMQy1ckJP0qdS3kS7sW6sEQyzs#scrollTo=prOt_VofnYCo)」 の[6][7]に記載してあります。
 * どこを見て推論結果が導出されてるかが一目瞭然でイケてますね！！

![スクリーンショット 2021-01-19 23.43.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/4f5fa85b-790b-75f8-9d14-c94d37b73ff7.png)



## 学習モデルのダウンロード
* 最後まで実行して行くと、最終的にK210向けのモデルに変換した結果のzipファイルがダウンロードできる。

![スクリーンショット 2021-01-17 16.11.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/df548965-3004-fb00-72f9-3d7825883b0d.png)

## DLしたファイルを解凍。
* 解凍すると、以下の３ファイルが得られる。
 * `boot.py`：M5UnitVの実行コード。
 * `labels.txt`：認識結果に対応づけるラベル
 * `model.kmodel`：K210用にコンバートされた学習モデル

![スクリーンショット 2021-01-17 16.16.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/4a00aee0-a785-5b2d-f5f4-aa6566add0c1.png)

## boot.pyの書き替え
* Maix Toolbox提供の`booy.py`のままだと、ちょいちょい`ValueError: object not in sequence`で停止する。
 * 原因は、未検出時にpmaxに`//./////`という未定義値?が取得されるため、その後の配列アクセスでExceptionが発生するため。
 * 検出できている場合、検出結果plistには0から1の間の値が入ってくるが、失敗する`//./////, //./////, //./////, ... `という値が取得されちゃうみたい。
 * これ、先ほどグラフ表示されてた各クラスに関する`Confusion Matrix`の値だな、きっと。
* なので値が0〜1の範囲にない場合は、配列アクセスしない様に回避コードを追記した所、うまく回避できた。
 * M5UnitVなので`lcd`のコードは消してあります。M5StickVで使う場合は元のコードを参照してください。 
* UnitVはlcdが無いため、検出した物体ラベルをシリアルコンソールと、キャプチャ画像上に表示するようにした。

```python:boot.py
# Original: https://iotdiary.blogspot.com/2019/07/maixpy-go-mobilenet-transfer-learning.html
# Slightly modified for M5UnitV
import sensor
import image
import KPU as kpu
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.set_hmirror(1)
sensor.run(1)
f = open('labels.txt', 'r')
labels = f.readlines()
f.close()
print(labels)
task = kpu.load("/sd/model.kmodel")
while(True):
    img = sensor.snapshot()
    
    # モデル入力用の画像サイズに変換
    img2 = img.resize(224, 224)
    # K210のRGB565 データを推論処理用のメモリブロック ​​RGB888 にコピー
    img2.pix_to_ai()
    # モデルの実行
    fmap = kpu.forward(task, img2)
    
    plist = fmap[:]
#    print("plist:" + str(plist) )
    pmax = max(plist)
#    print("pmax:" + str(pmax) )
#   Avoiding Exception for index of undef result.
    if pmax >=0 and pmax <=1:
        max_index = plist.index(pmax)
        img.draw_string(2, 10, "%s " % (labels[max_index].strip()), 20)
        print("DETECT:" + labels[max_index].strip())
    else:
        print("NOT DETECT")
a = kpu.deinit(task)
```

## `labels.txt`の書き換え
* `labels.txt`の中身はディレクトリ名になってる。これは検出したClassに対応するラベル名になっている。
 * こちらのファイルは、順番を変えずに任意の文字列に書き換えることが可能。
 * 自分の場合、`01`, `02`,, だと分からなかったので、`Elephant`, `Giraph`,,,に書き換えた。

# いよいよ、M5UnitVで物体検出
* 先ほどDLしたzipファイルの中身３ファイルをSDカードに入れて、M5UnitVを起動。
 * 検出した物体ラベルをシリアルコンソールと、キャプチャ画像上（かなり見にくい）で確認する事ができる。

 お〜！ゾウさん、認識できた！

![スクリーンショット 2021-01-17 17.11.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/c8c83b5e-25a0-3c44-580c-a82eb7aea25a.png)

 キリンも認識できる。

![スクリーンショット 2021-01-17 17.14.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/abd0f9b1-7ef0-58c3-c56f-d3bb55d298a8.png)

　ホシガメはなぜか正しく認識できない。。。。

![スクリーンショット 2021-01-17 17.16.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/4005f861-cad4-a597-ccd5-691d15e147f1.png)

う〜ん。
今の自分の実力ではなぜ認識できないのか、さっぱり分からない・・・・。

と思ったら、
つい最近（2021/1/8）に発売された「[からあげ先生のとにかく楽しいAI自作教室](https://karaage.hatenadiary.jp/entry/2020/12/11/073000)」に学習結果のグラフの見方が詳しく書かれてるじゃないですか！
過学習とか未学習とか、何となく用語の意味は知ってるけど、実際にこうやって手元で起こってる事象ベースで考えると理解が深まるじゃん。

って、↓ これか！！！！？

`validation_acc`

![スクリーンショット 2021-01-19 21.31.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283854/5548a645-dd66-f9ee-8c0a-5b146a5efd14.png)


ほうほう、「なぜホシガメが認識できないのか？」良い課題じゃないですか。
よく読み込んで解決してみよう。


# まとめ
* M5UnitVで物体認識ができた。
* 学習モデル作成については、V-Training以外の方法でK210の学習モデルを作成することができた。
 * 初めてGoogle Colaboratoryにも触れる事ができ、その便利さが理解でき、とても勉強になった。
 * 各実行ステップで何やってるのか、後でゆっくり理解してみようと思う。
* あとM5UnitV、楽しい。
 * こんな2000円くらいのボードで機械学習を使って遊べるってのが良い。
 * M5UnitVとM5Stackとを組み合わせた車体を使って物体の追尾は出来ているので、次は機械学習させたものを認識＆追尾させるのをやってみたい。(Yoloで認識する必要がある気がする)