【可視化】Ano-Unetで異常部分の可視化

ディープラーニングを使った異常検知で役に立つ「Ano-Unet」を開発しました。
Ano-Unetは、異常部分を可視化できます（**教師無し学習**）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/23de33cf-b720-350e-1291-14ec0930c5f6.png)

コード全体は[Github](https://github.com/shinmura0/Ano-Unet/blob/master/Ano-Unet.ipynb)に置きました。

※こちらは、[Pythonデータ分析勉強会#10](https://pythondata.connpass.com/event/138505/)の発表資料です。

#はじめに
このAno-Unetは[以前の記事](https://qiita.com/shinmura0/items/c2f7a86b156ebc5c5daa)の続きで開発したものです。

以前の記事では、オートエンコーダで可視化する手法を提案しました。
ところが、オートエンコーダだとうまくいかないとのご意見をいただいております。

そこで、実験してみました。まずは、以前に示した成功例です。

![ダウンロード.png](https://qiita-image-store.s3.amazonaws.com/0/264781/46517a29-393b-5e03-1b62-d7079992555a.png)

上の図は「スニーカー」を正常として学習させています。

そして、「ブーツ」を異常画像としてテストしてみた結果です。
ブーツのヒールなどが異常部分として可視化できています。

今回やってみた失敗例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/1294f4e8-54c4-5cc8-d3a7-74b6cf65f58e.png)

上の図は「馬」を正常として学習させています。

そして、「馬」を入力画像としてテストしてみた結果です。
正常画像なので、再構築が完璧に決まらないといけないところを
全然うまくいっていません。

#なぜうまくいかないのか？
※この部分は実装とは関係ないので、興味のない方は読み飛ばしてください。

結論からいうと、スニーカーvsブーツのように「大きさ」、「向き」、「背景」が
決まったものであれば、DOC' + オートエンコーダでもうまくいきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/9c80d4e5-795d-cce7-6601-16b43fbc0b7a.png)

例えば、工業製品の検査などは、上記の条件を守ることができると思います。

ところが、馬の画像のように「大きさ」や「向き」、「背景」が多用なものは
DOC' + オートエンコーダではうまくいきません。

これは、DOC'の特性上、しょうがないことです。

DOC'の学習では、正常画像と他の画像（例えばcifar-10）を見比べなら、正常画像の
特徴抽出方法を学習していきます。このとき、DOC'は分類用の畳み込みニューラル
ネットワーク（CNN）として学習を繰り返します。このとき、正常画像に写っている
「背景」は邪魔なノイズでしかありません。従って、「背景」は特徴として捉えない
ように学習します。

また、DOC'は正常画像におけるパーツの位置関係も重要視しません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/4f2fede0-15ff-aa38-9df4-632f74ee3a8a.png)
（画像は、[Understanding Hinton’s Capsule Networks. Part I: Intuition.](https://medium.com/ai%C2%B3-theory-practice-business/understanding-hintons-capsule-networks-part-i-intuition-b4b559d1159b)より引用）

上の図は、分類用CNNの欠点を挙げた説明画像です。例えば、「顔」と「リンゴ」を
分類するCNNを構築したとして、左の図でも右の図でも、CNNは顔と認識してしまう
可能性があります。

分類用CNNとして学習したDOC'も、このような状態に陥っている可能性があります。

（分類用のCNNと違って、通常のオートエンコーダでは、パーツの位置情報も
抽出するため、このような現象は起きにくいです。）

さらに、DOC'は大きさの変動にもロバストなように学習してしまいます。

まとめると、下の図のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/e7cfab23-c9e1-880d-38ac-096e89ced6a8.png)


このような条件下で、Decoderに元の画像を再構築させるのは不可能といえます。

#Ano-Unet
##特徴
+ 正常たらしめる特徴と**関係ない部分**（背景など）は元画像とそっくりに再現する。
+ 正常たらしめる特徴と**関係ある部分**（色、形状）は、なるべく「典型的な特徴」に近づけるように再現する。
+ [DOC](https://qiita.com/shinmura0/items/cfb51f66b2d172f2403b)やmetric learningに後付け可能な、可視化専用のニューラルネットワーク

Ano-Unetに、例えば「馬」を正常と学習させた場合、「鹿」の画像を入れると、
背景はそのまま再現します。そして、鹿の角を消して、鹿の小さい頭や
短い尻尾は馬に似せて再現します。

##構造
Ano-Unetの構造は以下のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/57c67130-1d36-e819-fdef-ef8cd076b2cd.png)

Ano-Unetは、基本的にU-netしか学習させません。
ちなみに、U-Netはこちらの記事に詳しく載っています。
https://qiita.com/koshian2/items/603106c228ac6b7d8356

そして、f-Netの重みは固定しています。f-NetはDOCでいうところの、DOC'に相当します。
もちろん、metric learningでも良いです。f-Netは特徴(feature)抽出器の役割を果たします。
f-Netは、U-netで再現した画像が正常画像の特徴を有しているかを判定します。

##損失関数
Ano-Unetの損失関数は以下のとおりです。

```math
L = L_R + \beta L_C \\
```

ただし、$L_R$は生成画像と元画像のピクセル差の合計です。
U-netで生成した画像を$G_{Unet}$とすると、

```math
L_R = \sum |x-G_{Unet}|
```

と表せます。$L_R$のおかげで、背景などは元画像とそっくりになるように再現します。

また、$L_C$は生成画像が持つ特徴について、「正常たらしめる特徴」から
どれくらい離れているかを表したものです。式で書くと以下のとおりです。

```math
L_c = \sum |F_{out}(G_{Unet}) - C|
```

ただし、$F_{out}(G_{Unet})$は、U-netで生成した画像をf-Netに入力し、出てきた
出力を指します。要は、特徴ベクトルです。また、Cは正常画像をf-Netに入れ、
出力の平均値をとったものです。

最後に、$\beta$はパラメータです。

Ano-Unetの損失関数は[AnoGAN](https://qiita.com/NakaokaRei/items/231ec4efe42dfe79d1ff)にかなり似ていますが、$L_C$の部分が違います。
$L_C$のおかげで、異常部分を可視化することができます。

##直感的説明
前述したように、Ano-Unetで一番重要なのは$L_C$の役割です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/6a12e920-5189-5202-7b54-971b9c0db48b.png)

いま、f-Netの出力が上の図のように与えられたとして、緑の点は正常画像（馬）です。
一方、赤い点は異常画像（鹿）です。（本稿の実装では、f-Netの出力は1280次元ですが、
説明の便宜上、二次元の図で説明します。）

図のxの点は、正常画像をf-Netに入れ、出てきた出力の平均値、すなわちCを指します。

与えられたテスト画像は$L_C$の効力によって、学習を繰り返すたびにCに向かうように
画像を変換していきます。ここで注意していただきたいのが、Ano-Unetは「学習→推論」
という流れではなく、テスト画像で**一枚ずつ「学習」フェーズが必要です。**そのため、
非常に時間がかかります。

このとき、正常画像はCに近いため、元画像からほとんど変わることなく学習が完了します。

一方、異常画像はCから離れているため、正常っぽくないところを変換しながら
学習を繰り返します。その結果、元の画像から変更された部分が出てきます。
そして、その部分こそ異常部分です。

##学習手順
+ DOC'もしくはmetric learningを学習させる。詳しくは[こちら](https://qiita.com/shinmura0/items/cfb51f66b2d172f2403b)を参照。
+ そして、学習させたモデル（f-Net）の重みを固定する。
+ f-Netに正常データを入れ、Cを求める。
+ f-NetをU-netに接続し、テスト画像を学習（推論）させる。

##パラメータ
Ano-Unetのパラメータとして、epochsと$\beta$があります。

epochsが大きすぎるとAdversarial Exampleのような現象が起き、見た目は
元画像とほぼ変わず、ノイズを入れてf-Netをだますような現象が起きました。

また、$\beta$は、大きすぎると背景の再現も影響を受けるようです。
逆に小さすぎると、異常部分が目立たないようになってしまいます。

#実験
+ f-Netのモデル
今回は、metric learning(L2 Softmax Loss)を使いました。
そして、モデルはMobileNet V2($\alpha=0.5$で、重みはImageNet）を使用しました。

+ 対象データ
可視化する画像は、異常データのみとしました。
正常データも可視化できますが、Ano-Unetは、metric learningのスコアで
異常と出たものだけ、可視化する使い方を想定しています。

+ 比較対象
比較対象として、[AnoGAN](https://qiita.com/NakaokaRei/items/231ec4efe42dfe79d1ff)を選びました。
AnoGANは異常部分の可視化だけではなく、異常スコアも算出できます。

+ 可視化の方法
オートエンコーダのときと同じように元画像と再構築画像の差をとり、
差が大きいところが赤くなるようにヒートマップで可視化しています。

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/4b70d00d-e7af-628a-6809-c9ac6be3f356.png)


##cifar-10の場合
コードは[Github](https://github.com/shinmura0/Ano-Unet/blob/master/Ano-Unet.ipynb)に置きました。
ColaboratoryのGPUで動くと思います。

「馬」を正常、「鹿」を異常としました。

metric learningで使ったデータ数は、以下のとおりです。

|画像サイズ|枚数|クラス数|画像の種類|
|---|---|---|---|
|96x96x3|6000|9（鹿を除く）|cifar-10|

Ano-UnetでCを求めるために使ったデータ数は、以下のとおりです。

|画像サイズ|枚数|クラス数|
|---|---|---|
|96x96x3|5000|1(馬)|

パラメータは以下のとおりです。

```
beta = 50
epochs = 150
```

###結果

+ 成功した例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/adf8a969-6cdb-9896-567a-30e6e2f1ccf6.png)

Ano-Unetでは、鹿の華奢な体が、異常な部分として可視化されています。
また、顔だけ白い部分も異常部分として可視化されています。

+ 微妙な例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/69fd99f3-776a-38a0-291c-2e6319c2cfb7.png)

鹿と背景が同じような色の場合、背景も一緒に可視化されてしまうことが
あるようです。また、残念ながら鹿の角は背景として認識されている
らしく、異常部分として認識されませんでした。

##DAGMの場合

[DAGM](https://qiita.com/leetmikeal/items/7c0d23e39bf38ab8be23#dagm-2007)は工業製品の画像を集めたデータセットです。
6クラスの画像データに対し、各クラスで正常1000枚、異常150枚の画像が添付されています。

metric learningで使ったデータ数は、以下のとおりです。

|画像サイズ|枚数|クラス数|画像の種類|
|---|---|---|---|
|96x96x3|6000|10|cifar-10|
|96x96x3|600|1(class6の正常データ)|DAGM|

Ano-UnetでCを求めるために使ったデータ数は、以下のとおりです。

|画像サイズ|枚数|クラス数|
|---|---|---|
|96x96x3|1000|1(class6の正常データ)|

正常画像はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/38de76c7-905f-7503-a55d-5a9ef1102113.png)

パラメータは以下のとおりです。

```
beta = 80
epochs = 200
```

###結果

+ 成功した例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/23de33cf-b720-350e-1291-14ec0930c5f6.png)

かなりノイジーなヒートマップですが、Ano-Unetでは、異常部分が可視化されています。

+ 微妙な例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/223aea1b-b430-067e-3a1d-f27007f68f59.png)

判別できなくもないですが、ノイズが多くて見づらいヒートマップになっています。

#AnoGANとの比較
Ano-UnetとAnoGANの違いについて、考察します。

正直、この記事をもってどちらが優れているかを結論を出すつもりはありません。
なぜなら、今回使ったAnoGANのGeneratorは表現力が乏しいからです。

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264781/63c5571c-9deb-b465-8b75-7fe5da5f2325.png)

