kaggle入門-泰坦尼克之災

引言

一直久聞kaggle大名,自己也陸陸續續學了一些機器學習方面的知識,想在kaggle上面嘗試一下,但是因爲各種煩雜的事情和課業拖累,一直沒時間參加一次kaggle的比賽。這次我將用kaggle的入門賽:Titanic: Machine Learning from Disaster來讓我熟悉比賽流程和各種數據處理技巧,也讓和我一樣第一次接觸kaggle的萌新們快速上手。本文旨在完整走一遍kaggle流程,並不旨在獲得一個很高的分數,因爲特徵處理和超參數的選擇都較爲隨意。

數據認識

雖然我們一直戲稱機器學習就是

train,target = load_data()
model.fit(data_train, data_taget)
model.predict(test)

我也一直吐槽機器學習就像煉丹,但是沒有充分的數據認識和特徵工程,預測出來的數據準確率將無法保證。那讓我們看看我們手上的數據長啥樣吧。

我們手上的文件一份是train.csv,一份是test.csv。我們用pandas將它打開
在這裏插入圖片描述
我們看到,總共有12列,其中Survived字段表示的是該乘客是否獲救,其餘都是乘客的個人信息,包括:

  • PassengerId: 乘客ID
  • Pclass: 乘客等級(1/2/3等艙位)
  • Name: 乘客姓名
  • Sex: 性別
  • Age: 年齡
  • SibSp: 堂兄弟/妹個數
  • Parch: 父母與小孩個數
  • Ticket: 船票信息
  • Fare: 票價
  • Cabin: 客艙
  • Embarked: 登船港口

我們首先使用pandas自帶的兩種方法查看data_train的綜合信息。

data_train.info()

我們可以看到我們的Age數據略有缺失,而cabin數據缺失嚴重,後面肯定要進行處理。

data_train.describe()

從這裏我們可以看到Survived只有0.38,說明只有三分之一的人獲救。船上的平均年齡是29.6歲,最大可達80歲。

上面都是非常概括的數據,我們需要對數據有一個更直觀的認識。我們使用matplotlib和snsborn兩個包來進行可視化分析。

fig=plt.figure(figsize=(20,4))
plt.subplot(1,4,1)
data_train.Pclass.value_counts().plot(kind="bar")
plt.ylabel("人數")
plt.title("乘客等級分佈")
plt.subplot(1,4,2)
data_train['Survived'].value_counts().plot(kind='bar')
plt.ylabel("人數")
plt.title('獲救情況')
plt.subplot(1,4,3)
sns.violinplot(x='Survived',y='Age',data=data_train,hue='Sex',split=True)
plt.title('年齡、性別與存活情況')
plt.subplot(1,4,4)
data_train['Age'][data_train['Pclass'] == 1].plot(kind='kde')   
data_train['Age'][data_train['Pclass'] == 2].plot(kind='kde')   
data_train['Age'][data_train['Pclass'] == 3].plot(kind='kde')   
plt.xlabel("年齡")# plots an axis lable
plt.ylabel("密度") 
plt.title("各等級的乘客年齡分佈")
plt.legend(('Pclass1','Pclass2','Pclass3'))

果然圖片要直觀多了。我們發現三號艙位的乘客最多,乘客年齡主要分佈在20-40附近。各個船艙的年齡分佈大致相同。
在這裏插入圖片描述

survived_1=data_train['Sex'][data_train['Survived']==1].value_counts()
survived_0=data_train['Sex'][data_train['Survived']==0].value_counts()
df_survived=pd.DataFrame({'0':survived_0,'1':survived_1})
df_survived.plot(kind='bar',stacked=True)
plt.title('性別與存活情況')

這裏可以看出女性獲救比例遠遠高於男性,這也映證了在泰坦尼克號事故中的Lady First(畢竟男同胞們都是紳士嘛)

survived_1=data_train['Pclass'][data_train['Survived']==1].value_counts()
survived_0=data_train['Pclass'][data_train['Survived']==0].value_counts()
df_survived=pd.DataFrame({'0':survived_0,'1':survived_1})
df_survived.plot(kind='bar',stacked=True)
plt.title('艙位與存活情況')

