第5回:教師あり学習:回帰

5.1 おさらい

教師あり学習 (supervised learning)

データにおける入力X(特徴量)出力y(正解データ)関係f (y=f(X))を学習する。機械学習の最も代表的なアプローチ。回帰分類の2タイプに大別される。

教師あり学習の目標

N個のデータとラベル (x, y) = { (x_1, y_1), (x_2, y_2), ..., (x_N, y_N) } = { (x_n, y_n) }_{n=1}^N とする。このとき、y_n = f(x_n ,w) + ε(ノイズ) として、N個のデータに対して、二乗誤差が最小となるf(x,w)とw*を求めたい(最小二乗法)。

5_eq_param.png

但し、

5_eq_error.png

ここで、f(x,w):モデル、w:パラメータ、f(x_n,w):モデル出力、y_n:目標値、正解データ

教師あり学習の実行手順

  1. 学習データ(x, y) = { (x_n, y_n) }_{n=1}^Nを準備する。(前処理やアノテーションはやっておく)
  2. モデルf(x,w)を選ぶ。(AI専門家は,優れたAIモデルを考案し,その学習アルゴリズムを導出する)
  3. 入力x_nをモデルに与えて出力hat{y}_n = f (x_n , w)を計算し,二乗誤差. E(w)を計算する。(最小二乗法の場合)
  4. 学習アルゴリズムに従って、パラメータwを更新する。(通常、何回も繰り返しが必要)
  5. テストデータを使って、更新されたモデルの予測精度(誤差)を評価する.

学習誤差とテスト誤差

学習データセットX = { (x_n, y_n) }_{n=1}^Nに対し、以下の平均二乗誤差 (Mean Square Error, MSE)を最小化してモデルパラメータwを最適化

5_eq_error.png

これはパラメータwの関数

テストデータセット tilde{X} = { (tilde{x}_n, tilde{y}_n) }_{n=1}^{tilde{N}}に対し、パラメータwを固定してモデルを評価

5_eq_error_test.png

これはテストデータセットtilde{X}の関数 モデルの評価は、学習途中でも行うし、学習終了後にも行う

5.2 例題:不動産価格の予測問題

準備

  1. Google Colabを開き,新規ノートブックを作成
  2. ノートブックの名前 Untitled.ipynb を realestate.ipynb に変更する

ライブラリのインポート

# PandasとNumPyをインポート
import pandas as pd
import numpy as np

# 日本語化MatplotLib
import matplotlib.pyplot as plt
!pip install japanize-matplotlib
import japanize_matplotlib

# Seabornをインポート
import seaborn as sns

# Pickleをインポート
import pickle

データの取得

5_newtaipei.png
図1: 新北市(Wikipediaより)
# 不動産データの取得
data = pd.read_csv("https://www2.cmds.kobe-u.ac.jp/~masa-n/dshandson/realestate-sample.csv")

データの理解・可視化

データの確認と整形

data
# 表の形を表示する
data.shape
# 各列の型を確認する
data.dtypes
data.info()
# 生データをコピーしておく(いつでもやり直せるように)
df = data.copy()

データを眺める

# 箱ひげ図
sns.boxplot(df[["坪単価"]])
5_boxplot.png
図2: 箱ひげ図
# ヒストグラム
sns.histplot(df["坪単価"])
5_histplot.png
図3: ヒストグラム
# 相関行列
sns.heatmap(df.corr(), annot=True)
5_corr.png
図4: 相関行列
# 散布図
sns.scatterplot(df, x="駅距離", y="坪単価", hue="コンビニ数")
5_scatterplot.png
図5: 散布図

前処理

データを修正する

