Python初心者の備忘録 #21 ~機械学習入門編07~

# はじめに
今回私は最近はやりのchatGPTに興味を持ち、深層学習について学んでみたいと思い立ちました！  
深層学習といえばPythonということなので、最終的にはPythonを使って深層学習ができるとこまでコツコツと学習していくことにしました。  
ただ、勉強するだけではなく少しでもアウトプットをしようということで、備忘録として学習した内容をまとめていこうと思います。  
この記事が少しでも誰かの糧になることを願っております！
**※投稿主の環境はWindowsなのでMacの方は多少違う部分が出てくると思いますが、ご了承ください。**
最初の記事：[Python初心者の備忘録 #01](https://qiita.com/Yushin-Tati/items/961dc47d6163f944f7e9)
前の記事：[Python初心者の備忘録 #20 ~機械学習入門編06~](https://qiita.com/Yushin-Tati/items/2473d0abb551b53f7e0d)
次の記事：[Python初心者の備忘録 #22 ~機械学習入門編08~](https://qiita.com/Yushin-Tati/items/861e88fea88c04fd9c32)

今回は**クラスタリング**、**決定木**、**SVM**についてまとめております。
## ■学習に使用している資料
Udemy：[【後編】米国データサイエンティストがやさしく教える機械学習超入門【Pythonで実践】
](https://www.udemy.com/course/mlpython-2/)
## ■クラスタリング(Clustering)
### ▶クラスタリングとは
- 教師データを使わない分類タスクの一種
データをカテゴリーやクラス、ラベルと呼ばれる「種類」に分別・グループ化していく
:::note info
分類/識別(classification)との違い
クラスタリングは**教師なし学習**
:::
#### 教師あり/なし学習のおさらい
##### 教師あり学習(Supervised learning)
- 正解の情報(ラベルや値)から損失を計算し、損失が最小となるようにモデルを構築
※回帰や分類タスクは教師あり学習
- ◎ 正解があるのでモデルの結果に対して解釈しやすい
- ✕ 正解データを集める必要がある

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/903533f4-e1e4-6cf6-359a-f6b67fccec7d.png)
##### 教師なし学習(Unsuperviced learning)
- 類似しているデータをグループ(クラスタ)にしてデータを分類
※クラスタリングは教師なし学習
- ✕ 正解データがないのでやり方によって結果が色々と異なり解釈が難しい
- ◎ 正解データを集める必要がないので簡単に行える

![explaine_clustering_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/2073eabd-99f3-34ae-8a61-96e995a9c120.png)

:::note info
**クラスタリングの実用例**
- Fake Newsやスパムメールなどの判定：その時に正解がわからないことが多い
- 顧客のセグメンテーション(マーケティング)：対象によって正解が異なる
- 画像処理：似た画像でも光の加減でRGB値が異なるので正解を決めにくい
:::
### ▶k-means
- 最も基本的なクラスタリングアルゴリズム
- データをk個のクラスタに分類する
→ 各クラスタ内の分散の合計が全体で最小となるようにする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/2815d079-9ff6-ca80-d2d3-bfee0b894283.png)
#### k-meansの損失関数
- 全クラスタ内の各データ同士の差の平方和(クラスタ内分散)の合計を損失関数とする
※あるアルゴリズムに従って局所解を見つける
クラスタ内分散：$min(\sum_{k=1}^K\sum_{i,i'\in C_{k}}(x_i-x_{i'})^2)$

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/7fa22070-a4c7-e318-bc4b-49fa92b16438.png)
#### k-meansのアルゴリズム
1. ランダムにそれぞれのデータに対して1～Kのクラスタに振り分ける
1. クラスタが変更しなくなるまで以下を繰り返す
    1. 各クラスタの中心を求める
    1. 最も近いクラスタの中心をそのデータのクラスタとする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/010da478-390e-7abd-b032-bc1baf3b757c.png)

:::note warn
このアルゴリズムで行きつく答えは最適解(Gloval Optima)にならず、**極小解(Local Optima)となる**ことがあることに注意する。
→ そのためk-meansを複数回行い、解が最小となるものを選択するのが一般的
:::
### ▶K(クラスタ数)の決め方
- (当然)データ数よりも少ない必要がある
- 明確な正解はなく、ドメイン知識やデータの背景から仮説を立てる必要がある
→ いろいろなKを試して考察するのが一番

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/f6c0596e-9e30-30b8-6b19-81d362ba4f44.png)
★**とは言いつつも方法がないわけではなく**、方法の一つに**Elbow method**がある。