雖然各個艙位獲救的人數大致相當,但是1號和2號船艙的獲救率還是遠遠高於3號船艙,可見金錢也是能提高生存率的=。=

總結

  • 性別是極大的影響因素,女性的生還率比男性高出不止一倍。
  • 不同的艙位代表着財富的不同,我們可以看出越是昂貴的艙位生還率越是高。

特徵處理

現在我們對數據有了一個直觀的認識,那接下來就是至關重要的特徵處理了,特徵處理的好壞直接影響最後模型預測結果的準確性和泛化能力。

我們先處理缺失數據,Cabin數據缺失過多,我們可以將NaN直接作爲一個特徵,將非缺失數據設爲Yes。

data_train.loc[data_train['Cabin'].notnull(),'Cabin']='Yes'
data_train.loc[data_train['Cabin'].isnull(),'Cabin']='No'

對於Age,age缺失的數據適中,我們可以試着通過已有數據將其擬合出來。這裏我們採用RandomForest來擬合一下缺失的年齡數據。

# 利用隨機森林擬合缺失的年齡數據
from sklearn.ensemble import RandomForestRegressor
age_train=data_train[data_train['Age'].notnull()].drop(['Age','Survived'],axis=1)
age=data_train['Age'][data_train['Age'].notnull()]
age_unknow=data_train[data_train['Age'].isnull()].drop(['Age','Survived'],axis=1)
rfr=RandomForestRegressor(random_state=0,n_estimators=2000,n_jobs=-1)
rfr.fit(age_train,age)
age_predict=rfr.predict(age_unknow)
data_train['Age'][data_train['Age'].isnull()]=age_predict

不同的數據我們有不同的處理方式:

  1. 對於離散型數據,例如Sex和Cabin,我們可以對它進行one-hot編碼。
  2. 對於連續型數據,例如Age和Fare,我們可以將其標準化,將其映射至[-1,1]之間。因爲不同屬性的scale不同,將對收斂速度造成極大的影響,當然我們也可以使用Adagrad算法解決這個問題。

對於Cabin、Sex和Embarked,我們使用pandas的get_dummies方法將其進行one-hot編碼。順便丟棄無用特徵(當然不是真的無用,只是目前我無法從中看出規律,日後我會對其做更詳細的分析)

# 對部分數據進行 one—hot編碼
sex_dummies=pd.get_dummies(data_train['Sex'],prefix='Sex')
embarked_dummies=pd.get_dummies(data_train['Embarked'],prefix='Embarked')
cabin_dummies=pd.get_dummies(data_train['Cabin'],prefix='Cabin')
data_train=data_train.join([sex_dummies,embarked_dummies,cabin_dummies])
data_train.drop(['Sex','Embarked','Cabin','Name','PassengerId','Ticket'],axis=1,inplace=True)
data_train

得到

然後將Age和Fare進行標準化,得到無量綱數據

# 標準化
from sklearn.preprocessing import StandardScaler
std=StandardScaler()
data_train.loc[:,['Age','Fare']]=std.fit_transform(data_train.loc[:,['Age','Fare']])

然後我們就處理好了我們所需要的訓練集

# 訓練集
x=data_train.iloc[:,1:]
y=data_train.iloc[:,0]

當然我們不僅需要對訓練數據進行處理,還需要同時將測試數據同訓練數據一起處理,使得二者具有相同的數據類型和數據分佈。

# 將預測集數據作相同處理
data_test=pd.read_csv('test.csv')

data_test.loc[data_test['Cabin'].notnull(),'Cabin']='Yes'
data_test.loc[data_test['Cabin'].isnull(),'Cabin']='No'
sex_dummies=pd.get_dummies(data_test['Sex'],prefix='Sex')
embarked_dummies=pd.get_dummies(data_test['Embarked'],prefix='Embarked')
cabin_dummies=pd.get_dummies(data_test['Cabin'],prefix='Cabin')
data_test=data_test.join([sex_dummies,embarked_dummies,cabin_dummies])
data_test.drop(['Sex','Embarked','Cabin','Name','Ticket'],axis=1,inplace=True)

