読者です 読者をやめる 読者になる 読者になる

カテゴリー変数に embedding layer を用いたNeural Net

kaggle のRossmann の3 位のNeokami Inc(entron)さんの用いた手法が面白かったので、その概要の紹介などをしていきたいと思います。
まず手法の名前は、"Entity Embeddings of Categorical Variables" で、
[1604.06737] Entity Embeddings of Categorical Variables
論文にもなっています。コードはGithub にありますので、興味のある方はご覧ください。
github.com
(これはPython3 を用いて書かれています。)

embedding layer を用いた他のコンペでの有力なsolution。
kaggle のtaxi コンペ: Taxi Trajectory Winners’ Interview: 1st place, Team ? | No Free Hunch
Netflix: Deep learning solution for netflix prize | karthkk

Rossmann のコンペでは、いくつかの日に対して各店舗の売上の予測を行っています。
今回は"Entity Embeddings of Categorical Variables"の手法について見ていきます。まずカテゴリ変数の扱いはたいてい、1つの変数ですべて整数に置き換えて表現、あるいはone-hot encoding のようにダミー変数で boolean 型で表現するだと思いますが、この手法を用いることで、カテゴリー変数の近さ(類似度)を図示できるというのが面白いところです。たとえば、曜日については
f:id:puyokw:20160521214850p:plain
のように図示されます。これを見ると平日と休日で売り上げの傾向が異なるというのが分かります。同様に、店舗ごとや月ごとのデータもプロットできます。(上記のgithub に記載されています)
f:id:puyokw:20160521215346p:plain

カテゴリー変数を上手く扱いたいときあるいは、NN でカテゴリー変数が多いときに試してみたいなと思っています。
やり方としましては、NeuralNet でembedding layer を用いて学習し、そのモデルを用いてt-sne で次元を落として視覚化しています。
コードの方は、

models = []
# 曜日 7 -> 7 x 6 -> 6 
model_dow = Sequential()
model_dow.add(Embedding(7, 6, input_length=1))
model_dow.add(Reshape(dims=(6,)))
models.append(model_dow)

# 月 12 -> 12 x 6 -> 6 
model_month = Sequential()
        model_month.add(Embedding(12, 6, input_length=1))
        model_month.add(Reshape(dims=(6,)))
        models.append(model_month)
# 他も同様
# 上で定義したモデルを結合する
self.model = Sequential()
self.model.add(Merge(models, mode='concat'))
# 以下はいつも通り
self.model.add(Dropout(0.02))
self.model.add(Dense(1000, init='uniform'))
self.model.add(Activation('relu'))
self.model.add(Dense(500, init='uniform'))
self.model.add(Activation('relu'))
self.model.add(Dense(1))
self.model.add(Activation('sigmoid'))
self.model.compile(loss='mean_absolute_error', optimizer='adam')

で、イメージとしては
f:id:puyokw:20160523013817p:plain
となります。
次に、始めに図示していたplot のやり方についてです。これは上図の7 x 6 や12 x 6 となっている部分を視覚化のためにどちらも 6 -> 2 次元(3次元でもOK)へとt-sne で次元を落とします。すなわち、それぞれ 7 x 2 と 12 x 2 となります。

import pickle
from sklearn import manifold
import matplotlib.pyplot as plt
import numpy as np

# 作成したNN のmodel を読み込む
with open("models.pickle", 'rb') as f:
    models = pickle.load(f)

# entity embedding layer の重みをそれぞれ分かりやすい名前を付ける
model = models[0].model
weights = model.layers[0].get_weights()
store_embedding = weights[0]
dow_embedding = weights[1]
year_embedding = weights[4]
month_embedding = weights[5]
day_embedding = weights[6]
german_states_embedding = weights[20]
woy_embedding = weights[21]
weather_event_embedding = weights[30]

# entity embedding layer の重みに対してt-sne を適用する
# 曜日
tsne = manifold.TSNE(init='pca', random_state=0, method='exact')
Y = tsne.fit_transform(dow_embedding)
names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat','Sun']
# plot する
plt.figure(figsize=(8,8))
plt.scatter(-Y[:, 0], -Y[:, 1])
for i, txt in enumerate(names):
    plt.annotate(txt, (-Y[i, 0],-Y[i, 1]))

plt.savefig('dow_embedding.png')

# 月
tsne = manifold.TSNE(init='pca', random_state=0, method='exact')
Y = tsne.fit_transform(month_embedding)
names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
plt.figure(figsize=(8,8))
plt.scatter(-Y[:, 0], -Y[:, 1])
for i, txt in enumerate(names):
    plt.annotate(txt, (-Y[i, 0],-Y[i, 1]))

plt.savefig('month_embedding.png')

などで行えます。距離はt-sne 空間での距離となっています。
他のコンペで実際に試してみたいなと思っています。