前言
本次是在Kaggle的第一个竞赛《Titanic: Machine Learning from Disaster》,即预测泰坦尼克号的人员幸存情况,是一个二分类问题。
本次最后精度为0.79665,排名Top 9%。
1 数据分析
1.1 数据概述
首先我们将测试集和训练集一同合并起来查看
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个缺失值
训练集为891,测试集为418;特征数量为12,4个为数值特征,5个为字符串特征;4个特征存在缺失值。
1.2 特征可视化
本次使用Excel数据透视表结合python画图观察各特征与生存率之间的关系
①Pclass——级别越高的客舱的生存率会更高(有钱就有地位,有地位就有钱)
②Sex——女性的生存率明显高于男性(女士优先)
③Parch——直系亲属数量适中的生存率较高(一个人没有寄托,英雄般献出生存机会;一个家庭需要剩下一两个回去传承,但其余的都得献身,所以显得生存率较小)
④SibSp——旁系亲属数量适中的生存率较高(同上)
⑤Embarked——在C港口进船的生存率较高(可能C港口在发达城市,各个都有钱吗)
⑥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——票价我们将其指数化可以看的更清楚,发现也是票价高的生存率会更高(钱钱钱!)
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()
以上特征图像我们结合实际想一想,发现确实是有些关联的,交给模型领悟领悟这些美德和现实吧。当然还有一些特征值过于多的我们后续再处理。
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——一起上船的全家人数,可以有的是一个姓却不是一个家庭,可能有的是一个家庭却没有一起上船(同一票根的表示房间号)。发现和Parch、SibSp有异曲同工之妙,生存率较高的都为人数适中的。
all['Family']=all['Surname']+all['Ticket']
Family_Count =dict(all['Family'].value_counts())
all['Family']=all['Family'].apply(lambda x:Family_Count[x])
④allFamilySize——旁系加直系亲属,无论是否在船上的家族人数。(很尴尬,和上面差不多,看来基本都是一家人上船的)
all['allFamilySize']=all['SibSp']+all['Parch']+1
⑤together——舱房内的舍友人数,其中包括了一个家庭和不是一个家庭的,认为可能是相识的。(嗯哼,似曾相识,不过与上还是有点区别的)
Ticket_Count =dict(all['Ticket'].value_counts())
all['together'] = all['Ticket'].apply(lambda x:Ticket_Count[x])
2.1.2 特征分箱
分箱!分箱!这此预测最麻烦的就是过拟合,分箱可以减少计算并且有效降低过拟合
①Age——对孩子、年轻人、老年人进行分箱。(这里先对已知的年龄分箱)
all['Age'] = all[all['Age'].notnull()]['Age'].apply(lambda x:0 if x<=14 else (1 if x<=60 else 2))
②allFamilySize——对家族人数根据数量适中,不适中,过多进行分箱。(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——对舍友人数根据数量适中,不适中,过多进行分箱。(和其上还是很像,暂时不删除,留给模型判断先)
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——根据图表的生存率转折进行分箱。(补充完缺失值再分箱)
(!!!后续调整时候,我试着把Fare调整会原数据,表现变好了。可能是我分箱的不准确或是调参的失误。)
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中位数为80,所以缺失值填充为C。
Fare缺失量为1,缺失Fare信息的乘客的Embarked为S,Pclass为3,所以用Embarked为S,Pclass为3的乘客的Fare中位数填充。
fare=all[(all['Embarked'] =="S") & (all['Pclass'] ==3)].Fare.median()
all['Fare']=all['Fare'].fillna(fare)
all['Embarked'] = all['Embarked'].fillna('C')
②Age——缺失值较多,我们使用随机森林进行预测,特征选择了'Pclass','Fare','Title'(凭感觉,如果后续过拟合可以试下调整年龄预测的模型参数和特征)
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 训练模型
最终我们选择了以下特征
all=all[['Fare','Age','allFamilySize','Pclass','Sex','Embarked','Survived']]
all=pd.get_dummies(all)
all.to_csv('all.csv',index=0)
3.1 随机森林模型
看了几篇文章都表示随机森林表现较可,尝试用随机森林调参并预测,成绩为0.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('每轮迭代运行结果:{0}'.format(evalute_result))
best_params = m.best_params_
best_score = m.best_score_
print(best_params,best_score)
name='RF'
return m,name
发现结果还行
3.2 模型融合
同样我对另外几个模型(XGBClassifier、DecisionTreeClassifier、AdaBoostClassifier)也进行预测,结果随着我调参过于飘忽...干脆全部融合起来。
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:
# 三个结果相同
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'] = '三个结果相同'
df.loc[i, 'e'] = 'abc'
# 两个结果相同
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'] = '两个结果相同'
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'] = '两个结果相同'
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'] = '两个结果相同'
df.loc[i, 'e'] = 'bc'
# 三个结果不相同
else:
df.loc[i, 'f'] = '三个结果不相同'
df.loc[i, 'd'] = df.loc[i, 'b']
print('-'*125)
print('列中各元素数量')
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)
后续发现三个模型融合比较好控制,五个模型反而降低了。因为是我第一次融合模型,估摸选的搭配和调参不对,可以再考究考究。
然而这里最大的问题!!!就是在交叉验证表现良好的,与提交上去的差了很多,还不一定为正比关系!啊啊啊,这就是过拟合的可怕吗?
于是我又回去各种尝试,试着删除一些特征,对一些特征分箱,改变一下预测年龄的特征等等。
最终最好成绩为三模型融合的0.79665,排名Top 9%。
小结
本次处理中发生较严重的过拟合问题,原因我想有三点:①数据集本身规模过小,太多因素不能从数据集中解释,这限制了得分在0.85以下才正常;②缺少具有代表性的特征,我并未提取出更为具有代表性的特征,例如我曾尝试将不同家庭分类,希望能将各自家庭中至少活下一两个的猜测做成特征,但是如果A家庭在训练集的成员都死亡,模型会容易将A家庭在测试集的成员都预测为死亡。这个问题我仍未想出方案。③模型调参无从下手,调参可以降低过拟合,但当其交叉验证的分数就变得不可信了,且提交成绩有限制时,我对调参颇为局促。
啊,从sofa到kaggle,发现这个kaggle果然是大平台,从排行榜的刷新就可以看得出其规模,当然我还发现许多想法不一的,还等我慢慢学。
主要参考: