本シリーズでは、Pythonを使用して機械学習を実装する方法を解説します。
また各アルゴリズムの数式だけでなく、その心、意図を解説していきたいと考えています。
本記事では、クラス分類問題において、SGDではうまくいかなかった場合に使用するkernel-approximation(カーネル近似)について、実装・解説します。
scikit-learnのマップの以下の黒矢印に対応します。
START→データが50以上→カテゴリーデータ→ラベルありデータ→データ数10万以上→SGDがうまくいかない→「kernel approximation」
SGDでうまくいかないXORのデータを生成
まずはじめにSGDではうまくいかないXORのデータを生成します。
そして前回のSGDをかけてみると以下のような結果となります。
これは、横軸x1が正で、縦軸x2が正だと0となり、横軸x1が正で、縦軸x2が負だと1となるようなXORの関係でラベリングしたデータです。
これにSGDをかけると無理やり真ん中あたりに斜めの識別平面が引かれていますが、正答率は45%程度と、まったく識別できていません。
このように、SGDでは直線で分割できないクラス分類には対応できません。
そこで、カーネル近似を利用します。
上図のXORをSGDで学習したコードを掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | # 1:ライブラリのインポート-------------------------------- import numpy as np #numpyという行列などを扱うライブラリを利用 import pandas as pd #pandasというデータ分析ライブラリを利用 import matplotlib.pyplot as plt #プロット用のライブラリを利用 from sklearn import linear_model, metrics, preprocessing, cross_validation #機械学習用のライブラリを利用 from mlxtend.plotting import plot_decision_regions #学習結果をプロットする外部ライブラリを利用 # 2:XORのデータを作成する(x=正、y=正)=0,(x=正、y=負)=1, 的な-------------- np.random.seed(0) X_xor=np.random.randn(200,2) y_xor=np.logical_xor(X_xor[:,0]>0, X_xor[:,1]>0) y_xor=np.where(y_xor,1,0) pd.DataFrame(y_xor) #この行を実行するとデータが見れる # 3:プロットしてみる------------------------------------------------------ %matplotlib inline plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==1, 1], c='b', marker='x', label='1') plt.scatter(X_xor[y_xor==0, 0], X_xor[y_xor==0, 1], c='r', marker='s', label='0') plt.legend(loc='best') plt.show # 4:データの整形------------------------------------------------------- X_std=X_xor z=y_xor # 5:機械学習で分類する--------------------------------------------------- clf_result=linear_model.SGDClassifier(loss="hinge") #loss="hinge", loss="log" clf_result.fit(X_std, z) # 6:K分割交差検証(cross validation)で性能を評価する--------------------- scores=cross_validation.cross_val_score(clf_result, X_std, z, cv=10) print("平均正解率 = ", scores.mean()) print("正解率の標準偏差 = ", scores.std()) # 7:トレーニングデータとテストデータに分けて実行してみる------------------ X_train, X_test, train_label, test_label=cross_validation.train_test_split(X_std,z, test_size=0.1, random_state=0) clf_result.fit(X_train, train_label) #正答率を求める pre=clf_result.predict(X_test) ac_score=metrics.accuracy_score(test_label,pre) print("正答率 = ",ac_score) #plotする X_train_plot=np.vstack(X_train) train_label_plot=np.hstack(train_label) X_test_plot=np.vstack(X_test) test_label_plot=np.hstack(test_label) #plot_decision_regions(X_train_plot, train_label_plot, clf=clf_result, res=0.01) #学習データをプロット plot_decision_regions(X_test_plot, test_label_plot, clf=clf_result, res=0.01, legend=2) #テストデータをプロット |
カーネル近似の実装
同じ問題に対して、カーネル近似を実装した結果とコードを紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | # 1:ライブラリのインポート-------------------------------- import numpy as np #numpyという行列などを扱うライブラリを利用 import pandas as pd #pandasというデータ分析ライブラリを利用 import matplotlib.pyplot as plt #プロット用のライブラリを利用 from sklearn import linear_model, metrics, preprocessing, cross_validation #機械学習用のライブラリを利用 from mlxtend.plotting import plot_decision_regions #学習結果をプロットする外部ライブラリを利用 from sklearn.kernel_approximation import RBFSampler #カーネル近似用の関数 from matplotlib.colors import ListedColormap #plot用 # 2:XORのデータを作成する(x=正、y=正)=0,(x=正、y=負)=1, 的な-------------- np.random.seed(0) X_xor=np.random.randn(200,2) y_xor=np.logical_xor(X_xor[:,0]>0, X_xor[:,1]>0) y_xor=np.where(y_xor,1,0) pd.DataFrame(y_xor) #この行を実行するとデータが見れる # 3:プロットしてみる------------------------------------------------------ %matplotlib inline plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==1, 1], c='b', marker='x', label='1') plt.scatter(X_xor[y_xor==0, 0], X_xor[y_xor==0, 1], c='r', marker='s', label='0') plt.legend(loc='best') plt.show # 4:データの整形------------------------------------------------------- X_std=X_xor z=y_xor #解説 5:カーネル近似を適用する------------------------------------------ rbf_feature = RBFSampler(gamma=1, n_components=100, random_state=1) X_std = rbf_feature.fit_transform(X_std) print("X_stdの大きさ ",pd.DataFrame(X_std).shape) #pd.DataFrame(X_std).to_clipboard() #これでクリップボードに保持できるのでエクセルに貼れる # 6:機械学習で分類する--------------------------------------------------- clf_result=linear_model.SGDClassifier(loss="hinge") #loss="hinge", loss="log" # 7:K分割交差検証(cross validation)で性能を評価する--------------------- scores=cross_validation.cross_val_score(clf_result, X_std, z, cv=10) print("平均正解率 = ", scores.mean()) print("正解率の標準偏差 = ", scores.std()) # 8:トレーニングデータとテストデータに分けて実行してみる------------------ X_train, X_test, train_label, test_label=cross_validation.train_test_split(X_std,z, test_size=0.1, random_state=1) clf_result.fit(X_train, train_label) #正答率を求める pre=clf_result.predict(X_test) ac_score=metrics.accuracy_score(test_label,pre) print("正答率 = ",ac_score) # 解説 9:Plotする x1_min, x1_max, x2_min, x2_max=-3, 3, -3, 3 resolution=0.02 xx1, xx2=np.meshgrid(np.arange(x1_min, x1_max, resolution),np.arange(x2_min, x2_max, resolution)) X=(np.array([xx1.ravel(), xx2.ravel()]).T) plot_z=clf_result.predict(rbf_feature.fit_transform(X)) colors=('red','blue') cmap=ListedColormap(colors[:len(np.unique(plot_z))]) plot_z=plot_z.reshape(xx1.shape) plt.contourf(xx1,xx2, plot_z, alpha=0.4, cmap=cmap) |
※今回は元々正規化されているのでデータの整形はありませんが、正規化が必要です。
解説 5:カーネル近似を適用する
この部分でカーネル近似を実施しています。
カーネル近似には「RBFSampler」と呼ばれるRadial Basis Function Kernelを近似する手法を使用しています。
RBFはいわゆるガウスカーネルです。
引数ガンマは分散の逆数に対応し、カーネルの形(急峻具合)を決めます。
n_components=100は、つくる非線形項の数を決めています。
元は2次元だったデータが100次元に拡張されます。
random_state=1は、乱数のseedを1に固定して、結果が再現されるようにしています。
解説 9:Plotする
この部分では識別結果のカラーマップを描画しています。
mlxtend.plotting の plot_decision_regions では今回対応できないので、地道に描画しています。
それでは「結局、カーネル近似って何をやっていたの?」を説明します。
カーネル近似の心
正確な情報は以下をご覧ください。
●scikit-learnのkernel-approximationの解説ページ
「カーネル近似」を知るには、「カーネルトリック」について知る必要があります。
カーネルトリックを知るには、今まで使っていた「線形分離」という言葉の意味を理解しないといけません。
いままでの「線形分離」の線形って言っていたのは、何が線形なのか?
それは、識別面が変数x1, x2, ・・・(とあとbias項)の線形和で表現するから、線形という言葉が当てはめられていました。
でも、今回はx1, x2,・・・の線形和ではうまく識別面を作れません・・・
それなら、非線形にしよう♪
となります。
イメージとしては以下の動画の通りです。
データn1がM次元で表わされ、n1=(n11, n12,・・・n1M)だった場合、非線形ということは、n11^2とか、n11*n12とか、ばんばん作ってやろうぜって感じです
そして、x1, x2, x1^2, x2^2, x1*x2, ・・・・とかの線形和にすれば、識別面作れるだろうってなります。
非線形項なんてx1^100とか作ればいくらでも作れるので、もはやなんでもありです。
(サンプル数より作っても意味ないですが・・・)
ということで(x1,x2,・・・xM)から非線形項(x1^2, x2^2,・・・ )を作る変換(写像)をφで表すと、φ(x)=(x1^2,x2^2,・・・)となります。
このφ(x)によりできた非線形項を使って識別平面を作成し、識別平面の係数を学習させたいということになります。
その過程で2つの問題点が出てきます。
1つ目の問題はデータn1とn2の写像されたもの同士の内積φ(n1)φ(n2)を計算するのが大変だということです。
写像φで次元が増えているので・・・
ですが、ここで良い感じのφ()を使ってあげると、φ(n1)φ(n2)が簡単に計算できる場合があります。
良い例
そこで、φ(n1)φ(n2)を愚直に計算しないで、都合の良いφ()のもとで、φ(n1)φ(n2)をn1とn2の内積を使って簡単に求める方法を「カーネルトリック」といいます。
このカーネルトリックを使った状態で識別する手法をカーネルSVMと呼びます。
ですが困ったことに、問題点がもうひとつあります。
2つ目の問題点はφ(n1)*φ(n2)みたいなのが要素となる行列がカーネルSVMには必要で、その次元はデータ数Nに応じた次元になるという点です。
つまり、N×Nの行列を作る必要があります。
これはデータ数Nが多いときにはかなり困難となります。
そこで、カーネル近似の出番となります。
結局カーネルトリックを捨てて、φ(n1)=(n11^2,n12^2,・・・)を求めます。
えっ、それだと1つ目の問題にまた戻るけど・・・って感じですが、そこで近似的にφ(n1)=(n11^2,n12^2,・・・)みたいなのを求めてあげる手法が「カーネル近似」です。
RBFSamplerの場合、乱数(モンテカルロ法)に従って近似しています。
さらに具体的に説明するのはブログ上では難しいですが、以上がカーネル近似の心です。
結局、
「非線形項を近似的に作ったよ~
それを使ってSGDClassifierするよ~」
って感じです。
とはいえ、いろいろ疑問が湧くかと思います。
疑問:カーネルはどれを使えば良いの?カーネルの係数や次元は?
カーネル近似を使用するには、
●非線形項を何次元分作るのか
●どんな形のどのカーネルを使うのか
を決める必要があります。
ですが、決まったルールはありません。
こうしたハイパーパラメータを設定するひとつの作戦としては、バリデーションデータを用意する方法があります。
手持ちのデータを学習データ、テストデータ、バリデーションデータに分けます。
そしてカーネルの設定やSGDのパラメータを決めるのに、学習データで識別器を作成して、バリデーションデータで性能を評価します。
その結果性能が良かったハイパーパラメータの設定を採用し、その設定でテストデータに対して識別を行って、最終的な性能を決めます。
また、rbfカーネルの場合、gammaには1/次元数が用いられることが多いです。
以上、Pythonとscikit-learnで学ぶ機械学習入門|第2回:クラス分類 -カーネル近似-でした。
次回は、Linear SVMについて解説します。