【機械学習図鑑】巻末のPython演習をデータを確認しながら実施したときのメモ

## 概要
先日DataCampのデータサイエンティストコースを修了しました。
英語での学習だったこともあり、日本語でざっとおさらいしたいと考えていたところ、機械学習図鑑という本がわかりやすくてよかったので読んだのですが、ありがたいことに「第4章 評価方法及び各種データの扱い」でPythonコードで実際に演習をする写経コンテンツがあったので復習しました。

途中少しコードを加筆修正している箇所もありますが、基本的にはほぼそのままです。
各コマンドで変わっていくデータ状態を確認するために、適宜コメントでデータのshape等を記載しています。

オリジナルのサンプルプログラムは下記URLで公開されていますので、オリジナルはそちらを参照ください。
ダウンロードの際には是非翔泳社さんのサイトへのサインアップをお願いします。

* [機械学習図鑑 - サンプルプログラム](https://www.shoeisha.co.jp/book/download/9784798155654)

コードの区切りは、実際に演習をした際にコードレベルで切ることができるところで分割しており、複数のトピックが混ざっています。

### その1
* 教師あり学習の評価
* 分類問題における評価方法

~~~python
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()    # ウィスコンシンの乳がんのデータ
Xo = data.data                 # Xo.shape = (569, 30)

y = 1 - data.target   # reverse target paramater
X = Xo[:, :10]        # mean_* のデータが先頭10カラムにある

# LogisticRegression
from sklearn.linear_model import LogisticRegression
# model_lor = LogisticRegression()                   # FutureWarning: Default solver will be changed to 'lbfgs' in 0.22　が出ます
model_lor = LogisticRegression(solver='liblinear')   # solverを指定して上げるとWarningは出ない。
model_lor.fit(X, y)
y_pred = model_lor.predict(X)

# ConfusionMatrix
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y, y_pred)
print("============= CONFUSION MATRIX ============= ")
print(cm)
'''
予測と正解のケース数のマトリクスで出てくる
          Predict
            0   1
Actual 0 [341  16]
       1 [ 36 176]
'''

# Accuracy Score
from sklearn.metrics import accuracy_score
accs = accuracy_score(y, y_pred)
print("============= ACCURACY SCORE ============= ")
print(accs)              # 正解率。予測結果全体に対する正解率。  (341+176) / 569

# Precision Score
from sklearn.metrics import precision_score
pres = precision_score(y, y_pred)
print("============= PRECISION SCORE ============= ")
print(pres)              # 適合率。ポジティブ(1)と予測したものに対すして正しくポジティブと予測できたものの割合。 176 / (176+16)

# Recall Score
from sklearn.metrics import recall_score
recs = recall_score(y, y_pred)
print("============= RECALL SCORE ============= ")
print(recs)              # 再現率。実際にポジティブ(1)なものに対して正しく予測できた割合。 176 / (36+176)
# 再現率が低い場合、陽性を見逃していることになるので危険。

# F Score
from sklearn.metrics import f1_score
f1s = f1_score(y, y_pred)
print("============= F SCORE ============= ")
print(f1s)               # 適合率と再現率の両方の傾向を反映させた指標。

# 予測確率
y_pred_proba = model_lor.predict_proba(X)
print(y_pred_proba)
'''
       Probability
                   0              1
Case [4.41813067e-03 9.95581869e-01]
     [4.87318129e-04 9.99512682e-01]
     [3.31064287e-04 9.99668936e-01]
     ...
'''
# 標準の .predict() を使うと prob > 0.5 (50%) で判断されるため、誤判定を上げて見逃しを減らしたいケースではパラメータを調整する

# 10%以上の可能性があるものを抽出
import numpy as np
y_pred2 = (model_lor.predict_proba(X)[:, 1] > 0.1).astype(np.int)
print("============= CONFUSION MATRIX ( 1 prob > 10% ) ============= ")
print(confusion_matrix(y, y_pred2))
'''
          Predict
            0   1
Actual 0 [259  98]
       1 [  2 210]
'''

