Help us understand the problem. What is going on with this article?

deeplearn.js で Autoencoder

More than 3 years have passed since last update.

MNIST のデータを使って、 deeplearn.js で Autoencoder を実装しました。
はじめに断っておくと、僕は Web フロントエンジニアで、機械学習はずぶの素人です。

作ったもの

https://github.com/kimamula/deeplearnjs-autoencoder

動かすと、 cost の変化や、その時点での Autoencoder の出力結果がブラウザに表示されます。
よかったら遊んでみてください。

Autoencoder とは

詳細な説明は省きますが、自己符号化器と訳されるもので、 encoder, decoder で変換した出力が、入力と同じになるように学習を行います。
DNN の事前学習や、データ圧縮、異常検知などの目的で利用されます。

作ったもの解説

主にこちらの TensorFlow の実装を参考にして作りました。
学習部分だけ抜き出すと、以下のような感じです。

const graph = new Graph();
const rni = new RandomNormalInitializer(0, 0.1);
const ci = new ConstantInitializer(0.001);
const x = graph.placeholder('x', [INPUT_VECTOR_LENGTH]);
const math = new NDArrayMathGPU();

// 1st encoding phase
const WEncode1 = graph.variable('WEncode1', rni.initialize([INPUT_VECTOR_LENGTH, 256], 0, 0));
const bEncode1 = graph.variable('bEncode1', ci.initialize([256], 0, 0));
const hEncode1 = graph.sigmoid(graph.add(graph.matmul(x, WEncode1), bEncode1));

// 2nd encoding phase
const WEncode2 = graph.variable('WEncode2', rni.initialize([256, 128], 0, 0));
const bEncode2 = graph.variable('bEncode2', ci.initialize([128], 0, 0));
const hEncode2 = graph.sigmoid(graph.add(graph.matmul(hEncode1, WEncode2), bEncode2));

// 1st decoding phase
const WDecode1 = graph.variable('WDecode1', rni.initialize([128, 256], 0, 0));
const bDecode1 = graph.variable('bDecode1', ci.initialize([256], 0, 0));
const hDecode1 = graph.sigmoid(graph.add(graph.matmul(hEncode2, WDecode1), bDecode1));

// 2nd decoding phase
const WDecode2 = graph.variable('WDecode2', rni.initialize([256, INPUT_VECTOR_LENGTH], 0, 0));
const bDecode2 = graph.variable('bDecode2', ci.initialize([INPUT_VECTOR_LENGTH], 0, 0));
const hDecode2 = graph.sigmoid(graph.add(graph.matmul(hDecode1, WDecode2), bDecode2));
const costTensor = graph.meanSquaredCost(x, hDecode2);

INPUT_VECTOR_LENGTH28 * 28 = 784 で、ここでは encoder で 784 -> 256 -> 128 と圧縮し、 decoder で 128 -> 256 -> 784 と次元を元に戻しています。
簡単ですね。

細かい話をすると、 initializer (RandomNormalInitializer, ConstantInitializer) の initialize メソッドの第二、第三引数に 0 を指定していますが、この値はなんでもいいです。
なぜなら使われていないから。
でも TypeScript の定義上必須の引数になっているので、渡さないとコンパイルが通りません。悲しい。

上のコードにはない部分ですが、元の Tensorflow の実装と違うところとして、以下のような点があります。

  • batch size を 256 -> 32 に変更
    • 256 だとすごく重かったので。。
  • optimizer を RMSPropOptimizer -> AdadeltaOptimizer に変更

結果

30,000 step ほど学習を回すと、以下のような出力が取れるようになりました。 (左が元画像、右が出力)

0.png
1.png
2.png
3.png
4.png
5.png
6.png
7.png
8.png
9.png

この間の cost の遷移は以下のような感じ。 (x 座標が step 数、 y 座標が cost)

learning-curve.png

ちょっと分かりづらいですが、一番最初は 0.13 くらいから始まり、そこから 0.3~0.4 くらいまではすぐに (100 step くらい) 下がって一旦そこで落ち着きます。
なので最初、その落ち着いたところで大体学習が終わったのかな、と思ってしまい、その時の結果がこんな感じだったので、

スクリーンショット 2017-12-17 16.13.59.png

実装失敗したかな?と思いました。
5,000 step くらいまで行くと、ようやく数字ごとに特徴的な形状が現れるようになり、安心しました。

どうも学習は二段階で進んでいるような印象があって、一段階目では、全ての数字に共通の、真ん中付近に白いものがある、というような特徴を学習し、その後二段階目で、数字ごとの特徴を学習するようです。
前者の学習はすぐに終わりますが、後者の方はかなり時間がかかリます。
こういう学習パターンもあるんですね。勉強になりました。

Convolutional Autoencoder

本当は、 Convolutional Autoencoder を実装したかったのですが、現時点では deeplearn.js による Convolutional Autoencoder の実装は難しそうです。
Tensorflow で Convolutional Autoencoder を実装する場合、 decode 時に tf.nn.conv2d_transpose() というメソッドを使うのですが、これに相当するものが deeplearn.js に用意されていないためです。
ひとまず今回は、関連する deeplearn.js の issue に、「こういうメソッドいりますよね?」みたいなコメントをつけるにとどめておきました。

終わりに

ブラウザ上での機械学習は、学習済みモデルをブラウザ上で動かすようなものが多いですが、学習からブラウザ上で動かせると、色々と可能性が広がってくると思います。
Autoencoder はモデルが単純なので、これで何か面白いことができないか、探っていきたいです。

kimamula
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away