Search on the blog

2015年6月14日日曜日

Facebook Recruiting IV: Human or Robot?

Kaggleの”Facebook Recruiting IV: Human or Robot?”の参加記を書こうと思います。

problem
オークションサイトの活動状況を見て、ユーザーが人間かボットかを判定する。
ユーザーが参加したオークション、入札時間、ユーザーが使用したデバイス、ユーザーのIPアドレス、リファレンス元URL、ユーザーの国コードなどの情報が与えられる。

challenge
ユーザーと入札という2つのデータが与えられるが、1:Nの関係にあるため、ユーザーが行った入札活動の総合的な情報を何らかの特徴量として抽出しなければならない。

approach
大まかには以下の流れで処理を行った。
  1. 特徴量抽出
  2. 特徴選択
  3. モデルの選択
  4. モデルの評価、パラメータ調整
1. では最大100個以上の特徴量を抽出した。

2. ではRandom Forestのfeature_importances_を使って明らかに重要度の低い特徴を削除した。

3. ではRandom Forest、Logistic Regression、AdaBoost、Ensemble SVMの比較を行い、最終的にRandom Forestを使うことにした。

3. の時点でローカル環境とサーバー上のAUCスコアに乖離が見られたため、overfittingが発生していると考えた。overfittingを軽減するために、特徴量のさらなる絞り込みを行うことにした。
ただし単純にfeature_importances_の高いものをgreedyに選ぶだけでは良い結果が得られなかった。そこで最適な特徴量の”組み合わせパターンを選ぶ”という問題に帰着させ、simulated annealingを行うことにした。特徴量数を[6, 9, 12, 15, 18]とし、特徴量はwith replacementで選べるようにした。
これにより異なるサブセット特徴量に基づく複数のモデルが出来るため、性能のよいものを50~100個程度選び平均をとったものを最終結果とした。

抽出した主な特徴量は以下のとおり。
  • 入札回数
  • 入札時間間隔の中央値
  • 入札時間間隔の分散
  • 1回しか入札を行なっていないIPアドレスの割合
  • IPアドレスのGini Impurity
  • 携帯モデルのGini Impurity
  • 参照元アドレスのGini Impurity
  • 参加オークション数
  • ブラックリスト国コードの割合(※)
  • ブラックリスト携帯モデルの割合(※)
※予めボットが好む国コード、携帯モデルを算出しておき、ブラックリストに登録。

result
59th / 985, AUC Score 0.93196.
ということで初参加にしては上出来かと。
時間をかければTop 10%に入れることが分かった。

暫くは常にTop 25%キープでなるべく多くのコンテストに参加することを目標にしようと思う。

future tasks
  • パラメータチューニング作業の完全自動化
  • データのビジュアライズ化(入札時刻がマスクされていて昼夜などの判定ができなかったが、グラフ化すると1周期=1日としてunmask出来たらしい)

2015年6月13日土曜日

PyplotでGradient Descentを可視化

目的関数を三次元グラフにプロット
以下では、目的関数をz = 2x2 + 5y2 - 4xyとする.
まず目的関数をグラフ表示してみる.

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np

# define x and y
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)

# define z: z = 2*x*x + 5*y*y - 4*x*y
vfunc = np.vectorize(lambda x, y: 2*x*x + 5*y*y - 4*x*y)
Z = vfunc(X, Y)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)

plt.show()

等高線のプロット
次に等高線をプロットしてみる.plt.contourの第4引数で等高線の本数を指定できる.

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np

# define x and y
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)

# define z: z = 2*x*x + 5*y*y - 4*x*y
vfunc = np.vectorize(lambda x, y: 2*x*x + 5*y*y - 4*x*y)
Z = vfunc(X, Y)

plt.figure()
CS = plt.contour(X, Y, Z, 20, colors='black')

plt.show()

Gradient Descentの可視化
最後に、Gradient Descentで目的関数を最小化する様子をグラフ化してみる.左側のサブグラフに探索点が移動する様子を、右側のサブグラフに目的関数値が減少していく様子を示した.

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np

