【機械学習_Python】k-means法

k-means法(k-平均法)についてメモ。

【目次】


前回取り上げた「k-近傍法」とは異なるので注意。
【機械学習_Python】k-近傍法(k-NN) - こちにぃるの日記

k-means法


データのクラスタリングに用いられる手法である。
分類問題における「教師なし学習」に属する。

アルゴリズムは比較的単純で、次の手順となる。

  1. 特徴空間上にk個のランダムな代表点(セントロイド)をプロットする。クラスタ数のkはハイパーパラメータであり、あらかじめ指定する。
  2. 各データとセントロイドの距離を求め、各データを最も近いセントロイドのグループに分類する。
  3. 上記2のグループでの重心(平均)を求め、新たなセントロイドとする。
  4. 上記2、3を繰り返す。
  5. セントロイドが更新されなくなれば終了し、分類を確定する。


f:id:cochineal19:20210524003450p:plain

数式としては、以下の関数を最小化する問題である。
「各クラスタ  C_{k} に属するデータ  x_{i} とセントロイド  \overline{x_{k}} との距離(ユーグリッド距離)の総和」(クラスタ内誤差平方和)を最小化できるセントロイドを求める。

クラスタ内誤差平方和(Sum of Squared errors of prediction、SSE)

 \quad J=\sum ^{K}_{k=1}\sum _{i\in C_{k}}\left\| x_{i}-\overline{x_{k}}\right\| ^{2}

 \qquad K:\verb|クラスタ数|,\ C_{k}:\verb|k番目のクラスタ|

 \qquad x_{i}:\verb|i番目のデータ|,\ \overline{x_{k}}:\verb|k番目のクラスタのセントロイド|


k-means++法


k-means法では、初期値のセントロイドをランダムな位置にプロットするため、複数のセントロイドが近くに位置する可能性があり、分類がうまくいかないことがある。

この問題の改良としてk-means++法があり、セントロイド同士が離れた位置にプロットされやすくなるアルゴリズムである。

エルボー法によるクラスタ数の推定


k-means法でのクラスタ数はハイパーパラメータであり、あらかじめ指定する必要がある。 このクラスタ数の推定方法として「エルボー法」がある。

エルボー法は、クラスタ数ごとのクラスタ内誤差平方和(SSE)を横に並べ、プロットがゆるやかになる位置を最適なクラスタ数とする方法である。

f:id:cochineal19:20210524021551p:plain:w450

Pythonコード


sklearn の cluster の KMeans で実装できる。
パラメタ説明はコメントアウトに記載する。

#--  k-means法
km1 = KMeans(n_clusters=4      # クラスター数指定。
             ,init="k-means++" # 'k-means++' or 'random'(k-means) 。default:'k-means++'
             ,n_init=10        # k-meansの実行回数
             ,random_state=0   # 乱数シード
             )
Y_km = km.fit_predict(X)  # 各データのクラスタ番号を求める
 
#-- SSE値を出力
print("Distortion: %.2f" % km.inertia_)


Irisデータでエルボー法のプロットを作ってみる。
クラスタ数(n_clusters=i)を1~10までループして、各クラスタ数のSSE(km.inertia_ で取得可)をプロットする。

from sklearn import datasets, cluster
import numpy as np
import matplotlib.pyplot as plt
 
#-- データ取得
X = datasets.load_iris()
 
#-- エルボー法(クラスタ数10まで)
SSE = []
for i in range(1, 11):
    km = cluster.KMeans(n_clusters = i
                ,init="k-means++"
                ,n_init=10
                ,random_state=1)
    km.fit(X.data)
    SSE.append(km.inertia_)
 
# グラフのプロット
plt.plot(range(1, 11), SSE, marker="o")
plt.xticks(np.arange(1, 11, 1))
plt.xlabel("クラスタ数")
plt.ylabel("クラスタ内誤差平方和(SSE)")
plt.show()

f:id:cochineal19:20210524025442p:plain:w300

参考


sklearn.cluster.KMeans — scikit-learn 0.24.2 documentation https://www.mext.go.jp/content/20200609-mxt_jogai01-000007843_007.pdf
https://www.jstage.jst.go.jp/article/fss/27/0/27_0_55/_pdf

【機械学習_Python】k-近傍法(k-NN)

k-近傍法(k-nearest neighbor algorithm、k-NN)についてメモ。
同じような名前の「k-means法」とは異なるので注意。

【目次】

k-近傍法とは


特徴空間において、距離が近い既知データから未知データのクラスを予測する方法である。 分類問題の「教師あり学習」である。
k-近傍法は教師データから学習(モデル作成)する訳でなく、単にデータ間の距離で分類する単純なアルゴリズムであり「怠惰学習」とも呼ばれる。

例えば、校庭(特徴空間)の様々な遊具(座標)の中で、すべり台付近に集まっている子たちが10人いて、 そのうち7人はA組、2人はB組の生徒だと分かっているとする(既知の情報)。 この情報から残り1名(未知の情報)を予測すると、A組の生徒の可能性が高いと予測される。

k-近傍法は、このようなアルゴリズムで周囲の既知データ(訓練データ)を多数決して、未知データのクラスを予測する。
なお、k-近傍法の「k」は周囲のいくつの既知データで予測するかを指定するハイパーパラメータである。多数決で予測を出すため奇数にするのが一般的である。
パラメータ k の値の違いで予測結果が変わることがある。

f:id:cochineal19:20210523124358p:plain:w450

なお、k=1 の場合を「最近傍法」と呼び、一番近くの既知データのクラスをそのまま採用する。

ミンコフスキー距離


ここで、近さの尺度として、特徴空間上の距離を表す「ミンコフスキー距離(Lpノルム)」に触れたい。
ミンコフスキー距離は「マンハッタン距離(L1ノルム)」や「ユーグリッド距離(L2ノルム)」を一般化したもので次の形をとる。

ミンコフスキー距離(Lpノルム)

\quad d\left( x,y\right) =\left( \sum ^{n}_{i=1}\left| x_{i}-y_{i}\right| ^{p}\right) ^{1/p}


パラメータ p=1 にてマンハッタン距離(L1ノルム)

\quad d\left( x,y\right) =\left( \sum ^{n}_{i=1}\left| x_{i}-y_{i}\right| ^{1}\right) ^{1/1} = \sum ^{n}_{i=1}\left| x_{i}-y_{i}\right|


パラメータ p=2 にてユーグリッド距離(L2ノルム)

\quad d\left( x,y\right) =\left( \sum ^{n}_{i=1}\left| x_{i}-y_{i}\right| ^{2}\right) ^{1/2} = \sqrt{ \sum ^{n}_{i=1}\left( x_{i}-y_{i}\right)^{2}}


k-近傍法では、このパラメータ p を設定することでマンハッタン距離やユーグリッド距離など複数の距離を扱える。一般的にはユーグリッド距離を使うことが多い。

pythonコード


sklearn の neighbors の KNeighbors にてk-近傍法を実装できる。
予測に用いる周囲の既知データ数(ハイパーパラメータ k )は、n_neighbors で指定する。デフォルトは n_neighbors=5
ミンコフスキー距離(Lpノルム)は p で指定する。デフォルトは p=2(ユーグリッド距離、L2ノルム)。

from sklearn import model_selection, neighbors, metrics
import seaborn as sns
import matplotlib.pyplot as plt

#-- データ取得・分割
iris = sns.load_dataset("iris")
X_train, X_test, y_train, y_test = train_test_split(iris.iloc[:,:4], iris.iloc[:,4], test_size=0.2, random_state=99)

