scikit-learnを使った機械学習


## はじめに
scikit-learnのバージョンについて、教科書に記載の環境(0.19.1)と私の環境(1.1.1)が結構違っていたので調べながら勉強しました。
内容が多くて少し心が折れましたが、機械学習の基本的なところなのでかなり参考になりました。

https://www.shoeisha.co.jp/book/detail/9784798158341

## データの前処理
### 欠損値の補完
データフレームの欠損値を補完するにはpandasのfilnaメソッドを使う方法がありますが、
scikit-learnでも同様のことができます。
以下は欠損値のあるデータフレームを作成して、SimpleImputerを使って補完しています。

``` python:in
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer 

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)

imp = SimpleImputer(strategy='mean')
imp.fit(df)
df2 = imp.transform(df)

print(df2)
```

``` :out
     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

[[ 1.    6.   11.  ]
 [ 3.25  7.   12.  ]
 [ 3.    8.   13.  ]
 [ 4.    7.75 14.  ]
 [ 5.   10.   15.  ]]
```

### カテゴリ変数(ラベル)エンコーディング
「a→1、b→2、c→3」のようにデータの要素を値に置き換える方法です。
classes_属性を表示させることでどのような置き換えが行われたのか確認しています。

``` python:in
from sklearn.preprocessing import LabelEncoder

df = pd.DataFrame(
    {
        'A':['c','x','b','c','a']
    }
)

le = LabelEncoder()
le.fit(df['A'])
df2 = le.transform(df['A'])

print(df2)
print(le.classes_)
```
列Aの値は小文字の英字でしたが、数値に置き換わっています。
「a,b,c,x」が「0,1,2,3」に置き換えられていることが確認できます。

``` :out
[2 3 1 2 0]
['a' 'b' 'c' 'x']
```

### One-hotエンコーディング
データの種類の数だけ列を作成し、データが該当する場合は1、該当しない場合は0にするような置き換えを行う場合はOne-Hotエンコーディングを行います。

``` python:in
from sklearn.preprocessing import OneHotEncoder

df = pd.DataFrame(
    {
        'A':['c','x','b','c','a']
    }
)

oe = OneHotEncoder(sparse = False)
df2 =oe.fit_transform(df)

print(df2)
```

左からaの列、bの列、cの列、xの列になっています。

``` :out
[[0. 0. 1. 0.]
 [0. 0. 0. 1.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [1. 0. 0. 0.]]
```

## 特徴量の分散正規化
桁の全く違うデータに対して特徴量の揺らぎを一定にしたい場合に正規化を行います。
分散正規化では平均が0、最大値が1となるように値を変換することができます。

``` python:in
import pandas as pd
from sklearn.preprocessing import StandardScaler

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

stdsc = StandardScaler()
stdsc.fit(df)

print(stdsc.transform(df))
```

``` :out
[[-1.41421356 -1.22474487]
 [-0.70710678 -0.81649658]
 [ 0.          0.        ]
 [ 0.70710678  0.40824829]
 [ 1.41421356  1.63299316]]
```

## 特徴量の最小最大正規化
もう一つ、桁の全く違うデータに対して最小値と最大値に対する重みで正規化する方法もあります。
こっちの方がシンプルな気もしますが、分散正規化の方が実態に即した正規化であるように感じます。


``` python:in
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

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

stdsc = MinMaxScaler()
stdsc.fit(df)
print(stdsc.transform(df))
```

``` :out
[[0.         0.        ]
 [0.25       0.14285714]
 [0.5        0.42857143]
 [0.75       0.57142857]
 [1.         1.        ]]
```

## 学習データとテストデータの分類
たくさんのデータを学習データとテストデータに分割するには、train_test_split()を使います。
以下はアヤメの花の種類を分類することができるデータセットで、データの数は150個あります。
このデータのうち、30%のデータをテストデータにして、70%を学習データとして分割するには以下ようにします。

``` python:in
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
x, y = iris.data, iris.target
print("data: " + str(x.shape))

x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.3, random_state=123)
print("train_data: " + str(x_train.shape))
print("test_data : " + str(x_test.shape))
print("train_ans : " + str(y_train.shape))
print("test_ans  : " + str(y_test.shape))
```

もともとのデータが150個で、学習データが105個、テストデータが45個という風に分割されています。

``` :out
data: (150, 4)
train_data: (105, 4)
test_data : (45, 4)
train_ans : (105,)
test_ans  : (45,)
```

## 分類モデルの構築
分類は、既知のデータをクラス分けするタスクであり、教師ありモデルを作成するために必要です。
最初は人間が手作業でクラス分けする必要がありますが、実装する際の分類モデルのアルゴリズムとしては以下のような種類があります。

