Udemy「Pythonで機械学習：scikit-learnで学ぶ識別入門」を受講後の感想・まとめ その1

機械学習を仕事で使うため、Udemyで色々と受講しています。
その中で、「Pythonで機械学習：scikit-learnで学ぶ識別入門」を受講したので、そのメモです。

# 感想
非常に充実しています。
オープニングで、扱っているのは予測ではなく識別のみと宣言しているため、内容が統一されていると感じます。
ボリュームはかなりあります。単に講座を聞くだけなら2~3日で終わりますが、講座を聞いて、手元のJupyter notebookで理解しながらコードを確認・修正して..とやっていると、社会人だと6~7日は必要かと思います。
データ分割～学習～認識の流れで、学習に進んだと思ったらデータ分割の別の方法の説明があるなどで、飛ぶことがある印象です。また、同じ識別器でもサンプルデータ・癌データでセクションが別なので、類似説明が続く印象もあります。その場合はスキップしてよいかと。最後にまとめがあると嬉しかったかもしれません。
なので、その要約です。トピックの紹介なので、詳細は他詳しいwiki/qiitaで調べたほうがよいです。

# テストのための大まかなフロー
#### 学習データ(含む訓練データ)とテストデータの分割
- Cross-Validationを実施する
- 分割方法は、` ShuffleSplit` 、` KFold` 、` StratifiedKFold` が有名。
- Leave one outやLeave one group outもある。Leave P outもあるが使われない。
- 学習サンプルに応じて何の手法を使うか決める必要があるが、ビッグデータの場合は複雑に分割することは難しい。KFoldが無難。
- 学習時、クラスにランダム性を持たせるため` Stratified(StratifiedKFold)` を利用することが重要。

#### 欠損値・特徴量の加工

- nanの値を` np.isnan` で見つけ、NaNを平均(` Imputer` から` transform` した値)で埋める / medianで埋める(` Imputer(strategy='median')` ) / 除外する (個々のケースによる)
- 全ての特徴が識別に有効ではない場合もある。その場合は特徴量を抽出するか、削減する。例えば以下。
- ` SelectKBest`: 影響する特徴を自動的に抽出する  
- ` PCA(主成分)` : 相関が高い2つの特徴をマージして特徴量を減らす
- ` PolynomialFeatures` : 非線形変換をおこなうことで、別の特徴量を算出する。例えば、$x1$,$x2$,$x3$ があった場合に、別の特徴量として、$x1\times x2$ をつくる。ただ、増やすと特徴量が大幅に増えるので、増やしたらPCAで削減することも検討すること。最後の手段であり、あまり使わない。

#### 標準化・スケーリング・正規化（重要）
- ` StandardScaler` (標準化): fit~transformすることで、特徴量を平均0, 標準偏差1にする。
- ` MinMaxScaler` (スケーリング):最大最小をある範囲に抑える。
`scaler=MinMaxScaler([-1, 1])`のように設定する。
- ` Normalizer` (正規化): サンプル毎に正規化する。そのため、fitは不要。transformだけ実行すればよい。
- ` PCA Whitening` : PCAだけだと次元を減らす。しかし分散が大きいので、whiten=Trueにすることで、平均0, 分散1にすることができる。
- ` ZCA Whitening` : PCAと類似しているが、ディープラーニングの画像認識で使われている。PCAより性能がでるとのこと。ここはもう少し理論を勉強する必要がありそう。。

#### 学習データの評価
- ` Confusion Matrix` : trainから算出した結果の正誤表を以下のようなmatrixで表示する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/292158/b86caf24-fbb1-bb6f-8a36-ec5edc7b9cd7.png)

TP: True Positive
TN: True Negative
FP: False Positive
FN: False Negative
ちょっと覚えにくい。。
TP/TNは**OK**, FP/FNは予測と異なるので**NG**ということ。