:::note info
**Elbow method**
- Kを増やしていき、損失が急に下がったところのKを採用する方法
※必ずしもElbowがあるとは限らない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/ba39f866-bb9c-83ff-969a-75647cbc5878.png)
:::
#### Pythonでk-means
- `sklearn.cluster.KMeans`ライブラリを使用する
    1. `KMeans(n_clusters)`でインスタンス生成(`n_cluster`：クラスタ数の指定)
    1. `.fit(X)`で学習 → クラスタリング実行
        - `.predict(X)`で各データのクラスタリングの結果を参照
        - `.score(X)`で損失を計算
```python:k-means
# データ準備
import seaborn as sns
df = sns.load_dataset('iris')
X = df.drop(columns=['species'])

# 標準化
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X)

# k-means
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3, random_state=0)
labels = kmeans.fit_predict(X_scaled) # .fit(X)と.predict(X)を同時に行う
```
```text:result
# ilisデータのクラスタリング結果が出ている
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2,
       0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2,
       2, 2, 2, 0, 0, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2,
       2, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 0], dtype=int32)
```
#### k-meansの描画
```python
## データの分布を描画
import pandas as pd
result_df = pd.concat([X, pd.DataFrame(labels, columns=['kmean_result'])], axis=1)
sns.pairplot(result_df, hue='kmean_result')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/653c085c-2017-8974-cf17-593a682ea7e9.png)
```python
# 教師付きのデータ分布
sns.pairplot(df, hue='species')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/b6b178cb-b8f8-3c15-8c92-898cc4f9b3ad.png)

:::note info
教師なし学習であるクラスタリングの描画と教師あり学習の分類の描画では大体あっているが多少の違いがあることがわかる。
→ 教師なし学習のクラスタリングでは正解を出すことは難しいが、ある程度の判断材料には使用できる。
:::

