赞
踩
第一次写博客,有点烂,勿喷小白嘻嘻!
在这里特别感谢 @可西哥 @歌以咏志,两位大神的指导!
提示:以下是本篇文章正文内容,下面案例可供参考。由于数据是公司内容,仅提供小部分用于学习交流。可加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_threshold print('指标-->%s 阈值: %d' % (indicator, threshold)) if (count[1] / count[0] > 0.00015) & (count[1] > 5): print('指标:%s 最终阈值:%d \n\n' % (indicator, threshold)) break else: max_threshold = max_threshold - step else: 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: continue elif num > 100000.00: x = 2000 elif num > 10000.00: x = 1000 elif num > 1000.00: x = 100 elif num > 200.00: x = 10 else: x = 1 threshold(index, x, num)
程序运行结果(这里给出我跑hive数据库得到的某一个指标的结果形式):
由此结果可以看到: 得到xxxx指标(太敏感)阈值27,在27以上是有 3326559人 不离网,643离网,可以排除很大一部分人。当然项目最后选择的30,因为这里看起来是很优秀的,总共500W 用户,可以排除掉300W。这将对后面的训练平衡化带来很大的改变。
代码如下:这里我不对算法做解释,主要是使用机器学习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
说明:数据预处理是机器学习的核心,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]: # 如整个字段数值均缺失,直接返回nan return np.nan elif 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个特征列,用于训练
好累好累,准备好了样本数据,终于可以让娃学习了,数据处理很完美,模型上限暂定为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 fm if 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')
至此:模型训练完毕,我们来看看实际效果。
这里我只给了方法,整体运行程序可以单独找我要
经历了万水千山,无数的坎坷,不断调整,样本数据经过手动采样:
1、 5万数据的效果:
2 130万的效果
不知道为啥,这么模糊。。。 结果f1值为 0.97 ,说明我们相当的成功。
先来举个例子,我们要预测一家人对电子游戏的喜好程度,考虑到年轻和年老相比,年轻更可能喜欢电子游戏,以及男性和女性相比,男性更喜欢电子游戏,故先根据年龄大小区分小孩和大人,然后再通过性别区分开是男是女,逐一给各人在电子游戏喜好程度上打分,如下图所示。
就这样,训练出了2棵树tree1和tree2,两棵树的结论累加起来便是最终的结论,所以小孩的预测分数就是两棵树中小孩所落到的结点的分数相加:2 + 0.9 = 2.9。爷爷的预测分数同理:-1 + (-0.9)= -1.9。具体如下图所示:
XGBoost的目标函数如下图所示:
其中:
XGBoost的核心算法思想不难,基本就是:
显然,我们的目标是要使得树群的预测值y′i尽量接近真实值yi,而且有尽量大的泛化能力,XGBoost是需要将多棵树的得分累加得到最终的预测得分(每一次迭代,都在现有树的基础上,增加一棵树去拟合前面树的预测结果与真实值之间的残差)。
那接下来,我们如何选择每一轮加入什么 f 呢?答案是非常直接的,选取一个 f 来使得我们的目标函数尽量最大地降低。这里 f 可以使用泰勒展开公式近似。
XGBoost对树的复杂度包含了两个部分:
我们再来看一下XGBoost的目标函数(损失函数揭示训练误差 + 正则化定义复杂度):
L(ϕ)=∑il(y′i−yi)+∑kΩ(ft)
正则化公式也就是目标函数的后半部分,对于上式而言,y′i是整个累加模型的输出,正则化项∑kΩ(ft)是则表示树的复杂度的函数,值越小复杂度越低,泛化能力越强。
很有意思的一个事是,我们从头到尾了解了xgboost如何优化、如何计算,但树到底长啥样,我们却一直没看到。很显然,一棵树的生成是由一个节点一分为二,然后不断分裂最终形成为整棵树。那么树怎么分裂的就成为了接下来我们要探讨的关键。对于一个叶子节点如何进行分裂,XGBoost作者在其原始论文中给出了一种分裂节点的方法:枚举所有不同树结构的贪心法
不断地枚举不同树的结构,然后利用打分函数来寻找出一个最优结构的树,接着加入到模型中,不断重复这样的操作。这个寻找的过程使用的就是贪心算法。选择一个feature分裂,计算loss function最小值,然后再选一个feature分裂,又得到一个loss function最小值,你枚举完,找一个效果最好的,把树给分裂,就得到了小树苗。
总而言之,XGBoost使用了和CART回归树一样的想法,利用贪婪算法,遍历所有特征的所有特征划分点,不同的是使用的目标函数不一样。具体做法就是分裂后的目标函数值比单子叶子节点的目标函数的增益,同时为了限制树生长过深,还加了个阈值,只有当增益大于该阈值才进行分裂。从而继续分裂,形成一棵树,再形成一棵树,每次在上一次的预测基础上取最优进一步分裂/建树。
凡是这种循环迭代的方式必定有停止条件,什么时候停止呢?简言之,设置树的最大深度、当样本权重和小于设定阈值时停止生长以防止过拟合。具体而言,则
XGBoost使用了一阶和二阶偏导, 二阶导数有利于梯度下降的更快更准. 使用泰勒展开取得函数做自变量的二阶导数形式, 可以在不选定损失函数具体形式的情况下, 仅仅依靠输入数据的值就可以进行叶子分裂优化计算, 本质上也就把损失函数的选取和模型算法优化/参数选择分开了. 这种去耦合增加了XGBoost的适用性, 使得它按需选取损失函数, 可以用于分类, 也可以用于回归。
脱离实际问题谈机器学习是毫无意义的,那么在给定数据集(所谓大数据)和具体问题的前提下,一般解决问题的步骤可以概括如下:
将数据集和具体问题抽象成数学语言,以恰当的数学符号表示。这样做自然是为了方便表述和求解问题,而且也更加直观。
机器学习是产生模型的算法,一般来说模型都有误差。如果模型学的太好,把训练样本自身的一些特点当成所有潜在样本具有的一般性质,这种情况称为过拟合,这样的模型在面对新样本时就会出现较大误差,专业表述就是导致模型的泛化性能下降。
与之相对的是欠拟合,模型对样本的一般性质都没学好,这种情况一般比较好解决,扩充数据集或者调整模型皆可。
而一般来说无论是机器学习还是现在很火的深度学习,面对的主要问题都是过拟合。那么为了保证模型的泛化能力足够强,必须要有衡量模型泛化能力的评价标准,也就是性能度量的设定。
很显然不同的性能度量会导致不同的评判结果,好的性能度量能够直观的显示模型的好坏,同时也能看到不同模型,或者模型的不同参数下对解决问题的程度好坏。
进一步,有的问题可以直接基于设定的性能度量直接做最优化,得出该问题的一般求解模型。
比如回归任务最常用的性能度量就是均方误差,目标就是让均方误差最小,这就直接转化成了一个最优化问题。
其他一些常用的有错误率与精度、查准查全率、ROC与AOC等。
当然更为重要的是,仅仅设定好性能度量是不够的,不同模型或者不同参数下得到的性能度量结果一般是不同的,一般来说不能简单的比较结果,而应该基于统计假设检验来做效果判定。也就是说通过比较检验的方法,我们就可以判断,如果观察到A比B好,在统计意义上A的泛化性能是否优于B,以及这个判断的把握有多大。
之所以要做数据预处理,是因为提供的数据集往往很少是可以直接拿来用的。
可能的情况有:
不同类的样本数相差比较大
这就是所谓类别不平衡问题。举个例子,样本里有998个反例,2个正例,如果一个模型对任何的新样本都预测为反例,那么它的精度为99.8%,虽然很高,但没有任何价值。
这种情况使用的基本策略是再缩放,具体方法则是采样。通过不同的采样方法来使类别达到平衡。
没有分出测试集和验证集
再说第五种情况,为了方便训练和验证模型好坏,数据集一般会以9:1或者其他合适比例(比例选择主要基于实际问题)分为测试集和验证集。如果给定的数据集只是已经标记好的样本,那么划分时必须保证数据集和测试集的分布大致均匀,这就涉及到具体的划分算法了。
样本量不足
第四种情况一般图像问题比较常遇到,如果样本量不足,不够模型来学习,效果自然很差。常见的方法一般有两种:
样本的属性太多
对于第三种情况,如果样本属性太多,为了保证模型的泛化性能足够强,则理论上必须保证数据集包括有所有属性的所有值,而这随着属性数目以及属性值是呈指数上升,很有可能达到天文数字,不仅难以计算,在现实情况也不可能收集到这么多的样本。
从数学角度看,每个样本的属性可以看成向量,属性数目是向量的维数,解决第三种情况一般有两种方法:
特征选择比较好理解,就是选择有用相关的属性,或者用另外一种表达方式:选择样本中有用、跟问题相关的特征。事实上这也很正常,并不一定样本的所有属性对具体问题都是有用的,通过一定的方法选择合适的特征可以保证模型更优。常用的方法大致分三类:过滤式、包裹式和嵌入式。
所谓的降维,即是多属性意味着是高维空间,在很多时候可以等价的映射到低维而不丢失主要信息。从空间映射的角度看,我们可以通过主成分分析PCA(线性映射)和核化主成分分析(非线性映射)来达到降维的目的。(补充:PCA是无监督降维方法,线性判别分析LDA则是监督降维防范)
有未标记样本
现实情况下往往很多数据集都有大量的未标记样本,有标记的样本反而比较少。如果直接弃用,很大程度上会导致模型精度低。这种情况解决的思路往往是结合有标记的样本,通过估计的方法把未标记样本变为伪的有标记样本。基本的方法有主动学习和半监督学习两种方法。
样本某些属性值缺失
样本的属性值缺失是很常见的一种情况。比如做书籍、视频、音乐等的个性化推荐时,需要用户对不同种类的偏好或评价。而用户不一定听过所有种类的歌,也不一定做出了评价。这就需要通过他已产生的样本数据和与之相类似的用户的数据来恢复和补全。
从原理上讲,这和压缩感知根据部分信息恢复全部信息是有类似的。
常用的方法涉及到协同过滤、矩阵补全等技术和方法。
总的来说,数据预处理是一个非常重要的过程,实际上数据预处理往往会和模型选择结合在一起。
在数据集完美的情况下,接下来就是根据具体问题选定恰当的模型了。
一种方式是根据有没有标记样本考虑。
如果是有标记样本,可以考虑有监督学习,反之则是无监督学习,兼而有之就看半监督学习是否派的上用场。
无监督学习方法主要提到的是聚类。随机选定几个样本,通过一定的算法不停迭代直至收敛或者达到停止条件,然后便将所有样本分成了几类。
对有监督学习而言,根据最终所需要的输出结果
如果涉及到分类,可以参考的模型有线性回归及其非线性扩展、决策树、神经网络、支持向量机SVM、规则学习等
如果是回归问题,可以认为是分类的连续形式,方法便是以上模型的变种或扩展
如果涉及到概率,可以参考的有神经网络、贝叶斯、最大似然、EM、概率图、隐马尔科夫模型、强化学习等
选定了模型,如何训练和优化也是一个重要问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。