Pythonではじめる機械学習2(2.3.5~)メモ

引き続き「Pythonではじめる機械学習」を読み進めていく。

決定木

概要

決定木の構築過程では、特徴量iは値αよりも大きいか?小さいか?などというテストを繰り返し行う。このテストのうち、最も情報量の多いテスト(分け方)を選びながら後続のテストを進めていく。枝葉が1つのクラスか回帰値のみになること純粋(pure)といい、そこまでテストを繰り返す。

f:id:futurasan:20200728234133p:plain
tree
from sklearn.datasets import load_breast_cancer
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.tree import export_graphviz

cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)
tree = DecisionTreeClassifier(random_state=0)
tree.fit(X_train, y_train)

export_graphviz(tree, out_file="tree.dot", class_names=["malignant", " benign"], feature_names=cancer.feature_names, impurity=False, filled=True)

dotファイルをjupyter上で開こうとしてエラーが出る場合はpngに変換してしまえばとりあえずは良い。

dot -T png tree.dot > tree.png
open tree.png

dotコマンドが使えなければ、インストールされていないかもしれない。

brew install graphviz

葉が純粋になるまでテストすると訓練データに適合しすぎてしまうケースが多い。過剰適合を防ぐ方法としては、

  • 事前枝刈り(pre-pruning):テストの深さを制御するなどして木の成長を早めに止める
  • 事後枝刈り(post-pruning):情報の少ないノードの削除など

決定木は訓練データに過剰適合することが多く、決定木を実用する際にはアンサンブル方が用いられることが多い。

ランダムフォレスト
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
from sklearn.model_selections import train_test_split

X, y = make_moons(n_samples=100, noise-0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratufy=y, random_state=42)
forest = RandomForestClassifier(n_estimators=5, random_state=42)
forest.fit(X_train, y_train)

ランダムフォレストは決定木が過剰適合してしまう問題へのひとつの対処法だ。いくつかの決定木をそれぞれが個々にことなったものとなるように構築し、その結果を平均したものを出力として得る。それぞれが異なるように構築するために

  1. ブートストラップサンプリング:訓練データをN個ある際に、その中から交換ありでランダムにN個抽出する。そうすることでそれぞれの決定木を構築する際に用いるデータが少しずつ異なる。
  2. 各ノードでのテストに用いる特徴量を選択:max_featuresに設定した数の特徴量をランダムに抽出してその中で最適なテストを実施する。

このようにすることで個々のデータに対しては過剰適合しているが、それぞれの木が少しずつ異なるため予測性能を維持したまま過剰適合を抑えることができる。

勾配ブーストティング回帰木
from sklearn.ensemble import GradientBoostingClassifier
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)

複数の決定木を組み合わせるアンサンブル法である。勾配ブースティングでは、1つ前の決定木の誤りを次の決定木が修正するようにして決定木を順番に作っていくとある。修正とあるが具体的にはどのように修正するのだろうか。
ざっくりとした理解になるが、

  1. まずはじめのモデルを構築し、真の値と予測した値との残差を得る。
  2. その残差を最小化するようなモデルをさらに構築する。

$$
F_1(x) = y\\
h_1(x) = y - F_1(x)\\
F(x)=F_1(x) \longmapsto F_2(x)=F_1(x)+h_1(x)\cdots \longmapsto F_M(x)=F_M−1(x)+h_M−1(x)
$$

勾配ブースティングについて下記、参考にさせていただきました。
Kaggle Masterが勾配ブースティングを解説する - Qiita
勾配ブースティングについてざっくりと説明する - About connecting the dots.


かなり間隔があいてしまったので、もう少し読みすすめる時間を確保していきたい。

Pythonではじめる機械学習2(2.3.3~2.3.4)メモ

引き続き「Pythonではじめる機械学習」を読み進めていく。

線形モデルによる回帰

線形モデルによる一般的な予測式は下記である。
$$
\hat{y} = w[0] \times x[0] + w[1] \times x[1] + \cdots + w[p] \times x[p] +b
$$
特徴量が1つのデータであればという説明が個人的にしっくりきた。
$$
\hat{y} = w[0] \times x[0] +b
$$
多数の特徴量を持つデータに対してこそ線形モデルは強力であるらしい。線形モデルを用いた回帰にはさまざまなアルゴリズムがあり、いくつかを説明している。順を追って見ていきたいと思う。

線形回帰(通常最小二乗法)

通常最小二乗法(Ordinary Least Squares)とも呼ばれる手法がある。予測と真の回帰ターゲットyとの平均二乗誤差(Mean Squared Error)が最小になるようにwとbを求める。線形回帰にはパラメータがない。

from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(X_train, y_train)
リッジ回帰

