XGBoost及随机森林处理kaggle—Titanic数据实战

一、什么是XGBoost

XGBoost是使用梯度提升框架GBDT实现的高效、灵活、可移植的机器学习库,是GBDT的一个C++实现。它将树的生成并行完成,从而提高学习速度。一般来说,XGBoost的速度和性能都要优于skearn.ensamble.GradientBoostingClassifier类。
官网为https://xgboost.readthedocs.io/en/latest/

二、实战

Titanic数据是在kaggle竞赛网上比较热门的题目之一,通过对乘客的年龄,性别,座次等数据的估计,预测乘客的生还情况。这次我们就用在竞赛中十分热门的算法XGBoost来对数据进行预测,为了便于比较,我也用随机森林和逻辑回归做了对比,先看看数据是什么样子的XGBoost及随机森林处理kaggle—Titanic数据实战

可以看到 数据中Age一列有少量缺失,我们可以考虑用均值替代法补充完整数据,也可以用一次随机森林,用其他几个特征对年龄进行估计。但Cabin一列有较多的缺失,我们可以用其他方法处理此列,也可以直接丢掉这一列。另外,Embarked列的数据是SCQ三个类别,我们要处理成one-hot编码形式。性别此列也最好用0.1表示。我们会在读入数据时进行这些处理

下面直接上代码

1.导入相关包

import xgboost as xgb
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
import csv

2.导入数据并进行初始化

x, y = load_data('12.Titanic.train.csv', True)

这里的load_data是自定义的读数据函数,

def load_data(file_name, is_train):
data = pd.read_csv(file_name)  # 数据文件路径
# print data.describe()
# 性别
data['Sex'] = data['Sex'].map({'female': 0, 'male': 1}).astype(int)
# 补齐船票价格缺失值
if len(data.Fare[data.Fare.isnull()]) > 0:
    fare = np.zeros(3)
    for f in range(0, 3):
        fare[f] = data[data.Pclass == f + 1]['Fare'].dropna().median()
    for f in range(0, 3):  # loop 0 to 2
        data.loc[(data.Fare.isnull()) & (data.Pclass == f + 1), 'Fare'] = fare[f]
            if is_train:
    # 年龄:使用随机森林预测年龄缺失值
    print ('随机森林预测缺失年龄:--start--')
    data_for_age = data[['Age', 'Survived', 'Fare', 'Parch', 'SibSp', 'Pclass']]
    age_exist = data_for_age.loc[(data.Age.notnull())]   # 年龄不缺失的数据
    age_null = data_for_age.loc[(data.Age.isnull())]
    # print age_exist
    x = age_exist.values[:, 1:]
    y = age_exist.values[:, 0]
    rfr = RandomForestRegressor(n_estimators=1000)
    rfr.fit(x, y)
    age_hat = rfr.predict(age_null.values[:, 1:])
    # print age_hat
    data.loc[(data.Age.isnull()), 'Age'] = age_hat
    print ('随机森林预测缺失年龄:--over--')
else:
    print ('随机森林预测缺失年龄2:--start--')
    data_for_age = data[['Age', 'Fare', 'Parch', 'SibSp', 'Pclass']]
    age_exist = data_for_age.loc[(data.Age.notnull())]  # 年龄不缺失的数据
    age_null = data_for_age.loc[(data.Age.isnull())]
    # print age_exist
    x = age_exist.values[:, 1:]
    y = age_exist.values[:, 0]
    rfr = RandomForestRegressor(n_estimators=1000)
    rfr.fit(x, y)
    age_hat = rfr.predict(age_null.values[:, 1:])
    # print age_hat
    data.loc[(data.Age.isnull()), 'Age'] = age_hat
    print ('随机森林预测缺失年龄2:--over--')

