机器学习实战 | 逻辑回归应用之“Kaggle泰坦尼克之灾”

做者: 寒小阳
时间:2015年11月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/49797143
声明:版权全部,转载请注明出处,谢谢。python

1.引言

因为临近毕业时间太紧,而我以前立的不少flag要在毕业前完成。可是也不会忘了push本身抽出时间来写更新Kaggle实战的文章,缘由也很是简单:web

  • 写完前两篇逻辑回归的介绍和各个角度理解以后,咱们讨论群(戳我入群)的小伙伴们纷纷表示『好像很高级的样纸,but 然并卵 啊!大家却是拿点实际数据来给咱们看看,这玩意儿 有!什!么!用!啊!』
  • talk is cheap, show me the code!
  • no example,no bb!

OK,OK,这就来了咯,社友们别着急,咱们先找个简单的实际例子,来看看,所谓的数据挖掘或者机器学习实际应用究竟是怎么样一个过程。算法

『喂,那几个说要看大数据上机器学习应用的,对,就是说大家!别着急好么,咱们以后拉点大一点实际数据用liblinear或者spark,MLlib跑给大家看,行不行?我们先拿个实例入入门嘛』bootstrap

好了,我是一个严肃的技术研究和分享者,咳咳,不能废话了,各位同窗继续往下看吧!微信

2. 背景

2.1 关于Kaggle

我是Kaggle地址,翻我牌子网络

亲,逼格这么高的地方,你必定听过对不对?是!这就是那个无数『数据挖掘先驱』们,在回答"枪我有了,哪能找到靶子练练手啊?"时候的答案!dom

这是一个要数据有数据,要实际应用场景有场景,要一块儿在数据挖掘领域high得不要不要的小伙伴就有小伙伴的地方啊!!!机器学习

艾玛,逗逼模式开太猛了。恩,不闹,不闹,说正事,Kaggle是一个数据分析建模的应用竞赛平台,有点相似KDD-CUP(国际知识发现和数据挖掘竞赛),企业或者研究者能够将问题背景、数据、指望指标等发布到Kaggle上,以竞赛的形式向广大的数据科学家征集解决方案。而热爱数(dong)据(shou)挖(zhe)掘(teng)的小伙伴们能够下载/分析数据,使用统计/机器学习/数据挖掘等知识,创建算法模型,得出结果并提交,排名top的可能会有奖金哦!svg

以前我也更新了两篇关于入门和经验之谈的文章,公众号也开设了专栏。函数

2.2 关于泰坦尼克号之灾

带你们去该问题页面溜达一圈吧

下面是问题背景页
在这里插入图片描述
下面是可下载Data的页面
在这里插入图片描述
下面是小伙伴们最爱的forum页面,你会看到各类神级人物厉(qi)害(pa)的数据处理/建模想法,你会直视『世界真奇妙』。
在这里插入图片描述
泰坦尼克号问题之背景

  • 就是那个你们都熟悉的『Jack and Rose』的故事,豪华游艇倒了,你们都惊恐逃生,但是救生艇的数量有限,没法人人都有,副船长发话了『lady and kid first!』,因此是否获救其实并不是随机,而是基于一些背景有rank前后的。

  • 训练和测试数据是一些乘客的我的信息以及存活情况,要尝试根据它生成合适的模型并预测其余人的存活情况。

  • 对,这是一个二分类问题,是咱们以前讨论的logistic regression所能处理的范畴。

3.说明

接触过Kaggle的同窗们可能知道这个问题,也可能知道RandomForest和SVM等等算法,甚至还对多个模型作过融合,取得过很是好的结果,那maybe这篇文章并非针对你的,你能够自行略过。

咱们由于以前只介绍了Logistic Regression这一种分类算法。因此本次的问题解决过程和优化思路,都集中在这种算法上。其他的方法可能咱们以后的文章里会提到。

说点我的的观点。不必定正确。

  • 『解决一个问题的方法和思路不止一种』
  • 『没有所谓的机器学习算法优劣,也没有绝对高性能的机器学习算法,只有在特定的场景、数据和特征下更合适的机器学习算法。』

