0
本文作者: AI研習(xí)社 | 2017-05-01 17:06 |
雷鋒網(wǎng)按:本文作者成達(dá),原文載于作者個人博客,雷鋒網(wǎng)已獲授權(quán)。
前面寫了很多篇理論,大家愿意一篇一篇堅持看下來其實(shí)挺不容易的,雖然理論很重要,但脫離了實(shí)踐還是空中樓閣啊,算法科學(xué)家也不可能不代碼啊,所以呀,今天我們就插播一期實(shí)踐,和大家聊一聊實(shí)際過程當(dāng)中機(jī)器學(xué)習(xí)算法的應(yīng)用。
對于我們這些初學(xué)者或者說外行來說,因?yàn)槲覀儧]機(jī)會接觸到機(jī)器學(xué)習(xí)真正的應(yīng)用項目,所以一些比賽平臺往往是我們不錯的選擇,比如說這個Kaggle啊,前一段時間被Google收購還挺火的,還有國內(nèi)的天池啊,DataFountain啊,上面都有不少比賽可以選擇,但是入門嘛,我們就從最簡單的開始,就是那個被無數(shù)文章博客提到過的泰坦尼克幸存者估計,我自己也看過一些,但是覺得很多文章把一開始數(shù)據(jù)探索部分寫得太重了,反而后面特征選擇和模型調(diào)參講的很少,我感覺這樣有些本末倒置的感覺,數(shù)據(jù)和特征決定了我們的上限。這里呢,我主要想講的就是完成一個數(shù)據(jù)競賽的整個流程以及其中最常見的一些套路,希望可以幫助大家可以快速入門競賽,以期取得好成績或者給自己求職增添砝碼。
下面進(jìn)入正題:
首先我們先觀察一下我們的數(shù)據(jù),看看哪些可以構(gòu)建為我們的特征
import pandas as pd
data=pd.read_csv('E:\Blog\Titanic\Train.csv')
data.info()
data.describe()
所有的數(shù)據(jù)中一共包括12個變量,其中7個是數(shù)值變量,5個是屬性變量,接下來我們具體來看一看。
PassengerId:這是乘客的編號,顯然對乘客是否幸存完全沒有任何作用,僅做區(qū)分作用,所以我們就不考慮它了。
Survived:乘客最后的生存情況,這個是我們預(yù)測的目標(biāo)變量。不過從平均數(shù)可以看出,最后存活的概率大概是38%。
Pclass:社會經(jīng)濟(jì)地位,這個很明顯和生存結(jié)果相關(guān)啊,有錢人住著更加高級船艙可能會享受著更加高級的服務(wù),因此遇險時往往會受到優(yōu)待。所以這顯然是我們要考慮的一個變量。
Name:這個變量看起來好像是沒什么用啊,因?yàn)楫吘箯拿帜阋膊荒芸闯瞿懿荒塬@救,但是仔細(xì)觀察數(shù)據(jù)我們可以看到,所有人的名字里都包括了Mr,Mrs和Miss,從中是不是隱約可以看出來一些性別和年齡的信息呢,所以干脆把名字這個變量變成一個狀態(tài)變量,包含Mr,Mrs和Miss這三種狀態(tài),但是放到機(jī)器學(xué)習(xí)里面我們得給它一個編碼啊,最直接的想法就是0,1,2,但是這樣真的合理嗎?因?yàn)閺木嚯x的角度來說,這樣Mr和Mrs的距離要小于Mr和Miss的距離,顯然不合適,因?yàn)槲覀儼阉闯善綑?quán)的三個狀態(tài)。
所以,敲黑板,知識點(diǎn)來了,對于這種狀態(tài)變量我們通常采取的措施是one-hot編碼,什么意思呢,有幾種狀態(tài)就用一個幾位的編碼來表示狀態(tài),每種狀態(tài)對應(yīng)一個一位是1其余各位是0的編碼,這樣從向量的角度來講,就是n維空間的n個基準(zhǔn)向量,它們相互明顯是平權(quán)的,此例中,我們分別用100,010,001來表示Mr,Mrs和Miss。
Sex:性別這個屬性肯定是很重要的,畢竟全人類都講究Lady First,所以遇到危險的時候,紳士們一定會先讓女士逃走,因此女性的生存幾率應(yīng)該會大大提高。類似的,性別也是一個平權(quán)的狀態(tài)變量,所以到時候我們同樣采取one-hot編碼的方式進(jìn)行處理。
Age:這個變量和性別類似,都是明顯會發(fā)揮重要作用的,因?yàn)闊o論何時,尊老愛幼總是為人們所推崇,但年齡對是否會獲救的影響主要體現(xiàn)在那個人處在哪個年齡段,因此我們選擇將它劃分成一個狀態(tài)變量,比如18以下叫child,18以上50以下叫adult,50以上叫elder,然后利用one-hot編碼進(jìn)行處理。不過這里還有一個問題就是年齡的值只有714個,它不全!這么重要的東西怎么能不全呢,所以我們只能想辦法補(bǔ)全它。
又敲黑板,知識點(diǎn)又來了,缺失值我們怎么處理呢?最簡單的方法,有缺失值的樣本我們就扔掉,這種做法比較適合在樣本數(shù)量很多,缺失值樣本舍棄也可以接受的情況下,這樣雖然信息用的不充分,但也不會引入額外的誤差。然后,假裝走心的方法就是用平均值或者中位數(shù)來填充缺失值,這通常是最簡便的做法,但通常會帶來不少的誤差。最后,比較負(fù)責(zé)任的方法就是利用其它的變量去估計缺失變量的值,這樣通常會更靠譜一點(diǎn),當(dāng)然也不能完全這樣說,畢竟只要是估計值就不可避免的帶來誤差,但心理上總會覺得這樣更好……
SibSp:船上兄弟姐妹或者配偶的數(shù)量。這個變量對最后的結(jié)果到底有什么影響我還真的說不準(zhǔn),但是預(yù)測年紀(jì)的時候說不定有用。
Parch:船上父母或者孩子的數(shù)量。這個變量和上個變量類似,我確實(shí)沒有想到特別好的應(yīng)用它的辦法,同樣的,預(yù)測年齡時這個應(yīng)該挺靠譜的。
Ticket:船票的號碼。恕我直言,這個謎一樣的數(shù)字真的是不知道有什么鬼用,果斷放棄了。
Fare:船票價格,這個變量的作用其實(shí)類似于社會地位,船票價格越高,享受的服務(wù)越高檔,所以遇難獲救的概率肯定相對較高,所以這是一個必須要考慮進(jìn)去的變量。
Cabin:船艙號,這個變量或許透露出了一點(diǎn)船艙等級的信息,但是說實(shí)話,你的缺失值實(shí)在是太多了,我要是把它補(bǔ)全引入的誤差感覺比它提供的信息還多,所以忍痛割愛,和你say goodbye!
Embarked:登船地點(diǎn),按道理來說,這個變量應(yīng)該是沒什么卵用的,但介于它是一個只有三個狀態(tài)的狀態(tài)變量,那我們就把它處理一下放進(jìn)模型,萬一有用呢對吧。另外,它有兩個缺失值,這里我們就不大動干戈的去預(yù)測了,就直接把它定為登船人數(shù)最多的S吧。
好的,到這里我們對所有變量應(yīng)該如何處理大致有譜了,狀態(tài)變量進(jìn)行one-hot編碼,那數(shù)值變量呢,直接用嗎?
知識點(diǎn)!對于數(shù)值變量,我們通常會先進(jìn)行歸一化處理,這樣有利于我們加快收斂速度,將各個維度限制在差不多的區(qū)間內(nèi),對一些基于距離的分類器有著非常大的好處,但是對于決策樹一類的算法其實(shí)就沒有意義了,不過這邊我們就對所有的數(shù)值變量都做一個歸一化處理吧。
到了這里,想必思路已經(jīng)很清晰了,下面我們再梳理一下過程:
1 剔除PassengerId,Ticket這兩個個變量,我們不用。
2 將Embarked變量補(bǔ)全,然后對Survived,Name,Sex, Embarked進(jìn)行one-hot編碼。
3對Pclass,F(xiàn)are,Sibsp和Parch進(jìn)行歸一化處理。
3 根據(jù)Name,Sex,SibSp,Parch預(yù)測age將其補(bǔ)全。
4 對age進(jìn)行歸一化處理。
5 將未編碼的Survived提出當(dāng)做目標(biāo)變量。
具體的代碼實(shí)現(xiàn)如下:
import pandas as pd
data=pd.read_csv('E:\Blog\Titanic\Train.csv')
#剔除變量
data.drop(['PassengerId','Ticket'],axis=1,inplace=True)
#補(bǔ)全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)
#預(yù)測年紀(jì)并補(bǔ)全
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()
完成了我們的數(shù)據(jù)預(yù)處理和特征工程之后,就開始選擇合適的機(jī)器學(xué)習(xí)模型來進(jìn)行學(xué)習(xí)就ok了。
這很顯然是個分類問題,那么我們現(xiàn)在可以的選擇有邏輯回歸(一個名字叫回歸卻干著分類器的活的家伙),決策樹以及決策樹提升的一些算法(包括什么GDBT,AdaBoost,RandomForest等等),還有SVC,甚至聚類算法我們都可以試試……不過呢,花板子我們就不玩了,這里我們就選擇邏輯回歸,支持向量分類器,隨機(jī)森林分類器和梯度提升分類器來做一下,看看它們在訓(xùn)練集上的表現(xiàn)如何:
#模型構(gòu)造
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)
輸出的結(jié)果為
0.783582089552
0.832089552239
0.824626865672
0.813432835821
我們可以看出SVC的效果最好,當(dāng)然這有一定的隨機(jī)性在里面,那我們就改變一下劃分訓(xùn)練集和測試集的種子,看看結(jié)果是否會發(fā)生變化,將Random_state的值改為1,輸出為
0.768656716418
0.787313432836
0.779850746269
0.768656716418
整體的正確率都發(fā)生了下降,但SVC的效果依然是做好的,所以我們不妨用SVC做一個結(jié)果先提交了看看正確率如何。那要想用這個模型進(jìn)行預(yù)測,那我們要對測試集的數(shù)據(jù)做和訓(xùn)練集數(shù)據(jù)同樣的事兒,包括補(bǔ)全無效值,預(yù)測年齡,one-hot編碼以及歸一化等等,只有這樣我們的訓(xùn)練模型才能最大限度的發(fā)揮它的作用。完成預(yù)測之后,我們要將對應(yīng)的ID和預(yù)測結(jié)果寫入一個csv文件提交,實(shí)現(xiàn)的代碼如下:
#預(yù)測數(shù)據(jù)
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)
將結(jié)果提交到Kaggle的網(wǎng)站之后,我們發(fā)現(xiàn)我們的準(zhǔn)確率是79.904%,居然已經(jīng)進(jìn)了20%了,還是蠻順利的嘛,那么如何繼續(xù)提升我們的成績呢?最有效的方法莫過于構(gòu)建新的特征,尋找更有效的特征永遠(yuǎn)是提升正確率的王道。當(dāng)然了,也有一些簡單的辦法有可能能幫助我們提高成績,那就是三個臭皮匠頂個諸葛亮。我們選擇幾個相關(guān)性不是很大的分類器,用它們預(yù)測的結(jié)果進(jìn)行投票,往往一定程度上也能提高我們的成績,比如下面這種做法:
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
測試集上的表現(xiàn)可真棒啊?。。∵@一下很大的鼓舞了我的信心,所以趕緊用這個模型做個結(jié)果提交一下看看。
結(jié)果,正確率變成了78.649%,正確率下降了,整段垮掉?。?!前面說的那么有模有樣,結(jié)果被無情的現(xiàn)實(shí)狠甩一記耳光啊……
不過最重要的還是這個思路,比如你可以和你的小伙伴各做一個模型,你們構(gòu)建模型的思路肯定不一樣啊,那么模型的相關(guān)性比較低,這樣組合起來提升效果應(yīng)該比較明顯,我就強(qiáng)行這樣圓一下吧。
其實(shí)我相信到這里大家也看出來了,做的這個模型我們還是把絕大數(shù)的時間花在了模型的構(gòu)造上了,真正模型訓(xùn)練啥的并沒有占據(jù)我們太多的時間。所以還是那句話,數(shù)據(jù)和特征決定了機(jī)器學(xué)習(xí)的上限,而模型和方法只能是逼近這個上限,好好做特征吧。
從初級到高級,理論 + 實(shí)戰(zhàn),一站式深度了解 TensorFlow!
本課程面向深度學(xué)習(xí)開發(fā)者,講授如何利用 TensorFlow 解決圖像識別、文本分析等具體問題。課程跨度為 10 周,將從 TensorFlow 的原理與基礎(chǔ)實(shí)戰(zhàn)技巧開始,一步步教授學(xué)員如何在 TensorFlow 上搭建 CNN、自編碼、RNN、GAN 等模型,并最終掌握一整套基于 TensorFlow 做深度學(xué)習(xí)開發(fā)的專業(yè)技能。
兩名授課老師佟達(dá)、白發(fā)川身為 ThoughtWorks 的資深技術(shù)專家,具有豐富的大數(shù)據(jù)平臺搭建、深度學(xué)習(xí)系統(tǒng)開發(fā)項目經(jīng)驗(yàn)。
時間:每周二、四晚 20:00-21:00
開課時長:總學(xué)時 20 小時,分 10 周完成,每周 2 次,每次 1 小時
線上授課地址:http://www.mooc.ai/
雷鋒網(wǎng)相關(guān)閱讀:
加入 Kaggle 大數(shù)據(jù)競賽,總共分幾步?
詳解 Kaggle 房價預(yù)測競賽優(yōu)勝方案:用 Python 進(jìn)行全面數(shù)據(jù)探索
從 Kaggle 困局,看國內(nèi)數(shù)據(jù)競賽平臺如何突圍
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。