美文网首页
Kaggle Titanic-2

Kaggle Titanic-2

作者: LY豪 | 来源:发表于2019-03-24 21:06 被阅读0次

    之前有写过一篇关于Titanic比赛的简书,这几天上kaggle-Titanic的kernels在MostVost找了一篇排第一的kernels来看,参考链接,这个Kernels在模型方面做得特别好,所以,另写一篇简书作为总结。

    流程

    1.观察数据,我们要对数据有所了解,可以参考我的简书
    2.特征工程以及数据清洗
    3.跑模型

    代码分析

    首先,导入我们需要用到的库

    import pandas as pd
    import numpy as np
    from sklearn.cross_validation import KFold
    import re
    import plotly.graph_objs as go
    import plotly.offline as py
    from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier,
                                  GradientBoostingClassifier, ExtraTreesClassifier)
    from sklearn.svm import SVC
    import xgboost as xgb
    import warnings
    warnings.filterwarnings('ignore')  # 忽略warning
    pd.set_option('display.max_columns', None)  # 输出结果显示全部列
    

    然后,导入数据

    train = pd.read_csv('train.csv')
    test = pd.read_csv('test.csv')
    PassengerId = test['PassengerId']
    full_data = [train, test]
    

    接下来,我们可以查看我们的数据

    # 查看train集的数据
    print(train.describe())  # 查看描述性统计,只能看数值型数据。
    print(train.info())  # 查看数据的信息
    # print(train.head())  # 查看train的前n行数据,默认为前5行
    

    从图上我们可以看到,其中有5列不是数值型的,我们需要对其进行转换成数值,而且Age、Cabin这两列是有缺失值的,我们要对其进行填充或者丢弃。

    特征工程以及数据清洗

    添加一些新的特征

    # 添加新的特征,名字的长度
    train['Name_length'] = train['Name'].apply(len)
    test['Name_length'] = test['Name'].apply(len)
    
    # 乘客在船上是否有船舱
    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)
    
    # 结合SibSp和Parch创建新的特性FamilySize
    for dataset in full_data:
        dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
    

    基于特征FamilySize创建新的特征IsAlone,因为一个人的话,顾虑没有那么多,只需要管好自己,生存的几率会大点,其中又分‘male’和‘female’,因为我记得电影中是有这样的一句台词“让女人和小孩先走”,所以,我们有理由相信,女性的生存率会比男性的要高。

    for dataset in full_data:
        dataset['IsAlone'] = 0
        dataset.loc[(dataset['FamilySize'] == 1) & (dataset['Sex'] == 'male'), 'IsAlone'] = 1
        dataset.loc[(dataset['FamilySize'] == 1) & (dataset['Sex'] == 'female'), 'IsAlone'] = 2
    

    通过name,添加特征Title

    # 定义从乘客名中提取新的特征[Title]的函数
    def get_title(name):
        title_search = re.search(' ([A-Za-z]+)\.', name)
        # 如果title存在,提取并返回它。
        if title_search:
            return title_search.group(1)
        return ""
    
    
    # 创建一个新的特征[Title]
    for dataset in full_data:
        dataset['Title'] = dataset['Name'].apply(get_title)
    # 将所有不常见的Title分组为一个“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')
    

    缺失值填充

    1. Embarked只缺了两个,所以通过统计三个登船地点,选出了登船人数最多的登船地点(s)来填充。
    2. Test集的Fare只有一个缺失,所以用了中位数来填充
    3. Age缺失的比较多,所以在[age_avg - age_std, age_avg + age_std]这个范围取值来填充(其中age_avg是Age的平均值,age_std是Age的标准差)
    # 通过统计三个登船地点人数最多的填充缺失值
    for dataset in full_data:
        dataset['Embarked'] = dataset['Embarked'].fillna('S')
    
    # 缺失值填充,Test集的Fare有一个缺失,按中位数来填充,以及创建一个新的特征[CategoricalFare]
    for dataset in full_data:
        dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
    train['CategoricalFare'] = pd.qcut(train['Fare'], 4)
    
    # 缺失值填充,以及创建新的特征[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)
    

    通过Age,创建新的特征,一会用来给Age分组

    train['CategoricalAge'] = pd.cut(train['Age'], 5)
    print(train['CategoricalAge'])
    

    从图片可以看出,年龄分为了5个范围,所以一会把年龄分为5组(0-4)。

    分组以及转换数值

    Sex:把性别转为0和1.
    Embarked:把登船地点转为0、1、2.
    Fare:把费用分为4组
    Age:把年龄分为5组

    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
    
    特征选择,丢弃一些不必要的特征
    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)
    # print(train.head())
    print(train.describe())
    # print(train.head())
    

    跑模型

    这部分是这个kernels的重点,用的是Stacking。Stacking使用第一级分类器的预测作为对第二级模型的训练输入。我们使用了(RandomForestClassifier, AdaBoostClassifier,GradientBoostingClassifier, ExtraTreesClassifier,Support Vector Classifier)这5个分类器的预测作为下一个分类器(xgboost)的特征。

    在下面的代码中,我们编写了一个类SklearnHelper,它允许扩展所有Sklearn分类器所共有的内置方法(如train、predict和fit)。这消除了冗余,因为如果我们想调用5个不同的分类器,就不需要编写相同的方法5次。

    # 一些有用的参数,下面会用到
    ntrain = train.shape[0]
    ntest = test.shape[0]
    SEED = 0
    NFOLDS = 5
    kf = KFold(ntrain, n_folds=NFOLDS, random_state=SEED)
    
    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_importances(self, x, y):
            return self.clf.fit(x, y).feature_importances_
    
    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):
            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)
    

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

    1.Random Forest classifier
    2.Extra Trees classifier
    3.AdaBoost classifer
    4.Gradient Boosting classifer
    5.Support Vector Machine

    输入上述分类器的参数

    # 随机森林的参数
    rf_params = {
        'n_jobs': -1,
        'n_estimators': 100,
         'warm_start': True,
         #'max_features': 0.2,
        'max_depth': 6,
        'min_samples_leaf': 2,
        'max_features': 'sqrt',
        'verbose': 0
    }
    
    # Extra Trees的参数
    et_params = {
        'n_jobs': -1,
        'n_estimators': 100,
        #'max_features': 0.5,
        'max_depth': 8,
        'min_samples_leaf': 2,
        'verbose': 0
    }
    
    # AdaBoost的参数
    ada_params = {
        'n_estimators': 100,
        'learning_rate': 0.01
    }
    
    # Gradient Boosting的参数
    gb_params = {
        'n_estimators': 100,
         #'max_features': 0.2,
        'max_depth': 5,
        'min_samples_leaf': 2,
        'verbose': 0
    }
    
    # Support Vector Classifier的参数
    svc_params = {
        'kernel': 'linear',
        'C': 0.025
    }
    
    第一级分类器
    # 通过前面定义的SklearnHelper类创建5个对象来表示5个学习模型
    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)
    # 创建包含train、test的Numpy数组,以提供给我们的模型
    y_train = train['Survived'].ravel()
    train = train.drop(['Survived'], axis=1)
    x_train = train.values
    # test = test.drop(['Parch', 'Embarked', 'Has_Cabin', 'IsAlone'], axis=1)
    x_test = test.values
    
    #这些将会作为新的特征被使用
    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
    

    现在已经获得了我们的第一级预测,我们可以把它看作是一组新的特性,作为下一个分类器的训练数据。

    查看各个特征对上述分类器的重要性

    rf_features = rf.feature_importances(x_train, y_train)
    et_features = et.feature_importances(x_train, y_train)
    ada_features = ada.feature_importances(x_train, y_train)
    gb_features = gb.feature_importances(x_train, y_train)
    
    cols = train.columns.values
    feature_dataframe = pd.DataFrame({'features': cols,
         'Random Forest feature importances': rf_features,
         'Extra Trees  feature importances': et_features,
          'AdaBoost feature importances': ada_features,
        'Gradient Boost feature importances': gb_features})
    
    feature_dataframe['mean'] = feature_dataframe.mean(axis=1)  # axis = 1 computes the mean row-wise
    print(feature_dataframe.head(11))
    

    画图查看各个分类器的相关性

    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()
        })
    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')
    

    ,这些模型彼此之间的相关性越低,得分越高。

    第二级分类器xgboost

    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(
     #learning_rate=0.01,
     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)
    

    提交

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

    提交后的分数,排名如下

    总结

    相比于其他的kernels,这个kernels的特征工程方面做的不突出(但还是比我之前的简书好很多,哈哈哈),突出的方面是用了新的方法Stacking,这个其他人在Titanic比赛中没有用到过的,这也是他排第一的原因。

    进一步改善的步骤
    必须指出的是,上述步骤只是显示了一个非常简单的方法。听说过在Kaggle的最高级别比赛中创建的组合,其中包括stacked classifiers的巨大组合,以及超过2级的stacking级别。

    相关文章

      网友评论

          本文标题:Kaggle Titanic-2

          本文链接:https://www.haomeiwen.com/subject/jxauvqtx.html