4.怎么作?

手把手教程立刻就来,先来两条我看到的,以为很重要的经验。

  1. 印象中Andrew Ng老师彷佛在coursera上说过,应用机器学习,千万不要一上来就试图作到完美,先撸一个baseline的model出来,再进行后续的分析步骤,一步步提升,所谓后续步骤可能包括『分析model如今的状态(欠/过拟合),分析咱们使用的feature的做用大小,进行feature selection,以及咱们模型下的bad case和产生的缘由』等等。

  2. Kaggle上的大神们,也分享过一些experience,说几条我记得的哈:

  • 『对数据的认识过重要了!』
  • 『数据中的特殊点/离群点的分析和处理过重要了!』
  • 『特征工程(feature engineering)过重要了!在不少Kaggle的场景下,甚至比model自己还要重要』
  • 『要作模型融合(model ensemble)啊啊啊!』

更多的经验分享请加讨论群,具体方式请联系做者,或者参见《“ML学分计划”说明书》

5.初探数据

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

import pandas as pd #数据分析
import numpy as np #科学计算
from pandas import Series,DataFrame

data_train = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/Train.csv")
data_train

pandas是经常使用的python数据处理包,把csv文件读入成dataframe各式,咱们在ipython notebook中,看到data_train以下所示:
在这里插入图片描述

这就是典型的dataframe格式,若是你没接触过这种格式,彻底没有关系,你就把它想象成Excel里面的列好了。

咱们看到,总共有12列,其中Survived字段表示的是该乘客是否获救,其他都是乘客的我的信息,包括:

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

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

data_train.info()

看到了以下的信息:
在这里插入图片描述

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

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

彷佛信息略少啊,想再瞄一眼具体数据数值状况呢?恩,咱们用下列的方法,获得数值型数据的一些分布(由于有些属性,好比姓名,是文本型;而另一些属性,好比登船港口,是类目型。这些咱们用下面的函数是看不到的):

在这里插入图片描述

咱们从上面看到更进一步的什么信息呢?

mean字段告诉咱们,大概0.383838的人最后获救了,2/3等舱的人数比1等舱要多,平均乘客年龄大概是29.7岁(计算这个时候会略掉无记录的)等等…

6.数据初步分析

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

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

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

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

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

6.1 乘客各属性分布

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

import matplotlib.pyplot as plt
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

plt.subplot2grid((2,3),(0,0))             # 在一张大图里分列几个小图
data_train.Survived.value_counts().plot(kind='bar')# 柱状图 
plt.title(u"获救状况 (1为获救)") # 标题
plt.ylabel(u"人数")  

plt.subplot2grid((2,3),(0,1))
data_train.Pclass.value_counts().plot(kind="bar")
plt.ylabel(u"人数")
plt.title(u"乘客等级分布")

plt.subplot2grid((2,3),(0,2))
plt.scatter(data_train.Survived, data_train.Age)
plt.ylabel(u"年龄")                         # 设定纵坐标名称
plt.grid(b=True, which='major', axis='y') 
plt.title(u"按年龄看获救分布 (1为获救)")


plt.subplot2grid((2,3),(1,0), colspan=2)
data_train.Age[data_train.Pclass == 1].plot(kind='kde')   
data_train.Age[data_train.Pclass == 2].plot(kind='kde')
data_train.Age[data_train.Pclass == 3].plot(kind='kde')
plt.xlabel(u"年龄")# plots an axis lable
plt.ylabel(u"密度") 
plt.title(u"各等级的乘客年龄分布")
plt.legend((u'头等舱', u'2等舱',u'3等舱'),loc='best') # sets our legend for our graph.


plt.subplot2grid((2,3),(1,2))
data_train.Embarked.value_counts().plot(kind='bar')
plt.title(u"各登船口岸上船人数")
plt.ylabel(u"人数")  
plt.show()

在这里插入图片描述

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

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

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

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

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

#看看各乘客等级的获救状况
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各乘客等级的获救状况")
plt.xlabel(u"乘客等级") 
plt.ylabel(u"人数") 
plt.show()

