[Kaggle] Titanic: Machine Learning from Disaster (4)


今回真似するノートパソコンはIntroduction to Ensembling/Stacking in Pythonです.(1)で全体の流れが見られた場合,(2)でEDAとfeature engineering,(3)でモデリングと複数の分類器が見られた場合,(4)で複数の分類器を組み合わせたensemblingを比較した.
通常,機械学習訓練は訓練データでモデル訓練を行い,確立したモデルで予測を行う.Ensemblingは、2つ以上のbaseモデルの結合です.
また,重ね合わせ集積は,基礎モデルから選択した予測を集合させ,訓練集として学習を行う方法である.したがって、スタック統合には、第1のレベルで使用される複数のベースモデルが必要であり、第2のレベルで予測データを使用して訓練される他のモデルが必要である.
その中でRandom Forest分類器,Extra Trees分類器,AdaBoost Classifier,Gradient Boosting分類器,Support Vector Machineの5つを基本モデルとして用いた.そしてsecond-levelでXGBoostを使用しました.

Feature Engineering


Ensembling、Stackingをメインにした文章なので、feature Engineeringは簡単にコードを書くだけです.前の記事と似ているため、詳細は前の記事を参照してください.
full_data = [train ,test]

# New feature : Name_length
train['Name_length'] = train['Name'].apply(len)
test['Name_length'] = test['Name'].apply(len)

# New feature : Has_Cabin
train['Has_Cabin'] = train['Cabin'].apply(lambda x: 0 if type(x) == float else 1)
test['Has_Cabin'] = test['Cabin'].apply(lambda x: 0 if type(x) == float else 1)

# New feature : FamilySize
for dataset in full_data:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
# New feature : IsAlone
for dataset in full_data:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1
# Fill null data : Embarked
for dataset in full_data:
    dataset['Embarked'] = dataset['Embarked'].fillna('S')
# Fill null data : Fare
for dataset in full_data:
    dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
# Categorize numeric feature : Fare
train['CategorialFare'] = pd.qcut(train['Fare'], 4)
# Fill null data & Categorize numeric feature : Age
for dataset in full_data:
    age_avg = dataset['Age'].mean()
    age_std = dataset['Age'].std()
    age_null_count = dataset['Age'].isnull().sum()
    age_null_random_list=  np.random.randint(age_avg - age_std, age_avg + age_std, size=age_null_count)
    dataset['Age'][np.isnan(dataset['Age'])] = age_null_random_list
    dataset['Age'] = dataset['Age'].astype(int)
train['CategorialAge'] = pd.cut(train['Age'], 5)

# New faature : Title
def get_title(name):
    title_search = re.search('([A-Za-z]+)\.', name)
    if title_search:
        return title_search.group(1)
    return ""

for dataset in full_data:
    dataset['Title'] = dataset['Name'].apply(get_title)
for dataset in full_data:
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
    