# define x and y
x = np.arange(-10, 10, 0.1)
y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x, y)

# define z: z = 2*x*x + 5*y*y - 4*x*y
func = lambda x, y: 2*x*x + 5*y*y - 4*x*y

# gradient descent
alpha = 0.03
itr = 50
A = [[2, -2], [-2, 5]]  # matrix expression of function z
xt = [5]
yt = [10]

for _ in range(itr):
    x_cur = xt[-1]
    y_cur = yt[-1]
    dx, dy = 2 * np.dot(A, [x_cur, y_cur])
    xt.append(x_cur - alpha * dx)
    yt.append(y_cur - alpha * dy)

# graph plot
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(12, 6, forward=True)

Z = np.vectorize(func)(X, Y)
ax1.contour(X, Y, Z, 20, colors='black')
ax1.plot(xt, yt, 'bo')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title('points plot on contour')

zt = map(lambda (a, b): func(a, b), zip(xt, yt))
ax2.plot(zt, 'r')
ax2.set_xlabel('iteration')
ax2.set_ylabel('z value')
ax2.set_title('z value against iteratoin')

fig.subplots_adjust(wspace = 0.5)
plt.show()

2015年6月7日日曜日

scikit-learnで線形回帰

単純な線形回帰
まず簡単な例から。
y = 3 x + 1 + err
という特性を満たすデータからサンプリングを行い、xとyの関係を求める。
ただし、err ~ N(0, 0.12)とする。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

# create samples
sample_size = 30
err_sigma = 0.1

x = np.random.rand(sample_size, 1)
err = err_sigma*np.random.randn(sample_size, 1)
y = 3 * x + 1 + err

# train a linear regression model
regr = LinearRegression()
regr.fit(x, y)

# make predictions
xt = np.linspace(0.0, 1.0, num=1000).reshape((1000, 1))
yt = regr.predict(xt)

# plot samples and regression result
plt.plot(x, y, 'o')
plt.plot(xt, yt)
plt.show()

二次関数の回帰
次に、多項式基底関数を作って二次関数の回帰を試してみる。

以下の関係を満たすデータからサンプルを行うこととする。
y = -0.3 x2 + 1 + err,
err ~ N(0, 0.52).

scikit-learnにはPolynomialFeaturesという多項式基底を作成する関数があるので、この関数を使えばよい。
また、Pipelineを使うことで基底の作成〜モデルの学習までの処理を纏めることが出来る。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# create samples
sample_size = 30
err_sigma = 0.5

x = 10*np.random.rand(sample_size, 1)
err = err_sigma*np.random.randn(sample_size, 1)
y = -0.3 * x*x + 1 + err

# train a linear regression model
regr = Pipeline([
    ('poly', PolynomialFeatures(degree=2)),
    ('linear', LinearRegression())
])
regr.fit(x, y)

# make predictions
xt = np.linspace(0.0, 10.0, num=1000).reshape((1000, 1))
yt = regr.predict(xt)

# plot samples and regression result
plt.plot(x, y, 'o')
plt.plot(xt, yt)
plt.show()

sinの回帰
最後に以下の関係を満たすデータの回帰を行う。

y = sin(x) + err,
err ~ N(0, 0.12).

基底として用いる多項式の次数を上げることで、よりよくフィットしていく様子が分かる。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from math import sin

# create samples
sample_size = 100
err_sigma = 0.1

x = 12*np.random.rand(sample_size, 1)
err = err_sigma*np.random.randn(sample_size, 1)
func = np.vectorize(sin)
y = func(x) + err

# plot train data
plt.plot(x, y, 'o')

# train linear regression models with different polynomial basis
deg = [1,3,6]
for d in deg:
    regr = Pipeline([
        ('poly', PolynomialFeatures(degree=d)),
        ('linear', LinearRegression())
    ])
    regr.fit(x, y)

    # make predictions
    xt = np.linspace(0.0, 12.0, num=1000).reshape((1000, 1))
    yt = regr.predict(xt)

    # plot regression result
    plt.plot(xt, yt, label='polynomial of degree %d' % (d))

plt.legend()
plt.show()