#-- k-近傍法
kn = neighbors.KNeighborsClassifier(n_neighbors=5)
kn.fit(X_train, y_train)    #-- 学習
y_pred1 = kn.predict(X_test) #-- 予測

#-- 評価
print(metrics.confusion_matrix(y_test, y_pred1))
print(metrics.classification_report(y_test, y_pred1))


参考


sklearn.neighbors.KNeighborsClassifier — scikit-learn 0.24.2 documentation
https://www.mext.go.jp/content/20200609-mxt_jogai01-000007843_007.pdf
ミンコフスキー距離 - Wikipedia

【機械学習_Python】主成分分析(PCA)

主成分分析(Principal Component Analysis、PCA)についてのメモ

【目次】

主成分分析とは


相関のある特徴量から、互いに無相関の合成変数を作る手法である。この合成変数を主成分(Principal components)という。
主成分は特徴量の数だけ作ることができ、p次元の特徴量からp個の主成分ができる。このうち寄与率の高い主成分を選択することで、情報量を保ったまま次元削減することができる。例えば13次元の特徴量から2個の主成分を取り出し、2次元データとする。

f:id:cochineal19:20210521134924p:plain:w300

なお、主成分分析では特徴量の分散共分散行列を使う方法と相関行列を使う方法がある。元データが基準化(平均0、分散1)されていれば結果は同じになる。
特徴量間で単位が異なったり、分散の大きさが異なる場合は相関行列を使う方法が良い。

主成分軸の作り方


簡単のため、特徴量  x_{1}, x_{2} の2次元データ(特徴量行列X)で考える。
主成分分析では、特徴量 X を重み係数 W の方向に射影した主成分 Z の軸を作成する。

\quad \verb|行列表現: | Z=W^{T}X
\quad \verb|第1主成分: | z_{1}=w_{1}x_{1} + w_{2}x_{2}

次の概念図の通り、射影した主成分軸上での分散が大きいほど元の特徴量の情報を保つことができるため、主成分軸の作成は『主成分 Z の分散を最大化する重み係数 W を見つける問題』となる。

f:id:cochineal19:20210522231219p:plain:w500

この際、重み係数 W に制約がないと \infty になってしまうため、慣習的に重み係数 W のノルム(長さ)を1にして計算する(重み係数は割合が分かればよい)。

 \quad ||W|| = \sqrt{w_{p1}^{2} + w_{p2}^{2} + ... + w_{pp}^{2}} = 1


分散の最大化(ラグランジュの未定乗数法)

第1主成分を作る場合

特徴量  x_{1}, x_{2} の2次元データ(特徴量行列X)から第1主成分を作ることを考える。
つまり、 \sqrt{w_{1}^{2} + w_{2}^{2}} = 1 の制約の下、第1主成分  z_{1} の分散  \sigma_{z_{1}}^{2} の最大化を考える。


先ず、第1主成分の分散は、元データの分散と共分散に重み係数を掛けた形に書き換えられる。

 \quad \sigma_{z_{1}}^{2} = \dfrac{1}{n} \sum \left( z_{1} - \bar{z_{1}} \right)^{2}

 \qquad \ = \dfrac{1}{n} \sum \{ \left(w_{1}x_{1} + w_{2}x_{2}\right) - \left(w_{1}\overline{x_{1}} + w_{2}\overline{x_{2}} \right) \}^{2}

 \qquad \ = \dfrac{1}{n} \sum \{ w_{1}\left(x_{1} + \overline{x_{1}}\right) - w_{2}\left(x_{2} + \overline{x_{2}} \right) \}^{2}

 \qquad \ = w_{1}^{2}\dfrac{1}{n}\sum \left( x_{1}-\overline{x_{1}}\right)^{2} + w_{2}^{2}\dfrac{1}{n}\sum \left( x_{2}-\overline{x}_{2}\right) ^{2} + 2w_{1}w_{2} \dfrac{1}{n}\sum \left( x_{1}-\overline{x}_{1}\right) \left( x_{2}-\overline{x}_{2}\right)

 \qquad \ = w_{1}^{2} \sigma _{x_{1}}^{2} + w_{2}^{2} \sigma _{ x_{2}}^{2} + 2w_{1}w_{2} \sigma _{  x_{1} x_{2} }


これを行列表現で一般化すると、重みベクトル W と分散共分散行列 V から次の通り記載でき、重み係数 W を求める問題は W^{T}VW を最大化する問題へと帰着する。

 \quad \sigma_{z_{1}}^{2} = W^{T}VW = \begin{bmatrix} w_{1} & \cdot \cdot \cdot & w_{p}\end{bmatrix} \begin{bmatrix} \sigma _{  x_{1} x_{1} } & \cdot \cdot \cdot & \sigma _{  x_{p} x_{1} } \\ \vdots & \ddots & \vdots \\ \sigma _{  x_{1} x_{p} } & \ldots & \sigma _{  x_{p} x_{p} } \end{bmatrix}\begin{bmatrix} w_{1} \\ \vdots \\ w_{p} \end{bmatrix}


ここに制約条件 ||W||=1 を設け、ラグランジュ関数(  \lambdaラグランジュ乗数)を導入し、

 \quad L\left( w_{1},w_{2},\lambda \right) = \left( w_{1}^{2} \sigma _{x_{1}}^{2} + w_{2}^{2} \sigma _{ x_{2}}^{2} + 2w_{1}w_{2} \sigma _{  x_{1} x_{2} } \right) - \lambda\left( w_{1}^{2}+w_{2}^{2}-1 \right)

 \qquad \qquad \qquad \quad \ =W^{T}VW- \lambda\left( W^{T}W-1 \right)

 w_{1}, w_{2}, \lambda偏微分より次を得る。

 \quad \dfrac{\partial L}{w_{1}}=2w_{1}\sigma _{x_{1}}^{2}+2w_{2}\sigma _{x_{1}x_{2}}-2w_{1}\lambda=w_{1}\sigma _{x_{1}}^{2}+w_{2}\sigma _{x_{1}x_{2}}-w_{1}\lambda=0

 \quad \dfrac{\partial L}{w_{2}}=2w_{2}\sigma _{x_{2}}^{2}+2w_{1}\sigma _{x_{1}x_{2}}-2w_{2}\lambda=w_{2}\sigma _{x_{1}x_{2}}+w_{1}\sigma _{x_{2}}^{2}-w_{2}\lambda=0

 \quad \dfrac{\partial L}{\partial \lambda }=-\left( w_{1}^{2}+w_{2}^{2}-1\right) =0


この重み係数  w_{1}, w_{2} の解について、変形して並べてみる。

 \quad w_{1}\sigma _{x_{1}}^{2}+w_{2}\sigma _{x_{1}x_{2}}=w_{1} \lambda

 \quad w_{1}\sigma _{x_{1}x_{2}}+w_{2}\sigma _{x_{2}}^{2}=w_{2} \lambda

これを行列表現で一般化すると次のようになる。

 \quad VW=W \lambda I

 \quad \begin{bmatrix} \sigma _{x_{1}x_{1}} & \ldots & \sigma _{x_{p} x_{1}} \\  \vdots & \ddots & \vdots \\ \sigma _{x_{1} x_{p}} & \ldots & \sigma _{x_{2}x_{2}} \end{bmatrix}\begin{bmatrix} w_{1} \\  \vdots \\ w_{p} \end{bmatrix}=\begin{bmatrix} w_{1} \\  \vdots \\ w_{p} \end{bmatrix}\begin{bmatrix} \lambda &  \ldots & 0  \\  \vdots & \ddots & \vdots \\ 0 & \ldots  & \lambda \end{bmatrix}


