【可視化】リアルタイムで処理、それがFaster-Grad-CAM

[前回](https://qiita.com/shinmura0/items/ba45b7b9da02a44ed1b1)は、深層距離学習にGrad-CAMを適用し、可視化する手法を紹介しました。

今回は、その論文を応用し、リアルタイムでGrad-CAMを処理できる
「Faster-Grad-CAM」を開発したので紹介します。

![heat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/f8318bdb-9ab4-cb62-7c92-522e74391cb1.gif)
（RaspberryPi3 modelBで実行）

※コードは[こちら](https://github.com/shinmura0/Faster-Grad-CAM)
※こちらは[Pythonデータ分析勉強会#18](https://pythondata.connpass.com/event/168046/)の発表資料です。

#結論から
+ Faster-Grad-CAMを使えば、通常のGrad-CAMより10倍以上高速化することが可能
+ しかも、[前回](https://qiita.com/shinmura0/items/ba45b7b9da02a44ed1b1)ご紹介した技術を使っているため、高精度化にも成功

#デモ
まずは試してみたいという方は、[こちら](https://github.com/shinmura0/Faster-Grad-CAM)にパーとグーを見分けるデモ機を作りました。
パーのときは反応せず、**グーにするとFaster-Grad-CAMが発動します。**

##環境
+ Keras 2.2.4
+ TensorFlow 1.9.0
+ sklearn 0.19.0
+ Opencv 3.4.3.18
+ RaspberryPi3 modelB(もしくはPC)

##使い方
カメラを準備し、以下のコマンドで実行できます。

```
python3 janken_demo.py
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">「Faster-Grad-CAM」で、CNNの注視部分の可視化。ラズパイ3で実行。 <a href="https://t.co/omQbTw8svx">pic.twitter.com/omQbTw8svx</a></p>&mdash; shinmura0 @ 3/14参加者募集中 (@shinmura0) <a href="https://twitter.com/shinmura0/status/1235043219336876034?ref_src=twsrc%5Etfw">March 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

RaspberryPi3で5～6FPSの速度です。
さらに、得られたヒートマップを二値化すれば、枠で囲うこともできます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">「s」キーを押せば、物体検出（ObjectDetection）風にすることも可能です。 <a href="https://t.co/ePdN9itSy8">pic.twitter.com/ePdN9itSy8</a></p>&mdash; shinmura0 @ 3/14参加者募集中 (@shinmura0) <a href="https://twitter.com/shinmura0/status/1235043581162741761?ref_src=twsrc%5Etfw">March 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#手法の紹介
※前回の記事をご覧になっていない方は、[こちら](https://qiita.com/shinmura0/items/ba45b7b9da02a44ed1b1#%E6%8F%90%E6%A1%88%E6%89%8B%E6%B3%95)も併せてご覧ください。

Faster-Grad-CAMの適用条件はただ一つ、**深層距離学習を使うことです。**
TripletでもArcFaceでも適用できます。

[前回紹介した論文](https://qiita.com/shinmura0/items/ba45b7b9da02a44ed1b1)では、深層距離学習にTripletを使っていました。
「Faster」ではTripletに代わりにArcFaceを用いています。

※データセットは@karaage0703 さんの[janken_dataset](https://github.com/karaage0703/janken_dataset)を使っております。

##学習方法
まずは、MobileNet V2をArcFaceを使って普通に分類問題で学習させます（step1）。

![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/78eb896f-f7bf-7d36-1a0b-63b6d0cb0e97.png)

次に、データベースを作ります（step2）。
データベースは、```embeded vector```と```weight vector```の二つがあります。

![step2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/6014b6c7-d74d-66e3-e281-805321cd1078.png)

```embeded vector```は通常のArcFaceで出力されるものです。
このベクトルを使うことで、分類や異常検知ができます。

```weight vector```は、```block_16_expan_relu```から出力されるベクトルを使って出力yを
偏微分したものです。といっても、通常のGrad-CAMと同じ操作をしているだけです。
```weight vector```は[前回](https://qiita.com/shinmura0/items/ba45b7b9da02a44ed1b1#grad-cam)の論文でいうと$\alpha_k$に相当します。

次に、Faster-Grad-CAMを学習させます（step3）。

![step3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/18b52c95-8a6d-a075-b371-280c7a67325a.png)

まずは、学習データの```embeded vector```を求め、k-meansを適用してクラスタリングを行います。
論文ではクラスタ数は50でしたが、Fasterでは10に変更しています。

そして、各クラスタ内でクラスタに属する```weight vector```の平均値を計算します。
最後に平均化されたベクトルのうち、Top50を取得し、その番地```channel No```と
平均値の大きさ```weight```を保存します。

※論文では、Tripletを使っているため、似たもの同士はユークリッド距離が近い傾向にありました。
　従って、k-meansを使うと、**似たもの同士が同じクラスタに存在する**可能性が高いです。一方、
　「Faster」は**コサイン類似度が大きいもの同士が似た傾向**を持っているため、k-meansで
　似たもの同士でどこまで固まるのか議論の余地があります。

##推論方法
最後に、Faster-Grad-CAMで推論します（step4）。

![step4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/fc5833d7-c37f-2580-b44f-2c11ca64b3fa.png)

まずは、テストデータを学習したMobileNet V2に入れ、テストデータの```embeded vector```と
```weight vector```を得ます。そして、得た```embeded vector```にk-meansを適用し、属する
クラスタを求めます。上の図では、cluster_Noが1のテストデータが得られたとしています。

次に、属するクラスタの```channel No```と```weight```をロードして、得られた```weight vector```に
適用します。```channel No```は、可視化に使うNoだけを指定し、```weight```はそれらNoに掛ける重みが
保存されています。

最後に、得られたヒートマップをリサイズして、画像と合成しています。

##速さの秘密
上の図から分かるとおり、「Faster」の推論で行う処理は以下のとおりです。

+ k-meansの推論
+ 保存データのロードやリサイズ

一方、普通のGrad-CAMは以下の処理を行っています。

+ ArcFaceの層とソフトマックス層の計算
+ 偏微分の計算
+ リサイズ

![step2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/6014b6c7-d74d-66e3-e281-805321cd1078.png)

step2では、前述したとおり、普通のGrad-CAMとほぼ同じ操作をしています。

「Faster」では、推論時、GAP以下の処理は不要なためネットワークから削除しています。
しかし、普通のGrad-CAMではそれが必要で、余分に計算時間がかかっています。

ただ、その計算時間は微々たるもので、**一番重たいのはやはり偏微分です**（上の図でいうと
```"Normal" Grad-CAM```）。どうしてここまで時間がかかるのか真剣に考えたことはありませんが、
とりあえず重いです。画像のサイズによりますが、1秒以下で終わったことはありません。

一方、「Faster」の方は一番重くてもk-meansくらいなので、可視化の計算はCPUでも楽々できます。
下手したら**数msecです。**

##学習方法
提供しているデモ機は、自分の画像で学習させることも可能です。
学習方法は、[こちら](https://github.com/shinmura0/Faster-Grad-CAM)の```Train_Faster-Grad-CAM.ipynb ```を参考にしてください。

###ハイパーパラメータの調整
+ thresh_hand
_```janken_demo.py```内でこの値をいじると、グーとパーの判定基準を変えることができます。
この値の範囲は0～1で、ArcFaceのコサイン類似度に相当します。この値を大きくすると、グーは
認識しやすくなりますが、パーの認識は厳しめになります。

+ thresh_OD
_ ```janken_demo.py```内でこの値をいじると、物体検出風の枠の閾値を変えることができます。
この値を大きくすると、枠は小さめになり、小さくすると、枠は大きめになります。

+ ArcFaceのパラメータ
ArcFaceのパラメータは主に、sとmの二つ存在し、分類精度に影響を与えます。2分類や3分類程度
であれば、初期値から変える必要はないかと思います。10分類以上になると、いじる必要があるかも
しれませんが、面倒な方は[AdaCos](https://qiita.com/shinmura0/items/d2a3a46c3fb287a1ea0e)を使う手もあります。AdaCosはパラメータを自動的に決めて
くれる手法です。


#応用例
##異常部分の可視化
[自己教師あり学習](https://qiita.com/daisukelab/items/e0ff429bd58b2befbb1b#%E5%AE%9F%E9%A8%933%E9%80%9A%E5%B8%B8%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%81%8B%E3%82%89%E7%95%B0%E5%B8%B8%E3%81%AA%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E8%87%AA%E5%B7%B1%E6%95%99%E5%B8%AB%E3%81%82%E3%82%8A%E5%AD%A6%E7%BF%92)を使った異常検知では、Grad-CAMを適用することで「異常部分を
可視化すること」ができました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">本当にやりたかったのはコレ！Faster-Grad-CAMで異常部分の可視化。<br><br>〇を正常として学習させました。（余分な線、欠けた線、異物は「異常」です。）リアルタイムで「可視化」と「異常検知」が同時にできるなんて夢のよう...(^^)。 <a href="https://t.co/66BFAc8Efz">pic.twitter.com/66BFAc8Efz</a></p>&mdash; shinmura0 @ 4/11参加者募集中 (@shinmura0) <a href="https://twitter.com/shinmura0/status/1236852805068939266?ref_src=twsrc%5Etfw">March 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

余分な線や欠けた線があると「異常」となり、異常部分を枠で囲ってくれます。

##オートアノテーションツール
以前に[アノテーションを自動化するツール](https://qiita.com/shinmura0/items/60a84ee24f9fb4546657)を作りました。

これは、Grad-CAMで二値化したマップにベイズ最適化を適用し、過不足なく
アノテーションするものです。

![k_means (1).gif](https://qiita-image-store.s3.amazonaws.com/0/264781/858c6056-bfdd-11dd-6786-2dbaea76e54a.gif)

この事例にFasterを適用すると、Grad-CAMで5秒かかっていたものが、ほぼ一瞬で
処理することができます。全体として、**動作時間を25％削減することができます。**
（20秒→15秒/画像1枚）

#まとめ
+ Faster-Grad-CAMを使えば、通常のGrad-CAMより10倍以上高速化することが可能
+ しかも、高精度化にも成功
+ 速さの秘密は、推論時に偏微分を使っていないこと