#### k-meansの損失の推移を描画
```python
import matplotlib.pyplot as plt
# Kが1～9の時の損失関数を計算
losses = []
for K in range(1, 10):
    kmeans = KMeans(n_clusters=K, random_state=0).fit(X_scaled)
    losses.append(-kmeans.score(X_scaled))

# Elbow methodを描画
plt.plot(range(1, 10), losses)
plt.xlabel('K')
plt.ylabel('loss')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/0443abe8-5982-4f47-4696-9240ffb2d70c.png)
★**Elbow methodを見る限り、Kは2か3がよさそう**と分かる
### ▶k-meansの注意点
- 特徴量の数が多い(高次元空間)場合は2組のデータ間の距離に差がなくなってくるので、うまくクラスタリングできなくなる：次元の呪い(curse of dimensionality)
→ **特徴量選択やPCAで次元削減する**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/ea1f42c4-6733-ffce-2d06-c33aeca3bcdf.png)
- 質的変数との相性が悪い
    - ダミー変数化して距離を計算しても距離の意味を持たない
    $\sqrt{(0-1)^2+(1-1)^2+(1-0)^2}$：0か1でしか計算できない
    - 高次元化し、やはりデータ間の距離に差がなくなっていく
→ **量的変数のみでk-meansをしたり、k-modesなどの別のアルゴリズムを使用する**
### ▶階層クラスタリング(Hierarchical Clustering)
- k-meansと双璧を成す有名なクラスタリング手法
- 事前にクラスタ数(K)を指定する必要がないのことが特徴で、dendrogram(樹状図)を作り"階層的に"クラスタリングする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/2e93bdf7-e6db-05b3-792f-dee75dc9b219.png)

:::note info
**dendrogramとは**
- 階層クラスタリングにおいてクラスタリングされていった過程を表したもの
![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/7f6794d8-5561-7e9a-e1f8-bacddb7128e0.png)
:::
#### 階層クラスタリングのアルゴリズムの流れ
※データは事前に標準化しておく
1. (初期設定)各データを各クラスタとする
1. 距離が最も近いクラスタ同士を融合し、1つのクラスタにする
1. 2.を繰り返す
1. 最終的にクラスが1つになり終了

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/7ed21db6-ee34-dc2b-430d-e96e6f65fc98.png)
### ▶クラスタ間の距離の測り方
#### single
- クラスタ間で最も近いデータの距離
※たまたま1つでも近いデータがあれば融合してしまうのでバランスが悪くなりがち

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/e9f68b3e-9034-2147-4b01-8571c7d32600.png)
#### complete
- クラスタ間で最も遠いデータの距離

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/81df3c00-d6b1-4070-2146-4d5e2ad1188a.png)
#### average
- クラスタ間のすべての2組のデータの距離の平均

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/5a621a73-98aa-2e72-3255-5e07c6a82534.png)
#### centroid
- 各クラスタの中心間の距離

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/d530ced4-5b53-20a5-fcc4-1dc94d4e40f0.png)

※**inversionが起こる可能性あり**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/559f4814-a082-7561-afb6-b5af88613438.png)
#### ward(ウォード法)
- 全データの中心から各データの距離の二乗和(分散)から、それぞれのクラスタの中心とそのクラスタのデータの距離の二乗和を引いた値
- 計算量は多いが分類の感度が良いのでよく使われる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/92887a68-4453-686b-c0d3-573a382bbbfe.png)

:::note info
**類似度(距離)の定義**
- ユークリッド距離
※特に理由がなければこれを使用する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/ffb28623-9329-fb7c-2159-2243f830c6ba.png)
- マンハッタン距離
※直線距離ではなく、直角に迂回するように距離を算出する

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/de3499bb-f2a6-25cb-0e6f-4ed11be93c4b.png)

**相関を使って類似度を測る**
- 距離でうまくクラスタリングできないケースでは相関を使うことでうまく出来ることがある

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/4fcf90a0-f3b0-e603-1c21-b5f56b475722.png)
:::
### ▶dengrogramの使い方と注意点
- 縦軸の任意の箇所で切ることで、好きなクラスタ数でクラスタリングができる

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/5ee5a0ff-b11c-422f-f690-6ee3a50547b3.png)
#### 注意点
- ネストできない様なデータに対してもネスト型の階層を作ってしまう
→ Kの値によって分類の軸を分けることができない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/0e35cbd0-bad5-279b-30b4-bc9f93219b0d.png)
### ▶Pythonで階層クラスタリング
#### 階層クラスタリング
- `scipy.cluster.hierarchy.linkage`ライブラリを使用
- `linkage(X, method)`でクラスタリングを実施
    - `method`には`'single', 'average', 'complete', 'centroid', 'ward'`等を指定
- dendrogramを構成する情報をNumpy Arrayで返す
    - `[クラスタ1のindex, クラスタ2のindex, 距離, データの数]`
        - index < データ数：学習データのインデックス(クラスタ = データ)
        - index >= データ数：linkageの戻り値のindex(結合されたクラスタに割り振られたindex)

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/2b39420b-f285-7bd1-72b1-0220a3644a4d.png)
```python:階層クラスタリング
from scipy.cluster.hierarchy import linkage, dendrogram
Z = linkage(X_scaled, method='ward')
# [クラスタ1のidx, クラスタ2のidx, 距離, データ数]のリスト
# idxは，idx<len(X)ならX[idx]　idx>=len(X)ならZ[idx-len(X)]でクラスタを取得
Z[:10]
```
```text:result
# n回目のクラスタリングで使用したデータの情報[クラスタ1のidx, クラスタ2のidx, 距離, データ数]のリスト
array([[1.01000000e+02, 1.42000000e+02, 0.00000000e+00, 2.00000000e+00],
       [7.00000000e+00, 3.90000000e+01, 1.21167870e-01, 2.00000000e+00],
       [1.00000000e+01, 4.80000000e+01, 1.21167870e-01, 2.00000000e+00],
       [9.00000000e+00, 3.40000000e+01, 1.31632184e-01, 2.00000000e+00],
       [0.00000000e+00, 1.70000000e+01, 1.31632184e-01, 2.00000000e+00],
       [1.28000000e+02, 1.32000000e+02, 1.31632184e-01, 2.00000000e+00],
       [1.27000000e+02, 1.38000000e+02, 1.33836265e-01, 2.00000000e+00],
       [2.00000000e+00, 4.70000000e+01, 1.33836265e-01, 2.00000000e+00],
       [8.00000000e+01, 8.10000000e+01, 1.43378956e-01, 2.00000000e+00],
       [1.90000000e+01, 4.60000000e+01, 1.43378956e-01, 2.00000000e+00]])