上の図は今回使ったGeneratorの生成画像です。馬に見えなくもないですが、
一般的に見られる「GANって凄い！」といわれるような表現力は持っていません。
GANの学習が地獄だったため、今回はAnoGANの探索に時間をかけていません。

##学習の安定性
よく言われているように、GANの学習は安定させることが難しいです。
従って、AnoGANでも学習を安定させる条件をどうにかして見つけないと
いけません。これは結構大変です。だったりします。

一方、**U-netの学習は安定しています。**そのうえ、表現力も豊かなため
U-netの学習フェーズはすぐに完了します。

##転移学習の適用
Ano-Unetでは転移学習が使えます。正確にいうと、f-Netで転移学習が使えます。
そのため、AnoGANのDiscriminatorと比べスタートラインが全然違います。

つまり、Ano-Unetは特徴を見分けるフィルターが育った状態で、学習していくため、
最終的に「厳しくチェックする目」を持つことができます。

##データの多様性
AnoGANは正常データしか見れませんが、Ano-Unetは他の画像を見ながら
学習することができます。Ano-Unetで使っているmetric learningやDOCは、
正常データと他のデータを見ながら学習を進めていきます。

そのため、Ano-Unetは正常データの特徴をより明確に捉えることができ、
**正常データたらしめる特徴を掴むこと**ができます。

