Kaggle:入门赛Tatanic(泰坦尼克号)84.21%带你冲进前2%
Kaggle:入門賽Tatanic(泰坦尼克號)84.21%帶你沖進前2%
0. 前言
可能你和我一樣是機器學習的入門者,或許看過吳恩達老師的課,或許看過李航的《統計方法》,或許看過周志華的《機器學習》。機器學習的武功心法牢記在心,人在江湖總是要與人過招的,有了心法就要練習。Kaggle的泰坦尼克號是一個經典的實戰項目,我也是個新手拜讀過多篇該項目的技術文章,走過很多彎路。故讓我們一起走進今天這邊基礎教學,如有不對之處請拍磚指正,小生不勝感激。
1. 背景
《泰坦尼克號》的電影相比大家都已經看過,那么以這個背景的項目我們就不能忽略背景的意義。任何的數據都是在真實現實中產生的,我們要分析數據,挖掘數據中的關系,就不能脫離背景。通過閱讀項目的介紹和結合背景的理解,我有這么幾個思考:
(1)婦女和兒童先走,能否在數據中表現出來
(2)船是從英國出發,社會等級是否會有影響
(3)船艙所處的位置是否會有影響
帶著一些問題和思考讓我們進行下一步
2. 數據導入
%matplotlib inline import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.ensemble import RandomForestRegressor from sklearn.pipeline import Pipeline,make_pipeline from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier from sklearn.feature_selection import SelectKBest from sklearn import cross_validation, metrics from sklearn.grid_search import GridSearchCV, RandomizedSearchCV import warnings warnings.filterwarnings('ignore')train = pd.read_csv('train.csv',dtype={"Age": np.float64}) test = pd.read_csv('test.csv',dtype={"Age": np.float64}) PassengerId=test['PassengerId'] all_data = pd.concat([train, test], ignore_index = True)然后讓我們看看數據的一些分析:
train_data.info() <class 'pandas.core.frame.DataFrame'> 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對訓練數據的分析我們可以看到有12個屬性,同理我們對測試數據集也進行同樣的分析,我們可以看出Survived是需要我們在測試數據集給出的結果,1-存活,0-死亡。同時我們也注意到Embarked屬性有少量缺失,Age屬性缺失不多,而Cabin屬性缺失的很多。對于缺失屬性的處理我們在第四節給出解決方法。下面給出各個屬性代表的含義:
PassengerId => 乘客ID
Pclass => 乘客等級(1/2/3等艙位)
Name => 乘客姓名
Sex => 性別
Age => 年齡
SibSp => 堂兄弟/妹個數
Parch => 父母與小孩個數
Ticket => 船票信息
Fare => 票價
Cabin => 客艙
Embarked => 登船港口
3. 數據分析與可視化
在我們對數據有了基本的了解后,就要尋找數據與最后結果生存的關系了。首先可以分析單個屬性與生存的關系,然后多個屬性的結合與生存的關系。
3.1 單特征分析
3.1.1 性別與是否幸存的關系
按照我們背景里的介紹,女士與小孩先走。是否是這樣呢,讓我們一探究竟:
sns.barplot(x="Sex", y="Survived", data=train, palette='Set3') 通過上圖我們可看女性的生存率是男性的3倍多,這也從側面說明在哪個年代的西方人紳士的一面,當然不能否認西方人哪個時代侵略的事。3.1.2 船艙等級與是否幸存的關系
按照我們所想,船艙等級越高,人的身份可能越高。雖然災難面前人與人之間是平等的,但是身份越高是否擁有特權呢,數據是不會說謊的:
sns.barplot(x="Pclass", y="Survived", data=train, palette='Set3')從上圖我們可以看出,1,2,3艙的幸存率遞減,1等艙的幸存率甚者是3等艙幸存率的3倍,災難面前確實有些人存在優先權,各位看官多多掙錢吧。
3.1.3 年齡與幸存率的關系
這個與咱們在3.1.1中說的一樣,看下是否兒童能夠獲得更高的幸存率:
facet = sns.FacetGrid(train, hue="Survived",aspect=2) facet.map(sns.kdeplot,'Age',shade= True) facet.set(xlim=(0, train['Age'].max())) facet.add_legend()從上面我們可以看出兒童的幸存比死亡高,而成年人大致上是死亡比幸存高。孩子是社會的未來,我們更應該好好的保護他們成長,而不是接二連三虐童和把刀子伸向手無寸鐵的孩子們。
3.1.4 兄弟姐妹數與幸存率的關系
SibSp屬性既然存在,兄弟姐妹數可能會間接體現這個家庭的生活水平,一家人出行兄弟姐妹多的很大概率就是兒童,能從側面反應幸存率的問題:
sns.barplot(x="SibSp", y="Survived", data=train, palette='Set3')從圖中我們能看出兄弟姐妹數為1,2的幸存率高,甚至比3,4的高一倍以上。
3.1.5 父母子女數是否與幸存有關系
在我們的印象中很少有兒童獨自出遠行(那個國家的家長也不放心),所以我猜測為0會是成年人,并且幸存率一定不高。父母子女數高的幸存率也不會高,災難面前拖家帶口的會照顧很多人,而且成年人也會把一丁點生的機會留給孩子和伴侶,人間的大愛不過如此,你說對不。下面就驗證下咱們的猜測:
sns.barplot(x="Parch", y="Survived", data=train, palette='Set3')從上圖我們看出1,2,3的幸存率要高一些,都超過一半,0和5的都比較低,可以結合兄弟姐妹數再次進行分析。
3.1.6 ?票價與幸存是否有關系
按照我們的經歷,價位越高所處的位置就越好,可能社會地位就越高,可能也就更容易幸存。是否如此呢,讓我們一起來看看。
首先我們來看下票價的信息:
train['Fare'].describe() count 891.000000 mean 32.204208 std 49.693429 min 0.000000 25% 7.910400 50% 14.454200 75% 31.000000 max 512.329200 Name: Fare, dtype: float64我們得知,在訓練集中票價的均值為32,方差49,最便宜的為0(可能是船上工作人員),最貴的是512,75%的價位是31甚至都不到平均價格。讓我們畫個圖來看看。facet = sns.FacetGrid(train, hue="Survived",aspect=2) facet.map(sns.kdeplot,'Fare',shade= True) facet.set(xlim=(0, 200)) facet.add_legend()
我們可以看出大致隨著票價的增長幸存比死亡高,有75%的票價不超過31,而這部分都是死亡率高于幸存率,而高于的幸存率都高過死亡率的。這說明票價與幸存是有一定相關性的。
3.1.7?船艙類型和幸存與否的關系
船艙有很多的缺失值,其實很不好分析。但是對于缺失值我們有這么幾種分析思路:
(1)分為有值:1與缺失值:0進行對比分析
(2)缺失值填充字符,與其他一起進行分析
下面我們就用著2種思路進行分析一波,看看是否與幸存有關系。
對于第一種:
train.loc[train.Cabin.isnull(), 'Cabin'] = 'U0' train['Has_Cabin'] = train['Cabin'].apply(lambda x: 0 if x == 'U0' else 1) sns.barplot(x="Has_Cabin", y="Survived", data=train, palette='Set3') 我們可以看到有值的幸存率是缺失值的一倍,可見這是一個舍棄可惜的特征。對于第二種思想:
all_data['Cabin'] = all_data['Cabin'].fillna('Unknown') all_data['Deck']=all_data['Cabin'].str.get(0) sns.barplot(x="Deck", y="Survived", data=all_data, palette='Set3') 可見不同艙的幸存率不一樣。3.1.8 稱呼與幸存是否有關系
如果我們在日常英語的學習中,尤其是對英國文化了解的情況下,我們就會注意到對不同社會等級,不同年紀的人都會有不同的稱呼。我們試著從Name特征中提取一些有用的信息。閱讀了很多篇相關的文章,都會提取乘客的稱呼,歸納分類。
all_data['Title'] = all_data['Name'].apply(lambda x:x.split(',')[1].split('.')[0].strip()) Title_Dict = {} Title_Dict.update(dict.fromkeys(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer')) Title_Dict.update(dict.fromkeys(['Don', 'Sir', 'the Countess', 'Dona', 'Lady'], 'Royalty')) Title_Dict.update(dict.fromkeys(['Mme', 'Ms', 'Mrs'], 'Mrs')) Title_Dict.update(dict.fromkeys(['Mlle', 'Miss'], 'Miss')) Title_Dict.update(dict.fromkeys(['Mr'], 'Mr')) Title_Dict.update(dict.fromkeys(['Master','Jonkheer'], 'Master')) all_data['Title'] = all_data['Title'].map(Title_Dict) sns.barplot(x="Title", y="Survived", data=all_data, palette='Set3') Mr我們一般都是稱呼成年男士,其幸存率甚至都不到20%。Ofiicer可能是船上工作人員和一些政要,其幸存率與其他相比低一半。3.1.9 港口與幸存是否有關系
泰坦尼克號從英國的南安普頓港出發,途徑法國瑟堡和愛爾蘭昆士敦,如果是短途游客下船就不會遇難對吧。所以我們可以畫個圖看看:
sns.barplot(x="Embarked", y="Survived", data=train, palette='Set3') 從圖中我們可以看出C港口幸存率最高,S,Q之間差距不大,這種差別不大的特征我個人感覺可以先留著。3.2 創造新特征
3.2.1 家庭人數與幸存是否有關系
我們在3.1中分析到兄弟姐妹特征,父母子女數可以生成一個新的特征:家庭人數。家庭人數也可以從側面反映出一個家庭的經濟狀態,當然有一點我們不能忽略當家庭人員多的時候我們的行動都會受到限制需要照顧全家可能就會耽誤最佳逃生時機。下面我們來分析看看是否如我們所想:
all_data['FamilySize']=all_data['SibSp']+all_data['Parch']+1 sns.barplot(x="FamilySize", y="Survived", data=all_data, palette='Set3')我們可以看到家庭人數為2,3,4的幸存率明顯高于其他,當家庭人數為5,6,7我的猜測可能是需要照顧可能耽誤最佳時機,哪怕有兒童。通過閱讀其他文章,提供給我們一個新的思路:可以把家庭人數分為3類,生產新的數值特征便于分析。
def Fam_label(s):if (s >= 2) & (s <= 4):return 2elif ((s > 4) & (s <= 7)) | (s == 1):return 1elif (s > 7):return 0 all_data['FamilyLabel']=all_data['FamilySize'].apply(Fam_label) sns.barplot(x="FamilyLabel", y="Survived", data=all_data, palette='Set3')3.2.2 票號相同數與幸存是否有關系
我們在3.1中分析了9個特征,還記得嗎我們訓練集有12個特征,其中1個Survived,1個PassengerId,我們漏掉了Ticket。因為發現有的票號是相同的,這提示我們有些可能是家庭票,那這樣是否會和我們在3.2.1分析的家庭成員數為2,3,4的時候幸存率明顯高于其他呢,讓我們畫個圖來一看究竟:
Ticket_Count = dict(all_data['Ticket'].value_counts()) all_data['TicketGroup'] = all_data['Ticket'].apply(lambda x:Ticket_Count[x]) sns.barplot(x='TicketGroup', y='Survived', data=all_data, palette='Set3') 可以看下3.2.1中的圖與我們在3.2.2分析基本一致,這也印證了我們的猜測。同理我們可以把Ticket分為三類生成新的特征,代碼類似就不貼出。3.3 思考
我們能否構造新的特征,新的分析方法,留給大家自己思考。
4. ?數據清洗與缺失值填充
如果注意到我們上面的代碼,你會發現我用了train,test,all_data,其中對一些特征的處理都是在all_data中處理的,這樣我們一次就把訓練集和測試集處理了。這里處理的方法我們借鑒于知乎:SweetWine的一些思路同時加上我的一些思考。
4.1 Age 特征填充
在上面的分析中我們得知Age屬性缺失很多,看多很多填充的方法,有用平均數的,有用中位數的,這2種方法其實都沒有考慮到每個乘客的不同屬性,看似合理其實是最懶最不合理的。我們可以結合幾個特征構造隨機森林模型來填充Age。
from sklearn import cross_validationtrain = all_data[all_data['Survived'].notnull()] test = all_data[all_data['Survived'].isnull()] # 分割數據,按照 訓練數據:cv數據 = 1:1的比例 train_split_1, train_split_2 = cross_validation.train_test_split(train, test_size=0.5, random_state=0)def predict_age_use_cross_validationg(df1,df2,dfTest):age_df1 = df1[['Age', 'Pclass','Sex','Title']]age_df1 = pd.get_dummies(age_df1)age_df2 = df2[['Age', 'Pclass','Sex','Title']]age_df2 = pd.get_dummies(age_df2)known_age = age_df1[age_df1.Age.notnull()].as_matrix()unknow_age_df1 = age_df1[age_df1.Age.isnull()].as_matrix()unknown_age = age_df2[age_df2.Age.isnull()].as_matrix()print (unknown_age.shape)y = known_age[:, 0]X = known_age[:, 1:]rfr = RandomForestRegressor(random_state=0, n_estimators=100, n_jobs=-1)rfr.fit(X, y)predictedAges = rfr.predict(unknown_age[:, 1::])df2.loc[ (df2.Age.isnull()), 'Age' ] = predictedAges predictedAges = rfr.predict(unknow_age_df1[:,1::])df1.loc[(df1.Age.isnull()),'Age'] = predictedAgesage_Test = dfTest[['Age', 'Pclass','Sex','Title']]age_Test = pd.get_dummies(age_Test)age_Tmp = df2[['Age', 'Pclass','Sex','Title']]age_Tmp = pd.get_dummies(age_Tmp)age_Tmp = pd.concat([age_Test[age_Test.Age.notnull()],age_Tmp])known_age1 = age_Tmp.as_matrix()unknown_age1 = age_Test[age_Test.Age.isnull()].as_matrix()y = known_age1[:,0]x = known_age1[:,1:]rfr.fit(x, y)predictedAges = rfr.predict(unknown_age1[:, 1:])dfTest.loc[ (dfTest.Age.isnull()), 'Age' ] = predictedAges return dfTestt1 = train_split_1.copy() t2 = train_split_2.copy() tmp1 = test.copy() t5 = predict_age_use_cross_validationg(t1,t2,tmp1) t1 = pd.concat([t1,t2])t3 = train_split_1.copy() t4 = train_split_2.copy() tmp2 = test.copy() t6 = predict_age_use_cross_validationg(t4,t3,tmp2) t3 = pd.concat([t3,t4])train['Age'] = (t1['Age'] + t3['Age'])/2test['Age'] = (t5['Age'] + t6['Age']) / 2 all_data = pd.concat([train,test]) print (train.describe()) print (test.describe())這里為什么這么處理呢,對訓練集分為兩半train1,train2,train1用作訓練,然后填充train2的缺失值,然后用填充后的train2用來預測填充測試集。然后再來一次,這次用train2訓練,train1填充。盡量降低對測試集的過擬合。
4.2 Embarked 特征填充
Embarkde特征缺失很少,我們看看都有哪些乘客。
all_data[all_data['Embarked'].isnull()]我沒看到只缺失2個,我們去找尋這2個缺失值的共性,Sex,Pclass,SliSp,Cabin,Fare特征都相同,并且Embarked為登船口,我們可以結合我們生活,做火車地點和票價Fare有著密切的聯系。Pclass為1登陸口為C的用Fare為80.0,所以我們填充C。
all_data['Embarked'] = all_data['Embarked'].fillna('C')4.3 ?Fare 特征填充
Fare特征的缺失值也很少,我們同樣可以利用幾個相關的屬性的中位數來填充,這里只給出代碼:
fare=all_data[(all_data['Embarked'] == "S") & (all_data['Pclass'] == 3)].Fare.median() all_data['Fare']=all_data['Fare'].fillna(fare)4.4 同組識別
這部分,原封不動的借鑒SweetWine分享的思路。
把姓氏相同的乘客劃分為同一組,從人數大于一的組中分別提取出每組的婦女兒童和成年男性。
發現絕大部分女性和兒童組的平均存活率都為1或0,即同組的女性和兒童要么全部幸存,要么全部遇難。
Female_Child=pd.DataFrame(Female_Child_Group.groupby('Surname')['Survived'].mean().value_counts()) Female_Child.columns=['GroupCount'] Female_Child 絕大部分成年男性組的平均存活率也為1或0。Male_Adult=pd.DataFrame(Male_Adult_Group.groupby('Surname')['Survived'].mean().value_counts()) Male_Adult.columns=['GroupCount'] Male_Adult因為普遍規律是女性和兒童幸存率高,成年男性幸存較低,所以我們把不符合普遍規律的反常組選出來單獨處理。把女性和兒童組中幸存率為0的組設置為遇難組,把成年男性組中存活率為1的設置為幸存組,推測處于遇難組的女性和兒童幸存的可能性較低,處于幸存組的成年男性幸存的可能性較高。
Female_Child_Group=Female_Child_Group.groupby('Surname')['Survived'].mean() Dead_List=set(Female_Child_Group[Female_Child_Group.apply(lambda x:x==0)].index) print(Dead_List) Male_Adult_List=Male_Adult_Group.groupby('Surname')['Survived'].mean() Survived_List=set(Male_Adult_List[Male_Adult_List.apply(lambda x:x==1)].index) print(Survived_List)為了使處于這兩種反常組中的樣本能夠被正確分類,對測試集中處于反常組中的樣本的Age,Title,Sex進行懲罰修改。 train=all_data.loc[all_data['Survived'].notnull()] test=all_data.loc[all_data['Survived'].isnull()] test.loc[(test['Surname'].apply(lambda x:x in Dead_List)),'Sex'] = 'male' test.loc[(test['Surname'].apply(lambda x:x in Dead_List)),'Age'] = 60 test.loc[(test['Surname'].apply(lambda x:x in Dead_List)),'Title'] = 'Mr' test.loc[(test['Surname'].apply(lambda x:x in Survived_List)),'Sex'] = 'female' test.loc[(test['Surname'].apply(lambda x:x in Survived_List)),'Age'] = 5 test.loc[(test['Surname'].apply(lambda x:x in Survived_List)),'Title'] = 'Miss'我的思考:我們從上面單特征的分析就得知女性和兒童的幸存率很高,成年男性的幸存率很低。對于女性和兒童遇難的,成年男性幸存的都是異常的樣本,在SVM模型中可以加上松弛變量與懲罰因子的道理一樣。
4.5 特征轉換
在這里我們把處理數值類型特征
all_data=pd.concat([train, test]) all_data=all_data[['Survived','Pclass','Sex','Age','Fare','Embarked','Title','FamilyLabel','Deck','TicketGroup']] all_data=pd.get_dummies(all_data) train=all_data[all_data['Survived'].notnull()] test=all_data[all_data['Survived'].isnull()].drop('Survived',axis=1) X = train.as_matrix()[:,1:] y = train.as_matrix()[:,0]5. 模型選擇與調參
5.1.1模型選擇
隨機森林有著眾多優點,我們也對異常樣本進行了懲罰不用擔心過擬合的問題。當然GDBT,XGboost現在是數據挖掘比賽最常用的模型,但是面對這么小的數據量我還是建議用隨機森林,或者說用簡單的邏輯回歸模型(需對票價和年齡進行縮放)。
5.2. 調參優化
pipe=Pipeline([('select',SelectKBest(k=20)), ('classify', RandomForestClassifier(random_state = 10, max_features = 'sqrt'))])param_test = {'classify__n_estimators':list(range(20,50,2)), 'classify__max_depth':list(range(3,60,3))} gsearch = GridSearchCV(estimator = pipe, param_grid = param_test, scoring='roc_auc', cv=10) gsearch.fit(X,y) print(gsearch.best_params_, gsearch.best_score_)5.3 預測提交
我們結合5.2節,不斷的調整參數,然后預測提交獲得更高的分數。
select = SelectKBest(k = 20) clf = RandomForestClassifier(random_state = 10, warm_start = True, n_estimators = 24,max_depth = 6, max_features = 'sqrt') pipeline = make_pipeline(select, clf) pipeline.fit(X, y) predictions = pipeline.predict(test) submission = pd.DataFrame({"PassengerId": PassengerId, "Survived": predictions.astype(np.int32)}) submission.to_csv("submission.csv", index=False)6. 總結
這是我取得的最高的分數,為了提高我也參閱很多資料比如Stacking框架進行模型融合。用RandomForest、AdaBoost、ExtraTrees、GBDT、DecisionTree、KNN、SVM 訓練第一層。第二次XGBoost使用第一層預測的結果作為特征對最終的結果進行預測。結果被沒有提高,可能是我第一層做模型融合這塊,有很多需要注意的地方沒有學習到,以后繼續學習。最后還是很感謝我看過的每一份資料幫助我學到這么多東西,所以在我能提升的時候也把我所理解到的東西分享出來留給后續的學習者,如果你們能有所提升,請告訴我,萬分感謝。
參考文章:
1.?Kaggle Titanic 生存預測(Top1.4%)完整代碼分享
2.?Kaggle Titanic 生存預測 -- 詳細流程吐血梳理
代碼分享:
不留下代碼就走的博主不是好博主:
1.?CSDN下載頁
2.?Gtihub下載頁
如果對你有幫助,在我的Github中點下Start,感謝。
總結
以上是生活随笔為你收集整理的Kaggle:入门赛Tatanic(泰坦尼克号)84.21%带你冲进前2%的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊天室应用开发实践(二):实现基于 We
- 下一篇: Ruby语言介绍(二)——Ruby基本语