XGboost进行分类预测,模型优化的实战

  • 前言
  • 一、特征工程处理程序
  • 二、使用xgboost算法进行训练
    • 1.引入库
    • 2.数据预处理
      • 对全部指标的预处理:
      • 对分类指标的处理
      • 对连续指标的处理
    • 3 模型调参部分
    • 4 训练结果展示
      • 算法解释部分
        • 1.1 XGBoost树的定义
        • 1.2 正则项:树的复杂度
        • 1.3 树该怎么长
        • 1.4 如何停止树的循环生成
        • 1.5XGBoost优势在哪里?
  • 总结
    • 1、数据抽象
    • 2、设定性能度量指标
    • 3、数据预处理
    • 4、选定模型
    • 5、训练及优化


前言

第一次写博客,有点烂,勿喷小白嘻嘻!

  • 机器学习/数据分析 说明:简单来说机器学习就是讲一段已经存有的数据,与数据对应的结果通过机器学习的算法,得到估计函数(训练得到的模型).然后再将需要预测的数据用估计函数(所得模型)得到预测结果。
  • 本项目目的: 对用户的多指标行为分析,预测用户是否存在潜在离网(这对运营商来说,可以给他们一些优惠套餐,使其回暖)。

在这里特别感谢 @可西哥 @歌以咏志,两位大神的指导!


提示:以下是本篇文章正文内容,下面案例可供参考。由于数据是公司内容,仅提供小部分用于学习交流。可加Q群 787800875.

一、特征工程处理程序