print("============= ACCURACY SCORE ( 1 prob > 10% ) ============= ")
print(accuracy_score(y, y_pred2))  # 0.8242...
print("============= RECALL SCORE ( 1 prob > 10% ) ============= ")
print(recall_score(y, y_pred2))    # 0.9905...

# ROC Curve, AUC
# ROC : Receiver Operation Characteristic
# AUC : Area Under the Curve
from sklearn.metrics import roc_curve
y_pred_proba = model_lor.predict_proba(X)
fpr, tpr, thresholds = roc_curve(y, y_pred_proba[:, 1])

import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
fig, ax = plt.subplots()             # 一度にfigure objとaxes objを返す
fig.set_size_inches(4.8, 5)
ax.step(fpr, tpr, 'gray')
ax.fill_between(fpr, tpr, 0, color='skyblue', alpha=0.8)
ax.set_xlabel('False Positive Rate')
ax.set_ylabel('True Positive Rate')
ax.set_facecolor('xkcd:white')
plt.show()                        # AUC(面積)が1に近づくほど精度が高いことを示す

# AUCを求める
from sklearn.metrics import roc_auc_score
print("============= AUC(Area Under the Curve) SCORE ( 1 prob > 10% ) ============= ")
print(roc_auc_score(y, y_pred_proba[:, 1]))   # 0.9767...
# 不均衡データ(Posi, Negaの数に偏りが大きい場合)を扱うときは指標にAUCを使ったほうが良い
~~~


### その2
* 回帰問題における評価方法
* 平均二乗誤差と決定係数の指標の違い
* 異なるアルゴリズムを利用した場合との評価
* ハイパーパラメータの設定
* モデルの過学習
* 過学習を防ぐ方法
* 学習データと検証データに分割
* 交差検証（クロスバリデーション）

~~~python
from sklearn.datasets import load_boston
data = load_boston()      # ボストンの住宅価格のデータ
Xo = data.data            # Xo.shape = (506, 13)
X = Xo[:, [5,]]           # X.shape = (506, 1)   Xo[:, 5][0] => numpy.float64   Xo[:, [5,]][0] => numpy.ndarray
y = data.target

from sklearn.linear_model import LinearRegression
model_lir = LinearRegression()
model_lir.fit(X, y)
y_pred = model_lir.predict(X)
print("============= LINEAR REGRESSION ============= ")
print("model_lir.coef_      : {}".format(model_lir.coef_))        # 傾き  y = ax + b の a
print("model_lir.intercept_ : {}".format(model_lir.intercept_))   # 切片  y = ax + b の b

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(X, y, color='pink', marker='s', label='data set')
ax.plot(X, y_pred, color='blue', label='regression curve')
ax.legend()
plt.show()

# 平均二乗誤差
# 実際の値と予測値の差を示す数値。y軸誤差を二乗して平均をとったもの。
from sklearn.metrics import mean_squared_error
print("============= MEAN SQUARED ERROR ============= ")
print(mean_squared_error(y, y_pred))   # 43.600...

# 決定係数(R**2)
# 平均二乗誤差を利用した、学習済みモデルの予測の当てはまり度を示す数値（0->1）。1が誤差なし。ひどいとマイナスになることも。
from sklearn.metrics import r2_score

print("============= R2 SCORE ============= ")
print(r2_score(y, y_pred))

# SVR (Linear Regression)
# Support Vector Machine (Kernel method)
from sklearn.svm import SVR
model_svr_linear = SVR(C=0.01, kernel='linear')
model_svr_linear.fit(X, y)
y_svr_pred = model_svr_linear.predict(X)

