泰坦尼克生存模拟

Kaggle泰坦尼克号生存者预测,带完整思路的机器学习实战项目

2018-09-03  本文已影响88人  ACphart

1. 概述

注: 此项目原地址在我的kaggle主页上,可以点击https://www.kaggle.com/acphart/titanic-with-scikit-learn进去直接Fork食用效果更佳,这里是直接把我在Jupyter Notebook中写的.ipynb文件直接转成md格式粘贴到这里的,而且这里不支持交互操作,输出的格式也比较乱,所以把所有的输出都clear了,在我的GitHub主页上,点击https://github.com/acphart/Kaggle_Titanic_with_sklearn可以下载到完整的.ipynb文件,下载之后在Jupyter Notebook中打开就可以进行交互式操作了 ,如果喜欢还可以在GitHub上送个Star哦。如果不嫌麻烦也可以把这里的的代码一个一个粘贴到Jupyter Notebook的cell中 ~~ :)

1.1 项目介绍

1.2 工作思路

  • 如果你准备研究机器学习的东西,或者构造机器学习应用程序,最好的实践方法不是建立一个非常复杂的系统,拥有多么复杂的变量;
    而是构建一个简单的算法,这样你可以很快地实现它。
  • 每当我研究机器学习的问题时,我最多只会花一天的时间,就是字面意义上的 24 小时,来试图很快的把结果搞出来,即便效果不好。
    坦白的说,就是根本没有用复杂的系统,但是只是很快的得到结果。即便运行得不完美,但是也把它运行一遍,最后通过交叉验证来检
    验数据。
  • 一旦做完,你可以画出学习曲线,通过画出学习曲线,以及检验误差,来找出你的算法是否有高偏差和高方差的问题,或者别的问题。
    在这样分析之后,再来决定用更多的数据训练,或者加入更多的特征变量是否有用。
  • 这么做的原因是:这在你刚接触机器学习问题时是一个很好的方法,你并不能提前知道你是否需要复杂的特征变量,或者你是否需要更
    多的数据,还是别的什么。提前知道你应该做什么,是非常难的,因为你缺少证据,缺少学习曲线。因此,你很难知道你应该把时间花在什
    么地方来提高算法的表现。但是当你实践一个非常简单即便不完美的方法时,你可以通过画出学习曲线来做出进一步的选择。你可以用这种
    方式来避免一种电脑编程里的过早优化问题,这种理念是:
  • 我们必须用证据来领导我们的决策,怎样分配自己的时间来优化算法,而不是仅仅凭直觉,凭直觉得出的东西一般总是错误的。
  • 除了画出学习曲线之外,一件非常有用的事是误差分析,通过检查交叉验证集中被错误预测的数据,你可以发现某些系统性的规律:
    什么类型的特征总是容易出错。 经常地这样做之后, 这个过程能启发你构造新的特征变量, 或者告诉你: 现在这个系统的短处,然后启
    发你如何去提高它。

1.3 我的流程

1.4 导入数据分析相关库

注: 这里我设置了IPython.core中cell交互的方式,所以下面cell中所有单行存在的变量都可以自动输出

没有设置的话,只有最后一个单行存在的变量可以自动输出

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import warnings
from IPython.core.interactiveshell import InteractiveShell

# 为了输出美观一点,忽略警告,因为后面有数组除数组会出现除0的情况,但并不影响操作,可以暂时把下面这句注释掉
warnings.filterwarnings('ignore')
# 下面这句是为了让cell中所有单行存在的变量都能输出,默认情况下是只输出最后一个单行存在的变量
InteractiveShell.ast_node_interactivity = 'all'     

1.5 读入训练数据和测试数据

train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
if True:
    for data in train, test:   
        data.head()                                      # 输出前5行数据
        data.describe()                                  # 输出数值型数据的基本统计描述信息
        data.describe(include=[np.object, 'category'])   # 输出类别型数据的基本统计描述信息

2. 初步数据特征探索

2.1 Pclass 船票的等级

train['Pclass'].value_counts()
train[['Pclass', 'Survived']].groupby(['Pclass'], as_index=False).mean()

