kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾

经典又兼具备趣味性的Kaggle案例泰坦尼克号问题

kaggle入门——逻辑回归应用之kaggle泰坦尼克之灾 原文连接:https://blog.csdn.net/han_xiaoyang/article/details/49797143#commentBox
https://www.kesci.com/home/project/5bfe39b3954d6e0010681cd1

背景

关于Kaggle

  • 我是kaggle地址,翻我牌子
  • 这就是哪个无数【数据挖掘先驱】们,在回答”枪我有了,哪能找到靶子练练手啊?“时候的答案

Kaggel是一个要数据有数据,要实际应用场景有场景,要一起在数据挖掘领域high得不要不要的小伙伴就有小伙伴的地方啊!!!
据挖掘竞赛),企业或者研究者可以将问题背景、数据、期望指标等发布到Kaggle上,以竞赛的形式向广大的数据科学家征集解决方案。而热爱数(dong)据(shou)挖(zhe)掘(teng)的小伙伴们可以下载/分析数据,使用统计/机器学习/数据挖掘等知识,建立算法模型,得出结果并提交,排名top的可能会有奖金哦!

关于泰坦尼克号之灾

  • 下面是问题的背景页
    kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第1张图片

  • 可下载Data的页面
    kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第2张图片

  • 下面是小伙伴们最爱的forum页面,你会看到各种神级人物厉(qi)害(pa)的数据处理/建模想法,你会直视『世界真奇妙』。
    kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第3张图片

  • 泰坦尼克号问题背景

    • 就是那个大家都熟悉的『Jack and Rose』的故事,豪华游艇倒了,大家都惊恐逃生,可是救生艇的数量有限,无法人人都有,副船长发话了『lady and kid first!』,所以是否获救其实并非随机,而是基于一些背景有rank先后的。
    • 训练和测试数据是一些乘客的个人信息以及存活状况,要尝试根据它生成合适的模型并预测其他人的存活状况。
    • 对,这是一个二分类问题,是我们之前讨论的logistic regression所能处理的范畴。

初探数据

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

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

data_train = pd.read_csv("e:/data/Titanic_data/train.csv")
data_train.columns

输出

Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')

看到大概有以下这些字段

PassengerId =>乘客ID

Pclass => 乘客登记(1/2/3等舱位)

Name => 乘客姓名

Sex =>性别

Age => 年龄

SibSp => 堂兄弟/妹个数

Parch => 父母与小孩个数

Ticket => 船票信息

Fare => 票价

Cabin => 客舱

Embarked =>登船港口

查看数据集信息

data_train.info()


RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB

上面的数据可以看出来,训练数据总共又891名乘客,但是有些属性数据不全,比如说:

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

    再查看一下数值的具体情况
data_train.describe()

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第4张图片
由mean字段的值,大概0.383838的人最后获救了,2/3等舱人数要比1等舱多,平均乘客年龄大概是29.7岁…
上面的简单描述性信息并没有什么用,需要更细力度的分析数据

数据初步分析

仅仅上面的对数据的了解,依旧无法提供给我们想法和思路。再深入一点看看数据

查看每个/多个 属性和最后的Survived之间有什么样的关系

乘客各属性分布

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

plt.subplot2grid((2,3),(0,0)) #在一张大图里分列几个小图
plt.rcParams['font.sans-serif'] = ['SimHei'] #指定默认字体
plt.rcParams['axes.unicode_minus'] = False #解决中文显示为方框的问题
data_train.Survived.value_counts().plot(kind='bar') #柱状图
plt.title("获救情况 (1为获救)") #标题
plt.ylabel("人数")

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

