Kaggle——泰坦尼克號0.79665-top9%

前言

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

image

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())
image

?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——級別越高的客艙的生存率會更高(有錢就有地位,有地位就有錢)

image

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

image

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

image

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

image

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

image

⑥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()
image

以上特征圖像我們結(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入門--泰坦尼克號之災(zāi)(手把手教你)

kaggle 泰坦尼克號生存預(yù)測——六種算法模型實現(xiàn)與比較

sofasofa——形狀識別(模型融合代碼實現(xiàn))

代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容