2.2 Name 姓名

  1. 名字中有诸如Mr、Miss、Mrs、Master等等标识乘客身份和性别的特征
  2. 名字中的姓氏也可能隐含着乘客之间的亲属关系
  3. 名字中其他部分含有的信息我也不了解,对外国人的名字就了解这么多了 :(
train['Name'].unique().size
train['Name'].head(10)

2.3 Sex

train[['Sex', 'Survived']].groupby(['Sex'], as_index=False).mean()

2.4 Age

n_bins = 40
fig, ax = plt.subplots(1, 1)
all_n, all_bins, all_patches = ax.hist(train['Age'].dropna(), bins=n_bins, color='r')
svd_n, svd_bins, svd_patches = ax.hist(train.loc[train['Survived']==1, 'Age'].dropna(), bins=n_bins, color='g')
svd_rate = svd_n/all_n
fig, ax = plt.subplots(1, 1)
_ = ax.plot(all_bins[:-1], svd_rate)

2.5 SibSp 兄弟姐妹和配偶(在船上的同辈亲属总数)

train['SibSp'].value_counts()   # 各组的总人数
train[['SibSp', 'Survived']].groupby(['SibSp'], as_index=False).mean()    #各组的存活率

2.6 Parch 在船上的父母和子女总数

train['Parch'].value_counts()   # 各组的总人数
train[['Parch', 'Survived']].groupby(['Parch'], as_index=False).mean()   # 各组的存活率

2.7 Ticket 船票编号

train['Ticket'].unique().size
train['Ticket'].head(10)

2.8 Fare 票价

n_bins = 30
fig, ax = plt.subplots(1, 1)
_ = ax.hist(train['Fare'], bins=n_bins, color='r')
_ = ax.hist(train.loc[train['Survived']==1, 'Fare'], bins=n_bins, color='g')

2.9 Cabin 船舱编号

train['Cabin'].count()/891
test['Cabin'].count()/418

2.10 Embarked 登船港口

# 各个港口登船的人数及相应的存活率
train['Embarked'].value_counts()
train[['Embarked', 'Survived']].groupby(['Embarked'], as_index=False).mean()

# 看一下登船港口的平均票价
train[['Embarked', 'Fare']].groupby(['Embarked'], as_index=False).mean()

3. 快速实现一个简单的预测系统

3.1 选取特征和处理数据

# 将字符型数据转换成数值型数据,这里顺便也把测试集也转换
train['Sex'] = train['Sex'].map({'female': 0, 'male': 1}).astype(int)
test['Sex'] = test['Sex'].map({'female': 0, 'male': 1}).astype(int)

# 选取特征
quick_feature = ['Pclass', 'Sex']

3.2 切分训练集和测试集

np.random.seed(20180818)

Qtest_n = 630
# 打乱数据
train = train.sample(frac = 1.0)
train = train.reset_index()

# 切分数据
X_train = train.loc[0:Qtest_n, quick_feature].values
y_train = train.loc[0:Qtest_n, 'Survived'].astype(int).values
X_Qtest = train.loc[Qtest_n+1:891, quick_feature].values
y_Qtest = train.loc[Qtest_n+1:891, 'Survived'].astype(int).values

3.3 训练算法并查看性能

from sklearn import linear_model, svm

lgr = linear_model.LogisticRegression()
svc = svm.SVC()

for clf in lgr, svc:
    _ = clf.fit(X_train, y_train)
    y_ = clf.predict(X_Qtest)
    # 计算准确率,其实对于二分类也可以直接用clf.score(X_Qtest, y_Qtest)得出准确率
    (y_ == y_Qtest).sum() / y_Qtest.size      

感觉见证了奇迹一般,其实数据根本没有改动一下,而且只用了两个特征,正确率就达到了0.7885

4. 分析结果

4.1 正确率究竟是多少

假设一个识别中国人民族的算法,它不用训练,拿到数据直接输出汉族,准确率也能到92%,但这样的算法能信吗?

1 - y_Qtest.sum()/y_Qtest.size
  • 真阳性率(True Positive Rate):真正活着的人中预测存活的比例
  • 真阴性率(True Negative Rate):真正去世的人中预测死亡的比例
  • 约登(Youden)指数:真阳性率 + 真阴性率 - 1
# 1 & 1 =1    1 | 1 = 1
# 1 & 0 =0    1 | 0 = 1
# 0 & 1 =0    0 | 1 = 1
# 0 & 0 =0    0 | 0 = 0
Qtest_svd = y_Qtest.sum()
Qtest_die = y_Qtest.size - y_Qtest.sum()
TPR = (y_ & y_Qtest).sum() / Qtest_svd
TNR = (y_Qtest.size - (y_ | y_Qtest).sum()) / Qtest_die
print("survived : {0} \t TPR : {1:.4f}".format(Qtest_svd, TPR))
print("    dead : {0} \t TNR : {1:.4f}".format(Qtest_die, TNR))
print("Yd_index : {0:.4f}".format(TPR+TNR-1))

5. 下一步要做的事(步入正题)

5.1 我们快速实现的预测系统存在的问题及相应的改进方向

6. 进一步探索数据特征

在初步探索特征中,我们已经能看到Pclass、Sex、Age、SibSp、Parch、Fare和Embarked和存活率有相关,其中Pclass和Sex已经可以直接使用了

接下来就是要对剩余的几个进行处理,这里要注意的是,train数据集和test数据集要做同样的处理

6.1 补全数据

np.random.seed(20180818)

for data in train, test:
    # 这里去除掉NaN,并将年龄转换成整数,得到一列包含已有年龄的数组
    age_list = (data['Age'].dropna() + 0.5).astype(int)    
    
    # 从已有年龄中随机抽取相应个数的年龄
    fillage = []                                         
    for i in range(len(data['Age']) - data['Age'].count()):
        fillage.append(int(np.random.choice(age_list.values, 1)))

    # 补全数据,因为Fare和Embarked都是极个别数据缺失,就直接以均值和众数进行补全
    data['Age'][pd.isna(data['Age'])] = fillage
    data['Fare'][pd.isna(data['Fare'])] = data['Fare'].mean()
    data['Embarked'][pd.isna(data['Embarked'])] = 'S'

6.2 Age

# 按年龄分组
for n in range(2, 10):
    train['SortAge'] = pd.cut(train['Age'], n)
    train[['SortAge', 'Survived']].groupby(['SortAge'], as_index=False).mean()
# 按人数分组
for n in range(2, 10):
    train['SortAge'] = pd.qcut(train['Age'], n)
    train[['SortAge', 'Survived']].groupby(['SortAge'], as_index=False).mean()
for data in train, test:
    data['SortAge_1'] = 0
    data['SortAge_2'] = 0
    data['SortAge_3'] = 0
    # SortAge_1
    data.loc[data['Age'] <= 14, 'SortAge_1'] = 0
    data.loc[(data['Age'] > 14) & (data['Age'] <= 27), 'SortAge_1'] = 1
    data.loc[(data['Age'] > 27) & (data['Age'] <= 40), 'SortAge_1'] = 2
    data.loc[(data['Age'] > 40) & (data['Age'] <= 53), 'SortAge_1'] = 3
    data.loc[(data['Age'] > 53) & (data['Age'] <= 66), 'SortAge_1'] = 4
    data.loc[ data['Age'] > 66, 'SortAge_1'] = 5
    # SortAge_2
    data.loc[data['Age'] <= 19, 'SortAge_2'] = 0
    data.loc[(data['Age'] > 19) & (data['Age'] <= 25), 'SortAge_2'] = 1
    data.loc[(data['Age'] > 25) & (data['Age'] <= 31), 'SortAge_2'] = 2
    data.loc[(data['Age'] > 31) & (data['Age'] <= 41), 'SortAge_2'] = 3
    data.loc[ data['Age'] > 41, 'SortAge_2'] = 4
    # SortAge_3
    data.loc[data['Age'] <= 17, 'SortAge_3'] = 0
    data.loc[(data['Age'] > 17) & (data['Age'] <= 21), 'SortAge_3'] = 1
    data.loc[(data['Age'] > 21) & (data['Age'] <= 30), 'SortAge_3'] = 2
    data.loc[(data['Age'] > 30) & (data['Age'] <= 36), 'SortAge_3'] = 3
    data.loc[ data['Age'] > 36, 'SortAge_3'] = 4
train['SortAge_3'].value_counts()
train[['SortAge_3', 'Survived']].groupby(['SortAge_3'], as_index=False).mean()

6.2 SibSp、Parch

train['SibSp'].value_counts()   # 各组的总人数
train[['SibSp', 'Survived']].groupby(['SibSp'], as_index=False).mean()    #各组的存活率
train['Parch'].value_counts()   # 各组的总人数
train[['Parch', 'Survived']].groupby(['Parch'], as_index=False).mean()    #各组的存活率
train['had_SibSp'] = 0
train['had_Parch'] = 0
train.loc[train['SibSp'] >0, 'had_SibSp'] = 1
train.loc[train['Parch'] >0, 'had_Parch'] = 1

train[['had_SibSp', 'Survived']].groupby(['had_SibSp'], as_index=False).mean()
train[['had_Parch', 'Survived']].groupby(['had_Parch'], as_index=False).mean()
train['FamilySize'] = train['SibSp'] + train['Parch']
train['IsAlone'] = 0
train.loc[train['FamilySize'] >0, 'IsAlone'] = 1
train[['IsAlone', 'Survived']].groupby(['IsAlone'], as_index=False).mean()
test['FamilySize'] = test['SibSp'] + test['Parch']
test['IsAlone'] = 0
test.loc[test['FamilySize'] >0, 'IsAlone'] = 1

6.3 Fare

for n in range(2, 11):
    train['SortFare'] = pd.cut(train['Fare'], n)
    train[['SortFare', 'Survived']].groupby(['SortFare'], as_index=False).mean()
for n in range(2, 11):
    train['SortFare'] = pd.qcut(train['Fare'], n)
    train[['SortFare', 'Survived']].groupby(['SortFare'], as_index=False).mean()
test['Fare'] = test['Fare'].fillna(test['Fare'].mean())
for data in train, test:
    data['SortFare_2'] = 0
    data['SortFare_3'] = 0
    data['SortFare_4'] = 0
    # SortFare_2
    data.loc[data['Fare'] <= 14.45, 'SortFare_2'] = 0
    data.loc[ data['Fare'] > 14.45, 'SortFare_2'] = 1
    # SortFare_3
    data.loc[data['Fare'] <= 8.66, 'SortFare_3'] = 0
    data.loc[(data['Fare'] > 8.66) & (data['Fare'] <= 26), 'SortFare_3'] = 1
    data.loc[ data['Fare'] > 26, 'SortFare_3'] = 2
    # SortFare_4
    data.loc[data['Fare'] <= 7.91, 'SortFare_4'] = 0
    data.loc[(data['Fare'] > 7.91)  & (data['Fare'] <= 14.45), 'SortFare_4'] = 1
    data.loc[(data['Fare'] > 14.45) & (data['Fare'] <= 31), 'SortFare_4'] = 2
    data.loc[ data['Fare'] > 31, 'SortFare_4'] = 3

6.4 Embarked

train['Embarked'] = train['Embarked'].fillna('S')
test['Embarked'] = test['Embarked'].fillna('S')

train['Embarked'].count()
test['Embarked'].count()

train['Embarked'] = train['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
test['Embarked'] = test['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)

再次检查数据,除Cabin外数据已经齐全

for data in train, test:
    data.describe()
    data.describe(include=[np.object, 'category'])

7. 进一步训练算法

7.1 使用已确定的特征

np.random.seed(2018082401)

# 打乱数据
train = train.sample(frac = 1.0)
train = train.reset_index()
train = train.drop(columns=['index'])

# 切分数据
train_n = 630
X_train = train.loc[0:train_n, :]
y_train = train.loc[0:train_n, 'Survived'].astype(int)
X_cv = train.loc[train_n+1:891, :]
y_cv = train.loc[train_n+1:891, 'Survived'].astype(int)

base_feature = ['Pclass', 'Sex', 'IsAlone', 'Embarked']

7.2 选择算法,并比较各个算法的正确率

from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression

clf_set = [ KNeighborsClassifier(),
            SVC(),
            DecisionTreeClassifier(),
            RandomForestClassifier(),
            AdaBoostClassifier(),
            GradientBoostingClassifier(),
            GaussianNB(),
            LinearDiscriminantAnalysis(),
            QuadraticDiscriminantAnalysis(),
            LogisticRegression()]

bf = base_feature
for clf in clf_set:
    _ = clf.fit(X_train[bf].values, y_train.values)
    print("{0:>30s} : {1:<.4f}".format(clf.__class__.__name__, clf.score(X_cv[bf].values, y_cv.values)))

7.3 确定Age和Fare哪种分组比较适合

sortage_set = ['SortAge_1', 'SortAge_2', 'SortAge_3']
for sortage in sortage_set:
    af = ['Pclass', 'Sex', 'IsAlone', 'Embarked']
    af.append(sortage)
    print("{:>41s}".format(sortage))
    for clf in clf_set:
        _ = clf.fit(X_train[af].values, y_train.values)
        print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[af].values, y_cv.values)))
sortfare_set = ['SortFare_2', 'SortFare_3', 'SortFare_4']
for sortfare in sortfare_set:
    ff = ['Pclass', 'Sex', 'IsAlone', 'Embarked']
    ff.append(sortfare)
    print("{:>41s}".format(sortfare))
    for clf in clf_set:
        _ = clf.fit(X_train[ff].values, y_train.values)
        print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[ff].values, y_cv.values)))
feature = ['Pclass', 'Sex', 'IsAlone', 'Embarked', 'SortAge_1', 'SortFare_2']

for clf in clf_set:
        _ = clf.fit(X_train[ff].values, y_train.values)
        print("{0:>30s} : {1:7.4f}".format(clf.__class__.__name__, clf.score(X_cv[ff].values, y_cv.values)))

最后可以选SVC算法,然后特征可以把上面的base_feature和feature都试一下

另外关于Name的特征处理可以参考一下其他的notebook,然后我的就到这里了

clf = SVC()
_ = clf.fit(X_train[feature].values, X_train['Survived'])
y_ = clf.predict(test[feature])
上一篇 下一篇

猜你喜欢

热点阅读