機械学習の入門編 とりあえずライブラリを使ってデータを分類してみる

今回は前回設定した環境を使って、何らかのデータを分類する方法を書きます。

とりあえず機械学習に触ってみたい人向けにPythonプログラムを書いて結果が出るまでを解説します。アルゴリズムの詳細やハイパーパラメータの意味は専門書を読んだほうがいいので書きません。(間違っていたら怖いし)プログラムの全文は最後に書いてます。

今回扱う問題

今回は教師あり学習の中の分類問題を扱います。教師あり学習は、すでに知っている入力Xと出力Yの情報からいい感じのXとYの関係を推定して、新しい入力X’が来た時にその関係性を使って正しいY’を求めることができるプログラム[学習器]を生成することが目標になります。画像に何が写ってるかを当てるみたいな問題を分類問題、立地とか家の素材、周辺地域の平均年収とかから家の値段を推定するみたいな問題を回帰問題といいます。今回扱うのは分類問題です。でも画像の分類みたいな楽しそうなやつではなく地味なデータでやります。

-SVMを使って分類してみる-

データ例はこれです。

この点を生成するスクリプトはこちら。

    SIZE = 1000
d = [[0,0,0,0,0,0,0,0,1,0],
[0,1,2,2,2,2,2,2,1,0],
[0,1,2,0,0,0,0,0,1,0],
[0,1,2,0,2,2,2,0,1,0],
[0,1,2,0,0,0,2,0,1,0],
[0,1,2,2,2,2,2,0,1,0],
[0,1,1,1,1,1,1,0,1,0],
[0,1,0,0,0,0,0,0,1,0],
[0,1,0,0,0,0,0,0,1,0],
[0,1,1,1,1,1,1,1,1,0]]
x = []
y = []
ud = np.random.rand #alias
for _ in range(SIZE):
a = ud()*10
b = ud()*10
x.append([a, b])
y.append(d[int(a)][int(b)])
x = np.array(x)
x = np.reshape(x,(-1,2))
y = np.array(y)

1000点生成して訓練用、テスト用にそれぞれ500点ずつ分割します。

ここで、numpyのappendはpythonネイティブの[].appendと比べて滅茶苦茶遅いので使わないようにします。

本来はもう一つ本当のテスト用にデータを分割しないといけないんですが、ややこしいので今回はしません。

今回の機械学習の目標は1つ1つの点が3つのうちどのクラスから生成されたかを推定することです。

Xとyの具体的な数値はこのようになっています。例えばXとyの最初の要素は[12.13, 7.37]の座標に点があってそれは1の領域にあることを表しています。

さっそくですが問題を解くためのアルゴリズムを適当に決めてみましょう。それらはディープラーニングを含めてたくさんの種類があります。一般的なものを使用せる場合はライブラリが存在しているので自分でコードを書く必要はありません。

今回はSVMとニューラルネット(階層が深くなるとディープラーニング)をそれぞれ実装してみます。

各アルゴリズムが何をしてるのかは、ここではとても説明できないので必要に応じてパターン学習と機械学習(PRML)とかの参考書で。

まずscikit-learnライブラリを用いてSVMを実装します。scikit-learnにはSVCとSVRの関数がありますが、今回は分類問題なのでSVCを使用します。回帰問題の場合はSVRです。

from sklearn.svm import SVC
clf = SVC(C=30, kernel="rbf", gamma=0.01,class_weight = "balanced")
clf.fit(X_train, y_train)
y_predict = clf.predict(X_test)

このy_predictに予測したラベルが入っています。

SVCにはハイパーパラメータとしてC,カーネル,ガンマが存在します。これらの変数は自動的に決まるものではなく自分で与えてあげるもので、SVMに限らずほぼすべてのアルゴリズムに存在します。

これらの最適値はデータごとに変わるので、通常はグリッドサーチという作業を行い最適なものを選択します。C, ガンマ, カーネルに様々な値を入力したときの一例は以下の通りです。

linearカーネルは境界線が直線なのに対してrbfカーネルは境界線が曲線なのが分かります。またrbfカーネルのガンマが大きいほうがより境界線が複雑になっています。Cは各領域内にノイズが含まれないので300のほうが結果がよくなるようです。

-ニューラルネットも使ってみる-

先ほどはSVMを用いてデータを分類してみました。次はニューラルネットを用いて分類してみます。

データセットは全く同じです。