plt.subplot2grid((2,3),(0,2))
plt.scatter(data_train.Survived,data_train.Age) #散点图
plt.ylabel("年龄")
plt.grid(b=True,which='major',axis='y') #显示y轴网格
plt.title("按年龄看获救分布 (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("年龄")
plt.ylabel("密度")
plt.title("各等级的乘客年龄分布")
plt.legend(('头等舱','2等舱','3等舱'),loc='best')  #显示图例

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

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第5张图片
在图上可以看出来,被救的人300多点,不到半数;3等舱人数非常多,遇难和获救的人年龄跨度似乎都很广;3个不同舱年龄总体趋势似乎也一致,2/3等舱乘客20多岁的人最多,1等舱40岁左右的最多(似乎符合财富和年龄的分配);登船港口人数按照S、C、Q递减,而且s远对于另外两个港口。

现在可能会有一些想法了:

  • 不同仓位/乘客等级了能和财富/地位有关系,最后获救的概率可能会不一样
  • 年龄对获救概率也一定是有影响的,副船长说 小孩和女士先走
  • 和登船港口是不是也有关系呢?也许登船港口不同,人的身份地位不同?
    口说无凭,空想无益,老老实实再来统计统计,看看这些属性值的统计分布。

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

查看各乘客等级的获救情况

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({'未获救':Survived_0,'获救':survived_1})
df.plot(kind='bar',stacked=True) #stacked表示叠加
plt.title('各乘客等级的获救情况')
plt.xlabel('乘客等级')
plt.ylabel('人数')
plt.show()


kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第6张图片
明显等级为1的乘客,获救的概率高很多。这一定是影响救结果的一个特征。

查看性别的获救情况

#### 查看性别的获救情况
fig = plt.figure()
fig.set(alpha=0.2)

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({'男性':Survived_m,'女性':Survived_f})
df.plot(kind='bar',stacked=True)
plt.title('按性别查看获救情况')
plt.xlabel('性别')
plt.ylabel('人数')
plt.show()


kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第7张图片
外国人果然尊重lady,lady first践行的不错。性别无疑也要作为重要特征加入最后的模型之中。

来个详细版的

#然后来看看各种舱级别情况下各性别的获救情况
fig=plt.figure()
fig.set(alpha=0.65)
plt.title('舱等级和性别的获救情况')

ax1 = fig.add_subplot(141)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar',label='female high class',color='#FA2479')
ax1.set_xticklabels(["未获救","获救"],rotation=0)
plt.legend(["女性/低级舱"],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(["未获救","获救"],rotation=0)
plt.legend(["女性/低级舱"],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(["未获救","获救"],rotation=0)
plt.legend(["男性/高级舱"],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(["未获救","获救"],rotation=0)
plt.legend(["男性/低级舱"],loc="best")


kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第8张图片
可以看出之前的判断是正确的

查看各登船港口的获救情况

fig = plt.figure()
fig.set(alpha=0.2) 

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({"未获救":Survived_0,"获救":Survived_1})
df.plot(kind='bar',stacked=True)
plt.title("各登船港口乘客的获救情况")
plt.xlabel("登船港口")
plt.ylabel("人数")

plt.show()


kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第9张图片

看堂兄弟/妹,孩子/父母有几人,对是否获救的影响

gg = data_train.groupby(['SibSp','Survived'])['PassengerId'].count()
print(gg)

gp = data_train.groupby(['Parch','Survived'])['PassengerId'].count()
print(gp)

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第10张图片
没有看出特别明显的规律。作为备选特征先放一放。

ticket cabin的分析

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

data_train.Cabin.value_counts()

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第11张图片
数据三三两两的很不集中,也许前面的ABCD是指加班位置,然后编号是房间号
Cabin属性,应该算作类目型的,本来缺失值就多,还如此不集中,第一感觉是,如果直接按照类目特征处理的话,太散了,估计每个因子化后的特征都拿不到什么权重。加上有那么多缺失值,就下那边Cabin缺失与否作为条件(虽然这部分信息缺失可能只是未登记,或者只是丢失了而已,所以这样做未必妥当),现在有无Cabin信息这个粗细力度上看看Survived的情况好了。

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

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


kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第12张图片
有Cabin记录的似乎获救概率稍高一些,先这么放着吧

简单数据预处理

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

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

【特征工程太重要了!】

【特征工程太重要了!】

【特征工程太重要了!】

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

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

再说Age:

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

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

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

我们这里使用scikit-learn种的RandomForest来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DesionTree,再进行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()]
    unknown_age = age_df[age_df.Age.isnull()]
    
    #y即目标年龄
    y = known_age.iloc[:,0]
    
    #x即特征属性
    X = known_age.iloc[:,1:]
    
    #fit到RandomForestRegressor之中
    rfr = RandomForestRegressor(random_state=0,n_estimators=2000,n_jobs=-1)
    rfr.fit(X,y)
    
    #用得到的模型进行未知年龄结果预测
    predictedAges = rfr.predict(unknown_age.iloc[:,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)
data_train.info()

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第13张图片
嗯,目的达到了,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) #inplace=True:不创建新的对象,直接对原始对象进行修改
df.columns

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第14张图片

df.info()

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第15张图片
成功的把类目属性全部转化成0,1的属性值了。
这样,看起来,需要的属性值都有课,且都是数值类型的。?
但是仔细已经按Age和Fare两个属性,乘客的数值幅度变化太大,如果了解逻辑回归与梯度下降的话,会知道,各属性之间scale差距太大,将对收敛速度造成几万点伤害,设置不收敛…所以先用scikit-learn里面的preprocessing模块对这两列做一个scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内。

import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
#知道shape属性是多少,但是想变成只有一列,行数不知道多少,通过`z.reshape(-1,1)`,Numpy自动计算
age_scale_param = scaler.fit(df['Age'].values.reshape(-1,1))
df['Age_scaled'] = scaler.fit_transform(df['Age'].values.reshape(-1,1),age_scale_param)
fare_scale_param = scaler.fit(df['Fare'].values.reshape(-1,1))
df['Fare_scaled'] = scaler.fit_transform(df['Fare'].values.reshape(-1,1),fare_scale_param)
df

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第16张图片

逻辑回归建模

把需要的feature字段取出来

from sklearn import linear_model

#用正则表达式取出我们要的属性值
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')


#y即Survival结果:第0列
y = train_df.iloc[:,0]

#X即特征属性:第1列及后序列
X = train_df.iloc[:,1:]

#fit到LogisticRegression之中
clf = linear_model.LogisticRegression(C=1.0,penalty='l1',tol=1e-6)
clf.fit(X,y)

clf

在这里插入图片描述

X.shape

(891, 14)
good 很顺利,我们得到了一个model.
先淡定!淡定!不是直接把test.csv直接丢到model里就能拿到结果啊…我们的"test_data"也要做和"train_data"一样的预处理啊!!

data_test = pd.read_csv("e:/data/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()
#根据特征属性Xy预测年龄并补上
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'].values.reshape(-1,1),age_scale_param)
df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'].values.reshape(-1,1),fare_scale_param)
df_test

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第17张图片
418 rows × 17 columns
不错不错,数据很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'],'Survived':predictions.astype(np.int32)})
result.to_csv("e:/data/Titanic_data/logistic_regression_predictions.csv",index=False)
pd.read_csv('e:/data/Titanic_data/logistic_regression_predictions.csv')

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第18张图片
418 rows × 2 columns
在Kaggle的Make a submission页面,提交上结果。如下:
kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第19张图片
0.76555,结果还不错,毕竟,这只是简单分析处理过后的一个baseline模型。

逻辑回归系统优化

模型系数关联分析

提交只是万里长征第一步,这才刚撸完baseline model,模型还需要优化

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

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

  • 首先,Name和Ticket两个属性被我们完整舍弃了(好吧,其实是因为这俩属性,几乎每一条记录都是一个完全不同的值,我们并没有找到很直接的处理方式)。
  • 然后,我们想想,年龄的拟合本身也未必是一件非常靠谱的事情,我们依据其余属性,其实并不能很好地拟合预测出未知的年龄。再一个,以我们地日常经验,小朋友和老人可能
    得到地照顾会多一些,这样看的话,年龄作为一个连续值,给一个固定的系数,应该和年龄是一个正相关或者负相关,西湖体现不出两头受照顾的实际情况,
    所以,说不定把年龄离散化,按区段分作类别属性会更合适一些。

    上面只是瞎想的,是不是这么回事呢,老老实实先把得到的model系数和feature关联起来看看。
LR模型系数
pd.DataFrame({"columns":list(train_df.columns)[1:],"coef":list(clf.coef_.T)})

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第20张图片

交叉验证

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

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

    先简单看看cross validation情况下的打分
from sklearn.model_selection import cross_val_score,train_test_split

#简单看看打分情况
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.iloc[:,1:]
y = all_data.iloc[:,0]
print(cross_val_score(clf,X,y,cv=5))

[ 0.81564246 0.81564246 0.78651685 0.78651685 0.81355932]
结果是[ 0.81564246 0.81564246 0.78651685 0.78651685 0.81355932]
似乎比Kaggle上的结果略高,毕竟用的不是同一份数据集评估的。
既然要做交叉验证,干脆就把交叉验证里面bad case拿出来看看,看看人眼审核,是否能发现什么蛛丝马迹,是忽略了哪些信息,使得这些乘客被判定错了。再把bad case上面得到的想法和前面系数分析的合在一起,然后逐个试试。
下面做数据分割,并再原始数据集上瞄一眼bad case:

#分隔数据,按照训练数据:测试数据 = 7:3
split_train,split_cv = train_test_split(df,test_size=0.3,random_state=42)

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.iloc[:,1:],train_df.iloc[:,0])

#对cross validation数据进行预测
cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
predictions = clf.predict(cv_df.iloc[:,1:])

origin_data_train = pd.read_csv("e:/data/Titanic_data/train.csv")
bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.iloc[:,0]]['PassengerId'].values)]
bad_cases

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第21张图片
可以跑一遍自己试试,拿到bad cases之后,仔细看看。也会有一些猜测和想法。其中会有一部分印证在系数分析部分的猜测,那这些优化的想法的优先级可以放高一些。

