前面写了很多篇理论,大家愿意一篇一篇坚持看下来其实挺不容易的,虽然理论很重要,但脱离了实践还是空中楼阁啊,算法科学家也不可能不代码啊,所以呀,今天我们就插播一期实践,和大家聊一聊实际过程当中机器学习算法的应用。
对于我们这些初学者或者说外行来说,因为我们没机会接触到机器学习真正的应用项目,所以一些比赛平台往往是我们不错的选择,比如说这个Kaggle啊,前一段时间被Google收购还挺火的,还有国内的天池啊,DataFountain啊,上面都有不少比赛可以选择,但是入门嘛,我们就从最简单的开始,就是那个被无数文章博客提到过的泰坦尼克幸存者估计,我自己也看过一些,但是觉得很多文章把一开始数据探索部分写得太重了,反而后面特征选择和模型调参讲的很少,我感觉这样有些本末倒置的感觉,数据和特征决定了我们的上限。这里呢,我主要想讲的就是完成一个数据竞赛的整个流程以及其中最常见的一些套路,希望可以帮助大家可以快速入门竞赛,以期取得好成绩或者给自己求职增添砝码。
下面进入正题:
首先我们先观察一下我们的数据,看看哪些可以构建为我们的特征
import pandas as pd data=pd.read_csv('E:\Blog\Titanic\Train.csv') data.info() data.describe()
所有的数据中一共包括12个变量,其中7个是数值变量,5个是属性变量,接下来我们具体来看一看。
PassengerId:这是乘客的编号,显然对乘客是否幸存完全没有任何作用,仅做区分作用,所以我们就不考虑它了。
Survived:乘客最后的生存情况,这个是我们预测的目标变量。不过从平均数可以看出,最后存活的概率大概是38%。
Pclass:社会经济地位,这个很明显和生存结果相关啊,有钱人住着更加高级船舱可能会享受着更加高级的服务,因此遇险时往往会受到优待。所以这显然是我们要考虑的一个变量。
Name:这个变量看起来好像是没什么用啊,因为毕竟从名字你也不能看出能不能获救,但是仔细观察数据我们可以看到,所有人的名字里都包括了Mr,Mrs和Miss,从中是不是隐约可以看出来一些性别和年龄的信息呢,所以干脆把名字这个变量变成一个状态变量,包含Mr,Mrs和Miss这三种状态,但是放到机器学习里面我们得给它一个编码啊,最直接的想法就是0,1,2,但是这样真的合理吗?因为从距离的角度来说,这样Mr和Mrs的距离要小于Mr和Miss的距离,显然不合适,因为我们把它看成平权的三个状态。
所以,敲黑板,知识点来了,对于这种状态变量我们通常采取的措施是one-hot编码,什么意思呢,有几种状态就用一个几位的编码来表示状态,每种状态对应一个一位是1其余各位是0的编码,这样从向量的角度来讲,就是n维空间的n个基准向量,它们相互明显是平权的,此例中,我们分别用100,010,001来表示Mr,Mrs和Miss。
Sex:性别这个属性肯定是很重要的,毕竟全人类都讲究Lady First,所以遇到危险的时候,绅士们一定会先让女士逃走,因此女性的生存几率应该会大大提高。类似的,性别也是一个平权的状态变量,所以到时候我们同样采取one-hot编码的方式进行处理。
Age:这个变量和性别类似,都是明显会发挥重要作用的,因为无论何时,尊老爱幼总是为人们所推崇,但年龄对是否会获救的影响主要体现在那个人处在哪个年龄段,因此我们选择将它划分成一个状态变量,比如18以下叫child,18以上50以下叫adult,50以上叫elder,然后利用one-hot编码进行处理。不过这里还有一个问题就是年龄的值只有714个,它不全!这么重要的东西怎么能不全呢,所以我们只能想办法补全它。
又敲黑板,知识点又来了,缺失值我们怎么处理呢?最简单的方法,有缺失值的样本我们就扔掉,这种做法比较适合在样本数量很多,缺失值样本舍弃也可以接受的情况下,这样虽然信息用的不充分,但也不会引入额外的误差。然后,假装走心的方法就是用平均值或者中位数来填充缺失值,这通常是最简便的做法,但通常会带来不少的误差。最后,比较负责任的方法就是利用其它的变量去估计缺失变量的值,这样通常会更靠谱一点,当然也不能完全这样说,毕竟只要是估计值就不可避免的带来误差,但心理上总会觉得这样更好……
SibSp:船上兄弟姐妹或者配偶的数量。这个变量对最后的结果到底有什么影响我还真的说不准,但是预测年纪的时候说不定有用。
Parch:船上父母或者孩子的数量。这个变量和上个变量类似,我确实没有想到特别好的应用它的办法,同样的,预测年龄时这个应该挺靠谱的。
Ticket:船票的号码。恕我直言,这个谜一样的数字真的是不知道有什么鬼用,果断放弃了。
Fare:船票价格,这个变量的作用其实类似于社会地位,船票价格越高,享受的服务越高档,所以遇难获救的概率肯定相对较高,所以这是一个必须要考虑进去的变量。
Cabin:船舱号,这个变量或许透露出了一点船舱等级的信息,但是说实话,你的缺失值实在是太多了,我要是把它补全引入的误差感觉比它提供的信息还多,所以忍痛割爱,和你say goodbye!
Embarked:登船地点,按道理来说,这个变量应该是没什么卵用的,但介于它是一个只有三个状态的状态变量,那我们就把它处理一下放进模型,万一有用呢对吧。另外,它有两个缺失值,这里我们就不大动干戈的去预测了,就直接把它定为登船人数最多的S吧。
好的,到这里我们对所有变量应该如何处理大致有谱了,状态变量进行one-hot编码,那数值变量呢,直接用吗?
知识点!对于数值变量,我们通常会先进行归一化处理,这样有利于我们加快收敛速度,将各个维度限制在差不多的区间内,对一些基于距离的分类器有着非常大的好处,但是对于决策树一类的算法其实就没有意义了,不过这边我们就对所有的数值变量都做一个归一化处理吧。
到了这里,想必思路已经很清晰了,下面我们再梳理一下过程:
1 剔除PassengerId,Ticket这两个个变量,我们不用。
2 将Embarked变量补全,然后对Survived,Name,Sex, Embarked进行one-hot编码。
3对Pclass,Fare,Sibsp和Parch进行归一化处理。
3 根据Name,Sex,SibSp,Parch预测age将其补全。
4 对age进行归一化处理。
5 将未编码的Survived提出当做目标变量。
具体的代码实现如下:
import pandas as pd data=pd.read_csv('E:\Blog\Titanic\Train.csv') #剔除变量 data.drop(['PassengerId','Ticket'],axis=1,inplace=True) #补全Embarked变量 data.loc[data.Embarked.isnull(),'Embarked']='S' #one-hot编码 from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import LabelEncoder #ohe_pclass=OneHotEncoder(sparse=False).fit(data[['Pclass']]) #Pclass_ohe=ohe_pclass.transform(data[['Pclass']]) le_sex=LabelEncoder().fit(data['Sex']) Sex_label=le_sex.transform(data['Sex']) ohe_sex=OneHotEncoder(sparse=False).fit(Sex_label.reshape(-1,1)) Sex_ohe=ohe_sex.transform(Sex_label.reshape(-1,1)) le_embarked=LabelEncoder().fit(data['Embarked']) Embarked_label=le_embarked.transform(data['Embarked']) ohe_embarked=OneHotEncoder(sparse=False).fit(Embarked_label.reshape(-1,1)) Embarked_ohe=ohe_embarked.transform(Embarked_label.reshape(-1,1)) def replace_name(x): if 'Mrs' in x: return 'Mrs' elif 'Mr' in x: return 'Mr' else: return 'Miss' data['Name']=data['Name'].map(lambda x:replace_name(x)) le_name=LabelEncoder().fit(data['Name']) Name_label=le_name.transform(data['Name']) ohe_name=OneHotEncoder(sparse=False).fit(Name_label.reshape(-1,1)) Name_ohe=ohe_name.transform(Name_label.reshape(-1,1)) data['Sex_0']=Sex_ohe[:,0] data['Sex_1']=Sex_ohe[:,1] data['Embarked_0']=Embarked_ohe[:,0] data['Embarked_1']=Embarked_ohe[:,1] data['Embarked_2']=Embarked_ohe[:,2] data['Name_0']=Name_ohe[:,0] data['Name_1']=Name_ohe[:,1] data['Name_2']=Name_ohe[:,2] #归一化处理 from sklearn.preprocessing import StandardScaler Pclass_scale=StandardScaler().fit(data['Pclass']) data['Pclass_scaled']=StandardScaler().fit_transform(data['Pclass'].reshape(-1,1),Pclass_scale) Fare_scale=StandardScaler().fit(data['Fare']) data['Fare_scaled']=StandardScaler().fit_transform(data['Fare'].reshape(-1,1),Fare_scale) SibSp_scale=StandardScaler().fit(data['SibSp']) data['SibSp_scaled']=StandardScaler().fit_transform(data['SibSp'].reshape(-1,1),SibSp_scale) Parch_scale=StandardScaler().fit(data['Parch']) data['Parch_scaled']=StandardScaler().fit_transform(data['Parch'].reshape(-1,1),Parch_scale) #预测年纪并补全 from sklearn.ensemble import RandomForestRegressor def set_missing_age(data): train=data[['Age','SibSp_scaled','Parch_scaled','Name_0','Name_1','Name_2','Sex_0','Sex_1']] known_age=train[train.Age.notnull()].as_matrix() unknown_age=train[train.Age.isnull()].as_matrix() y=known_age[:,0] x=known_age[:,1:] rf=RandomForestRegressor(random_state=0,n_estimators=200,n_jobs=-1) rf.fit(x,y) print rf.score(x,y) predictage=rf.predict(unknown_age[:,1:]) data.loc[data.Age.isnull(),'Age']=predictage return data,rf data,rf=set_missing_age(data) Age_scale=StandardScaler().fit(data['Age']) data['Age_scaled']=StandardScaler().fit_transform(data['Age'].reshape(-1,1),Age_scale) train_x=data[['Sex_0','Sex_1','Embarked_0','Embarked_1','Embarked_2','Name_0','Name_1','Name_2','Pclass_scaled','Age_scaled','Fare_scaled']].as_matrix() train_y=data['Survived'].as_matrix()
完成了我们的数据预处理和特征工程之后,就开始选择合适的机器学习模型来进行学习就ok了。
这很显然是个分类问题,那么我们现在可以的选择有逻辑回归(一个名字叫回归却干着分类器的活的家伙),决策树以及决策树提升的一些算法(包括什么GDBT,AdaBoost,RandomForest等等),还有SVC,甚至聚类算法我们都可以试试……不过呢,花板子我们就不玩了,这里我们就选择逻辑回归,支持向量分类器,随机森林分类器和梯度提升分类器来做一下,看看它们在训练集上的表现如何:
#模型构造 from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression x_tr,x_te,y_tr,y_te=train_test_split(train_x,train_y,test_size=0.3,random_state=0) lr=LogisticRegression(C=1.0,tol=1e-6) lr.fit(x_tr,y_tr) print lr.score(x_te,y_te) from sklearn.svm import SVC svc=SVC(C=2, kernel='rbf', decision_function_shape='ovo') svc.fit(x_tr,y_tr) print svc.score(x_te,y_te) from sklearn.ensemble import RandomForestClassifier randomf=RandomForestClassifier(n_estimators=500,max_depth=5,random_state=0) randomf.fit(x_tr,y_tr) print randomf.score(x_te,y_te) from sklearn.ensemble import GradientBoostingClassifier gdbt=GradientBoostingClassifier(n_estimators=600,max_depth=5,random_state=0) gdbt.fit(x_tr,y_tr) print gdbt.score(x_te,y_te)
输出的结果为
0.783582089552
0.832089552239
0.824626865672
0.813432835821
我们可以看出SVC的效果最好,当然这有一定的随机性在里面,那我们就改变一下划分训练集和测试集的种子,看看结果是否会发生变化,将Random_state的值改为1,输出为
0.768656716418
0.787313432836
0.779850746269
0.768656716418
整体的正确率都发生了下降,但SVC的效果依然是做好的,所以我们不妨用SVC做一个结果先提交了看看正确率如何。那要想用这个模型进行预测,那我们要对测试集的数据做和训练集数据同样的事儿,包括补全无效值,预测年龄,one-hot编码以及归一化等等,只有这样我们的训练模型才能最大限度的发挥它的作用。完成预测之后,我们要将对应的ID和预测结果写入一个csv文件提交,实现的代码如下:
#预测数据 data_test=pd.read_csv('test.csv') data_test.drop(['Ticket'],axis=1,inplace=True) data_test.loc[data_test.Embarked.isnull(),'Embarked']='S' Sex_label_test=le_sex.transform(data_test['Sex']) Sex_ohe_test=ohe_sex.transform(Sex_label_test.reshape(-1,1)) Embarked_label_test=le_embarked.transform(data_test['Embarked']) Embarked_ohe_test=ohe_embarked.transform(Embarked_label_test.reshape(-1,1)) data_test['Name']=data_test['Name'].map(lambda x:replace_name(x)) Name_label_test=le_name.transform(data_test['Name']) Name_ohe_test=ohe_name.transform(Name_label_test.reshape(-1,1)) data_test['Sex_0']=Sex_ohe_test[:,0] data_test['Sex_1']=Sex_ohe_test[:,1] data_test['Embarked_0']=Embarked_ohe_test[:,0] data_test['Embarked_1']=Embarked_ohe_test[:,1] data_test['Embarked_2']=Embarked_ohe_test[:,2] data_test['Name_0']=Name_ohe_test[:,0] data_test['Name_1']=Name_ohe_test[:,1] data_test['Name_2']=Name_ohe_test[:,2] data_test['Pclass_scaled']=StandardScaler().fit_transform(data_test['Pclass'].reshape(-1,1),Pclass_scale) data_test.loc[data_test.Fare.isnull(),'Fare']=0 data_test['Fare_scaled']=StandardScaler().fit_transform(data_test['Fare'].reshape(-1,1),Fare_scale) data_test['SibSp_scaled']=StandardScaler().fit_transform(data_test['SibSp'].reshape(-1,1),SibSp_scale) data_test['Parch_scaled']=StandardScaler().fit_transform(data_test['Parch'].reshape(-1,1),Parch_scale) train_test=data_test[['Age','SibSp_scaled','Parch_scaled','Name_0','Name_1','Name_2','Sex_0','Sex_1']] unknown_age_test=train_test[train_test.Age.isnull()].as_matrix() x_test=unknown_age_test[:,1:] predictage=rf.predict(x_test) data_test.loc[data_test.Age.isnull(),'Age']=predictage data_test['Age_scaled']=StandardScaler().fit_transform(data_test['Age'].reshape(-1,1),Age_scale) test_x=data_test[['Sex_0','Sex_1','Embarked_0','Embarked_1','Embarked_2','Name_0','Name_1','Name_2','Pclass_scaled','Age_scaled','Fare_scaled']].as_matrix() predictions=model.predict(test_x).astype(np.int32) result=pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(),'Survived':predictions}) result.to_csv('svc.csv',index=False)
将结果提交到Kaggle的网站之后,我们发现我们的准确率是79.904%,居然已经进了20%了,还是蛮顺利的嘛,那么如何继续提升我们的成绩呢?最有效的方法莫过于构建新的特征,寻找更有效的特征永远是提升正确率的王道。当然了,也有一些简单的办法有可能能帮助我们提高成绩,那就是三个臭皮匠顶个诸葛亮。我们选择几个相关性不是很大的分类器,用它们预测的结果进行投票,往往一定程度上也能提高我们的成绩,比如下面这种做法:
from sklearn.ensemble import VotingClassifier model=VotingClassifier(estimators=[('lr',lr),('svc',svc),('rf',randomf),('GDBT',gdbt)],voting='hard',weights=[0.5,1.5,0.6,0.6]) model.fit(x_tr,y_tr) print model.score(x_te,y_te)
输出为:
0.860830527497
测试集上的表现可真棒啊!!!这一下很大的鼓舞了我的信心,所以赶紧用这个模型做个结果提交一下看看。
结果,正确率变成了78.649%,正确率下降了,整段垮掉!!!前面说的那么有模有样,结果被无情的现实狠甩一记耳光啊……
不过最重要的还是这个思路,比如你可以和你的小伙伴各做一个模型,你们构建模型的思路肯定不一样啊,那么模型的相关性比较低,这样组合起来提升效果应该比较明显,我就强行这样圆一下吧。
其实我相信到这里大家也看出来了,做的这个模型我们还是把绝大数的时间花在了模型的构造上了,真正模型训练啥的并没有占据我们太多的时间。所以还是那句话,数据和特征决定了机器学习的上限,而模型和方法只能是逼近这个上限,好好做特征吧。
====================================分割线================================
本文作者:AI研习社
本文转自雷锋网禁止二次转载,原文链接