fig, ax = plt.subplots()
ax.scatter(X, y, color='pink', marker='s', label='data set')
ax.plot(X, y_pred, color='blue', label='regression curve')
ax.plot(X, y_svr_pred, color='red', label='SVR')
ax.legend()
plt.show()

# SVR(Linear Regression)の検証
print("============= SVR SCORE (LINEAR REGRESSION) ============= ")
print("mean_squared_error : {}".format(mean_squared_error(y, y_svr_pred)))
print("r2_score           : {}".format(r2_score(y, y_svr_pred)))
print("model_lir.coef_    : {}".format(model_svr_linear.coef_))
print("model_lir.coef_    : {}".format(model_svr_linear.intercept_))

# SVR(rbf)
model_svr_rbf = SVR(C=1.0, kernel='rbf')
model_svr_rbf.fit(X, y)
y_svr_pred = model_svr_rbf.predict(X)
print("============= SVR SCORE (RBF) ============= ")
print("mean_squared_error : {}".format(mean_squared_error(y, y_svr_pred)))
print("r2_score           : {}".format(r2_score(y, y_svr_pred)))

# Over Fitting
X_train, X_test = X[:400], X[400:]
y_train, y_test = y[:400], y[400:]
model_svr_rbf_1 = SVR(C=1.0, kernel='rbf')
model_svr_rbf_1.fit(X_train, y_train)
y_train_pred = model_svr_rbf_1.predict(X_test)
print("============= SVR SCORE (RBF) - OverFitting ============= ")
print("mean_squared_error : {}".format(mean_squared_error(y_test, y_train_pred)))
print("r2_score           : {}".format(r2_score(y_test, y_train_pred)))

# Prevent Over Fitting
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()    # 先述のがんのデータ
X = data.data
y = data.target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)   # testデータの抽出を同じにしたい場合は random_stateを指定

from sklearn.svm import SVC
model_svc = SVC()
model_svc.fit(X_train, y_train)
y_train_pred = model_svc.predict(X_train)
y_test_pred = model_svc.predict(X_test)
from sklearn.metrics import accuracy_score

print("============= ACCURACY SCORE (SVC) ============= ")
print("Train_Accuracy_score  : {}".format(accuracy_score(y_train, y_train_pred)))  # 1.0
print("Test_Accuracy_score   : {}".format(accuracy_score(y_test, y_test_pred)))    # 0.60
# 学習データの正答率に対して予測データでのスコアが低いので過学習。

from sklearn.ensemble import RandomForestClassifier
model_rfc = RandomForestClassifier()
model_rfc.fit(X_train, y_train)
y_train_pred = model_rfc.predict(X_train)
y_test_pred = model_rfc.predict(X_test)
print("============= ACCURACY SCORE (RFC) ============= ")
print("Train_Accuracy_score  : {}".format(accuracy_score(y_train, y_train_pred)))  # 0.9974...
print("Test_Accuracy_score   : {}".format(accuracy_score(y_test, y_test_pred)))    # 0.9590...
# RandomForestにしたことで予測データでのスコアが上昇。

# Cross Validation
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
cv = KFold(5, shuffle=True)
model_rfc_1 = RandomForestClassifier()
print("============= CROSS VALIDATION SCORE ============= ")
print("Cross_Valication_Score x5 Scoring=Accuracy   : {}".format(cross_val_score(model_rfc_1, X, y, cv=cv, scoring='accuracy')))
print("Cross_Valication_Score x5 Scoring=F1         : {}".format(cross_val_score(model_rfc_1, X, y, cv=cv, scoring='f1')))
~~~

### その3
* ハイパーパラメータの探索

