VAE（変分オートエンコーダ）の判断根拠の可視化（異常検知）

# 概要
　土木の分野では、エラー取り（異常検知）はよく行われます。時系列データから異常検知を行う際は、画像に変換してからAIを使う場合が多く見受けられます。異常検知においても、判断根拠の可視化は必要だと考えています。

　画像分類において、判断根拠の可視化の方法として、GradCamが知られているが、VAE（変分オートエンコーダ）の場合は、知らなかったので調べてみました。

　論文は、以下を参照しました。
https://arxiv.org/pdf/1911.07389.pdf

　PyTorchを使った実装は、以下があります。私は、tensorflowを使う人なのでわかりませんでした。

https://github.com/liuem607/expVAE

日本語の解説として、

https://salty-vanilla.github.io/portfolio/post/explaining_vae/

があります。

# VAEを使った異常検知（One-Class VAE）

　簡単にMNISTを例に異常検知を説明する。手書き数字0の画像を正常とし他の手書き数字1～9の画像を異常とする。

　数字0の画像のみでVAEを学習させ、数字0の画像を再構成させる。学習が上手くいけば、本来の数字0の画像とVAEによって再構成された数字0の画像の誤差は小さいはず。

　そして、手書き数字1～9の画像をVAEに入力すると、数字0以外の画像は再構成されないはずなので、数字0以外の画像とVAEによって再構成された画像の誤差は大きくなり、異常だと見抜くことが出来るだろう。
　
　あとは、適当に誤差の閾値を決め、異常かどうか決めればよい。


# Attention map の作成

　VAEが構築できたなら、Attention map の作成する。基本的には、ほぼGradCamと同じである。ただし、GradCamの場合は、勾配を計算する際に予想クラスの出力（1つ）が必要であるが、VAEの場合は、潜在ベクトル（全部）が必要である。

　エンコーダーの最後の畳み込みレイヤーから、特徴量マップ $ A\in \mathbb{R}^{n\times h \times w}$ と潜在ベクトル $z\in \mathbb{R}^{D}$ を取得する。$n,h,w,D$ は、順にチャネル、畳み込み後の高さ・幅、潜在空間の次元である。

　以下の上・下付き添え字 $k,p,q,i$ は、順にチャネル、畳み込み後の高さ・幅、潜在空間の成分を表す。

　特徴量マップと潜在ベクトルから、各チャネルごとに $\alpha_k^i$ を計算する。

```math
\alpha_k^i=\frac{1}{h}\frac{1}{w}\sum_{p=1}^{h}\sum_{q=1}^{w}\left(\frac{\partial z_i}{\partial A_k^{pq}}  \right)
```

　次に、各潜在ベクトルごとのAttention map $M^{i}$ を計算する。

```math
M^{ipq} = \rm{Relu}\left(\sum_{k=1}^n \alpha_k^i  A_k^{pq} \right)
```
　最後に、各潜在ベクトルごとのAttention map $M^{i}$ の平均をとり、Attention map $M$ が計算される。

```math
M^{pq} =\frac{1}{D} \sum_{i=1}^D M^{ipq} 
```
　コードは（たぶん）こんな感じになる。

```php
# batch サイズは１であることが前提
with tf.GradientTape(persistent=True) as tape:
    A,z = エンコーダーからの出力
   
    dim = tf.shape(z)[1]
    grads = [tape.gradient(z[:,i],A)  for i in range(dim)]
        
alpha =tf.reduce_mean(grads, axis=(1,2,3))
# チャネル平均
M_i = tf.einsum('ij,klmj->iklm', alpha, A)
M_i= tf.nn.relu(map)
# 潜在空間の次元ごとに平均
M = tf.reduce_mean(M_i, axis=0)
```


# The normal difference distribution 
　理想的には、正常データによって学習されて得られた潜在空間は、正規分布に従って生成されているはず。正常データを用い学習されたエンコーダーに、異常データを入力して得られた潜在空間の確率分布は、学習によって得られた正規分布とは異なるはず。正常データと異常データの差のを表す確率分布（normal difference distribution）を使い、異常データのAttention mapを作成することを考える。

　以下の添え字 $x$ は正常データ、添え字 $y$ は異常データを表す。