最後の式は、分散共分散行列  V固有値問題であり、固有値固有ベクトルはそれぞれ次に対応する。
・最大固有値  \lambda =主成分  z_{1} の分散  \sigma_{z_{1}}^{2}
固有ベクトル=重み係数  W

つまり、重み係数 W を求める問題は、分散共分散行列  V固有値問題に帰着する。
なお、標準化する場合、分散共分散行列でなく相関行列 R を用いる。


第p主成分を作る場合

第2主成分以上を作りたい場合も基本的な計算は変わらず、最終的には分散共分散行列  V固有値問題に帰着する。

 \quad VW=W \lambda I

 \quad \begin{bmatrix} \sigma _{x_{1}x_{1}} & \ldots & \sigma _{x_{p} x_{1}} \\  \vdots & \ddots & \vdots \\ \sigma _{x_{1} x_{p}} & \ldots & \sigma _{x_{2}x_{2}} \end{bmatrix}\begin{bmatrix} w_{1} \\  \vdots \\ w_{p} \end{bmatrix}=\begin{bmatrix} w_{1} \\  \vdots \\ w_{p} \end{bmatrix}\begin{bmatrix} \lambda &  \ldots & 0  \\  \vdots & \ddots & \vdots \\ 0 & \ldots  & \lambda \end{bmatrix}


なお、第2主成分は第1主成分、第3主成分は第1・第2主成分と直交する軸のうち分散を最大化する軸を求める。以降も同様。

ここで、固有値を大きい順に  \lambda_{1}, \lambda_{2}, ..., \lambda_{p} と並べ、対応する固有ベクトル w_{}, w_{2}, ... , w_{p} とすると、この並び順が第1主成分、第2主成分、・・第p主成分となる。先述の通り、最大固有値  \lambda _{1} が第1主成分である。


次元削減の基準

寄与率と累積寄与率

第k主成分でどのくらいの分散を表せているか(情報量があるか)を固有値を用いて「寄与率」という尺度で表すことができる。

 \quad \dfrac{\lambda_{k}}{\sum _{i=1}^{p} \lambda} = \dfrac{\lambda_{k}}{\lambda_{1}+\lambda_{2}+...+\lambda_{p}}

また、第1主成分から第k主成分までの寄与率の合計を「累積寄与率」という。

 \quad \dfrac{\sum _{j=1}^{k} \lambda_{j}}{\sum _{i=1}^{p} \lambda_{i}} = \dfrac{\lambda_{1}+\lambda_{2}+...+\lambda_{k}}{\lambda_{1}+\lambda_{2}+...+\lambda_{k}+...+\lambda_{p}}


この「累積寄与率」を十分に高く保てる範囲で次元削減する方法がある。一般的に80%、50%などあるが根拠があるわけではない。

カイザー基準とスクリー基準

分散の加法性により、基準化したデータセット(平均0、分散1)における分散の合計は、特徴量の数と等しくなる(2次元なら分散の合計は2、p次元なら分散の合計はp)。よって、固有値が1に満たないものは、特徴量1個分の情報量もないと考えることができる。このように固有値が1以上であるかで判断する方法を「カイザー基準」という。

また、固有値を大きい順に並べてプロットし、推移がゆるやかになるところまで選択する方法がある。この方法を「スクリー基準」と言い、この時の図をスクリープロットと言う。

f:id:cochineal19:20210523020003p:plain:w400

計算手順のまとめ


主成分分析~次元削減までの手順は、簡単に次の通りまとめられる。
①:p次元の特徴量行列 X を標準化(平均0、分散1)する。
②:①から相関行列 R を求める。
③:②から固有値  \lambda 固有ベクトル(重み係数  W )を求める。
④:③の固有値  \lambda を大きい順にk個取り出し、これらに対応する固有ベクトル(重み係数  W )を結合して特徴量の変換行列 D を作る。
⑤:④の変換行列 D を用いて①の特徴量行列 X を変換する。X'=XD

Pythonコード


sklearn の decomposition にある PCA で主成分分析ができる。
次元削減したい場合は PCA の n_components で指定する。例:2次元データに圧縮する。PCA(n_components=2)

from sklearn.decomposition import PCA
import numpy as np

#-- 標準化
X = (X - X.mean(axis=0)) / X.std(axis=0)

#-- 主成分分析
pca1   = PCA() #-- 次元数を指定する場合、n_components
X_pca1 = pca1.fit_transform(X) #-- 分析

print("-- 固有値(主成分の分散) --")
print(pca1.explained_variance_)
print("-- 固有ベクトル(重み係数W) --")
print(pca1.components_)
print("-- 寄与率 --")
print(pca1.explained_variance_ratio_)


参考


統計学(出版:東京図書), 日本統計学会編
高橋 信, 井上 いろは, マンガでわかる統計学 [因子分析編], トレンドプロ
市川 伸一 他, SASによるデータ解析入門 [第3版], 東京大学出版会
sklearn.decomposition.PCA — scikit-learn 0.24.2 documentation
http://www.ie.reitaku-u.ac.jp/~tak/datB/datB_prin00.pdf
http://manabukano.brilliant-future.net/document/text-PCA.pdf
http://web.wakayama-u.ac.jp/~wuhy/am10.pdf

【機械学習_Python】サポートベクターマシーン

SVM(サポートベクターマシーン)についてのメモ。


SVM(サポートベクターマシーン)とは


SVMは「マージン最大化」の考えに基づいて分類する手法である。
n次元空間に超平面(線形識別関数、二次元なら直線)で分類境界を作ることでベクトルを分離する。
(マージンが大きいほど、未知データでの識別誤りが起こりにくいだろうという理屈でマージン最大化する)。

手法としては、線形分離可能を仮定する「ハードマージン法」と任意の識別誤りを許容する「ソフトマージン法」がある。
さらに、非線形問題に対しても、高次元特徴空間を作り「カーネルトリック」を使って分類する方法がある。
前者を線形SVM、後者を非線形SVMと呼ぶ。

線形SVM(ハードマージン法とソフトマージン法)


「ハードマージン法」は、訓練データ(学習するデータ)を超平面で完全に分離できると仮定した問題である。

f:id:cochineal19:20210520010026p:plain

ただし、現実的には線形分離可能な問題は珍しく、ある程度の識別誤りを許容する「ソフトマージン法」が用いられる。識別誤りのパラメータとしてスラック変数 ξ を導入し、ハイパーパラメータCでその許容を調整する。

f:id:cochineal19:20210520010101p:plain

主問題と双対問題

SVMでは「主問題」を直接解くのではなく、計算コストの良い「双対問題」に帰着して解く方法が実装されている。双対問題を解く際、後に記載する「カーネルトリック」も扱える。

双対問題を解くにあたっては、主問題を不等式制約のラグランジュ未定乗数法を用いて双対問題へ置き換える(計算の詳細は割愛)。
以下はハードマージン法とソフトマージン法の式を横に並べたもの。赤字部分が異なる箇所。
f:id:cochineal19:20210520021551p:plain

ここで双対問題とは、主変数  w, b, \xi を最小化し、双対変数  \alpha , \beta を最大化する問題である。
これは主変数にとっての最小値が、双対変数にとっての最大値になる「鞍点」(あんてん)を探している(鞍点が最適解ということ。鞍点はKKT条件を満たす)。鞍点は字のごとく、馬の鞍(くら)の形。

