Titanic生存预测1

背景音乐:Toca Toca - Fly Project
背景音乐有毒……

前言

前段时间玩了一下Kaggle平台上的Titanic号生存预测竞赛,这是一个非常适合入门机器学习的项目。

这场竞赛的内容是,通过一部分乘客的数据及其存活率,来预测另一部分乘客的存活率,目标是预测的准确率尽可能高。

我的最好分数是0.81339,在9606支队伍中排名300+~500+,也就是说有差不多200人拿到了这个成绩噗。对于我投入的时间而言,这个成绩还算满意,而且我相信还有一定的提升空间。

这里假设,看这篇文章的人已经参加过,但是没有拿到较好的成绩(我认为拿到0.8以上就不错了)。


我的建议

拿到0.8以上的分数后,就不要再追求排名了,而是需要看看别人的kernel学习他们的特征处理方式,理由是:

包括我在内,很多参赛选手发现,自己在测试集上有0.85左右的分数,但是一提交结果却发现只有0.78左右,这说明模型已经过拟合了。

不是你的问题,因为该项目的数据量太少,噪音也很多,因此做好模型不如做好特征工程来得重要,更不如发现数据背后的规律并善加利用重要。后者要求你对数据有敏锐的观察力,同时利用这些规律来修正你的模型的预测结果,不然我敢保证你的模型极限只会是0.83。

为什么有一些人的准确率会有0.9甚至1.0呢?理由很简单,他们在现有模型的基础上,加了很多认为设定的规则,故意让结果“过拟合”。理论上,除非数据集有问题,否则模型的准确率不可能会达到100%的。他们中的有些人可能确实发现了一个牛逼的特征,但我相信一部分人是存在作弊嫌疑的……

废话不多说,上干货。


环境:python 3.6.2
系统:macOS 10.13.1

1. 数据探索

EDA很重要,很重要,很重要!!!

但我同样假设你已经发现了一部分规律,比如:
1)女性存活率要远高于男性;
2)Embarked为S的乘客幸存率较低;
……

所以我这里跳过该部分。不了解的童鞋看这里:EDA To Prediction(DieTanic)

from sklearn.preprocessing import LabelEncoder
from sklearn import model_selection
from xgboost import XGBRegressor
import pandas as pd

path_data = '../../data/titanic/'
df_train = pd.read_csv(path_data + 'train.csv')
df_test = pd.read_csv(path_data + 'test.csv')
df_data = pd.concat([df_train, df_test])

2. 特征工程

所谓特征工程,最重要的就是通过已有的特征,去构建有预测能力的新特征。
而有预测能力的新特征,有三条基本特性(瞎掰的哈):可解释性、相关性、易获得性。

我做了4方面内容——数据清洗、构造新特征、特征选择、特征转换:

2.1 数据清洗

缺少数据的主要有4个变量,其中:

  • Cabin:缺太多了,提取头字母,将缺失值和头字母数少于10的归为一类
  • Embarked和Fare:都缺很少,可以用众数填补
  • Age:有部分缺失,但EDA结果显示它很重要,因此我决定构造模型来预测Age。不过该部分放在最后。
df_data['Embarked'].fillna(df_data['Embarked'].mode()[0], inplace=True)
df_data['Fare'].fillna(df_data['Fare'].median(), inplace=True)
df_data['Cabin'] = df_data['Cabin'].apply(lambda x:x[0] if x is not np.nan else 'X')
cabin_counts = df_data['Cabin'].value_counts()
df_data['Cabin'] = df_data['Cabin'].apply((lambda x:'X' if cabin_counts[x] < 10 else x))

2.2 构造新特征

构造新特征其实是一件特别蛋疼的事……因为这个完全靠经验+尝试, 别无他法。

常常是拍脑瓜想出一个新特征,然后看这个特征和结果的相关性,如果还不错的话,说明有一定的预测能力,然后代入到模型中,看测试集的表现是否有提高。如果有提高,高兴啦;如果没什么变化甚至降低了,就要考虑是暂时保留还是剔除掉……

下面直接列出我用的、对结果有一定帮助的特征。

FamilySize

df_data['FamilySize'] = df_data['SibSp'] + df_data['Parch'] + 1

IsAlone

df_data['IsAlone'] = 1
df_data['IsAlone'].loc[df_data['FamilySize'] > 1] = 0

Title

拥有人数少于10的title都改成Rare

df_data['Title'] = df_data['Name'].str.split(", ", expand=True)[1].str.split(".", expand=True)[0]
title_counts = df_data['Title'].value_counts()
df_data['Title'] = list(map(lambda x:'Rare' if title_counts[x] < 10 else x, df_data['Title'])) 

Family_Name

部分西方国家中人名的重复度较高,而姓氏重复度较低,姓氏具有一定辨识度。
姓氏相同的乘客,可能是一家人,而一家人同时幸存或遇难的可能性较高。

df_data['Family_Name'] = df_data['Name'].apply(lambda x: str.split(x, ",")[0])

Family_Survival

此处逻辑是:

  • 一个人的家庭存活率为0.5
  • 再将Family_Name和Fare进行组合,认为同一姓氏且有着同一票价的人们组成一个家庭,对于一个家庭(大于1人)而言,设如果有人存活则家庭存活率为1,否则为0
  • 再将Family_Name和Ticket进行组合,认为同一姓氏且有着共享一张票的人们组成一个家庭,对于一个家庭(大于1人)而言,设如果有人存活则家庭存活率为1,否则为0
DEFAULT_SURVIVAL_VALUE = 0.5
df_data['Family_Survival'] = DEFAULT_SURVIVAL_VALUE

for grp, grp_df in df_data.groupby(['Family_Name', 'Fare']):
    if (len(grp_df) != 1):
        for ind, row in grp_df.iterrows():
            smax = grp_df.drop(ind)['Survived'].max()
            smin = grp_df.drop(ind)['Survived'].min()
            passID = row['PassengerId']
            if (smax == 1.0):
                df_data.loc[df_data['PassengerId'] == passID, 'Family_Survival'] = 1
            elif (smin==0.0):
                df_data.loc[df_data['PassengerId'] == passID, 'Family_Survival'] = 0

for _, grp_df in df_data.groupby('Ticket'):
    if (len(grp_df) != 1):
        for ind, row in grp_df.iterrows():
            if (row['Family_Survival'] == 0) | (row['Family_Survival']== 0.5):
                smax = grp_df.drop(ind)['Survived'].max()
                smin = grp_df.drop(ind)['Survived'].min()
                passID = row['PassengerId']
                if (smax == 1.0):
                    df_data.loc[df_data['PassengerId'] == passID, 'Family_Survival'] = 1
                elif (smin==0.0):
                    df_data.loc[df_data['PassengerId'] == passID, 'Family_Survival'] = 0

2.3 预测年龄

这一部分也属于数据清洗,但是比较高级。

在这里我用交叉验证和GridSearchCV的方式训练出一个较好的xgboost回归模型来预测年龄。

def predict_age(x_train, y_train, x_test):
    param_grid = {
        'learning_rate':[.001, .005, .01, .05, .1],
        'max_depth':[2, 4, 6, 8],
        'n_estimators':[50, 100, 300, 500, 1000],
        'seed':[2018]
    }
    cv_split = model_selection.ShuffleSplit(n_splits = 10, test_size = .3, train_size = .6, random_state = 0) 
    tune_model = model_selection.GridSearchCV(XGBRegressor(nthread=-1), param_grid=param_grid, 
                                              scoring = 'neg_mean_squared_error', cv = cv_split)
    tune_model.fit(x_train, y_train)
    print(tune_model.best_params_)
    y_test = tune_model.best_estimator_.predict(x_test)

    return y_test

data_p = df_data.drop(['Cabin', 'Embarked', 'FareBin', 'Name', 'PassengerId',
                       'Sex', 'Survived', 'Ticket', 'Title', 'Family_Name'], 1)
x_train = data_p.loc[~data_p['Age'].isnull(), :].drop('Age', 1)
y_train = data_p.loc[~data_p['Age'].isnull(), :]['Age']
x_test = data_p.loc[data_p['Age'].isnull(), :].drop('Age', 1)
df_data.loc[df_data['Age'].isnull(), 'Age'] = predict_age(x_train, y_train, x_test)

2.4 特征转换

该部分要做的就是将分类变量处理成哑变量,因为模型只认识数字不认识字符。

要转换的特征有Sex、Embarked、Title、Cabin这4个,其中Sex属于二分类,可以用LabelEncoder处理。

label = LabelEncoder()
df_data['Sex_Code'] = label.fit_transform(df_data['Sex'])  # female为0, male为1

df_data = pd.concat([df_data, pd.get_dummies(df_data[['Embarked', 'Title', 'Cabin']])], axis=1)

2.5 剔除特征

经历了上面几个部分的特征处理,最后剔除掉没用的特征,并将结果保留下来,用以训练。

drop_columns = ['Sex', 'Name', 'Embarked', 'Cabin', 'Ticket', 'Title', 'Family_Name']
df_data = df_data.drop(drop_columns, 1)
df_data.to_csv(path_data + 'fe_data.csv', index=False)

得到一份有着1309行,26列的数据集

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容