8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

機械学習(回帰問題)

Last updated at Posted at 2021-08-29

#データの作成

from sklearn.model_selection import train_test_split

target_col = ''
exclude_cols = ['', '', '', '']
feature_cols = []
for col in dataset.columns:
    if col not in exclude_cols:
        feature_cols.append(col)
        
X = dataset[feature_cols]
y = dataset[target_col]           

X_train, X_test, y_train, y_test = train_test_split(X , y , test_size=0.3, random_state=1234)

print('X_test Features Shape: ', X_test.shape)
print('y_test Target Shape: ', y_test.shape)
print('X_train Features Shape: ', X_train.shape)
print('y_train Target Shape: ', y_train.shape)

回帰分析

線形回帰

誤差が最小になるように切片($\beta_0$)と傾き($\beta_1,\cdots,\beta_K$)を求める。

Y = \beta_0 + \beta_1 X_1 + \cdots + \beta_K X_K + \epsilon

       output.png

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

lm = LinearRegression()
lm.fit(X_train, y_train)
y_pred = lm.predict(X_test)

lm_mse = mean_squared_error(y_test, y_pred)
print('LinerRegression RMSE: ', round(np.sqrt(lm_mse), 3))
print('決定係数:', lm.score(X_test, y_test))

#回帰係数
df_coef = pd.DataFrame({'feature':X.columns, 'coefficient':lm.coef_})
sns.barplot(x='coefficient', y='feature', 
            data=df_coef.sort_values('coefficient', ascending=False))
plt.show()

Lasso回帰

損失関数にL1正則化項を足し合わせたものを使用。
不要と判断される特徴量の係数$\beta_k$が0になるため、特徴量を削減することができる。

\begin{align}

 (損失関数) &= MSE + \alpha ||w||_1  \\
          &= \frac{1}{n} \displaystyle \sum_{ i = 1 }^{ n } (\hat{y_i} - (\beta_0+\cdots+\beta_i x_{i,k}+\cdots))^2 +\alpha \displaystyle \sum_{ k = 1 }^{ K } |\beta_k|\\

\end{align}
from sklearn.linear_model import LassoLars
from sklearn.metrics import mean_squared_error

lasso =  LassoLars(alpha=1.0)
lasso.fit(X_train, y_train)
y_pred = lasso.predict(X_test)

lasso_mse = mean_squared_error(y_test, y_pred)
print('LassoLars RMSE: ', round(np.sqrt(lasso_mse), 3))
print('決定係数:', lasso.score(X_test, y_test))

#回帰係数
df_coef = pd.DataFrame({'feature':X.columns, 'coefficient':lm.coef_})
sns.barplot(x='coefficient', y='feature', 
            data=df_coef.sort_values('coefficient', ascending=False))
plt.show()

#パラメーターの調整
from sklearn.model_selection import GridSearchCV

lasso = LassoLars()
params = {"alpha": [0.1*i for i in range(1,10,1)]}
gscv = GridSearchCV(lasso, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#best_pramsの可視化
gscv_df = pd.DataFrame(gscv.cv_results_['mean_test_score'],
                       index=np.round(params['alpha'],1))
plt.plot(gscv_df)
plt.xlabel('alpha')
plt.ylabel('mean_test_score')
plt.grid(True)
plt.show()

Ridge回帰

損失関数にL2正則化項を足し合わせたものを使用。
係数$\beta_k$は完全に0になることはない性質を持つ。

\begin{align}

 (損失関数) &= MSE + \alpha ||w||_2  \\
          &= \frac{1}{n} \displaystyle \sum_{ i = 1 }^{ n } (\hat{y_i} - (\beta_0+\cdots+\beta_i x_{i,k}+\cdots))^2 +\alpha \displaystyle \sum_{ k = 1 }^{ K } |\beta_k|^2\\

\end{align}
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error

ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)
y_pred = ridge.predict(X_test)

ridge_mse = mean_squared_error(y_test, y_pred)
print('Ridge RMSE: ', round(np.sqrt(ridge_mse), 3))
print('決定係数:', ridge.score(X_test, y_test))

#回帰係数
df_coef = pd.DataFrame({'feature':X.columns, 'coefficient':ridge.coef_})
sns.barplot(x='coefficient', y='feature',
            data=df_coef.sort_values('coefficient', ascending=False))
plt.show()

#パラメーターの調整
from sklearn.model_selection import GridSearchCV