# 起始城市
data.loc[(data.Embarked.isnull()), 'Embarked'] = 'S'  # 保留缺失出发城市
# data['Embarked'] = data['Embarked'].map({'S': 0, 'C': 1, 'Q': 2, 'U': 0}).astype(int)
# print data['Embarked']
# 变成独热码
embarked_data = pd.get_dummies(data.Embarked)
# print embarked_data

# embarked_data = embarked_data.rename(columns={'S': 'Southampton', 'C': 'Cherbourg', 'Q': 'Queenstown', 'U': 'UnknownCity'})
# 对独热码的列重命名
embarked_data = embarked_data.rename(columns=lambda x: 'Embarked_' + str(x))
# 将独热码加入原数组
data = pd.concat([data, embarked_data], axis=1)
print (data.describe())
data.to_csv('New_Data.csv')

x = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked_C', 'Embarked_Q', 'Embarked_S']]
# x = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']]
y = None
if 'Survived' in data:
    y = data['Survived']

x = np.array(x)
y = np.array(y)

# 思考:这样做,其实发生了什么? 沿y轴方向复制了5倍

x = np.tile(x, (5, 1))
y = np.tile(y, (5, ))
if is_train:
    return x, y
return x, data['PassengerId']

我们已经得到了处理好的数据,先打印一下看看

  PassengerId    Survived     ...      Embarked_S  Embarked_U
	count   891.000000  891.000000     ...      891.000000  891.000000
	mean    446.000000    0.383838     ...        0.722783    0.002245
	std     257.353842    0.486592     ...        0.447876    0.047351
	min       1.000000    0.000000     ...        0.000000    0.000000
	25%     223.500000    0.000000     ...        0.000000    0.000000
	50%     446.000000    0.000000     ...        1.000000    0.000000
	75%     668.500000    1.000000     ...        1.000000    0.000000
	max     891.000000    1.000000     ...        1.000000    1.000000

可以看到数据已经没有缺失并且符合我们的要求了
接下来将一半的数据用来训练,一半用来测试

if __name__ == "__main__":
    x, y = load_data('12.Titanic.train.csv', True)
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.5, random_state=1)

这样就得到了训练数据和测试数据,我们先用逻辑回归来试一下

    lr = LogisticRegression(penalty='l2')
    lr.fit(x_train, y_train)
    y_hat = lr.predict(x_test)
    lr_rate = show_accuracy(y_hat, y_test, 'Logistic回归 ')		

这里的show_accuracy函数是自定义的计算正确率的函数,文章最后贴出
得到结果为

 Logistic回归:78.770%

接下来用随机森林,这里我用了100颗数,没有进行其他参数的调整

rfc = RandomForestClassifier(n_estimators=100)
    rfc.fit(x_train, y_train)
    y_hat = rfc.predict(x_test)
    rfc_rate = show_accuracy(y_hat, y_test, '随机森林 ')

得到结果为

随机森林:97.935%

最后我们用XBoost,这里设置了参数 每棵树最大深度3、 衰减因子0.1 防止过拟合 、不输出生成树的过程、 指定逻辑回归的导数和二阶导数

data_train = xgb.DMatrix(x_train, label=y_train)
data_test = xgb.DMatrix(x_test, label=y_test)
watch_list = [(data_test, 'eval'), (data_train, 'train')]
param = {'max_depth': 3, 'eta': 0.1, 'silent': 1, 'objective': 'binary:logistic'}
       
bst = xgb.train(param, data_train, num_boost_round=100, evals=watch_list)
y_hat = bst.predict(data_test)
# write_result(bst, 3)
y_hat[y_hat > 0.5] = 1
y_hat[~(y_hat > 0.5)] = 0
xgb_rate = show_accuracy(y_hat, y_test, 'XGBoost ')

结果为

XGBoost:85.592%

什么!效果还不如随机森林,于是我试着调整最大深度和衰减因子,可以得到十分好的效果,但是深度越大,衰减因子越小,越容易发生过拟合现象。所以说具体参数的调整还得考虑多方面的因素