模型融合 - 第二课

kaggle比赛中,单个模型,基本上已经不能取得好成绩,需要多个模型集合。本文是一个非常基本和简单的入门教程,来介绍集成(组合)基础学习模型的方法,特别是称为堆叠的集成变体。简而言之,堆叠用作第一级(基础),预测几个基本分类器,然后在第二级使用另一个模型来预测早期第一级预测的输出。
示例数据集使用泰坦尼克数据集。此外,尽管Stacking已经成为许多团队赢得Kaggle比赛的原因,但似乎很少有资料介绍如何进行Stacking。

# Load in our libraries
import pandas as pd
import numpy as np
import re
import sklearn
import xgboost as xgb
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

import plotly.offline as py
py.init_notebook_mode(connected=True)
import plotly.graph_objs as go
import plotly.tools as tls

import warnings
warnings.filterwarnings('ignore')

# Going to use these 5 base models for the stacking
from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier, 
                              GradientBoostingClassifier, ExtraTreesClassifier)
from sklearn.svm import SVC
from sklearn.cross_validation import KFold

特征工程,数据清洗

现在我们将继续讨论大多数内核的结构,即首先探索现有数据,识别可能的特征工程以及对任何分类特征进行数字编码。

# Load in the train and test datasets
train = pd.read_csv('../input/train.csv')
test = pd.read_csv('../input/test.csv')

# Store our passenger ID for easy access
PassengerId = test['PassengerId']
train.head(3)

关于如何探索数据,可以参考我们的第一课

full_data = [train, test]

# Some features of my own that I have added in
# Gives the length of the name
train['Name_length'] = train['Name'].apply(len)
test['Name_length'] = test['Name'].apply(len)
# Feature that tells whether a passenger had a cabin on the Titanic
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)

# Feature engineering steps taken from Sina
# Create new feature FamilySize as a combination of SibSp and Parch
for dataset in full_data:
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
# Create new feature IsAlone from FamilySize
for dataset in full_data:
    dataset['IsAlone'] = 0
    dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1
# Remove all NULLS in the Embarked column
for dataset in full_data:
    dataset['Embarked'] = dataset['Embarked'].fillna('S')
# Remove all NULLS in the Fare column and create a new feature CategoricalFare
for dataset in full_data:
    dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
train['CategoricalFare'] = pd.qcut(train['Fare'], 4)
# Create a New feature CategoricalAge
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['CategoricalAge'] = pd.cut(train['Age'], 5)
# Define function to extract titles from passenger names
def get_title(name):
    title_search = re.search(' ([A-Za-z]+)\.', name)
    # If the title exists, extract and return it.
    if title_search:
        return title_search.group(1)
    return ""
# Create a new feature Title, containing the titles of passenger names
for dataset in full_data:
    dataset['Title'] = dataset['Name'].apply(get_title)
# Group all non-common titles into one single grouping "Rare"
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')

for dataset in full_data:
    # Mapping Sex
    dataset['Sex'] = dataset['Sex'].map( {'female': 0, 'male': 1} ).astype(int)
    
    # Mapping titles
    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)
    
    # Mapping Embarked
    dataset['Embarked'] = dataset['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
    
    # Mapping Fare
    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)
    
    # Mapping Age
    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 ;


# Feature selection
drop_elements = ['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp']
train = train.drop(drop_elements, axis = 1)
train = train.drop(['CategoricalAge', 'CategoricalFare'], axis = 1)
test  = test.drop(drop_elements, axis = 1)

好了,现在已经清理了部分功能并提取了相关信息,并删除了分类列,都是数字,这种格式适合我们的机器学习模型。 然而,在我们继续之前,让我们生成一些简单的变换数据集的相关和分布图来观察

可视化

** Pearson相关热图**

让我们生成一些特征的相关图,以查看一个特征与下一个特征的相关性。 为此,我们将利用Seaborn绘图软件包,使我们能够非常方便地绘制热图,如下所示