# 欠損値の確認
df.isnull().sum()
# 外れ値を見つける:各変数の要約統計量
df.describe()
# 外れ値を見つける:各変数の箱ひげ図
fig, axes= plt.subplots(nrows=2, ncols=4, tight_layout=True, squeeze=False)
for i, col in  enumerate(["取引日",  "築年数",  "駅距離",  "コンビニ数",  "緯度",  "経度",  "坪単価"]):
sns.boxplot(df[[col]], ax=axes[i//4, i%4])
5_boxplots.png
図6: 各変数の箱ひげ図

外れ値に対する処理は省略

# 重複行を見つける
df[df.duplicated(keep=False)]

データを変換する

# 特徴量に指定する列名リスト
features = df.columns[0:6]

# 正解データに指定する列名
target = df.columns[6:7]

# 特徴量
X = df[features]

# 正解データ
y = df[target]

# X, yのそれぞれを訓練データとテストデータに分ける (訓練:テスト=8:2)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
# 標準化のためのスケーラー
sc = StandardScaler()

# 訓練データにフィットさせる
sc.fit(X_train)

# 訓練データをスケール変換して、データフレームに入れなおす
X_train_sc = pd.DataFrame(sc.transform(X_train), index=X_train.index,columns=X_train.columns)

# テストデータもスケール変換する
X_test_sc = pd.DataFrame(sc.transform(X_test), index=X_test.index,columns=X_test.columns)

教師あり学習の場合は、訓練データとテストデータを分割した後にスケーリングを行う

- データ分割前にスケーラーをfitさせてはならない(訓練データがテストデータに依存してしまう)

- 訓練データにスケーラーをfit、transformさせ、同じスケーラーでテストデータをtransformさせること

- 訓練データとテストデータで別々のスケーラを使ってはいけない

線形回帰モデルによる学習・評価

モデルの選択と学習

# 線形回帰モデルの選択
from sklearn import linear_model
model_lr = linear_model.LinearRegression()

# 線形回帰モデルの学習(標準化した訓練データを使う)
model_lr.fit(X_train_sc, y_train)

モデルの評価

# 線形回帰モデルのパラメータ
m_exp = pd.DataFrame()
m_exp.index = ["切片"] + model_lr.feature_names_in_.tolist()
m_exp["重み"] = [model_lr.intercept_[0]] + model_lr.coef_[0].tolist()
m_exp
# 実際にあっているかどうかを確認してみる
y_eval = pd.DataFrame()
y_eval["正解"] = y_test[target]
y_eval["予測"] = model_lr.predict(X_test_sc)
y_eval["誤差"] = (y_eval["正解"] - y_eval["予測"])

from sklearn.metrics import PredictionErrorDisplay, r2_score, mean_absolute_error, mean_squared_error
disp = PredictionErrorDisplay(y_true = y_eval["正解"], y_pred= y_eval["予測"])
# 正解値と予測値を散布図にプロット
disp.plot(kind="actual_vs_predicted")
5_actual_vs_predicted_lr.png
図7: 正解値と予測値を散布図にプロット(線形回帰モデル)
# 残差と予測値を散布図にプロット
disp.plot()
5_residuals_lr.png
図8: 残差と予測値を散布図にプロット(線形回帰モデル)
# 決定係数
r2 = r2_score(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"決定係数:{r2}")

# MAE(平均絶対誤差)
mae = mean_absolute_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MAE(平均絶対誤差):{mae}")

# MSE(平均二乗誤差)
mse = mean_squared_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MSE(平均二乗誤差):{mse}")

# RMSE(二乗平均平方根誤差)
rmse = np.sqrt(mse)
print(f"RMSE(二乗平均平方根誤差):{rmse}")

汎化性能の確認

# 訓練データに対しても評価してみる
y_eval_train = pd.DataFrame()
y_eval_train["正解"] = y_train[target]
y_eval_train["予測"] = model_lr.predict(X_train_sc)
y_eval_train["誤差"] = (y_eval_train["正解"] - y_eval_train["予測"])
# 正解値と予測値を散布図にプロット(訓練データ、テストデータ両方とも)
plt.xlabel("Predicted values")
plt.ylabel("Actual values")
plt.xlim(0.0, 120.0)
plt.ylim(0.0, 120.0)
plt.grid(True)
plt.gca().set_aspect('equal', adjustable='box')
plt.scatter(y_eval["予測"], y_eval["正解"], label="test", s=10, alpha=0.5, linewidths=1)
plt.scatter(y_eval_train["予測"], y_eval_train["正解"], label="train", s=10, alpha=0.5, linewidths=1)
plt.plot([0, y_test.max()], [0, y_test.max()], 'k--', lw=1)  # y=x
plt.legend()
plt.show()
5_actual_vs_predicted_lr2.png
図9: 正解値と予測値を散布図にプロット(訓練、テスト両方)(線形回帰モデル)
# 残差と予測値を散布図にプロット(訓練データ、テストデータ両方とも)
plt.xlabel("Predicted values")
plt.ylabel("Residuals (Actual - Predicted)")
plt.xlim(0.0, 100.0)
plt.ylim(-100.0, 100.0)
plt.grid(True)
plt.scatter(y_eval["予測"], y_eval["誤差"], label="test", s=10, alpha=0.5, linewidths=1)
plt.scatter(y_eval_train["予測"], y_eval_train["誤差"], label="train", s=10, alpha=0.5, linewidths=1)
plt.legend()
plt.show()
5_residuals_lr2.png
図10: 残差と予測値を散布図にプロット(訓練、テスト両方)(線形回帰モデル)
# 学習誤差とテスト誤差の比較
# 決定係数(テストデータ)
r2 = r2_score(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"決定係数(テストデータ):{r2}")

# 決定係数(訓練データ)
r2_train = r2_score(y_true = y_eval_train['正解'], y_pred = y_eval_train['予測'])
print(f"決定係数(訓練データ) :{r2_train}")

# MAE(テストデータ)
mae = mean_absolute_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MAE(テストデータ):{mae}")

# MAE(訓練データ)
mae_train = mean_absolute_error(y_true = y_eval_train['正解'], y_pred = y_eval_train['予測'])
print(f"MAE(訓練データ) :{mae_train}")
# 学習曲線を作成する
from sklearn.model_selection import learning_curve

train_sizes, train_scores, test_scores = learning_curve(
    estimator=model_lr, X=X_train_sc, y=y_train, cv=10, scoring='r2',
    train_sizes=np.linspace(0.1, 1.0, 10), random_state=1234)

train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)

plt.plot(train_sizes, train_scores_mean, 'o-', color='r', label='Training score')
plt.plot(train_sizes, test_scores_mean, 'o-', color='g', label='Validation score')
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.1, color='r')
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                 test_scores_mean + test_scores_std, alpha=0.1, color='g')
plt.grid()
plt.title('Learning curve')
plt.xlabel('Number of training examples')
plt.ylabel('R2')
plt.ylim(0.0, 1.0)
plt.legend(loc='best')
plt.show()
5_learningcurve_lr.png
図11: 学習曲線(線形回帰モデル)