在这里插入图片描述

啧啧,果真,钱和地位对舱位有影响,进而对获救的可能性也有影响啊←_←

咳咳,跑题了,我想说的是,明显等级为1的乘客,获救的几率高不少。恩,这个必定是影响最后获救结果的一个特征。

#看看各性别的获救状况
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_m = data_train.Survived[data_train.Sex == 'male'].value_counts()
Survived_f = data_train.Survived[data_train.Sex == 'female'].value_counts()
df=pd.DataFrame({u'男性':Survived_m, u'女性':Survived_f})
df.plot(kind='bar', stacked=True)
plt.title(u"按性别看获救状况")
plt.xlabel(u"性别") 
plt.ylabel(u"人数")
plt.show()

在这里插入图片描述

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

再来个详细版的好了。

#而后咱们再来看看各类舱级别状况下各性别的获救状况
fig=plt.figure()
fig.set(alpha=0.65) # 设置图像透明度,无所谓
plt.title(u"根据舱等级和性别的获救状况")

ax1=fig.add_subplot(141)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar', label="female highclass", color='#FA2479')
ax1.set_xticklabels([u"获救", u"未获救"], rotation=0)
ax1.legend([u"女性/高级舱"], loc='best')

ax2=fig.add_subplot(142, sharey=ax1)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='female, low class', color='pink')
ax2.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"女性/低级舱"], loc='best')

ax3=fig.add_subplot(143, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass != 3].value_counts().plot(kind='bar', label='male, high class',color='lightblue')
ax3.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"男性/高级舱"], loc='best')

ax4=fig.add_subplot(144, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='male low class', color='steelblue')
ax4.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"男性/低级舱"], loc='best')
plt.show()

在这里插入图片描述

恩,坚决了以前的判断。

咱们看看各登船港口的获救状况。

fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_0 = data_train.Embarked[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Embarked[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各登陆港口乘客的获救状况")
plt.xlabel(u"登陆港口") 
plt.ylabel(u"人数") 

plt.show()

在这里插入图片描述

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

g = data_train.groupby(['SibSp','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
print df

g = data_train.groupby(['SibSp','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
print df

在这里插入图片描述 在这里插入图片描述

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

#ticket是船票编号,应该是unique的,和最后的结果没有太大的关系,先不归入考虑的特征范畴把
#cabin只有204个乘客有值,咱们先看看它的一个分布

data_train.Cabin.value_counts()

部分结果以下:

在这里插入图片描述

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

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

fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
Survived_nocabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
df=pd.DataFrame({u'有':Survived_cabin, u'无':Survived_nocabin}).transpose()
df.plot(kind='bar', stacked=True)
plt.title(u"按Cabin有无看获救状况")
plt.xlabel(u"Cabin有无") 
plt.ylabel(u"人数")
plt.show()

在这里插入图片描述

咳咳,有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等等来下降过拟合现象,提升结果的机器学习算法,咱们以后会介绍到)

from sklearn.ensemble import RandomForestRegressor
 
### 使用 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(data_train)
data_train = set_Cabin_type(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"之上,以下所示。

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)
df

在这里插入图片描述

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

这样,看起来,是否是咱们须要的属性值都有了,且它们都是数值型属性呢。

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

import sklearn.preprocessing as preprocessing
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)
df

在这里插入图片描述

恩,好看多了,万事俱备,只欠建模。立刻就要看到成效了,哈哈。咱们把须要的属性值抽出来,转成scikit-learn里面LogisticRegression能够处理的格式。

8.逻辑回归建模

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

from sklearn import linear_model

# 用正则取出咱们要的属性值
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)
    
clf

good,很顺利,咱们获得了一个model,以下:

在这里插入图片描述

先淡定!淡定!你觉得把test.csv直接丢进model里就能拿到结果啊…骚年,图样图森破啊!咱们的"test_data"也要作和"train_data"同样的预处理啊!!

data_test = pd.read_csv("/Users/Hanxiaoyang/Titanic_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)
df_test

在这里插入图片描述

不错不错,数据很OK,差最后一步了。

下面就作预测取结果吧!!

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("/Users/Hanxiaoyang/Titanic_data/logistic_regression_predictions.csv", index=False)

在这里插入图片描述

啧啧,挺好,格式正确,去make a submission啦啦啦!

在Kaggle的Make a submission页面,提交上结果。以下:

在这里插入图片描述

0.76555,恩,结果还不错。毕竟,这只是咱们简单分析处理事后出的一个baseline模型嘛。

9.逻辑回归系统优化

9.1 模型系数关联分析

亲,你觉得结果提交上了,就完事了?

我不会告诉你,这只是万里长征第一步啊(泪牛满面)!!!这才刚撸完baseline model啊!!!还得优化啊!!!

看过Andrew Ng老师的machine Learning课程的同窗们,知道,咱们应该分析分析模型如今的状态了,是过/欠拟合?,以肯定咱们须要更多的特征仍是更多数据,或者其余操做。咱们有一条很著名的learning curves对吧。

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

  • 首先,Name和Ticket两个属性被咱们完整舍弃了(好吧,实际上是由于这俩属性,几乎每一条记录都是一个彻底不一样的值,咱们并无找到很直接的处理方式)。

  • 而后,咱们想一想,年龄的拟合自己也未必是一件很是靠谱的事情,咱们依据其他属性,其实并不能很好地拟合预测出未知的年龄。再一个,以咱们的平常经验,小盆友和老人可能获得的照顾会多一些,这样看的话,年龄做为一个连续值,给一个固定的系数,应该和年龄是一个正相关或者负相关,彷佛体现不出两头受照顾的实际状况,因此,说不定咱们把年龄离散化,按区段分做类别属性会更合适一些。

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

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状况下的打分

from sklearn import 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)

结果是下面酱紫的:
[0.81564246 0.81005587 0.78651685 0.78651685 0.81355932]

彷佛比Kaggle上的结果略高哈,毕竟用的是否是同一份数据集评估的。

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

下面咱们作数据分割,而且在原始数据集上瞄一眼bad case:

# 分割数据,按照 训练数据:cv数据 = 7:3的比例
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("/Users/HanXiaoyang/Titanic_data/Train.csv")
bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)]
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,而后坚信它必定有效,结果试验下来,效果还不如试验以前的结果。恩,须要坚持和耐心,以及不断的挖掘。

我最好的结果是在『Survived~C(Pclass)+C(Title)+C(Sex)+C(Age_bucket)+C(Cabin_num_bucket)Mother+Fare+Family_Size』下取得的,结果以下(抱歉,博主君commit的时候手抖把页面关了,因而没截着图,下面这张图是在我获得最高分以后,用此次的结果从新make commission的,截了个图,得分是0.79426,不是目前个人最高分哈,所以排名木有变…):

在这里插入图片描述

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。

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

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来完成上面的思路,过程很是简单。代码以下:

from sklearn.ensemble import BaggingRegressor

train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
train_np = train_df.as_matrix()

# y即Survival结果
y = train_np[:, 0]

# X即特征属性值
X = train_np[:, 1:]

# fit到BaggingRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1)
bagging_clf.fit(X, y)

test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
predictions = bagging_clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("/Users/HanXiaoyang/Titanic_data/logistic_regression_bagging_predictions.csv", index=False)

而后你再Make a submission,恩,发现对结果仍是有帮助的。

在这里插入图片描述

11.总结

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

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

在问题的结果过程当中:

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

本文中用机器学习解决问题的过程大概以下图所示:
在这里插入图片描述

12. 传送门

关于数据和代码

公众号后台回复“Kaggle01”获取本文中的数据和代码,欢迎你们下载和本身尝试。

在这里插入图片描述

△微信扫一扫关注「迈微电子研发社」公众号

知识星球:社群旨在分享AI算法岗的秋招/春招准备攻略(含刷题)、面经和内推机会、学习路线、知识题库等。
在这里插入图片描述

△扫码加入「迈微电子研发社」学习辅导群

在这里插入图片描述