　最初に、学習されたエンコーダに正常データを代入し、潜在空間のサンプル $z$ を生成させる。多数のサンプル $z$ から、各ベクトルの成分ごとに平均 $\mu_i^x$ と分散 $\sigma_i^x$ を計算する。
　
　次に、学習されたエンコーダに異常データを代入し、平均 $\mu_i^y$ と分散 $\sigma_i^y$ を出力する。正常データの平均 $\mu_i^x$ と分散 $\sigma_i^x$ と異常データの平均 $\mu_i^y$ と分散 $\sigma_i^y$ を使い、以下のnormal difference distribution から、潜在ベクトル $u$ を生成する。


```math
P_{q(z_i|x) - q(z_i|y) }(u_i) = \frac{1}{\sqrt{2\pi\sigma_i^2} }\exp\left\{-\frac{(u_i -\mu_i)^2}{2\sigma_i^2} \right\}  
```

```math
\mu_i=\mu_i^x - \mu_i^y  \ ,\ \sigma_i^2=(\sigma_i^x)^2 +(\sigma_i^y)^2 
```

　異常データのAttention mapは、潜在ベクトル $u$ を使い計算する。つまり、上記で説明したAttention map の作成方法で使われる $z$ を $u$ に変えて計算する。 

　normal difference distributionに関して、コードは（たぶん）こんな感じになる。

```php5
batch = tf.shape(y_mean)[0]
dim = tf.shape(y_mean)[1]
epsilon = tf.keras.backend.random_normal(shape=(batch, dim))

#　通常のVAEで使われる z
z= y_mean + tf.exp(0.5 * y_log_var) * epsilon
#　normal difference distributionからサンプリングされる u
u=(x_mean-y_mean) +( tf.sqrt(x_var + tf.exp(y_log_var)) )* epsilon

```


# 検証

　MNISTを使い、判断根拠の可視化を行った。手書き数字0の画像を正常データとして学習させ、他の手書き数字1～9の画像を異常データとして入力する。

　通常のVAEの学習は、それなりにできている。もちろん、手書き数字0の画像のみで学習している。以下の可視化では、検証誤差が最小となった重みを使っている。
![history_loss_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/46611093-86b2-6178-2ce7-164e87c736d7.png)

　異常かどうかの判断は、入力画像と再構成画像の誤差で判断する。誤差関数は、自乗誤差が使われるかもしれないが、VAEを学習する際にクロスエントロピーを用いたので、クロスエントロピーを使うことにする。

　次に、異常かどうか判断するための閾値を設定する必要がある。MNISTの訓練データ（手書き数字1～9の画像も含む）を使い、再構成誤差の値でヒストグラムを書いてみる。

![histogram-test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/4bc4d66d-cc55-9089-42d9-6d321345d31b.png)

　青色のヒストグラムが、正常データの再構成誤差のヒストグラムであり、オレンジ色のヒストグラムが、異常データの再構成誤差のヒストグラムである。正常データの再構成誤差のヒストグラムの平均は123であり、標準偏差は30ぐらいである。
　
　よって、異常かどうか判断するための閾値を123＋2×30とする。つまり、4σ以上のデータ（片側だけど）は、異常とみなす。

　最後に、テストデータを使い異常とみなされた手書き数字1～9の画像を見ていく。3つの画像の順番は、入力画像・再構成画像・Attention map である。


![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/dea2a3f8-d1d6-8bce-0fce-4268a5b774d4.png)
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/63751d34-4d45-62d5-66ad-36a15297cf06.png)
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/4cefcbf2-1057-e8cf-8f2a-b43206709f8b.png)
![24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/4bd91e82-d0b9-0dfc-ba3a-7ea8d8348193.png)
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/2ac12bc3-51a7-3344-37a4-ef694239478c.png)
![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/c4e5279e-0b61-d146-cfe1-d0935fc50890.png)
![41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/f7f2fcb9-6283-1be2-a7d1-dea4cf3fe1ad.png)
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/235cd899-8bb3-e856-f69f-42a7cb682813.png)
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521272/8a992c0f-f645-5b9b-6fb3-1a01cda5a996.png)

# まとめ

　とりあえずMNISTは、それっぽくなる。