首先我们看一看原始数据数据的格式以及内容(由于参数太多,我就不一一列举了,大家可以直接去网上看,下面我简单贴个图

说明:在大数据的情况下(hive),例如:在某省大概有800万人用联通网,每个月有6-8万人离网,当然也有人入网(因为跟项目不涉及,个人没有咨询统计过),这是极端不平衡的样本数据,曾经采用升采样/降采样 的方法去平衡 训练,效果很差(因为数据不平衡导致的过拟合太严重)。后来经过不断的思考,根据目的出发,我是要找出潜在离网用户,所以我可以先排除大量的不离网用户。
思想方法:根据调整指标值计算出,各区间
代码如下:

import os
import numpy as np
import pandas as pd
import logging
from pyhive import hive
import time

核心找阈值方法:

def threshold(indicator, step, max_threshold):""":param indicator: 调参指标:param step: 调参步长:param max_threshold: 阈值最大值:return:"""print("对指标:%s 进行调参,其离网最大值为:%d,适配步长为:%d" % (index, max_threshold, x))while 1:count = sum_data.loc[sum_data[indicator] >= max_threshold, 'iflwyh'].value_counts()  # 0/1 计数if len(count) == 2:print('区间离网概率: ', count[1] / count[0])print('区间离网人数: ', count[1])print('区间非离网人数: ', count[0])threshold = max_thresholdprint('指标-->%s 阈值: %d' % (indicator, threshold))if (count[1] / count[0] > 0.00015) & (count[1] > 5):print('指标:%s 最终阈值:%d \n\n' % (indicator, threshold))breakelse:max_threshold = max_threshold - stepelse:print('指标-->%s 不参与计算' % indicator)break
print('开始读取数据...')
use_cols = ['指标1','指标2','指标3','指标4','指标5', 'iflwyh']
# --------------------训练自身数据时,只需修改如下两行文件的定位--------------------#
# ori_data = pd.read_csv(r'D:\project\xgboost_调优\降采样/stat_lwyh_kpi_day.csv', dtype={'msisdn': str})  # data.csv  # stat_lwyh_kpi_day.csv
print("读取hive数据库 :      ")
time.sleep(10)
hive_conns = HiveFetcher()
sum_data = orgindata_fetch(hive_conns, '用户名字段', use_cols, 75, '表名', end_day=None)
# 测试用
print(sum_data.shape)
save_data = sum_data.iloc[:50000]
save_data.to_csv('sum_data.csv')print("共有样本数: ", sum_data.shape[0])
sum_lwdata = sum_data[sum_data['标签指标'] > 0]  # 取离网用户
print("共有正样本数: ", sum_lwdata.shape[0])
result = sum_lwdata.max()  # 取离网用户各指标最大值
sum_data.loc[sum_data['标签指标'] > 0, '标签指标'] = 1  # 独热化
# 获取指标阈值
for index in result.index[0:-1]:num = float(result[index])if num > 1000000.00:continueelif num > 100000.00:x = 2000elif num > 10000.00:x = 1000elif num > 1000.00:x = 100elif num > 200.00:x = 10else:x = 1threshold(index, x, num)

程序运行结果(这里给出我跑hive数据库得到的某一个指标的结果形式):

由此结果可以看到: 得到xxxx指标(太敏感)阈值27,在27以上是有 3326559人 不离网,643离网,可以排除很大一部分人。当然项目最后选择的30,因为这里看起来是很优秀的,总共500W 用户,可以排除掉300W。这将对后面的训练平衡化带来很大的改变。

二、使用xgboost算法进行训练

1.引入库

代码如下:这里我不对算法做解释,主要是使用机器学习sklearn 切分数据集,得到分类报告、混淆矩阵等等,在不断的更新调整后,使用了线性回归、逻辑回归、神经网络、支持向量机、神经网络(初步) 等等算法之后,最后确定算法是xgboost,分类算法(这个后面可以作一些解释,选择该算法也是因为最后的f1最好)

import os, sys, time, logging, datetime
import numpy as np
import pandas as pd
# from collections import Counter
from imblearn.over_sampling import SMOTE, KMeansSMOTE  # ---------导入过采样算法库-------------#
from imblearn.under_sampling import ClusterCentroids  # ---------导入降采样算法库-------------#
from xgboost.sklearn import XGBClassifier
from sklearn.linear_model import LogisticRegression
import joblib
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.preprocessing import StandardScaler, RobustScaler
import sklearn.metrics as sm
from warnings import simplefilter
import random
from pyhive import hive
import traceback

2.数据预处理

说明:数据预处理是机器学习的核心,As everyone knows数据处理决定了模型效果的上限,而算法只能是想办法去接近这个上限!
这里需要我们的,sql能力、pandas操作技巧、机器学习 预备知识
在这里:给到我们的样本数据是 每个用户 在过去两个月的 40个行为指标。分为连续型/分类型 例如:
在这里我只能打比方(太敏感)
指标1 某用户过去两个月 每日打电话的次数
指标2 某用户过去两个月 每日使用流量数
and so on …
. . . .

对全部指标的预处理:

1 主要有两个方法:
1、基础处理,去掉无用字段(经过业务知识筛选)
2、选取用户过去60天,day粒度的数据

# -----------------------------整体指标处理函数-------------------------------#
def data_time_select(data):  # 根据时间段选取数据start = data['时间指标'].max() - pd.Timedelta('60 D')  # 提取60天内的数据data = data[data['时间指标'] > start]return data
def data_basic_treat(data, useless_columns):  # 基础处理方式data = data.drop(columns=useless_columns)  # 纵向去除无用字段data = data.dropna(thresh=2, axis=0)  # 横向去除完全缺失的数据, thresh代表只有超过该阈值的非空值data = data.drop_duplicates()  # 去重处理return data

对分类指标的处理

说明:对每个用户而言,其分类指标属性,应该是唯一的。我们做相应的哑变量、分箱化处理。
# --------------------------------处理分类变量部分--------------------------------#
‘’’
主要步骤是:
1. 单独提取分类指标数据;
2. 对每个用户统一各自的分类属性指标值
3. 此时用户统一指标值之后,仍旧可能存在部分属性缺失的情况,以众数进行填充处理
3. 将分类指标进行哑变量处理
‘’’

# -----------------------------分类指标处理函数-------------------------------#
def class_data_treat(data):  # 分类指标处理'''对每个用户而言,其分类指标属性,应该是唯一的。因此,考虑取最新有效值,作为统一值'''if data.isna().sum() == data.shape[0]:  # 如整个字段数值均缺失,直接返回nanreturn np.nanelif data.isna().sum() == 0:  # 如全部都没用缺失数值,则返回第一个值,也就是最新值return data.iloc[0]else:return data[data.first_valid_index()]  # 否则返回第一个有效值def classify_data(data, columns):  # 这个是人为设定的。如从业务方面认为,切分点的位置和数量不合理,可自行修改此处.for column in columns:if column == '分类指标1':class_level = [0, 20, 88, np.inf]  # 人为将套餐价格进行分类elif column == '分类指标2':class_level = [0, 20, 60, np.inf]  # 人为将年龄段进行分类elif column == '分类指标3':class_level = [0, 50, 100, np.inf]  # 人为将入网时长进行分类# label的编码,从0开始编号,自动根据class_level的长度设置。只要改变class_level的长度,编码也会自动改变。# 这样分箱化处理后,方便后续的one-hot编码。否则会因为这种类型的字段,数据偏多,导致生成过多字段# print(type(data[column]))# print(data[column])# print(data[column][-3:-1])data[column] = data[column].map(add_one)data[column] = pd.cut(data[column], class_level,labels=[i for i in range(len(class_level) - 1)])  # 将数据分箱化,按照不同的切割点设置,将原数据分成3个类别return data

分类指标处理后:

对连续指标的处理

说明: 1. 分别对每个指标求和,求均值,获得每个用户的相关指标
2. 在该时间周期内,求每个指标的方差或标准差,以查看用户操作的频率,及变动情况。
# --------------------------------处理连续变量部分--------------------------------#
‘’’
主要步骤:
1. 单独提取连续指标数据;
2. 按照时间和用户进行降序处理,以便将来与分类指标数据合并;
3. 对缺失值先填充数值0,表示时间段内无操作;
4. 对按照用户,分别对数据求总和,均值,标准差,统计次数,以获得新的数据指标。
5. 保留原用户的三个指标:套餐,年龄,入网时长,以便仍旧作为连续遍历,参与标准化处理。也就是对这三个指标,同时产生分类指标,和连续指标参与建模
6. 对这些连续数据进行标准化处理,以消除量纲影响
‘’’

# -----------------------------连续指标处理函数-------------------------------#
def continus_data_treat(data):  # 特征工程处理'''1. 分别对每个指标求和,求均值,获得每个用户的相关指标2. 在该时间周期内,求每个指标的方差或标准差,以查看用户操作的频率,及变动情况。'''data_sum = data.groupby('用户指标')[data.columns].agg(np.sum)  # 求总和data_sum.columns = ['%s_sum' % i for i in data_sum.columns]  # 对字段重命名,以便合并data_sum.sort_index(inplace=True, ascending=False)data_mean = data.groupby('用户指标')[data.columns].agg(np.mean)  # 求均值data_mean.columns = ['%s_mean' % i for i in data_mean.columns]data_mean.sort_index(inplace=True, ascending=False)data_count = data.groupby('用户指标')[data.columns].count()  # 计数data_count.columns = ['%s_count' % i for i in data_count.columns]data_count.sort_index(inplace=True, ascending=False)# ----------取连续数据集的各用户数据的标准差,可自行尝试是否增加这些指标的差异--------## data_std = data.groupby('用户指标')[data.columns].agg(np.std) #求标准差# data_std.columns = ['%s_std'%i for i in data_std.columns]# data_std.sort_index(inplace = True, ascending=False)# data_std.fillna(0, inplace= True) #标准化处理比较特殊,会导致缺失值的出现,因此需要额外进行数值0的填充。new_data = pd.concat([data_sum, data_mean, data_count], join='outer', axis=1)  # 合并连续指标字段数据return new_data

对连续变量标准化处理:

def standscale(data, columns):  # 对连续变量进行标准化处理std = StandardScaler()  # 常规标准化处理数据for column in columns:data[column] = data[column].map(add_one)# std = RobustScaler() #使用稳健标准化处理数据,该方法适用于离散程度较大的数据std_data = std.fit_transform(data)return std_data

连续变量处理后:
连续指标

至此数据预处理方法结束,得到了105个特征列,用于训练

3 模型调参部分

好累好累,准备好了样本数据,终于可以让娃学习了,数据处理很完美,模型上限暂定为1不过分吧,哈哈哈。。。。接下来,算法来接近我们模型上限,这可不简单!
虽然算法都是成熟的,但是我们需要调参得到最优解
模型调参数,我们需要一些必备的知识去看懂模型,然后进行调整。
首先需要一些基机器学习础知识
另外的高等数学函数收敛、线性代数、概率论 等等基础知识,让我们理解模型函数变得简单
当然不怎么会这些,也可以根据前人的经验去进行调参。后面再来的说说xgboost。。。

# -----------------------------模型调参函数-------------------------------#
# 适用于xgboot模型
def adjust_params(X, y):ne_interval = [100, 50, 10, 5]  # 设置决策树数量的搜素步长,便于自动更新模型。其用于逐步缩小最优值的范围搜索k = 20  # 设置搜索点的个数# 设定初始值,以便进行相应搜索other_params = {'n_estimators': 100, 'max_depth': 5, 'min_child_weight': 1, 'gamma': 0,'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0, 'reg_lambda': 1, 'learning_rate': 0.1}print('开始对模型调参,耗时较长,请耐心等待相关提示...')t3 = time.time()xgbr1_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params)# 第一次设定的搜索范围,为第一个步长起步,往后第k步长的范围值。params1_new = {'n_estimators': range(2 * ne_interval[0], (k + 1) * ne_interval[0] + 1, ne_interval[0])}grid1_new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_new)grid1_new.fit(X, y)ne1_new = grid1_new.best_params_['n_estimators']if ne1_new == params1_new['n_estimators'][-1]:params1_1new = {'n_estimators': range(ne1_new, ne1_new + (k + 1) * ne_interval[0] + 1, ne_interval[0])}grid1_1new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_1new)grid1_1new.fit(X, y)ne1_1new = grid1_1new.best_params_['n_estimators']params1_2new = {'n_estimators': range(ne1_1new - ne_interval[0], ne1_1new + ne_interval[0] + 1, ne_interval[1])}grid1_2new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_2new)grid1_2new.fit(X, y)ne1_2new = grid1_2new.best_params_['n_estimators']params1_3new = {'n_estimators': range(ne1_2new - ne_interval[1], ne1_2new + ne_interval[1] + 1, ne_interval[2])}grid1_3new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_3new)grid1_3new.fit(X, y)ne1_3new = grid1_3new.best_params_['n_estimators']params1_4new = {'n_estimators': range(ne1_3new - ne_interval[2], ne1_3new + ne_interval[2] + 1, ne_interval[3])}grid1_4new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_4new)grid1_4new.fit(X, y)res1_new = grid1_4new.best_params_['n_estimators']else:params1_1new = {'n_estimators': range(ne1_new - ne_interval[0], ne1_new + ne_interval[0] + 1, ne_interval[1])}grid1_1new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_1new)grid1_1new.fit(X, y)ne1_1new = grid1_1new.best_params_['n_estimators']params1_2new = {'n_estimators': range(ne1_1new - ne_interval[1], ne1_1new + ne_interval[1] + 1, ne_interval[2])}grid1_2new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_2new)grid1_2new.fit(X, y)ne1_2new = grid1_2new.best_params_['n_estimators']params1_3new = {'n_estimators': range(ne1_2new - ne_interval[2], ne1_2new + ne_interval[2] + 1, ne_interval[3])}grid1_3new = GridSearchCV(xgbr1_new, cv=10, param_grid=params1_3new)grid1_3new.fit(X, y)res1_new = grid1_3new.best_params_['n_estimators']print('模型调参所获的的最优参数n_estimators是:', res1_new)t4 = time.time()time_cost1 = pd.to_timedelta(t4 - t3, unit='s')print('模型的n_estimators调参训练耗时%s' % (time_cost1.round("s")))other_params2new = {'n_estimators': res1_new, 'max_depth': 5, 'min_child_weight': 1, 'gamma': 0,'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0, 'reg_lambda': 1,'learning_rate': 0.1}xgbr2_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params2new)params2_new = {'max_depth': range(3, 10), 'min_child_weight': range(1, 6)}grid2_new = GridSearchCV(xgbr2_new, cv=10, param_grid=params2_new)grid2_new.fit(X, y)res2_new = (grid2_new.best_params_['max_depth'], grid2_new.best_params_['min_child_weight'])print('模型调参所获的的最优参数max_depth是:', res2_new[0])print('模型调参所获的的最优参数min_child_weight是:', res2_new[1])t5 = time.time()time_cost2 = pd.to_timedelta(t5 - t4, unit='s')print('模型的max_depth和min_child_weight调参训练耗时%s' % (time_cost2.round("s")))# 调节gamma的参数other_params3new = {'n_estimators': res1_new, 'max_depth': res2_new[0], 'min_child_weight': res2_new[1], 'gamma': 0,'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0, 'reg_lambda': 1,'learning_rate': 0.1}xgbr3_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params3new)params3_new = {'gamma': np.arange(0, 1.1, 0.1)}grid3_new = GridSearchCV(xgbr3_new, cv=10, param_grid=params3_new)grid3_new.fit(X, y)res3_new = grid3_new.best_params_['gamma']print('模型调参所获的的最优参数gamma是:', res3_new)t6 = time.time()time_cost3 = pd.to_timedelta(t6 - t5, unit='s')print('模型的gamma调参训练耗时%s' % (time_cost3.round("s")))# 调节subsample、colsample_bytree的参数other_params4new = {'n_estimators': res1_new, 'max_depth': res2_new[0], 'min_child_weight': res2_new[1],'gamma': res3_new,'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0, 'reg_lambda': 1,'learning_rate': 0.1}xgbr4_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params4new)params4_new = {'subsample': np.arange(0.5, 1.01, 0.1), 'colsample_bytree': np.arange(0.5, 1.01, 0.1)}grid4_new = GridSearchCV(xgbr4_new, cv=10, param_grid=params4_new)grid4_new.fit(X, y)res4_new = (grid4_new.best_params_['subsample'], grid4_new.best_params_['colsample_bytree'])print('模型调参所获的的最优参数subsample是' % res4_new[0])print('模型调参所获的的最优参数colsample_bytree是' % res4_new[1])t7 = time.time()time_cost4 = pd.to_timedelta(t7 - t6, unit='s')print('模型的subsample和colsample_bytree调参训练耗时%s' % (time_cost4.round("s")))# 调节reg_alpha、reg_lambda的参数other_params5new = {'n_estimators': res1_new, 'max_depth': res2_new[0], 'min_child_weight': res2_new[1],'gamma': res3_new,'subsample': res4_new[0], 'colsample_bytree': res4_new[1], 'reg_alpha': 0, 'reg_lambda': 1,'learning_rate': 0.1}xgbr5_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params5new)params5_new = {'reg_alpha': np.arange(0.5, 1.01, 0.1), 'reg_lambda': np.arange(0.5, 1.01, 0.1)}grid5_new = GridSearchCV(xgbr5_new, cv=10, param_grid=params5_new)grid5_new.fit(X, y)res5_new = (grid5_new.best_params_['reg_alpha'], grid5_new.best_params_['reg_lambda'])print('模型调参所获的的最优参数reg_alpha是:', res5_new[0])print('模型调参所获的的最优参数reg_lambda是', res5_new[1])t8 = time.time()time_cost5 = pd.to_timedelta(t8 - t7, unit='s')print('模型的reg_alpha和reg_lambda调参训练耗时%s' % (time_cost5.round("s")))# 调节learning_rate的参数other_params6new = {'n_estimators': res1_new, 'max_depth': res2_new[0], 'min_child_weight': res2_new[1],'gamma': res3_new,'subsample': res4_new[0], 'colsample_bytree': res4_new[1], 'reg_alpha': res5_new[0],'reg_lambda': res5_new[1], 'learning_rate': 0.1}xgbr6_new = XGBClassifier(random_state=1, n_jobs=-1, **other_params6new)params6_new = {'learning_rate': np.arange(0.01, 0.11, 0.01)}grid6_new = GridSearchCV(xgbr6_new, cv=10, param_grid=params6_new)grid6_new.fit(X, y)res6_new = grid6_new.best_estimator_print('模型调参所获的的最优参数learning_rate是:', grid6_new.best_params_['learning_rate'])t9 = time.time()time_cost6 = pd.to_timedelta(t9 - t8, unit='s')print('模型的learning_rate调参训练耗时%s' % (time_cost6.round("s")))param_dict = dict(n_estimators=res1_new, max_depth=res2_new[0], min_child_weight=res2_new[1],gamma=res3_new, subsample=res4_new[0], colsample_bytree=res4_new[1], reg_alpha=res5_new[0],reg_lambda=res5_new[1], learning_rate=grid6_new.best_params_['learning_rate'],train_col=X.columns.tolist())  # 此行代码保存了实际参与训练的指标数据new_params = pd.DataFrame([param_dict])  # 需用列表,确保转成一行的格式# 考虑保存为csv格式,方便最优参数的追加保存。json格式文件无法追加# 追加时,无需再次写入字段名,以便后期读取文件时,不存在格式转换的问题。if os.path.exists(model_save_path + r"/model_param.csv"):new_params.to_csv(model_save_path + r"/model_params.csv", encoding='utf-8', index=False, mode='a', header=False)else:new_params.to_csv(model_save_path + r"/model_params.csv", encoding='utf-8', index=False)print('模型调参结束')return res6_new

还有一个打印混淆矩阵的方法!

# 格式化混淆矩阵
def format_print_confusion_matrix(confusion_matrix, type_name=None, placeholder_length=5):global fmif type_name is not None:type_name.insert(0, 'T \ P')  # 头部插入一个元素补齐for tn in type_name:fm = '%' + str(placeholder_length) + 's'print(fm % tn, end='')  # 不换行输出每一列表头print('\n')for i, cm in enumerate(confusion_matrix):if type_name is not None:fm = '%' + str(placeholder_length) + 's'print(fm % type_name[i + 1], end='')  # 不换行输出每一行表头for c in cm:fm = '%' + str(placeholder_length) + 'd'print(fm % c, end='')  # 不换行输出每一行元素print('\n')

至此:模型训练完毕,我们来看看实际效果。
这里我只给了方法,整体运行程序可以单独找我要

4 训练结果展示

经历了万水千山,无数的坎坷,不断调整,样本数据经过手动采样:
1、 5万数据的效果:

2 130万的效果

不知道为啥,这么模糊。。。 结果f1值为 0.97 ,说明我们相当的成功。

算法解释部分

1.1 XGBoost树的定义

先来举个例子,我们要预测一家人对电子游戏的喜好程度,考虑到年轻和年老相比,年轻更可能喜欢电子游戏,以及男性和女性相比,男性更喜欢电子游戏,故先根据年龄大小区分小孩和大人,然后再通过性别区分开是男是女,逐一给各人在电子游戏喜好程度上打分,如下图所示。

就这样,训练出了2棵树tree1和tree2,两棵树的结论累加起来便是最终的结论,所以小孩的预测分数就是两棵树中小孩所落到的结点的分数相加:2 + 0.9 = 2.9。爷爷的预测分数同理:-1 + (-0.9)= -1.9。具体如下图所示:

XGBoost的目标函数如下图所示:
其中:

  • 红色箭头所指向的L 即为损失函数(比如平方损失函数:l* (yi,yi)=(yi−yi)2)
  • 红色方框所框起来的是正则项(包括L1正则、L2正则)
  • 红色圆圈所圈起来的为常数项
  • 对于f(x),XGBoost利用泰勒展开三项,做一个近似。f(x)表示的是其中一颗回归树

XGBoost的核心算法思想不难,基本就是:

  • 不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新函数f(x),去拟合上次预测的残差。
  • 当我们训练完成得到k棵树,我们要预测一个样本的分数,其实就是根据这个样本的特征,在每棵树中会落到对应的一个叶子* 节点,每个叶子节点就对应一个分数
    最后只需要将每棵树对应的分数加起来就是该样本的预测值。

显然,我们的目标是要使得树群的预测值y′i尽量接近真实值yi,而且有尽量大的泛化能力,XGBoost是需要将多棵树的得分累加得到最终的预测得分(每一次迭代,都在现有树的基础上,增加一棵树去拟合前面树的预测结果与真实值之间的残差)。

那接下来,我们如何选择每一轮加入什么 f 呢?答案是非常直接的,选取一个 f 来使得我们的目标函数尽量最大地降低。这里 f 可以使用泰勒展开公式近似。

1.2 正则项:树的复杂度

XGBoost对树的复杂度包含了两个部分:

  • 一个是树里面叶子节点的个数T
  • 一个是树上叶子节点的得分w的L2模平方(对w进行L2正则化,相当于针对每个叶结点的得分增加L2平滑,目的是为了避免过拟合)

我们再来看一下XGBoost的目标函数(损失函数揭示训练误差 + 正则化定义复杂度):
L(ϕ)=∑il(y′i−yi)+∑kΩ(ft)
正则化公式也就是目标函数的后半部分,对于上式而言,y′i是整个累加模型的输出,正则化项∑kΩ(ft)是则表示树的复杂度的函数,值越小复杂度越低,泛化能力越强。

1.3 树该怎么长

很有意思的一个事是,我们从头到尾了解了xgboost如何优化、如何计算,但树到底长啥样,我们却一直没看到。很显然,一棵树的生成是由一个节点一分为二,然后不断分裂最终形成为整棵树。那么树怎么分裂的就成为了接下来我们要探讨的关键。对于一个叶子节点如何进行分裂,XGBoost作者在其原始论文中给出了一种分裂节点的方法:枚举所有不同树结构的贪心法

不断地枚举不同树的结构,然后利用打分函数来寻找出一个最优结构的树,接着加入到模型中,不断重复这样的操作。这个寻找的过程使用的就是贪心算法。选择一个feature分裂,计算loss function最小值,然后再选一个feature分裂,又得到一个loss function最小值,你枚举完,找一个效果最好的,把树给分裂,就得到了小树苗。

总而言之,XGBoost使用了和CART回归树一样的想法,利用贪婪算法,遍历所有特征的所有特征划分点,不同的是使用的目标函数不一样。具体做法就是分裂后的目标函数值比单子叶子节点的目标函数的增益,同时为了限制树生长过深,还加了个阈值,只有当增益大于该阈值才进行分裂。从而继续分裂,形成一棵树,再形成一棵树,每次在上一次的预测基础上取最优进一步分裂/建树。

1.4 如何停止树的循环生成

凡是这种循环迭代的方式必定有停止条件,什么时候停止呢?简言之,设置树的最大深度、当样本权重和小于设定阈值时停止生长以防止过拟合。具体而言,则

  1. 当引入的分裂带来的增益小于设定阀值的时候,我们可以忽略掉这个分裂,所以并不是每一次分裂loss function整体都会增加的,有点预剪枝的意思,阈值参数为(即正则项里叶子节点数T的系数);
  2. 当树达到最大深度时则停止建立决策树,设置一个超参数max_depth,避免树太深导致学习局部样本,从而过拟合;
  3. 样本权重和小于设定阈值时则停止建树。什么意思呢,即涉及到一个超参数-最小的样本权重和min_child_weight,和GBM的 min_child_leaf 参数类似,但不完全一样。大意就是一个叶子节点样本太少了,也终止同样是防止过拟合;
1.5XGBoost优势在哪里?

XGBoost使用了一阶和二阶偏导, 二阶导数有利于梯度下降的更快更准. 使用泰勒展开取得函数做自变量的二阶导数形式, 可以在不选定损失函数具体形式的情况下, 仅仅依靠输入数据的值就可以进行叶子分裂优化计算, 本质上也就把损失函数的选取和模型算法优化/参数选择分开了. 这种去耦合增加了XGBoost的适用性, 使得它按需选取损失函数, 可以用于分类, 也可以用于回归。


总结

脱离实际问题谈机器学习是毫无意义的,那么在给定数据集(所谓大数据)和具体问题的前提下,一般解决问题的步骤可以概括如下:

1、数据抽象

将数据集和具体问题抽象成数学语言,以恰当的数学符号表示。这样做自然是为了方便表述和求解问题,而且也更加直观。

2、设定性能度量指标

机器学习是产生模型的算法,一般来说模型都有误差。如果模型学的太好,把训练样本自身的一些特点当成所有潜在样本具有的一般性质,这种情况称为过拟合,这样的模型在面对新样本时就会出现较大误差,专业表述就是导致模型的泛化性能下降。

与之相对的是欠拟合,模型对样本的一般性质都没学好,这种情况一般比较好解决,扩充数据集或者调整模型皆可。

而一般来说无论是机器学习还是现在很火的深度学习,面对的主要问题都是过拟合。那么为了保证模型的泛化能力足够强,必须要有衡量模型泛化能力的评价标准,也就是性能度量的设定。

很显然不同的性能度量会导致不同的评判结果,好的性能度量能够直观的显示模型的好坏,同时也能看到不同模型,或者模型的不同参数下对解决问题的程度好坏。

进一步,有的问题可以直接基于设定的性能度量直接做最优化,得出该问题的一般求解模型。

比如回归任务最常用的性能度量就是均方误差,目标就是让均方误差最小,这就直接转化成了一个最优化问题。

其他一些常用的有错误率与精度、查准查全率、ROC与AOC等。

当然更为重要的是,仅仅设定好性能度量是不够的,不同模型或者不同参数下得到的性能度量结果一般是不同的,一般来说不能简单的比较结果,而应该基于统计假设检验来做效果判定。也就是说通过比较检验的方法,我们就可以判断,如果观察到A比B好,在统计意义上A的泛化性能是否优于B,以及这个判断的把握有多大。

3、数据预处理

之所以要做数据预处理,是因为提供的数据集往往很少是可以直接拿来用的。

可能的情况有:

  1. 样本某些属性值缺失
  2. 有未标记样本
  3. 样本的属性太多
  4. 样本量不足
  5. 没有分出测试集和验证集
  6. 不同类的样本数相差比较大

不同类的样本数相差比较大

这就是所谓类别不平衡问题。举个例子,样本里有998个反例,2个正例,如果一个模型对任何的新样本都预测为反例,那么它的精度为99.8%,虽然很高,但没有任何价值。

这种情况使用的基本策略是再缩放,具体方法则是采样。通过不同的采样方法来使类别达到平衡。

没有分出测试集和验证集

再说第五种情况,为了方便训练和验证模型好坏,数据集一般会以9:1或者其他合适比例(比例选择主要基于实际问题)分为测试集和验证集。如果给定的数据集只是已经标记好的样本,那么划分时必须保证数据集和测试集的分布大致均匀,这就涉及到具体的划分算法了。

样本量不足

第四种情况一般图像问题比较常遇到,如果样本量不足,不够模型来学习,效果自然很差。常见的方法一般有两种:

  1. 基于原数据集做扩充。比如对于图片,可以做各种处理,比如旋转、crop、对比度、亮度等基于像素值的调整,使得一个样本能够生成多个样本,从而达到扩充数据集的目的。
  2. 通过人工标注生成样本。比如大名鼎鼎的ImageNet数据集就是通过全球众包完成的,当然这个不仅耗时长,人工成本也很高,需谨慎考虑。

样本的属性太多

对于第三种情况,如果样本属性太多,为了保证模型的泛化性能足够强,则理论上必须保证数据集包括有所有属性的所有值,而这随着属性数目以及属性值是呈指数上升,很有可能达到天文数字,不仅难以计算,在现实情况也不可能收集到这么多的样本。

从数学角度看,每个样本的属性可以看成向量,属性数目是向量的维数,解决第三种情况一般有两种方法:

  1. 降维
  2. 特征选择

特征选择比较好理解,就是选择有用相关的属性,或者用另外一种表达方式:选择样本中有用、跟问题相关的特征。事实上这也很正常,并不一定样本的所有属性对具体问题都是有用的,通过一定的方法选择合适的特征可以保证模型更优。常用的方法大致分三类:过滤式、包裹式和嵌入式。

所谓的降维,即是多属性意味着是高维空间,在很多时候可以等价的映射到低维而不丢失主要信息。从空间映射的角度看,我们可以通过主成分分析PCA(线性映射)和核化主成分分析(非线性映射)来达到降维的目的。(补充:PCA是无监督降维方法,线性判别分析LDA则是监督降维防范)

有未标记样本

现实情况下往往很多数据集都有大量的未标记样本,有标记的样本反而比较少。如果直接弃用,很大程度上会导致模型精度低。这种情况解决的思路往往是结合有标记的样本,通过估计的方法把未标记样本变为伪的有标记样本。基本的方法有主动学习和半监督学习两种方法。

样本某些属性值缺失

样本的属性值缺失是很常见的一种情况。比如做书籍、视频、音乐等的个性化推荐时,需要用户对不同种类的偏好或评价。而用户不一定听过所有种类的歌,也不一定做出了评价。这就需要通过他已产生的样本数据和与之相类似的用户的数据来恢复和补全。

从原理上讲,这和压缩感知根据部分信息恢复全部信息是有类似的。

常用的方法涉及到协同过滤、矩阵补全等技术和方法。

总的来说,数据预处理是一个非常重要的过程,实际上数据预处理往往会和模型选择结合在一起。

4、选定模型

在数据集完美的情况下,接下来就是根据具体问题选定恰当的模型了。

一种方式是根据有没有标记样本考虑。

如果是有标记样本,可以考虑有监督学习,反之则是无监督学习,兼而有之就看半监督学习是否派的上用场。

无监督学习方法主要提到的是聚类。随机选定几个样本,通过一定的算法不停迭代直至收敛或者达到停止条件,然后便将所有样本分成了几类。

对有监督学习而言,根据最终所需要的输出结果

如果涉及到分类,可以参考的模型有线性回归及其非线性扩展、决策树、神经网络、支持向量机SVM、规则学习等
如果是回归问题,可以认为是分类的连续形式,方法便是以上模型的变种或扩展
如果涉及到概率,可以参考的有神经网络、贝叶斯、最大似然、EM、概率图、隐马尔科夫模型、强化学习等

5、训练及优化

选定了模型,如何训练和优化也是一个重要问题。

  • 如果要评估训练集和验证集的划分效果,常用的有留出法、交叉验证法、自助法、模型调参等
  • 如果模型计算时间太长,可以考虑剪枝
  • 如果是过拟合,则可通过引入正则化项来抑制(补偿原理)
  • 如果单个模型效果不佳,可以集成多个学习器通过一定策略结合,取长补短(集成学习)

机器学习——XGboost进行分类预测,模型优化的实战相关推荐

  1. 深度学习机器学习面试题汇——模型优化,轻量化,模型压缩

    深度学习机器学习面试题汇--模型优化,轻量化,模型压缩 提示:互联网大厂可能考的面试题 若CNN网络很庞大,在手机上运行效率不高,对应模型压缩方法有了解吗 介绍一下模型压缩常用的方法?为什么用知识蒸馏 ...

  2. 机器学习-工作流程与模型优化

    一.前序工作流程 1. 数据处理 数据清洗     不可信的样本丢掉     缺省值极多的字段考虑不用 数据采样     下/上采样     保证样本均衡 2. 特征工程 特征处理:     数值型 ...

  3. Scikit中的特征选择,XGboost进行回归预测,模型优化的实战

    前天偶然在一个网站上看到一个数据分析的比赛(sofasofa),自己虽然学习一些关于机器学习的内容,但是并没有在比赛中实践过,于是我带着一种好奇心参加了这次比赛. 赛题:足球运动员身价估计 比赛概述 ...

  4. 机器学习实战案例:使用随机森林/XGBoost等模型进行分类预测,提高银行营销活动效率

    1. 项目背景介绍 在这篇文章中,我们将使用 Python 搭建逻辑回归(Logistic Regression),随机森林(Random Forest),XGBoost,Bagging,KNN (K ...

  5. 加载svr模型_机器学习XGBoost实战,网格搜索自动调参,对比随机森林,线性回归,SVR【完整代码(含注释)+数据集见原文链接】...

    建议:阅读2020.8.7的文章,完全了解GDBT和XGBT的原理. 机器学习- XGBoost,GDBT[过程:决策树,集成学习,随机森林,GDBT,XGBT,LightGBM] 本次实践内容: 数 ...

  6. R语言构建文本分类模型并使用LIME进行模型解释实战:文本数据预处理、构建词袋模型、构建xgboost文本分类模型、基于文本训练数据以及模型构建LIME解释器解释多个测试语料的预测结果并可视化

    R语言构建文本分类模型并使用LIME进行模型解释实战:文本数据预处理.构建词袋模型.构建xgboost文本分类模型.基于文本训练数据以及模型构建LIME解释器解释多个测试语料的预测结果并可视化 目录

  7. AI:人工智能领域算法思维导图集合之有监督学习/无监督学习/强化学习类型的具体算法简介(预测函数/优化目标/求解算法)、分类/回归/聚类/降维算法模型选择思路、11类机器学习算法详细分类之详细攻略

    AI:人工智能领域算法思维导图集合之有监督学习/无监督学习/强化学习类型的具体算法简介(预测函数/优化目标/求解算法).分类/回归/聚类/降维算法模型选择思路.11类机器学习算法详细分类(决策树/贝叶 ...

  8. 机器学习模型应用以及模型优化的一些思路

    机器学习模型应用以及模型优化的一些思路 1 概述 本文会介绍如何应用机器学习模型来构建一个智能化应用的通用的过程以及过程中每个环节一些实践性的思路. 2 搭建基于机器学习模型的智能化应用的通用流程 下 ...

  9. 【ML】基于机器学习的房价预测研究(系列7:双向LSTM模型)

    写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌. 本次实战的项目是:基于机器学习的房价预测研究(附完整代 ...

最新文章

  1. python制作ios游戏_python自动化生成IOS的图标
  2. 别在迷恋正则表达式解析html了,好吗?
  3. Redis Template使用append方法不起作用的解决办法以及序列化/反序列化的解释
  4. 学习笔记(08):Python网络编程并发编程-实现服务端可以对多个客户端提供服务
  5. 为什么程序员总是打扮成这样一幅鬼样子
  6. 2023年中国AI论文影响力超越美国?
  7. SQL Server 2005中NTEXT与NVARCHAR(MAX)
  8. ajax回调函数有时成功有时失败,javascript - Ajax 回调函数行为随机。我做错了什么?...
  9. win7右键没有新建文件夹了
  10. ElementUI:使input自动聚焦的两种方法
  11. mysql怎么显示创表的语句_MySql轻松入门系列——第二站 使用visual studio 对mysql进行源码级调试...
  12. 《Spring实战,【吐血整理】
  13. ListView列表项View复用--ViewHolder模式
  14. c语言中fflush作用,详解C语言fflush()函数的使用
  15. 理想汽车的智能驾驶“方法论”
  16. vSphere 5.0 开发系列(一)vSphere 5.0 环境搭建手顺
  17. 【笔记】封神榜游戏设计VR
  18. android adb 命令汇总
  19. android SDK-25事件分发机制--源码正确解析
  20. java 继承关键字_java怎么继承,使用什么关键字?

热门文章

  1. 一、ABP启动运行项目
  2. Unity官方图形教程 学习笔记(二) -- Precomputed Realtime GI(实时全局光照)
  3. 【2022研电赛】兆易创新杯全国二等奖:自动驾驶汽车路面目标智能检测系统
  4. 人声修音效果器 – Celemony Melodyne Studio 5 V5.0.1 WiN-MAC
  5. 一些kindle资源
  6. ERROR:the default discovery settings are unsuitable for production use
  7. 思科 CCNA2 第六章测验答案
  8. 让你刷剧一直爽,CDN原理是什么丨技术前沿
  9. 堆栈段、数据段、代码段
  10. 《Python自然语言处理-雅兰·萨纳卡(Jalaj Thanaki)》学习笔记:03 理解句子的结构