Kerasを使ってニューラルネットもモデルを作ります。今回は入力データが2次元、分類するクラスは3クラスなので間に隠れ層としてノード数が5のモデルは以下のスクリプトで作ることができます。

from keras.layers import Input, Dense, Activation, Dropout
from keras.models import Model, Sequential
from keras.wrappers.scikit_learn import KerasClassifier

def make_model():
model = Sequential()
model.add(Dense(5, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
adam = optimizers.Adam(lr = 0.001, decay = 0)
model.compile(loss='sparse_categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'],
)
return model

X_train = X_train.reshape((len(X_train), np.prod(X_train.shape[1:])))
y_train = np.reshape(y_train, (np.shape(X_train)[0],1))
clf = KerasClassifier(make_model, batch_size=100)
clf.fit(X_train, y_train, epochs=10000, validation_data=(X_test, y_test),)

このプログラムで入力層のノード数が2,隠れ層のノード数が5,出力層で3クラスの分類を行うモデルが作られます。表にするとこんなモデルです。

早速ですが結果を見てみましょう。

ぜんぜんですね。それでは次は隠れ層をものすごく増やして同じことをやってみましょう。

def make_model():
model = Sequential()
model.add(Dense(200, input_dim=2, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(3, activation='softmax'))
adam = optimizers.Adam(lr = 0.001, decay = 0)
model.compile(loss='sparse_categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'],
)
return model

結果はこうなりました。

先ほどよりはよくなりましたが、もっと良くするにはどうすればいいでしょうか。

原因を探るためにAccuracyとLossのepochごとの経過を見てみましょう。

ニューラルネットでは各ノードの適切な重みを計算するために何回も訓練データを使って学習します。

その回数がepoch数で,訓練データとテストデータそれぞれ見ます。

まずは層の少ないほう。

trainのAccuracyがそもそも低いのでネットワークの表現力が足りないことがわかります。

つまりノード数が少なすぎるということです。

次にノードの多いほうを見てみましょう。

trainのほうはaccuracy,lossともに回数を重ねるにつれて改善されてますが、testのほうは悪化しています。これは典型的な過学習の症状です。これを解消するにはノードを減らす、Dropoutやepoch数を減らす、trainデータを増やすことが考えられます。

実際Dropoutを行うコードを追加すると。まだ過学習気味ですが、結果はよくなります。

def make_model():
model = Sequential()
model.add(Dense(200, input_dim=2, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dense(200, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(200, activation='relu'))
model.add(Dense(3, activation='softmax'))
adam = optimizers.Adam(lr = 0.001, decay = 0)
model.compile(loss='sparse_categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'],
)
return model

結果はこうなりました。

緑の領域の左側で改善が見られます。

また、入力データ数を50倍にするとより正確な結果が出ます。

隠れ層の数を増やしていくとニューラルネットの表現力は増加しますが、計算にかかる時間は増加します。教師データ数25000,ノード数200の隠れ層の数が1から10の時の所要時間は前回使用したGPUサーバとノートパソコンでそれぞれ計測すると次のようになりました。一応スペックをもう一回。

手元のノートパソコン(ideapad 720S)GPUサーバー
CPUCore i7 8550U 1.8Ghz 4コア8スレッドXeon Silver 4116 × 2 2.1Ghz 24コア48スレッド
メモリ8GB128GB
GPUなしTesla V100 × 2 + 1080Ti × 2
OSUbuntu 16.04 DesktopUbuntu 16.04 Server

層が増えるにしたがって時間が増加していますが、増加率はノートパソコンのほうが多い結果になりました。CNNの時はもっと差があったのでモデルが複雑になるほど差が出るのかも?

今回はこれで終了します。本当はここからどのアルゴリズムを使うかや、適切なハイパーパラメータをチューニング、データの前処理をして正しい方法で評価し、一番使えるものを探さなければなりません。

まとめ

今回は本当に使ってみただけで雰囲気程度でした。

次回からは実際の業務に使えそうなものを作ることに挑戦しようと思います。

プログラム全文

import numpy as np
from matplotlib import pyplot as plt
import joblib

def make_data(SIZE):

d = [[0,0,0,0,0,0,0,0,1,0],
[0,1,2,2,2,2,2,2,1,0],
[0,1,2,0,0,0,0,0,1,0],
[0,1,2,0,2,2,2,0,1,0],
[0,1,2,0,0,0,2,0,1,0],
[0,1,2,2,2,2,2,0,1,0],
[0,1,1,1,1,1,1,0,1,0],
[0,1,0,0,0,0,0,0,1,0],
[0,1,0,0,0,0,0,0,1,0],
[0,1,1,1,1,1,1,1,1,0]]


x = []
y = []
ud = np.random.rand #alias
for _ in range(SIZE):
a = ud()*10
b = ud()*10
x.append([a, b])
y.append(d[int(a)][int(b)])
x = np.array(x)
x = np.reshape(x,(-1,2))
y = np.array(y)
joblib.dump(x,"mx.pkl")
joblib.dump(y,"my.pkl")

def main():
x = joblib.load("mx.pkl")
y = joblib.load("my.pkl")
dx = x
#データの一部を表示
fig = plt.figure()
ax = fig.add_subplot(111)
colors = ["#ff0000", "#00ff00", "#0000ff"]
print("Drawing Images")
if len(dx) > 3000:
px = dx[0:3000]
else:
px = dx
for i ,v in enumerate(px):
ax.scatter(v[0], v[1], c=colors[int(y[i])-1], marker='o', alpha = 0.3)
ax.set_title('Dataset')
print("Finish")
plt.show()
plt.close()

from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.model_selection import train_test_split

seed = 12345
(X_train, X_test, y_train, y_test) = train_test_split(x, y, test_size=0.50, random_state=seed)
for k in ["rbf", "linear"]:
for c in [30,300]:
for g in [0.01, 1]:
clf = SVC(C=c, kernel=k, gamma=g,class_weight = "balanced")
clf.fit(X_train, y_train)
y_predict = clf.predict(X_test)
ac = accuracy_score(y_test, y_predict)
f1 = f1_score(y_test, y_predict, average="macro")
fig = plt.figure()
ax = fig.add_subplot(111)
if len(X_test) > 3000:
px = X_test[0:3000]
else:
px = X_test
colors = ["#ff8800", "#00ff88", "#8800ff"]
print("Data plotting")
for i ,v in enumerate(px):
ax.scatter(v[0], v[1], c=colors[int(y_predict[i])-1], marker='o', alpha = 0.3)
ax.set_title('')
print("Finish")
print([k,c,g])
plt.show()
plt.close()
np.set_printoptions(suppress=True)
np.set_printoptions(threshold=np.inf, precision=2, floatmode='maxprec')
print(ac)
print(f1)

from keras.layers import Input, Dense, Activation, Dropout
from keras.models import Model, Sequential
from keras.wrappers.scikit_learn import KerasClassifier
from keras import optimizers

layers = 0

def make_model():
model = Sequential()
model.add(Dense(200, input_dim=2, activation='relu'))
for _ in range(layers):
model.add(Dense(200, activation='relu'))
model.add(Dense(3, activation='softmax'))
adam = optimizers.Adam(lr = 0.001, decay = 0)
model.compile(loss='sparse_categorical_crossentropy',
optimizer=adam,
metrics=['accuracy'],
)

#plot_model(model, to_file='model.png', show_shapes=True)
return model

X_train = X_train.reshape((len(X_train), np.prod(X_train.shape[1:])))
y_train = np.reshape(y_train, (np.shape(X_train)[0],1))

for i in range(10):
layers = i
clf = KerasClassifier(make_model, batch_size=100)
history = clf.fit(X_train, y_train, epochs=100, verbose = 0, validation_data=(X_test, y_test))

np.set_printoptions(suppress=True)
np.set_printoptions(threshold=np.inf, precision=2, floatmode='maxprec')
ac = accuracy_score(y_test, y_predict)
f1 = f1_score(y_test, y_predict, average="macro")
print(ac)
print(f1)

# summarize history for accuracy
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
# summarize history for loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()

y_predict = clf.predict(X_test)

fig = plt.figure()
ax = fig.add_subplot(111)
if len(X_test) > 3000:
px = X_test[0:3000]
else:
px = X_test
colors = ["#ff8800", "#00ff88", "#8800ff"]
for i ,v in enumerate(px):
ax.scatter(v[0], v[1], c=colors[int(y_predict[i])-1], marker='o', alpha = 0.3)
ax.set_title('Test plot')
print("Finish")
plt.show()
plt.close()


if __name__ =="__main__":
make_data(50000)
main()

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中