予測に用いる式は通常最小二乗法と同じだが、リッジ回帰には係数ここではwの大きさをならべく0に近づけるように調整(制約)することができる。この調整はL2正則化と呼ばれる。
調整に用いられるパラメータはalphaであり、このalphaを増やすと係数は0に近くなり訓練セットに対する性能は低下する一方で汎化にはいいかもしれない。一般的には訓練セットにおける精度と汎化性能はトレードオフのようだ。

from sklearn.linear_model import Ridge
ridge = Ridge().fit(X_train, y_train, alpha=0.1)
Lasso

Lassoはリッジ回帰と同様にwをなるべく0になるように制約をかけるが、その方法に違いがある。リッジがL2正則化に対してLassoはL1正則化を行うようだ。
その結果いくつかの係数が0になり自動的に特徴量を選択しているかのように考えることができる。重要な特徴量のみに目を向けることができそうだ。制約を調整するパラメータはalphaでリッジと同じだ。

from sklearn.linear_model import Lasso
lasso = Lasso().fit(X_train, y_train, alpha=0.1)
ElasticNet

scikit-learnにはRidgeとLassoを組み合わせたハイブリッドモデルがある。ElasticNetはalphaの他にl1_ratioというパラメータがある。ハイブリッドなので両者の長所を活かせる可能性があるが、調整するパラメータが増えるというコストもある。

from sklearn.linear_model import ElasticNet
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
L1正則化とL2正則化

損失関数を𝑓(𝑥)、パラメータ𝜆とししたときに、
正則化なし
$$
\min{f(x)}
$$
L1正則化
正則化を行う項はペナルティ項と呼ばれるらしい。L1正則化では係数の絶対値の合計に対して、ペナルティをかけることにより合計を小さくする。それによりいくつかの係数は0になるため、特徴量選択を行ってることに等しく次元圧縮の役割となる。
$$
\min{f(x)+\lambda\sum_{i=1}^n |w_i|}\\
$$
L2正則化
L2正則化では係数の二乗和に対してペナルティをかける。L1正則化は次元圧縮のために用いられることが多く、L2正則化過学習を防ぐために用いられることが多い。
$$
\min{f(x)+\frac{\lambda}{2}\sum_{i=1}^n |w_i|^2}\\
$$

L1,L2正則化について下記参考にさせていただきました。
正則化の種類と目的 L1正則化 L2正則化について | AVILEN AI Trend

線形モデルによるクラス分類

2クラス分類

線形モデルでクラス分類を行うこともでき、2値分類を行う場合は次の式で予測を行う。
この式で得られた値が0より大きければ一方のクラス、0より小さければもう一方のクラスへ分類するものだ。
$$
\hat{y} = w[0] \times x[0] + w[1] \times x[1] + \cdots + w[p] \times x[p] +b > 0
$$

線形モデルを学習するアルゴリズムは主に次の2点で区別される。

  1. 係数wと切片bの特定の組み合わせと訓練データの適合度を図る尺度
  2. 正規化を行うかとその方法。

主に2番目に意味があるらしい。

ロジスティック回帰(logistic regression)、線形サポートベクタマシン(linear support vector machines : SVM)

ロジスティック回帰は回帰アルゴリズムではなく、分類アルゴリズムであるらしく線形回帰と混同しないようにと記載がある。
scikit-learnのロジスティック回帰と線形サポートベクタマシンではデフォルトでL2正則化を行いパラメータCを大きくすると正則化は弱くなり、小さく設定すると正則化の影響が強くなる。penaltyという引数に"l1"を指定することでL1正則化を行うこともできる。

from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
logreg = LogisiticRegression(C=0.01).fit(X_train, y_train)
lr_l1 = LogisiticRegression(C=0.01, penalty="l1").fit(X_train, y_train)
svc = LinearSVC().fit(X_train, y_train)
線形モデルによる多クラス分類

2クラス分類に用いる線形クラス分類モデルを他クラス分類アルゴリズムに拡張する手法として1対その他アプローチというものがある。
$$
w[0] \times x[0] + w[1] \times x[1] + \cdots + w[p] \times x[p] +b
$$
1クラスにつき1つの2クラス分類器があるため、クラスごとに上記のwとbの組み合わせがある。この出力が最も大きいクラスを予測としては採用する。3クラスのデータセットに対しての例が紹介されている。linear_svm.coef_.shapeの出力は(3, 2)であり各クラスに対応する係数ベクトルがあることがわかる。linear_svm.intercept_.shapeの出力は(3,)なので各クラスのbに対応する切片があることがわかる。

from sklearn.datasets import make_blobs
from sklearn.svm import LinearSVC
X, y = make_blobs(random_state=42)
linear_svm = LinearSVC().fit(X, y)