age_unknow=data_test[data_test['Age'].isnull()].drop(['Age','PassengerId'],axis=1)
age_predict=rfr.predict(age_unknow)
data_test['Age'][data_test['Age'].isnull()]=age_predict

std=StandardScaler()
data_test.loc[:,['Age','Fare']]=std.fit_transform(data_test.loc[:,['Age','Fare']])
data_test.fillna(data_test.mean(),inplace=True) # 用均值填充fare缺失值

建模預測

這裏就是人們最喜歡的調參煉丹環節啦,開始我們快樂的model.fit()和model.predict()。

logistic分類模型

# logistic預測
# 建模
from sklearn import linear_model
lr=linear_model.LogisticRegression(C=1.0,penalty='l2',solver='liblinear',tol=1e-6)
lr.fit(x,y)
survived_predict=lr.predict(data_test.drop('PassengerId',axis=1))
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("logistic_regression_predictions.csv", index=False)

最後得分0.76555,emmm。。。

隨機森林

# 隨機森林預測
from sklearn.ensemble import RandomForestClassifier
rfr2=RandomForestClassifier(random_state=0,n_estimators=2000,n_jobs=-1)
rfr2.fit(x,y)
survived_predict=rfr2.predict(data_test.drop('PassengerId',axis=1))
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("RandomForestClassifier_predictions.csv", index=False)

得分0.75598,比logistic還差了

SVM

from sklearn.svm import SVC
svc=sklearn.svm.SVC(C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, shrinking=True, 
                probability=True, tol=0.001, cache_size=200, class_weight=None, 
                verbose=False, max_iter=-1, decision_function_shape='ovr', 
                random_state=None)
svc.fit(x,y)
survived_predict=svc.predict(data_test.drop('PassengerId',axis=1))
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("SVC_predictions.csv", index=False)

最後得分0.77511,有進步!

xgboost

我們來嘗試一下大名鼎鼎的xgboost

from xgboost import XGBClassifier
xgb = XGBClassifier(learning_rate=0.01,
                      n_estimators=10,           # 樹的個數-10棵樹建立xgboost
                      max_depth=4,               # 樹的深度
                      min_child_weight = 1,      # 葉子節點最小權重
                      gamma=0.,                  # 懲罰項中葉子結點個數前的參數
                      subsample=1,               # 所有樣本建立決策樹
                      colsample_btree=1,         # 所有特徵建立決策樹
                      scale_pos_weight=1,        # 解決樣本個數不平衡的問題
                      random_state=27,           # 隨機數
                      slient = 0
                      )
xgb.fit(x,y)
survived_predict=xgb.predict(data_test.drop('PassengerId',axis=1))
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("XGBClassifier_predictions.csv", index=False)

得分0.77033,怎麼比svm還差一點,可能是調參的問題,以後我再來嘗試一下。

模型驗證

交叉驗證

因爲我們手上只有一份訓練集,所以我們可以通過交叉驗證的方式進行模型檢驗(有人會問爲什麼不直接提交結果進行驗證,因爲每日的提交是有次數限制的啊,而且有時候上交的人多要等5分鐘纔出結果)。交叉驗證的思想就是將所有數據分成n份,每次取一份作爲測試集,其餘作爲訓練集。

# 交叉驗證
from sklearn.model_selection import cross_val_score
cross_val_score(lr,x,y,cv=5)

學習曲線

高偏差:

隨着樣本數量的增加,測試集與交叉驗證集的偏差幾乎相等,這代表着,在高偏差(欠擬合)的情況下,增加數據集並不會優化算法。

解決方法:

  1. 增加特徵
  2. 增加多項式
  3. 減小正則化

高方差

