美文网首页
实战1-Kaggle-Tatanic

实战1-Kaggle-Tatanic

作者: yz_wang | 来源:发表于2017-02-10 16:17 被阅读0次

    泰坦尼克生存预测问题是机器学习入门的经典案例,通过分析已知训练集的乘客信息和生存结果,对预测集中的乘客做出预测。简单、教程多,是个初学者练手的好项目。

    1. 数据可视化

    #coding:utf-8
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    import statsmodels.api as sm
    from statsmodels.nonparametric.kde import KDEUnivariate
    from statsmodels.nonparametric import smoothers_lowess
    from pandas import DataFrame
    
    df = pd.read_csv("data/train.csv")
    df = df.drop(['Ticket','Cabin'], axis=1)
    
    # 指定图的参数
    fig = plt.figure(figsize=(18,6), dpi=100)
    a = 0.65
    # Step 1
    ax1 =fig.add_subplot(341)
    df.Survived.value_counts().plot(kind='bar', color="blue", alpha=a)
    ax1.set_xlim(-1, len(df.Survived.value_counts()))
    plt.title("Step. 1")
    
    ax2 = fig.add_subplot(345)
    df.Survived[df.Sex == 'male'].value_counts(sort=False).plot(kind='bar',label='Male')
    df.Survived[df.Sex == 'female'].value_counts(sort=False).plot(kind='bar', color='#FA2379',label='Female')
    ax2.set_xlim(-1, 2)
    plt.title("Step. 2 \nWho Survied? with respect to Gender."); plt.legend(loc='best')
    
    ax3 = fig.add_subplot(346)
    (df.Survived[df.Sex == 'male'].value_counts(sort=False)/float(df.Sex[df.Sex == 'male'].size)).plot(kind='bar',label='Male')
    (df.Survived[df.Sex == 'female'].value_counts(sort=False)/float(df.Sex[df.Sex == 'female'].size)).plot(kind='bar', color='#FA2379',label='Female')
    ax3.set_xlim(-1,2)
    plt.title("Who Survied proportionally?"); plt.legend(loc='best')
    
    
    # Step 3
    ax4 = fig.add_subplot(349)
    female_highclass = df.Survived[df.Sex == 'female'][df.Pclass != 3].value_counts()
    female_highclass.plot(kind='bar', label='female highclass', color='#FA2479', alpha=a)
    ax4.set_xticklabels(["Survived", "Died"], rotation=0)
    ax4.set_xlim(-1, len(female_highclass))
    plt.title("Who Survived? with respect to Gender and Class"); plt.legend(loc='best')
    
    ax5 = fig.add_subplot(3,4,10, sharey=ax1)
    female_lowclass = df.Survived[df.Sex == 'female'][df.Pclass == 3].value_counts()
    female_lowclass.plot(kind='bar', label='female, low class', color='pink', alpha=a)
    ax5.set_xticklabels(["Died","Survived"], rotation=0)
    ax5.set_xlim(-1, len(female_lowclass))
    plt.legend(loc='best')
    
    ax6 = fig.add_subplot(3,4,11, sharey=ax1)
    male_lowclass = df.Survived[df.Sex == 'male'][df.Pclass == 3].value_counts()
    male_lowclass.plot(kind='bar', label='male, low class',color='lightblue', alpha=a)
    ax6.set_xticklabels(["Died","Survived"], rotation=0)
    ax6.set_xlim(-1, len(male_lowclass))
    plt.legend(loc='best')
    
    ax7 = fig.add_subplot(3,4,12, sharey=ax1)
    male_highclass = df.Survived[df.Sex == 'male'][df.Pclass != 3].value_counts()
    male_highclass.plot(kind='bar', label='male highclass', alpha=a, color='steelblue')
    ax7.set_xticklabels(["Died","Survived"], rotation=0)
    ax7.set_xlim(-1, len(male_highclass))
    plt.legend(loc='best')
    
    plt.show()
    

    关键语句解析:

    • 图片格式:bar, kde, barh
    • set_xticklables, setxlim

    </br></br></br>

    2. 清理数据

    原始信息如下:

    可以用data.info()获取整体信息:

    2.1 补全缺少的信息(年龄、Cabin)
    f = pd.read_csv("data/train.csv")
    
    ### 使用 RandomForestClassifier 填补缺失的年龄属性
    def set_missing_ages(df):
    
        # 把已有的数值型特征取出来丢进Random Forest Regressor中
        age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
    
        # 乘客分成已知年龄和未知年龄两部分
        known_age = age_df[age_df.Age.notnull()].as_matrix()
        unknown_age = age_df[age_df.Age.isnull()].as_matrix()
    
        # y即目标年龄
        y = known_age[:, 0]
    
        # X即特征属性值
        X = known_age[:, 1:]
    
        # fit到RandomForestRegressor之中
        rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
        rfr.fit(X, y)
    
        # 用得到的模型进行未知年龄结果预测
        predictedAges = rfr.predict(unknown_age[:, 1::])
    
        # 用得到的预测结果填补原缺失数据
        df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges
    
        return df, rfr
    
    
    
    def set_Cabin_type(df):
        df.loc[ (df.Cabin.notnull()), 'Cabin' ] = "Yes"
        df.loc[ (df.Cabin.isnull()), 'Cabin' ] = "No"
        return df
    
    data_train, rfr = set_missing_ages(df)
    data_train = set_Cabin_type(data_train)
    
    

    通常遇到缺值的情况,我们会有几种常见的处理方式

    • 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了
    • 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中
    • 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。
    • 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。

    Age我们尝试拟合补全,用scikit-learn中的RandomForest来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DecisionTree,再进行average等等来降低过拟合现象,提高结果的机器学习算法)
    按Cabin有无数据,将这个属性处理成Yes和No两种类型。

    2.2 转换为0、1
    dummies_Cabin=pd.get_dummies(data_train['Cabin'],prefix='Cabin')
    dummies_Embarked=pd.get_dummies(data_train['Embarked'],prefix='Embarked')
    dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')
    dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')
    df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
    df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
    

    因子化:以Cabin为例,原本一个属性维度,因为其取值可以是[‘yes’,’no’],而将其平展开为’Cabin_yes’,’Cabin_no’两个属性

    • 原本Cabin取值为yes的,在此处的”Cabin_yes”下取值为1,在”Cabin_no”下取值为0
    • 原本Cabin取值为no的,在此处的”Cabin_yes”下取值为0,在”Cabin_no”下取值为1
    2.3 缩小某些参数的波动尺度
    #scalling age and fare
    scaler=preprocessing.StandardScaler()
    age_scale_param=scaler.fit(df['Age'])
    df['Age_scaled']=scaler.fit_transform(df['Age'],age_scale_param)
    fare_scale_param = scaler.fit(df['Fare'])
    df['Fare_scaled'] = scaler.fit_transform(df['Fare'], fare_scale_param)
    

    2.4 相同方法处理测试集

    data_test = pd.read_csv("data/test.csv")
    data_test.loc[ (data_test.Fare.isnull()), 'Fare' ] = 0
    # 接着我们对test_data做和train_data中一致的特征变换
    # 首先用同样的RandomForestRegressor模型填上丢失的年龄
    tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
    null_age = tmp_df[data_test.Age.isnull()].as_matrix()
    # 根据特征属性X预测年龄并补上
    X = null_age[:, 1:]
    predictedAges = rfr.predict(X)
    data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges
    
    data_test = set_Cabin_type(data_test)
    dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
    dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
    dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
    dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')
    
    
    df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
    df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
    df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param)
    df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param)
    

    </br>

    3. 逻辑回归建模

    #logistec regression model build
    # 用正则取出我们要的属性值
    train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    train_np = train_df.as_matrix()
    
    # y即Survival结果
    y = train_np[:, 0]
    
    # X即特征属性值
    X = train_np[:, 1:]
    
    
    # fit到RandomForestRegressor之中
    clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
    clf.fit(X, y)
    
    

    </br>

    4. 预测结果并输出

    # 预测结果
    test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    predictions = clf.predict(test)
    result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
    result.to_csv("data/logistic_regression_predictions.csv", index=False)
    

    得到的结果:

    </br>

    5. 算法优化

    print pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})
    

    得到生存结果和各个变量之间的相关关系:


    交叉验证:

    # Cross Validation
    clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) #分类器
    all_data = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    X = all_data.as_matrix()[:,1:]
    y = all_data.as_matrix()[:,0]
    print cross_validation.cross_val_score(clf, X, y, cv=5)  #cv参数代表不同的交叉验证分类方法
    
    
    #数据分割,看看bad case
    split_train, split_cv = cross_validation.train_test_split(df, test_size=0.3, random_state=0)
    train_df =split_train.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    # 生成模型
    clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
    clf.fit(train_df.as_matrix()[:,1:], train_df.as_matrix()[:,0])
    # 对cross validation数据进行预测
    cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    predictions = clf.predict(cv_df.as_matrix()[:,1:])
    origin_data_train = pd.read_csv("data/train.csv")
    bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)]
    
    print bad_cases
    

    现在有了”train_df” 和 “vc_df” 两个数据部分,前者用于训练model,后者用于评定和选择模型。

    我们随便列一些可能可以做的优化操作:

    • Age属性不使用现在的拟合方式,而是根据名称中的『Mr』『Mrs』『Miss』等的平均值进行填充。
    • Age不做成一个连续值属性,而是使用一个步长进行离散化,变成离散的类目feature。
    • Cabin再细化一些,对于有记录的Cabin属性,我们将其分为前面的字母部分(我猜是位置和船层之类的信息) 和 后面的数字部分(应该是房间号,有意思的事情是,如果你仔细看看原始数据,你会发现,这个值大的情况下,似乎获救的可能性高一些)。
    • Pclass和Sex俩太重要了,我们试着用它们去组出一个组合属性来试试,这也是另外一种程度的细化。
    • 单加一个Child字段,Age<=12的,设为1,其余为0(你去看看数据,确实小盆友优先程度很高啊)
    • 如果名字里面有『Mrs』,而Parch>1的,我们猜测她可能是一个母亲,应该获救的概率也会提高,因此可以多加一个Mother字段,此种情况下设为1,其余情况下设为0
    • 登船港口可以考虑先去掉试试(Q和C本来就没权重,S有点诡异)
    • 把堂兄弟/兄妹 和 Parch 还有自己 个数加在一起组一个Family_size字段(考虑到大家族可能对最后的结果有影响)
    • Name是一个我们一直没有触碰的属性,我们可以做一些简单的处理,比如说男性中带某些字眼的(‘Capt’, ‘Don’, ‘Major’, ‘Sir’)可以统一到一个Title,女性也一样。

    ** learning curves**
    有一个很可能发生的问题是,我们不断地做feature engineering,产生的特征越来越多,用这些特征去训练模型,会对我们的训练集拟合得越来越好,同时也可能在逐步丧失泛化能力,从而在待预测的数据上,表现不佳,也就是发生过拟合问题。

    对过拟合而言,通常以下策略对结果优化是有用的:

    • 做一下feature selection,挑出较好的feature的subset来做training
    • 提供更多的数据,从而弥补原始数据的bias问题,学习到的model也会更准确

    从另一个角度上说,如果模型在待预测的数据上表现不佳,除掉上面说的过拟合问题,也有可能是欠拟合问题,也就是说在训练集上,其实拟合的也不是那么好。

    • 而对于欠拟合而言,我们通常需要更多的feature,更复杂的模型来提高准确度。

    我们也可以把错误率替换成准确率(得分),得到另一种形式的learning curve(sklearn 里面是这么做的)。

    #coding:utf-8
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.learning_curve import learning_curve
    
    
    # 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib画出learning curve
    def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1,
                            train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True):
        """
        画出data在某模型上的learning curve.
        参数解释
        ----------
        estimator : 你用的分类器。
        title : 表格的标题。
        X : 输入的feature,numpy类型
        y : 输入的target vector
        ylim : tuple格式的(ymin, ymax), 设定图像中纵坐标的最低点和最高点
        cv : 做cross-validation的时候,数据分成的份数,其中一份作为cv集,其余n-1份作为training(默认为3份)
        n_jobs : 并行的的任务数(默认1)
        """
    
        train_sizes, train_scores, test_scores = learning_curve(
            estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose)
    
        train_scores_mean = np.mean(train_scores, axis=1)
        train_scores_std = np.std(train_scores, axis=1)
        test_scores_mean = np.mean(test_scores, axis=1)
        test_scores_std = np.std(test_scores, axis=1)
    
        if plot:
            plt.figure()
            plt.title(title)
            if ylim is not None:
                plt.ylim(*ylim)
            plt.xlabel(u"训练样本数")
            plt.ylabel(u"得分")
            plt.gca().invert_yaxis()
            plt.grid()
    
            plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std,
                             alpha=0.1, color="b")
            plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std,
                             alpha=0.1, color="r")
            plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分")
            plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分")
    
            plt.legend(loc="best")
    
            plt.draw()
            plt.show()
            plt.gca().invert_yaxis()
    
        midpoint = ((train_scores_mean[-1] + train_scores_std[-1]) + (test_scores_mean[-1] - test_scores_std[-1])) / 2
        diff = (train_scores_mean[-1] + train_scores_std[-1]) - (test_scores_mean[-1] - test_scores_std[-1])
    
        return midpoint, diff
    
    

    这个模块用于画出基于测试集和训练集中的评分得到的曲线,用来判断是否还需要更多特征。在trainning.py中调用:
    learningCurve.plot_learning_curve(clf, u"学习曲线", X, y)

    目前的曲线看来,我们的model并不处于overfitting的状态(overfitting的表现一般是训练集上得分高,而交叉验证集上要低很多,中间的gap比较大)。因此我们可以再做些feature engineering的工作,添加一些新产出的特征或者组合特征到模型中。

    </br></br>

    6. 模型融合(model ensemble)

    当我们手头上有一堆在同一份数据集上训练得到的分类器(比如logistic regression,SVM,KNN,random forest,神经网络),那我们让他们都分别去做判定,然后对结果做投票统计,取票数最多的结果为最后结果。
    模型融合可以比较好地缓解,训练过程中产生的过拟合问题,从而对于结果的准确度提升有一定的帮助。
    解决过拟合问题还可以用bagging,sklearn里也有这个方法,具体就是每次训练去整个训练集的一个子集,这样每次的过拟合都是针对子集的过拟合,整合起来就会减轻过拟合问题。

    相关文章

      网友评论

          本文标题:实战1-Kaggle-Tatanic

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