~~~python
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
Xo = data.data
y = 1 - data.target     # reverse label
X= Xo[:, :10]

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold
cv = KFold(5, shuffle=True)
param_grid = {'max_depth': [5, 10, 15], 'n_estimators': [10, 20, 30]}
model_rfc_2 = RandomForestClassifier()
grid_search = GridSearchCV(model_rfc_2, param_grid, cv=cv, scoring='accuracy')
# grid_search = GridSearchCV(model_rfc_2, param_grid, cv=cv, scoring='f1')
grid_search.fit(X, y)
print("============= GRID SEARCH RESULTS ============= ")
print("GridSearch BEST SCORE   : {}".format(grid_search.best_score_))
print("GridSearch BEST PARAMS  : {}".format(grid_search.best_params_))
~~~

### その4
* 文書データの変換処理
* 単語カウントによる変換
* tf-idfによる変換
* 機械学習モデルへの適用

~~~python
# tf-idf
# tf  : Term Frequency
# idf : Inverse Document Frequency
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.svm import LinearSVC
from sklearn.datasets import fetch_20newsgroups

categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med']
remove = ('headers', 'footers', 'quotes')
twenty_train = fetch_20newsgroups(subset='train', remove=remove, categories=categories)
twenty_test = fetch_20newsgroups(subset='test', remove=remove, categories=categories)

count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(twenty_train.data)
X_test_count = count_vect.transform(twenty_test.data)

model = LinearSVC()
model.fit(X_train_counts, twenty_train.target)
predicted = model.predict(X_test_count)
print("============= CountVectorizer ============= ")
print("predicted == twenty_test.target  : {}".format(np.mean(predicted == twenty_test.target)))   # 0.7423...

tf_vec = TfidfVectorizer()
X_train_tfidf = tf_vec.fit_transform(twenty_train.data)
X_test_tfidf = tf_vec.transform(twenty_test.data)

model = LinearSVC()
model.fit(X_train_tfidf, twenty_train.target)
predicted = model.predict(X_test_tfidf)
print("============= TfidfVectorizer ============= ")
print("predicted == twenty_test.target  : {}".format(np.mean(predicted == twenty_test.target)))   # 0.8149...
~~~

### その5
* 画像データの変換処理
* ピクセルの情報をそのまま数値として利用する
* 変換後のベクトルデータを入力として機械学習モデルを適用する

~~~python
from PIL import Image
import numpy as np

# Transform image data to vector data
img = Image.open('/Users/***/****.jpg').convert('L')    # 適当にローカルの画像のフルパスを指定
width, height = img.size
img_pixels = []
for y in range(height):
    for x in range(width):
        img_pixels.append(img.getpixel((x,y)))          # getpixelで指定した位置のピクセル値を取得

print("============= Print Pixel ============= ")
print("len(img_pixels)  : {}".format(len(img_pixels)))
print("img_pixels  : {}".format(img_pixels))            # [70, 183, 191, 194, 191, 187, 180, 171, 157, 143, ....]

# Predict number images
from sklearn import datasets
from sklearn import metrics
from sklearn.ensemble import RandomForestClassifier

digits = datasets.load_digits()                 # type(digits) => sklearn.utils.Bunch    type(digits.images) => numpy.ndarray

n_samples = len(digits.images)                  # 1797    digits.images.shape => (1797, 8, 8)
data = digits.images.reshape((n_samples, -1))   # digits.images.reshape((n_samples, -1)).shape => (1797, 64)

model = RandomForestClassifier()
model.fit(data[:n_samples // 2], digits.target[:n_samples // 2])
# data[:n_samples // 2].shape => (898, 64)
# digits.target[:n_samples // 2].shape => (898,)

expected = digits.target[n_samples // 2:]
predicted = model.predict(data[n_samples // 2:])

print(metrics.classification_report(expected, predicted))
~~~

翔泳社さんの機械学習図鑑、とてもわかりやすいよい本なのでおすすめです。（DataCampの機械学習セクションの前に読んでおきたかった・・・）

[翔泳社 機械学習図鑑](https://www.amazon.co.jp/gp/product/4798155659/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4798155659&linkCode=as2&tag=takujiabe-22&linkId=1f8caa5af2f8e99e82bb2a2d000c608f)