现在有了"train_df"和"cv_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,然后坚信它一定有效,结果试验下来,效果还不如试验之前的结果。恩,需要坚持和耐心,以及不断的挖掘。

learning curves

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

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

过拟合和欠拟合解释起来就是:

  • 过拟合就像是你班上学数学比较刻板的同学,老师讲过的题目,一字不漏全记下来了,于是老师再出一样的题目,分分钟精确出结果。but数学考试,
    总是碰到新题目,所以成绩不咋地。
  • 欠拟合就像是,差生。练老师讲的练习题也记不住,于是连老师出一样题目复习的周测都做不好,考试更是可想而知了。
    而在机器学习的问题上,对于过拟合和欠拟合两种情形,优化的方式是不同的。

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

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

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

著名的learing curve可以帮我们判定我们的模型现在所处状态。我们以样本数为横坐标,训练和交叉验证集上的错误率为纵坐标,两种状态分别如下两张图所示:过拟合(overfitting/high variace),欠拟合(underfitting/high bias)
kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第22张图片
我们也可可以把错误率替换成准确率,得到另一种形式的learning curve(sklearn 里面是这么做的)。

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

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve

#用sklearn的learning_curve 得到training_score和cv_scoer,使用matplotlib画出learning curve
def plot_learing_curve(estimator,title,X,y,ylim=None,cv=None,n_jobs=1,
                      train_size=np.linspace(.05,1.,20),verbose=0,plot=True):
    '''
    画出data在某模型上的learning curve.
    参数解释
    --------
    estimator:使用的分类器
    title:表格的标题
    X:输入的feature,numpy类型
    y:输入的target vactor
    ylim:tuple格式的(ymin,ymax),设定图像中纵坐标的最低点和最高点
    cv:做cross-validation的时候,数据分成的份数
    n_jobs:并行的任务数(默认1)
    '''
    train_sizes,train_scores,test_scores = learning_curve(
        estimator,X,y,cv=cv,n_jobs=n_jobs,train_sizes=train_size,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.gca().invert_yaxis()
        plt.show()
    
    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_learing_curve(clf, u"学习曲线", X, y)

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第23张图片
从实际数据上看,我们得到的learning curve 没有推到的那么光滑,但是可以大致看出来,训练集和交叉验证集上的得分走势曲线还是符合预期的。

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

模型融合(model ensemble)

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

  • 模型融合很重要
  • 模型融合很重要
  • 模型融合很重要

重要的事情说三遍。

什么是模型融合呢,举例子来直观理解一下。

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

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

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

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

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

既然这个时候模型没得选,而欧美让你就在数据上动动手脚。如果模型出现过拟合,一定是在我们的训练上出现拟合度过度造成的对吧。

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

from sklearn.ensemble import BaggingRegressor
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')

#y即Survived结果
y = train_df.iloc[:,0]

#X即特征属性值
X = train_df.iloc[:,1:]

#fir到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'],'Survived':predictions})
result.to_csv("e:/data/Titanic_data/logistic_regression_bagging_predictions.csv",index=False)
pd.read_csv('e:/data/Titanic_data/logistic_regression_bagging_predictions.csv')

kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第24张图片
然后再Make a submission,嗯,发现对结果还是有帮助的。
kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第25张图片

总结

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

【对数据的认识太重要了!】
【数据中的特殊点/离群点的分析和处理太重要了!】
【特征工程(feature engineering)太重要了!】
【模型融合(model ensemble)太重要了!】
本文中用机器学习解决问题的过程大概如下所示:
kaggle 入门:逻辑回归应用之Kaggle泰坦尼克之灾_第26张图片

你可能感兴趣的:(机器学习)