文章目录
1 LightGBM原理
1.1 GBDT和 LightGBM对比
GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT 在工业界应用广泛,通常被用于点击率预测,搜索排序等任务。GBDT 也是各种数据挖掘竞赛的致命武器,据统计 Kaggle 上的比赛有一半以上的冠军方案都是基于 GBDT。
LightGBM (Light Gradient Boosting Machine)是一个实现 GBDT 算法的框架,支持高效率的并行训练,并且具有以下优点:
更快的训练速度
更低的内存消耗
更好的准确率
分布式支持,可以快速处理海量数据
如下图,在 Higgs 数据集上 LightGBM 比 XGBoost 快将近 10 倍,内存占用率大约为 XGBoost 的1/6,并且准确率也有提升。
1.2 LightGBM 的动机
常用的机器学习算法,例如神经网络等算法,都可以以 mini-batch 的方式训练,训练数据的大小不会受到内存限制。
而 GBDT 在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的 GBDT 算法是不能满足其需求的。
LightGBM 提出的主要原因就是为了解决 GBDT 在海量数据遇到的问题,让 GBDT 可以更好更快地用于工业实践。
1.3 Xgboost 原理
目前已有的 GBDT 工具基本都是基于预排序的方法(pre-sorted)的决策树算法(如 xgboost)。这种构建决策树的算法基本思想是:
首先,对所有特征都按照特征的数值进行预排序。
其次,在遍历分割点的时候用O(#data)的代价找到一个特征上的最好分割点。
最后,找到一个特征的分割点后,将数据分裂成左右子节点。
这样的预排序算法的优点是:能精确地找到分割点。
缺点也很明显:
首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。
其次,时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。
最后,对 cache 优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对 cache 进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的 cache miss。
1.4 LightGBM 优化
LightGBM 优化部分包含以下:
基于 Histogram 的决策树算法
带深度限制的 Leaf-wise 的叶子生长策略
直方图做差加速
直接支持类别特征(Categorical Feature)
Cache 命中率优化
基于直方图的稀疏特征优化
多线程优化。
下面主要介绍 Histogram 算法、带深度限制的 Leaf-wise 的叶子生长策略和直方图做差加速。
1.4.1 Histogram 算法
直方图算法的基本思想是先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。
使用直方图算法有很多优点。首先,最明显就是内存消耗的降低,直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的值,而这个值一般用 8 位整型存储就足够了,内存消耗可以降低为原来的1/8。
然后在计算上的代价也大幅降低,预排序算法每遍历一个特征值就需要计算一次分裂的增益,而直方图算法只需要计算k次(k可以认为是常数),时间复杂度从O(#data*#feature)优化到O(k*#features)。
当然,Histogram 算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。
原因是决策树本来就是弱模型,分割点是不是精确并不是太重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单棵树的训练误差比精确分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。
1.4.2 带深度限制的 Leaf-wise 的叶子生长策略
在 Histogram 算法之上,LightGBM 进行进一步的优化。首先它抛弃了大多数 GBDT 工具使用的按层生长 (level-wise) 的决策树生长策略,而使用了带有深度限制的按叶子生长 (leaf-wise) 算法。Level-wise 过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上 Level-wise 是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。
Leaf-wise 则是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同 Level-wise 相比,在分裂次数相同的情况下,Leaf-wise 可以降低更多的误差,得到更好的精度。Leaf-wise 的缺点是可能会长出比较深的决策树,产生过拟合。因此 LightGBM 在 Leaf-wise 之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。
1.4.3 直方图加速
LightGBM 另一个优化是 Histogram(直方图)做差加速。一个容易观察到的现象:一个叶子的直方图可以由它的父亲节点的直方图与它兄弟的直方图做差得到。通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。利用这个方法,LightGBM 可以在构造一个叶子的直方图后,可以用非常微小的代价得到它兄弟叶子的直方图,在速度上可以提升一倍。
1.4.4 直接支持类别特征
实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化到多维的0/1 特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM 优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1 展开。并在决策树算法上增加了类别特征的决策规则。在 Expo 数据集上的实验,相比0/1 展开的方法,训练速度可以加速 8 倍,并且精度一致。据我们所知,LightGBM 是第一个直接支持类别特征的 GBDT 工具。
LightGBM 的单机版本还有很多其他细节上的优化,比如 cache 访问优化,多线程优化,稀疏特征优化等等。优化汇总如下:
1.4.5 LightGBM并行优化
LightGBM 还具有支持高效并行的优点。LightGBM 原生支持并行学习,目前支持特征并行和数据并行的两种。
特征并行的主要思想是在不同机器在不同的特征集合上分别寻找最优的分割点,然后在机器间同步最优的分割点。
数据并行则是让不同的机器先在本地构造直方图,然后进行全局的合并,最后在合并的直方图上面寻找最优分割点。
LightGBM 针对这两种并行方法都做了优化:
在特征并行算法中,通过在本地保存全部数据避免对数据切分结果的通信;
在数据并行中使用分散规约 (Reduce scatter) 把直方图合并的任务分摊到不同的机器,降低通信和计算,并利用直方图做差,进一步减少了一半的通信量。基于投票的数据并行则进一步优化数据并行中的通信代价,使通信代价变成常数级别。在数据量很大的时候,使用投票并行可以得到非常好的加速效果。
1.5 其他注意
- 当生长相同的叶子时,Leaf-wise 比 level-wise 减少更多的损失。
- 高速,高效处理大数据,运行时需要更低的内存,支持
- GPU 不要在少量数据上使用,会过拟合,建议 10,000+ 行记录时使用。
2 lightGBM代码
2.1 基础代码
# 01. train set and test set 划分训练集和测试集
train_data = lgb.Dataset(dtrain[predictors],label=dtrain[target],feature_name=list(dtrain[predictors].columns), categorical_feature=dummies)
test_data = lgb.Dataset(dtest[predictors],label=dtest[target],feature_name=list(dtest[predictors].columns), categorical_feature=dummies)
# 02. parameters 参数设置
param = {
'max_depth':6,
'num_leaves':64,
'learning_rate':0.03,
'scale_pos_weight':1,
'num_threads':40,
'objective':'binary',
'bagging_fraction':0.7,
'bagging_freq':1,
'min_sum_hessian_in_leaf':100
}
param['is_unbalance']='true'
param['metric'] = 'auc'
#03. cv and train 自定义cv函数和模型训练
bst=lgb.cv(param,train_data, num_boost_round=1000, nfold=3, early_stopping_rounds=30)
estimators = lgb.train(param,train_data,num_boost_round=len(bst['auc-mean']))
#04. test predict 测试集结果
ypred = estimators.predict(dtest[predictors])
2.2 模板代码
2.2.1 二分类
import lightgbm as lgb
import pandas as pd
import numpy as np
import pickle
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
print("Loading Data ... ")
# 导入数据
train_x, train_y, test_x = load_data()
# 用sklearn.cross_validation进行训练数据集划分,这里训练集和交叉验证集比例为7:3,可以自己根据需要设置
X, val_X, y, val_y = train_test_split(
train_x,
train_y,
test_size=0.05,
random_state=1,
stratify=train_y # 这里保证分割后y的比例分布与原数据一致
)
X_train = X
y_train = y
X_test = val_X
y_test = val_y
# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
# specify your configurations as a dict
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': {'binary_logloss', 'auc'}, #二进制对数损失
'num_leaves': 5,
'max_depth': 6,
'min_data_in_leaf': 450,
'learning_rate': 0.1,
'feature_fraction': 0.9,
'bagging_fraction': 0.95,
'bagging_freq': 5,
'lambda_l1': 1,
'lambda_l2': 0.001, # 越小l2正则程度越高
'min_gain_to_split': 0.2,
'verbose': 5,
'is_unbalance': True
}
# train
print('Start training...')
gbm = lgb.train(params,
lgb_train,
num_boost_round=10000,
valid_sets=lgb_eval,
early_stopping_rounds=500)
print('Start predicting...')
preds = gbm.predict(test_x, num_iteration=gbm.best_iteration) # 输出的是概率结果
# 导出结果
threshold = 0.5
for pred in preds:
result = 1 if pred > threshold else 0
# 导出特征重要性
importance = gbm.feature_importance()
names = gbm.feature_name()
with open('./feature_importance.txt', 'w+') as file:
for index, im in enumerate(importance):
string = names[index] + ', ' + str(im) + '\n'
file.write(string)
2.2.2 多分类
import lightgbm as lgb
import pandas as pd
import numpy as np
import pickle
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
print("Loading Data ... ")
# 导入数据
train_x, train_y, test_x = load_data()
# 用sklearn.cross_validation进行训练数据集划分,这里训练集和交叉验证集比例为7:3,可以自己根据需要设置
X, val_X, y, val_y = train_test_split(
train_x,
train_y,
test_size=0.05,
random_state=1,
stratify=train_y ## 这里保证分割后y的比例分布与原数据一致
)
X_train = X
y_train = y
X_test = val_X
y_test = val_y
# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
# specify your configurations as a dict
params = {
'boosting_type': 'gbdt',
'objective': 'multiclass',
'num_class': 9,
'metric': 'multi_error',
'num_leaves': 300,
'min_data_in_leaf': 100,
'learning_rate': 0.01,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'lambda_l1': 0.4,
'lambda_l2': 0.5,
'min_gain_to_split': 0.2,
'verbose': 5,
'is_unbalance': True
}
# train
print('Start training...')
gbm = lgb.train(params,
lgb_train,
num_boost_round=10000,
valid_sets=lgb_eval,
early_stopping_rounds=500)
print('Start predicting...')
preds = gbm.predict(test_x, num_iteration=gbm.best_iteration) # 输出的是概率结果
# 导出结果
for pred in preds:
result = prediction = int(np.argmax(pred))
# 导出特征重要性
importance = gbm.feature_importance()
names = gbm.feature_name()
with open('./feature_importance.txt', 'w+') as file:
for index, im in enumerate(importance):
string = names[index] + ', ' + str(im) + '\n'
file.write(string)
2.3 lightGBM 和 xgboost 的代码比较
2.3.1 划分训练集测试集
#xgboost
dtrain = xgb.DMatrix(x_train,label=y_train)
dtest = xgb.DMatrix(x_test)
# lightgbm
train_data = lgb.Dataset(x_train,label=y_train)
2.3.2 设置参数
#xgboost
parameters = {
'max_depth':7,
'eta':1,
'silent':1,
'objective':'binary:logistic',
'eval_metric':'auc',
'learning_rate':.05}
# lightgbm
param = {
'num_leaves':150,
'objective':'binary',
'max_depth':7,
'learning_rate':.05,
'max_bin':200}
param['metric'] = ['auc', 'binary_logloss']
2.3.3 模型训练
#xgboost
num_round = 50
from datetime import datetime
start = datetime.now()
xg = xgb.train(parameters,dtrain,num_round)
stop = datetime.now()
# lightgbm
num_round = 50
start = datetime.now()
lgbm = lgb.train(param,train_data,num_round)
stop = datetime.now()
2.3.4 模型执行时间
#xgboost
execution_time_xgb = stop - start
execution_time_xgb
# lightgbm
execution_time_lgbm = stop - start
execution_time_lgbm
2.3.5 模型测试
#xgboost
ypred = xg.predict(dtest)
ypred
# lightgbm
ypred2 = lgbm.predict(x_test)
ypred2[0:5]
2.3.6 分类转换
#xgboost
for i in range(0,9769):
if ypred[i] >= .5: # setting threshold to .5
ypred[i] = 1
else:
ypred[i] = 0
# lightgbm
for i in range(0,9769):
if ypred2[i] >= .5: # setting threshold to .5
ypred2[i] = 1
else:
ypred2[i] = 0
2.3.7 准确率计算
#xgboost
from sklearn.metrics import accuracy_score
accuracy_xgb = accuracy_score(y_test,ypred)
accuracy_xgb
# lightgbm
accuracy_lgbm = accuracy_score(ypred2,y_test)
accuracy_lgbm
y_test.value_counts()
from sklearn.metrics import roc_auc_score
2.3.8 roc_auc_score计算
#xgboost
auc_xgb = roc_auc_score(y_test,ypred)
# lightgbm
auc_lgbm = roc_auc_score(y_test,ypred2)
最后可以建立一个 dataframe 来比较 Lightgbm 和 xgb:
auc_lgbm comparison_dict = {
'accuracy score':(accuracy_lgbm,accuracy_xgb),
'auc score':(auc_lgbm,auc_xgb),
'execution time':(execution_time_lgbm,execution_time_xgb)}
comparison_df = DataFrame(comparison_dict)
comparison_df.index= ['LightGBM','xgboost']
comparison_df
3 lightGBM调参
3.1 参数
3.1.1制参数
3.1.2 核心参数
3.1.3 IO参数
3.2 调参
下表对应了 Faster Speed ,better accuracy ,over-fitting 三种目的时,可以调的参数
4 lightGBM的坑
4.1 设置提前停止
如果在训练过程中启用了提前停止,可以用 bst.best_iteration从最佳迭代中获得预测结果:
ypred = bst.predict(data,num_iteration = bst.best_iteration )
4.2 自动处理类别特征
当使用本地分类特征,LightGBM能提供良好的精确度。不像简单的one-hot编码,lightGBM可以找到分类特征的最优分割。
用categorical_feature指定分类特征
首先需要转换为int类型,并且只支持非负数。转换为连续范围更好。
使用min_data_per_group,cat_smooth去处理过拟合(当#data比较小,或者#category比较大)
对于具有高基数的分类特征(#category比较大),最好转换为数字特征。
4.3 自动处理缺失值
lightGBM通过默认方式处理缺失值,可以通过设置use_missing = false 来使其无效。
lightGBM通过默认的方式用NA(NaN)去表示缺失值,可以通过设置zero_as_missing = true 将其变为0
当设置zero_as_missing = false(默认)时,在稀疏矩阵里(和lightSVM),没有显示的值视为0
当设置zero_as_missing = true时,NA和0(包括在稀疏矩阵里,没有显示的值)视为缺失。
参考:
作者:Ghost_Hzp 链接:https://blog.csdn.net/weixin_39807102/article/details/81912566