人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習、Deep Learning、Kerasに関する記事が多いです。



Kerasによる2クラスロジスティック回帰

まずはもっとも簡単な2クラスロジスティック回帰モデルをKerasで書いてみる。前にTheanoでやった(2015/5/19)のをKerasで書き換えただけ。ロジスティック回帰は、回帰とつくけど分類のアルゴリズムで、隠れ層がなく、活性化関数にシグモイド関数を使ったニューラルネットとしてモデル化できる。

データは、PRMLの4章のex2data1 を使う。1列目と2列目がデータで3列目がクラスラベル(0または1の2クラス)。

ソースコード:ex2data1.py

データのロードと正規化

データを読み込むライブラリにはpandasなどもあるが、ここではnumpy.genfromtxt()を使う。Xがデータで二次元データのリスト、tがラベルで0または1のリスト。

# load training data
data = np.genfromtxt(os.path.join('data', 'ex2data1.txt'), delimiter=',')
X = data[:, (0, 1)]
t = data[:, 2]

データの各列の平均が0、標準偏差が1になるようにデータを正規化する。この正規化をしないと学習がまったく進まない(=lossが小さくならない)ことが多かったのでやった方がよさそう。sklearn.preprocessingモジュールに正規化のメソッドがあるので使う。

from sklearn import preprocessing
# normalize data
X = preprocessing.scale(X)

以下のコードでデータの各列が平均0、標準偏差1に正規化されたことが確認できる。データのaxis=0(行方向)に平均と標準偏差を求めている。平均は0っぽくないが、e-17なので限りなく0に近い。

print(np.mean(X, axis=0))
print(np.std(X, axis=0))
[ -7.66053887e-17   1.11022302e-15]
[ 1.  1.]

データの可視化

どのようなデータかmatplotlibで可視化する。

import matplotlib.pyplot as plt

def plot_data(X, t):
    positive = [i for i in range(len(t)) if t[i] == 1]
    negative = [i for i in range(len(t)) if t[i] == 0]

    plt.scatter(X[positive, 0], X[positive, 1], c='red', marker='o', label='positive')
    plt.scatter(X[negative, 0], X[negative, 1], c='blue', marker='o', label='negative')

# plot training data
plt.figure(1)
plot_data(X, t)

赤が正例(positive)で青が負例(negative)。この2クラスを直線で分類するのが目標。

f:id:aidiary:20161030205617p:plain

ロジスティック回帰モデルの構築

f:id:aidiary:20161030220153p:plain:w400

ロジスティック回帰モデルを組み立てる。空のSequentialモデルを作成し、そこにレイヤ(Dense)や活性化関数(Activation)を順番に追加し、最後にコンパイルする。Sequentialモデルというのは通常のニューラルネットのように層を積み重ねたモデルを指す。コンパイル時に最適化手法(optimizer)、損失関数(loss)、評価指標(metrics)を指定する。

# create the logistic regression model
model = Sequential()
model.add(Dense(1, input_shape=(2, )))
model.add(Activation('sigmoid'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
  • 入力データは2次元、出力は1次元
  • このタスクは0または1を予測する二値分類なので損失関数にはbinary_crossentropy
  • あとで実験するがMNISTのような多値分類ではcategorical_crossentropy
  • 最適化アルゴリズムにはAdam、評価指標には精度を用いた。これは訓練データで精度求めているのかな?要検証

Kerasの層(layer)は、一般的な重みがある層だけでなく、活性化関数も層とみなしている。下の実行結果からわかるようにActivationkeras.layersに含まれている。

print(model.layers[0])
print(model.layers[1])
<keras.layers.core.Dense object at 0x7f556811b0b8>
<keras.layers.core.Activation object at 0x7f556816a5c0>

modelの組み立て方は上の方法がスタンダードだけれど、Functional APIを使うともっと柔軟にモデルが作れるようだ。あとでこのAPIを使った書き方もしてみよう。

訓練

モデルの訓練はscikit-learnと同じくfit()という関数にデータとラベルを渡せばよい。

# fit the model
model.fit(X, t, nb_epoch=1000, batch_size=5, verbose=1)
  • fit()には、固定のエポック数(nb_epoch)、バッチサイズ(batch_size)、経過出力方法(verbose)を指定する。
Epoch 1/1000
100/100 [==============================] - 0s - loss: 0.6531 - acc: 0.6600
Epoch 2/1000
100/100 [==============================] - 0s - loss: 0.6455 - acc: 0.6700
Epoch 3/1000
100/100 [==============================] - 0s - loss: 0.6385 - acc: 0.6700
Epoch 4/1000
100/100 [==============================] - 0s - loss: 0.6310 - acc: 0.6800
Epoch 5/1000
100/100 [==============================] - 0s - loss: 0.6243 - acc: 0.6800
  • verbose=1にしておくと学習経過を棒グラフで表示してくれるので非常に便利!
  • あとで紹介するEarly-stoppingを使うと固定エポックだけループを回すのではなく、収束判定して止めてくれるようになる。

学習した重みの取得

model.layersリストに各層の情報が格納されている。好きな層を指定してget_weights()で重みが取得できる。重みが多次元リストなのでややこしいが、最初の次元は、[0]が重み、[1]がバイアス項を表すようだ。モデルからは重みだけでなく、出力も取り出せるのであとでまとめておこう。

# get the learned weight
weights = model.layers[0].get_weights()
w1 = weights[0][0, 0]
w2 = weights[0][1, 0]
b = weights[1][0]

ここでは、決定境界を描画したいので学習した重みw1、w2とバイアスbを取得した。

決定境界の描画

ロジスティック回帰の決定境界の条件は

 w1 * x1 + w2 * x2 + b = 0

なので [x1, x2] 平面上での直線の方程式に直すと

 x2 = - (w1 / w2) * x1 - (b / w2)

となる。この直線をmatplotlibで先ほどのグラフに追記する。

# draw decision boundary
plt.figure(1)
xmin, xmax = min(X[:, 0]), max(X[:, 0])
ymin, ymax = min(X[:, 1]), max(X[:, 1])
xs = np.linspace(xmin, xmax, 100)
ys = [- (w1 / w2) * x - (b / w2) for x in xs]
plt.plot(xs, ys, 'b-', label='decision boundary')
plt.xlabel('x1')
plt.ylabel('x2')
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
plt.legend()
plt.show()

結果は、

f:id:aidiary:20161030205644p:plain

となり、分類する直線が学習できていることがわかる。 今回は、テストデータを使った評価などは行っていない。

参考