之前有写过两篇关于Titanic比赛的简书,这几天上kaggle-Titanic的kernels在MostVost找了一篇排第一的kernels来看,参考链接,这个Kernels在模型方面做得特别好,所以,另写一篇简书作为总结。
流程
- 观察数据,我们要对数据有所了解,可以参考简书
- 特征工程以及数据清洗
- 介绍模型
- 跑模型
- 修改第二层模型
- 总结
1.代码分析
首先,导入我们需要用到的库
import pandas as pd
import numpy as np
from sklearn.cross_validation import KFold
import re
import plotly.graph_objs as go
import plotly.offline as py
from sklearn.ensemble import (RandomForestClassifier, AdaBoostClassifier,
GradientBoostingClassifier, ExtraTreesClassifier)
from sklearn.svm import SVC
import xgboost as xgb
import warnings
warnings.filterwarnings('ignore') # 忽略warning
pd.set_option('display.max_columns', None) # 输出结果显示全部列
然后,导入数据
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
PassengerId = test['PassengerId']
full_data = [train, test]
接下来,我们可以查看我们的数据
# 查看train集的数据
print(train.describe()) # 查看描述性统计,只能看数值型数据。
print(train.info()) # 查看数据的信息
# print(train.head()) # 查看train的前n行数据,默认为前5行
从图上我们可以看到,其中有5列不是数值型的,我们需要对其进行转换成数值,而且Age、Cabin这两列是有缺失值的,我们要对其进行填充或者丢弃。
2.特征工程以及数据清洗
添加一些新的特征
# 添加新的特征,名字的长度
train['Name_length'] = train['Name'].apply(len)
test['Name_length'] = test['Name'].apply(len)
# 乘客在船上是否有船舱
train['Has_Cabin'] = train["Cabin"].apply(lambda x: 0 if type(x) == float else 1)
test['Has_Cabin'] = test["Cabin"].apply(lambda x: 0 if type(x) == float else 1)
# 结合SibSp和Parch创建新的特性FamilySize
for dataset in full_data:
dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1
基于特征FamilySize创建新的特征IsAlone,因为一个人的话,顾虑没有那么多,只需要管好自己,生存的几率会大点,其中又分‘male’和‘female’,因为我记得电影中是有这样的一句台词“让女人和小孩先走”,所以,我们有理由相信,女性的生存率会比男性的要高。
for dataset in full_data:
dataset['IsAlone'] = 0
dataset.loc[dataset['FamilySize'] == 1), 'IsAlone'] = 1
通过name,添加特征Title
# 定义从乘客名中提取新的特征[Title]的函数
def get_title(name):
title_search = re.search(' ([A-Za-z]+)\.', name)
# 如果title存在,提取并返回它。
if title_search:
return title_search.group(1)
return ""
# 创建一个新的特征[Title]
for dataset in full_data:
dataset['Title'] = dataset['Name'].apply(get_title)
# 将所有不常见的Title分组为一个“Rare”组
for dataset in full_data:
dataset['Title'] = dataset['Title'].replace(
['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
缺失值填充
- Embarked只缺了两个,所以通过统计三个登船地点,选出了登船人数最多的登船地点(s)来填充。
- Test集的Fare只有一个缺失,所以用了中位数来填充
- Age缺失的比较多,所以在[age_avg - age_std, age_avg + age_std]这个范围取值来填充(其中age_avg是Age的平均值,age_std是Age的标准差)
# 通过统计三个登船地点人数最多的填充缺失值
for dataset in full_data:
dataset['Embarked'] = dataset['Embarked'].fillna('S')
# 缺失值填充,Test集的Fare有一个缺失,按中位数来填充,以及创建一个新的特征[CategoricalFare]
for dataset in full_data:
dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
train['CategoricalFare'] = pd.qcut(train['Fare'], 4)
# 缺失值填充,以及创建新的特征[CategoricalAge]
for dataset in full_data:
age_avg = dataset['Age'].mean()
age_std = dataset['Age'].std()
age_null_count = dataset['Age'].isnull().sum()
age_null_random_list = np.random.randint(age_avg - age_std, age_avg + age_std, size=age_null_count)
dataset['Age'][np.isnan(dataset['Age'])] = age_null_random_list
dataset['Age'] = dataset['Age'].astype(int)
通过Age,创建新的特征,一会用来给Age分组
train['CategoricalAge'] = pd.cut(train['Age'], 5)
print(train['CategoricalAge'])
从图片可以看出,年龄分为了5个范围,所以一会把年龄分为5组(0-4)。
分组以及转换数值
Sex:把性别转为0和1.
Embarked:把登船地点转为0、1、2.
Fare:把费用分为4组
Age:把年龄分为5组
for dataset in full_data:
dataset['Sex'] = dataset['Sex'].map({'female': 0, 'male': 1}).astype(int)
title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
dataset['Title'] = dataset['Title'].map(title_mapping)
dataset['Title'] = dataset['Title'].fillna(0)
dataset['Embarked'] = dataset['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}).astype(int)
dataset.loc[dataset['Fare'] <= 7.91, 'Fare'] = 0
dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare'] = 2
dataset.loc[dataset['Fare'] > 31, 'Fare'] = 3
dataset['Fare'] = dataset['Fare'].astype(int)
dataset.loc[dataset['Age'] <= 16, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
dataset.loc[dataset['Age'] > 64, 'Age'] = 4
特征选择,丢弃一些不必要的特征
PassengerID、Name、Ticket、Cabin、Sibsp等特征丢弃的原因是都已组合成新的特征,所以给予丢弃
CategorcalAge、CategoricalFare这些特征创建时是用来查看Age、Fare的分组区间,已使用,所以给予丢弃
drop_elements = ['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp']
train = train.drop(drop_elements, axis=1)
train = train.drop(['CategoricalAge', 'CategoricalFare'], axis=1)
test = test.drop(drop_elements, axis=1)
# print(train.head())
print(train.describe())
# print(train.head())
3.模型介绍
在跑模型之前,先提及一下本次kernels的基础知识,本次使用的
是Stacking融合模型,stacking集成模型与《机器学习》提及Bagging与Boosting不同,Bagging与Boosting是用弱模型的结果经过投票加权等方法集成一个新的强模型。而stacking翻译成中文叫做模型堆叠,接下来我们将对stacking进行介绍,是如何将模型堆叠在一起的。
stacking定义
stacking是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为训练集进行再训练,从而得到完整的stacking模型。
首先,我们来看一下下面这个图(两层基础),并进行步骤分解
步骤:
- 首先将数据分为5份,
- 在stacking的第一层定义5个基模型,其中每个模型选择做一下5折的交叉验证的预测,这样就相当于每个模型将所有数据预测了一遍
- 将第一层5个基模型的输出预测向量,作为第二层模型的特征做训练,
- 做test时,直接将test的数据喂给之前第一层训练好的5个基模型,5个模型预测出的至平均后作为第二层模型的输入
- 第二层使用一个分类器,将前面得出的特征,进行一次分类,因为主要结果在第一层已经预测完成,第二层要求不大(xgboost、LGBM等)即可。
Stacking注意事项
第一层的基模型最好是强模型,而第二层的基模型可以放一个简单的分类器,防止过拟合。
第一层基模型的个数不能太小,因为一层模型个数等于第二层分类器的特征维度。大家可以把勉强将其想象成神经网络的第一层神经元的个数,当然这个值也不是越多越好。
第一层的基模型必须“准而不同”,如果有一个性能很差的模型出现在第一层,将会严重影响整个模型融合的效果
本次比赛中部分模型的准确率:
- 朴素贝叶斯(Gaussian Naive Bays):72.2%
- 随机梯度下降(Stochastic Gradient Descent):76.8%
- 感知器(Perceptron):78%
- Linear SVC:79.12%
- 逻辑回归(Ligistic Regression):80.3%
- SVC(support Vector Machines):83.4%
- ExtraTreesClassifie: 84.5%
- AdaBoostClassifier: 85.6%
- 决策树(Decision tree):86.7%
- 随机森林(Random Forest):86.7%
4. 跑模型
这部分是这个kernels的重点,用的是Stacking。Stacking使用第一级分类器的预测作为对第二级模型的训练输入。我们使用了(RandomForestClassifier, AdaBoostClassifier,GradientBoostingClassifier, ExtraTreesClassifier,Support Vector Classifier)这5个分类器的预测作为下一个分类器(xgboost)的特征。
为什么使用的是这5个模型,其他需求还是这5个模型吗?为什么不用其他如神经网络等强模型呢?
-
这5个模型是当前比赛排名准度率最好的5个强模型,而其他的模型准度较低如逻辑回归(准确率0.80左右,而强模型均在0.83以上),这样会影响堆叠后的准确率,决策树准确率虽然达到86%,但是和随机森林相关性过高,且属于单树模型,容易过拟合,所以选取随机森林。
注意:当碰到其他需求的时候,不一定还是这5个模型,需要进行对其他模型的准确率测试,相关性确认等。
不使用神经网络原因:
- 神经网络不太可控,调参困难,容易出现过拟合等问题
- 较好的神经网络计算量较大,需要很大的数据量,而本次的比赛中Data只有890行,学习速度调整困难,收敛可能发生过快等问题,无法工作
在下面的代码中,我们编写了一个类SklearnHelper,它允许扩展所有Sklearn分类器所共有的内置方法(如train、predict和fit)。这消除了冗余,因为如果我们想调用5个不同的分类器,就不需要编写相同的方法5次。
# 一些有用的参数,下面会用到
ntrain = train.shape[0]
ntest = test.shape[0]
SEED = 0
NFOLDS = 5
kf = KFold(ntrain, n_folds=NFOLDS, random_state=SEED)
class SklearnHelper(object):
def __init__(self, clf, seed=0, params=None):
params['random_state'] = seed
self.clf = clf(**params)
def train(self, x_train, y_train):
self.clf.fit(x_train, y_train)
def predict(self, x):
return self.clf.predict(x)
def fit(self, x, y):
return self.clf.fit(x, y)
def feature_importances(self, x, y):
return self.clf.fit(x, y).feature_importances_
def get_oof(clf, x_train, y_train, x_test):
oof_train = np.zeros((ntrain,))
oof_test = np.zeros((ntest,))
oof_test_skf = np.empty((NFOLDS, ntest))
for i, (train_index, test_index) in enumerate(kf):
x_tr = x_train[train_index]
y_tr = y_train[train_index]
x_te = x_train[test_index]
clf.train(x_tr, y_tr)
oof_train[test_index] = clf.predict(x_te)
oof_test_skf[i, :] = clf.predict(x_test)
oof_test[:] = oof_test_skf.mean(axis=0)
return oof_train.reshape(-1, 1), oof_test.reshape(-1, 1)
现在让我们准备五个学习模型作为我们的第一级分类。这些模型都可以通过Sklearn库方便地调用,如下所示
1.Random Forest classifier
2.Extra Trees classifier
3.AdaBoost classifer
4.Gradient Boosting classifer
5.Support Vector Machine
输入上述分类器的参数
# 随机森林的参数
rf_params = {
'n_jobs': -1,
'n_estimators': 100,
'warm_start': True,
#'max_features': 0.2,
'max_depth': 6,
'min_samples_leaf': 2,
'max_features': 'sqrt',
'verbose': 0
}
# Extra Trees的参数
et_params = {
'n_jobs': -1,
'n_estimators': 100,
#'max_features': 0.5,
'max_depth': 8,
'min_samples_leaf': 2,
'verbose': 0
}
# AdaBoost的参数
ada_params = {
'n_estimators': 100,
'learning_rate': 0.01
}
# Gradient Boosting的参数
gb_params = {
'n_estimators': 100,
#'max_features': 0.2,
'max_depth': 5,
'min_samples_leaf': 2,
'verbose': 0
}
# Support Vector Classifier的参数
svc_params = {
'kernel': 'linear',
'C': 0.025
}
第一级分类器
# 通过前面定义的SklearnHelper类创建5个对象来表示5个学习模型
rf = SklearnHelper(clf=RandomForestClassifier, seed=SEED, params=rf_params)
et = SklearnHelper(clf=ExtraTreesClassifier, seed=SEED, params=et_params)
ada = SklearnHelper(clf=AdaBoostClassifier, seed=SEED, params=ada_params)
gb = SklearnHelper(clf=GradientBoostingClassifier, seed=SEED, params=gb_params)
svc = SklearnHelper(clf=SVC, seed=SEED, params=svc_params)
# 创建包含train、test的Numpy数组,以提供给我们的模型
y_train = train['Survived'].ravel()
train = train.drop(['Survived'], axis=1)
x_train = train.values
# test = test.drop(['Parch', 'Embarked', 'Has_Cabin', 'IsAlone'], axis=1)
x_test = test.values
#这些将会作为新的特征被使用
et_oof_train, et_oof_test = get_oof(et, x_train, y_train, x_test) # Extra Trees
rf_oof_train, rf_oof_test = get_oof(rf, x_train, y_train, x_test) # Random Forest
ada_oof_train, ada_oof_test = get_oof(ada, x_train, y_train, x_test) # AdaBoost
gb_oof_train, gb_oof_test = get_oof(gb, x_train, y_train, x_test) # Gradient Boost
svc_oof_train, svc_oof_test = get_oof(svc, x_train, y_train, x_test) # Support Vector Classifier
现在已经获得了我们的第一级预测,我们可以把它看作是一组新的特性,作为下一个分类器的训练数据。
查看各个特征对上述分类器的重要性
rf_features = rf.feature_importances(x_train, y_train)
et_features = et.feature_importances(x_train, y_train)
ada_features = ada.feature_importances(x_train, y_train)
gb_features = gb.feature_importances(x_train, y_train)
cols = train.columns.values
feature_dataframe = pd.DataFrame({'features': cols,
'Random Forest feature importances': rf_features,
'Extra Trees feature importances': et_features,
'AdaBoost feature importances': ada_features,
'Gradient Boost feature importances': gb_features})
feature_dataframe['mean'] = feature_dataframe.mean(axis=1) # axis = 1 computes the mean row-wise
print(feature_dataframe.head(11))
以图形形式显示各模型对特征的相关性,观察
从图中观察可以知道,第一层模型特征相关性平均后,各特征的相关性相对降低,准而不同”这个要求的。所以第一层五个模型融合是符合的。
画图查看各个分类器的相关性
base_predictions_train = pd.DataFrame( {'RandomForest': rf_oof_train.ravel(),
'ExtraTrees': et_oof_train.ravel(),
'AdaBoost': ada_oof_train.ravel(),
'GradientBoost': gb_oof_train.ravel()
})
data = [
go.Heatmap(
z= base_predictions_train.astype(float).corr().values ,
x=base_predictions_train.columns.values,
y= base_predictions_train.columns.values,
colorscale='Viridis',
showscale=True,
reversescale = True
)
]
py.iplot(data, filename='labelled-heatmap')
这些模型彼此之间的相关性越低,得分越高。
第二级分类器xgboost
x_train = np.concatenate((et_oof_train, rf_oof_train, ada_oof_train, gb_oof_train, svc_oof_train), axis=1)
x_test = np.concatenate((et_oof_test, rf_oof_test, ada_oof_test, gb_oof_test, svc_oof_test), axis=1)
gbm = xgb.XGBClassifier(
#learning_rate=0.01,
n_estimators=2000,
max_depth=4,
min_child_weight=2,
# gamma=1,
gamma=0.9,
subsample=0.8,
colsample_bytree=0.8,
objective='binary:logistic',
nthread=-1,
scale_pos_weight=1).fit(x_train, y_train)
predictions = gbm.predict(x_test)
提交
StackingSubmission = pd.DataFrame({'PassengerId': PassengerId,
'Survived': predictions})
StackingSubmission.to_csv("StackingSubmission.csv", index=False)
提交后的分数如下
训练所用时间:
5.对模型第二层进行修改
LGBM与XGBOOST:
XGBoost是在GBDT(梯度提升决策树)基础上发展而来,针对传统GBDT算法做了很多细节改进,包括损失函数、正则化、切分点查找算法优化、稀疏感知算法、并行化算法设计等等
LightGBM 是一个梯度 boosting 框架,使用基于学习算法的决策树。它可以说是分布式的,高效的
与以往的算法比较①histogram算法替换了传统的Pre-Sorted,某种意义上是牺牲了精度(但是作者声明实验发现精度影响不大)换取速度,直方图作差构建叶子。(xgboost的分布式实现也是基于直方图的,利于并行)②带有深度限制的按叶子生长 (leaf-wise) 算法代替了传统的(level-wise) 决策树生长策略,提升精度,同时避免过拟合危险。
LightGBM作者对模型的一些解释:
https://www.zhihu.com/question/51644470/answer/130946285
- 两者的结构主要区别:在过滤数据样例寻找分割值时,LightGBM 使用的是全新的技术:基于梯度的单边采样(GOSS);而 XGBoost 则通过预分类算法和直方图算法来确定最优分割。
选择LGBM替换XGBOOST的理由:
1.在速度上LGBM比XGBOOST的快十倍甚至百倍以上
2.LGBM与xgboost的精度不相上下
将stacking第二层xgboost替换成LGBM
lgbm_train = lgbm.Dataset ( data=x_train ,
label=y_train)
lgbm_params = {
'boosting': 'dart' ,
'application': 'binary' ,
'learning_rate': 0.01 ,
'feature_fraction': 0.5 ,
'verbose' : -1,
'drop_rate': 0.02
}
cv_results = lgbm.cv ( train_set=lgbm_train ,
params=lgbm_params ,
nfold=5 ,
num_boost_round=600 ,
early_stopping_rounds=50 ,
verbose_eval=50 ,
metrics=['auc'] )
optimum_boost_rounds = np.argmax ( cv_results['auc-mean'] )
print ( 'Optimum boost rounds = {}'.format ( optimum_boost_rounds ) )
print ( 'Best CV result = {}'.format ( np.max ( cv_results['auc-mean'] ) ) )
clf = lgbm.train ( train_set=lgbm_train ,
params=lgbm_params ,
num_boost_round=optimum_boost_rounds
)
##预测结果为浮点数,而本次比赛的预测结果需要0,1,所以将其转换
predictions = clf.predict ( x_test )
predictions = predictions + 0.5
predictions = predictions.astype(int)
LGBM的重要参数用法:
学习控制参数 | 含义 | 用法 |
---|---|---|
max_depth | 树的最大深度 | 当模型过拟合时,可以考虑首先降低 max_depth |
min_data_in_leaf | 叶子可能具有的最小记录数 | 默认20,过拟合时用 |
feature_fraction | 例如 为0.8时,意味着在每次迭代中随机选择80%的参数来建树 | boosting 为 random forest 时用 |
bagging_fraction | 每次迭代时用的数据比例 | 用于加快训练速度和减小过拟合 |
early_stopping_round | 如果一次验证数据的一个度量在最近的early_stopping_round 回合中没有提高,模型将停止训练 加速分析,减少过多迭代 | |
lambda | 指定正则化 | 0~1 |
min_gain_to_split | 描述分裂的最小 gain v控制树的有用的分裂 | |
max_cat_group | 在 group 边界上找到分割点 | 当类别数量很多时,找分割点很容易过拟合时 |
核心参数 | 含义 | 用法 |
---|---|---|
Task | 数据的用途 | 选择 train 或者 predict |
application | 模型的用途 | 选择 regression: 回归时,binary: 二分类时,multiclass: 多分类时 |
boosting | 要用的算法 | gbdt, rf: random forest, dart: Dropouts meet Multiple Additive Regression Trees, goss: Gradient-based One-Side Sampling |
num_boost_round | 迭代次数 | 通常 100+ |
learning_rat | e 如果一次验证数据的一个度量在最近的 early_stopping_round 回合中没有提高,模型将停止训练 | 常用 0.1, 0.001, 0.003… |
num_leaves | 默认 31 | |
device | cpu 或者 gpu | |
metric | mae: mean absolute error , mse: mean squared error , binary_logloss: loss for binary classification , multi_logloss: loss for multi classification |
IO参数 | 含义 |
---|---|
max_bin | 表示 feature 将存入的 bin 的最大数量 |
categorical_feature | 如果 categorical_features = 0,1,2, 则列 0,1,2是 categorical 变量 |
ignore_column | 与 categorical_features 类似,只不过不是将特定的列视为categorical,而是完全忽略 |
save_binary | 这个参数为 true 时,则数据集被保存为二进制文件,下次读数据时速度会变快 |
调参
IO parameter | 含义 |
---|---|
num_leaves | 取值应 <= 2 ^(max_depth), 超过此值会导致过拟合 |
min_data_in_leaf | 将它设置为较大的值可以避免生长太深的树,但可能会导致 underfitting,在大型数据集时就设置为数百或数千 |
max_depth | 这个也是可以限制树的深度 |
下表对应了 Faster Speed ,better accuracy ,over-fitting 三种目的时,可以调的参数
Faster Speed | better accuracy | over-fitting |
---|---|---|
将 max_bin 设置小一些 | 用较大的 max_bin | max_bin 小一些 |
num_leaves 大一些 | num_leaves 小一些 | |
用 feature_fraction 来做 sub-sampling | 用 feature_fraction | |
用 bagging_fraction 和 bagging_freq | 设定 bagging_fraction 和 bagging_freq | |
training data 多一些 | training data 多一些 | |
用 save_binary 来加速数据加载 | 直接用 categorical feature | 用 gmin_data_in_leaf 和 min_sum_hessian_in_leaf |
用 parallel learning 用 dart | 用 lambda_l1, lambda_l2 ,min_gain_to_split 做正则化 | |
num_iterations 大一些,learning_rate 小一些 | 用 max_depth 控制树的深度 |
本次比赛使用的参数解释:
- 'boosting': 'dart' # dart通过删除已经构建好的树,来降低模型过拟合的情况。
- application': 'binary' # 本次比赛为一个二分类问题,所以,选择binary
- 'learning_rate': 0.01 # 学习速率,基于数据量和精准度去选择,本次比赛数据量小,所以选择0.01即可以提高准度,也可以提高速度
- 'verbose' : -1#取消警告No further splits with positive gain, best gain: -inf,不设置为-1的话,数据量较少可能会提出警告
- 'drop_rate': 0.02 # 解决类别不平衡所采用的的欠采样方法,类别不平衡就是指分类任务中不同类别的训练样例数目差别很大的情况,而欠采样,即去除一些反例使得正、反例数目接近,然后再进行学习。
- nfold=5 #第一层有5个分类器,所以使用5折即可
提交
提交后分数如下
训练所用时间:
6.总结
相比于其他的kernels,这个kernels的特征工程方面做的不突出,突出的方面是用了新的方法Stacking,这个其他人在Titanic比赛中没有用到过的,这也是他排第一的原因。
进一步改善的步骤
必须指出的是,上述步骤只是显示了一个非常简单的方法。听说过在Kaggle的最高级别比赛中创建的组合,其中包括stacked classifiers的巨大组合,以及超过2级的stacking级别。这次尝试修改这个模型的第二层的时候,结果得分比xgboost更高,有可能是因为在作为分类层,xgboost需要人工去选择权重的变化,而LGBM可以根据实际变化而修改,xgboost权重在第一层的时候应该还可以调节。但是速度上真的LGBM大约快了60倍,Xgboost由于对比LGBM属于'元老'级模型,对于调参方面在网上的攻略更加完善,而且优化方面也相对突出,LGBM因为运行速度的提升或许在后面会优化并超越XGBOOST也有可能
代码地址:
https://gitee.com/ZHBIT-MachineLearning/Machine-Learning-Base/tree/master/TItanic