終末 A.I.

Deep Learning を中心に、週末に機械学習するエンジニアのブログ

あえてDNNで渦巻きデータを学習してみる

TJO さんの下記のブログに触発されまして、NNで渦巻きデータを分類するタスクをやってみました。

使用したデータは、下記のコードにより適当に生成した渦巻きデータです。Neural Network Playgroundの渦巻きデータのように、中心と周辺でデータの分布が近しくなるようにちょっと調整しています。

K = 2
N = 200
X = np.zeros((N * K, 2))

for i in range(K):
    ix = range(N * i, N * (i+1))
    a = int(N/4)
    r = np.concatenate((
        np.linspace(0.0, 1.50, a),
        np.linspace(1.50, 2.75, a),
        np.linspace(2.75, 3.90, a),
        np.linspace(3.90, 4.76, a)))
    theta = 3 * (i + r)
    X[ix] = np.c_[r * np.sin(theta), r * np.cos(theta)]

生成されたデータを適当に半分に分割して、訓練データとテストデータとします。左から、データ全体、訓練データ、テストデータとなります。

f:id:KSKSKSKS2:20170827175504p:plain:w200 f:id:KSKSKSKS2:20170827175627p:plain:w200 f:id:KSKSKSKS2:20170827175639p:plain:w200

出オチですが、普通こうするよねというの先に書いておくと、カーネルSVM で RBFカーネルを使用した場合、きれいなロール型の識別境界を学習してくれます。テストデータでの精度は 0.96 と上々の結果になります。

svc = SVC(C=10.0, probability=True)
svc.fit(X_train, y_train)

f:id:KSKSKSKS2:20170827180906p:plain:w200

シンプルな極座標で表せるデータの生成プロセスと、別の空間にデータを写像しそれらを分離する超平面を学習するカーネルSVMの特性を考えれば、上記の結果はそこまで驚くものではないと思います。

前置きが長くなりましたが、このデータをNNで分類するとどうなるかやってみたいと思います。 類似のデータでは、スタンフォード大学の授業資料内で3クラス分類をやって、非線形な分類が行える例を示しているものがあります。

この記事とSVMで学習した際の識別境界を比べても分かるように、前処理もしていないこういった形式のデータの学習に対して、NNがそこまで向いていなさそうなことは、なんとなく察することができます。

とはいえ、どれだけできるのか?どうフィッテイングしようとするのか?は試してみなければ分かりません。

というわけで、TensorFlow 1.3.0 から追加された DNNClasifier クラスを使って上記のデータを学習してみました。DNNClasifier クラスは、TensorFlow の内部構造に合わせて作られているため、よく意図がわからない変なインターフェースですが、生のAPIよりはお気軽にDNNを組むことができます。

cls = tf.estimator.DNNClassifier(
    hidden_units=[20, 20, 20, 20],
    feature_columns=[tf.feature_column.numeric_column("x", shape=[2])],
    n_classes=2,
    optimizer=tf.train.ProximalAdagradOptimizer(
      learning_rate=0.05,
      l2_regularization_strength=0.001),
    activation_fn=tf.nn.relu,
    dropout=0.2)

train_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x": np.array(X_train)},
    y=np.array(y_train),
    batch_size=32,
    num_epochs=2000,
    shuffle=True)

cls.train(input_fn=train_input_fn)

上記のコードでは、隠れ層が4層でそれぞれ20次元、アクティベーション関数として ReLUを用い、入力データ2次元の2クラス分類を行うネットワークを構築しています。 L2正規化とドロップアウトを用い、学習アルゴリズムはAdagrad、バッチサイズは32、2000エポックの学習を行わせています。

learning_rate と dropout の値を変えながら学習した結果のうち比較的うまくいったケースを描画すると以下のようになります。

左から、learning_rate が 0.01 dropout なし、learning_rate が 0.05 dropout なし、learning_rate が 0.05 dropout 0.2、learning_rate が 0.05 dropout 0.5 の場合の識別境界と訓練データを描画したものになります。

f:id:KSKSKSKS2:20170827184009p:plain:w200 f:id:KSKSKSKS2:20170827184028p:plain:w200 f:id:KSKSKSKS2:20170827203506p:plain:w200 f:id:KSKSKSKS2:20170827203841p:plain:w200

learning_rate が 0.01 のものでは、学習がちゃんと完了できていないようで、識別境界があいまいになってしまっています。

learning_rate を 0.05 にした場合は、学習が完了し、むしろ訓練データに対して過学習してしまっている傾向が見られます。

過学習防止用に dropuout を追加すると、識別境界が緩やかになり、0.2 でちょうど渦巻きの形を再現するような識別境界を学習できる傾向にありました。

この場合のテストデータでの精度は、0.93 くらいですのでこのあたりがまあ限界ではないかと思います。

SVMの結果と比較してみますと、識別境界をきれいな渦巻状としては学習できておらず、細かい矩形を組み合わせてなんとかデータからそれっぽい境界を学習しているように見えます。

もしかするともっと良い設定があるかもしれませんが、正直なんとか頑張ってみました感がぬぐえず、それならSVMの方が良いなといったところではないでしょうか。

また、この論文にあるように、Dropout を用いて学習した重みで予測時もDropoutを使用すると、その出力はベイズ推定で得られた事後分布からサンプリングしたものと等しくなります。*1

これを利用して、上記までの結果も推定値をもとにサンプリングして、どれくらい自信のある予測結果なのかを見てみることにします。*2

以下は、dropout を使用してそれぞれの座標で予測結果を100サンプル取得して、その平均をもとに描画したものになります。*3

上から、learning_rate が 0.05 dropout 0.2、learning_rate が 0.05 dropout 0.5 の場合の通常の識別境界の描画と、dropout を予測時にも適用した場合の識別境界を描画したものです。

f:id:KSKSKSKS2:20170827203506p:plain:w200 f:id:KSKSKSKS2:20170827204859p:plain:w200

f:id:KSKSKSKS2:20170827203841p:plain:w200 f:id:KSKSKSKS2:20170827204915p:plain:w200

特に dropout 0.2 の時が分かりやすいですが、二つの点が混じってどっちか分かりにくい部分に関しては実はあまり予測結果に自身がないことが分かります。

渦巻状の識別境界になんとかフィッティングしているように見えますが、実のところ、「なんとなくこんな感じになっている気がする」といった具合に予測しているのでしょう。

このあたりは、DNNの柔軟性とも捉えることができますが、上記までの結果は、「その柔軟性が一番良いアプローチではない時もある」ということを示しているように思います。

*1:こちらのブログ記事で詳細が丁寧に説明されていますが、数学的に変分推定した結果と等しいと考えることができます。

*2:DNNClasifier を用いていると、残念なことに予測時にDropoutを使用する場合、ややトリッキーなことをする必要があります。詳しくはコードを参照ください。

*3:計算方法、描画方法ともにちょっと微妙な気がしてはいる。