- ` classification_report` : precision/recall/f1-score/supportを算出する。
- recallは**再現率**(True Positive Rate)。$TP / (TP + FN)$ の計算式。実際に正で、予測が正だった割合。
- precisionは**適合度**。$TP / (TP + FP)$。予測が正で、実際に正だった割合。
- Fはprecisionとrecallの調和平均で、予測精度の指標を表す。
- これらは、**ROC曲線/AUC**で利用する。ROC曲線は、縦軸にTP、横軸にFPの割合を2次元プロットして点を線で連結した曲線。AUCはArea Under the Curveのこと。ROC曲線の曲線よりしたの面積。分類器の精度評価に使う。

#### 色々な識別器
ロジスティック回帰・SVM(線形非線形)、ランダムフォレスト、(多層)パーセプトロン、k近傍識別器あたりが説明の中心です。最初はOneVsRestの説明、その後は各識別器の説明でした。かいつまんで記載します。


- **ロジスティック回帰のOne-vs-Rest:One-vs-Rest:** 例えば3クラス分類の際、あるクラスAと、それ以外のB/Cの識別器を作る。ロジスティック回帰でもone-vs-oneはあるにはあるが、sklearnでは実装されていない。

- **SVMのOne-vs-Rest:** ovr指定にて、one-vs-restになる。

``` python
 svm.SVC(kernel='linear', decision_function_shape='ovr')
``` 

- **SVMのOne-vs-One:** ` ovo`を設定する。ovrと異なり、1:1で識別器を作成する。 ovoはコンビネーションで増えてしまうので、**クラス数が多いと使われない**。

- **k近傍識別器(kNN: neighbors):** 以下のようにして使う。

```python
sklearn import neighbors
clf = neighbors.KNeighborsClassifier(n_neighors=N) 
# Nは自身からの距離を示す。N=3なら、3番目に近いところまでを含める。
clf.fit(X, y)
```
- **radius NN(RadiusNeighborsClassifier):** ある半径の中に入っているもののサンプルで多数決を取る。 

```python
clf = neighbors.RadiusNeighborsClassifier()
clf.fit(X_train, y_train)
```

- **パーセプトロン:**
講座ではパーセプトロンの動き・損失関数を可視化して説明。損失関数は、正の値の際には変更しない。
基本は単独では使わない。線形識別の場合、ロジスティック回帰が基本使われる。

``` python
from sklearn.linear_model import Perceptoron
clf = Perceptron() # default n_iter=5(epoch)
# 既に学習済の場合、clf.warm_start = Trueで、前回の結果を流用する
clf.fit(X, y)
``` 
- **ロジスティック回帰:** 損失関数がパーセプトロンとは異なり、正しい値でも一部を損失に含める。講座では頻繁に登場。

``` python
clf = LogisticRegression()
clf.fit(X_Train, y_train)
clf.predict(X_test)
```
- **SVM:**
損失関数はパーセプトロンに類似しているが、正の値でも少しだけ損失を加える。識別境界に近いものは損失を加える。線形非線形があり、非線形がよく使われる。線形の場合、LinearSVCを使うべき。

``` python
from sklearn.svm import SVC
clf = SVC(kernel='linear')
# 確率を算出するには、fit前にprobability = Trueを設定しておく必要がある。
# 非線形カーネル(rbf/poly)もある。
# デフォルトはrbf。linearの場合、LinearSVCが用意されているので、そちらを使うほうが高速。
# polyは、スケーリングしておかないと計算が終わらない可能性あり。

```
 
- **MLP(多層パーセプトロン):**
パーセプトロンにおいて、中間層を複数にしたモデル。

``` python
from sklearn.neural_network import MLPClassifier
clf = MLPClassifier(random_state=8)
clf.alpha = 1 # 仮
clf.max_iter = 2000 # 仮
hidden_layer_sizes = (10,10,5) # 中間層3つ(計5層)の例
```

- **ランダムフォレスト:** 
決定木の応用で、決定木(sklearnの`DecisionTreeClassifier()`)が多数集まった構成。使うツリーの数を`n_estimators`で変更する。`n_estimators=1`なら決定木と同じ。本講座では分類だけだが、ランダムフォレストは回帰でも利用可能(`RandomForestRegressor`)。
また、ランダムフォレストではスケーリングの効果はない。縦軸横軸をスケーリングしても変わらないため。