※双対問題・鞍点の概念は、以下リンクのスライド22枚目が分かりやすかった(他サイト様のもの)。
はじめてのパターン認識 第8章 サポートベクトルマシン

以上により最適解 \alpha_{i} が求まれば、ラグランジュ関数にて求めた次の式で係数wが求まる。

 \quad w=\sum_{i\in sv}α_{i}t_{i}x_{i}, \  sv:サポートベクトル

ここで、マージンの外側(本記事の概念図で背景色を付けた領域)にある正解ベクトルは \alpha_{i}=0 となり、マージンの内側(  0 < \xi _{i} \leq 1, または  1 < \xi_{i} )にあるベクトルは  \alpha _{i} = C となる。そして、ちょうどマージンの上(概念図の点線)にあるベクトルは  0 < \alpha_{i} < C となることから、\alpha ≠ 0 であるマージンの内側とマージン上のベクトル(これらが「サポートベクトル」と言われる)のみを用いて係数wを求めれば良い。


<再掲>
f:id:cochineal19:20210520010101p:plain:w400

Pythonによる実行

sklearn の svm.LinearSVC で線形SVMが実行できる。
ソフトマージン法のハイパーパラメータCはデフォルトで C=1.0 である。

Pythonコード

from sklearn.svm import LinearSVC
 
mod1 = LinearSVC(C=C, random_state=1)
mod1.fit(X_train, y_train)
y_pred1 = mod1.predict(X_test)
print(confusion_matrix(y_test, y_pred1))
print(classification_report(y_test, y_pred1))
print()


なお、線形SVMでもL1正則化とL2正則化を指定できる。デフォルトは penalty="l2"(L2正則化)。
正則化については次の記事を参照。
【機械学習_Python】Ridge回帰、Lasso回帰、Elastic Net回帰 - こちにぃるの日記

非線形SVMカーネルトリック


ソフトマージン法によって線形分離不可能なデータセットも扱えるようになったが、現実的には非線形の分類境界を作りたい場面が多い。
このような場面に対して、入力空間を高次元特徴空間に変換(特徴量を非線形写像)して分類可能とする方法がある。
f:id:cochineal19:20210520162542p:plain:w400

上の例では、関数  \phi \left( x_{i}\right)^{T} \phi \left( x_{j}\right) を用いて新たな特徴量(例えば  x_{1} x_{2} )を作り、3次元空間とすることで分離可能になった。このように、特徴量を高次元に写像するほど分離可能性が高まる。
この方法は、次元を高めるほど計算コストが爆発的に高まり実装しづらい欠点があったが、カーネル関数を導入した「カーネルトリック」と呼ばれる方法によって計算コスト面を解消することができる。

カーネル関数

 \quad K\left(x_{i},x_{j}\right)=\phi \left( x_{i}\right)^{T} \phi \left( x_{j}\right)

※簡単な記載になってしまうが、カーネル関数を用いることで関数  \phi \left( x_{i}\right)^{T} \phi \left( x_{j}\right) の中身を計算せずとも、等価な結果を計算することができる。

f:id:cochineal19:20210520163237p:plain

Pythonによる実行

sklearn の svm.SVC非線形SVMが実行できる。
ソフトマージン法と同じくハイパーパラメータ:Cを設定可能。デフォルトは C=1.0
カーネルの指定は、linearpolyrbfsigmoidprecomputed があり、デフォルトは kernel='rbf'poly多項式カーネルrbf がGaussカーネルsigmoid がシグモイドカーネルで、linear は線形SVMsklearn.svm.LinearSVC と少し異なるらしい)。
多クラス分類においては、decision_function_shapeにおいて、ovo(one vs one、1対1)とovr(one vs rest、1対残り)を指定できる。ovoはクラス同士のペアを総当たりするため計算量が多い。デフォルトは decision_function_shape='ovr'

Pythonコード

from sklearn.svm import SVC
 
mod1 = SVC(C=C, kernel='rbf', random_state=1)
mod1.fit(X_train, y_train)
y_pred1 = mod1.predict(X_test)
print(confusion_matrix(y_test, y_pred1))
print(classification_report(y_test, y_pred1))
print()


Wineデータで実行


Wineデータから線形分離が難しそうなデータの組を適当に見つけて、線形SVM非線形SVMの識別性能を比較してみる。

import seaborn as sns
import pandas as pd
import numpy as np
from sklearn import datasets, model_selection
from sklearn.svm import LinearSVC, SVC
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt

ROW = datasets.load_wine()
ADS = pd.DataFrame(ROW.data, columns=ROW.feature_names).assign(target=np.array(ROW.target))
sns.pairplot(ADS[["ash","proline","target"]], hue="target")

f:id:cochineal19:20210520192333p:plain:w400

特徴量 "ash", "proline" の target 1 vs. 2 が良い感じに混ざっていたので、今回これらを用いる。

ADS12 = ADS[ADS["target"]!=0]
ADS12["target"] = ADS["target"] - 1
 
X_train, X_test, y_train, y_test = model_selection.train_test_split(
        ADS12[["ash","proline"]], ADS12["target"], test_size=0.3, random_state=999)
 
#-- 線形SVM
mod1 = LinearSVC(C=1, random_state=999)
mod1.fit(X_train, y_train)
y_pred1 = mod1.predict(X_test)
print('線形SVM')
print(confusion_matrix(y_test, y_pred1))
print(mod1.score(X_test, y_test))
#print(classification_report(y_test, y_pred1))
print()
 
#-- 非線形SVM
klist=['poly','rbf','sigmoid']
for k in klist:
    mod1 = SVC(C=1, kernel=k, random_state=999)
    mod1.fit(X_train, y_train)
    y_pred1 = mod1.predict(X_test)
    print('非線形SVM ', k)
    print(confusion_matrix(y_test, y_pred1))
    print(mod1.score(X_test, y_test))
    #print(classification_report(y_test, y_pred1))
    print()

f:id:cochineal19:20210520192618p:plain:w150

線形SVMと比べ、テストデータに対する accuracy は、非線形SVM多項式カーネル(poly)とGaussカーネル(rbf)が良さそうだった。

参考


https://home.hiroshima-u.ac.jp/tkurita/lecture/svm.pdf
サポートベクターマシン(Support Vector Machine, SVM)~優秀な(非線形)判別関数~
サポートベクターマシンの詳しい理論的な解説について【線形分離可能な場合】
はじめてのパターン認識 第8章 サポートベクトルマシン

【機械学習_Python】決定木とランダムフォレスト

決定木(Decision Tree)とランダムフォレスト(Random Forest)について。

【目次】

決定木(Decision Tree)


分類問題、回帰問題の両方に対応でき、それぞれ「分類木」「回帰木」と呼ばれる。
決定木は、分割前後の不純度の差(情報利得)が最大になるように学習する。

Python 基本コード

from sklearn.tree import DecisionTreeClassifier
mod1 = RandomForestClassifier(criterion = "gini", max_depth = i_integer)
mod1.fit(X_train, y_train)

情報利得(Information gain)

 \quad IG\left( D_{p},f\right) =I\left( D_{p}\right)-\left\{ \dfrac{N_{left}}{N} I \left( D_{left} \right) + \dfrac{N_{right}}{N} I \left( D_{right} \right) \right\}

 \qquad ※f = 特徴量(説明変数)
 \qquad \quad I \left( D_{p} \right)\ ,\ I\left(D_{left\ }\right),\ I \left( D_{right\ }\right) = 親ノード、左右の子ノードの不純度
 \qquad \quad N, \ N_{left}\ ,\ N_{right}\ = 親ノード、左右の子ノードのサンプル数