隨着樣本數量的增加,測試集與交叉驗證集的偏差仍有很大的差距,這代表着,在高方差(過擬合)的情況下,增加數據集會一定程度上優化算法。

解決方法:

  1. 更多訓練集
  2. 減少特徵
  3. 增大正則化
# learning curve
from sklearn.model_selection import learning_curve
train_sizes,train_scores,test_scores=learning_curve(vote,x,y,train_sizes=np.linspace(0.5,1,20))
train_scores_mean=np.mean(train_scores,axis=1)
test_scores_mean=np.mean(test_scores,axis=1)
plt.plot(train_sizes,train_scores_mean,'o-',color='b',label='train')
plt.plot(train_sizes,test_scores_mean,'o-',color='r',label='test')
plt.legend()
plt.gca().invert_yaxis()

這裏我們可以看出我們的模型處於高偏差狀態,我們應該增加特徵數量,我們去除的名字和船票可能能幫助我們改善模型

模型融合

最後我們將使用最終武器,模型融合。
俗話說三個臭皮匠頂個諸葛亮,每個模型有每個模型的長處和短處,這時候我們讓所有模型都進行預測,然後投票選擇最終答案,不就能大大提高正確率嗎。這就是Voting Bagging的思想。
Bagging 將多個模型,也就是多個基學習器的預測結果進行簡單的加權平均或者投票。它的好處是可以並行地訓練基學習器。Random Forest就用到了Bagging的思想。
我們將上面所有模型合併起來

# volting
from sklearn.ensemble import VotingClassifier
vote=VotingClassifier(estimators=[('lr',lr),('rfr',rfr2),('svm',svc),('xgb',xgb)],voting='soft')
vote.fit(x,y)
survived_predict=vote.predict(data_test.drop('PassengerId',axis=1))
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("VotingClassifier_predictions.csv", index=False)

得到最終得分:0.78468。看,還是有點用的。
在這裏插入圖片描述

總結

可以看出我們的模型還有很大的改進空間,還有很多可以挖掘的空間,比如我們這個特徵處理還是挺粗糙的,丟棄特徵也過於隨意;超參數的選擇上也有很大改進空間,我們可以使用網格搜索尋找最佳的參數。

後記

答主後來又試着用神經網絡來預測結果

from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,pd.get_dummies(y), train_size=0.25)# 切割數據集

import keras
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()    # 建立一個模型
# 搭建網絡
'''@param
    Dense: Fully connect layer
    input_dim: 輸入層
    units: 神經元
    activation: 激活函數
'''
model.add(Dense(input_dim=10, units=200, activation='relu'))   # 建立一個神經網絡
# 再加一個隱含層
model.add(Dense(units=200, activation='relu'))
# 輸出層
model.add(Dense(units=2, activation='softmax'))    # 輸出向量長度爲10,激活函數爲softmax
# loss function
model.compile(loss='categorical_crossentropy',  # 損失函數:交叉熵
              optimizer='adam',  # 優化器(都是梯度下降)
              metrics=['accuracy']  # 指標
              )
# batch_size: 將訓練集隨機分爲分爲幾個batch,每次計算隨機的一個
# 所有batch都計算一次,一個epoch結束
model.fit(x_train, y_train, batch_size=20, epochs=20)

# case1: 測試集正確率
score = model.evaluate(x_test, y_test)
print('Total loss on Test Set:', score[0])
print('Accuracy of Testing Set:', score[1])
# case 2:模型預測
result = model.predict(data_test.drop('PassengerId',axis=1))

for n,i in enumerate(result):
    result[n]=i.argmax()
result = pd.DataFrame({'PassengerId':data_test['PassengerId'], 'Survived':survived_predict})
result.to_csv("neuralnetwork_predictions.csv", index=False)

但最終結果還是0.78468,不管怎麼改神經元數量、網絡結構、激活函數都是這個準確率,感覺是是這組特徵所能預測的極限了=。=這也側面反映良好的特徵處理是有多重要,特徵處理不行,再調參,嘗試再多的模型也無法提高正確率。

(如果覺得有用請點個贊吧)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章