【機械学習】SVMで見る学習から予測の基本的な流れ
機械学習はscikit-learnなどの便利なライブラリのおかげで色々な処理を簡単に扱えるようになってきているが、やることが多い。
前処理でもやることはたくさんあるし、モデル構築の段階でもやること(考慮すること)が色々あり、ややこしい。
本記事では、モデル構築時に最低限のやるべきことについて説明する。
データについて、特徴量作成、欠損値補完、エンコーディングなどの前処理は終わっている前提とする。
データ作成
make_regressionを使って、回帰用のデータを作成する。
1import numpy as np
2from sklearn.datasets import make_regression
3
4# 回帰分析用のデータ作成
5X, y = make_regression(n_samples=3000,
6 n_features=50,
7 n_informative=10,
8 n_targets=1,
9 noise=50,
10 random_state=0)
11print(X.shape)
12print(y.shape)
13# (3000, 50)
14# (3000,)
3000行、50列で有用な特徴量は10列、分散は50になるようなデータを作成。
RMSEが50に近いほどいいモデルができていることになる。
データ分割(学習データとホールドアウト検証データ)
データを学習用とホールドアウト検証用に分割する。
1from sklearn.model_selection import train_test_split
2
3# 25%をホールドアウト検証データとして分割
4X_train, X_test, y_train, y_test = train_test_split(X, y,
5 test_size=0.25,
6 random_state=0)
7
8print(f"{X_train.shape=}")
9print(f"{X_test.shape=}")
10print(f"{y_train.shape=}")
11print(f"{y_test.shape=}")
12# X_train.shape=(2250, 50)
13# X_test.shape=(750, 50)
14# y_train.shape=(2250,)
15# y_test.shape=(750,)
上記の例では全体の25%をホールドアウト検証として用いる。
ホールドアウト検証用データは今後、モデル構築には一切使用せず、精度の確認のみに使用する。
ホールドアウト検証データは未知のデータとして扱うことがポイント。
標準化
学習データのスケールを合わせる。
今回の学習データは自動で生成しているため、各列によって絶対値が大きく異なることはないが、一般的なデータの場合、例えば列Aで値が1~10ぐらいを取り、列Bは1,000~10,000ぐらいの値を取るようなデータも珍しくはない。
上記のような場合、列Aで1と10は10倍の差があるが、列Bから見ると0.01~0.1%の誤差でしかない。
決定木系のアルゴリズムでは別に問題ないが、線形回帰系のアルゴリズムでは正則化が効きにくくなる。
そのため、列間での値の大きさの差をなくすために標準化を行う。
1from sklearn.preprocessing import StandardScaler
2
3sc = StandardScaler()
4sc.fit(X_train)
5X_train_sc = sc.transform(X_train)
6X_test_sc = sc.transform(X_test)
StandardScalerを使い、平均0、標準偏差が1になるように尺度を変える。
fitはtrain(学習データ)に対して行う。
test(検証データ)は未知のデータなので、fitできない。
以下の通り、元のデータの尺度が元々0付近なので、分かりにくいが、標準化の前後で少し値が変わっているのを確認できる。
1print(X_train[0])
2# [ 0.28325033 1.07074588 -1.12259194 0.12482685 0.32978808 -0.61511529
3# 0.36759317 1.13131087 -2.16367798 -0.6328112 1.60237631 -0.89142781
4# 0.39116843 0.74040278 -0.11932513 0.79855081 -1.38552558 0.08396813
5# 0.10252703 -1.74865144 1.17845942 0.20911389 0.72615549 0.69911599
6# 0.54129892 0.07650403 -2.7456654 -1.56637034 0.03329115 0.20994779
7# 0.72608238 -0.30277616 -1.49908916 -1.17107782 -0.1334914 1.21872967
8# -0.08809313 -1.27772591 -0.9317254 -0.21782625 -1.43633312 -0.83144669
9# 0.09842575 1.26867915 0.65585556 -1.56802865 0.62350746 -0.49052053
10# 1.57293572 1.57683721]
1print(X_train_sc[0])
2# [ 0.28949814 1.0163383 -1.10981007 0.136464 0.32988671 -0.64672602
3# 0.33668315 1.1223578 -2.15983765 -0.61716011 1.59771926 -0.90762235
4# 0.39416221 0.70325049 -0.13167213 0.77410763 -1.39349832 0.08963495
5# 0.10761379 -1.69613182 1.15822067 0.21895201 0.74125273 0.65640894
6# 0.52099756 0.09800042 -2.79115686 -1.56136502 0.03502904 0.21296581
7# 0.6788819 -0.30161501 -1.4774251 -1.18765259 -0.08458699 1.24861091
8# -0.14034651 -1.28168847 -0.96111811 -0.20280315 -1.40474221 -0.84041044
9# 0.07912572 1.28980165 0.6504885 -1.61867175 0.62185165 -0.48127675
10# 1.57766503 1.53041753]
ここまでで、学習するための準備は完了。
基本的な学習とホールドアウト検証
シンプルに学習して予測をしたり、精度を計算するだけなら非常にシンプルなコードになる(実際は色々やることがある)。
1from sklearn.svm import SVR
2from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
3
4svr = SVR()
5# 学習
6svr.fit(X_train_sc, y_train)
7# 予測
8y_test_pred = svr.predict(X_test_sc)
9# 検証データのR2
10r2 = svr.score(X_test_sc, y_test)
11# 検証データのMAE
12mae = mean_absolute_error(y_true=y_test, y_pred=y_test_pred)
13# 検証データのRMSE
14rmse = np.sqrt(mean_squared_error(y_true=y_test, y_pred=y_test_pred))
15print(f"{r2=:.4f}, {mae=:.2f}, {rmse=:.2f}")
16# r2=0.0845, mae=161.63, rmse=201.93
モデル構築はfit()に説明変数と目的変数を渡すだけでOK。データ量やアルゴリズム、計算リソースによっては時間がかかる。
予測はpredictに説明変数を渡すだけで簡単にできる。計算時間もほとんどかからない。
ホールドアウト検証は、以下のような流れになる。
- 検証用データで予測(predict)する
- 予測結果と検証データの目的変数で各種スコアを計算する
R2(決定係数)については、score()で計算してくれるアルゴリズムが多い。
MAEやRMSEなどを計算する時は、予測を行い、出てきた予測値と検証データの目的変数で各種スコアの計算を行う。
R2についてもr2_scoreがあるので、r2_score(y_true, y_pred)のように計算してもOK。
ちなみに、このモデルの精度は非常に悪い。R2は8%しかないし、RMSEは200もある(データ作成時に指定したnoiseは50)。
基本の流れに追加で行うこと
より精度を上げていくために、実際の現場ではハイパーパラメーターのチューニングが行われる。
また、学習モデルの精度計算には、より汎化性を上げるためにクロスバリデーションが行われる。
ハイパーパラメーター
ハイパーパラメーターは各アルゴリズムが持っているパラメーター。
アルゴリズム毎にパラメーターは異なる。
ハイパーパラメーターのチューニング
ハイパーパラメーターに色々な値を入れてモデルを構築し、よりベターなモデルを構築できるパラメーターの組み合わせを探すこと。
ある程度のセオリーはあるが、実際に色々な値でモデルを構築してみないと、良いかどうかは分からない。
最適なハイパーパラメーターの探索方法にはグリッドサーチやベイズ最適化を使った探索方法などがある(大抵はベイズ最適化を使うが、処理内容の理解はグリッドサーチの方が圧倒的に簡単)。
クロスバリデーションでの学習と精度
自前でクロスバリデーションの実装
自前でクロスバリデーションを行うと以下のようなコードになる。
1from sklearn.model_selection import KFold
2
3kf = KFold(n_splits=5, shuffle=True, random_state=0)
4
5r2_scores = []
6mae_scores = []
7rmse_scores = []
8for train_idx, valid_idx in kf.split(X_train_sc):
9 # 学習データを学習用とバリデーション用に分ける
10 X_tr, X_valid = X_train_sc[train_idx], X_train_sc[valid_idx]
11 y_tr, y_valid = y_train[train_idx], y_train[valid_idx]
12 # 学習
13 svr = SVR()
14 svr.fit(X_tr, y_tr)
15 # バリデーションデータで精度を計算する
16 valid_pred = svr.predict(X_valid)
17 r2 = r2_score(y_valid, valid_pred)
18 mae = mean_absolute_error(y_valid, valid_pred)
19 rmse = np.sqrt(mean_squared_error(y_valid, valid_pred))
20 # スコア保存
21 r2_scores.append(r2)
22 mae_scores.append(mae)
23 rmse_scores.append(rmse)
24
25# 各foldのスコア平均
26# これが汎化性を考慮した学習データに対するモデルの精度。ホールドアウト検証データに対する精度ではないことに注意。
27# ホールドアウト検証の精度を計算する際には、モデルを全データで学習し直す。
28r2 = np.average(r2_scores)
29mae = np.average(mae_scores)
30rmse = np.average(rmse_scores)
31print(f"{r2=:.4f}, {mae=:.2f}, {rmse=:.2f}")
32# r2=0.0662, mae=160.62, rmse=201.70
学習データに対するMAEやRMSEと先ほど計算したホールドアウト検証データに対するMAEやRMSEは非常に値が近いので、このモデルは精度は悪いが、未知のデータに対しても同じ程度の精度が出そうなことが分かる。
なおクロスバリデーションは一般的にハイパーパラメータ探索と同時に行われ、ハイパーパラメーター探索もループを回すことになるので、forが2重になる。
やりたい内容によってはグリッドサーチとクロスバリデーションを1つの関数で実行できたりするものもあるので、自前で実装する必要が出てくるまでは、ライブラリで用意された機能を使うのがいい。
ライブラリを使ったクロスバリデーションの精度計算
1from sklearn.model_selection import cross_validate
2
3svr = SVR()
4# クロスバリデーションのスコア計算。推定器、説明変数、目的変数、分割数、評価指標を設定する。
5scores = cross_validate(svr, X_train_sc, y_train, cv=5,
6 scoring=("r2",
7 "neg_mean_absolute_error",
8 "neg_root_mean_squared_error"))
9# scoresのキーを確認
10print(scores.keys())
11# 各スコアの平均値を表示
12print(f"r2={scores['test_r2'].mean():.4f}, " \
13 f"mae={-scores['test_neg_mean_absolute_error'].mean():.2f}, " \
14 f"rmse={-scores['test_neg_root_mean_squared_error'].mean():.2f}")
15# dict_keys(['fit_time', 'score_time', 'test_r2', 'test_neg_mean_absolute_error', 'test_neg_root_mean_squared_error'])
16# r2=0.0637, mae=160.70, rmse=201.81
random_stateを指定できないため、完全には結果が一致しないが、ほぼ同じ値になる。
なお、クロスバリデーションはあくまで学習データのみでモデルの精度を計算して汎化性能を上げるためのものであるため、実際に予測に使うモデルを作る際は全学習データを学習に使う。