```
#### dendrogram
- `scipy.cluster.hierarchy.dendrogram`ライブラリを使用
- `dendrogram(Z)`で描画
    - `Z`には`linkage`の戻り値を入れる
- `trancate_mode`引数および`p`引数で任意の高さでdendrogramを切ることができる
    - `trancate_mode='lastp', p=K`：指定したK個のクラスタになるように切る
    - `trancate_mode='lastp', p=段数`：p段になるように切る
```python:dendrogram
d = dendrogram(Z, truncate_mode='level', p=2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/cd554301-9a73-68e8-492e-1f280cd98eac.png)
### ▶実際にクラスタリングしてみる
#### 各データのラベル付け
- `scipy.cluster.hierarchy.fcluster`ライブラリを使用
    - `Z`：linkage関数の戻り値
    - `criterion`引数：どの段階のクラス手分けを使うかを指定
        - `'maxclust'`：`t`引数に指定したクラスタ数でラベル付けを行う
    - `t`引数：`criterion`引数によって異なる値を指定する
```python:クラスタの分布描画
from scipy.cluster.hierarchy import fcluster

# ラベル付け
clusters = fcluster(Z, criterion='maxclust', t=3)


hc_result_df = pd.concat([X, pd.DataFrame({'cluster': clusters})], axis=1)
sns.pairplot(hc_result_df, hue='cluster')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/f865cba1-f660-1bd9-0063-2429f4603952.png)
```python:実際の分布
sns.pairplot(df, hue='species')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/edb9ce6f-01b7-50f9-8b4a-e62115e3b1b5.png)
## ■決定木
### ▶決定木とは
- 回帰にも分類にも使用できるアルゴリズムで、人間の意思決定のロジックに近いので非常に解釈しやすい
- 単体での制度は低く、複数の決定木を組み合わせることで非常に高い精度モデルを構築できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/100a071d-5737-7164-ad3b-ace83238c3f2.png)
※**左がtrue、右がfalse、yは高さ**

:::note info
**決定木の部位の名称**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/72834b05-9d1b-0ac3-aa4c-10b560626096.png)
:::
### ▶決定木のアルゴリズム概要
- データを2つの領域に分割していく
回帰の場合：領域の学習データの平均値を予測値をする
分類の場合：多数決

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/669805f4-7bbe-eb97-b482-b257fb415a96.png)
### ▶領域の分割の仕方
#### 回帰
- 分割後の2つの領域の残差(偏差)の平方和の合計が最小になるように二分していく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/0233e789-ff3e-bc8a-7b33-1a7c9b81f443.png)
#### 分類
- **ジニ不純度(gini impurity)** を使用
    - どれだけ綺麗に分けられるかを表す指標
    ⇒ どれだけ「不純なもの」が含まれるか
    $G=\sum_{k=1}^Kp(k)(1-p(k))$（$K$：クラス数、$p(k)$：その領域でのクラスkの割合）
- 分割後の2つの領域のジニ不純度の合計が最小になるように二分していく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/78802c0c-9589-9447-e39a-0f4e24cb4031.png)

:::note info
理想はすべての領域における偏差の平方和の合計が最小になることだが、それだと計算量が高くなるので**分割毎に最小になるようにする**。

そのため、最終的にすべての領域の平方和の合計が**最小になるとは限らない**...
⇒ 途中の分割が後々悪い分割になる可能性もある。(**最適解ではなく、極小解を求めている**)
:::
### ▶決定木の過学習
- 分割を進めていくとモデルが複雑になり、過学習で汎化性能が下がる
⇒ 精度の低下、解釈性の低下、学習時間の増大が発生する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/fea57e3d-4da3-f42c-58e3-aceb899591a9.png)
#### 決定木の分割を途中で終了する
- 主に使用されるのは以下の2パターン
    - 深さを指定
    - 葉の最小データ数を指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/228a630f-29a0-f6fd-d91a-c2e1cf9cf454.png)
