Python  - ScikitLearnの学習 ( 前処理、SVM )

Odyssey CBTのPython3 エンジニア認定データ分析試験の学習を始めました。今回はScikitLearnを用いた前処理、およびサポートベクターマシーンを用いた機械学習・分類アルゴリズムについて学習しました。

https://scikit-learn.org/stable/index.html

# ScikitLearnについて
ScikitLearnは機械学習・データマイニングを行うためのライブラリです。 かなり複雑な深層学習以外の機械学習は、たいていScikitで実行できます。 `numpy・pandas`と合わせて使うのが一般的です。

# 前処理
Scikitではデータの前処理を行う事ができます。
機械学習・データマイニングを行うためには、欠損値を埋め合わせる・アルゴリズムに適した値の範囲にデータを収める等々の処理する必要があります。

- 欠損値の対処
- 定性データをワンホットベクトルに変換
- 定量データの正規化・正則化

## 欠損値の除去・保管

```python
import numpy as np
import pandas as pd

df = pd.DataFrame(
    {
    "A":[1,np.nan,3,4,5],
    "B":[6,7,8,np.nan,10],
    "C":[11,12,13,14,15]
    }
)

print(df)
# 欠損値かをbool値で返す
df.isnull()
```

```python
     A    B    C
0  1.0   6.0  11
1  NaN   7.0  12
2  3.0   8.0  13
3  4.0   NaN  14
4  5.0  10.0  15

       A	   B	  C
0	False	False	False
1	True	False	False
2	False	False	False
3	False	True	False
4	False	False	False
```

欠損値の保管 -`SimpleImputer`というメソッドで欠損値を平均・中央値などに変換する
```python
# 欠損値の保管 - 値を補完する、SimpleImputerのメソッドを用いる
# mean-平均、median-中央値、most_frequent-最頻値、constant-定数
from sklearn.impute import SimpleImputer

imp = SimpleImputer(strategy="mean")
imp.fit(df)
imp.transform(df)
```
```python
array([[ 1.  ,  6.  , 11.  ],
       [ 3.25,  7.  , 12.  ],
       [ 3.  ,  8.  , 13.  ],
       [ 4.  ,  7.75, 14.  ],
       [ 5.  , 10.  , 15.  ]])
```

```python
from sklearn.impute import SimpleImputer
value = 0

imp = SimpleImputer(strategy="constant", fill_value=value)
imp.fit(df)
imp.transform(df)
```
```python
array([[ 1.,  6., 11.],
       [ 0.,  7., 12.],
       [ 3.,  8., 13.],
       [ 4.,  0., 14.],
       [ 5., 10., 15.]])
```
# 定性データをワンホットベクトルに変換
`LabelEncoder` - str型などの定性データを値ごとにワンホットベクトルに変換する。
str型などの定性データはワンホットベクトルにすることでSVM・ニューラルネットワークが分類・回帰のアルゴリズムに利用できるようになる。
```python
# LabelEncoder - str型などの定性データを値ごとにワンホットベクトルに変換する
from sklearn.preprocessing import LabelEncoder
df = pd.DataFrame(
    {
    "A":[1,2,3,4,5],
    "B":["a","b","c","a","b"]
    }
)

print(df)

#エンコーディング
le = LabelEncoder()
le.fit(df.loc[:,"B"])

#変換後・元の値を確認
print(le.transform(df.loc[:,"B"]))
print(le.classes_ )
```
```python
   A  B
0  1  a
1  2  b
2  3  c
3  4  a
4  5  b

[0 1 2 0 1]
['a' 'b' 'c']
```

```python
# LabelEncoder - str型などの定性データを値ごとにワンホットベクトルに変換する
from sklearn.preprocessing import LabelEncoder,OneHotEncoder
from sklearn.compose import ColumnTransformer

#データフレームを複製
df_ohe = df.copy()

#エンコーディング
le = LabelEncoder()
df_ohe.loc[:,"B"] = le.fit_transform(df_ohe.loc[:,"B"])

ohe = ColumnTransformer([("OneHotEncoder", OneHotEncoder(),[1])], remainder="passthrough")

df_ohe = ohe.fit_transform(df_ohe)
df_ohe
```
```python
array([[1., 0., 0., 1.],
       [0., 1., 0., 2.],
       [0., 0., 1., 3.],
       [1., 0., 0., 4.],
       [0., 1., 0., 5.]])
```

# 定量データの正規化・正則化
- **分散正規化**-平均が0，標準偏差を1にする
- **最小最大正規化** - 最小値が0、最大値を1にする

```python
#分散正規化
import pandas as pd

df = pd.DataFrame(
    {
    "A":[1,2,3,4,5],
    "B":[100,200,400,500,800]
    }
)

print(df)

#StandardScaler - 分散正規化のインスタンス
from sklearn.preprocessing import StandardScaler

#インスタンスを作成→fit、transformで正規化を実行する
stdsc = StandardScaler()

stdsc.fit(df)
stdsc.transform(df)
```
```python
   A    B
0  1  100
1  2  200
2  3  400
3  4  500
4  5  800
array([[-1.41421356, -1.22474487],
       [-0.70710678, -0.81649658],
       [ 0.        ,  0.        ],
       [ 0.70710678,  0.40824829],
       [ 1.41421356,  1.63299316]])
```

```python
#MinMaxScaler - 最大最小正規化のインスタンス
from sklearn.preprocessing import MinMaxScaler

#インスタンスを作成→fit、transformで正規化を実行する
mmsc = MinMaxScaler()

mmsc.fit(df)
mmsc.transform(df)
```
```python
array([[0.        , 0.        ],
       [0.25      , 0.14285714],
       [0.5       , 0.42857143],
       [0.75      , 0.57142857],
       [1.        , 1.        ]])
```