colormap = plt.cm.RdBu
plt.figure(figsize=(14,12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sns.heatmap(train.astype(float).corr(),linewidths=0.1,vmax=1.0, 
            square=True, cmap=colormap, linecolor='white', annot=True)
image.png

Pearson Correlation图可以告诉我们的一件事是,没有太多的特征彼此强烈相关。 从将这些特征提供到学习模型中的观点来看,这是很好的,因为这意味着我们的训练集中没有太多冗余或多余的数据,我们很高兴每个特征都带有一些独特的信息。 这里有两个最相关的特征是家庭大小和Parch(父母和孩子)。 为了本练习的目的,我仍然会将这两个功能都保留下来。
最后让我们生成一些配对图来观察从一个特征到另一个特征的数据分布。 我们再次使用Seaborn来帮助我们。

g = sns.pairplot(train[[u'Survived', u'Pclass', u'Sex', u'Age', u'Parch', u'Fare', u'Embarked',
       u'FamilySize', u'Title']], hue='Survived', palette = 'seismic',size=1.2,diag_kind = 'kde',diag_kws=dict(shade=True),plot_kws=dict(s=10) )
g.set(xticklabels=[])
image.png

Stacking模型

最后,在关于特征工程和格式化后,我们终于开始本文的主旨。
创建堆叠合奏!
在这里,我们调用Python的类。 对于任何新的编程人员,通常会听到与面向对象编程(OOP)结合使用的类。 简而言之,类有助于扩展一些代码/程序来创建对象(旧学校窥视的变量)以及实现特定于该类的函数和方法。

在下面的代码部分中,我们基本上编写了一个类SklearnHelper,它允许扩展所有Sklearn分类器共有的内置方法(例如训练,预测和拟合)。 因此,如果我们想要调用五个不同的分类器,这将消除冗余,因为不需要编写相同的方法五次。

Out-of-Fold Predictions

现在如上文在介绍部分中所提到的,堆叠使用基础分类器的预测作为对第二级模型的训练的输入。 然而,不能简单地在完整训练数据上训练基础模型,在完整测试集上生成预测,然后将这些预测输出到第二级训练。 这会冒你的基础模型预测已经“看到”测试集并因此在提供这些预测时过度拟合的风险。

#生成我们的基础一级模型

所以现在让我们准备五个学习模型作为我们的第一级分类。 这些模型都可以通过Sklearn库方便地调用,如下所示:

1.随机森林分类器
2.额外的树木分类器

  1. AdaBoost分类
  2. Gradient Boosting classifer
    5.支持向量机

862/5000
****参数

只是我们将在此列出的完整性参数的解释,

** n_jobs **:用于培训过程的核心数。 如果设置为-1,则使用所有核心。
** n_estimators **:学习模型中的分类树数量(默认设置为10)
** max_depth **:树的最大深度,或节点应扩展的程度。 请注意,如果设置得太高,则数字会冒着过度拟合的风险,因为人们会过长地生长树
** verbose **:控制是否要在学习过程中输出任何文本。 值0将抑制所有文本,而值3则在每次迭代时输出树学习过程。
请通过Sklearn官方网站查看完整说明。 在那里你会发现你可以使用其他许多有用的参数。

# Put in our parameters for said classifiers
# Random Forest parameters
rf_params = {
    'n_jobs': -1,
    'n_estimators': 500,
     'warm_start': True, 
     #'max_features': 0.2,
    'max_depth': 6,
    'min_samples_leaf': 2,
    'max_features' : 'sqrt',
    'verbose': 0
}

# Extra Trees Parameters
et_params = {
    'n_jobs': -1,
    'n_estimators':500,
    #'max_features': 0.5,
    'max_depth': 8,
    'min_samples_leaf': 2,
    'verbose': 0
}

# AdaBoost parameters
ada_params = {
    'n_estimators': 500,
    'learning_rate' : 0.75
}

# Gradient Boosting parameters
gb_params = {
    'n_estimators': 500,
     #'max_features': 0.2,
    'max_depth': 5,
    'min_samples_leaf': 2,
    'verbose': 0
}

# Support Vector Classifier parameters 
svc_params = {
    'kernel' : 'linear',
    'C' : 0.025
    }

现在让我们通过我们之前定义的Helper Sklearn类创建5个对象,这些对象代表我们的5个学习模型。

# Create 5 objects that represent our 4 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)

从我们的训练和测试数据集中创建NumPy阵列

在准备好我们的第一层基础模型之后,我们现在可以通过从原始数据帧生成NumPy数组,来准备训练和测试测试数据,以输入到我们的分类器中,如下所示

# Create Numpy arrays of train, test and target ( Survived) dataframes to feed into our models
y_train = train['Survived'].ravel()
train = train.drop(['Survived'], axis=1)
x_train = train.values # Creates an array of the train data
x_test = test.values # Creats an array of the test data

第一级预测的输出**

我们现在将训练和测试数据提供给我们的5个基本分类器,并使用我们之前定义的Out-of-Fold预测函数来生成我们的第一级预测。 允许少数几分钟运行下面的代码块。

# 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) # Extra Trees
rf_oof_train, rf_oof_test = get_oof(rf,x_train, y_train, x_test) # Random Forest
ada_oof_train, ada_oof_test = get_oof(ada, x_train, y_train, x_test) # AdaBoost 
gb_oof_train, gb_oof_test = get_oof(gb,x_train, y_train, x_test) # Gradient Boost
svc_oof_train, svc_oof_test = get_oof(svc,x_train, y_train, x_test) # Support Vector Classifier

print("Training is complete")

不同分类器生成的特征重要性

现在我们已经学习了第一级分类器,我们可以利用Sklearn模型的一个非常漂亮的功能,即通过一行非常简单的代码输出训练和测试集中各种功能的重要性。

根据Sklearn文档,大多数分类器都内置了一个属性,只需键入**。feature_importances _ **即可返回要素重要性。 因此,我们将通过我们的函数earliand调用这个非常有用的属性来绘制特征重要性

rf_feature = rf.feature_importances(x_train,y_train)
et_feature = et.feature_importances(x_train, y_train)
ada_feature = ada.feature_importances(x_train, y_train)
gb_feature = gb.feature_importances(x_train,y_train)

这个结果包含输入数据的重要性,先打包成一个数据结构,下面我们通过Plotly包进行简单的绘图。

cols = train.columns.values
# Create a dataframe with features
feature_dataframe = pd.DataFrame( {'features': cols,
     'Random Forest feature importances': rf_feature,
     'Extra Trees  feature importances': et_feature,
      'AdaBoost feature importances': ada_feature,
    'Gradient Boost feature importances': gb_feature
    })

交互式散点图绘制

使用交互式Plotly包,通过调用“Scatter”,通过绘图散点图可视化不同分类器的要素重要性值,如下所示:

# Scatter plot 
trace = go.Scatter(
    y = feature_dataframe['Random Forest feature importances'].values,
    x = feature_dataframe['features'].values,
    mode='markers',
    marker=dict(
        sizemode = 'diameter',
        sizeref = 1,
        size = 25,
#       size= feature_dataframe['AdaBoost feature importances'].values,
        #color = np.random.randn(500), #set color equal to a variable
        color = feature_dataframe['Random Forest feature importances'].values,
        colorscale='Portland',
        showscale=True
    ),
    text = feature_dataframe['features'].values
)
data = [trace]

layout= go.Layout(
    autosize= True,
    title= 'Random Forest Feature Importance',
    hovermode= 'closest',
#     xaxis= dict(
#         title= 'Pop',
#         ticklen= 5,
#         zeroline= False,
#         gridwidth= 2,
#     ),
    yaxis=dict(
        title= 'Feature Importance',
        ticklen= 5,
        gridwidth= 2
    ),
    showlegend= False
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig,filename='scatter2010')

# Scatter plot 
trace = go.Scatter(
    y = feature_dataframe['Extra Trees  feature importances'].values,
    x = feature_dataframe['features'].values,
    mode='markers',
    marker=dict(
        sizemode = 'diameter',
        sizeref = 1,
        size = 25,
#       size= feature_dataframe['AdaBoost feature importances'].values,
        #color = np.random.randn(500), #set color equal to a variable
        color = feature_dataframe['Extra Trees  feature importances'].values,
        colorscale='Portland',
        showscale=True
    ),
    text = feature_dataframe['features'].values
)
data = [trace]

layout= go.Layout(
    autosize= True,
    title= 'Extra Trees Feature Importance',
    hovermode= 'closest',
#     xaxis= dict(
#         title= 'Pop',
#         ticklen= 5,
#         zeroline= False,
#         gridwidth= 2,
#     ),
    yaxis=dict(
        title= 'Feature Importance',
        ticklen= 5,
        gridwidth= 2
    ),
    showlegend= False
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig,filename='scatter2010')

# Scatter plot 
trace = go.Scatter(
    y = feature_dataframe['AdaBoost feature importances'].values,
    x = feature_dataframe['features'].values,
    mode='markers',
    marker=dict(
        sizemode = 'diameter',
        sizeref = 1,
        size = 25,
#       size= feature_dataframe['AdaBoost feature importances'].values,
        #color = np.random.randn(500), #set color equal to a variable
        color = feature_dataframe['AdaBoost feature importances'].values,
        colorscale='Portland',
        showscale=True
    ),
    text = feature_dataframe['features'].values
)
data = [trace]

layout= go.Layout(
    autosize= True,
    title= 'AdaBoost Feature Importance',
    hovermode= 'closest',
#     xaxis= dict(
#         title= 'Pop',
#         ticklen= 5,
#         zeroline= False,
#         gridwidth= 2,
#     ),
    yaxis=dict(
        title= 'Feature Importance',
        ticklen= 5,
        gridwidth= 2
    ),
    showlegend= False
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig,filename='scatter2010')

# Scatter plot 
trace = go.Scatter(
    y = feature_dataframe['Gradient Boost feature importances'].values,
    x = feature_dataframe['features'].values,
    mode='markers',
    marker=dict(
        sizemode = 'diameter',
        sizeref = 1,
        size = 25,
#       size= feature_dataframe['AdaBoost feature importances'].values,
        #color = np.random.randn(500), #set color equal to a variable
        color = feature_dataframe['Gradient Boost feature importances'].values,
        colorscale='Portland',
        showscale=True
    ),
    text = feature_dataframe['features'].values
)
data = [trace]

layout= go.Layout(
    autosize= True,
    title= 'Gradient Boosting Feature Importance',
    hovermode= 'closest',
#     xaxis= dict(
#         title= 'Pop',
#         ticklen= 5,
#         zeroline= False,
#         gridwidth= 2,
#     ),
    yaxis=dict(
        title= 'Feature Importance',
        ticklen= 5,
        gridwidth= 2
    ),
    showlegend= False
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig,filename='scatter2010')

现在让我们计算所有要素重要性的平均值,并将其存储为要素重要性数据框中的新列。

# Create the new column containing the average of values

feature_dataframe['mean'] = feature_dataframe.mean(axis= 1) # axis = 1 computes the mean row-wise
feature_dataframe.head(3)

**平均特征重要性的Plotly Barplot **

获得所有分类器的平均特征重要性后,我们可以将它们绘制成Plotly条形图,如下所示:

y = feature_dataframe['mean'].values
x = feature_dataframe['features'].values
data = [go.Bar(
            x= x,
             y= y,
            width = 0.5,
            marker=dict(
               color = feature_dataframe['mean'].values,
            colorscale='Portland',
            showscale=True,
            reversescale = False
            ),
            opacity=0.6
        )]

layout= go.Layout(
    autosize= True,
    title= 'Barplots of Mean Feature Importance',
    hovermode= 'closest',
#     xaxis= dict(
#         title= 'Pop',
#         ticklen= 5,
#         zeroline= False,
#         gridwidth= 2,
#     ),
    yaxis=dict(
        title= 'Feature Importance',
        ticklen= 5,
        gridwidth= 2
    ),
    showlegend= False
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='bar-direct-labels')

#第一级输出的二级预测

作为新功能的第一级输出

现在已经获得了我们的第一级预测,可以将其视为一组数据,用作下一个分类器的训练数据。 根据下面的代码,我们创建新列作为我们之前的分类器中的第一级预测,并在此基础上训练下一个分类器。

base_predictions_train = pd.DataFrame( {'RandomForest': rf_oof_train.ravel(),
     'ExtraTrees': et_oof_train.ravel(),
     'AdaBoost': ada_oof_train.ravel(),
      'GradientBoost': gb_oof_train.ravel()
    })
base_predictions_train.head()

二级训练集的相关热图

data = [
    go.Heatmap(
        z= base_predictions_train.astype(float).corr().values ,
        x=base_predictions_train.columns.values,
        y= base_predictions_train.columns.values,
          colorscale='Viridis',
            showscale=True,
            reversescale = True
    )
]
py.iplot(data, filename='labelled-heatmap')

已经有不少文章和Kaggle比赛获奖者关于训练模型的优点,这些模型彼此之间更不相关,产生更好的分数。

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)

现在已经连接并加入了第一级输出和测试预测x_train和x_test,我们现在可以训练二级学习模型。

通过XGBoost的二级学习模型

在这里,我们选择了极其着名的XGBoost,用于提升树木学习模型,XGBoost。 它的构建是为了优化大规模的提升树算法。 有关该算法的更多信息,请查看[官方文档] [1]。
[1]:https://xgboost.readthedocs.io/en/latest/
无论如何,我们称之为XGBC分类器并将其与第一级训练和目标数据相匹配,并使用学习模型预测测试数据,如下所示:

gbm = xgb.XGBClassifier(
    #learning_rate = 0.02,
 n_estimators= 2000,
 max_depth= 4,
 min_child_weight= 2,
 #gamma=1,
 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)

只需快速了解模型中使用的XGBoost参数:
** max_depth **:你想要树的深度。 请注意,如果设置得太高,则数字可能存在过度拟合的风险。
** gamma **:在树的叶节点上进行进一步分区所需的最小损失减少量。 算法越大,越保守。
** eta **:每个增压步骤中使用的步长收缩,以防止过度拟合

制作提交文件
最后,我们已经训练并适应了所有的一级和二级模型,现在我们可以将预测输出到适当的格式,以便提交给泰坦尼克号竞赛如下:

# Generate Submission File 
StackingSubmission = pd.DataFrame({ 'PassengerId': PassengerId,
                            'Survived': predictions })
StackingSubmission.to_csv("StackingSubmission.csv", index=False)

进一步改进的步骤**

作为结束语,必须注意的是,上面采取的步骤只是展示了一种生成整体Stacking机的非常简单的方法。 你会听到在最高级别的Kaggle比赛中创造的Ensembling,其中涉及Stacking分类器的怪异组合以及达到2级以上的Stacking等级。

可以采取一些额外步骤来提高一个人的分数:

1.在训练模型时实施良好的交叉验证策略以找到最佳参数值
2.引入更多种类的学习基础模型。 结果越不相关,最终得分越高。

结论

这个笔记本介绍的关于Stacking学习模型的工作脚本,对您有所帮助。

有关Stacking或整理的其他优秀材料,请参阅MLWave网站上的事实必读文章:[Kaggle Ensembling Guide] [1]。

直到下一次,Peace Out
[1]:http://mlwave.com/kaggle-ensembling-guide/

你可能感兴趣的:(模型融合 - 第二课)