過学習をしている場合の対処法1:正則化

5_overfit.png
図12: オーバーフィッティング(Wikipediaより)
# ラッソ回帰モデル
# モデルの選択
model_lasso = linear_model.Lasso(alpha=0.1)

# モデルの学習(標準化した訓練データを使う)
model_lasso.fit(X_train_sc, y_train)

# 決定係数
# テストデータ
r2_lasso = model_lasso.score(X_test_sc, y_test)
print(f"決定係数(テストデータ)(ラッソ回帰):{r2_lasso}")
# 訓練データ
r2_train_lasso = model_lasso.score(X_train_sc, y_train)
print(f"決定係数(訓練データ)(ラッソ回帰) :{r2_train_lasso}")
# リッジ回帰モデル
# モデルの選択
model_ridge = linear_model.Ridge(alpha=0.1)

# モデルの学習(標準化した訓練データを使う)
model_ridge.fit(X_train_sc, y_train)

# 決定係数
# テストデータ
r2_ridge = model_ridge.score(X_test_sc, y_test)
print(f"決定係数(テストデータ)(リッジ回帰):{r2_ridge}")
# 訓練データ
r2_train_ridge = model_ridge.score(X_train_sc, y_train)
print(f"決定係数(訓練データ)(リッジ回帰) :{r2_train_ridge}")
# ElasticNetモデル
# モデルの選択
model_elastic = linear_model.ElasticNet(alpha=0.1, l1_ratio=0.5)

# モデルの学習(標準化した訓練データを使う)
model_elastic.fit(X_train_sc, y_train)