#### 決定木の剪定(pruning)
- 葉の数だけペナルティを与える(cost complexity pruning)

![clustering_culcuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/e7c1a7ee-0b0b-616e-284c-b5b16ab5f545.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/bb867877-4ef6-0f55-c5f5-cb1d74242fee.png)

:::note info
**最も小さい$\alpha$を持つ枝から選定していく**とは？
$\alpha$が小さくても分割前と分割後の損失の差がほとんどないということは、分割しなくても結果に影響しないということになる。
⇒ つまり、**分割の必要性が低い**
:::
### ▶Pythonで決定木
#### 回帰
- `sklean.tree.DecisionTreeRegressor`ライブラリを使用
    - `max_depth`：木の深さを指定
    - `min_samples_split`：最低限分割に必要なデータ数を指定
    - `ccp_alpha`：cost complexity pruningの$\alpha$の値を指定
- 使い方は他のsklearnのモデルと同じで、パラメータをしていないと最後まで学習して過学習を起こすので注意
#### 分類
- `sklean.tree.DecisionTreeClassifier`ライブラリを使用
※使い方は回帰と同じ
```python:回帰
import seaborn as sns
import pandas as pd
from sklearn.model_selection import train_test_split

# データ準備
df = sns.load_dataset('tips')
df = pd.get_dummies(df, drop_first=True)
y_col = 'tip'
X = df.drop(columns=[y_col])
y = df[y_col]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

# 決定木モデル(回帰)
from sklearn import tree
model = tree.DecisionTreeRegressor(max_depth=4)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

# MSE
from sklearn.metrics import mean_squared_error
mean_squared_error(y_test, y_pred) # -> 1.1060246260428703

# R^2(R-squared)
model.score(X_test, y_test) # -> 0.3590618660303134
```
### ▶決定木の可視化
#### 描画
- `sklearn.tree.plot_tree`ライブラリを使用
    - `decision_tree`：sklearnのtreeモデルを引数にする
    - `fontsize`：フォントサイズを指定
    - `feature_name`：特徴量の名前をリストで渡す(`model.feature_names_in_`が便利)
- 図の内容が戻り値で返ってくる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/347da27f-ce5c-0720-0157-db81b6219c7e.png)
```python:描画
# figureサイズの調整
import matplotlib.pyplot as plt
plt.figure(figsize=(40, 20))

# 決定木の描画
_ = tree.plot_tree(model, fontsize=10, feature_names=model.feature_names_in_)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/65492a1f-a882-5705-7521-dab696fd9bd4.png)
#### テキスト
- `sklearn.tree.export_text`ライブラリを使用
※使い方は`plot_tree`と同じ
- `print()`関数に渡すことできれいに表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/30db0041-1341-be8f-e829-fcc578b16d2d.png)
```python:テキスト
print(tree.export_text(model, feature_names=list(model.feature_names_in_)))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/c708856d-5fcc-bcce-082c-240460360a12.png)
### ▶決定木の特徴量の重要性
- 予測に重要な特徴量ほど決定木の上部で分岐される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/fd85d118-dde9-d097-9c8f-9875d21151e8.png)
#### Pythonで重量度の可視化
- `.feature_importances_`属性でモデル内の特徴量の重要度を取得できる
⇒ 分岐の際にどれだけ不純度を下げることができたかで計算する
```python:特徴量の重要度の描画
# 各特徴量の重要度
model.feature_importances_ # -> array([0.87823592, 0.07825569, 0., 0.03169922, 0., 0.01180916, 0., 0.])

# 描画
plt.barh(model.feature_names_in_, model.feature_importances_)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/cfbd0ed7-9e4f-a387-b179-6b4e74f0f29f.png)
### ▶Minimal cost complexity pruning(剪定)
- `.cost_complecity_pruning_path(X, y)`で、$\alpha$と不純度をリストで取得できる
- それぞれの$\alpha$でのモデルの精度を計測し、最良の$\alpha$を使用する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/a77078c5-4497-df95-518b-eb9e3910dba6.png)
```python:剪定
# model作成
model = tree.DecisionTreeRegressor(random_state=0)

# 決定木を剪定する閾値となるαと不純度のリスト
path = model.cost_complexity_pruning_path(X_train, y_train)
eff_alphas = path.ccp_alphas

