ランダムフォレスト (random forest) を用いたオートエンコーダ (Autoencoder) の実装

はじめに

少し変わり種かもしれませんが、異常検知の際に用いられるオートエンコーダ(Autoencoder)をランダムフォレスト(random forest)を使って実装したという論文が面白そうだなと思ったので、既に実装されているテストコード(https://github.com/AntoinePassemiers/Encoder-Forest)と論文(https://arxiv.org/abs/1709.09018)を読んで簡単に実装してみたいと思います。

特にこのモデルはニューラルネットのオートエンコーダよりかなり速いみたいなので、ある程度精度が担保された速いオートエンコーダを使いたいという時にはいいかもしれません。

Encoder Forest、略してeForestとは

彼らの論文の中では、EncoderForestを略してeForestと呼んでいます。ネットで「eForest」で検索をかけるといくつかランダムフォレストのオートエンコーダがヒットするので、この呼び方で通じそうですね。

さて、このeForestはどういうものかというと、ランダムフォレストにinputするデータがどの葉ノード(leaf node)に入るかわかれば、その経路の情報を用いて元の入力を再構成することができるということみたいです。例えば論文中の図1(下図)をみてみると、その経路が赤く塗られています。

例えばx1に注目してやれば、Tree 1では0<=x1<2.7ということがわかり、Tree 2ではx1>=0.5、Tree nではx1<1.6なのでまとめると、0.5<=x1<1.6であるということがわかります。この情報を用いてx1の代表値を出してやり、デコードすると入力データを復元できるといった内容です。また、この各決定木の分類の部分集合等も情報になりえるということについても言及されています。

また復元する際に用いる代表値は、Maximal-Compatible Rule(MCR)というルールを用いて計算し、特に先ほど挙げたx1のような数値の代表値を計算する際には、下限と上限の平均を取る操作が入ったりします(ここもルールは自由に選択できるので、それによって精度も変わってきそうです。)。この実装では、既存の設定に従ってMCRを計算することとします。

eForestをpythonで実装してみる

さて、おおまかなロジックがわかったので次に実装の方に移りたいと思います。実装はpythonを用いて行います。さらに、公開されているテストコード(https://github.com/AntoinePassemiers/Encoder-Forest)に沿って組んでみます。

結論からいうと、このテストコードに少し手を加えてMnistのデータを複数復元するようにしています。以下にそのコードを貼ります。


from encoder import EncoderForest
from keras.datasets import mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape((x_train.shape[0], -1))
x_test = x_test.reshape((x_test.shape[0], -1))

encoder = EncoderForest(1000)
encoder.fit(x_train, max_depth=20) # Fit embedding trees (unsupervised eForest)
test_encoded=encoder.encode(x_test)
encoded = encoder.encode(x_train) # Encode the entire training set


save_decoded=[]
for j in range (10):
    print(j)
    decoded = encoder.decode(test_encoded[j]) # Decode 

    # Intuitively, the performance of an embedding tree could be measured
    # as the log(volume) returned by this tree divided by the log(volume) of the MCR.

    # Get the path rules used to decode the first instance
    rule_list = encoder.compute_rule_list(test_encoded[j])

    # For each path rule (hyper-parallelepiped), compute its log(volume)
    for i, path_rule in enumerate(rule_list):
        log_volume = path_rule.compute_volume()
        #print("Log-volume of hyper-parallelepiped %i (tree %i): %f" % (i, i, log_volume))

    # Get the intersection of the subspaces described by the path rules
    MCR = encoder.calculate_MCR(rule_list)

    # Compute the log(volume) of the subspace described by the MCR
    #print("MCR log-volume: %f" % MCR.compute_volume())

    # Decode by generating a random sample in the final subspace
    decoded = MCR.sample()
    save_decoded.append(decoded)

はじめに、公開されているテストコードをgithubからダウンロードし、EncoderForestをインポートします。さらにこのコードでは、trainデータを用いて教師ありの実装をしています。

また、元のテストコードではMCRの値がプリントされているので、ランダムフォレストの数が多すぎる場合はソースコードからそれを取り除くと見やすくなるかもしれません。

結果

最後に出力した結果をランダムフォレストの数に応じてチェックしてみます。データ(画像)の出力は以下で行いました。

import matplotlib.pyplot as plt
%matplotlib inline

for i in range(len(save_decoded)):
    plt.figure(figsize=(10, 4))  # Figure size

    # Plot for original image
    plt.subplot(1, 2, 1)
    plt.imshow(x_test[i].reshape(28, 28), cmap='gray')
    plt.title('Original Image')
    plt.axis('off')

    # Plot for decoded image
    plt.subplot(1, 2, 2)
    plt.imshow(save_decoded[i].reshape(28, 28), cmap='gray')
    plt.title('Decoded Image')
    plt.axis('off')

    plt.tight_layout()
    plt.show()

EncoderForest(10)の場合:tree数=10

EncoderForest(200)の場合:tree数=200

EncoderForest(1000)の場合:tree数=1000

tree数を多くすればするほど精度もいい感じで上がっていますね。また、現在のデータ(MNIST)では1000以上パラメータをあげてもあまり変わらないので、tree数=1000辺りをパラメータ数値の上限として色々試してみるのがいいかもしれません。またmax_depthも20程度なのでこのパラメータを変更することでより精度が高くなる可能性もあります。

以上が、異常検知等で用いられるオートエンコーダ+ランダムフォレストのモデルeForestについての実装になります。ニューラルネットで組んだモデルとどの程度の差があるかなどまだまだ調べる余地がたくさんあるので、引き続き勉強していきたいと思います。