情報利得は分割前後の不純度(Impurity Criterion)の差である。
複数分類が混合する分割前データから各分類をきれいに分離できるほど分割前後の不純度の差は大きくなる。このことから、情報利得の最大化が目的関数となる。

次の図は、分割方法AとBを比較したものである。この例では、分割方法Bの方が分割後の不純度が小さいので情報利得が大きくなる。したがって、情報利得を最大化できる分割方法Bが選択される。
f:id:cochineal19:20210517191134p:plain:w450

不純度(Impurity Criterion)

Python scikit-learn 0.24.2 では、不純度の評価指標としてジニ係数(Gini Index)とエントロピー(Entropy)が実装されている。
ジニ係数を使ったアルゴリズムにはCART(Classification And Regression Trees)、エントロピーを使ったアルゴリズムにはC4.5がある。

エントロピー(Entropy)

  \qquad I_{H}=-\sum ^{c}_{j=1}p_{j}\log _{2}\left( p_{j}\right) \qquad p_{j}=\dfrac{n_{j}}{N},\ c=\verb|分類数|


ジニ係数(Gini Index)

 \qquad I_{G}=1-\sum ^{c}_{j=1}p_{j}^{2}


分析例

次のサンプルデータで手順を追ってみる。
目的変数は★〇△の3分類。特徴量はX1、X2、X3の3つ。
f:id:cochineal19:20210517194051p:plain

サンプルサイズ10個、★5個、〇3個、△2個なので、不純度(初期値)は:

 \qquad Entropy : \ I_{H}=-1 \cdot \left( \dfrac{5}{10}\cdot\log_{2}\dfrac{5}{10} + \dfrac{3}{10}\cdot\log_{2}\dfrac{3}{10} + \dfrac{2}{10}\cdot\log_{2}\dfrac{2}{10}\right) \approx 1.485
 \qquad Gini \quad \ \ : \ I_{G}=1 - \biggl\{ \left( \dfrac{5}{10}\right)^{2} + \left(\dfrac{3}{10}\right)^{2} + \left(\dfrac{2}{10}\right)^{2}\biggr\}  \approx 0.620


これを特徴量X1(A or notA)で分割すると次の通り:
f:id:cochineal19:20210517200219p:plain

 \qquad P_{A}=\dfrac{n_{A}}{N}=\dfrac{3}{10}=0.3, \quad P_{notA}=\dfrac{n_{notA}}{N}=\dfrac{7}{10}=0.7
 \qquad Entropy \left(A\right) \quad \ : \ I_{H}=-1 \cdot \left( \dfrac{2}{3}\cdot\log_{2}\dfrac{2}{3} + \dfrac{1}{3}\cdot\log_{2}\dfrac{1}{3} + 0 \right) \approx 0.918
 \qquad Entropy \left(notA\right) : \ I_{H}=-1 \cdot \left( \dfrac{3}{7}\cdot\log_{2}\dfrac{3}{7} + \dfrac{2}{7}\cdot\log_{2}\dfrac{2}{7} + \dfrac{2}{7}\cdot\log_{2}\dfrac{2}{7}\right) \approx 1.557
 \qquad Gini \left(A\right) \qquad \ \ \ : \ I_{G}=1 - \biggl\{ \left( \dfrac{2}{3}\right)^{2} + \left(\dfrac{1}{3}\right)^{2} + 0\biggr\}  \approx 0.444
 \qquad Gini \left(notA\right) \quad \ : \ I_{G}=1 - \biggl\{ \left( \dfrac{3}{7}\right)^{2} + \left(\dfrac{2}{7}\right)^{2} + \left(\dfrac{2}{7}\right)^{2}\biggr\}  \approx 0.653


よって情報利得は:

 \qquad Entropy: \ IG = 0.620 - \left( 0.3 \cdot 0.918 + 0.7 \cdot 01.557 \right) \approx 0.120
 \qquad Gini \quad \ \ : \ IG = 0.620 - \left( 0.3 \cdot 0.444 + 0.7 \cdot 0.653 \right) \approx 0.030


同じように、特徴量X2(1 or 2)、X3("あ" or not"あ")でも計算してみる: f:id:cochineal19:20210517233107p:plain

ジニ係数で話を進めると、情報利得は X3 = 0.380 が最大のため、特徴量X3で分割する。

python コード

import pandas as pd
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
 
#-- サンプルデータ
df = pd.DataFrame({"y": ["★","★","☆","☆","★","☆","★","★","△","△"]
                   ,"X1": ["A","A","A","B","B","B","B","C","C","B"]
                   ,"X2": [1,0,1,0,1,0,1,0,1,0]
                   ,"X3": ["あ","あ","い","い","あ","い","あ","あ","い","う"]})
 
#-- カテゴリ変数はダミー変数化が必要。
df = pd.get_dummies(df, columns=["X1", "X3"])
 
#-- 決定木
def DecisionTree(lst, criterion1, max_depth1):
    clf = DecisionTreeClassifier(criterion=criterion1, max_depth=max_depth1)
    clf.fit(X=df[lst], y=df["y"])
    plot_tree(clf, filled=True, fontsize=12, feature_names=lst)
 
#-- 実行・描画
plt.figure(figsize=(15,5))
plt.subplot(2, 3, 1); DecisionTree(lst=["X1_A"],  criterion1="entropy", max_depth1=1)
plt.subplot(2, 3, 2); DecisionTree(lst=["X2"],    criterion1="entropy", max_depth1=1)
plt.subplot(2, 3, 3); DecisionTree(lst=["X3_あ"], criterion1="entropy", max_depth1=1)
plt.subplot(2, 3, 4); DecisionTree(lst=["X1_A"],  criterion1="gini", max_depth1=1)
plt.subplot(2, 3, 5); DecisionTree(lst=["X2"],    criterion1="gini", max_depth1=1)
plt.subplot(2, 3, 6); DecisionTree(lst=["X3_あ"], criterion1="gini", max_depth1=1)
plt.show()

f:id:cochineal19:20210517233932p:plain

勉強がてら更にもう1深度分割してみる。

plt.figure(figsize=(10,5))
plt.subplot(1, 2, 1); DecisionTree(lst=["X1_A","X1_B","X2","X3_あ","X3_い"], criterion1="gini", max_depth1=2)
plt.subplot(1, 2, 2); DecisionTree(lst=["X1_A","X1_B","X2","X3_あ","X3_い"], criterion1="entropy", max_depth1=2)
plt.show()

f:id:cochineal19:20210517234355p:plain

f:id:cochineal19:20210517234501p:plain

今回のサンプルデータでは「X3="あ" or not"あ"」→「X3="い" or not"い"」の分岐が良いようであった。

決定木の応用

ランダムフォレスト、XGboost及びLightGBMなど良く耳にする手法は、決定木を学習器としたアンサンブル学習(バギング、ブースティング等)であり、高い精度を持つ。

f:id:cochineal19:20210516203200p:plain
※本記事ではランダムフォレストについて触れたい。XGboost、LightGBMは別の記事で。

ランダムフォレスト(Random Forest)


ランダムフォレストは決定木と同様に分類問題、回帰問題の両方に対応できる。
決定木を並列で実行し、最終結果を得るバギング(アンサンブル学習の1つ)という方法をとる。

Python 基本コード

from sklearn.ensemble import RandomForestClassifier
mod1 = RandomForestClassifier(n_estimators = 100
                              ,criterion = "gini"
                              ,max_depth = i_integer
                              ,max_features = "auto"
                              ,bootstrap = True
                              ,random_state = 1)

