zoukankan      html  css  js  c++  java
  • 逻辑回归应用之Kaggle泰坦尼克之灾(转)

     正文:14pt

    代码:15px

    1 初探数据

    先看看我们的数据,长什么样吧。在Data下我们train.csv和test.csv两个文件,分别存着官方给的训练和测试数据。

    1 import pandas as pd #数据分析
    2 import numpy as np #科学计算
    3 from pandas import Series,DataFrame
    4 
    5 data_train = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/Train.csv")
    6 data_train

    pandas是常用的python数据处理包,把csv文件读入成dataframe各式,我们可以看到data_train如下所示:

    这就是典型的dataframe格式,如果你没接触过这种格式,完全没有关系,你就把它想象成Excel里面的列好了。 
    我们看到,总共有12列,其中Survived字段表示的是该乘客是否获救,其余都是乘客的个人信息,包括:

    • PassengerId => 乘客ID
    • Pclass => 乘客等级(1/2/3等舱位)
    • Name => 乘客姓名
    • Sex => 性别
    • Age => 年龄
    • SibSp => 堂兄弟/妹个数
    • Parch => 父母与小孩个数
    • Ticket => 船票信息
    • Fare => 票价
    • Cabin => 客舱
    • Embarked => 登船港口

    逐条往下看,要看完这么多条,眼睛都有一种要瞎的赶脚。好吧,我们让dataframe自己告诉我们一些信息,如下所示:

    1 data_train.info()

    看到了如下的信息:

    上面的数据说啥了?它告诉我们,训练数据中总共有891名乘客,但是很不幸,我们有些属性的数据不全,比如说:

    • Age(年龄)属性只有714名乘客有记录
    • Cabin(客舱)更是只有204名乘客是已知的

    似乎信息略少啊,想再瞄一眼具体数据数值情况呢?恩,我们用下列的方法,得到数值型数据的一些分布(因为有些属性,比如姓名,是文本型;而另外一些属性,比如登船港口,是类目型。这些我们用下面的函数是看不到的):

    我们从上面看到更进一步的什么信息呢? 
    mean字段告诉我们,大概0.383838的人最后获救了,2/3等舱的人数比1等舱要多,平均乘客年龄大概是29.7岁(计算这个时候会略掉无记录的)等等…

    2数据初步分析

    每个乘客都这么多属性,那我们咋知道哪些属性更有用,而又应该怎么用它们啊?说实话这会儿我也不知道,但我们记得前面提到过

    • 『对数据的认识太重要了!』
    • 『对数据的认识太重要了!』
    • 『对数据的认识太重要了!』

    重要的事情说三遍,恩,说完了。仅仅最上面的对数据了解,依旧无法给我们提供想法和思路。我们再深入一点来看看我们的数据,看看每个/多个 属性和最后的Survived之间有着什么样的关系呢。

    2.1 乘客各属性分布

    脑容量太有限了…数值看花眼了。我们还是统计统计,画些图来看看属性和结果之间的关系好了,代码如下:

     1 #乘客各属性分布
     2 import matplotlib.pyplot as plt
     3 fig=plt.figure(figsize(14,10))
     4 fig.set(alpha=0.2) #设定图标颜色参数
     5 
     6 plt.subplot2grid((2,3),(0,0))             # 在一张大图里分列几个小图
     7 data_train.Survived.value_counts().plot(kind='bar')# 柱状图 
     8 plt.title(u"获救情况 (1为获救)",size=15) # 标题
     9 plt.ylabel(u"人数",size=15)  
    10 plt.grid(b=True)
    11 
    12 plt.subplot2grid((2,3),(0,1))
    13 data_train.Pclass.value_counts().plot(kind="bar")
    14 plt.ylabel(u"人数",size=15)
    15 plt.title(u"乘客等级分布",size=15)
    16 plt.grid(b=True)
    17 
    18 plt.subplot2grid((2,3),(0,2))
    19 plt.scatter(data_train.Survived, data_train.Age)
    20 plt.ylabel(u"年龄",size=15)                         # 设定纵坐标名称
    21 plt.grid(b=True, which='major', axis='y') 
    22 plt.title(u"按年龄看获救分布 (1为获救)",size=15)
    23 plt.grid(b=True)
    24 
    25 plt.subplot2grid((2,3),(1,0),colspan=2)
    26 data_train.Age[data_train.Pclass==1].plot(kind='kde')
    27 data_train.Age[data_train.Pclass==2].plot(kind='kde')
    28 data_train.Age[data_train.Pclass==3].plot(kind='kde')
    29 plt.xlabel(u'年龄',size=15)
    30 plt.ylabel(u'密度',size=15)
    31 plt.title(u'各等级的乘客年龄分布',size=15)
    32 plt.legend((u'1等舱',u'2等舱',u'3等舱'),loc='best')
    33 
    34 plt.subplot2grid((2,3),(1,2))
    35 data_train.Embarked.value_counts().plot(kind='bar')
    36 plt.ylabel(u'人数',size=15)
    37 plt.title(u'各登船口岸上船人数',size=15)
    38 plt.grid(b=True)

    bingo,图还是比数字好看多了。所以我们在图上可以看出来,被救的人300多点,不到半数;3等舱乘客灰常多;遇难和获救的人年龄似乎跨度都很广;3个不同的舱年龄总体趋势似乎也一致,2/3等舱乘客20岁多点的人最多,1等舱40岁左右的最多(→_→似乎符合财富和年龄的分配哈,咳咳,别理我,我瞎扯的);登船港口人数按照S、C、Q递减,而且S远多于另外俩港口。

    这个时候我们可能会有一些想法了:

    • 不同舱位/乘客等级可能和财富/地位有关系,最后获救概率可能会不一样
    • 年龄对获救概率也一定是有影响的,毕竟前面说了,副船长还说『小孩和女士先走』呢
    • 和登船港口是不是有关系呢?也许登船港口不同,人的出身地位不同?

    口说无凭,空想无益。老老实实再来统计统计,看看这些属性值的统计分布吧。

    6.2 属性与获救结果的关联统计

     1 #属性与获救结果的关联统计
     2 fig=plt.figure(figsize(6,4))
     3 fig.set(alpha=0.2)  # 设定图表颜色alpha参数
     4 
     5 Survived_0=data_train.Pclass[data_train.Survived==0].value_counts()
     6 Survived_1=data_train.Pclass[data_train.Survived==1].value_counts()
     7 df=pd.DataFrame({u'获救':Survived_1,u'未获救':Survived_0})
     8 df.plot(kind='bar',stacked=True)
     9 
    10 
    11 plt.xlabel(u'乘客等级',size=15)
    12 plt.ylabel(u'人数',size=15)
    13 plt.title(u'各等级的乘客获救情况',size=15)
    14 plt.legend((u'未获救',u'获救'),loc='best')
    15 plt.grid(b=True)

    啧啧,果然,钱和地位对舱位有影响,进而对获救的可能性也有影响啊←_← 
    咳咳,跑题了,我想说的是,明显等级为1的乘客,获救的概率高很多。恩,这个一定是影响最后获救结果的一个特征。

     1 #查看各个性别的获救情况
     2 fig=plt.figure(figsize(6,4))
     3 fig.set(alpha=0.2)  # 设定图表颜色alpha参数
     4 
     5 Survived_0=data_train.Sex[data_train.Survived==0].value_counts()
     6 Survived_1=data_train.Sex[data_train.Survived==1].value_counts()
     7 df=pd.DataFrame({u'未获救':Survived_0,u'获救':Survived_1})
     8 df=df.transpose()
     9 df.plot(kind='bar',stacked=True)
    10 
    11 plt.xlabel(u'性别',size=15)
    12 plt.ylabel(u'人数',size=15)
    13 plt.title(u'按性别看获救情况',size=15)
    14 plt.legend((u'女性',u'男性'),loc='best')
    15 plt.grid(b=True)

    歪果盆友果然很尊重lady,lady first践行得不错。性别无疑也要作为重要特征加入最后的模型之中。

    再来个详细版的好了,也就是将性别和阶层组合起来看看情况如何。

     1 #根据舱等级和性别查看获救情况
     2 fig=plt.figure(figsize(16,8))
     3 fig.set(alpha=0.2)  # 设定图表颜色alpha参数
     4 plt.title(u'根据舱等级和性别查看获救情况',size=15)
     5 
     6 
     7 ax1=fig.add_subplot(141)
     8 data_train.Survived[data_train.Pclass!=3][data_train.Sex=='female'].value_counts().plot(kind='bar',label="female highclass",color='#FA2479')
     9 ax1.set_xticklabels([u'获救',u'未获救'],rotation=0)
    10 ax1.legend([u"女性/高级舱"], loc='best')
    11 plt.grid()
    12 
    13 
    14 ax2=fig.add_subplot(142,sharey=ax1)
    15 data_train.Survived[data_train.Pclass==3][data_train.Sex=='female'].value_counts().plot(kind='bar',label="female lowclass",color='pink')
    16 ax2.set_xticklabels([u'获救',u'未获救'],rotation=0)
    17 ax2.legend([u"女性/低级舱"], loc='best')
    18 plt.grid()
    19 
    20 ax3=fig.add_subplot(143,sharey=ax1)
    21 data_train.Survived[data_train.Pclass!=3][data_train.Sex=='male'].value_counts().plot(kind='bar',label="male highclass",color='lightblue')
    22 ax3.set_xticklabels([u'未获救',u'获救'],rotation=0)
    23 ax3.legend([u"男性/高级舱"], loc='best')
    24 plt.grid()
    25 
    26 ax4=fig.add_subplot(144,sharey=ax1)
    27 data_train.Survived[data_train.Pclass==3][data_train.Sex=='male'].value_counts().plot(kind='bar',label="male lowclass",color='steelblue')
    28 ax4.set_xticklabels([u'未获救',u'获救'],rotation=0)
    29 ax4.legend([u"男性/低级舱"], loc='best')
    30 plt.grid()

    恩,坚定了之前的判断。

    我们看看各登船港口的获救情况。

     1 #各个登录港口乘客的获救情况
     2 fig=plt.figure()
     3 fig.set(alpha=0.2)
     4 
     5 Survived_0=data_train.Embarked[data_train.Survived==0].value_counts()
     6 Survived_1=data_train.Embarked[data_train.Survived==1].value_counts()
     7 df=pd.DataFrame({u'获救':Survived_1,u'未获救':Survived_0})
     8 df.plot(kind='bar',stacked=True)
     9 
    10 plt.xlabel(u'登录港口',size=15)
    11 plt.ylabel(u'人数',size=15)
    12 plt.title(u'各登录港口乘客的获救情况',size=15)
    13 plt.legend((u'未获救',u'获救'),loc='best')

    下面我们来看看 堂兄弟/妹,孩子/父母有几人,对是否获救的影响。

     1 #看看堂兄/弟,孩子/父母有几人,是否对获救有影响
     2 fig=plt.figure()
     3 fig.set(alpha=0.2)
     4 
     5 Survived_0=data_train.SibSp[data_train.Survived==0].value_counts()
     6 Survived_1=data_train.SibSp[data_train.Survived==1].value_counts()
     7 df=pd.DataFrame({u'获救':Survived_1,u'未获救':Survived_0})
     8 df.plot(kind='bar',stacked=True)
     9 plt.xlabel(u'拥有堂兄弟的数目',size=15)
    10 plt.ylabel(u'人数',size=15)
    11 plt.title(u'按照堂兄弟的数目看获救情况',size=15)
    12 plt.grid(b=True)
    13 
    14 Survived_0=data_train.Parch[data_train.Survived==0].value_counts()
    15 Survived_1=data_train.SibSp[data_train.Survived==1].value_counts()
    16 df=pd.DataFrame({u'获救':Survived_1,u'未获救':Survived_0})
    17 df.plot(kind='bar',stacked=True)
    18 plt.xlabel(u'拥有父母子女的数目',size=15)
    19 plt.ylabel(u'人数',size=15)
    20 plt.title(u'按照父母子女的数目看获救情况',size=15)
    21 plt.grid(b=True)

    好吧,没看出特别特别明显的规律(为自己的智商感到捉急…),先作为备选特征,放一放。

    1 #查看一下Cabin的分布
    2 data_train.Cabin.value_counts()

    这三三两两的…如此不集中…我们猜一下,也许,前面的ABCDE是指的甲板位置、然后编号是房间号?…好吧,我瞎说的,别当真…

    关键是Cabin这鬼属性,应该算作类目型的,本来缺失值就多,还如此不集中,注定是个棘手货…第一感觉,这玩意儿如果直接按照类目特征处理的话,太散了,估计每个因子化后的特征都拿不到什么权重。加上有那么多缺失值,要不我们先把Cabin缺失与否作为条件(虽然这部分信息缺失可能并非未登记,maybe只是丢失了而已,所以这样做未必妥当),先在有无Cabin信息这个粗粒度上看看Survived的情况好了。

     1 #根据Cabin信息的有无看看获救情况
     2 fig=plt.figure()
     3 fig.set(alpha=0.2)
     4 
     5 Survived_cabin=data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
     6 Survived_nocabin=data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
     7 df=pd.DataFrame({u'有cabin':Survived_cabin,u'无cabin':Survived_nocabin})
     8 df=df.transpose();
     9 
    10 df.plot(kind='bar',stacked=True)
    11 plt.xlabel(u'有无cabin',size=15,rotation=0)
    12 plt.ylabel(u'人数',size=15)
    13 plt.title(u'按cabin有无看获救情况',size=15)
    14 plt.legend((u'未获救',u'获救'),loc='best')
    15 plt.grid(b=True)

    咳咳,有Cabin记录的似乎获救概率稍高一些,先这么着放一放吧。

    7.简单数据预处理

    大体数据的情况看了一遍,对感兴趣的属性也有个大概的了解了。 
    下一步干啥?咱们该处理处理这些数据,为机器学习建模做点准备了。

    对了,我这里说的数据预处理,其实就包括了很多Kaggler津津乐道的feature engineering过程,灰常灰常有必要!

    『特征工程(feature engineering)太重要了!』 
    『特征工程(feature engineering)太重要了!』 
    『特征工程(feature engineering)太重要了!』

    恩,重要的事情说三遍。

    先从最突出的数据属性开始吧,对,Cabin和Age,有丢失数据实在是对下一步工作影响太大。

    先说Cabin,暂时我们就按照刚才说的,按Cabin有无数据,将这个属性处理成Yes和No两种类型吧。

    再说Age:

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

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

    本例中,后两种处理方式应该都是可行的,我们先试试拟合补全吧(虽然说没有特别多的背景可供我们拟合,这不一定是一个多么好的选择)

    我们这里用scikit-learn中的RandomForest来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DecisionTree,再进行average等等来降低过拟合现象,提高结果的机器学习算法,我们之后会介绍到)

     1 from sklearn.ensemble import RandomForestRegressor
     2 
     3 def set_missing_ages(df):
     4     #将已经存在数值特征取出来
     5     age_df=df[['Age','Fare','Parch','SibSp','Pclass']]  
     6     #将乘客分成已知年龄和未知年龄两部分
     7     known_age=age_df[age_df.Age.notnull()].as_matrix()
     8     unknown_age=age_df[age_df.Age.isnull()].as_matrix()
     9     
    10     #y为目标年龄
    11     y=known_age[:,0]
    12     #X为特征属性值
    13     X=known_age[:,1::]
    14     # fit到RandomForestRegressor之中
    15     rfr=RandomForestRegressor(random_state=0,n_estimators=2000,n_jobs=-1)
    16     rfr.fit(X,y)
    17     
    18     #用得到的模型进行未知年龄结果预测
    19     predictedAges=rfr.predict(unknown_age[:,1::])
    20     
    21     #用得到的结果填补原缺失数据
    22     df.loc[(df.Age.isnull()),'Age']=predictedAges 
    23     return df,rfr
    24 
    25 
    26 def set_Cabin_type(df):
    27     #下面两句话的顺序千万不能颠倒
    28     df.loc[(df.Cabin.notnull()),'Cabin']="Yes"
    29     df.loc[(df.Cabin.isnull()),'Cabin']="No"
    30     return df
    31 
    32 data_train=pd.read_csv("D:/kaggle/titanic/train.csv")
    33 data_train,rfr=set_missing_ages(data_train)
    34 data_train=set_Cabin_type(data_train)
    35 
    36 data_train

    恩。目的达到,OK了。

    因为逻辑回归建模时,需要输入的特征都是数值型特征,我们通常会先对类目型的特征因子化。 
    什么叫做因子化呢?举个例子:

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

    • 原本Cabin取值为yes的,在此处的”Cabin_yes”下取值为1,在”Cabin_no”下取值为0
    • 原本Cabin取值为no的,在此处的”Cabin_yes”下取值为0,在”Cabin_no”下取值为1

    我们使用pandas的”get_dummies”来完成这个工作,并拼接在原来的”data_train”之上,如下所示。

    1 #将类目型特征因子化
    2 dummies_Cabin=pd.get_dummies(data_train['Cabin'],prefix='Cabin')
    3 dummies_Sex=pd.get_dummies(data_train['Sex'],prefix='Sex')
    4 dummies_Embarked=pd.get_dummies(data_train['Embarked'],prefix='Embarked')
    5 dummies_Pclass=pd.get_dummies(data_train['Pclass'],prefix='Pclass')
    6 
    7 df=pd.concat([data_train,dummies_Cabin,dummies_Sex,dummies_Embarked,dummies_Pclass],axis=1)
    8 df.drop(['Pclass','Name','Sex','Ticket','Cabin','Embarked'],axis=1,inplace=True)
    9 df

    bingo,我们很成功地把这些类目属性全都转成0,1的数值属性了。

    这样,看起来,是不是我们需要的属性值都有了,且它们都是数值型属性呢。

    有一种临近结果的宠宠欲动感吧,莫急莫急,我们还得做一些处理,仔细看看Age和Fare两个属性,乘客的数值幅度变化,也忒大了吧!!如果大家了解逻辑回归与梯度下降的话,会知道,各属性值之间scale差距太大,将对收敛速度造成几万点伤害值!甚至不收敛! (╬▔皿▔)…所以我们先用scikit-learn里面的preprocessing模块对这俩货做一个scaling,所谓scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内。

     1 #scaling Age和Fare俩属性
     2 import sklearn.preprocessing as preprocessing
     3 scaler=preprocessing.StandardScaler()
     4 age_scale_param=scaler.fit(df['Age'])
     5 df['Age_Scaled']=scaler.fit_transform(df['Age'],age_scale_param)
     6 
     7 fare_scale_param=scaler.fit(df['Fare'])
     8 df['Fare_Scaled']=scaler.fit_transform(df['Fare'],fare_scale_param)
     9 
    10 df

    恩,好看多了,万事俱备,只欠建模。马上就要看到成效了,哈哈。我们把需要的属性值抽出来,转成scikit-learn里面LogisticRegression可以处理的格式。

    8.逻辑回归建模

    我们把需要的feature字段取出来,转成numpy格式,使用scikit-learn中的LogisticRegression建模。

     1 #将需要的特征提取出来,然后用于建模
     2 from sklearn import linear_model
     3 
     4 #利用正则取出我们需要的属性值
     5 train_df=df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')#是按照数据在表中的顺序取出,而不是按照在参数中的顺序
     6 train_np=train_df.as_matrix()#只有当将DataFrame转换成matrix之后,才能进行train_np[:,:]操作
     7 
     8 #y既是Survival的结果
     9 y=train_np[:,0]
    10 #X为特征属性值
    11 X=train_np[:,1:]
    12 #在逻辑回归模型中进行拟合
    13 clf=linear_model.LogisticRegression(C=1.0,penalty='l1',tol=1e-6)
    14 clf.fit(X,y)
    15 
    16 clf

    good,很顺利,我们得到了一个model,如下: 

    先淡定!淡定!你以为把test.csv直接丢进model里就能拿到结果啊…骚年,图样图森破啊!我们的”test_data”也要做和”train_data”一样的预处理啊!!

     1 #对test也做同样的处理
     2 data_test=pd.read_csv("D:/kaggle/titanic/test.csv")
     3 data_test.loc[data_test.Fare.isnull(),"Fare"]=0
     4 
     5 #用随机森林模型填上丢失的年龄
     6 tmp_df=data_test[['Age','Fare','Parch','SibSp','Pclass']]
     7 null_age=tmp_df[data_test.Age.isnull()].as_matrix()
     8 
     9 X=null_age[:,1:]
    10 predictAges=rfr.predict(X)
    11 data_test.loc[(data_test.Age.isnull()),'Age']=predictAges
    12 
    13 data_test=set_Cabin_type(data_test)
    14 dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
    15 dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
    16 dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
    17 dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')
    18 
    19 df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
    20 df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
    21 df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param)
    22 df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param)
    23 df_test

    不错不错,数据很OK,差最后一步了。 
    下面就做预测取结果吧!!

    1 #利用上面的模型进行预测
    2 test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    3 predictions = clf.predict(test)
    4 result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
    5 result.to_csv("D:/kaggle/titanic/result.csv", index=False)

    啧啧,挺好,格式正确,去make a submission啦(提交成绩为0.69)

    9.逻辑回归系统优化

    9.1 模型系数关联分析

    亲,你以为结果提交上了,就完事了? 
    我不会告诉你,这只是万里长征第一步啊(泪牛满面)!!!这才刚撸完baseline model啊!!!还得优化啊!!!

    看过Andrew Ng老师的machine Learning课程的同学们,知道,我们应该分析分析模型现在的状态了,是过/欠拟合?,以确定我们需要更多的特征还是更多数据,或者其他操作。我们有一条很著名的learning curves对吧。

    不过在现在的场景下,先不着急做这个事情,我们这个baseline系统还有些粗糙,先再挖掘挖掘。

    • 首先,Name和Ticket两个属性被我们完整舍弃了(好吧,其实是因为这俩属性,几乎每一条记录都是一个完全不同的值,我们并没有找到很直接的处理方式)。

    • 然后,我们想想,年龄的拟合本身也未必是一件非常靠谱的事情,我们依据其余属性,其实并不能很好地拟合预测出未知的年龄。再一个,以我们的日常经验,小盆友和老人可能得到的照顾会多一些,这样看的话,年龄作为一个连续值,给一个固定的系数,应该和年龄是一个正相关或者负相关,似乎体现不出两头受照顾的实际情况,所以,说不定我们把年龄离散化,按区段分作类别属性会更合适一些。

    上面只是我瞎想的,who knows是不是这么回事呢,老老实实先把得到的model系数和feature关联起来看看。

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

    首先,大家回去前两篇文章里瞄一眼公式就知道,这些系数为正的特征,和最后结果是一个正相关,反之为负相关。

    我们先看看那些权重绝对值非常大的feature,在我们的模型上:

    • Sex属性,如果是female会极大提高最后获救的概率,而male会很大程度拉低这个概率。
    • Pclass属性,1等舱乘客最后获救的概率会上升,而乘客等级为3会极大地拉低这个概率。
    • 有Cabin值会很大程度拉升最后获救概率(这里似乎能看到了一点端倪,事实上从最上面的有无Cabin记录的Survived分布图上看出,即使有Cabin记录的乘客也有一部分遇难了,估计这个属性上我们挖掘还不够)
    • Age是一个负相关,意味着在我们的模型里,年龄越小,越有获救的优先权(还得回原数据看看这个是否合理
    • 有一个登船港口S会很大程度拉低获救的概率,另外俩港口压根就没啥作用(这个实际上非常奇怪,因为我们从之前的统计图上并没有看到S港口的获救率非常低,所以也许可以考虑把登船港口这个feature去掉试试)。
    • 船票Fare有小幅度的正相关(并不意味着这个feature作用不大,有可能是我们细化的程度还不够,举个例子,说不定我们得对它离散化,再分至各个乘客等级上?)

    噢啦,观察完了,我们现在有一些想法了,但是怎么样才知道,哪些优化的方法是promising的呢?

    因为test.csv里面并没有Survived这个字段(好吧,这是废话,这明明就是我们要预测的结果),我们无法在这份数据上评定我们算法在该场景下的效果…

    而『每做一次调整就make a submission,然后根据结果来判定这次调整的好坏』其实是行不通的…

    9.2 交叉验证

    重点又来了:

    『要做交叉验证(cross validation)!』 
    『要做交叉验证(cross validation)!』 
    『要做交叉验证(cross validation)!』

    恩,重要的事情说三遍。我们通常情况下,这么做cross validation:把train.csv分成两部分,一部分用于训练我们需要的模型,另外一部分数据上看我们预测算法的效果。

    我们用scikit-learn的cross_validation来帮我们完成小数据集上的这个工作。

    先简单看看cross validation情况下的打分

    1 #进行交叉验证
    2 from sklearn import cross_validation
    3 
    4 #简单看看打分情况
    5 clf=linear_model.LogisticRegression(C=1.0,penalty='l1',tol=1e-6)
    6 all_data = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    7 X = all_data.as_matrix()[:,1:]
    8 y = all_data.as_matrix()[:,0]
    9 print cross_validation.cross_val_score(clf, X, y, cv=5)

    似乎比Kaggle上的结果略高哈,毕竟用的是不是同一份数据集评估的。

    等等,既然我们要做交叉验证,那我们干脆先把交叉验证里面的bad case拿出来看看,看看人眼审核,是否能发现什么蛛丝马迹,是我们忽略了哪些信息,使得这些乘客被判定错了。再把bad case上得到的想法和前头系数分析的合在一起,然后逐个试试。

    下面我们做数据分割,并且在原始数据集上瞄一眼bad case:

     1 #分割数据
     2 split_train,split_cv=cross_validation.train_test_split(df,test_size=0.3,random_state=0)
     3 train_df = split_train.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
     4 #生成模型
     5 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
     6 clf.fit(train_df.as_matrix()[:,1:], train_df.as_matrix()[:,0])
     7 
     8 #对交叉验证数据进行预测
     9 cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    10 predictions = clf.predict(cv_df.as_matrix()[:,1:])
    11 
    12 origin_data_train = pd.read_csv("D:/kaggle/titanic/train.csv")
    13 bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions!=cv_df.as_matrix()[:,0]]['PassengerId'].values)]
    14 bad_cases

    我们判定错误的 bad case 中部分数据如下: 

    大家可以自己跑一遍试试,拿到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,女性也一样。

    大家接着往下挖掘,可能还可以想到更多可以细挖的部分。我这里先列这些了,然后我们可以使用手头上的”train_df”和”cv_df”开始试验这些feature engineering的tricks是否有效了。

    试验的过程比较漫长,也需要有耐心,而且我们经常会面临很尴尬的状况,就是我们灵光一闪,想到一个feature,然后坚信它一定有效,结果试验下来,效果还不如试验之前的结果。恩,需要坚持和耐心,以及不断的挖掘。

    9.3 learning curves

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

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

    额,这个欠拟合和过拟合怎么解释呢。这么说吧:

    • 过拟合就像是你班那个学数学比较刻板的同学,老师讲过的题目,一字不漏全记下来了,于是老师再出一样的题目,分分钟精确出结果。but数学考试,因为总是碰到新题目,所以成绩不咋地。
    • 欠拟合就像是,咳咳,和博主level差不多的差生。连老师讲的练习题也记不住,于是连老师出一样题目复习的周测都做不好,考试更是可想而知了。

    而在机器学习的问题上,对于过拟合欠拟合两种情形。我们优化的方式是不同的。

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

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

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

    著名的learning curve可以帮我们判定我们的模型现在所处的状态。我们以样本数为横坐标,训练和交叉验证集上的错误率作为纵坐标,两种状态分别如下两张图所示:过拟合(overfitting/high variace),欠拟合(underfitting/high bias)

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

    回到我们的问题,我们用scikit-learn里面的learning_curve来帮我们分辨我们模型的状态。举个例子,这里我们一起画一下我们最先得到的baseline model的learning curve。

     1 #画一下最初得到的模型的learning curve
     2 import numpy as np
     3 import matplotlib.pyplot as plt
     4 from sklearn.learning_curve import learning_curve
     5 
     6 def plot_learning_curve(estimator,title,X,y,ylim=None,cv=None,n_jobs=1,train_sizes=np.linspace(0.05,1.0,20),verbose=0,plot=True):
     7     """
     8     画出data在某模型上的learning curve.
     9     参数解释
    10     ----------
    11     estimator : 你用的分类器。
    12     title : 表格的标题。
    13     X : 输入的feature,numpy类型
    14     y : 输入的target vector
    15     ylim : tuple格式的(ymin, ymax), 设定图像中纵坐标的最低点和最高点
    16     cv : 做cross-validation的时候,数据分成的份数,其中一份作为cv集,其余n-1份作为training(默认为3份)
    17     n_jobs : 并行的的任务数(默认1)
    18     """
    19     train_sizes,train_scores,test_scores=learning_curve(estimator,X,y,cv=cv,n_jobs=n_jobs,train_sizes=train_sizes,verbose=verbose)
    20     train_scores_mean = np.mean(train_scores, axis=1)
    21     train_scores_std = np.std(train_scores, axis=1)
    22     test_scores_mean = np.mean(test_scores, axis=1)
    23     test_scores_std = np.std(test_scores, axis=1)
    24     
    25     if plot:
    26         plt.figure()
    27         plt.title(title)
    28         if ylim is not None:
    29             plt.ylim(*ylim)
    30         plt.xlabel(u"训练样本数")
    31         plt.ylabel(u"得分")
    32         plt.grid()
    33         
    34         plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, 
    35                          alpha=0.1, color="b")
    36         plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, 
    37                          alpha=0.1, color="r")
    38         plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分")
    39         plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分")
    40         plt.legend(loc="best")
    41 
    42         plt.draw()
    43         plt.show()
    44     
    45     return train_sizes,train_scores
    46 #缩进格式要注意
    47 train_sizes,train_scores= plot_learning_curve(clf, u"学习曲线", X, y)

    在实际数据上看,我们得到的learning curve没有理论推导的那么光滑哈,但是可以大致看出来,训练集和交叉验证集上的得分曲线走势还是符合预期的。

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

    10.模型融合(model ensemble)

    好了,终于到这一步了,我们要祭出机器学习/数据挖掘上通常最后会用到的大杀器了。恩,模型融合。

    『强迫症患者』打算继续喊喊口号… 
    『模型融合(model ensemble)很重要!』 
    『模型融合(model ensemble)很重要!』 
    『模型融合(model ensemble)很重要!』 
    重要的事情说三遍,恩,噢啦。

    先解释解释,一会儿再回到我们的问题上哈。 
    啥叫模型融合呢,我们还是举几个例子直观理解一下好了。

    大家都看过知识问答的综艺节目中,求助现场观众时候,让观众投票,最高的答案作为自己的答案的形式吧,每个人都有一个判定结果,最后我们相信答案在大多数人手里。

    再通俗一点举个例子。你和你班某数学大神关系好,每次作业都『模仿』他的,于是绝大多数情况下,他做对了,你也对了。突然某一天大神脑子犯糊涂,手一抖,写错了一个数,于是…恩,你也只能跟着错了。 
    我们再来看看另外一个场景,你和你班5个数学大神关系都很好,每次都把他们作业拿过来,对比一下,再『自己做』,那你想想,如果哪天某大神犯糊涂了,写错了,but另外四个写对了啊,那你肯定相信另外4人的是正确答案吧?

    最简单的模型融合大概就是这么个意思,比如分类问题,当我们手头上有一堆在同一份数据集上训练得到的分类器(比如logistic regression,SVM,KNN,random forest,神经网络),那我们让他们都分别去做判定,然后对结果做投票统计,取票数最多的结果为最后结果

    bingo,问题就这么完美的解决了。

    模型融合可以比较好地缓解,训练过程中产生的过拟合问题,从而对于结果的准确度提升有一定的帮助。

    话说回来,回到我们现在的问题。你看,我们现在只讲了logistic regression,如果我们还想用这个融合思想去提高我们的结果,我们该怎么做呢?

    既然这个时候模型没得选,那咱们就在数据上动动手脚咯。大家想想,如果模型出现过拟合现在,一定是在我们的训练上出现拟合过度造成的对吧。

    那我们干脆就不要用全部的训练集,每次取训练集的一个subset,做训练,这样,我们虽然用的是同一个机器学习算法,但是得到的模型却是不一样的;同时,因为我们没有任何一份子数据集是全的,因此即使出现过拟合,也是在子训练集上出现过拟合,而不是全体数据上,这样做一个融合,可能对最后的结果有一定的帮助。对,这就是常用的Bagging。

    我们用scikit-learn里面的Bagging来完成上面的思路,过程非常简单。代码如下:

     1 #Bagging
     2 from sklearn.ensemble import BaggingRegressor
     3 
     4 train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
     5 train_np = train_df.as_matrix()
     6 
     7 # y即Survival结果
     8 y = train_np[:, 0]
     9 
    10 # X即特征属性值
    11 X = train_np[:, 1:]
    12 
    13 # fit到BaggingRegressor之中
    14 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
    15 bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1)
    16 bagging_clf.fit(X, y)
    17 test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
    18 predictions = bagging_clf.predict(test)
    19 result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
    20 result.to_csv("D:/kaggle/titanic/result.csv", index=False)

    我在官网上提交了以下,很可惜,还不如原来的成绩高

    11.总结

    文章稍微有点长,非常感谢各位耐心看到这里。 
    总结的部分,我就简短写几段,出现的话,很多在文中有对应的场景,大家有兴趣再回头看看。

    对于任何的机器学习问题,不要一上来就追求尽善尽美,先用自己会的算法撸一个baseline的model出来,再进行后续的分析步骤,一步步提高

    在问题的结果过程中:

    • 『对数据的认识太重要了!』
    • 『数据中的特殊点/离群点的分析和处理太重要了!』
    • 『特征工程(feature engineering)太重要了!』
    • 『模型融合(model ensemble)太重要了!』

    本文中用机器学习解决问题的过程大概如下图所示

  • 相关阅读:
    canvas-color的几种设置
    canvas-2lineCap.html
    canvas-2lineJoin.html
    canvas-0trasform.html
    总体、个体和简单随机样本
    大数定律
    切比雪夫不等式
    B1032. 挖掘机技术哪家强
    Array(数组)对象-->join() 方法
    Array(数组)对象-->shift() 方法
  • 原文地址:https://www.cnblogs.com/xiaohua92/p/5525975.html
Copyright © 2011-2022 走看看