ridge = Ridge()
params = {"alpha": [0.1*i for i in range(1,10,1)]}
gscv = GridSearchCV(ridge, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#best_pramsの可視化
gscv_df = pd.DataFrame(gscv.cv_results_['mean_test_score'],
                       index=np.round(params['alpha'],1))
plt.plot(gscv_df)
plt.xlabel('alpha')
plt.ylabel('mean_test_score')
plt.grid(True)
plt.show()

決定木

回帰問題の場合、MSEが最小となるように分類し、各クラスの平均値を出力する。

output.png

重要度の求め方
ある特徴量で分岐させることでどのくらい値を下げられるかを示す。
回帰問題の場合はMSE、分類問題の場合はジニ係数を使用している。
(例)回帰問題

\begin{align}
 (OverallQualの重要度) &= \frac{I(OverallQual)_1+I(OverallQual)_2+I(OverallQual)_3}{\sum{I(X)}}\\
\\
I(OverallQual)_1 &= (699*6055931186.045)-((559*2297939697.554)+(110*7185735311.169))  \\
I(OverallQual)_2 &= (559*2297939697.554)-((405*1262133046.748)+(154*1982912431.17))  \\
I(OverallQual)_3 &= (110*7185735311.169)-((78*2987618758.878)+(32*9164978854.358))  \\

\end{align}
from sklearn.tree import DecisionTreeRegressor, plot_tree
from sklearn.metrics import mean_squared_error

dt = DecisionTreeRegressor(random_state=1234, 
                           max_depth=None  #全てのリーフがPureになるまで分岐する
                           )
dt.fit(X_train, y_train)
y_pred = dt.predict(X_test)

dt_mse = mean_squared_error(y_test, y_pred)
print('Decision Tree RMSE: ', round(np.sqrt(dt_mse), 3))
print('決定係数:', dt.score(X_test, y_test))

#特徴量の重要度
df_feature_importance = pd.DataFrame({'feature':X.columns, 'importance':dt.feature_importances_})
sns.barplot(x='importance', y='feature',
            data=df_feature_importance.sort_values('importance', ascending=False))
plt.show()

#決定木の可視化
plot_tree(dt, feature_names=X.columns, filled=True, rounded=True)
plt.show()

#パラメータ調整
from sklearn.model_selection import GridSearchCV

dt = DecisionTreeRegressor(random_state=1234)
params = {"max_depth": [i for i in range(4,10,1)]}
gscv = GridSearchCV(dt, param_grid=params, verbose=1, cv=3,scoring='neg_mean_squared_error', n_jobs=-1)
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#グリッドサーチの可視化
gscv_df = pd.DataFrame(gscv.cv_results_['mean_test_score'],
                       index=params['max_depth'])
plt.plot(gscv_df)
plt.xlabel('max_depth')
plt.ylabel('mean_test_score')
plt.grid(True)
plt.show()

ランダムフォレスト

決定木をたくさん作り、多数決を行う。
回帰問題の場合は各決定木の平均値を出力する。
サンプリングはデータと特徴量両方で行われる。

252B495F-24E3-41C6-8930-7E87D5968901.jpeg

重要度の求め方
OOBのデータを使用。
ある特徴量(列)をシャッフルし、ランダムフォレストの精度の差を求める。

(重要度)=(シャッフル前の精度)-(シャッフル後の精度)

精度が下がる → その特徴量が重要であった。
精度があまり変わらない → その特徴量があまり必要ではない。

FBAC9618-E0D3-40EA-8284-5C94AE7AC3B5.jpeg

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error

rf = RandomForestRegressor(random_state=1234,
                           n_estimators=100,
                           max_depth=None  #全てのリーフがPureになるまで分岐する
                           )
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)

rf_mse = mean_squared_error(y_test, y_pred)
print('Random Forest RMSE: ', round(np.sqrt(rf_mse), 3))
print('決定係数:', rf.score(X_test, y_test))

#特徴量の重要度
df_feature_importance = pd.DataFrame({'feature':X.columns, 'importance':rf.feature_importances_})
sns.barplot(x='importance', y='feature',
            data=df_feature_importance.sort_values('importance', ascending=False))
plt.show()

#ハイパーパラメータ調整
from sklearn.model_selection import GridSearchCV