``` python
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=8)
clf.max_depth = N 
# 決定木の深さの最大値。過学習をおこさないため、調整が必要。
clf.n_estimators = M 
# 決定木の個数。こちらも調整が必要。
clf.fit(X, y)
```
#### そのほか(tips)
- **グリッドサーチ(パラメータ調整):**　ロジスティック回帰を例にすると、Cの値がパラメータで設定できる。これをfor文で順番に学習させると手間がかかるので、グリッドサーチを用いる。また、パラメータ変更は大胆に変える。1,2,3..ではなく、$10^0$, $10^1$, $10^2$,..のようにする。

``` python
from sklearn.model_selection import GridSearchCV
C_range = {1e-5, 1e-3, 1e-2, 1, 1e2, 1e5, 1e10}
# ケースによっては複数設定するので、その場合は以下のようにリストにする
# param = [ {'C:' C_range}, {'kernel': ['linear']}, ..]
param = {'C': C_range} # clf.Cのこと
gs = GridSearchCV(clf, param) # clfは識別器
gs.fit(X_train, y_train)
# この中で3-fold-cross-validationをおこなっている

#gs.best_params_, gs.best_score_, gs.best_estimator_で結果を参照する
gs.score(X_test, y_test)
#ベストの識別パラメータがgsに設定されている
```

- **ランダムサーチ(パラメータ調整):** MLPになると、パラメータが多数存在する。`hidden_layer_sizes`, `activation`, `beta_1`, `beta_2`, `alpha`など..。これらをグリッドサーチしていると、計算量が膨大になるため、探索パラメータの中から抜粋して計算する。注意点としては、ランダムなので、もう一度実行すると認識率は変わる。

```python
from sklearn.model_selection import RandomizedSearchCV
param = 
gs = RandomizedSearchCV(clf, param, n_iter=20, n_jobs=1, verbose=2)
# n_jobsはコア数、n_iterは組み合わせの試行回数
# verboseは公式ドキュメント見てもわからず、講座でも触れておらず..。あまり性能には関係なさそう。
gs.fit(X_train, y_train)
..
gs.best_params_, gs.best_score_, gs.best_estimator_
```

- **パイプライン:** 一連の処理を一括りにしてくれる。グリッドサーチ・正規化と組み合わせると便利。

```python
# 識別器のパイプライン例
from sklearn.pipeline import Pipeline
estimators = [('pca', PCA(whiten=True)),
('clf', SVC())]
pipe = Pipeline(estimators)
pipe.fit(X_train, y_train)
pipe.score(X_test, y_test)
```

```python
# pipeline + param + clf
from sklearn.model_selection import RandomizedSearchCV
estimators = [('pca', PCA()),
('clf', SVC())]

param = {'clf__C': [1e-5, 1e-3],
'clf__kernel': ['linear', 'rbf'],
'pca__whiten': [True, False]}
# clf__Cの__は必須。
pipe = Pipeline(estimators)
gs.RandomizedSearchCV(pipe, param, n_jobs=1, verbose=2)
gs.fit(X_train, y_train)
```

- 正**則化パラメータC:** ロジスティック回帰・SVMで$C$が重要。その変更の影響の確認。SVMのlinearで試したところ、$C$が正の値で、認識率が高い結果となっている。パイプライン・グリッドサーチと組み合わせて検証してみるのがよいかと。

- **データの作成:** sklearnでは癌のデータがすでに存在する。それ以外でもサンプル用に即座に作成することができる。

```python
# 癌データの学習・テストデータ分離の例
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = data.data
y = data.target
from sklearn.model_selection import ShuffleSplit
ss = ShuffleSplit(n_splits=1,
             tran_size=0.8,
             test_size=0.2,
             random_state=0)

train_index, test_index=next(ss.split(X, y))

X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
```

```python
# 意図的にサンプル作成
X, y = make_blobs(n_samples=20, # 20個生成
           n_features=2, # 2次元
           centers=2, # クラスタ中心2個
           cluster_std=1, # 標準偏差
           random_state=8 # seed(固定)
           )
```


# 最後に
この講座で利用していたnumpy, matplotlibのコマンドについては別途記載予定。