# 各αでのモデルを構築
models = []
for eff_alpha in eff_alphas:
    model = tree.DecisionTreeRegressor(random_state=0, ccp_alpha=eff_alpha)
    model.fit(X_train, y_train)
    models.append(model)

# それぞれのモデルの精度指標を記録(上記のfor文の中で実行した方が効率がいいが，ここでは別ループにて実行する)
from sklearn.metrics import mean_squared_error
train_scores = [mean_squared_error(y_train, model.predict(X_train)) for model in models]
test_scores = [mean_squared_error(y_test, model.predict(X_test)) for model in models]

# 各αで剪定したときのモデルの精度の推移を描画
plt.plot(eff_alphas, train_scores, marker='o', label='train', drawstyle='steps-post')
plt.plot(eff_alphas, test_scores, marker='o', label='test', drawstyle='steps-post')
plt.legend()
plt.xlabel('alpha')
plt.ylabel('MSE')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/8a19b7c3-7416-de9b-5805-46a8d1db5446.png)
※**大体$\alpha$が1の時のモデルがちょうどよさそうというのが見てとれる**

:::note warn
**最後まで剪定したモデルを見てみる**
```python
print(tree.export_text(models[-1], feature_names=list(models[-1].feature_names_in_)))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/220f9529-4b33-7233-ad19-c456e794f064.png)
**１つしかない** ⇒ モデルが極限に**シンプルになっている**。
```python:予測
models[-1].predict(X_test)
```
```text:result
# 全て2.96...と予測される
array([2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412, 2.96429412,
       2.96429412, 2.96429412, 2.96429412, 2.96429412])
```
:::
## ■SVM(Support Vector Machine)
### ▶SVMとは
- 分類と回帰のどちらにも使用可能で、単体のモデルとしては精度が高いことで有名
※深層学習を除いて...
- 決定境界を引くことで分類を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/92da0395-bfb3-d365-a6fe-90d12b438158.png)

- この決定境界があまりにギリギリだと未知のデータを誤分類してしまう可能性がある

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/54062365-bb86-7391-079b-1d388bd1201a.png)

- 決定境界線はある程度の余裕(マージン)を取って引いた方がいい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/c97b87af-2f05-a5c0-05de-92bc13b295f0.png)

:::note info
**結論**
- 直近のデータからある程度余裕を持った方が未知のデータにも対応できる
- あるデータがサポートとなり，そこからの距離(マージン)が最大化するように決定境界をひく
- 距離を使うので事前にSVMでは標準化することが望ましい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/b128bb43-12e8-0e4f-db3f-7a2c4f3f69f1.png)
:::
### ▶ソフトマージンとハードマージン
#### ハードマージン
- 誤分類を許容しないので、外れ値があると大きく結果が変わってしまう
⇒ high variance、過学習、汎化性能が低いモデルになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/91c02e0a-1fb7-54d6-6398-9fb9636b609e.png)
#### ソフトマージン
- 誤分類を許容するので、外れ値があった場合でも大きく結果は変わらない
⇒ モデルのロバスト性が高くなる
- マージンの中にデータが入ることも許容する(ハードマージンでは許容しない)
※境界の直近のデータがサポートベクターになるとは限らない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/b24286af-4baf-1845-35ee-938e3448b682.png)
### ▶SVMの損失関数
- マージンを最大化しつつ誤分類を減らすような関数
$min(\frac{1}{M}+C\sum_{i=1}^m\xi_i)$ ⇒ パラメータCにより誤分類の許容を調整する

:::note info
**超平面**
- n次元空間内の平坦なn-1次元の集合
⇒ 2次元空間の超平面は線(1次元)、3次元空間の超平面は平面(2次元)
- 線形分類器の**決定境界は超平面**になる
つまり、**マージンは超平面からの距離**ということになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/d6da9bde-6fb2-8973-554b-d549ab4c6d54.png)
:::
#### ヒンジ損失(hinge loss)
- 交差エントロピー(log loss)を近似した損失で、近似により計算量が削減できる
- 1>や-1<で損失するようにすることで、マージンを考慮した損失となる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/b79b8fa4-9477-10b1-4730-d389d4f0e68f.png)

:::note info
上図において、$\theta^Tx$が0の時にp(x)=0.5になり、0より大きいか小さいかで正解/不正解の識別は可能のように見えるが、それぞれ1や-1までいかなければ十分なマージンではないということを表している。
:::
#### ハードマージンにおける制約
- 誤分類が許されないので、以下のような制約で最適化問題を解くこととなる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/99f72ebd-fb3e-6ffb-14a8-51334108048b.png)
#### ソフトマージンにおける制約
- 誤分類を許容するので、制約は以下のように緩める

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/63e15e2f-977f-4c92-7b0b-27a07c4d8fbb.png)
### ▶非線形の分類
- SVMを非線形分離可能のアルゴリズムとする
- 特徴量空間を非線形変換することで非線形分離を実現する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/2a0fb8e3-21b6-818e-3ff9-59d09cf66e91.png)
#### カーネルトリック(kernel trick)
- 特徴量を写像した際に膨大となる各データ間の内積の計算を解決する方法
カーネル関数：$K(x_i, x_j)=\phi(x_i)^T\phi(x_j)$
##### 様々なカーネル関数
- 多項式カーネル
$K(x_i, x_j)=(x_i^Tx_j+c)^d$
- ガウスカーネル
$K(x_i, x_j)=exp(-\frac{||x_i-x_j||^2}{2\sigma^2})$
- シグモイドカーネル
$K(x_i, x_j)=\frac{1}{1+exp(-\gamma x_i^Tx_j)}$
### ▶PythonでSVM
- `sklearn.svm.SVC`を使用する
    - `C`：エラーの正則化項の係数(誤分類の許容値と考えればいい)
    - `kernel`：使用するカーネル
        - `linear`,`poly`,`rdf`,`sigmoid`など
    - `degree`：'poly'のd(デフォルトだと3)
    - `gamma`：'poly'、'rdf'、'sigmoid'の係数$\gamma$
        - 'scale'：$\frac{1}{n×Var(X)}$（デフォルト）
        - 'auto'：$\frac{1}{n}$
    - `.support_vectors_`でサポートベクトルのリストを取得
- 回帰は`sklearn.svm.SVR`
```python
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# データ準備
df = sns.load_dataset('iris')
y_col = 'species'
X = df.drop(columns=[y_col])
y = df[y_col]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