ランダムフォレストの流れ

ランダムフォレストの基本的な流れは次の通り。

  1. Bootstrap samplingにて、訓練データからサンプルサイズnの小標本を生成する。Samplingの重複は許容される(例:Aさんの同一データが複数回抽出されてもOK)。
    引数:bootstrap。デフォルトはTrue。

  2. 上記1の小標本について、学習に用いる特徴量(説明変数)をランダムにm個決める(例:身長、体重、脈拍、血圧の特徴量から身長、脈拍のみを学習に用いる)。
    引数:max_features={"auto", "sqrt", "log2"}。デフォルトは"auto"。"auto"では√m個の特徴量が指定される。

  3. 上記1,2で決定木をk個つくり学習する。※上記1のみならバギングツリー、上記1,2両方ならランダムフォレスト。
    分類アルゴリズムCARTジニ係数)とC4.5(エントロピー)がある。
    引数:n_estimators。作成する決定木の数を指定。デフォルトは100。
    引数:criterion={“gini”, “entropy”}。デフォルトは"gini"(ジニ係数)。
    引数:max_depth。決定木の深さを指定。デフォルトはNone。簡易決定木のため、通常の決定木より小さい値にすると良い。

  4. 上記3の決定木を使ってテストデータで予測する。
    各決定木の結果を多数決(分類問題)や平均化(回帰問題)して最終結果を出す。

  5. 評価指標を用いてモデルを評価する。

wineデータを使った分析例

3分類のワインデータを決定木とランダムフォレストで分類してみる。
※データ詳細は以下。
sklearn.datasets.load_wine — scikit-learn 0.24.2 documentation

ランダムサーチでハイパーパラメータを調整し、得られた結果を混同行列やF1スコアで評価する。

import pandas as pd
import numpy as np
from scipy import stats
from sklearn import datasets, model_selection
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
 
ROW = datasets.load_wine()
ADS = pd.DataFrame(ROW.data, columns=ROW.feature_names).assign(target=np.array(ROW.target))
 
X_train, X_test, y_train, y_test = model_selection.train_test_split(
        ADS[ROW.feature_names], ADS["target"], test_size=0.3, random_state=1)
 
#-- ランダムサーチ(決定木 vs. ランダムフォレスト)
modfnc1 = [DecisionTreeClassifier(), RandomForestClassifier()]
prm1 = [{"max_depth": stats.randint(1,6)
         ,"criterion":["gini","entropy"]
         ,"random_state":[1]}
        ,{"n_estimators": stats.randint(10,201)
         ,"max_depth": stats.randint(1,6)
         ,"criterion":["gini","entropy"]
         ,"max_features":["auto", "sqrt", "log2"]
         ,"random_state":[1]}]

for modfnc1, prm1 in zip(modfnc1, prm1):
    clf = model_selection.RandomizedSearchCV(modfnc1, prm1)
    clf.fit(X_train, y_train)
    y_pred1 = clf.predict(X_test)
    print(clf.best_estimator_)
    print(confusion_matrix(y_test, y_pred1))
    print(classification_report(y_test, y_pred1))
    print()

f:id:cochineal19:20210518011640p:plain:w450

今回の結果では、ランダムフォレストの方がF1スコア等が高く良好のようだった。

参考


1.10. Decision Trees — scikit-learn 0.24.2 documentation
Understanding Decision Trees for Classification (Python) | by Michael Galarnyk | Towards Data Science
決定木分析のパラメータ解説 – S-Analysis

【機械学習_Python】Ridge回帰、Lasso回帰、Elastic Net回帰

Ridge回帰、Lasso回帰、Elastic Net回帰についてのメモ。
本記事は「機械学習」における回帰分析という視点が強め。

Ridge回帰等と区別するため、最小二乗法による一般的な回帰分析を最小二乗回帰分析(ordinary least square:OLS)と呼ぶ。

本記事では Python の sklearn を用い、損失関数の計算式は scikit-learn 0.24.2 の仕様のものを記載している。

【目次】


はじめに① ~最小二乗回帰分析(ordinary least square:OLS)のおさらい~


最小二乗回帰分析については次の記事も参照。統計学寄りで書いた記事。
【統計】単回帰分析 - こちにぃるの日記
【統計】重回帰分析 - こちにぃるの日記

回帰モデル、予測式及び回帰係数(重み)は次のとおり(上の記事より抜粋)。

\quad y=\beta_{0}+\beta_{1}x_{1}+\beta_{2}x_{2}+...+\beta_{k}x_{k} + \varepsilon\ ,\quad\sim\varepsilon: N\left(0,\sigma^{2}\right)

\quad \widehat{y}_{i}=b_{0}+b_{1}x_{1i}+b_{2}x_{2i}+...+b_{k}x_{ki}

\quad b_{0}=\overline{y} - \left( b_{1}\overline{x_{1}}+b_{2}\overline{x_{2}}+...+b_{k}\overline{x_{k}} \right)

\quad \begin{bmatrix} b_{1} \\ b_{2} \\ \ \vdots \\ b_{k} \end{bmatrix} = \begin{bmatrix} S_{x_{1}x_{1}} \quad S_{x_{1}x_{2}} \quad \ldots \quad S_{x_{1}x_{k}} \\ S_{x_{2}x_{1}} \quad S_{x_{2}x_{2}} \quad \ldots \quad S_{x_{2}x_{k}} \\ \ \vdots \qquad \ \vdots \qquad \ \ddots \qquad \ \vdots \\ S_{x_{k}x_{1}} \quad S_{x_{k}x_{2}} \quad \ldots \quad S_{x_{k}x_{k}} \end{bmatrix}^{-1} \begin{bmatrix} S_{x_{1}y} \\ S_{x_{2}y} \\ \ \vdots \\ S_{x_{k}y} \\ \end{bmatrix}


これを行列表現すると回帰係数(重み)は次のように求められる。

正規方程式

\quad \left( \boldsymbol{X}^{t}\boldsymbol{X}\right) \widehat{\boldsymbol{\beta }}=\boldsymbol{X}^{t}\boldsymbol{Y}

\quad \widehat{\boldsymbol{\beta} }=\left( \boldsymbol{X}^{t} \boldsymbol{X} \right) ^{-1}\left( \boldsymbol{X}^{t} \boldsymbol{Y} \right)

  ※ ^ t = 転置、^ -1 = 逆行列

Pythonコード

#-- インポート
from sklearn.linear_model import LinearRegression
from sklearn import metrics
import numpy as np
  
#-- 訓練・テスト
mod1 = LinearRegression()
mod1.fit(X_train, y_train)  #-- 訓練データで学習
y_pred1 = mod1.predict(X_test)  #-- テストデータで予測
  
#-- モデル評価
R2sq = mod1.score(X_test, y_test)  #-- 決定係数(寄与率)の算出
MAE  = metrics.mean_absolute_error(y_test, y_pred1)  #--  MAEの算出
RMSE = np.sqrt(metrics.mean_squared_error(y_test, y_pred1)) #-- RMSEの算出
  
#-- 切片、回帰係数(重み)の取得
print("切片 = ", mod1.intercept_)
print("回帰係数 = ", mod1.coef_)


はじめに② ~正則化法の導入~


最小二乗回帰分析では、損失関数  L を実測値と予測値の差(残差  e_{i} = y_{i} - \hat{y_{i}} )とし、当該関数の最小化を目的とした。

 \quad L = || \boldsymbol{y} - \boldsymbol{X \beta} || _{2}^{2} = {\displaystyle \Sigma_{i=1}^{n}}\left(y_{i} - \hat{y_{i}}\right)^{2} = {\displaystyle \Sigma_{i=1}^{n}} e_{i}^{2}