# Convert to numeric data & Simplify
for dataset in full_data:
    dataset['Sex'] = dataset['Sex'].map({'female': 0, 'male': 1}).astype(int)
    title_mapping = {'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4, 'Rare': 5}
    dataset['Title'] = dataset['Title'].map(title_mapping)
    dataset['Title'] = dataset['Title'].fillna(0)
    dataset['Embarked'] = dataset['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
    dataset.loc[dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
    dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
    dataset['Fare'] = dataset['Fare'].astype(int)
    
    dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
    dataset.loc[ dataset['Age'] > 64, 'Age'] = 4 ;
    

Ensembling & Stacking models


スタック統合を作成する前に、複数のbase分類器をより容易に処理するためにclassを作成しました.既存のSKlearning分類器の訓練,予測,フィット関数を拡張し,初期化段階で訓練パラメータを定義し,次から次へと過程を省いた.
ntrain = train.shape[0]
ntest = test.shape[0]
SEED = 0
NFOLDS = 5
kf = KFold(n_splits=NFOLDS, random_state=SEED, shuffle=True)

class SKlearnHelper(object):
    def __init__(self, clf, seed=0, params=None):
        params['random_state'] = seed
        self.clf = clf(**params)
        
    def train(self, x_train, y_train):
        self.clf.fit(x_train, y_train)
    
    def predict(self, x):
        return self.clf.predict(x)

    def fit(self, x, y):
        return self.clf.fit(x, y)
    
    def feature_importance(self, x, y):
        print(self.clf.fit(x, y).feature_importances_)
        return self.clf.fit(x, y).feature_importances_
Custom class SKlearn-Helperは初期化時にclf,seed,paramsをパラメータとして受信する.clfは、使用するsklearning分類器を表します.seedはrandom seedを表し、paramsはclfに使用されるパラメータを表す.

Out-of-Fold Prediction


Stackingの基本思想は,ベース分類器の予測を二次モデルの訓練入力として用いることである.しかし,ベース分類器を訓練する際には,単純に訓練セット全体を用いて予測を確立し,それを用いて二次モデルを訓練することはできない.baseモデルは予測段階でtest setが見られたため,第2層モデルがオーバーフィットする可能性が高い.
Out−of−Fold予測を用いて,このような重畳集積において起こり得る問題を解決した.Out−of−Fold予測はモデルトレーニングで使用されていないデータで予測することを意味する.
def get_oof(clf, x_train, y_train, x_test):
    oof_train = np.zeros((ntrain, ))
    oof_test = np.zeros((ntest, ))
    oof_test_skf = np.empty((NFOLDS, ntest))
    
    for i, ((train_index, test_index)) in enumerate(kf.split(x_train)):
        x_tr = x_train[train_index]
        y_tr = y_train[train_index]
        x_te = x_train[test_index]
        
        clf.train(x_tr, y_tr)
        
        oof_train[test_index] = clf.predict(x_te)
        oof_test_skf[i, :] = clf.predict(x_test)
        
    oof_test[:] = oof_test_skf.mean(axis=0)
    return oof_train.reshape(-1, 1), oof_test.reshape(-1, 1)

Generate First-level Models


前述した5つの分類器を用いて予測を行った.各分類器のパラメータを定義し、自分で作成したSKlearn-Helperクラスを使用してobjectを作成し、out-of-fold予測を行います.
# Put in our parameters for classifiers
rf_params = {
    'n_jobs': -1,
    'n_estimators': 500,
    'warm_start': True,
    'max_depth': 6,
    'min_samples_leaf': 2,
    'max_features': 'sqrt',
    'verbose': 0
}

et_params = {
    'n_jobs' : -1,
    'n_estimators': 500,
    'max_depth': 8,
    'min_samples_leaf': 2,
    'verbose': 0
}

ada_params = {
    'n_estimators': 500,
    'learning_rate': 0.75
}

gb_params = {
    'n_estimators': 500,
    'max_depth': 5,
    'min_samples_leaf': 2,
    'verbose': 0
}

svc_params = {
    'kernel': 'linear',
    'C': 0.025
}

# Create 5 objects that represent our models
rf = SKlearnHelper(clf=RandomForestClassifier, seed=SEED, params=rf_params)
et = SKlearnHelper(clf=ExtraTreesClassifier, seed=SEED, params=et_params)
ada = SKlearnHelper(clf=AdaBoostClassifier, seed=SEED, params=ada_params)
gb = SKlearnHelper(clf=GradientBoostingClassifier, seed=SEED, params=gb_params)
svc = SKlearnHelper(clf=SVC, seed=SEED, params=svc_params)

# Create our OOF train and test predictions. These base results will be used as new features
et_oof_train, et_oof_test = get_oof(et, x_train, y_train, x_test)
rf_oof_train, rf_oof_test = get_oof(rf, x_train, y_train, x_test)
ada_oof_train, ada_oof_test = get_oof(ada, x_train, y_train, x_test)
gb_oof_train, gb_oof_test = get_oof(gb, x_train, y_train, x_test)
svc_oof_train, svc_oof_test = get_oof(svc, x_train, y_train, x_test)

Feature importances generated from the different classifiers


Pythonのployを使用してFirst level分類器の各特徴的重要性をインタラクティブな散点図として描画します.plogyコードは省略し,結果のみ画像にアップロードする.




Generate Second-level Models

x_train = np.concatenate(( et_oof_train, rf_oof_train, ada_oof_train, gb_oof_train, svc_oof_train), axis=1)
x_test = np.concatenate(( et_oof_test, rf_oof_test, ada_oof_test, gb_oof_test, svc_oof_test), axis=1)

gbm = xgb.XGBClassifier(
    n_estimator=2000,
    max_depth=4,
    min_child_weight=2,
    gamma=0.9,
    subsample=0.8,
    colsample_bytree=0.8,
    objective='binary:logistic',
    nthread=-1,
    scale_pos_weight=1).fit(x_train, y_train)

predictions = gbm.predict(x_test)