Pythonではじめる機械学習1~2章(~2.3.2)メモ

Pythonではじめる機械学習という本を読みはじめたので内容を記録していきたいと思う。本の内容は私のようにあまり詳しくなくても結構わかりやすく取り組みやすいと感じている。

1章最初のモデル:k-最近傍法(k-Nearest Neighbors)

1章は基本的な概要と最後にscikit-learnを用いてk-Nearest Neighborsを説明している。
k-Nearest Neighborsでクラス分類を行う手順としては、

  1. 正解クラスが既知なデータを用いてモデルを構築する。
  2. 新しくクラスが未知なデータがあるときに訓練セットで用いたデータと特徴量の値が近いものを取得
  3. そのデータのクラスを新しいデータのクラスとして採用する。

kとは近いデータを取得する際にいくつのデータを対象とするかの個数であり、

  • k=1であればそのデータと同じクラスを採用
  • k>=2であれば取得したデータのうち多数を含むクラスを採用する。

k= 1であれば下の図のようなイメージ。
○と△がクラスが既知のデータ、☆が新規のデータでクラスが未知であり最も近いデータが線で結ばれている。

f:id:futurasan:20200726141924p:plain
k=1

1章ではk-Nearest Neighborsをscikit-learnで実施する方法を紹介してくれている。scikit-learnには複数のアルゴリズムに基づくクラス分類や回帰モデルが用意されており、基本的に実施するフォーマットが統一されている。k-Nearest Neighborsを実施する際はscikit-learnからKneighborsClassifierクラスをインポートし、そのインスタンスを生成して用いる。scikit-learnがインストールされていない場合には

pip install scikit-learn

をすれば良い。

from sklearn.neighbors import KNeighborsClassifirer
knn=KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train) 
knn.predit(X_new)

基本的にはこのような形で既知のデータでモデルを構築し新規のデータに対して予測が可能となる。

2章

KNeighborsClassifier詳細

n_neighborsを変更した際の精度を確認することで、モデルの複雑さと汎化性能の確認を行う。n_neighborsは近傍点を何個用いるかを指定するパラメータである。ここでは1から10まで順にパラメータを指定してモデル構築を行い、精度を検証する。データはscikit-learnに含まれるcancerデータを用いる。最適なパラメータはデータによっても異なるだろうが、例では6あたりがtraining,testデータの両者における精度のバランスが良さそうである。
f:id:futurasan:20200726145343p:plain

from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=66)
training_accuracy = []
test_accuracy = []
neighbors_settings = range(1, 11)

# n_neighborsを1から順に設定しモデル構築、精度取得
for n_neighbors in neighbors_settings:
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(X_train, y_train)
    training_accuracy.append(clf.score(X_train, y_train))
    test_accuracy.append(clf.score(X_test, y_test))

# trainとtestの精度結果を描画
plt.plot(neighbors_settings, training_accuracy, label="training accuracy")
plt.plot(neighbors_settings, test_accuracy, label="test accuracy")
plt.ylabel("Accuracy")
plt.xlabel("n_neighbors")
plt.legend()

データをtrainとtestに分割する際にtrain_test_split()を用いている。引数のうちstratifyというものがあるが、これに元データのlabel(cancer.target)を指定することでtrainとtestに分割した際にも正解ラベルが元データと同じ比率に指定することができるようだ。
stratifyについて下記参考にさせていただきました。
scikit-learnでデータを訓練用とテスト用に分割するtrain_test_split | note.nkmk.me

k-Nearest Neighbors 回帰

k-Nearest Neighborsはクラス分類の他に回帰を行うこともできる。

  1. 正解クラスが既知なデータを用いてモデルを構築する。
  2. 新しくクラスが未知なデータがあるときに訓練セットで用いたデータと特徴量の値が近いものを取得
  3. そのデータの値を新しいデータの値として用いる。
    • k=1であればそのデータと同じ値を採用
    • k>=2であれば取得したデータを平均した値を用いる。

f:id:futurasan:20200726151704p:plain

from sklearn.neighbors import KNeighborsRegressor

をすれば後はKneighborsClassifierと同じようにモデル構築、予測ができる。

k-Nearest Neighbors における近いとは?

近いデータを取得する際にはユークリッド距離を用いることが多いようだ。
ユークリッド距離とはx, yという2つのデータがあった場合にその距離dが

$$
d(x, y) = {\sqrt{(x_1 - y_1)^2+(x_2 - y_2)^2 + \cdots + (x_n - y_n)^2}}
$$

で表される。x1, y1などは特徴量の値であり、x1は1つ目の特徴量、x2はふたつめの特徴量で良さそうだ。
ユークリッド距離について下記参考にさせていただきました。
クラスター分析の手法①(概要) | データ分析基礎知識