前言
本次是在Kaggle的第一個競賽《Titanic: Machine Learning from Disaster》,即預(yù)測泰坦尼克號的人員幸存情況,是一個二分類問題。
本次最后精度為0.79665,排名Top 9%。

1 數(shù)據(jù)分析
1.1 數(shù)據(jù)概述
首先我們將測試集和訓(xùn)練集一同合并起來查看
train=pd.read_csv('train.csv')
test=pd.read_csv('test.csv')
all=pd.concat([train,test],axis=0,ignore_index=True)
print(all.info())

?PassengerID(ID)——乘客編號可以刪去
?Survived(存活與否)
?Pclass(客艙等級)
?Name(姓名)
?Sex(性別)
?Age(年齡)——存在缺失值
?Parch(直系親友)
?SibSp(旁系)
?Ticket(票編號)
?Fare(票價)——2個缺失值
?Cabin(客艙編號)——過多的缺失值
?Embarked(上船的港口編號)——1個缺失值
訓(xùn)練集為891,測試集為418;特征數(shù)量為12,4個為數(shù)值特征,5個為字符串特征;4個特征存在缺失值。
1.2 特征可視化
本次使用Excel數(shù)據(jù)透視表結(jié)合python畫圖觀察各特征與生存率之間的關(guān)系
①Pclass——級別越高的客艙的生存率會更高(有錢就有地位,有地位就有錢)

②Sex——女性的生存率明顯高于男性(女士優(yōu)先)

