LoginSignup
37
26

More than 3 years have passed since last update.

機械学習スタッキング テンプレート(回帰)

Last updated at Posted at 2020-02-14

機械学習 スタッキング (回帰問題)

kaggleでは、上位の人はほとんどスタッキングを使っている。
自分も上位に食い込みたいので、スタッキングを勉強してみた。
ここでは、スタッキングの時に、使用するコードをコピペで使えるようにしている。
(ただし、回帰問題に限る)

今回は、2層のスタッキングです。

使用した機械学習

使用した機械学習は、なんとなく

・XGBoost
・LightGBM
・Catboost
・MLP(隠れ層2)
・Linear SVR
・Kernel SVR (rbf)
・KNN
・Lasso
・Ridge
・ElasticNet
・RandomForest

ハイパーパラメータ について

なお、ハイパーパラメータは、きちんと調節していません。
でも、スタッキングの場合、集解調節しすぎると過学習になってしまって
テストデータで精度が落ちてしまう可能性があり。難しいところです。

テンプレート

以下に、テンプレートを貼っておきます。
classを作ることで、
・学習する時 fit
・予測するときは predict
で統一できるので、非常に便利になっております(真似しただけです)

なお、すべてコピー&ペーストで使えるようになっている(はず)。

XGBoost

import xgboost as xgb

class Model1Xgb:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        xgb_params = {'objective': 'reg:squarederror', #binary:logistic #multi:softprob,
                  'random_state': 10,
                  #'eval_metric': 'rmse'
                     }
        dtrain = xgb.DMatrix(tr_x, label=tr_y)
        dvalid = xgb.DMatrix(va_x, label=va_y)
        evals = [(dtrain, 'train'), (dvalid, 'eval')]
        self.model = xgb.train(xgb_params, dtrain, num_boost_round=10000,early_stopping_rounds=50, evals=evals)

    def predict(self, x):
        data = xgb.DMatrix(x)
        pred = self.model.predict(data)
        return pred

LightGBM

import lightgbm as lgb

class Model1lgb:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        lgb_params = {'objective': 'rmse',
                  'random_state': 10,
                  'metric': 'rmse'}
        lgb_train = lgb.Dataset(tr_x, label=tr_y)
        lgb_eval = lgb.Dataset(va_x, label=va_y,reference=lgb_train)
        self.model = lgb.train(lgb_params, lgb_train, valid_sets=lgb_eval, num_boost_round=10000,early_stopping_rounds=50)

    def predict(self, x):
        pred = self.model.predict(x,num_iteration=self.model.best_iteration)
        return pred

Catboost

import catboost
class Model1catboost:

    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        #https://catboost.ai/docs/concepts/python-reference_catboostregressor.html
        #catb = catboost.CatBoostClassifier(
        catb = catboost.CatBoostRegressor(
                                    iterations=10000, 
                                    use_best_model=True, 
                                    random_seed=10, 
                                    l2_leaf_reg=3,
                                    depth=6,
                                    loss_function="RMSE",#"CrossEntropy",
                                    #eval_metric = "RMSE", #'AUC',
                                    #classes_coun=3
                                  )
        self.model = catb.fit(tr_x,tr_y,eval_set=(va_x,va_y),early_stopping_rounds=50)
        print(self.model.score(va_x,va_y))
    def predict(self, x):
        pred = self.model.predict(x)
        return pred

Multi-layer Perceptron(MLP)

今回は、4層のニューラルネットです。

rom keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.callbacks import EarlyStopping

class Model1NN:
    def __init__(self):
        self.model = None
        self.scaler = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)

        batch_size = 128
        epochs = 10000

        tr_x = self.scaler.transform(tr_x)
        va_x = self.scaler.transform(va_x)

        early_stopping =  EarlyStopping(
                            monitor='val_loss',
                            min_delta=0.0,
                            patience=20,
        )

        model = Sequential()
        model.add(Dense(32, activation='relu', input_shape=(tr_x.shape[1],)))
        model.add(Dropout(0.5))
        model.add(Dense(32, activation='relu'))
        model.add(Dropout(0.5))
        model.add(Dense(1, activation='sigmoid'))

        model.compile(loss='mean_squared_error', 
                      #'categorical_crossentropy',#categorical_crossentropy
                      optimizer='adam')

        history = model.fit(tr_x, tr_y,
                            batch_size=batch_size, epochs=epochs,
                            verbose=1,
                            validation_data=(va_x, va_y),
                            callbacks=[early_stopping])
        self.model = model

    def predict(self, x):
        x = self.scaler.transform(x)
        pred = self.model.predict_proba(x).reshape(-1)
        return pred

Linear SVR

from sklearn.svm import LinearSVR

class Model1LinearSVR:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        #params = {"C":np.logspace(0,1,params_cnt),"epsilon":np.logspace(-1,1,params_cnt)}
        self.model = LinearSVR(max_iter=1000,
                               random_state=10,
                               C = 1.0, #損失の係数(正則化係数の逆数)
                               epsilon = 5.0

                              )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

Kernel SVR