# 決定係数
# テストデータ
r2_elastic = model_elastic.score(X_test_sc, y_test)
print(f"決定係数(テストデータ)(ElasticNet):{r2_elastic}")
# 訓練データ
r2_train_elastic = model_elastic.score(X_train_sc, y_train)
print(f"決定係数(訓練データ)(ElasticNet) :{r2_train_elastic}")

回帰木モデルによる学習・評価

モデルの選択と学習

# 回帰木モデルの選択
from sklearn import tree
model_dt = tree.DecisionTreeRegressor(max_depth=10, random_state=1234) #max_depthは木の深さの最大値

# 回帰木モデルの学習
model_dt.fit(X_train, y_train)

モデルの評価

# 得られた木を可視化する(木が深いと描画に時間がかかる)
plt.figure(figsize=(40, 20))
_ = tree.plot_tree(model_dt, fontsize=10, feature_names=X.columns)
5_tree.png
図11: 得られた木を可視化
# 誤差を確認する
y_eval = pd.DataFrame()
y_eval["正解"] = y_test[target]
y_eval["予測"] = model_dt.predict(X_test)
y_eval["誤差"] = (y_eval["正解"] - y_eval["予測"])

from sklearn.metrics import PredictionErrorDisplay, r2_score, mean_absolute_error, mean_squared_error
disp = PredictionErrorDisplay(y_true = y_eval["正解"], y_pred= y_eval["予測"])
# 正解値と予測値を散布図にプロット
disp.plot(kind="actual_vs_predicted")
5_actual_vs_predicted_dt.png
図12: 正解値と予測値を散布図にプロット(回帰木モデル)
# 残差と予測値を散布図にプロット
disp.plot()
5_residuals_dt.png
図12: 残差と予測値を散布図にプロット(回帰木モデル)
# 決定係数
r2 = r2_score(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"決定係数:{r2}")

# MAE(平均絶対誤差)
mae = mean_absolute_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MAE(平均絶対誤差):{mae}")

# MSE(平均二乗誤差)
mse = mean_squared_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MSE(平均二乗誤差):{mse}")

# RMSE(二乗平均平方根誤差)
rmse = np.sqrt(mse)
print(f"RMSE(二乗平均平方根誤差):{rmse}")

汎化性能の確認

# 訓練データに対しても評価してみる
y_eval_train = pd.DataFrame()
y_eval_train["正解"] = y_train[target]
y_eval_train["予測"] = model_dt.predict(X_train)
y_eval_train["誤差"] = (y_eval_train["正解"] - y_eval_train["予測"])
# 正解値と予測値を散布図にプロット(訓練データ、テストデータ両方とも)
plt.xlabel("Predicted values")
plt.ylabel("Actual values")
plt.xlim(0.0, 120.0)
plt.ylim(0.0, 120.0)
plt.grid(True)
plt.gca().set_aspect('equal', adjustable='box')
plt.scatter(y_eval["予測"], y_eval["正解"], label="test", s=10, alpha=0.5, linewidths=1)
plt.scatter(y_eval_train["予測"], y_eval_train["正解"], label="train", s=10, alpha=0.5, linewidths=1)
plt.plot([0, y_test.max()], [0, y_test.max()], 'k--', lw=1)  # y=x
plt.legend()
plt.show()
5_actual_vs_predicted_dt2.png
図13: 正解値と予測値を散布図にプロット(訓練、テスト両方)(回帰木モデル)
# 残差と予測値を散布図にプロット(訓練データ、テストデータ両方とも)
plt.xlabel("Predicted values")
plt.ylabel("Residuals (Actual - Predicted)")
plt.xlim(0.0, 100.0)
plt.ylim(-100.0, 100.0)
plt.grid(True)
plt.scatter(y_eval["予測"], y_eval["誤差"], label="test", s=10, alpha=0.5, linewidths=1)
plt.scatter(y_eval_train["予測"], y_eval_train["誤差"], label="train", s=10, alpha=0.5, linewidths=1)
plt.legend()
plt.show()
5_residuals_dt2.png
図14: 残差と予測値を散布図にプロット(訓練、テスト両方)(回帰木モデル)
# 学習誤差とテスト誤差の比較
# 決定係数(テストデータ)
r2 = r2_score(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"決定係数(テストデータ):{r2}")

