ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

python+scikit-learnでSVM(サポートベクターマシン)の実験

遅ればせながら最近機械学習の勉強をちびちびとやっています。
とはいえもう数学的なことはよくわからず、TensorFlowとかガンガン動かせる
nVIDIAGPU搭載マシンとかも持っていないので、python+scikit-learnベースで
簡単な実験しつつ、まずは実践で動かして確かめる感じ。

データサイエンティスト養成読本とか、いろいろググって集めた資料とか
読み漁った結果SVM(サポートベクターマシン)というのがシンプルで、工夫次第で
応用効きそうということで簡単な実験をしてみることに。
実験用のLinux PCにanacondaをインストールして、numpyとかSciPyとかscikit-learn
とかをインストールして環境を作ります。ベースはpython3です。

その辺はあちこちに情報は転がっていると思います。

実験内容としては平面上(左下座標-1,-1,右上座標1,1の範囲)でランダムに点を打ち、
各点が原点を中心とする半径1の円の内側か外側かを学習させて任意の座標を判定
させてみる。もちろん機械学習とか使わずに点の座標のxとy使ってx^2+y^2の平方根
を計算して半径1と比較すれば..半径1が比較対象なので平方根すらいらないわけですが..
ビシっと答えはわかります。これを正解値として敢えて機械学習させてみる。

とりあえず4000個の座標をランダムに作りうち2000個について座標、円の内側or外側かを
判定した結果をSVMに流しこんで学習させ、残り2000個で結果を確認。
下に書いたサンプルソースで実行してみると、

elapsed time to learn 2000 samples: 0.140 [sec]
Predict OK num =  1961.0
Accuracy Rate =  0.9805

という感じで2000個の点を推定させて、正解率98%。何度か繰り返すと
正解率は96%〜98%くらいの間でばらつく感じです。
学習させる数を数万オーダーに上げていくと99%以上の正解率が得られました。

散布図は2つ生成していますが、
上の散布図は単純に学習対象とテスト用のランダム生成した点を円の内側を青、外側赤で表示したものと
下の散布図は学習後2000点を推定させた結果で色分けは下記の通りです。

正解値 推定値 判定
円内 円内 OK
円外 円外 OK
円内 円外 NG
マゼンタ 円外 円内 NG

# -*- coding: utf-8-*-
# SVMの実験
# 2次元の任意の点が、原点を中心とする半径1の円の内側か否かを
# SVMで学習させる

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn import cross_validation
import time

TESTSAMPLE=2000
TOTALSAMPLE=2000+TESTSAMPLE

# 原点からの距離を計算(半径1.0との比較なのでsqrtは蛇足だが..)
def calc_dist(point):
	return np.sqrt(point[0]**2+point[1]**2)

# Generate random point in 2dimension range (-1.0,-1.0 to 1.0,1.0)
sample = (np.random.random(TOTALSAMPLE * 2) * 2.0 - 1.0).reshape(TOTALSAMPLE,2)
dist_list = []
result_list = []

for index in range(TOTALSAMPLE):
	pt = sample[index]
	dist = calc_dist(pt)
	dist_list.append(dist)
	if dist <= 1.0:
		result_list.append(1)
	else:
		result_list.append(0)


result = np.array(result_list)

# Check Generated data
CHECKNUM = 20
print('---- Generated data first %d samples ----' % CHECKNUM)
for i in range(CHECKNUM):
	print('AXIS %f,%f: DISTANCE from (0,0) %f: In Circle %d' % (sample[i][0],sample[i][1],dist_list[i],result[i]))

# Draw graph
incircle = sample[result == 1]
outcircle = sample[result == 0]
plt.subplot(211)
plt.scatter(incircle[:,0],incircle[:,1],c='b')
plt.scatter(outcircle[:,0],outcircle[:,1],c='r')

svc = svm.SVC()
start = time.time()
svc.fit(sample[:-TESTSAMPLE],result[:-TESTSAMPLE])
end = time.time()

test_sample = sample[-TESTSAMPLE:]
test_result = result[-TESTSAMPLE:]
predict_result = svc.predict(test_sample)

test_result2_tmp = []
# Validate prediction and expected result
for i in range(TESTSAMPLE):
	if test_result[i] == 1 and predict_result[i] == 1:
		res = 0 # Predicted as inside Circle.Accurate
	elif test_result[i] == 0 and predict_result[i] == 0:
		res = 1 # Predicted as outside Circle.Accurate
	elif test_result[i] == 1 and predict_result[i] == 0:
		res = 2 # Predicted as inside Circle. Fail
	elif test_result[i] == 0 and predict_result[i] == 1:
		res = 3 # Predicted as outside Circle. Fail
	test_result2_tmp.append(res)

test_result2 = np.array(test_result2_tmp)
# print(test_result2)

predict_ok_in_circle = test_sample[test_result2 == 0]
predict_ok_out_circle = test_sample[test_result2 == 1]
predict_ng_in_circle = test_sample[test_result2 == 2]
predict_ng_out_circle = test_sample[test_result2 == 3]

OK_num =  (predict_ok_in_circle.size + predict_ok_out_circle.size)/2
print('elapsed time to learn %d samples: %.3f [sec]' % ((TOTALSAMPLE - TESTSAMPLE),end - start))
print('Predict OK num = ', OK_num)
print('Accuracy Rate = ', OK_num/TESTSAMPLE)

plt.subplot(212)
plt.scatter(predict_ok_in_circle[:,0],predict_ok_in_circle[:,1],c = 'g')
plt.scatter(predict_ok_out_circle[:,0],predict_ok_out_circle[:,1],c = 'b')
plt.scatter(predict_ng_in_circle[:,0],predict_ng_in_circle[:,1],c = 'r')
plt.scatter(predict_ng_out_circle[:,0],predict_ng_out_circle[:,1],c = 'm')
plt.show()

なお使用しているCPUはシングルコアのCeleronでCPU infoを抜粋するとこんな感じ。
学習用サンプルを20,000個した場合の学習時間は6.8secくらいでした。
入力が2次元ベクトルだからこの程度ですが、株価予測とかで過去200営業日の価格
とか入れるとどうなるかはそのうち試してみようかと

processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 22
model name	: Intel(R) Celeron(R) CPU          550  @ 2.00GHz
stepping	: 1
microcode	: 0x33
cpu MHz		: 1995.163

さて、最初の実験は任意の点がある円の内側にあるか否かの判定をさせるものでしたが、
条件を変えてある正方形の中にあるか無いかでやってみました。条件としては原点を囲む一辺の長さ1.0の正方形の内側にある点と外側にある点を学習させます。
その結果を図示したのが下記です。上が期待集合、下が20,000点を学習後2,000点を判定させた結果。

正解率は下記のように95.65%で20,000点学習で何度かやっても正解率は96%前後で円の判定より難しそうです。
直感的には境界線が滑らかでないものは難しいだろうという気がします。

elapsed time to learn 20000 samples: 9.359 [sec]
Predict OK num =  1913.0
Accuracy Rate =  0.9565

ちなみに学習点数を10万まで増やしてみましたが正解率はあまり変わりませんでした。
SVMのパラメータを変えてチューニング可能かは、別途見てみたいと思います。