from sklearn.svm import SVR

class Model1KernelSVR:

    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        #params = {"kernel":['rbf'],"C":np.logspace(0,1,params_cnt), "epsilon":np.logspace(-1,1,params_cnt)}
        self.model = SVR(kernel='rbf',
                         gamma='auto',
                         max_iter=1000,
                         C = 1.0, #損失の係数(正則化係数の逆数)
                         epsilon = 5.0

                              )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

Lasso

from sklearn.linear_model import Lasso

class Model1Lasso:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        '''
        (1 / (2 * n_samples)) * ||y - Xw||^2_2 + alpha * ||w||_1
        #https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.html
        '''
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        self.model = Lasso(
            alpha=1, #L1係数
            fit_intercept=True,
            max_iter=1000,
            random_state=10,
            )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

Ridge

from sklearn.linear_model import Ridge

class Model1Ridge:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        '''
        |y - Xw||^2_2 + alpha * ||w||^2_2
        #https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html
        '''
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        self.model = Ridge(
            alpha=1, #L2係数
            max_iter=1000,
            random_state=10,
                              )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

ElasticNet

from sklearn.linear_model import ElasticNet

class Model1ElasticNet:

    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        '''1 / (2 * n_samples) * ||y - Xw||^2_2
        + alpha * l1_ratio * ||w||_1
        + 0.5 * alpha * (1 - l1_ratio) * ||w||^2_2
       ref)  https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ElasticNet.html
        '''

        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        self.model = ElasticNet(
            alpha=1, #L1係数
            l1_ratio=0.5,
                              )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

RandomForest

from sklearn.ensemble import RandomForestRegressor

class Model1RF:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        self.model = RandomForestRegressor(
            max_depth=5,
            n_estimators=100,
            random_state=10,
        )
        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

KNN(K近傍法)

from sklearn.neighbors import KNeighborsRegressor

class Model1KNN:
    def __init__(self):
        self.model = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        #params = {"kernel":['rbf'],"C":np.logspace(0,1,params_cnt), "epsilon":np.logspace(-1,1,params_cnt)}
        self.model = KNeighborsRegressor(n_neighbors=5,
                                         #weights='uniform'
                                        )

        self.model.fit(tr_x,tr_y)

    def predict(self,x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

線形回帰(Linear Regression)

from sklearn.linear_model import LinearRegression

class Model2Linear:
    def __init__(self):
        self.model = None
        self.scaler = None

    def fit(self, tr_x, tr_y, va_x, va_y):
        self.scaler = StandardScaler()
        self.scaler.fit(tr_x)
        tr_x = self.scaler.transform(tr_x)
        self.model = LinearRegression()
        self.model.fit(tr_x, tr_y)

    def predict(self, x):
        x = self.scaler.transform(x)
        pred = self.model.predict(x)
        return pred

Out-of-fold

今回は、4foldに分けて、out-of-foldしています。
foldは通常、2-5foldくらいにするらしい。
適当なデータで10foldでやってみたら、精度が落ちた。
fold数を大きくしすぎると、似ているデータセットばかりで学習することになるから
バリデーションデータに過学習しちゃうのかなって思っています。

from sklearn.model_selection import KFold
from sklearn.preprocessing import StandardScaler

def predict_cv(model, train_x, train_y, test_x):
    preds = []
    preds_test = []
    va_idxes = []

    kf = KFold(n_splits=4, shuffle=True, random_state=10)

    # クロスバリデーションで学習・予測を行い、予測値とインデックスを保存する
    for i, (tr_idx, va_idx) in enumerate(kf.split(train_x)):
        tr_x, va_x = train_x.iloc[tr_idx], train_x.iloc[va_idx]
        tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]
        model.fit(tr_x, tr_y, va_x, va_y)
        pred = model.predict(va_x)
        preds.append(pred)
        pred_test = model.predict(test_x)
        preds_test.append(pred_test)
        va_idxes.append(va_idx)

    # バリデーションデータに対する予測値を連結し、その後元の順序に並べ直す
    va_idxes = np.concatenate(va_idxes)
    preds = np.concatenate(preds, axis=0)
    order = np.argsort(va_idxes)
    pred_train = preds[order]

    # テストデータに対する予測値の平均をとる
    preds_test = np.mean(preds_test, axis=0)

    return pred_train, preds_test

2層目の特徴量を作る

train_x_2,test_x_2を作る時(下の行)に、どのデータを用いるかで精度がかなり変わってくる。
自分で、いろいろ組み合わせを試して、精度を確認してみると、案外面白い。
今回は、pred_1d(LinearSVR)を除外した方が精度が高かったので、外しました。

model_1a = Model1Xgb()
pred_train_1a, pred_test_1a = predict_cv(model_1a, df_trainval, y_trainval, df_test)

model_1b = Model1lgb()
pred_train_1b, pred_test_1b = predict_cv(model_1b, df_trainval, y_trainval, df_test)

model_1c = Model1NN()
pred_train_1c, pred_test_1c = predict_cv(model_1c, df_trainval, y_trainval, df_test)