（AnoGANは「異常検知」で、metric learningやDOCは、異常検知ではなく「半教師あり
学習」との指摘もあります。ここは、同じ土俵ではないため、不平等の可能性があります。
しかし、実用上はmetric learningやDOCも、異常検知と同じように**正常データさえ集めれば**
よく、やる作業はあまり変わりません。）


#応用範囲
異常検知ではありませんが、metric learningでよく行われる「人物の照合」の際に
Ano-Unetを適用すると、どこが似ていないのか可視化することができるかもしれません。

#課題

+ 動作速度について
Ano-Unetはただでさえ、推論という名の「学習」を行っているので遅いです。
さらに、**Ano-UnetはAnoGANより遅いです。**U-netの構造にもよりますが、
体感でAnoGANの100倍くらい時間がかかっています。

+ メモリ使用量について
今回の実験はColaboratoryで実行しましたが、テスト画像20枚くらいが限界でした。
それ以上だとメモリ上限を上回ってしまい、Colaboratoryが初期化されてしまいます。
テスト画像1枚に対し、U-netを一つ構築しているので、非常にメモリを食います。
ローカルPCで実行する場合は気を付けてください。

#まとめ

+ 特徴抽出器ベースの異常検知器（DOCなど）で異常部分を可視化できる「Ano-Unet」を開発しました。
+ AnoGANと比べても、見劣りしない性能を発揮できると思います。
+ ただし、動作速度とメモリ使用量がネックになっており、大量の画像は処理できません。