最小二乗回帰分析は機械学習の分野でも「教師あり学習」として広く用いられる手法であるが、複数の特徴量(説明変数)を扱うモデルにおいては次の問題に注意が必要となる。特に「過学習」は将来の予測に重きを置く機械学習の分野で良く耳にするワードである。

・多重共線性(Multicollinearity)
過学習(Over fitting)

これら問題への対策の1つとして正則化法がある。
式としては、最小二乗回帰分析の損失関数  L正則化\lambda \left( Lp \right) (ペナルティ項、罰則項)を加えたものを目的関数とする。

 \quad L = || \boldsymbol{y} - \boldsymbol{X \beta} || _{2}^{2} + \lambda \left( Lp \right)= {\displaystyle\Sigma_{i=1}^{n}}\left(y_{i} - \hat{y_{i}}\right)^{2}  + \lambda \left( Lp \right)

正則化項のラムダ  \lambda はハイパーパラメータであり、ラムダ  \lambda を0にすれば通常の最小二乗回帰分析となる。
また、  Lp は回帰係数(重み)を用いたLpノルムであり、L1またはL2ノルムが用いられる。損失関数(残差)を小さくするために特徴量(説明変数)を多くすれば、Lpノルムが大きくなり罰則として働く。

L1ノルム(マンハッタン距離)

 \quad L_{1} = ||\boldsymbol{\beta}||_{1} = {\displaystyle\Sigma_{j=1}^{m}} |\beta_{j}|

L2ノルム(ユーグリッド距離)

 \quad L_{2} = ||\boldsymbol{\beta}||_{2} = \sqrt{ {\displaystyle \Sigma_{j=1}^{m}} \beta_{j}^{2}}


回帰係数2つのL1ノルムとL2ノルムの2乗は次のとおりである。
f:id:cochineal19:20210516132056p:plain:w500

なお、Lp正則化を行う場合、特徴量(説明変数)を事前に標準化しておく必要がある。

Pythonコード

from sklearn.preprocessing import StandardScaler
 
SS = StandardScaler()
X_train_SS = SS.fit_transform(X_train)
X_test_SS  = SS.transform(X_test)


Ridge回帰


Ridge回帰はL2正則化を用いた方法である。L2ノルム(ユーグリッド距離)の2乗を用いる。

 \quad L = || \boldsymbol{y} - \boldsymbol{X \beta} || _{2}^{2}+\lambda || \boldsymbol{\beta} || _{2}^{2} = {\displaystyle\Sigma_{i=1}^{n}}\left(y_{i} - \hat{y_{i}}\right)^{2} +\lambda {\displaystyle\Sigma_{j=1}^{m}} \beta_{j}^{2}


Ridge回帰の特徴は、多重共線性の問題を回避できるところである。
最小二乗回帰分析のセクションで正規方程式を示したが、最小二乗回帰分析では逆行列  \left( X^{t}X\right) ^{-1} が存在しない場合、多重共線性の問題が生じる。

\quad \widehat{\boldsymbol{\beta} }=\left( \boldsymbol{X}^{t} \boldsymbol{X} \right) ^{-1}\left( \boldsymbol{X}^{t} \boldsymbol{Y} \right) \quad \verb|(再掲)|

Ridge回帰では、正規方程式に  \lambda \boldsymbol{ I } が加わることで逆行列が計算できるようになり、多重共線性の問題が解消される。なお、 \boldsymbol{ I }単位行列 \lambdaスカラーでハイパーパラメータである。

\quad \widehat{\boldsymbol{\beta} }=\left( \boldsymbol{X}^{t} \boldsymbol{X} + \lambda \boldsymbol{I}\right) ^{-1}\left( \boldsymbol{X}^{t} \boldsymbol{Y}\right)


Ridge回帰では特徴量選択(変数選択)こそ出来ないが、相関の高いデータセットに対して安定性があり、汎化性の向上が見込める。


Pythonコード

from sklearn.linear_model import Ridge
mod1 = Ridge(alpha=1)
mod1.fit(X_train_SS, y_train_SS)
 
#-- 以降の使い方はLinearRegressionと同じなので割愛

※引数 alpha が本記事内のハイパーパラメータ :λ(ラムダ)にあたる。コード例の設定値はデフォルト。デフォルトのままで良いなら記載不要。

Lasso回帰(Least absolute shrinkage and selection operator regression)


Lasso回帰はL1正則化を用いた方法である。L1ノルム(マンハッタン距離)を用いる。

 \quad L = \dfrac{1}{2n} || \boldsymbol{y} - \boldsymbol{X \beta} || _{2}^{2}+\lambda || \boldsymbol{\beta} || _{1} = \dfrac{1}{2n}{\displaystyle\Sigma_{i=1}^{n}}\left(y_{i} - \hat{y_{i}}\right)^{2} +\lambda {\displaystyle\Sigma_{j=1}^{m}} | \beta_{j} |


Lasso回帰の特徴は、いくつかの回帰係数(重み)が0になることであり、この性質から特徴量選択(変数選択)を自動的に行うことができる。スパース推定と言われる。

Lasso回帰による特徴量選択(変数選択)は非常に有用であるが、使用の際は次の性質に注意されたい。

  • サンプルサイズn以上の特徴量を選択できない。
  • 相関の高い特徴量同士では片方が選択されない可能性がある。重要な特徴量同士の相関が高い場合、特に注意。
  • 特徴量間の相関が高いデータセットでは、少しの変動で選択される特徴量が変わる不安定さがあるようである。


回帰係数(重み)の計算方法は現時点で理解できていないため割愛する。
PythonのskleanやRのglmnetではCoordinate Descent法(CD法、座標降下法)が用いられているようである。

次の図は、正則化の概念を示した良く見かける図であり、損失関数(最小二乗誤差)の最適値と制約条件内(水色の円形と四角形)での最適値を示したものである。

f:id:cochineal19:20210516134227p:plain:w700

制約条件内の最適値が線上にあれば回帰係数(重み)が0になるため特徴量選択ができる。
この制約条件(水色の円形と四角形)の大きさはハイパーパラメータ:λ(ラムダ)で調整できる。

Pythonコード

from sklearn.linear_model import Lasso
mod1 = Lasso(alpha=1)
mod1.fit(X_train_SS, y_train_SS)
  
#-- 以降の使い方はLinearRegressionと同じなので割愛

※引数 alpha が本記事内のハイパーパラメータ :λ(ラムダ)にあたる。コード例の設定値はデフォルト。デフォルトのままで良いなら記載不要。

Elastic Net回帰


Elastic Net回帰はL1正則化とL2正則化の両方を用いた方法である。

 \quad \rho = L_{1}Ratio \qquad *\ \rho=[ 0,\ 1]

 \quad L = \dfrac{1}{2} || \boldsymbol{y} - \boldsymbol{X \beta} || _{2}^{2}+\lambda \ \rho \ || \boldsymbol{\beta }|| _{1} + \dfrac{\lambda \left(1 - \rho \right)}{2} || \boldsymbol{\beta} || _{2}^{2}

 \qquad = \dfrac{1}{2} {\displaystyle\Sigma_{i=1}^{n}}\left(y_{i} - \hat{y_{i}}\right)^{2} +\lambda \ \rho \ {\displaystyle\Sigma_{j=1}^{m}} | \beta_{j}| + \dfrac{\lambda \left(1 - \rho \right)}{2} {\displaystyle\Sigma_{j=1}^{m}} \beta_{j}^{2}


