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

望月いちろうのREADME.md

書き溜めておいた技術記事や旅行記のバックアップです。

Pythonで SVM (サポートベクターマシーン)を利用する

Sklearnを利用する

Pythonでの機械学習の定番はScikit-learn(sklearn)という名前のライブラリです。インストールは簡単です。

pip3 install sklearn

インストールが無事完了したら、次に進みます。

SVMで何ができるの?

SVM(サポートベクターマシン)で可能になるのは、分類問題です。たとえば、それぞれ形と色が異なる丸いものが沢山あります。これを2つの種類に分類したいとします。どうすればよいでしょうか?

簡単ですね、線を引けばよいのです。もし2つに分けたクラスに新しくデータが入ったときのためにできるだけ2つのクラスは話しておけばよさそうです。

このとき(特徴ベクトルに変換して、この例では分かり易いように2次元にプロット)こんな形をしている場合は難しいことを考えずに単純に直線を引けば大丈夫そうです。

昔の偉い人もこのように考えました。数学的にはマージン最大化問題として定式化されます。つまり、できるだけ2つのクラスに境界になる点と分類用の直線との距離を最大化しておきたいのです。

数学的には以下のように定式化されます。 距離の最小値が1となるよう重みを調整する

{}

􏰀􏰀􏰁$$ min_i | w x_i - w_0 | = 1 $$

このとき,学習データの境界面の最小距離, すなわちマージンは

􏰀􏰀􏰀 $$ min_i D (x_i) = min_i \frac{ | w x_i + w_0 | }{ || w || } = \frac{1}{ || w|| } $$

􏰃􏰄 􏰅􏰆􏰇 􏰈 􏰂􏰂 すべての学習データを分類できる条件として

$$ y_i (w x_i + w_0) >= 1 $$

􏰀􏰀􏰁

つまり,マージン最大化は,この条件のもとでの

$$ || w || $$

の最小化になる

ラグランジュの未定乗数法により

$$ L(w,w_0, {\alpha}) = \frac{1}{2} || w ||^2 - \sum_{i}^{N} {\alpha}_i 􏰁 􏰋􏰌(y_i (w x_i + w_0) -1 ) $$

で偏微分して極値(最小値)の場合は

􏰋􏰆􏰇 􏰋􏰌

􏰊􏰀$$ \frac{\partial L}{\partial w_0} = 0 = \sum_{i}^{N} {\alpha}_i 􏰁 􏰋􏰌y_i $$

$$ \frac{\partial L}{\partial w} = 0 w = \sum_{i}^{N} {\alpha}_i 􏰁 􏰋􏰌y_i x_i $$

$$ L({\alpha}) = $$

$$ \frac{1}{2} \sum_{i}^{N} {\alpha}_i {\alpha}_j 􏰌y_i 􏰋y_j x_i x_j - \sum_{i}^{N} {\alpha}_i $$

適切なアルゴリズムを選択して$$ {\alpha} $$を求める

これによって分離平面

$$ w x + w_0 = 0 $$

が得られる 􏰀􏰀 􏰊

複雑な境界面をしている場合

上のように直線で簡単に境界面を引ければよいですが、実際は2つのカテゴリ間で複雑な境界面をしている場合があります。このようなときに一筋縄では行きません。

このようなときに有用になるのがカーネル法です。直感的に言えば、高次元(2次元の場合ならば、3次元空間上)にデータを移す(射影する)ことによって、対応することが可能です。一般的にこれをカーネルトリックと呼びます。

$$ K(x,x') = {\phi} (x) {\phi} (x') $$

$$ g({\phi} (x_j)) = $$

$$ \sum_{i}^{N} {\alpha}_i {y_i} {\phi} (x) {\phi} (x') + w_o = \sum_{i}^{N} {{\alpha}_i} y_i K(x,x')+ w_o $$