# 分類アルゴリズム
データをクラスに分類するアルゴリズムを活用します。大量のデータを分類するには、機械学習の中でも**教師あり学習**が活用されます。 ユーザーを利用履歴などをもとに購入層を分析する、またはサービスから退会する可能性を予測する等々のマーケティング戦略に応用されています。

- サポートベクターマシン(SVM)
- 決定木
- ランダムフォレスト法

分類用のモデルを作成するには、まず**データを学習用・テスト用に分割**します。 機械学習の目的は**学習用のデータだけに適応する**だけでなく、**テスト用のデータのような未知の値に対応する性能（汎化能力）を向上すること**である。 汎化能力を効率よく向上させる方法として、学習・テストデータの分割とモデル構築を複数回繰り返す**交差検証**が活用されています。

# SVM

サポートベクターマシーン(SVM) - 教師あり学習の分類・回帰に使えるアルゴリズム。
SVMは**直線・平面などで分類できない（＝線形分離できない）データを分類する**ことができます。SVMのアルゴリズムは線形代数を用いて高次元空間でのデータの内積＝距離を計算することで、データを高次元の空間上で線形分離するというタスクを要します。これに関しては数学オンチなので（笑）復習してきます。

https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%83%99%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%9E%E3%82%B7%E3%83%B3

今回は説明変数が一様乱数で生成した連続値で、2つのベクトルデータ・目的変数が0か1のスカラー値の場合を扱います。

```python
#SVM
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#まずは学習に使うベクトルデータと目的変数を生成する
#乱数シード
rng = np.random.default_rng(123)

#一様乱数とそのラベルを生成 - ラベルが0か1かで分類する
X0 = rng.uniform(size =(100, 2))
y0 = np.repeat(0, 100)

X1 = rng.uniform(-1, 0, size =(100, 2))
y1 = np.repeat(1, 100)

#乱数をプロット
fig, ax = plt.subplots()
ax.scatter(X0[:,0], X0[:,1], marker="o", label="Class-0")
ax.scatter(X1[:,0], X1[:,1], marker="x", label="Class-1")

ax.set_xlabel("x")
ax.set_xyabel("y")
ax.legend()
plt.show()
```
![スクリーンショット 2023-05-20 171934.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337578/3a3ca010-bf59-6fce-358d-d0f07c165c75.png)

データの生成が完了 - SVMを使って生成した乱数の決定境界を求める

```python
#SVMを使って生成した乱数の決定境界・マージン・サポートベクターを求める
from sklearn.svm import SVC

def plot_boundary_margin(X0, y0, X1, y1, kernel, C, xmin = -1, xmax = 1, ymin = -1, ymax = 1):
    #SVMのインスタンス
    #C - マージンの幅。広いほどサポートベクターの数が増える
    svc= SVC(kernel=kernel, C=C)
    
    #学習 - 学習に用いるデータをvstack・hstackで結合する
    svc.fit(np.vstack((X0,X1)), np.hstack((y0,y1)))
    
    #散布図
    fig, ax = plt.subplots()
    ax.scatter(X0[:,0], X0[:,1], marker="o", label="Class-0")
    ax.scatter(X1[:,0], X1[:,1], marker="x", label="Class-1")
    
    #決定境界とマージンをプロット
    xx,yy = np.meshgrid(np.linspace(xmin,xmax,100), np.linspace(ymin,ymax,100))
    xy = np.vstack([xx.ravel(), yy.ravel()]).T
    
    p = svc.decision_function(xy).reshape((100, 100))
    ax.contour(xx, yy, p, colors="k", levels=[-1,0,1], alpha=0.5, linestyles=["--","-","--"])
    
    #サポートベクター
    ax.scatter(svc.support_vectors_[:,0], svc.support_vectors_[:,1], s=250, facecolors="none", edgecolors="black")
    
    
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.legend(loc="best")
    plt.show()

plot_boundary_margin(X0, y0, X1, y1, kernel="linear", C=1.0)
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337578/0183e588-a458-a6d8-42fe-c1322134ecbb.png)

# 決定境界が非線形となる場合
決定境界が非線形となる分布を分類します。高度な知性の本質とは、非線形的な「難しい」分類ができることです。**非線形な決定境界を決めるにはSVMやランダムフォレスト法・ニューラルネットワーク**が最も適しています。欠点はなぜその決定境界となったのか？という分類の基準が曖昧になりやすいことです。**(ブラックボックス化)**

```python
# y = 2(x-0.5)^2 + 0.5 が決定境界となる分布をSVMに分類させる
#乱数シード
rng = np.random.default_rng(123)

#一様乱数とそのラベルを生成 - ラベルが0か1かで分類する
#内包表記でラベルの値を多項目式で生成できる
X = rng.random(size=(100,2))
y = (X[:,1] > 2*(X[:,0] - 0.5)**2 + 0.5).astype(int)
#fig, ax = plt.subplots()

#散布図
#ax.scatter(X[y==0, 0], X[y==0, 1], marker="o", label="Class-0")
#ax.scatter(X[y==1, 0], X[y==1, 1], marker="x", label="Class-1")

#内容表記を使った代入 - Y==0等の条件に合う場合、その値のみをリストに代入する
X0, X1 = X[y==0,:],X[y==1,:]
y0, y1 = y[y==0],y[y==1]

plot_boundary_margin(X0, y0, X1, y1, kernel="rbf", C=1e3, xmin=0, ymin=0)

#ax.legend()
#plt.show()
```
![スクリーンショット 2023-05-20 172038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337578/8e32cb32-9264-f149-401d-6307cb517245.png)

ScikitLearnでデータの前処理、SVMといった比較的高度なアルゴリズムが扱えるようになりました。次回は決定木とランダムフォレスト法を学習していきます。