Kaggle競賽--泰坦尼克號生存預測
目錄
- 載入和查看數據
- 描述性統計
- 特徵處理
- 訓練單個模型
- 集成學習模型
泰坦尼克號生存預測是Kaggle舉辦的一項數據挖掘比賽,目的是根據給定的乘客信息來預測該乘客最終是否可以存活下來。具體的信息和數據可以在這裡找到(Titanic: Machine Learning from Disaster)。下面就是我用Python 3結合機器學習包 scikit 進行分析的過程:
首先先導入我們需要用到的包:
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom sklearn.linear_model import LogisticRegressionfrom sklearn.svm import SVCfrom sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier,VotingClassifierfrom xgboost import XGBClassifierfrom sklearn.model_selection import cross_val_scorefrom sklearn.model_selection import StratifiedKFoldfrom sklearn.model_selection import GridSearchCV
1. 載入和查看數據
在進行分析前的第一步就是要載入和查看我們的數據:
trainData = pd.read_csv(train.csv)#載入訓練數據testData= pd.read_csv(test.csv)#載入測試數據full_data = [trainData,testData]trainData.info()#訓練數據的信息testData.info()trainData.sample(n=10) #隨機查看訓練數據testData.sample(n=10)
從輸出的結果中可以看出:
- 訓練集中一共有819個數據,其中Age, Cabin和Embarked列的數據有缺失值
- 測試集中一共有418個數據,其中Age, Fare, Cabin列的數據有缺失值
- 訓練集中的特徵一共有:PassengerId, Survived, Pclass, Name, Sex, Age, SibSp, Parch, Ticket, Fare, Cabin和Embarked; 其中Name , Sex, Ticket, Cabin和Embarked列數據為文本數據,這在後面需要進行編碼的轉換
- 而測試集中的數據基本和訓練集中的一樣,只不過少了Survived列,這也是我們要預測的那一列數據
2.描述性統計
(1)生存率:
訓練集中存活下來的人佔比0.383,
trainData["Survived"].value_counts(normalize=True)
0 0.616162
1 0.383838
(2)分類變數和生存率
在給定的特徵中,分類變數或離散型變數一共有:Pclass, Sex, SibSp, Parch, Embarked,用柱狀圖表示它們和生存率之間的關係
fig,axes = plt.subplots(2,3,sharey=True)#Pclass和生存率sns.factorplot(x=Pclass,y=Survived,data=trainData,kind=bar,ax=axes[0,0])sns.barplot(x=Sex,y=Survived,data=trainData,ax=axes[0,1])sns.barplot(x=SibSp,y=Survived,data=trainData,ax=axes[0,2])sns.barplot(x=Parch,y=Survived,data=trainData,ax=axes[1,0])sns.barplot(x=Embarked,y=Survived,data=trainData,ax=axes[1,1])
從上面的描述統計中可以看出:
- 不同階級(Pclass)下用戶之間的存活率差別比較大,而且Pclass=1的乘客的存活率最大
- 女性存活的幾率遠大於男性,說明性別對生存率的影響和強
- SibSp和Parch對存活率也有一定的影響,而且他們的數值越小的話就越有可能存戶下來
- Embarked對生存率也有一定的影響,考慮保留在最終的特徵中
(3)連續變數和生存率
給定的特徵中,連續變數有:Age和Fare, 他們和Survived的情況如下:
fig,axes = plt.subplots(2,1)#不同Survived下年齡的分布sns.kdeplot(trainData["Age"][(trainData["Survived"] == 0) & (trainData["Age"].notnull())], color="Red", shade = True,ax=axes[0],label=Not Survived)sns.kdeplot(trainData["Age"][(trainData["Survived"] == 1) & (trainData["Age"].notnull())], color="Blue", shade= True,ax=axes[0],label=Survived)#不同Survived下Fare的分布sns.kdeplot(trainData["Fare"][(trainData["Survived"] == 0) & (trainData["Fare"].notnull())], color="Red", shade = True,ax=axes[1],label=Not Survived)sns.kdeplot(trainData["Fare"][(trainData["Survived"] == 1) & (trainData["Fare"].notnull())], color="Blue", shade= True,ax=axes[1],label=Survived)
從上面的圖中可以看出:
- 在年齡方面,大部分乘客的年齡都集中在20-40之間,而存活下來的乘客年齡有兩個峰段,年齡較小(0-8)和年齡中等(20-35)
- 而Fare在存活的乘客和沒有存活的乘客的分布較為相似,但沒有存活的乘客的Fare更為集中一些
(4)Pclass,Sex和Survived
sns.pointplot(x=Pclass,y=Survived,hue=Sex,data=trainData)
無論是在那個Pclass,女性(Female)的生存率都是遠大於男性(male)的,而且無論隨著Pclass的增加,生存率都是遞減的。
3.特徵處理
(1)特徵處理的步驟:參考這篇文章我把特徵的處理分為四個部分(4C):
- 異常值處理(Correcting):檢測數據中是否有異常值的出現,如果有的話,可以採取刪除該實例或用其他數值來替換異常值。
- 缺失值處理(Completing):從之前的查看數據時,就發現數據中存在缺失值,對於這些缺失值,如果樣本量很大的話,可以考慮刪除;但是這裡樣本量比較小,考慮用其他的數值來代替這些缺失值。
- 創建特徵(Creating):創建可能對預測結果有幫助的其他特徵。
- 特徵編碼轉化(Converting):主要是將文本類型的數據、分類變數轉換為可供模型計算的數值型變數。
(2)異常值處理(Correcting)
對於分類變數而言,通過前面的描述統計可以看出不存在異常的地方, 因此異常值檢測主要就是集中於連續型變數(Age, Fare)上,用箱線圖來檢測:
fig,axes = plt.subplots(1,2)sns.boxplot(y=Age,data=trainData,ax=axes[0])sns.boxplot(y=Fare,data=trainData,ax=axes[1])fig.show()
可以看出:相比於年齡,Fare列的數據更集中一點;
而Age列數據有幾個異常值,都是年齡比較大的乘客,不過這也比較正常;
在Fare中則有一個極端值是大於500的,可以考慮將其替換為Fare的中位數。
(3)缺失值處理(Completing)
在訓練集中缺失數據的列有:Age, Cabin 和Embarked;在測試集中有:Age, Fare, Cabin和Embarked。處理的方法如下:
Age:首先要從乘客的名稱中提取出稱謂(title), 然後將缺失的具有某個title的乘客的年齡替換為擁有這個title的其他乘客年齡的均值
Cabin:該列數據缺失太多了,因此考慮最終將其從特徵中去掉
Embarked:用該列的中位數來替換缺失值
Fare:用中位數來替換缺失值
#如果title不在出現次數較多的title中,將其替換為Raredef fix_title(title): if title.strip() not in [Mr,Miss,Mrs,Master,Dr]: return Rare else: return title.strip()# 用含有相同title的人的年齡的均值來填補年齡的缺失值for dataset in full_data: #提取名稱中的title dataset[Title] = dataset[Name].str.extract(, (S+). ,expand = False) #將一些出現次數較少的title替換為Rare dataset[Title] = dataset[Title].map(fix_title) #替換年齡中的缺失值 dataset[Age]=dataset.groupby(Title)[Age].apply(lambda x:x.fillna(x.mean())) #用Embarked的眾數來填補缺失值 dataset[Embarked].fillna(dataset[Embarked].mode()[0],inplace=True) #測試數據中的Fare有缺失值,因此用中位數來替代 dataset[Fare]= dataset[Fare].fillna(dataset[Fare].median())#查看訓練集和測試集中的缺失值情況trainData.isnull().sum()testData.isnull().sum()
可以看到在測試集和訓練集中除了Cabin列之外,其他的列都不存在缺失值了。
(4)創建特徵(Creating)
創建特徵主要是從數據中提取出一些對預測結果有幫助的特徵,我新建的特徵有以下幾個:
FamilySize:表示乘客家庭的大小,有SibSp和Parch相加,再加1得到
IsAlone:表示乘客是否是獨自一人,當FamilySize==1時,IsAlone為True
IsMother:表明該乘客是否為母親
Child:表示該乘客是否為孩子
def is_mother(row): if row[Title]==Mrs and row[Age]>20 and row[Parch]>0: return 1 else: return 0for dataset in full_data: dataset[FamilySize] = dataset[SibSp] + dataset[Parch] + 1 dataset[IsAlone] = dataset.apply(lambda x:1 if x[FamilySize]==1 else 0,axis=1) dataset[Mother] = dataset.apply(is_mother,axis=1) dataset[Child] = dataset.apply(lambda row:1 if row[Age]<16 and row[IsAlone]!=1 else 0 ,axis=1)
(5)特徵編碼轉化(Converting)
現在就剩下最後的編碼轉換部分了:
- 文本類型的列有:Sex, Embarked,Title, 對於這些列而言,需要將文本轉換為對應的數字編碼,便於模型的計算。
- 對於Age, Fare這樣的數值列數據而言,他們的數據範圍和其他列的數據範圍相差太大,會導致模型受這兩列的影響很大,因此也將其進行分組,然後轉換為離散型的變數。
#將分類變數轉換為數值型sex_mapping ={female:0,male:1}embarked_mapping = {S:1,C:2,Q:3}title_mapping = {Mr:1,Miss:2,Mrs:3,Master:4,Dr:5,Rare:6}for dataset in full_data: dataset[Sex] = dataset[Sex].map(sex_mapping) #另外創建一個變數:Class_Sex dataset[Class_Sex] = dataset[Pclass] * dataset[Sex] dataset[Embarked] = dataset[Embarked].map(embarked_mapping) dataset[Title] = dataset[Title].map(title_mapping)#將Age和Fare分組並編碼def age_encode(age): if age<=16:return 0 elif age<=32:return 1 elif age<=48:return 2 elif age<=64:return 3 elif age<=80:return 4def fare_encode(fare): if fare<=7:return 0 elif fare<=14:return 1 elif fare<=31:return 2 elif fare<=600:return 3for dataset in full_data: dataset[Age] = dataset.Age.map(age_encode) dataset[Fare] = dataset.Fare.map(fare_encode)#最後去掉一些我們不需要的列drop_columns=[PassengerId,Name,SibSp,Parch,Ticket,Cabin]trainData.drop(drop_columns,axis =1,inplace=True)testData.drop(drop_columns,axis=1,inplace=True)
最終得到的數據是這樣子的:
再用熱力圖來看一下特徵之間的相關性:可以看出,特徵之間的相關性還是比較小的
#利用熱力圖來查看特徵之間的相關關係colormap = plt.cm.RdBuplt.figure()sns.heatmap(trainData.astype(float).corr(),cmap=colormap,linecolor=white,annot=True)
4. 訓練單個模型
首先需要準備訓練數據和測試數據:
#準備訓練的數據X_train=trainData.iloc[:,1:]y_train=trainData[Survived]X_test = testData#用於交叉驗證kf = KFold(n_splits = 10,random_state = 0)
分別使用5個基本的分類模型來擬合訓練數據,並根據交叉驗證的結果來選擇模型的參數
#模型1:訓練邏輯回歸模型C = [0.01,0.1,0.5,1,2,3,4,5,6]accuracy = dict()for c in C: ls = LogisticRegression(penalty=l2,C = c, solver=lbfgs,random_state=0) cross_scores = cross_val_score(ls,X_train,y=y_train,cv =kf) accuracy[c] = np.mean(cross_scores)print(best C:,sorted(accuracy.items(),key=lambda x:x[1],reverse=True)[0])#模型2:用支持向量機來擬合模型svc = SVC(random_state=0)params={kernel:[linear,rbf,sigmoid], C:[1,1.2,1.4,1.5,1.8,2,2.5,3,4,10,20,50], gamma: [0.1,0.15,0.2,0.25,0.3,0.4]}best_model_svc = GridSearchCV(svc,param_grid = params,refit=True,cv=kf).fit(X_train,y_train)print(best accuracy,best_model_svc.best_score_print(best parameters,best_model_svc.best_params_)#模型3:用隨機森林來擬合模型params = {n_estimators:[50,100,150,200,250], max_depth:[3,5,7], min_samples_leaf:[2,4,6]}RF = RandomForestClassifier(random_state=0)best_model_rf = GridSearchCV(RF,param_grid=params,refit=True,cv=kf).fit(X_train,y_train)print(best accuracy,best_model_rf.best_score_)print(best parameters,best_model_rf.best_params_)#模型4:用GBDT來擬合模型gbdt = GradientBoostingClassifier(random_state=0)params ={learning_rate:[0.01,0.05,0.1,0.15,0.2], n_estimators:[100,300,500], max_depth:[3,5,7]}best_model_gbdt = GridSearchCV(gbdt,param_grid=params,refit=True).fit(X_train,y_train)print(best accuracy,best_model_gbdt.best_score_)print(best parameters,best_model_gbdt.best_params_)#模型5:xgboost擬合模型xbc = XGBClassifier(random_state = 0)params ={n_estimators:[50,100,300,500], max_depth:[2,3,5,7], learning_rate:[0.01, 0.03, 0.05, 0.1, 0.25]}best_model_xbc = GridSearchCV(xbc,param_grid=params,refit=True,cv=kf).fit(X_train,y_train)print(best accuracy,best_model_xbc.best_score_)print(best parameters,best_model_xbc.best_params_)
模型運行的結果:
best C: (2, 0.82383270911360795)best accuracy 0.83164983165best parameters {C: 1.2, gamma: 0.1, kernel: rbf} best accuracy 0.832772166105best parameters {max_depth: 5, min_samples_leaf: 2, n_estimators: 250}best accuracy 0.830527497194best parameters {learning_rate: 0.01, max_depth: 3, n_estimators: 100}best accuracy 0.83164983165best parameters {learning_rate: 0.01, max_depth: 2, n_estimators: 300}
從單個模型的結果來看,隨機森林交叉驗證的準確率是最高的,為0.8350。分別用上面的模型來擬合訓練集的數據,並對測試集進行預測,提交之後得到的準確率如下:
LogisticRegression : 0.80382
SVC : 0.78947
RandomForest : 0.79425
GradientBoostingClassifier : 0.79904
XGBClassifier : 0.79425
看起來邏輯回歸模型得到的準確率是最高的。
5. 集成學習模型
為了讓預測的結果準確率更高一點,可以將上面5個表現最佳的模型結合起來,採用多數表決(majority vote)的方式來對一個實例進行預測。
多數表決的具體方法:對於測試集中的一個實例而言,如果有超過3個模型預測的結果為1,那麼最終的結果就是1,否則預測的結果為0。可以藉助sklearn的VotingClassifier來實現。
models = [(lr:LogisticRegression(penalty=l2,C =2, solver=lbfgs,random_state=0)), (svc:SVC(kernel=rbf,C=1.2,gamma=0.1,random_state=0)), (rf:RandomForestClassifier(n_estimators=250,max_depth=5,min_samples_leaf=2,random_state=0)), (gbdt:GradientBoostingClassifier(learning_rate=0.01,n_estimators=100,max_depth=3,random_state=0)), (xgbc:XGBClassifier(learning_rate=0.01,max_depth=2,n_estimators=300,random_state=0)) ]vote= VotingClassifier(models,voting=hard)vote.fit(X_train,y_train)#最後利用vote對測試集中的數據進行預測y_test_pred = vote.predict(X_test)
將測試集中的PassengerId和預測的結果y_test_pred輸出為csv文件並提交到Kaggle上就可以得到預測的準確率:0.79425。
綜上,通過對泰坦尼克號數據集進行數據描述、特徵構建和擬合模型,最終發現利用邏輯回歸模型可以得到最高的準確率(0.80382)。
推薦閱讀:
※信用評分卡建模分析——基於R語言
※零基礎自學兩月後三月三次輕鬆進入kaggle比賽top20小結
※使用Tensorflow做Kaggle入門題之泰坦尼克
※Kaggle—So Easy!百行代碼實現排名Top 5%的圖像分類比賽
※Kaggle學習筆記(一)·特徵工程