rf = RandomForestRegressor(random_state=1234)
params = {"n_estimators":[10*i for i in range(8,12,1)], "max_depth":[5*i for i in range(1,5,1)]}
gscv = GridSearchCV(rf, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#グリッドサーチの可視化
gscv_df = pd.DataFrame(np.reshape(gscv.cv_results_['mean_test_score'],(len(params['n_estimators']),len(params['max_depth']))),
                       index=gscv.param_grid['n_estimators'],
                       columns=gscv.param_grid['max_depth'])
sns.heatmap(gscv_df, annot=True, fmt="1.2f")
plt.show()

XGBoost

勾配Boosting木の一種で精度が高く、使いやすい。

勾配Boosting木(Gradient Boosting Decision Tree)
〜特徴〜
・特徴量は数値         → 特徴量がある値より大きいか小さいかで分岐するため
・欠損値を扱うことができる   → 欠損値かどうかで分岐もすることができる
・変数間の相互作用が反映される → 分岐の繰り返しによりできる
・スケーリングする必要がない  → 値の大小関係のみが重要
・カテゴリー変数をone-hot-encodingする必要がない
                → 決定木の分岐の際に、勝手に特徴量を抽出してくれる
・疎行列への対応がサポートされている

〜仕組み〜
目的変数$y_i$と予測値$\hat{y_i}$の差について学習し、逐次的に決定木を作成する。
ハイパーパラメータである決定木の本数分、上記を繰り返す。
最終的に各決定木のウェイト$W_m$の総和が予測値となる。

C8112030-AF9A-40A8-910C-5825268AC099.jpeg

木の作成の仕方
Level-wise tree growth・・・深さ単位で木を成長させる。
CEC13020-2396-4D3E-8197-A06304F5C3AD.jpeg

重要度の求め方
gain → 特徴量によって得られた損失の減少を示し、基本的な手法。
他にも、"weight","cover","total_gain","total_cover"がある。

#sklearnAPI ver.
from xgboost import XGBRegressor
from sklearn.metrics import mean_squared_error

xgb =XGBRegressor(random_state=1234,
                  learning_rate=0.3,  # 学習率
                  max_depth=6,  #木の深さ(最も重要)
                  importance_type='gain'  #特徴量の算出方法
                  )
xgb.fit(X_train, y_train)
y_pred = xgb.predict(X_test)

xgb_mse = mean_squared_error(y_test, y_pred)
print('XGBoost RMSE: ', round(np.sqrt(xgb_mse), 3))
print('決定係数:', xgb.score(X_test, y_test))

#特徴量の重要度
df_feature_importance = pd.DataFrame({'feature':X.columns, 'importance':xgb.feature_importances_})
sns.barplot(x='importance', y='feature',
            data=df_feature_importance.sort_values('importance', ascending=False))
plt.show()

#ハイパーパラメータ調整
from sklearn.model_selection import GridSearchCV

xgb =XGBRegressor(random_state=1234)
params = {"learning_rate":[0.05*i for i in range(0,5)],
          "max_depth":[i for i in range(6,10,1)]}
gscv = GridSearchCV(xgb, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#グリッドサーチの可視化
gscv_df = pd.DataFrame(np.reshape(gscv.cv_results_['mean_test_score'],(len(params['learning_rate']),len(params['max_depth']))),
                       index=np.round(gscv.param_grid['learning_rate'], 2),
                       columns=gscv.param_grid['max_depth'])
sns.heatmap(gscv_df, annot=True, fmt="1.2f")
plt.show()

LGBM

XGBoostと比較して、高速かつ精度が同等程度と考えられている。

木の作成の仕方
Leaf-wise tree growth・・・葉単位で木を成長させる
CDB0F4F3-03AB-4EB0-A7B5-A490F680FB21.jpeg

重要度の求め方
split → 分岐に使われた回数
他にも、"gain"を使うことができる。

#sklearnAPI ver.
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_squared_error

lgbm = LGBMRegressor(random_state=1234,
                     num_leaves=31,  #葉の数
                     min_child_samples =20,  #各ノードに入る最小データ数
                     importance_type='split'  #特徴量の算出方法
                     )
lgbm.fit(X_train, y_train)
y_pred = lgbm.predict(X_test)

lgbm_mse = mean_squared_error(y_test, y_pred)
print('LGBM RMSE: ', np.sqrt(lgbm_mse), 3)
print('決定係数:', lgbm.score(X_test, y_test))

#特徴量の重要度
df_feature_importance = pd.DataFrame({'feature':X.columns, 'importance':lgbm.feature_importances_})
sns.barplot(x='importance', y='feature',
            data=df_feature_importance.sort_values('importance', ascending=False))
plt.show()

#ハイパーパラメータ調整
from sklearn.model_selection import GridSearchCV

lgbm = LGBMRegressor(random_state=1234)
params = {"num_leaves":[i for i in range(28,34,1)],
          "min_child_samples":[i for i in range(17,23,1)]}
gscv = GridSearchCV(lgbm, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best MSE:', gscv.best_score_)

#グリッドサーチの可視化
gscv_df = pd.DataFrame(np.reshape(gscv.cv_results_['mean_test_score'],(len(params['num_leaves']),len(params['min_child_samples']))),
                       index=gscv.param_grid['num_leaves'],
                       columns=gscv.param_grid['min_child_samples'])
sns.heatmap(gscv_df, annot=True, fmt="1.2f")
plt.show()

###SVM

from sklearn.svm import SVR
from sklearn.metrics import mean_squared_error

svm = SVR(kernel='rbf', #linear:直線的に分割
          C=1,  #正規化パラメータ
          gamma='scale')  #決定境界の複雑さ scale:1/(n_features*X.var()), auto:1/n_features
svm.fit(X_train, y_train)
y_pred = svm.predict(X_test)
svm_mse = mean_squared_error(y_pred, y_test)

print('SVM RMSE: ', round(np.sqrt(svm_mse), 3))
print('決定係数:', svm.score(X_test, y_test))

#ハイパーパラメータ調整
from sklearn.model_selection import GridSearchCV

svm = SVR(kernel='rbf')
params = {'C':[0.1*i for i in range(0,10,1)], "gamma":[0.01*i for i in range(0,10,1)]}
gscv = GridSearchCV(svm, 
                    param_grid=params,
                    verbose=1,  #詳細を表示
                    cv=3,  #交差検証の分割数
                    scoring='neg_mean_squared_error',
                    n_jobs=-1  #全てのプロセッサを使用する。
                    )
gscv.fit(X_train, y_train)
print('best params:', gscv.best_params_)
print('best logloss:', gscv.best_score_)

#グリッドサーチの可視化
gscv_df = pd.DataFrame(np.reshape(gscv.cv_results_['mean_test_score'],(len(params['C']),len(params['gamma']))),
                       index=np.round(gscv.param_grid['C'],1),
                       columns=np.round(gscv.param_grid['gamma'],2))
sns.heatmap(gscv_df, annot=True, fmt="1.2f")
plt.show()

損失関数(最小二乗法)

以下の尤度関数が最小となるように$,f(x_i)$を求める。

\begin{align}
\displaystyle \sum_{ i = 1 }^{ n } (y_i - f(x_i))^2
\end{align}

精度評価指標

\begin{align}

MSE(MeanSquaredError) &= \frac{1}{n} \displaystyle \sum_{ i = 1 }^{ n } (\hat{y_i} - y_i)^2\\

RMSE(RootMeanSquaredError) &= \sqrt{\frac{1}{n} \displaystyle \sum_{ i = 1 }^{ n } (\hat{y_i} - y_i)^2}  \quad  \cdots  \quad 一般的 \\
MAE(Mean Absolute Error) &= \frac{1}{n} \displaystyle \sum_{ i = 1 }^{ n } |\hat{y_i} - y_i|\\

\end{align}
メリット デメリット
MSE 誤差に対して罰則が強い。
通常の線形回帰モデルを使う場合は、目的関数そのままなので直接最適化可能
元の単位の2乗なので解釈しにくい。
RMSE 元の単位に合っているので解釈しやすい。
通常の線形回帰モデルを使う場合は、目的関数そのままなので直接最適化可能
外れ値に対する罰則は強い。
MAE 外れ値の罰則を緩めることができる。 通常の線形回帰モデルでは直接最適化しているわけではない。

決定係数

モデルが予測できている割合。
分類問題の場合は、Accuracyと等価。

R^2 = 1 - \frac{\displaystyle \sum_{ i = 1 }^{ n } (\hat{y_i} - y_i)^2}
{\displaystyle \sum_{ i = 1 }^{ n } (\bar{y} - y_i)^2}\\

y_i \cdots 実測値 \quad \hat{y_i} \cdots 予測値 \quad \bar{y} \cdots 実測値の平均値

#あとがき
KaggleやSignateのコンペティションに熱中している間に、モデルの内容や指標の解釈を忘れてしまうので、すぐ概要を確認できるようにまとめました。
詳細を突き詰めると、間違えている箇所はあるかもしれませんがご容赦ください。

<参考文献>
・scikit-learnホームページ(https://scikit-learn.org/stable/)
・XGBoostホームページ(https://xgboost.readthedocs.io/en/latest/)
・LightGBMホームページ(https://lightgbm.readthedocs.io/en/latest/)
・Kaggleで勝つデータ分析の技術(技術評論社/著:門脇大輔,阪田隆司,保坂桂佑,平松雄司)

8
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?