雑記

Keras をよくわかっていないのでおかしな点があればご指摘いただきたいのですが、1つ前までの記事で紹介していた DAGMMメモ1メモ2メモ3メモ4)を Keras で実装しようとすると以下のような手順でできるんでしょうか。よくわかりません。

  • 不整脈データ(274次元)を真ん中の特徴量が2次元の深層自己符号化器にかける。
  • 深層自己符号化器が終わったところで、最初の入力と特徴量を回収してきて、損失に再構築誤差を流し込むと同時に、特徴量に再構築エラー(2次元)を concat してこれを出力する(カスタムレイヤー0)。
  • さっきの出力を何層かの全結合層にかけて各分布への割り当て確率にする(不整脈データは混合数2)。
  • ここで4次元の特徴量を回収してきて、尤度と正則化項を損失に流し込む(カスタムレイヤー1)。

ガワだけ実装すると以下のようなイメージですが、中身を実装してコンパイルできるのか知りませんし、訓練して勾配計算が意図通りに動くのかも知りません。

  # DAGMM
  x_org  = Input(shape=[274])
  h_0    = Dense(10, activation='tanh')(x_org)
  z_c    = Dense(2)(h_0)
  h_1    = Dense(10, activation='tanh')(z_c)
  x_dash = Dense(274)(h_1)
  z      = MyLayer0()([x_org, z_c, x_dash])    # 損失に再構築誤差を付加 & 再構築エラーをconcat
  h_2    = Dense(10, activation='tanh')(z)
  h_3    = Dropout(0.5)(h_2)
  gamma  = Dense(2, activation='softmax')(h_3)
  gamma_ = MyLayer1()([z, gamma])              # 損失にエネルギーと正則化を付加
  DAGMM  = Model(x_org, gamma_)
  DAGMM.compile(optimizer=optimizers.Adam(lr=0.0001), loss=None)
  DAGMM.summary()
_______________________________________________________________________________________
Layer (type)               Output Shape      Param #     Connected to
=======================================================================================
input_1 (InputLayer)       (None, 274)       0
_______________________________________________________________________________________
dense_1 (Dense)            (None, 10)        2750        input_1[0][0]
_______________________________________________________________________________________
dense_2 (Dense)            (None, 2)         22          dense_1[0][0]
_______________________________________________________________________________________
dense_3 (Dense)            (None, 10)        30          dense_2[0][0]
_______________________________________________________________________________________
dense_4 (Dense)            (None, 274)       3014        dense_3[0][0]
_______________________________________________________________________________________
my_layer0_1 (MyLayer0)     (None, 4)         0           input_1[0][0]
                                                         dense_2[0][0]
                                                         dense_4[0][0]
_______________________________________________________________________________________
dense_5 (Dense)            (None, 10)        50          my_layer0_1[0][0]
_______________________________________________________________________________________
dropout_1 (Dropout)        (None, 10)        0           dense_5[0][0]
_______________________________________________________________________________________
dense_6 (Dense)            (None, 2)         22          dropout_1[0][0]
_______________________________________________________________________________________
my_layer1_1 (MyLayer1)     (None, 2)         0           my_layer0_1[0][0]
                                                         dense_6[0][0]
=======================================================================================

カスタムレイヤー0は以下です。再構築エラーをつくって低次元の特徴に concat すればいいと思うんですが、まだつくっていないのでなので低次元の特徴を2つ concat しています(不整脈データの例では低次元の特徴と再構築エラーが同じ2次元なので)。雰囲気です。

from keras import backend as K

# 損失に再構築誤差を付加 & 再構築エラーをconcat
class MyLayer0(Layer):
  def __init__(self, **kwargs):
    self.output_dim = 4
    super(MyLayer0, self).__init__(**kwargs)
  
  def reconstruction_loss(self, x_org, x_dash):
    return metrics.mean_squared_error(x_org, x_dash) 
  
  def reconstruction_error(self, x_org, x_dash):
    z_e = None # IMPLIMENT ME
    return z_e
  
  def call(self, inputs):
    x_org  = inputs[0]
    z_c    = inputs[1]
    x_dash = inputs[2]
    loss = self.reconstruction_loss(x_org, x_dash)
    self.add_loss(loss, inputs=[x_org, x_dash]) # 再構築誤差を付加
    z_e = z_c # self.reconstruction_error(x_org, x_dash) # 再構築エラーを計算
    z = K.concatenate([z_c, z_e], axis=1)
    return z
  
  def compute_output_shape(self, input_shape):
    return(input_shape[1][0], self.output_dim)

カスタムレイヤー1は以下です。ここは損失にエネルギー項と正則化項を足すだけですがまだ書いていません。一応各分布への割り当て gamma をそのまま output していますが output する必要はないです。

# 損失にエネルギーと正則化を付加
class MyLayer1(Layer):
  def __init__(self, **kwargs):
    self.output_dim = 2
    super(MyLayer1, self).__init__(**kwargs)
  
  def custom_loss(self, z, gamma):
    return 0 # IMPLIMENT ME
  
  def call(self, inputs):
    z = inputs[0]
    gamma = inputs[1]
    loss = self.custom_loss(z, gamma)
    self.add_loss(loss, inputs=[z, gamma])
    return gamma

  def compute_output_shape(self, input_shape):
    return(input_shape[1][0], self.output_dim)

まだ実装してないのでわかりませんというか実装すればいいんですが気が向いたらやります。いつ気が向くのかはわかりません。


深層自己符号化器までは何も問題ないはずなので取り出して動かしてみます。

  # 不整脈データ
  df = pd.read_csv('arrhythmia.data', header=None)
  df = df.drop(df.columns[[10,11,12,13,14]], axis=1) # 欠損カラムを除去
  # 異常ラベルを貼り変える
  df.iloc[:,274][(df.iloc[:,274]>=1 ) & (df.iloc[:,274]<=2 )] = 20
  df.iloc[:,274][(df.iloc[:,274]>=3 ) & (df.iloc[:,274]<=5 )] = 21
  df.iloc[:,274][(df.iloc[:,274]>=6 ) & (df.iloc[:,274]<=6 )] = 20
  df.iloc[:,274][(df.iloc[:,274]>=7 ) & (df.iloc[:,274]<=9 )] = 21
  df.iloc[:,274][(df.iloc[:,274]>=10) & (df.iloc[:,274]<=13)] = 20
  df.iloc[:,274][(df.iloc[:,274]>=14) & (df.iloc[:,274]<=15)] = 21
  df.iloc[:,274][(df.iloc[:,274]>=16) & (df.iloc[:,274]<=16)] = 20
  df.iloc[:,274] = df.iloc[:,274] - 20
  
  x = df.drop(df.columns[[274]], axis=1).values
  y = df.iloc[:,274].values
  
  # 深層自己符号化器のみ取り出して学習
  DAE = Model(x_org, x_dash)
  DAE.compile(loss='mean_squared_error', optimizer=optimizers.Adam(lr=0.0001))
  DAE.fit(x, x, shuffle=True, epochs=10000, batch_size=128)
  
  # エンコーダを取り出してプロット
  encoder = Model(x_org, z_c)
  z_trained = encoder.predict(x)
  plt.figure(figsize=(12, 8))
  plt.scatter(z_trained[:,0], z_trained[:,1], c=y, cmap=plt.get_cmap("bwr"))
  cb = plt.colorbar()
  for t in cb.ax.get_yticklabels():
    t.set_fontsize(18)
  plt.tick_params(labelsize=18)
  plt.show()

上で学習すると特徴空間(2次元)は以下のようになりました。青が正常で赤が異常です。452点あったはずなんですが、相当重なっていると思います。

f:id:cookie-box:20180527165625p:plain:w630

あと再構築誤差方向への分布はどうなっているのかも気になると思います。さらに何らかの再構築誤差も含めてプロットしようとするともう3次元プロットになってしまうのでその準備をします。

from mpl_toolkits.mplot3d import Axes3D

def my_relative_distance(x, y):
  ret = []
  for i in range(x.shape[0]):
    e2 = 0
    x2 = 0
    for j in range(x.shape[1]):
      e2 = e2 + (x[i][j] - y[i][j]) * (x[i][j] - y[i][j])
      x2 = x2 + x[i][j] * x[i][j]
    ret.append(e2 / x2)
  return ret

その上でこうすると再構築誤差も含めてプロットできると思います。

  # エンコーダを取り出してプロット
  encoder = Model(x_org, z_c)
  
  x0 = df.loc[df.iloc[:,274]==0,:].drop(df.columns[[274]], axis=1).values
  x1 = df.loc[df.iloc[:,274]==1,:].drop(df.columns[[274]], axis=1).values
  z_trained0 = encoder.predict(x0)
  z_trained1 = encoder.predict(x1)
  r_error0 = my_relative_distance(x0, DAE.predict(x0))
  r_error1 = my_relative_distance(x1, DAE.predict(x1))
  
  fig = plt.figure(figsize=(12, 8))
  ax = Axes3D(fig)
  ax.plot(z_trained0[:,0], z_trained0[:,1], r_error0, "o", color="#0000ff")
  ax.plot(z_trained1[:,0], z_trained1[:,1], r_error1, "o", color="#ff0000")
  plt.tick_params(labelsize=18)
  plt.show()

なんか再構築誤差方向にも異常データと正常データがあまり分離されているようにみえないので、深層自己符号化器のみで分離する場合は適するハイパーパラメータが違うんだと思います。

f:id:cookie-box:20180527171437p:plain:w720

論文読みメモ: 深層自己符号化器+混合ガウスモデルによる教師なし異常検知(その4)

以下の論文を読みます。

Bo Zong, Qi Song, Martin Renqiang Min, Wei Cheng, Cristian Lumezanu, Daeki Cho, Haifeng Chen. Deep Autoencoding Gaussian Mixture Model for Unsupervised Anomaly Detection. International Conference on Learning Representations, 2018. https://openreview.net/forum?id=BJJLHbb0-
※ キャラクターに元ネタはないです。お気付きの点がありましたらお手数ですがコメント等にてご指摘ください。
前回:その3 / 次回:まだ
f:id:cookie-box:20180513082851p:plain:w60

前回までのあらすじですが、今回 DAGMM という異常検知向けに多次元データの密度を推定するモデルを開発したので、通信データや医療データの4データセットを用いて異常検知性能を検証します。DAGMM の比較対象となるのは既存の state-of-the-art な4モデル及び DAGMM のフレームワークの各要素の検証となる variant な7モデルの計11モデルです。DAGMM やデータセット、比較対象11モデルについては前回までのメモを参照してください。

f:id:cookie-box:20180513082908p:plain:w60

そうそう、異常検知って、「そのサンプルに対する確率密度が閾値以下なら異常」みたいな判定をすると思うんだけど、複数のモデルを比較検証するときはどんな風に閾値を決めるんだろ。同じ閾値ではフェアな比較ができなさそうだよね。そもそも密度推定によらない異常検知手法もあるし。

f:id:cookie-box:20180513082851p:plain:w60

それも含めて、検証実験のコンフィギュレーションを説明しましょう。今回は2つの実験を行うのですが、1つ目の実験では、各データセットをまず訓練用とテスト用に2分割します。そして、訓練用データから正常データのみを取り出してモデルを訓練します。そのモデルをテストデータに適用するのですが、今回は教師なし学習なので、モデルの出力は各サンプルが「正常か異常か」ではなく「どれくらい異常か」ですね。そこで、今回は次のようにします。「各データセットの異常データ混入割合をa%としたとき、テストデータ異常度の上位a%が『異常』、その他が『正常』と判定されたとする。」 例えば、3つ目の不整脈のデータセットには異常データが15%含まれているので、異常度の上位15%が『異常』判定ということです。こうすれば、通常の分類問題の評価指標がそのまま利用できますね。つまり、精度、感度、F値で各モデルを比較します。

f:id:cookie-box:20180513082908p:plain:w60

なるほど、それならある程度フェアな比較になるのかな。

f:id:cookie-box:20180513082851p:plain:w60

それでその1つ目の実験の結果ですが、論文10ページに数表がありますが、見やすいようにF値のみグラフにしてみました。

f:id:cookie-box:20180524193826p:plain:w700

f:id:cookie-box:20180513082908p:plain:w60

わざわざ!? 甲状腺データと不整脈データの方が通信データよりも異常データを分離する難易度高めなんだね。あと KDDCUP の E2E-AE は何があったし…。

f:id:cookie-box:20180513082851p:plain:w60

さらに2つ目の実験です。1つ目の実験との違いは、訓練時に正常データのみを利用するのではなく、わざと異常データをコンタミします。KDDCUP データセットに対する結果が以下です。こちらもF値のみですがまた勝手にグラフを描いてみました。ratio は異常データのコンタミ率です。0% のF値は上のグラフの該当するF値と同じです。このグラフから提案手法 DAGMM はコンタミにも強いことがわかります。

f:id:cookie-box:20180524201948p:plain:w640

f:id:cookie-box:20180513082908p:plain:w60

あー、OC-SVM は与えられた訓練データを囲む面を求めようとするから、そこに異常データがコンタミしていたら異常検知としての性能がガタ落ちしちゃうって感じなのかな。

f:id:cookie-box:20180513082851p:plain:w60

4.5 節に低次元表現の可視化がありますね。Figure 3. (a) の DAGMM の低次元表現は、他のモデル (b) (c) (d) よりも正常データ(青)と異常データ(赤)をよく分離できていると思いませんか?

f:id:cookie-box:20180513082908p:plain:w60

まあ、(a) が比較的赤と青が混ざっていないようには見えるけど…3次元プロットじゃ他の角度からも見てみないとよくわかんないような。

f:id:cookie-box:20180513082851p:plain:w60

あとこの節で取り上げられているのは、Figure 3. の (c) や (d) は事前学習をせず結合学習する手法であるにもかかわらず、事前学習する (b) に比べて低次元表現にあまり変化がみられないという点ですね。ということからも、非線形な次元削減を最初から regularization して学習するのが肝要だと。しかも、(a) DAGMM の自己符号化器部分の再構築エラーは事前学習した自己符号化器並みに小さいということなので、DAGMM は自己符号化器の学習のフレームワークとしても優秀なのではないかと書いてあるようなないような気がしますね。

f:id:cookie-box:20180513082908p:plain:w60

どっち!? ちゃんと読んで!

f:id:cookie-box:20180513082851p:plain:w60

論文の内容は以上になります。あ、今後の課題とかは特にないです。

f:id:cookie-box:20180513082908p:plain:w60

ないんだ!?

f:id:cookie-box:20180513082851p:plain:w60

実際にベンチマークに用いられたデータセットを見てみましょう。どのデータセットも Web 上で見つかると思いますが、一番サイズが小さいところで不整脈のデータは以下に落ちてました。
UCI Machine Learning Repository: Arrhythmia Data Set
これは一番最後のカラムが不整脈の種類ラベルみたいですね。それで、論文には dimension が 274 とあるのにラベルを除いても 279 カラムあってどういうことだろうかと思ったんですが、11~15カラム目に欠損値があったのでおそらくそこを除いていると思います。なので欠損カラムを取り除いてとりあえず自己符号化器にかけてみました。ニューロン数も活性化関数もバッチサイズもエポック数も全て論文に明記されていますからね。あ、論文には tensorflow で実装したとありますが、さしあたり keras で書きます。

# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense

if __name__ == '__main__':
  df = pd.read_csv('arrhythmia.data', header=None)
  df = df.drop(df.columns[[10,11,12,13,14]], axis=1)
  y = df.iloc[:,274].values
  x = df.drop(df.columns[[274]], axis=1).values
  print(y.shape)
  print(x.shape)
  
  encoder = Sequential()
  encoder.add(Dense(10, activation='tanh', input_shape=(274,)))
  encoder.add(Dense(2))
  
  decoder = Sequential()
  decoder.add(Dense(10, activation='tanh', input_shape=(2,)))
  decoder.add(Dense(274))
  
  compression_net = Sequential()
  compression_net.add(encoder)
  compression_net.add(decoder)
  compression_net.compile(loss='mean_squared_error', optimizer='RMSProp')
  compression_net.fit(x, x, shuffle=True, epochs=10000, batch_size=128)

これで学習できます。まあ本当は全データを訓練にかけるんじゃなくて、訓練用とテスト用に分割した上で訓練用から異常データを除いて学習しなきゃなんですけど。そもそも、自己符号化器だけ学習しちゃ駄目だし。

f:id:cookie-box:20180513082908p:plain:w60

じゃあもうちょっと頑張ろうよ!?

f:id:cookie-box:20180513082851p:plain:w60

今日はもう遅いので…。ところで、R を書いた直後に Python を書くと混乱しませんか?

f:id:cookie-box:20180513082908p:plain:w60

知らないよ…。

(次回があれば)つづく

論文読みメモ: 深層自己符号化器+混合ガウスモデルによる教師なし異常検知(その3)

以下の論文を読みます。

Bo Zong, Qi Song, Martin Renqiang Min, Wei Cheng, Cristian Lumezanu, Daeki Cho, Haifeng Chen. Deep Autoencoding Gaussian Mixture Model for Unsupervised Anomaly Detection. International Conference on Learning Representations, 2018. https://openreview.net/forum?id=BJJLHbb0-
※ キャラクターに元ネタはないです。お気付きの点がありましたらお手数ですがコメント等にてご指摘ください。
前回:その2 / 次回:まだ
f:id:cookie-box:20180513082851p:plain:w60

前回までのあらすじです。

f:id:cookie-box:20180521234046p:plain:w600

f:id:cookie-box:20180513082908p:plain:w60

あらすじにはなってないよね!?

f:id:cookie-box:20180513082851p:plain:w60

ここで一旦2節の Related Work に戻りましょうか。高次元データに対する異常検知の先行手法は3タイプに分類されると書いています(この辺りの話、全体的に抽象的ですが…)。

  • 「異常サンプルは低次元に圧縮しにくいだろう」という考えのもと、次元削減を試行するタイプ。
    • 次元削減したときにデコードが上手くいかないようなデータは異常なはずだ、ということです。このアプローチでは元々 PCA や kernel PCA やその他の PCA の亜種が用いられ、近年では深層自己符号化器を用いた手法が提案されています。ただ、サンプルの異常性を専ら「再構築エラー」のみから判断しているために限界がある、と書かれていますね。普通に正常サンプル並みの再構築エラーになる異常サンプルもあるだろうと。特にデータが全体的に再構築しにくい場合や、逆に次元削減手法の表現力があらゆるサンプルをカバーする場合はそうなるだろうと言っています。
  • (特徴空間に写像した後に)クラスタリングしてから孤立サンプル/少数クラスタを判別するタイプ。
    • そのままですが、このアプローチでもクラスタリングをする以上、高次元データに対しては次元削減必須です。この手法では次元削減とクラスタリングの学習が分離しているために、クラスタリングに必要な情報が次元削減の段階で失われうると指摘されています。これに対処するために、深層自己符号化器を利用して次元削減とクラスタリングを一貫で学習する手法も提案されていますが、クラスタリング時のモデルが単純すぎたり、自己符号化器の事前学習がクラスタリング側からの modify を妨げていると。
      • 3タイプの分類とは別枠で「次元削減と混合分布の学習の結合にも関心が高まっていた」という記述があるのですが(別枠なのはこれは必ずしも異常検知でないからですね)、話としてはこの2タイプ目に該当するのではないかと思うのでここに書いておきますね。といっても、この種の先行手法は次元削減が線形であったり、次元削減が事前学習を前提としていて柔軟性がなかったり、という主張が繰り返されているだけですね。それらに比べて本手法 DAGMM は「次元削減が事前学習不要で」「かつ非線形で」「かつGMMと結合して学習する」というのがよいのだと。それに加えて、特に異常検知のために再構築エラーも活用していると。
  • (特徴空間に写像した後に)一定割合の外れ値を覗いた正常データを囲む境界面を求めようとするタイプ(=One Class SVM)。
    • これもそのままです。特徴空間で正常データと異常データを分離する超平面を探そうということですね。このアプローチは高次元だと次元の呪いのために局所最適解に陥りやすいと書かれています。あ、Qiita で以下のような記事を見つけました。
      異常検知のための One Class SVM - Qiita
今回の提案手法 DAGMM は2番目のアプローチに1番目のアプローチの要素も取り入れたものといった位置付けなのですかね。そんなこんなで、今回の検証で DAGMM の比較対象にする先行手法は以下です。
OC-SVM
(Chen2001)
これは上の3番目の One Class SVM です。カーネル関数は通常の RBF カーネルを採用したとのことです。
DSEBM-e
(Zhai2016)
これらは上の1番目のアプローチで、ナブラを取ると深層自己符号化器の再構築エラーとなるようなエネルギー関数を定義するようですね。それで、DSEBM-e ではエネルギーが大きいサンプルを異常とみなし、DSEBM-r では再構築エラーが大きいサンプルを異常とみなします。
DSEBM-r
(Zhai2016)
DCN
(Yang2017)
異常検知に限らない次元削減と混合分布の結合学習のところで出てきた先行手法ですが、3タイプの分類だと2番目に該当するでしょうね。これは自己符号化器(深層ではない)の学習を後段の k-means が制御します。今回の異常検知の用途では、所属するクラスタクラスタ中心から距離が離れたデータを異常とみなします。

f:id:cookie-box:20180513082908p:plain:w60

色々なアプローチにつて最新の手法と比較したってことなのかな。One Class SVM はちょっと古いけど、もう最近の研究はないのかな。SVM は異常検知に利用するには柔軟性に欠ける?

f:id:cookie-box:20180513082851p:plain:w60

あと、上の比較対象の先行手法とは別に、DAGMM のフレームワークの各要素の有効性の検証のため、以下の DAGMM の亜種でも異常検知を行って DAGMM の結果と比較してみます。

GMM-EN学習時のみ目的関数から「再構築エラー」部分を取り除いたものです。学習後の異常検知は元の DAGMM で行います。
PAE目的関数から「エネルギー」ごと取り除いたものです。なのでもはやただの深層自己符号化器です。再構築エラーによって異常検知します。また、この場合は深層自己符号化器に事前学習(Vincent2010)を施すようです。
E2E-AEモデルの設定は上の PAE と同じそうなのですが、end-to-end で学習するようです。ちょっとよくわからなかったのですが、Zhai2016 と同様のエネルギー関数を利用して尤度を最大化しようとするということなのでしょうか。再構築エラーによって異常検知するということです。
PAE-GMM-EMまず事前学習済の深層自己符号化器を学習してから、EMアルゴリズムによってGMMのパラメータを推定します。学習後の異常検知は元の DAGMM と同じエネルギー関数で行います。
PAE-GMM上の PAE-GMM-EM とほとんど同じですが、EMアルゴリズムではなく推定ネットワークによってGMMのパラメータを推定します。
DAGMM-p上の PAE-GMM と似ているのですが、まず圧縮ネットワークを学習した後、次にモデル全体を結合学習します。
DAGMM-NVIDAGMM のエネルギー関数を、neural variational inference のそれに置き換えたものです。

f:id:cookie-box:20180513082908p:plain:w60

亜種多いな!

f:id:cookie-box:20180513082851p:plain:w60

なんかもう楽しそうですね。それで、再構築エラーには relative Euclidean distance  || x - x' ||_2 / ||x||_2 及びコサイン類似度  x \cdot  x' / (||x||_2 ||x'||_2) を採用したと書いてあるんですが、これは  f(x, x') \in \mathbb{R}^2 ってことなんですよね? あ、これらの距離関数を選定した理由は Appendix を読むようにと。

(次回があれば)つづく

論文読みメモ: 深層自己符号化器+混合ガウスモデルによる教師なし異常検知(その2)

以下の論文を読みます。

Bo Zong, Qi Song, Martin Renqiang Min, Wei Cheng, Cristian Lumezanu, Daeki Cho, Haifeng Chen. Deep Autoencoding Gaussian Mixture Model for Unsupervised Anomaly Detection. International Conference on Learning Representations, 2018. https://openreview.net/forum?id=BJJLHbb0-
※ キャラクターに元ネタはないです。お気付きの点がありましたらお手数ですがコメント等にてご指摘ください。
前回:その1 / 次回:まだ
f:id:cookie-box:20180513082851p:plain:w60

異常検知するのに有効なデータの密度推定をするのに、次元削減する「圧縮ネットワーク」と、次元削減後の点が混合正規分布のうち何番目の分布に帰属するか予測する「推定ネットワーク」を同時に学習しようという話でした。時間の都合で2節の Related Work は後回しにしますね。3節も3.3節まではイントロの時点で透けてた内容なんで3.4節までとばしますが、ここで示される DAGMM の目的関数は以下です。 \theta_e, \theta_d, \theta_m はエンコーダ、デコーダ、推定ネットワークのパラメータです。
   \displaystyle J(\theta_e, \theta_d, \theta_m) = \frac{1}{N} \sum_{i=1}^N L(x_i, x_i') + \frac{\lambda_1}{N} \sum_{i=1}^N E(z_i) + \lambda_2 P(\hat{\Sigma})
このうち  L(x_i, x_i') はサンプル  x_i とその再構築  x_i' の誤差関数で、通常L2ノルム  L(x_i, x_i') = || x_i - x_i' ||_2^2 にするのがよいとか書いてありますね。 E(z_i) はサンプル  x_i に対応する確率密度の対数にマイナスをかけたものですね。 z_i \equiv [{z_c}_i , \, f(x_i, x_i')]x_i の低次元表現です。x_i を自己符号化器でエンコードした  {z_c}_i に加えて、再構築エラー  f(x_i, x_i') が concatinate されているのに注意ですね。 f(x_i, x_i') はこれも  x_i x_i' の誤差=距離の関数ですが、必ずしも距離の定義を1つに絞る必要はなく、色々な尺度での誤差を並べて多次元にしてもよいだろうということみたいです。この論文では  E(z_i) のことをエネルギーといっていますね。 E(z_i) はネットワークの学習時につかうのみならず、学習し終えていざ異常検知するという段で「閾値よりも高エネルギーなら異常」というようにつかうと。

f:id:cookie-box:20180513082908p:plain:w60

前回、「圧縮ネットワーク」の目的関数は再構築エラーで「推定ネットワーク」の目的関数は尤度のはずだけどどのように2つのネットワークを同時に学習するんだろう、って気になってたけど、双方を重み付きで足し合わせたものを全体の目的関数とするんだね。ただこの場合、重みのバランスってどうなるんだろう…。あと、最後の項の  P(\hat{\Sigma}) は?

f:id:cookie-box:20180513082851p:plain:w60

正則化項です。次元削減と密度推定の結果、得られた密度が特徴空間内で各サンプルに対応する点の周りに局在してしまったら困りますよね。そもそも次元削減と密度推定によってサンプルとサンプルの隙間の密度を埋めたかったのに。このような悲惨な事態を避けるために以下の正則化項を導入します。
   \displaystyle P(\hat{\Sigma}) = \sum_k \sum_j \frac{1}{(\hat{\Sigma}_{k})_{j,j}}
 (\hat{\Sigma}_{k})_{j,j} は混合されている分布のうち  k 番目の分布の分散共分散行列の  j 番目の対角要素です。どの分布のどの対角要素もゼロになってほしくないので、このようなペナルティを導入するというわけです。この正則化項を入れることによって、自己符号化器部分の性能が、事前学習した自己符号化器並みによくなるとか(イントロ部分でメモに記していませんでしたが、自己符号化器は事前学習した方が自己符号化器部分の性能はよくなるが、今回の目的でそれをしてしまうと密度推定側からの要請で自己符号化器側を modify するということがしづらくなってしまうので事前学習はしないというスタンスです、すみません)。

f:id:cookie-box:20180513082908p:plain:w60

確かに、避けるべきトラップがそれだけなのかはぱっとわからないけど、その事態を避けなければいけないのはマストだね。でもその正則化項もどんな重みで足せばいいのかわからないな…。 \lambda_1, \, \lambda_2 はどう決めるの?

f:id:cookie-box:20180513082851p:plain:w60

 \lambda_1=0.1, \, \lambda_2=0.005 がオススメらしいです。おそらく試行錯誤の結果なんじゃないでしょうか。

f:id:cookie-box:20180513082908p:plain:w60

そっか。

f:id:cookie-box:20180513082851p:plain:w60

3.5節はDAGMM の密度推定プロセスを、通常の variational inference と対比するとこうって話でしょうか。ただモデルの選定基準や性能に直接関わる話ではなさそうなのでとばしますね。3.6節の内容は上で少し触れました。なので次は4節の検証ですね。

f:id:cookie-box:20180513082908p:plain:w60

最適化アルゴリズムの話は?

f:id:cookie-box:20180513082851p:plain:w60

少なくとも3節にはないですね。目的関数はわかったんで、プログラミング部の人に「これを最小化してください」って実装してもらえばいいんじゃないですか?

f:id:cookie-box:20180513082908p:plain:w60

迷惑だよね!?

f:id:cookie-box:20180513082851p:plain:w60

今回検証に用いられているベンチマークデータセットですが、論文にはそれぞれ何のデータか明記されていないので、軽く調べておきましょう(リンク先は論文に記述されているリポジトリとは限りません)。

KDDCUP
(KDD Cup 99 Data Set)
これは「マルウェア攻撃」と「正常通信」の両方を含む通信データで、494,021件のデータが含まれています(これは10%抽出版の件数であって、元々はサイズが約10倍のデータセットです)。「正常通信」の方が20%と少ないので、DAGMM の検証では「正常通信」の側を「検知すべき異常」とみなしています。調べるとこのデータセットは「マルウェア攻撃」にも22種類の攻撃が含まれているらしいので、だから混合分布によるモデリングが適するのかもしれません。
http://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html
ThyroidThyroidとは「甲状腺」ですね。3772件のデータには「甲状腺機能低下」「やや低下」「正常」の3クラスのデータが含まれ、検証ではこのうち「甲状腺機能低下」を「検知すべき異常」としているようです。
http://odds.cs.stonybrook.edu/thyroid-disease-dataset/
ArrhythmiaArrhythmiaは「不整脈」です。452件のデータにはそれぞれ1~16のラベルが付されており、1が「正常」、2~15がそれぞれタイプの異なる不整脈、16がその他という意味とのことです。DAGMM の検証では、データの絶対数が少ないラベル 3~5, 7~9, 14, 15 を異常とみなしたようです。
https://archive.ics.uci.edu/ml/datasets/arrhythmia
KDDCUP-Revこの論文の著者が KDDCUP データセットから改めて抽出したデータセットですね。元々の KDDCUP データセットは全体の20%が「正常通信」で残りの80%が「マルウェア攻撃」ですが、DAGMM による異常検知でも「正常通信」を正常として取り扱うため、「正常通信」のデータは全て保持しておいて、それに加えて「正常通信」のデータサイズの20%の件数にあたる「マルウェア攻撃」をランダムに抽出して、全体の80%が「正常通信」であるような新しいデータセットを用意したということのようです。

(次回があれば)つづく

論文読みメモ: 深層自己符号化器+混合ガウスモデルによる教師なし異常検知(その1)

以下の論文を読みます。

Bo Zong, Qi Song, Martin Renqiang Min, Wei Cheng, Cristian Lumezanu, Daeki Cho, Haifeng Chen. Deep Autoencoding Gaussian Mixture Model for Unsupervised Anomaly Detection. International Conference on Learning Representations, 2018. https://openreview.net/forum?id=BJJLHbb0-
※ キャラクターに元ネタはないです。お気付きの点がありましたらお手数ですがコメント等にてご指摘ください。
次回:まだ
f:id:cookie-box:20180513082851p:plain:w60

多次元データの教師なし異常検知をするときは次元削減と密度推定の2段階のアプローチをするのが常套手段ですが、次元削減は次元削減で、密度推定は密度推定で学習しているために局所最適解に陥りやすい、という問題提起です。次元削減の段階で異常検知に必要な情報が失われてしまう可能性があると。

f:id:cookie-box:20180513082908p:plain:w60

そりゃ次元削減で情報が失われるかもって言われたら失われるかもしれないけど…じゃあどうするのかな?

f:id:cookie-box:20180513082851p:plain:w60

なので次元削減と密度推定をもっと効果的に組合せたいということですが…この方針は何もこの研究が初出ということではなく、過去に既に考案されているらしいです。ただそれらの先行研究では低次元空間での特徴保存や、密度推定モデルの表現力や、密度推定の学習アルゴリズムに難があったと。

f:id:cookie-box:20180513082908p:plain:w60

まあコンセプトが同じだったら何が優れているのか明記しなきゃだしね。

f:id:cookie-box:20180513082851p:plain:w60

それで提案手法の深層自己符号化混合ガウスモデル(Deep Autoencoding Gaussian Mixture Model: DAGMM)ですが、このモデルでは深層自己符号化器によって低次元の特徴を生成し、また再構築時の誤差も得て、それらを混合ガウスモデルに導入すると。深層自己符号化器と混合ガウスモデルは別々に学習するのではなく、通常のEMアルゴリズムで学習するのでもなく、深層自己符号化器と混合ガウスモデルのパラメータを同時に最適化すると。

f:id:cookie-box:20180513082908p:plain:w60

再構築時の誤差も導入するの? 低次元の特徴だけではなくて? なんでだろう。

f:id:cookie-box:20180513082851p:plain:w60

Introduction でこの DAGMM のウリが3つ紹介されているんですが、たぶんその1つ目の内容がそれを説明していますね。1つ目のウリはDAGMM は従来手法と違って入力データ中の必要な情報をちゃんと保持できる。次元削減してから密度推定するとき、密度が小さいところに異常データは位置しますが、そもそも異常なデータは次元削減しにくい=次元削減した点から再構築したときの誤差が大きくなるものだとこの論文は主張しています。なので、この再構築エラーをも低次元の特徴に concatinate して、この新しい特徴ベクトルの空間で密度推定すると。Figure 1 で例が示されていますね。x 軸近辺のデータはほとんど再構築エラーがなかった=適切に次元削減されたデータで、この x 軸近辺でも正常データと異常データは分離されていますが、それに加えて再構築エラーが大きいという y 軸方向にも正常データと異常データは分離されています。従来手法はこの y 軸方向の広がりを見逃していたと。でも DAGMM の第一段階「圧縮ネットワーク」は自己符号化器によって次元削減し、低次元の特徴に再構築エラーをくっつけて密度推定のプロセスに渡すと。

f:id:cookie-box:20180513082908p:plain:w60

そっか。「入力データ中の必要な情報を保持できる」ってどういうことかと思ったけど、従来手法では失われていた情報をサルベージしたって雰囲気なのかな。必要な情報を拾い切ってやったっていうよりは。

f:id:cookie-box:20180513082851p:plain:w60

2つ目のウリは、密度推定に混合ガウスモデルを採用する。従来手法は混合モデルでないので表現力に乏しいと。ただ、混合モデルを採用する場合、混合モデルは通常EMアルゴリズムでパラメータ推定しますが、これは次元削減と密度推定を同時に最適化するには具合が悪いです。

f:id:cookie-box:20180513082908p:plain:w60

うーん、EステップとMステップを反復するのが厄介ってことかな。それで完結しちゃうと次元削減側に示唆がないよね。わかんないけど。じゃあどうやって混合分布のパラメータを最適化するのかな?

f:id:cookie-box:20180513082851p:plain:w60

そこで「推定ネットワーク」を導入してそのサンプル(の特徴)が何番目の分布に帰属するかの予測を出力します。それをもとに混合分布のパラメータを直接推定します。入力データのもとで尤度が最大のパラメータを。Eステップなんて必要なかったんです。

f:id:cookie-box:20180513082908p:plain:w60

ええ…それって自然な発想なのかな…。まあ、「特徴分布は2つのガウス分布の混合らしい」とか最初からわかってたら、各サンプルがどちらに属すだろうかっていうクラス分類をニューラルネットワークでやっちゃえっていうのはわからなくもない、のかな。あと、それで全体を学習するときって、あくまで「圧縮ネットワーク」は再構築エラーを目的関数に学習して「推定ネットワーク」はその特徴のもとでの尤度を目的関数に学習するの? それとも、全体として尤度を目的関数にする? 後者だと自己符号化器部分が自己符号化器じゃなくなっちゃうけど、前者だとそれって「次元削減と密度推定の同時学習」っていえるのかな。

f:id:cookie-box:20180513082851p:plain:w60

Introduction だけ読むと前者にはみえますが、おいおい数式が出てくればわかるでしょう。3つ目のウリは、もう散々言及されてしまいましたが、次元削減と密度推定の同時学習が実現でき、ネットワークの事前学習も不要ということですね。

(次回があれば)つづく