model_1d = Model1LinearSVR()
pred_train_1d, pred_test_1d = predict_cv(model_1d, df_trainval, y_trainval, df_test)

model_1e = Model1KernelSVR()
pred_train_1e, pred_test_1e = predict_cv(model_1e, df_trainval, y_trainval, df_test)

model_1f = Model1catboost()
pred_train_1f, pred_test_1f = predict_cv(model_1f, df_trainval, y_trainval, df_test)

model_1g = Model1KNN()
pred_train_1g, pred_test_1g = predict_cv(model_1g, df_trainval, y_trainval, df_test)

model_1h = Model1Lasso()
pred_train_1h, pred_test_1h = predict_cv(model_1h, df_trainval, y_trainval, df_test)

model_1i = Model1Ridge()
pred_train_1i, pred_test_1i = predict_cv(model_1i, df_trainval, y_trainval, df_test)

model_1j = Model1ElasticNet()
pred_train_1j, pred_test_1j = predict_cv(model_1j, df_trainval, y_trainval, df_test)

model_1k = Model1RF()
pred_train_1k, pred_test_1k = predict_cv(model_1k, df_trainval, y_trainval, df_test)


from sklearn.metrics import mean_absolute_error
'''
#Calculate RMSE
from sklearn.metrics import mean_square_error
def rmse(y_true,y_pred):
    rmse = np.sqrt(mean_squared_error(y_true,y_pred))
    print('rmse',rmse)
    return rmse
'''

'''
以下で、1層目の各予測モデルの精度を確認している。
今回は、mean_absolute_error(MAE)で精度確認
'''
print(f'a LGBM mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1a):.4f}')
print(f'b XGBoostmean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1b):.4f}')
print(f'c MLP mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1c):.4f}')
print(f'd LinearSVR mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1d):.4f}')
print(f'e KernelSVR mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1e):.4f}')
print(f'f Catboost mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1f):.4f}')
print(f'g KNN mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1g):.4f}')
print(f'h Lasso mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1h):.4f}')
print(f'i Ridge mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1i):.4f}')
print(f'j ElasticNet mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1j):.4f}')
print(f'k RandomForest mean_absolute_error: {mean_absolute_error(y_trainval,pred_train_1k):.4f}')

'''
以下では、1層目の予測値をデータフレームにまとめている。
データフレームにまとめて、2層目の特徴量として、まとめている
'''
train_x_2 = pd.DataFrame({'pred_1a': pred_train_1a,
                          'pred_1b': pred_train_1b,
                          'pred_1c': pred_train_1c,
                          #'pred_1d': pred_train_1d,
                          'pred_1e': pred_train_1e,
                          'pred_1f': pred_train_1f,
                          'pred_1g': pred_train_1g,
                          'pred_1h': pred_train_1h,
                          'pred_1i': pred_train_1i,
                          'pred_1j': pred_train_1j,
                          'pred_1k': pred_train_1k,
                         })
test_x_2 = pd.DataFrame({'pred_1a': pred_test_1a,
                          'pred_1b': pred_test_1b,
                          'pred_1c': pred_test_1c,
                          #'pred_1d': pred_test_1d,
                          'pred_1e': pred_test_1e,
                          'pred_1f': pred_test_1f,
                          'pred_1g': pred_test_1g,
                          'pred_1h': pred_test_1h,
                          'pred_1i': pred_test_1i,
                          'pred_1j': pred_test_1j,
                          'pred_1k': pred_test_1k
                         })

スタッキング2層目に線形回帰を設定

今回は、1層目の各モデルの予測値のみを2層目の特徴量として設定しました。
2層目の特徴量の作成の仕方には、いくつか方法があります。
1. 1層目の予測値のみを特徴量とする(今回)
2. 1層目の予測値 + 元々の特徴量
3. 1層目の予測値 + 元々の特徴量 or 予測値の平均・分散(統計量)など 
4. 1層目の予測値 + 元々の特徴量 or 予測値の平均・分散(統計量)など or 元々の特徴量を加工したもの(PCA,t-SNEなど)

model2 = Model2Linear()
pred_train_2, pred_test_2 = predict_cv(model2, train_x_2, y_trainval, test_x_2)

#2層のスタッキング後の、精度を確認。
print(f'mean_absolute_error: {mean_absolute_error(y_trainval, pred_train_2):.4f}')

最後に

スタッキングすることで、精度は向上していきます。
強いkagglerとかは、スタッキングを3、4層とかにつなげていきます。
3、4層にしていくことで精度は、上がっていきますが、
一般的に、層を重ねるごとに改善していく度合いは、小さくなっていきます。

また、スタッキングのやり方を頑張って考えるよりも、
自分で特徴量を作成して、良い特徴量を見つけた時の方が、精度は上がりやすいです。
スタッキングに頼るだけでなく、頑張って、自分で良い特徴量を作成したいですね。

参考)
Kaggleで勝つ データ分析の技術
https://github.com/ghmagazine/kagglebook/tree/master/ch07

間違いなど、ありましたら
コメントよろしくお願いします。

37
26
0

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
37
26