# 標準化
scaler = StandardScaler().fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)

# PCA
pca = PCA(n_components=2)
X_train_pc = pca.fit_transform(X_train)
X_test_pc = pca.transform(X_test)

# 学習(SVM)
from sklearn.svm import SVC
model = SVC(kernel='poly', C=100)
model.fit(X_train_pc, y_train)

# 予測
y_pred = model.predict(X_test_pc)

# 評価
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred) # -> 0.9333333333333333

# サポートベクトルのリスト取得
# model.support_vectors_
```
### ▶決定境界とサポートベクトルの可視化
- `sklearn.inspectopn.DecisionBoundaryDisplay`を使用
※version 1.1から使用可能(python>3.8)
    - `.from_estimator(model, X)`で描画
```python
from sklearn.inspection import DecisionBoundaryDisplay
import matplotlib.pyplot as plt
import numpy as np

# 決定境界描画
DecisionBoundaryDisplay.from_estimator(model, 
                                       X_train_pc,
                                       plot_method='contour',
                                       cmap=plt.cm.Paired,
                                       xlabel='first principal component',
                                       ylabel='second principal component')

# (PCA後の)学習データ描画
for class_, color in zip(model.classes_, 'bry'):
    idx = np.where(y_train == class_)
    plt.scatter(X_train_pc[idx, 0],
                X_train_pc[idx, 1],
                c=color,
                label=class_,
                edgecolor='black',
                s=20)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/a1978a84-fbb8-19d1-663d-bb1aa790b5e3.png)
```python
# サポートベクトル描画
plt.scatter(model.support_vectors_[:, 0],
            model.support_vectors_[:, 1],
            s=100,
            facecolor='none',
            linewidth=1,
            edgecolor='black')
plt.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3448232/66c09e41-243c-999b-eedf-35108fe3eac4.png)
#### 次の記事
[Python初心者の備忘録 #22 ~機械学習入門編08~](https://qiita.com/Yushin-Tati/items/861e88fea88c04fd9c32)