Data ScientistMachine Learning

Titanic生存预测1

2018-02-06  本文已影响53人  章光辉_数据

背景音乐: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个变量,其中:

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

此处逻辑是:

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列的数据集

上一篇下一篇

猜你喜欢

热点阅读