# 決定係数(訓練データ)
r2_train = r2_score(y_true = y_eval_train['正解'], y_pred = y_eval_train['予測'])
print(f"決定係数(訓練データ) :{r2_train}")

# MAE(テストデータ)
mae = mean_absolute_error(y_true = y_eval['正解'], y_pred= y_eval['予測'])
print(f"MAE(テストデータ):{mae}")

# MAE(訓練データ)
mae_train = mean_absolute_error(y_true = y_eval_train['正解'], y_pred = y_eval_train['予測'])
print(f"MAE(訓練データ) :{mae_train}")
# 学習曲線を作成する
from sklearn.model_selection import learning_curve

train_sizes, train_scores, test_scores = learning_curve(
  estimator=model_dt, X=X_train, y=y_train, cv=10, scoring='r2',
  train_sizes=np.linspace(0.1, 1.0, 10), random_state=1234)

train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)

plt.plot(train_sizes, train_scores_mean, 'o-', color='r', label='Training score')
plt.plot(train_sizes, test_scores_mean, 'o-', color='g', label='Validation score')
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                  train_scores_mean + train_scores_std, alpha=0.1, color='r')
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                  test_scores_mean + test_scores_std, alpha=0.1, color='g')
plt.grid()
plt.title('Learning curve')
plt.xlabel('Number of training examples')
plt.ylabel('R2')
plt.ylim(0.0, 1.0)
plt.legend(loc='best')
plt.show()
5_learningcurve_dt.png
図15: 学習曲線(回帰木モデル)

過学習をしている場合の対処法2:ハイパーパラメータチューニング

# 検証曲線を作成する(横軸はmax_depth)
from sklearn.model_selection import validation_curve

param_range = np.arange(1, 16)
train_scores, test_scores = validation_curve(
    estimator=model_dt, X=X_train, y=y_train, cv=10, scoring='r2',
    param_name='max_depth', param_range=param_range)

train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)

plt.plot(param_range, train_scores_mean, 'o-', color='r', label='Training score')
plt.plot(param_range, test_scores_mean, 'o-', color='g', label='Validation score')
plt.fill_between(param_range, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.1, color='r')
plt.fill_between(param_range, test_scores_mean - test_scores_std,
                 test_scores_mean + test_scores_std, alpha=0.1, color='g')
plt.grid()
plt.title('Validation curve')
plt.xlabel('Parameter max_depth')
plt.ylabel('R2')
plt.ylim(0.0, 1.0)
plt.legend(loc='best')
plt.show()
5_validationcurve_dt.png
図16: 検証曲線(回帰木モデル)

過学習をしている場合の対処法3:特徴量選択

# 特徴量重要度の確認
imp_value = pd.DataFrame({'feature':features, 'importance':model_dt.feature_importances_})
print(imp_value)
# 特徴量重要度のグラフ化
imp = model_dt.feature_importances_
label = X.columns
indices = np.argsort(imp)

fig=plt.figure(figsize=(4,4))
plt.barh(range(len(imp)), imp[indices])
plt.yticks(range(len(imp)), label[indices], fontsize=10)
plt.xticks(fontsize=10)
plt.ylabel("Feature", fontsize=12)
plt.xlabel("Feature importance", fontsize=12)
5_featureimportances.png
図17: 特徴量重要度
# 上位2つの特徴量以外を削除
X_train_dropped = X_train.drop(columns=["コンビニ数", "取引日", "緯度", "築年数"])
X_test_dropped = X_test.drop(columns=["コンビニ数", "取引日", "緯度", "築年数"])
# 確認
X_train_dropped.info()
X_test_dropped.info()
# 再評価
# モデルの学習(特徴量を選別した訓練データを使う)
model_dt.fit(X_train_dropped, y_train)

# 決定係数
# テストデータ
r2 = model_dt.score(X_test_dropped, y_test)
print(f"決定係数(テストデータ)(回帰木):{r2}")
# 訓練データ
r2_train = model_dt.score(X_train_dropped, y_train)
print(f"決定係数(訓練データ)(回帰木) :{r2_train}")

トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS