機械学習のお勉強(scikit-learnを使ってみる:分類問題)
教科書
Perceptron
実装
# -*- coding: utf-8 -* from sklearn import datasets import numpy as np # Added version check for recent scikit-learn 0.18 checks from distutils.version import LooseVersion as Version from sklearn import __version__ as sklearn_version if Version(sklearn_version) < '0.18': from sklearn.cross_validation import train_test_split else: from sklearn.model_selection import train_test_split # 正規化 from sklearn.preprocessing import StandardScaler # パーセプトロン from sklearn.linear_model import Perceptron from sklearn.metrics import accuracy_score # load data iris = datasets.load_iris() X = iris.data[:, [2, 3]] y = iris.target print('Class labels:', np.unique(y)) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0) # 正規化 sc = StandardScaler() sc.fit(X_train) X_train_std = sc.transform(X_train) X_test_std = sc.transform(X_test) # Perceptron Fit ppn = Perceptron(n_iter=40, eta0=0.1, random_state=0) ppn.fit(X_train_std, y_train) # show accuracy y_pred = ppn.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
大事なのは
# Perceptron Fit ppn = Perceptron(n_iter=40, eta0=0.1, random_state=0) ppn.fit(X_train_std, y_train)
結果
Misclassified samples: 4 Accuracy: 0.91
結果を可視化(コードは教科書Git参照) 👇
3クラス分類において、線形の決定境界で完全に区切ることができない
ロジスティック回帰
分類のためのモデルで、線形分類問題と2値分類問題に使用される
ロジスティック関数
起こりやすさを表すオッズ比(P =p/(1-p))の対数の逆関数
入力として実数を受け取り、[0,1]の範囲の値に変換する。出力はそのクラスに所属している確率と解釈できる。
例として、降水確率や病気の確率を出す際などに利用されている
コスト関数
J = cost func p = probability if y==0: J = -log(1-p) elis y==1: J = -log(p)
実装
上記パーセプトロンのPerceptron Fit以降を下のように書き換える
from sklearn.linear_model import LogisticRegression lr = LogisticRegression(C=1000.0, random_state=0) lr.fit(X_train_std, y_train) y_pred = lr.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred)) # output probability print lr.predict_proba(X_test_std[0,:])
結果
Misclassified samples: 1 Accuracy: 0.98 [[ 2.05743774e-11 6.31620264e-02 9.36837974e-01]]
パーセプトロンより向上している
lr = LogisticRegression(C=1000.0, random_state=0)
のCについて以下で説明する
L2正規化
極端なパラメータの重みにペナルティをかす。コスト関数に正規化の項を追加すればよい
+ lambda / 2 * w2 **をコスト関数に足すことで、さらには、lambdaを大きくすることで、重みを小さく保ちながら学習させることができる。
J = cost func p = probability w = weight if y==0: J = -log(1-p) + lambda / 2 * w**2 elis y==1: J = -log(p) + lambda / 2 * w**2
C = 1 / lambdaを表している。Cの値を減らすと重みが小さくなる。
正規化をするメリットは?
直感的には、正規化することによりニューラルネットワークがより小さな重みを好むようになります。 大きな重みが許されるのは、そうすることがコスト関数の第1項を余程大きく改善する場合だけです。 言い換えると、正規化とは重みを小さくすることと元のコスト関数を小さくすることの間でバランスを取る方法であると 見ることもできます。このバランスを取る上で、2つの要素の相対的な重要性を決定するのが λλ の値です: λλ が小さい時は元のコスト関数を最小化することを好み、λλ が大きい時には より小さな重みを好むのです。
さて、実際のところ、そのようなバランスを取ることがなぜ過適合を軽減する助けになるのか、 その理由は誰の目にもすぐさま明らかなものではありません。しかし、正規化が実際に過適合を軽減することは分かっています。
もっと深堀りすると 👇
正規化されたニューラルネットワークで期待されるように、 ニューラルネットワークの大部分では小さな重みを持つと仮定しましょう。 重みが小さいということは、ここそこでランダムな入力を変化させても ニューラルネットワークの振る舞いが大きくは変わらないことを意味します。 そのため、正規化されたニューラルネットワークでは、 データに含まれる局所的なノイズの効果を学習しづらくなっています。 その代わり、正規化されたニューラルネットワークは訓練データの中で繰り返し見られる データの特徴に反応するのです。対照的に、大きな重みを持つニューラルネットワークは、 入力の小さな変化に敏感に反応してその振る舞いを大きく変えてしまいます。 そのため、正規化されていないニューラルネットワークは、大きな重みを使って、 訓練データのノイズに関する情報をたくさん含んだ複雑なモデルを学習してしまうのです。 要するに、正規化されたニューラルネットワークは訓練データに頻繁に現れるパターンに基づいた 比較的シンプルなモデルを構築します。そして、訓練データが持つノイズの特異性を学ぶことに対して 耐性を持つのです。このため、ニューラルネットワークがノイズではなく現象そのものに対する真の学習をして、 それをより良く汎化できるのではないかと、希望が持てます。
サポートベクターマシン
マージン最大化を行う
コスト関数
cost func = 1 / 2 * w**2
スラック変数を用いた際のコスト関数
epsilon = slack variable cost func = 1 / 2 * w**2 + C * (sum(epsilon))
このCを変化させることで、バイアスとバリアンスのトレードオフを調整できる
二次計画法
数学的に考えてみる(ハードマージンSVM) - Shogo Computing Laboratory
http://numaf.net/Z9/Z9a/html/THESIS/H15/abst_toda.pdf
http://www.r.dl.itc.u-tokyo.ac.jp/~nakagawa/SML1/kernel1.pdf
実装
上記パーセプトロンのPerceptron Fit以降を下のように書き換える
svm = SVC(kernel='linear', C=1.0, random_state=0) svm.fit(X_train_std, y_train) y_pred = svm.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
結果
Misclassified samples: 1 Accuracy: 0.98
カーネルSVM
考え方:射影関数によってデータを高次元空間へ射影し、線形分離できるようにする
デメリット
射影変換(phi(x))を行うと本来、学習過程で生じるxxである計算がphi(x)phi(x)となるため、計算コストがかかる
しかし、カーネルトリックを用いることで軽減できる
k(x_i,x_j) = phi(x_i) * phi(x_j) # Radial Basis Function kernel k(x_i,x_j) = exp(-(x_i - x_j)**2 / (2 * sigma**2)) # Simple Radial Basis Function kernel k(x_i,x_j) = exp(- gamma *(x_i - x_j)**2 )
Radial Basis Function kernelは2つのサンプル間の類似度を表してる
実装
上記パーセプトロンのPerceptron Fit以降を下のように書き換える
先のkernelのパラメータがlinearからrbfに変わっただけ 👇
svm = SVC(kernel='rbf', random_state=0, gamma=0.10, C=10.0) svm.fit(X_train_std, y_train) y_pred = svm.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
パラメータのgammaは
# Simple Radial Basis Function kernel k(x_i,x_j) = exp(- gamma *(x_i - x_j)**2 )
のgammaで小さくするとなめらかに、大きくすると複雑になる
gamma = 0.2にすると 👇
gamma = 100.0にすると 👇
未知のデータでは汎化誤差が生じることが予想される
決定木
意味解釈可能性に配慮する場合に良いモデル
上記のようなカテゴリだけでなく実数でも可能。閾値を1.0などと決めるだけで良い。
決定木の深さが深くなる場合、深さの制限や剪定が必要になる。
情報利得の最大化
目的関数(二分木)
親ノードの不純度と子ノードの不純度の合計の差。つまり、子ノードの不純度が低いほど情報利得は大きくなる
I = Impurity(不純度) Dp = Parent DataSet Dleft = Child DataSet Dright f = function I(Dp, f) = I(Dp) - Nleft/Np * I (Dleft) - Nright/Np * I (Dright)
不純度:エントロピー
t:ノード
相互情報量が最大化になるよう試みる。p(1|t)=1 or p(0|t)=0の時エントロピーは0。p(1|t)=1/2 or p(0|t)=1/2の時エントロピーは1。
Ih = - sum(p(i|t)log2 p(i|t))
不純度:ジニ不純度
誤分類の確率を最小化
Ig = sum(p(i|t) * (1 - p(i|t))) = 1 - sum(p(i|t)**2)
エントロピーと同様、p(1|t)=1 or p(0|t)=0の時エントロピーは0。p(1|t)=1/2 or p(0|t)=1/2の時エントロピーは1。
不純度:分類誤差
すべてのサンプルが最大条件付き確率を与えるクラスに所属すると予測した時に間違えるサンプルの割合を表現する
Ie = 1 - max(p(i|t))
不純度の比較
実装
上記パーセプトロンのPerceptron Fit以降を下のように書き換える
決定木の場合、特徴量のスケーリングは必要ない。
from sklearn.tree import DecisionTreeClassifier tree = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=0) tree.fit(X_train, y_train) y_pred = tree.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred)) # 決定木の可視化 from sklearn.tree import export_graphviz export_graphviz(tree, out_file='tree.dot', feature_names=['petal length', 'petal width'])
ランダムフォレスト
決定木のアンサンブルと考えることができる
トレーニングデータからランダムにn個のサンプルを抽出
決定木を成長させる
上記を繰り返す
決定木ごとの予測をまとめて多数決に基づいてクラスラベルを割り当てる
「はじめてでもわかる RandomForest 入門-集団学習による分類・予測 -」 -第7回データマイニング+WEB勉強会@東京
メリット
パラメータの設定に悩む必要が少なく、上記ステップ1,2を何回繰り返すかを設定すれば良い。繰り返す回数を増やせば性能が上がるが計算コストが増える。
ポイント
上記ステップ1のnは多くの場合サンプルと同じ数に設定するのが良い
実装
上記パーセプトロンのPerceptron Fit以降を下のように書き換える
10個決定木からランダムフォレストをトレーニング
from sklearn.ensemble import RandomForestClassifier forest = RandomForestClassifier(criterion='entropy', n_estimators=10, random_state=1, n_jobs=2) forest.fit(X_train, y_train) y_pred = forest.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
k近傍法
怠惰学習の代表例。識別関数を学習せずトレーニングデータセットを暗記する。ノンパラメトリックモデル。
kの値と距離指標を選択
分類したいサンプルからk個の最近傍のデータ点を見つける
多数決によりクラスラベルを割り当てる
実装
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski') # Power parameter for the Minkowski metric knn.fit(X_train_std, y_train) y_pred = knn.predict(X_test_std) print('Misclassified samples: %d' % (y_test != y_pred).sum()) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred))
結果
Misclassified samples: 0 Accuracy: 1.00