③Parch——直系親屬數(shù)量適中的生存率較高(一個人沒有寄托,英雄般獻(xiàn)出生存機會;一個家庭需要剩下一兩個回去傳承,但其余的都得獻(xiàn)身,所以顯得生存率較?。?/p>

④SibSp——旁系親屬數(shù)量適中的生存率較高(同上)

⑤Embarked——在C港口進(jìn)船的生存率較高(可能C港口在發(fā)達(dá)城市,各個都有錢嗎)

⑥Age——孩子(15歲前)生存率最高,年輕人生存率其次,而老年人(60歲后)生存率最低(老人愿意犧牲自己剩下的生命,留給還有大把未來的孩子和年輕人)
facet = sns.FacetGrid(all, hue="Survived",aspect=2)
facet.map(sns.kdeplot,'Age',shade=True)
facet.set(xlim=(0, all['Age'].max()))
facet.add_legend()
plt.xlabel('Age')
plt.ylabel('density')
plt.show()

⑦Fare——票價我們將其指數(shù)化可以看的更清楚,發(fā)現(xiàn)也是票價高的生存率會更高(錢錢錢?。?/p>

all['Fare']=all['Fare'].map(lambda x:np.log(x+1))
facet = sns.FacetGrid(all, hue="Survived",aspect=2)
facet.map(sns.kdeplot,'Fare',shade=True)
facet.set(xlim=(0, all['Fare'].max()))
facet.add_legend()
plt.xlabel('Fare')
plt.ylabel('density')
plt.show()

以上特征圖像我們結(jié)合實際想一想,發(fā)現(xiàn)確實是有些關(guān)聯(lián)的,交給模型領(lǐng)悟領(lǐng)悟這些美德和現(xiàn)實吧。當(dāng)然還有一些特征值過于多的我們后續(xù)再處理。
2 特征工程
2.1 特征處理
2.1.1 提取新特征
①Title——從人們Name提取出來的稱呼,可以分為有職位的,有地位的,婦女,女士等等。可以看出有地位的還有女性的生存率高一點。
all['Title'] = all['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['Title'] = all['Title'].map(Title_Dict)

②Surname——從人們Name提取出來的姓,這是為了找尋一家人。
all['Surname']=all['Name'].apply(lambda x:x.split(',')[0].strip())
③FamilySize——一起上船的全家人數(shù),可以有的是一個姓卻不是一個家庭,可能有的是一個家庭卻沒有一起上船(同一票根的表示房間號)。發(fā)現(xiàn)和Parch、SibSp有異曲同工之妙,生存率較高的都為人數(shù)適中的。
all['Family']=all['Surname']+all['Ticket']
Family_Count =dict(all['Family'].value_counts())
all['Family']=all['Family'].apply(lambda x:Family_Count[x])

④allFamilySize——旁系加直系親屬,無論是否在船上的家族人數(shù)。(很尷尬,和上面差不多,看來基本都是一家人上船的)
all['allFamilySize']=all['SibSp']+all['Parch']+1

⑤together——艙房內(nèi)的舍友人數(shù),其中包括了一個家庭和不是一個家庭的,認(rèn)為可能是相識的。(嗯哼,似曾相識,不過與上還是有點區(qū)別的)
Ticket_Count =dict(all['Ticket'].value_counts())
all['together'] = all['Ticket'].apply(lambda x:Ticket_Count[x])

2.1.2 特征分箱
分箱!分箱!這此預(yù)測最麻煩的就是過擬合,分箱可以減少計算并且有效降低過擬合
①Age——對孩子、年輕人、老年人進(jìn)行分箱。(這里先對已知的年齡分箱)
all['Age'] = all[all['Age'].notnull()]['Age'].apply(lambda x:0 if x<=14 else (1 if x<=60 else 2))

②allFamilySize——對家族人數(shù)根據(jù)數(shù)量適中,不適中,過多進(jìn)行分箱。(Parch、SibSp、FamilySize、allFamilySize分箱后都類似,取其中一個)
def Fam_label(s):
if (s >=2) & (s <=4):
return 2
elif ((s >4) & (s <=7)) | (s ==1):
return 1
elif (s >7):
return 0
all['allFamilySize']=all['allFamilySize'].apply(Fam_label)

③together——對舍友人數(shù)根據(jù)數(shù)量適中,不適中,過多進(jìn)行分箱。(和其上還是很像,暫時不刪除,留給模型判斷先)
def Ticket_Label(s):
if (s >=2) & (s <=4):
return 2
elif ((s >4) & (s <=8)) | (s ==1):
return 1
elif (s >8):
return 0
all['together'] = all['together'].apply(Ticket_Label)

④Fare——根據(jù)圖表的生存率轉(zhuǎn)折進(jìn)行分箱。(補充完缺失值再分箱)
(?。?!后續(xù)調(diào)整時候,我試著把Fare調(diào)整會原數(shù)據(jù),表現(xiàn)變好了。可能是我分箱的不準(zhǔn)確或是調(diào)參的失誤。)
train['Fare']=train['Fare'].map(lambda x:0 if x<1.5 else (1 if x<2.7 else 2))

2.2 補充缺失值
①Embarked和Fare——
Embarked缺失量為2,缺失Embarked信息的乘客的Pclass均為1,且Fare均為80,因為Embarked為C且Pclass為1的乘客的Fare中位數(shù)為80,所以缺失值填充為C。
Fare缺失量為1,缺失Fare信息的乘客的Embarked為S,Pclass為3,所以用Embarked為S,Pclass為3的乘客的Fare中位數(shù)填充。
fare=all[(all['Embarked'] =="S") & (all['Pclass'] ==3)].Fare.median()
all['Fare']=all['Fare'].fillna(fare)
all['Embarked'] = all['Embarked'].fillna('C')
②Age——缺失值較多,我們使用隨機森林進(jìn)行預(yù)測,特征選擇了'Pclass','Fare','Title'(憑感覺,如果后續(xù)過擬合可以試下調(diào)整年齡預(yù)測的模型參數(shù)和特征)
from sklearn.ensembleimport RandomForestClassifier
age_df = all[['Age', 'Pclass','Fare','Title']]
age_df=pd.get_dummies(age_df)
known_age = age_df[age_df.Age.notnull()].as_matrix()
unknown_age = age_df[age_df.Age.isnull()].as_matrix()
y = known_age[:, 0]
X = known_age[:, 1:]
rfr = RandomForestClassifier(random_state=0, n_estimators=100, n_jobs=-1)
rfr.fit(X, y)
predictedAges = rfr.predict(unknown_age[:, 1::])
all.loc[(all.Age.isnull()), 'Age' ] = predictedAges
3 訓(xùn)練模型
最終我們選擇了以下特征
all=all[['Fare','Age','allFamilySize','Pclass','Sex','Embarked','Survived']]
all=pd.get_dummies(all)
all.to_csv('all.csv',index=0)
3.1 隨機森林模型
看了幾篇文章都表示隨機森林表現(xiàn)較可,嘗試用隨機森林調(diào)參并預(yù)測,成績?yōu)?.78708。
def RF_():
cv_params = {}
other_params = {'n_estimators':100, 'max_depth':6, 'min_samples_leaf':2,
'max_features':'sqrt'}
model = RandomForestClassifier(**other_params)
m = GridSearchCV(estimator = model, param_grid = cv_params, scoring='roc_auc', cv=10)
m.fit(train_x, train_y)
evalute_result = m.cv_results_
# print('每輪迭代運行結(jié)果:{0}'.format(evalute_result))
best_params = m.best_params_
best_score = m.best_score_
print(best_params,best_score)
name='RF'
return m,name
發(fā)現(xiàn)結(jié)果還行
3.2 模型融合
同樣我對另外幾個模型(XGBClassifier、DecisionTreeClassifier、AdaBoostClassifier)也進(jìn)行預(yù)測,結(jié)果隨著我調(diào)參過于飄忽...干脆全部融合起來。
submit = pd.read_csv("gender_submission.csv")
def prediction(m,name):
y_pred = m.predict(test)
submit['Survived'] = y_pred
submit['Survived'] = submit['Survived'].astype(int)
submit.to_csv('prediction_{}'.format(name)+'.csv', index=False)
model=(XGB_ (), RF_(), DTC_(),XGB2_(),ABC_())
for i in model:
m,name=i
prediction(m,name)
#三模型融合
import pandas as pd
df1=pd.read_csv('prediction_RF.csv')
df2=pd.read_csv('prediction_XGB.csv')
df3=pd.read_csv('prediction_DTC.csv')
df=pd.merge(df1, df2, on = 'PassengerId')
df=pd.merge(df,df3,on='PassengerId')
print(df.head(5))
df.rename(columns={'Survived_x':'a','Survived_y':'b','Survived':'c'}, inplace=True)
print(df.head(5))
for i in df.index:
# 三個結(jié)果相同
if df.loc[i, 'a'] + df.loc[i, 'b'] + df.loc[i, 'c'] == 0 or df.loc[i, 'a'] + df.loc[i, 'b'] + df.loc[i, 'c'] == 3 :
df.loc[i, 'd'] = (df.loc[i, 'a'] + df.loc[i, 'b'] + df.loc[i, 'c']) / 3
df.loc[i, 'f'] = '三個結(jié)果相同'
df.loc[i, 'e'] = 'abc'
# 兩個結(jié)果相同
elif df.loc[i, 'a'] == df.loc[i, 'b'] and df.loc[i, 'b'] != df.loc[i, 'c']:
df.loc[i, 'd'] = df.loc[i, 'a']
df.loc[i, 'f'] = '兩個結(jié)果相同'
df.loc[i, 'e'] = 'ab'
elif df.loc[i, 'a'] == df.loc[i, 'c'] and df.loc[i, 'b'] != df.loc[i, 'c']:
df.loc[i, 'd'] = df.loc[i, 'a']
df.loc[i, 'f'] = '兩個結(jié)果相同'
df.loc[i, 'e'] = 'ac'
elif df.loc[i, 'b'] == df.loc[i, 'c'] and df.loc[i, 'a'] != df.loc[i, 'c']:
df.loc[i, 'd'] = df.loc[i, 'b']
df.loc[i, 'f'] = '兩個結(jié)果相同'
df.loc[i, 'e'] = 'bc'
# 三個結(jié)果不相同
else:
df.loc[i, 'f'] = '三個結(jié)果不相同'
df.loc[i, 'd'] = df.loc[i, 'b']
print('-'*125)
print('列中各元素數(shù)量')
print('-'*125)
print(df['f'].value_counts() )
print('-'*125)
print(df['d'].value_counts() )
print('-'*125)
print(df['e'].value_counts() )
print(df.head(5))
submit = pd.read_csv("gender_submission.csv")
submit['Survived'] = df['d']
submit['Survived'] = submit['Survived'].astype(int)
submit.to_csv('prediction_vote.csv', index=False)
#五模型融合
import pandas as pd
df1=pd.read_csv('prediction_RF.csv')
df2=pd.read_csv('prediction_XGB.csv')
df3=pd.read_csv('prediction_DTC.csv')
df4=pd.read_csv('prediction_ABC.csv')
df5=pd.read_csv('prediction_XGB2.csv')
df=pd.merge(df1, df2, on = 'PassengerId')
df=pd.merge(df,df3,on='PassengerId')
df=pd.merge(df, df4, on = 'PassengerId')
df=pd.merge(df,df5,on='PassengerId')
df.columns=['Id','a','b','c','d','e']
print(df.head(5))
df['S']=df['a']+df['b']+df['c']+df['d']+df['e']
df['S']=df['S'].apply(lambda x:1 if x>=3 else 0)
submit = pd.read_csv("gender_submission.csv")
submit['Survived'] = df['S']
submit['Survived'] = submit['Survived'].astype(int)
submit.to_csv('prediction_vote.csv', index=False)
后續(xù)發(fā)現(xiàn)三個模型融合比較好控制,五個模型反而降低了。因為是我第一次融合模型,估摸選的搭配和調(diào)參不對,可以再考究考究。
然而這里最大的問題!?。【褪窃诮徊骝炞C表現(xiàn)良好的,與提交上去的差了很多,還不一定為正比關(guān)系!啊啊啊,這就是過擬合的可怕嗎?
于是我又回去各種嘗試,試著刪除一些特征,對一些特征分箱,改變一下預(yù)測年齡的特征等等。
最終最好成績?yōu)槿P腿诤系?.79665,排名Top 9%。
小結(jié)
本次處理中發(fā)生較嚴(yán)重的過擬合問題,原因我想有三點:①數(shù)據(jù)集本身規(guī)模過小,太多因素不能從數(shù)據(jù)集中解釋,這限制了得分在0.85以下才正常;②缺少具有代表性的特征,我并未提取出更為具有代表性的特征,例如我曾嘗試將不同家庭分類,希望能將各自家庭中至少活下一兩個的猜測做成特征,但是如果A家庭在訓(xùn)練集的成員都死亡,模型會容易將A家庭在測試集的成員都預(yù)測為死亡。這個問題我仍未想出方案。③模型調(diào)參無從下手,調(diào)參可以降低過擬合,但當(dāng)其交叉驗證的分?jǐn)?shù)就變得不可信了,且提交成績有限制時,我對調(diào)參頗為局促。
啊,從sofa到kaggle,發(fā)現(xiàn)這個kaggle果然是大平臺,從排行榜的刷新就可以看得出其規(guī)模,當(dāng)然我還發(fā)現(xiàn)許多想法不一的,還等我慢慢學(xué)。
主要參考:
kaggle 泰坦尼克號生存預(yù)測——六種算法模型實現(xiàn)與比較