传送门:
首先更正上次的一个失误,上次确定年龄的最大值时我使用了目测法,数据少的时候还好,数据多了肯定要瞎,正确的姿势是采用describe()函数:
In[3]: dt_train_p.describe()
Out[3]:
PassengerId Survived Pclass Age SibSp \
count 891.000000 891.000000 891.000000 714.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008
std 257.353842 0.486592 0.836071 14.526497 1.102743
min 1.000000 0.000000 1.000000 0.420000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000
50% 446.000000 0.000000 3.000000 28.000000 0.000000
75% 668.500000 1.000000 3.000000 38.000000 1.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000
Parch Fare Relatives
count 891.000000 891.000000 891.000000
mean 0.381594 32.204208 0.904602
std 0.806057 49.693429 1.613459
min 0.000000 0.000000 0.000000
25% 0.000000 7.910400 0.000000
50% 0.000000 14.454200 0.000000
75% 0.000000 31.000000 1.000000
max 6.000000 512.329200 10.000000
基本上所有的基本统计量都在里面了,非常详细,比目测靠谱多了。
继续机器学习的部分,对数据有个大致了解之后就是预测存活率的工作了,首先就是找个靠谱的算法。由于是计算存活情况,可以看做是典型的二分分类(0-1),那只要找个分类器就好啦~
之前正好在读《Building Machine Learning Systems with Python》,里面有个例子是评价StackOverflow上答案是好是坏的例子,跟这个颇有相似之处。答案好坏问题中,也是通过一堆特征(比如答案中的代码行数,链接数量等等)去区分一个二分的结果(好,坏),泰坦尼克号的问题也是通过一堆特征(比如舱位,性别等等)去区分一个二分的结果(生,死)。作者先是顺着之前的方法使用kNN来分类,但效果并不好,然后作者在改进方法中推出了Logistic回归。
Logistic回归的原理就不多说了,一来网上很多,二来我也解释不太清楚……反正大意貌似是将每个特征量按照各自权重作线型组合,组合成一个z值:
z=w0x0+w1x1+w2x2+…+wnxn
然后计算z的Sigmoid函数值:
g(z)=1/(1+e-z)
这个函数的值域也就是(0,1),那g(z)≥0.5就算作1,否则就算作0,以此来完成二分分类。
那我们可以用train里的数据来训练分类器,然后来根据test的features数据来预测每个人是否生存。
那接着就是训练分类器,这里我着实又耗费了不少精力。我们可以把之前的代码全都保存成一个文件,我这里叫FirstStep.py,然后我新建了一个py文件来进行后续处理,这样主要看起来清楚一点。
由于需要引用FirstStep.py里的数据,所以我就直接:
from FirstStep import *
这样比较方便,等于是接着FirstStep.py后面写,但要注意命名空间的问题。
然后就是数据清理的问题,按照先前的结论,我主要选择的数据是Pclass、Sex、Age、Embarked、Relatives(即SibSp+Parch),其中性别和上船地点要转化为数值,否则无法fit,这里用map()就能解决。然后就是数据缺失的问题,抛开我们排除的数据项,就上述5项来说,在train的数据中,主要是Age和Embarked有缺失,而test的数据中,主要缺失的是Age,Embarked倒是很完整。那我的思路就是用2个classifier,一个是用上述5项作为features,用来预测数据完整的项;另一个用去除Age之后的4项作为features,用来预测缺少Age的项。
代码如下:
dt_train_p['Relatives']=dt_train_p['SibSp']+dt_train_p['Parch'] #Relatives=SibSp+Parch,亲属人数
dt_learn=dt_train_p.copy()
sex_to_digi={'male':1,'female':0}
ebk_to_digi={'C':-1,'Q':0,'S':1}
dt_learn['Sex']=dt_learn['Sex'].map(sex_to_digi) #将性别转化为数值
dt_learn['Embarked']=dt_learn['Embarked'].map(ebk_to_digi) #将上船地点转化为数值
dt_learnNA=dt_learn[np.isnan(dt_learn['Age'])] #不含Age项
dt_learn=dt_learn.dropna() #含Age项,与上一句顺序不要颠倒
dt_learn_features=dt_learn[['Pclass','Sex','Age','Embarked','Relatives']] #选取features
dt_learnNA_features=dt_learnNA[['Pclass','Sex','Embarked','Relatives']] #选取features
dt_learn_target=dt_learn['Survived'] #选取target
dt_learnNA_target=dt_learnNA['Survived'] #选取target
好了,到此为止,features和target都选取完毕了,接下来就要靠sklearn这个高大上的库了!
我们可以直接从sklearn里导入Logistic Regression,当然,不要忘记Cross Validation!《Building Machine Learning Systems with Python》里反复强调CV的重要性和必要性。代码如下:
from sklearn.linear_model import LogisticRegression #导入逻辑回归模块
from sklearn import cross_validation #导入交叉检验
classifier = LogisticRegression() #有Age
scores=cross_validation.cross_val_score(classifier,dt_learn_features,dt_learn_target,cv=5) #交叉检验
print scores,scores.mean()
classifierNA = LogisticRegression() #无Age
scoresNA=cross_validation.cross_val_score(classifierNA,dt_learnNA_features,dt_learnNA_target,cv=5) #交叉检验
print scoresNA,scoresNA.mean()
就在Cross Validation上我差点就憋伤了……我本来按照书里的做法,从cross_validation里导入KFold,然后依葫芦画瓢:
cv = KFold(n=len(trainp2),n_folds=5,indices=True)
结果后续步骤怎么写都报错……网上查了好久,写了句:
[classifier.fit(trainp2.ix[train],trainp_target[train]).score(trainp2.ix[test],trainp_target[test]) for train, test in cv]
[classifier.fit(trainp2[0:700],trainp_target[0:700]).score(trainp2[701:890],trainp_target[701:890])
这样终于对了,因为每次fit是多个数据,但这样我岂不是要手动CV?这应该也不是个科学的方法,直到我查到了刚才代码里的方法,原来这么方便……
运行结果:
[ 0.76223776 0.83216783 0.75524476 0.76056338 0.82978723] 0.788000192795
[ 0.83333333 0.86111111 0.94285714 0.74285714 0.85714286] 0.84746031746
第一行是包含Age的,第二行不包含Age,看起来结果还不错啊~然后调一下分类器的参数C(默认C=1),形如:
classifier = LogisticRegression(C=10000)
C=0.0001时的结果:
[ 0.59440559 0.59440559 0.59440559 0.59859155 0.59574468] 0.595510602673
[ 0.69444444 0.69444444 0.71428571 0.71428571 0.71428571] 0.706349206349
C=10000时的结果:
[ 0.76223776 0.82517483 0.76923077 0.77464789 0.81560284] 0.789378816169
[ 0.83333333 0.86111111 0.94285714 0.77142857 0.85714286] 0.853174603175
当然,我们取C=10000,有兴趣可以0.01、0.1、10、100一个个试过来。此外我试过在dt_learn_features中去除Age、Embarked、Relatives之中的一项或几项,但效果不如保留的好,所以我也就不动它了。之后对于test的数据,只要使用classifier.predict()来完成预测就可以了。
我们可以在gendermodel.csv中看到test里每位乘客最后的生存情况,为了看看正确率,我把结果(Survived一列)复制进了test.csv,另存成testN.csv,接下来就是最后的步骤了:
#test数据预处理
dt_test=pd.read_csv('D:\\Documents\\Python Scripts\\kaggle-titanic\\testN.csv')
dt_test['Sex']=dt_test['Sex'].map(sex_to_digi)
dt_test['Embarked']=dt_test['Embarked'].map(ebk_to_digi)
dt_test['Relatives']=dt_test['SibSp']+dt_test['Parch']
dt_test['Predict']='' #此处要先添加一列,不然后面会出错
dt_test_features=dt_test[['Pclass','Sex','Age','Embarked','Relatives']]
dt_test_target=dt_test['Survived'] #此为最终结果
#拟合
classifier.fit(dt_learn_features,dt_learn_target)
classifierNA.fit(dt_learnNA_features,dt_learnNA_target)
#分情况预测
for i in range(len(dt_test)):
if np.isnan(dt_test['Age'][i]):
dt_test_features.ix[i]=dt_test[['Pclass','Sex','Embarked','Relatives']].ix[i]
dt_test['Predict'][i]=classifierNA.predict(dt_test_features.ix[i].dropna())
else:
dt_test['Predict'][i]=classifier.predict(dt_test_features.ix[i])
print dt_test_target[i],int(dt_test['Predict'][i])
#计算正确率
acc=1-float(sum(abs(dt_test['Predict']-dt_test_target)))/dt_test['Predict'].count()
print acc
可以看到Survived(正确答案)和Predict(程序预测)一行行跳出来,最后跳出正确率和一些警告,警告我就暂时不管他了,反正不报错就好了~结果截个图:
最终正确率高达93%!也就是说计算机通过对train数据的学习训练,有93%的几率预测准test中的人是否活下来,我个人对这个结果是非常满意啦~
当然,在过程中还发现了一些有意思的内容,比如性别和上船地点数值化的时候,这个取值对拟合的结果似乎也有影响,比如Embarked一项中C、Q、S取(-1,0,1) 和取(1,10,100) CV出的数据都略有差别,具体是为什么我觉得我还要学习一个,提高姿势水平。
这一小步走的很艰辛,但非常有成就感!