### サポートベクタマシン
線形分類できないデータに対して高次元の空間に移して分離するアルゴリズム

### 決定木
データを分類するルールを作成していくことでデータを分離するアルゴリズム

### ランダムフォレスト
サンプリングしたデータから決定木を複数作成して、その結果の多数決でデータを分離するアンサンブル学習という方式を採用したアルゴリズム


## モデルの評価
データに対するモデルの手制度を定量化する指標として、適合率、再現率、F値、正解率の指標があります。
モデルファイルとテストデータと正解ラベルの情報があれば、
scikit-learnのmetricsモジュールのclassification_report関数を使うことでこれらの指標値を抽出することができます。

``` python:in
from sklearn.datasets import load_iris
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

#Irisデータセットのロード
iris = load_iris()
data, target = iris.data[:100, :], iris.target[:100]
#テストデータ比率30%でデータを分割
train_data, test_data, train_target, test_target = train_test_split(data,target,test_size=0.3, random_state=123)
#サポートベクタマシンのインスタンス化
svc = SVC()
#学習データを使ったトレーニング
svc.fit(train_data, train_target)
#テストデータを使った検証
test_pred = svc.predict(test_data)
#検証結果の表示
print(classification_report(test_target,test_pred))
```

``` :out
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        15
           1       1.00      1.00      1.00        15

    accuracy                           1.00        30
   macro avg       1.00      1.00      1.00        30
weighted avg       1.00      1.00      1.00        30
```

出力結果の「0」,「1」はラベルの種類を表していて、
横軸の「precision」は適合率、「recall」は再現率、「f1-score」はF値、「support」はデータの件数を表しています。

## 次元削減
次元削減は、データの持っている傾向をなるべく損ねることなく、次元を削減することでモデル作成に必要なデータの総量を減らすタスクです。

### 主成分分析
次元削減する際に、どの要素が重要であるかを調査する方法が主成分分析です。
scikitlernで主成分分析を行う場合には、decompositionモジュールのPCAクラスを使って行うことができます。

## ハイパーパラメータの最適化
機械学習のアルゴリズムには、決定木の深さやランダムフォレストの木の数など、ユーザが値を設定してチューニングするパラメータがあり、それをハイパーパラメータと呼びます。(実際に仕事で機械学習を使う際にはこの辺のチューニングができる人材が重宝されるような気がします)

これらのパラメータを直感で決めるのではなく、候補を何個か上げたうえで、実際に動かしながら最適値を決定する方法として、グリッドサーチやランダムサーチと呼ばれる方法があります。

以下はグリッドサーチを使って決定木の深さの最適値を表示しているコードです。
cv=10として、10分割の交差検証で値を週出していますが、結果は毎回違う結果になるように見えました。

``` python:in
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

#データセットをロードして学習データとテストデータに分割
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=123)

#決定木のインスタンスを作成
clf = DecisionTreeClassifier()

#グリッドサーチで最適化
param_grid = {"max_depth": [3, 8, 12]}
cv = GridSearchCV(clf, param_grid=param_grid, cv=10)

#学習とテストを実施
cv.fit(X_train, y_train)
y_pred = cv.predict(X_test)

#分類結果と最適だったパラメータを表示
print(y_test) #正解ラベル
print(y_pred) #回答ラベル
print(cv.best_params_)
```

``` :out
[1 2 2 1 0 2 1 0 0 1 2 0 1 2 2 2 0 0 1 0 0 2 0 2 0 0 0 2 2 0 2 2 0 0 1 1 2 0 0 1 1 0 2 2 2]
[1 2 2 1 0 2 1 0 0 1 2 0 1 2 2 2 0 0 1 0 0 1 0 2 0 0 0 2 2 0 2 1 0 0 1 1 2 0 0 1 1 0 2 2 2]
{'max_depth': 8}
```

## クラスタリング
クラスタリングは、データの類似性から、データをグループにまとめるタスクです。
クラスタリングのアルゴリズムとして、k-meansと階層的クラスタリングがあります。

### k-means
各データに対して、クラスタの数だけランダムな位置にラベルを配置して、ラベルに対する近さでクラスタを作成し、作成したクラスタの中心にラベルを移動させることを何度か繰り返すことでクラスタリングを行う。

### 階層的クラスタリング
階層的クラスタリングには凝縮型と分散型があり、それぞれ以下のような処理を行う。

凝縮型：似ているデータをまとめて小さなクラスタを作り、次にそのクラスタと似ているデータをまとめ、最終的にデータが1つのクラスタにまとめられるまで処理を繰り返すクラスタリング手法。

分散型：最初にすべてのデータをまとめて一つのクラスタとみなして、それを順次分割していくクラスタリング手法。計算量が多いため、凝集型より利用頻度は少ない。