L1正則化の「特徴量選択」とL2正則化の「特徴量間の相関の高いデータセットに対する安定性」を組み合わせた方法であり、L1正則化とL2正則化の比率(L1-Ratio)を指定することでモデルの調整ができる。L1-Ratioはハイパーパラメータである。

Ridge回帰やLasso回帰に比べて、Elastic Net回帰が必ずしも良いという訳ではなく、最終的には決定係数やRMSE等の評価指標を用いて最良モデルを選択する。

Elastic Net回帰は式の通り、L1-Ratio=1でLasso回帰と同等となるが、L1-Ratio=0の時はRidge回帰とイコールにならない。
※ただし、Rのglmnetライブラリでは、Elastic Netの式をベースとしてL1-Ratio=1をLasso回帰、L1-Ratio=0をRidge回帰としているようである。
https://cran.r-project.org/web/packages/glmnet/glmnet.pdf
https://cran.r-project.org/web/packages/glmnet/vignettes/glmnet.pdf

Pythonコード

from sklearn.linear_model import ElasticNet
mod1 = ElasticNet(alpha=1, l1_ratio=0.5)
mod1.fit(X_train_SS, y_train_SS)
  
#-- 以降の使い方はLinearRegressionと同じなので割愛

※引数 alphaとl1_ratio が本記事内のハイパーパラメータ :λ(ラムダ)とρ(ロー、L1-ratio)にあたる。コード例の設定値はデフォルト。デフォルトのままで良いなら記載不要。

bostonデータで各モデルを実行してみる


bostonデータを使って最小二乗回帰、Ridge回帰、Lasso回帰、Elastic Net回帰を実行してみる。

from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn import datasets, model_selection, preprocessing, metrics
import pandas as pd
import numpy as np

#-- ロード
ROW = datasets.load_boston()
ADS = pd.DataFrame(ROW.data, columns=ROW.feature_names).assign(target=np.array(ROW.target))

#-- 分割
X_train, X_test, y_train, y_test = model_selection.train_test_split(
        ADS[ROW.feature_names], ADS["target"], test_size=0.3, random_state=1)

#-- 標準化
SS = preprocessing.StandardScaler()
X_train_SS = SS.fit_transform(X_train)
X_test_SS  = SS.transform(X_test)

#-- 回帰分析
lm1 = {"最小二乗回帰":LinearRegression()
       ,"Lasso回帰":Lasso(alpha=1)
       ,"Ridge回帰":Ridge(alpha=1)
       ,"ElasticNet回帰 L1-Ratio=0"  :ElasticNet(alpha=1, l1_ratio=0)
       ,"ElasticNet回帰 L1-Ratio=0.5":ElasticNet(alpha=1, l1_ratio=0.5)
       ,"ElasticNet回帰 L1-Ratio=1"  :ElasticNet(alpha=1, l1_ratio=1)
       }

tmp1 = pd.DataFrame(["決定係数","MAE","RMSE"], columns=["評価指標"])
tmp2 = pd.DataFrame(ROW.feature_names, columns=["特徴量"])

for modnm, modcd in lm1.items(): 
    mod1 = modcd
    mod1.fit(X_train_SS, y_train)
    y_pred1 = mod1.predict(X_test_SS)
    R2sq = mod1.score(X_test_SS, y_test)
    MAE  = metrics.mean_absolute_error(y_test, y_pred1)
    RMSE = np.sqrt(metrics.mean_squared_error(y_test, y_pred1))
    tmp1[modnm] = [R2sq, MAE, RMSE]
    tmp2[modnm] = mod1.coef_


今回の結果をRMSEで比べてみるとRidge回帰が最も良好だった。

f:id:cochineal19:20210517002901p:plain


また、回帰係数(重み)については、Lasso回帰とElasticNet回帰(L1-Ratio > 0)で特徴量選択がされている。
他、ElasticNet回帰のセクションで触れた通り、L1-Ratio=1ではLasso回帰と同値となるが、L1-Ratio=0ではRidge回帰と一致しない。

f:id:cochineal19:20210517002934p:plain


参考


1. Supervised learning — scikit-learn 0.24.2 documentation
https://www.jstage.jst.go.jp/article/bjsiam/28/2/28_28/_pdf/-char/ja
Rでスパースモデリング:Elastic Net回帰についてまとめてみる - データサイエンティスト(仮)
リッジ回帰とラッソ回帰の理論と実装を初めから丁寧に - Qiita
線形回帰・Lasso(ラッソ)回帰・Ridge(リッジ)回帰を実装してみよう!|スタビジ

【R】ggplot:対応のあるデータの推移図 + 2つの座標点の角度を求める

対応のあるデータを線でつないで増減をみるグラフを作りたくなったのでメモ。

サンプルデータ


library(tidyverse)

ads <- data.frame(id=(1:5)
                  ,x=c(9,6,3,8,7)
                  ,y=c(5,1,2,4,1)
                  ,z=c(8,7,9,5,2))

ads2 <- data.frame()
for (i in (2:4)) {
  ads2 <- union_all(ads2
                   ,data.frame(id=ads[1]
                               ,param=rep(colnames(ads[i]),nrow(ads))
                               ,aval =c(ads[,i])))
}
ads2$param <- factor(ads2$param, level=c("x","y","z"))
ads2$id <- factor(ads2$id)


作図


ggplot(data=ads2, mapping=aes(x=param, y=aval, group=id, color=id)) +
  geom_vline(xintercept="x", lty=1) +
  geom_vline(xintercept="y", lty=1) +
  geom_vline(xintercept="z", lty=1) +
  geom_line(size=1.2) +
  annotate("text", x="x", y=ads2[ads2$param=="x","aval"]
           ,label=ads2[ads2$param=="x","id"], hjust=2, size=8) +
  labs(title="対応のあるデータの変化の推移", x="時期", y="値") +
  scale_x_discrete(labels=c("時期1","時期2","時期3")) +
  theme(plot.title  = element_text(size=20)
        ,axis.title = element_text(size=20)
        ,axis.text  = element_text(size=20)
        ,legend.title = element_text(size=20)
        ,legend.text  = element_text(size=20)
        )


f:id:cochineal19:20210415003350p:plain:w500

2つの座標点の角度を求める


角度により増減の度合いを判断したいとする。
すると、コサインを知りたい。
弧度法による計算は次のとおり。atan2(アークタンジェント2)関数を用いる。度数法への変換もできる。
atan2 - Wikipedia

 弧度法 : ラジアン = atan2(y, x)
 度数法 : 度 = ラジアン × 180 ÷ 円周率

> #-- ここでは、y=関心のある値の差、x=1として、yの違いを角度で表す。
> ads$ラジアンxy <- atan2(y = ads$y - ads$x, x = 1)
> ads$度xy <- ads$ラジアンxy * 180 / pi
> 
> ads$ラジアンyz <- atan2(y = ads$z - ads$y, x = 1)
> ads$度yz <- ads$ラジアンyz * 180 / pi
> ads
 
  id x y z ラジアンxy      度xy ラジアンyz     度yz
1  1 9 5 8 -1.3258177 -75.96376  1.2490458 71.56505
2  2 6 1 7 -1.3734008 -78.69007  1.4056476 80.53768
3  3 3 2 9 -0.7853982 -45.00000  1.4288993 81.86990
4  4 8 4 5 -1.3258177 -75.96376  0.7853982 45.00000
5  5 7 1 2 -1.4056476 -80.53768  0.7853982 45.00000
本ブログは個人メモです。 本ブログの内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。