概要
土木の分野では、エラー取り(異常検知)はよく行われます。時系列データから異常検知を行う際は、画像に変換してからAIを使う場合が多く見受けられます。異常検知においても、判断根拠の可視化は必要だと考えています。
画像分類において、判断根拠の可視化の方法として、GradCamが知られているが、VAE(変分オートエンコーダ)の場合は、知らなかったので調べてみました。
論文は、以下を参照しました。
https://arxiv.org/pdf/1911.07389.pdf
PyTorchを使った実装は、以下があります。私は、tensorflowを使う人なのでわかりませんでした。
日本語の解説として、
があります。
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$ を計算する。
\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}$ を計算する。
M^{ipq} = \rm{Relu}\left(\sum_{k=1}^n \alpha_k^i A_k^{pq} \right)
最後に、各潜在ベクトルごとのAttention map $M^{i}$ の平均をとり、Attention map $M$ が計算される。
M^{pq} =\frac{1}{D} \sum_{i=1}^D M^{ipq}
コードは(たぶん)こんな感じになる。
# batch サイズは1であることが前提
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$ を生成する。
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\}
\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に関して、コードは(たぶん)こんな感じになる。
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の画像のみで学習している。以下の可視化では、検証誤差が最小となった重みを使っている。
異常かどうかの判断は、入力画像と再構成画像の誤差で判断する。誤差関数は、自乗誤差が使われるかもしれないが、VAEを学習する際にクロスエントロピーを用いたので、クロスエントロピーを使うことにする。
次に、異常かどうか判断するための閾値を設定する必要がある。MNISTの訓練データ(手書き数字1~9の画像も含む)を使い、再構成誤差の値でヒストグラムを書いてみる。
青色のヒストグラムが、正常データの再構成誤差のヒストグラムであり、オレンジ色のヒストグラムが、異常データの再構成誤差のヒストグラムである。正常データの再構成誤差のヒストグラムの平均は123であり、標準偏差は30ぐらいである。
よって、異常かどうか判断するための閾値を123+2×30とする。つまり、4σ以上のデータ(片側だけど)は、異常とみなす。
最後に、テストデータを使い異常とみなされた手書き数字1~9の画像を見ていく。3つの画像の順番は、入力画像・再構成画像・Attention map である。
まとめ
とりあえずMNISTは、それっぽくなる。