空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

機械学習のお勉強(scikit-learnを使ってみる:分類問題)

教科書

GitHub - rasbt/python-machine-learning-book: The "Python Machine Learning (1st edition)" book code repository and info resource

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参照) 👇

f:id:robonchu:20171007142545p:plain

3クラス分類において、線形の決定境界で完全に区切ることができない

ロジスティック回帰

分類のためのモデルで、線形分類問題と2値分類問題に使用される

f:id:robonchu:20171007145614p:plain

ロジスティック関数

起こりやすさを表すオッズ比(P =p/(1-p))の対数の逆関数

f:id:robonchu:20171007144018p:plain

入力として実数を受け取り、[0,1]の範囲の値に変換する。出力はそのクラスに所属している確率と解釈できる。

例として、降水確率や病気の確率を出す際などに利用されている

コスト関数

J = cost func
p = probability

if y==0:
    J = -log(1-p)
elis y==1:
    J = -log(p)

f:id:robonchu:20171007145559p:plain

実装

上記パーセプトロン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]]

パーセプトロンより向上している

f:id:robonchu:20171007150358p:plain

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の値を減らすと重みが小さくなる。

f:id:robonchu:20171007152629p:plain

正規化をするメリットは?

参考:ニューラルネットワークと深層学習

直感的には、正規化することによりニューラルネットワークがより小さな重みを好むようになります。 大きな重みが許されるのは、そうすることがコスト関数の第1項を余程大きく改善する場合だけです。 言い換えると、正規化とは重みを小さくすることと元のコスト関数を小さくすることの間でバランスを取る方法であると 見ることもできます。このバランスを取る上で、2つの要素の相対的な重要性を決定するのが λλ の値です: λλ が小さい時は元のコスト関数を最小化することを好み、λλ が大きい時には より小さな重みを好むのです。

さて、実際のところ、そのようなバランスを取ることがなぜ過適合を軽減する助けになるのか、 その理由は誰の目にもすぐさま明らかなものではありません。しかし、正規化が実際に過適合を軽減することは分かっています。

もっと深堀りすると 👇

正規化されたニューラルネットワークで期待されるように、 ニューラルネットワークの大部分では小さな重みを持つと仮定しましょう。 重みが小さいということは、ここそこでランダムな入力を変化させても ニューラルネットワークの振る舞いが大きくは変わらないことを意味します。 そのため、正規化されたニューラルネットワークでは、 データに含まれる局所的なノイズの効果を学習しづらくなっています。 その代わり、正規化されたニューラルネットワークは訓練データの中で繰り返し見られる データの特徴に反応するのです。対照的に、大きな重みを持つニューラルネットワークは、 入力の小さな変化に敏感に反応してその振る舞いを大きく変えてしまいます。 そのため、正規化されていないニューラルネットワークは、大きな重みを使って、 訓練データのノイズに関する情報をたくさん含んだ複雑なモデルを学習してしまうのです。 要するに、正規化されたニューラルネットワークは訓練データに頻繁に現れるパターンに基づいた 比較的シンプルなモデルを構築します。そして、訓練データが持つノイズの特異性を学ぶことに対して 耐性を持つのです。このため、ニューラルネットワークがノイズではなく現象そのものに対する真の学習をして、 それをより良く汎化できるのではないかと、希望が持てます。

サポートベクターマシン

マージン最大化を行う

f:id:robonchu:20171007154052p:plain

コスト関数

cost func = 1 / 2 * w**2

スラック変数を用いた際のコスト関数

epsilon = slack variable
cost func = 1 / 2 * w**2 + C * (sum(epsilon))

このCを変化させることで、バイアスとバリアンスのトレードオフを調整できる

f:id:robonchu:20171007164718p:plain

二次計画法

線形SVM - 人工知能に関する断創録

数学的に考えてみる(ハードマージン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

f:id:robonchu:20171007165203p:plain

カーネルSVM

考え方:射影関数によってデータを高次元空間へ射影し、線形分離できるようにする

f:id:robonchu:20171007170455p:plain

デメリット

射影変換(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にすると 👇

f:id:robonchu:20171007172620p:plain

gamma = 100.0にすると 👇

f:id:robonchu:20171007172701p:plain

未知のデータでは汎化誤差が生じることが予想される

決定木

意味解釈可能性に配慮する場合に良いモデル

f:id:robonchu:20171007173102p:plain

上記のようなカテゴリだけでなく実数でも可能。閾値を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))
不純度の比較

f:id:robonchu:20171007180032p:plain

実装

上記パーセプトロン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'])

f:id:robonchu:20171007181056p:plain

f:id:robonchu:20171007181113p:plain

ランダムフォレスト

決定木のアンサンブルと考えることができる

  1. トレーニングデータからランダムにn個のサンプルを抽出

  2. 決定木を成長させる

  3. 上記を繰り返す

  4. 決定木ごとの予測をまとめて多数決に基づいてクラスラベルを割り当てる

アンサンブル学習

ランダムフォレスト

「はじめてでもわかる 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))

f:id:robonchu:20171007185228p:plain

k近傍法

怠惰学習の代表例。識別関数を学習せずトレーニングデータセットを暗記する。ノンパラメトリックモデル。

  1. kの値と距離指標を選択

  2. 分類したいサンプルからk個の最近傍のデータ点を見つける

  3. 多数決によりクラスラベルを割り当てる

f:id:robonchu:20171007185813p:plain

実装

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))

f:id:robonchu:20171007190729p:plain

結果

Misclassified samples: 0
Accuracy: 1.00