$$ L({\alpha}) = \frac{1}{2} \sum_{i,j}^{N} {\alpha}_i {\alpha}_j 􏰌y_i􏰁 􏰋􏰌y_j K(x, x') - {\sum_{i}^{N}} {\alpha}_i $$

このときにカーネル関数を選択する必要があります。一般的なのは線形カーネルとガウシアン(RBF)カーネルです。

線形カーネル

$$ K(x,x') = x \cdot x' $$

ガウシアンカーネル

$$ K(x,x') = exp (- \frac{||x-x'||}{{\sigma}^2}) $$

多項式カーネル

$$ K(x,x') = {(x \cdot x' + l)}^p $$

ディープラーニングが流行る前には、機械学習といえば、サポートベクターマシンが非常に人気でした。その理由はカーネル法によって非常に複雑な分類問題にも対応できるからです。

教師あり学習

基本的にSVMは教師あり学習です。

つまり、モデルを推定するためには、データと分類済みのラベルのセットが必要になります。

実際の推定

今回利用するのはirisのデータです。これはアメリカ原産の3種のアヤメの花の特徴量とその種類ラベルのセットで、非常にまとまりのよいデータセットです。今回は分類器の性能を検証するために半分を教師データとして、もう半分をテストデータとして利用します。

最初にパッケージをロードする。

from sklearn import datasets
from sklearn import svm
clf = svm.SVC()

アイリスのデータセットをインポートします

 iris = datasets.load_iris()

次にラベル(Y)と特徴ベクトル(X)の組から実際の推定を行います。今回はRBFカーネルを利用します。

>>> clf.fit(iris.data[1::2],iris.target[1::2])


SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,


  decision_function_shape=None, degree=3, gamma='auto', kernel='rbf',


  max_iter=-1, probability=False, random_state=None, shrinking=True,


  tol=0.001, verbose=False)

そして上で学習したモデルを利用してデータの分類をすることが可能です。当然ですが、モデルの学習に使ったのとは別のデータを用意してください。今回はテスト用に残したデータを利用します。

>>> y_pred = clf.predict(iris.data[0::2])

>>> y_pred

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

       0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1,

       1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,

       2, 2, 2, 2, 2, 2])

結果を見る(統計量を計算) 先ほどはモデルにテストデータをあてはめてラベル値の推定を行いました。モデルの性能を測るために本当のラベルと比較してその性能を見てみましょう。

print(classification_report(iris.target[0::2], y_pred, target_names=["Setosa","Versicolour","Virginica"]))

結果

precision recall f1-score support
Setosa 1.00 1.00 1.00 25
Versicolour 1.00 0.92 0.96 25
Virginica 0.93 1.00 0.96 25
avg / total 0.98 0.97 0.97 75

precision

適合率 Aと予測したラベルの内、Aに分類できた割合を示します。

recall

再現率 正しいラベルに分類できた割合を示します。

f1-score

適合率と再現率の調和平均 $$ 2 \frac{pre \cdot rec}{pre + rec} $$

F値のこと

カーネルを変更する

線形カーネル

clf = svm.SVC(kernel='linear')
clf.fit(iris.data,iris.target)
y_pred = clf.predict(iris.data[0::2])
print(classification_report(iris.target[0::2], y_pred, target_names=["Setosa","Versicolour","Virginica"]))
precision recall f1-score support
Setosa 1.00 1.00 1.00 25
Versicolour 1.00 1.00 1.00 25
Virginica 1.00 1.00 1.00 25
avg / total 1.00 1.00 1.00 75

多項式カーネル

clf = svm.SVC(kernel='poly')
clf.fit(iris.data,iris.target)
y_pred = clf.predict(iris.data[0::2])
print(classification_report(iris.target[0::2], y_pred, target_names=["Setosa","Versicolour","Virginica"]))
precision recall f1-score support
Setosa 1.00 1.00 1.00 25
Versicolour 1.00 0.96 0.98 25
Virginica 0.96 1.00 0.98 25
avg / total 0.99 0.99 0.99 75