3000円の液晶付きAIカメラでオフライン転移学習する #M5StickV

# はじめに
　液晶ディスプレイ・カメラ・マイク・深層学習用推論モジュールなどを搭載し、約3000円で入手できるAIカメラである [M5Stack](https://m5stack.com/)社の [M5StickV](https://www.switch-science.com/catalog/5700/) を利用して、デバイス単独で画像のリアルタイム学習・推論を行う拙作アプリ [Brownie](https://github.com/ksasao/brownie) の実装について説明します。

　下記が Brownie の動作の一例です。パー、グー、チョキの順に１回ずつ学習していくと、それぞれがだんだん識別できるように学習されていく様子が分かると思います。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Brownie Learn で、まっさらな状態から少しずつじゃんけんを学習していく様子です。数字が小さいほど自信ありです(登録時の画像の特徴ベクトルとの距離の2乗)。 <a href="https://t.co/GX1jR1mbJ8">https://t.co/GX1jR1mbJ8</a> <a href="https://twitter.com/hashtag/M5StickV?src=hash&amp;ref_src=twsrc%5Etfw">#M5StickV</a> <a href="https://t.co/nrvSSGpyHN">pic.twitter.com/nrvSSGpyHN</a></p>&mdash; ミクミンP/Kazuhiro Sasao (@ksasao) <a href="https://twitter.com/ksasao/status/1161978500091301893?ref_src=twsrc%5Etfw">August 15, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

## M5StickV について
M5StickV の主な特徴は以下の通りです。詳しくは[公式サイト](https://docs.m5stack.com/#/en/core/m5stickv)を参照してください。
### プロセッサ
[Cannan Inc.](https://canaan.io/)の [Kendryte K210](https://kendryte.com/) という SoC (System-on-a-chip)が採用されており、下記のようなもので構成されています。

- 64bit RISC-V プロセッサ × 2 (400MHz動作)
- ハードウェアによるFFT(高速フーリエ変換)
- 深層学習向けプロセッサ Neural Network Processor(KPU) (0.8TOPS)
- 8MiB 64bit幅 SRAM
- 16MB Flash

### 開発環境・言語
[SiPEED](https://www.sipeed.com/)が開発した RISC-V プロセッサ向け micropython 開発環境 [MaixPy](https://maixpy.sipeed.com/)を利用するのが簡単です。組み込み向けであり、NumPyなどは利用できませんが、最小限のコードで tiny YOLO v2 などが利用できるような API が[提供されています](https://bbs.sipeed.com/t/topic/683)。

# 動作のしくみ
　224x224 ピクセルの入力画像に対して、MobileNet v1 を特徴抽出器として利用し、画像から得られた 768次元の特徴ベクトルを K近傍法(k-Nearest Neighbor algorithm, k-NN)で分類しています。なお、今回は k=1 (最近傍法)としています。以下で簡単に説明していきます。

## 画像分類の大まかな仕組み
　深層学習を利用して画像に何が映っているかを判別(Classification)する仕組みは、おおむね下図のようになっています。
　入力された画像は、特徴抽出層(主に畳み込み層)によって、同じカテゴリ(クラス)であれば位置や明るさや形などが多少変化しても近い値となり、そうでない場合は異なる値となるような低次元の画像特徴量に変換されます。今回の場合は 224x224x3(RGB) = 150528次元の画像を 768次元の画像特徴量に変換しています。さまざまな画像でうまく動作するような都合の良い特徴抽出層は簡単には作れないように思われますが、すでに非常に大量の画像を利用して学習したものが多く公開されています。今回はそれを利用しています。 ![画像認識のしくみ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43450/a126f664-6d93-bb4f-60cb-0bc3bfb566f1.png)
　自分で独自の画像分類を行いたい場合、上記の特徴抽出器の部分は変更せずにそのまま使いまわし、特徴抽出器の出力を何らかの方法で分類することが良く行われています。このようにある領域(ドメイン)で学習したものを他に転用する技術を転移学習と呼びます。
　ニューラルネットワークのみで転移学習を行う場合は、上図のように特徴抽出層に全結合層などを追加し、追加した部分のみ学習を行うことが多いです。このような用途で利用するために、例えば TensorFlow Hub には、特徴抽出層のみを含み、全結合層などを含まないさまざまな Headless Model が[提供されています](https://tfhub.dev/s?module-type=image-feature-vector)。
　M5Stackが提供している無料のモデル作成サービス [V-training](http://v-training.m5stack.com/) でも、識別したい種類の数(クラス数)に応じて、全結合層などを適宜追加し、その部分のみ再学習を行っています。この計算は、GPUなどが使えれば比較的短時間で行うことが可能ですが、M5StickV 単体で行うことは困難なので別な手を考えます。

## M5StickV上で転移学習
　左側に一般的に行われている機械学習の模式図を示します。●, ■, ▲が学習時の画像から得た特徴量です。それぞれをうまく分離でき、新しいデータがやってきてもきちんと分類できるようなできるだけもっともらしい境界線を引くことができれば、うまく学習したといえます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43450/457bdfdd-4650-e909-18c0-599945af1f59.png)

　このような境界線を引くのはなかなか大変なので、今回は、右側のようにあらかじめ学習した画像特徴量とカメラ画像から新たに得た画像特徴量との距離を計算し、最も近いものを探すという実装をしています。このような方法は[k近傍法](https://ja.wikipedia.org/wiki/K近傍法)のうち最近傍法と呼ばれています。

2つの画像特徴量 $\boldsymbol{a}$, $\boldsymbol{b}$ 間の距離 dist (単純にユークリッド距離を利用しました)を求めるのは簡単で、下記のようなコードになります。

```py
    dist = 0
    for i in range(768):
       dist = dist + (a[i]-b[i])**2
```
　これをあらかじめ取得した画像特徴量の数だけ繰り返し、最も値が小さいものを探せばよいことになります。なお、ユークリッド距離をもとめるには、最後に平方根をとる必要がありますが、今回は距離の大小関係のみわかれば十分なので省略しています。

　さまざまな画像について上記の計算を行ってみると、今回の構成では dist < 200 の時に対象が映っているとみなすと概ねうまく動きました。一方、全く異なるような対象を撮影しても dist は 500 程度とそれほど大きくは変化しません。

## ２枚の画像を使って量を求める
　2枚の類似した画像から得た画像特徴量もまたそれなりに似たものとなっているはずです。これを利用して、対象までの距離のようなものを測ることができます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">転移学習で対象までの距離のようなものが測れるような気がしたので試したらできた <a href="https://twitter.com/hashtag/M5StickV?src=hash&amp;ref_src=twsrc%5Etfw">#M5StickV</a> <a href="https://t.co/Vedtk5gy0b">pic.twitter.com/Vedtk5gy0b</a></p>&mdash; ミクミンP/Kazuhiro Sasao (@ksasao) <a href="https://twitter.com/ksasao/status/1185909464471232512?ref_src=twsrc%5Etfw">October 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

  カメラで撮影した画像より得た画像特徴量 $\boldsymbol{p}$ が、画像特徴量 $\boldsymbol{a}$ に一致するとき 0, 画像特徴量 $\boldsymbol{b}$ に一致するとき 1 となるような関数を考えます。$\boldsymbol{p}$ は $\boldsymbol{a}$ と $\boldsymbol{b}$ を通る直線上にあるとは限らないので、$\boldsymbol{p}$ を含み、$\vec{ab}$ を法線ベクトルとするような超平面を考えます。また、$\boldsymbol{a}$, $\boldsymbol{b}$ を通る直線とこの平面の交点を $\boldsymbol{x}$ と置きます。

そうすると、図の $t$ を求めればよいことになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43450/006d3b31-be12-e176-e280-b28ff98e70a9.png)

768次元空間の超平面の法線ベクトルを $\boldsymbol{u} = \boldsymbol{b}-\boldsymbol{a}$ とすると、$\boldsymbol{p}$ を通る超平面の方程式は、

```math
\sum_{k=0}^{767} u_k(x_k-p_k)=0   \cdots ①
```
一方、$\boldsymbol{a}$ を通り、$\boldsymbol{u}$ と平行な直線の方程式は、

```math
\begin{pmatrix}
x_0 \\
x_1 \\
\vdots \\
x_{767}  
\end{pmatrix}
= t \begin{pmatrix}
u_0 \\
u_1 \\
\vdots \\
u_{767}  
\end{pmatrix}
+
\begin{pmatrix}
a_0 \\
a_1 \\
\vdots \\
a_{767}  
\end{pmatrix}
   \cdots ②
```
② を ① に代入して、$t$ について整理すると、

```math
\sum_{k=0}^{767} u_k(t u_k+a_k-p_k)=0 \\
\Leftrightarrow
t\sum_{k=0}^{767} u_k^2 = \sum_{k=0}^{767} u_k(p_k-a_k) \\
\Leftrightarrow
t = \frac{\sum_{k=0}^{767} u_k(p_k-a_k)}{\sum_{k=0}^{767} u_k^2} \\
\Leftrightarrow
t = \frac{\sum_{k=0}^{767} (b_k-a_k)(p_k-a_k)}{\sum_{k=0}^{767} (b_k-a_k)^2} \\
```
となります。上記の動画のスレッドにも記載していますが、この方法で飲み物の残量をもとめたり、左右のどのあたりにいるかを求めたりといったことも可能です。なお、実際には線形とみなせないことも多いので、あくまで目安として使うとよいでしょう。

## 使いどころ
　今回の手法は、汎化性能はそれほど高くないものの、少ない教師画像(1枚から数枚)で動作するため気軽に利用することができます。例えば、[マグロとサーモンを区別](https://twitter.com/ksasao/status/1200707417132003328)したり、[素人を判別したり](https://twitter.com/ksasao/status/1201047666878119936)、[ドアの開閉状態を監視](https://twitter.com/ksasao/status/1190394991278882817)したり、[ずんだもちを監視したり](https://twitter.com/ksasao/status/1160532010856665089)といったことが可能です。一目で見て簡単に区別できる程度の違いがあればおおむね動作すると期待できます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">マグロとサーモンの違いがわかる Brownie <a href="https://twitter.com/hashtag/NT%E6%9C%AD%E5%B9%8C?src=hash&amp;ref_src=twsrc%5Etfw">#NT札幌</a> <a href="https://twitter.com/hashtag/M5StickV?src=hash&amp;ref_src=twsrc%5Etfw">#M5StickV</a> <a href="https://t.co/F5j1iIgJcc">pic.twitter.com/F5j1iIgJcc</a></p>&mdash; ミクミンP/Kazuhiro Sasao (@ksasao) <a href="https://twitter.com/ksasao/status/1200707417132003328?ref_src=twsrc%5Etfw">November 30, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

また、*exclude という文字列の書かれたQRコードをかざすことで、特定の対象を除外することもできます。

 現在人間が監視しているようなものを [Brownie](https://github.com/ksasao/brownie) で置き換えてみませんか？

## 関連資料
- <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/ksasao/20190928-m5stickv-tfug" title="20190928 M5StickVではじめる軽量モデルの実世界への応用 #TFUG" target="_blank">20190928 M5StickVではじめる軽量モデルの実世界への応用 #TFUG</a> </strong> from <strong><a href="https://www.slideshare.net/ksasao" target="_blank">Kazuhiro Sasao</a></strong> </div>