社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

数学推导+纯Python实现机器学习算法:LightGBM

小白学视觉 • 2 年前 • 237 次点击  

点击上方小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达


    第17讲我们谈到了竞赛大杀器XGBoost,本篇我们来看一种比XGBoost还要犀利的Boosting算法——LightGBM。LightGBM全称为轻量的梯度提升机(Light Gradient Boosting Machine),由微软于2017年开源出来的一款SOTA Boosting算法框架。跟XGBoost一样,LightGBM也是GBDT算法框架的一种工程实现,不过更加快速和高效。


XGBoost可优化的地方

    XGBoost通过预排序的算法来寻找特征的最佳分裂点,虽然预排序算法能够准确的找出特征的分裂点,但该方法占用空间的代价太大,在数据量和特征量都比较多的情况下,会严重影响算法性能。XGBoost寻找最佳分裂点的算法复杂度可以估计为:

复杂度=特征数量*特征分裂点的数量*样本数量


    既然XGBoost的复杂度是由特征数量、特征分裂点的数量和样本数量所决定的,那么LightGBM的优化空间自然是从这三个角度来考虑。LightGBM总体上仍然属于GBDT算法框架,关于GBDT算法特征我们已经在第15篇的时候重点叙述过,这里不再重复。我们重点梳理上述三个优化角度的基本原理即可。


Histogram算法

    为了减少特征分裂点的数量和更加高效寻找最佳特征分裂点,LightGBM区别于XGBoost的预排序算法,采用Histogram直方图的算法寻找最佳特征分裂点。其基本想法是将连续的浮点特征值进行离散化为k个整数并构造一个宽度为k的直方图。对某个特征数据进行遍历的时候,将离散化后的值用为索引作为直方图的累积统计量。遍历完一次后,直方图便可累积对应的统计量,然后根据该直方图寻找最佳分裂点。直方图算法如下图所示。


    直方图算法并不什么特别的创新之举,本质上就是一种数据离散化和分箱操作,但架不住速度快性能优,计算代价和内存占用都大大减少。


    直方图另外一个好处在于差加速。一个叶子节点的直方图可由其父节点的直方图与其兄弟节点的直方图做差得到,这也可以加速特征节点分裂。

    所以,从特征寻找最优分裂点角度,LightGBM使用了直方图算法进行优化。完整的直方图算法流程如下伪代码所示:


GOSS算法

    GOSS全称为单边梯度抽样算法(Gradient-based One-Side Sampling),是LightGBM从减少样本角度进行优化还设计的算法,算是LightGBM的核心原理之一。单边梯度抽样算法的主要思路是从减少样本的角度出发,将训练过程中大部分权重较小的样本剔除,仅对剩余样本数据计算信息增益。


    第16讲我们谈到了Adaboost算法,该算法的一个关键要素就是样本权重,通过在训练过程不断调整样本分类权重而达到最优分类效果的过程。但在GBDT系列中并没有样本权重的相关设计,GBDT采用样本梯度来代替权重的概念。一般来说,训练梯度小的样本,其经验误差也相对较小,说明这部分数据已经获得了较好的训练,GBDT的想法就是再一下的残差拟合中丢弃掉这部分样本,但这样做可能会改变训练样本的数据分布,对最终的训练精度有影响。


    针对以上问题,LightGBM提出采用GOSS采样算法。其目的就是最大效率的保留对计算信息增益有帮助的样本,提高模型训练速度。GOSS的基本做法是先将需要进行分裂的特征按绝对值大小降序排序,取绝对值最大的前a%个数据,假设样本大小为n,在剩下的(1-a)%个数据中随机选择b%个数据,将这b%个数据乘以一个常数(1-a)/b,这种做法会使得算法更加关注训练不够充分的样本,并且原始的数据分布不会有太大改变。最后使用a+b个数据来计算该特征的信息增益。GOSS算法流程伪代码如下所示。


    GOSS算法主要是从减少样本的角度来对GBDT进行优化的。丢弃梯度较小的样本并且在不损失太多精度的情况下提升模型训练速度,这使得LightGBM速度较快的原因之一。


EFB算法

    直方图算法对应于特征分裂点的优化、单边梯度抽样对应于样本量的优化,最后还剩下特征数量的优化没有谈到。而EFB算法就是针对于特征的优化。EFB算法全称为互斥特征捆绑算法(Exclusive Feature Bundling),通过将两个互斥的特征捆绑在一起,合为一个特征,在不丢失特征信息的前提下,减少特征数量,从而加速模型训练。大多数时候两个特征都不是完全互斥的,可以用定义一个冲突比率对特征不互斥程度进行衡量,当冲突比率较小时,可以将不完全互斥的两个特征捆绑,对最后的模型精度也没有太大影响。


    所谓特征互斥,即两个特征不会同时为非零值,这一点跟分类特征的one-hot表达有点类似。互斥特征捆绑算法的关键问题有两个,一个是如何判断将哪些特征进行绑定,另外一个就是如何将特征进行绑定,即绑定后的特征如何进行取值的问题。


    针对第一个问题,EFB算法将其转化为图着色(Graph Coloring Problem)的问题来求解。其基本思路是将所有特征看作为图的各个顶点,用一条边连接不相互独立的两个特征,边的权重则表示为两个相连接的特征的冲突比例,需要绑定在一起的特征就是图着色问题中要涂上同一种颜色的点(特征)。基于图着色问题的EFB求解算法伪代码如下:


    第二个问题是要确定绑定后的特征如何进行取值,其关键在于能够将原始的特征从合并后的特征中进行分离,也就是说绑定到一个特征后,我们仍然可以在这个绑定的bundle里面识别出原始特征。EFB算法针对该问题尝试从直方图的角度来处理,具体做法是将不同特征值分到绑定的bundle中不同的直方图箱子中,通过在特征取值中加一个偏置常量来进行处理。举个简单的例子,假设我们要绑定特征A和特征B两个特征,特征A的取值区间为[10,20),特征B的取值范围为[10,30),我们可以给特征B的取值范围加一个偏置量10,则特征B的取值范围变成了[20,40),绑定后的特征取值范围变成了[10,40),这样特征A和特征B即可进行愉快的融合了。特征合并算法伪代码如下所示:

    以上三个算法就是LightGBM在XGBoost基础上,针对特征分裂点、样本数量和特征数量分别做出的优化处理方法。


Leaf-Wise

    除了Histogram、GOSS和EFB算法之外,LightGBM还提出了区别于XGBoost的按层生长的叶子节点生长方法,即带有深度限制的按叶子节点生长(Leaf-Wise)的决策树生成算法。具体如下图所示:


    XGBoost采用按层生长的Level-Wise算法,好处是可以多线程优化,也方便控制模型复杂度,且不易过拟合。但缺点是不加区分的对待同一层所有叶子节点,大部分的节点分裂和增益计算不是必须的,带来了多余的计算开销。LightGBM提出了按叶子节点生长的Leaf-Wise算法,精度更高且更有效率,能够节约不必要的计算开销,同时为防止某一节点过分生长而加上一个深度限制机制,能够在保证精度的同时一定程度上防止过拟合。


    除了以上四点改进算法之外,LightGBM在工程实现上也有一些改进和优化。比如可以直接支持类别特征(不需要再对类别特征进行one-hot等处理)、高效并行和Cache命中率优化等。这里不做详述,读者朋友们可以查找LightGBM原文研读。


LightGBM实现

    从头开始实现了一个完整的LightGBM算法是一个复杂的系统性工程,限于时间和精力,这里笔者就不再进花时间手撸该算法。LightGBM开发团队提供了该算法的完整实现,这使得我们能够方便的进行调用。


    pip直接安装即可:

pip install lightgbm

    LightGBM提供了分类和回归两大类接口,下面以分类问题和iris数据集为例给出原生LightGBM接口的一个使用示例:

import pandas as pdimport lightgbm as lgbfrom sklearn.metrics import mean_squared_errorfrom sklearn.datasets import load_irisfrom sklearn.model_selection import train_test_splitfrom sklearn.datasets import make_classification
# 导入数据iris = load_iris()data = iris.datatarget = iris.targetX_train, X_test, y_train, y_test =train_test_split(data, target, test_size=0.2)
# 创建模型gbm = lgb.LGBMRegressor(objective='regression', num_leaves=31, learning_rate=0.05, n_estimators=20)# 模型训练gbm.fit(X_train, y_train,eval_set=[(X_test, y_test)],eval_metric='l1',early_stopping_rounds=5)# 预测测试集y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_)# 模型评估print(mean_squared_error(y_test, y_pred) ** 0.5)# 查看特征重要性print(list(gbm.feature_importances_))


    下面给出一个LightGBM回归模型五折交叉验证训练的代码模板,仅供大家参考。

import timeimport numpy as npimport pandas as pdimport lightgbm as lgbfrom sklearn.model_selection import KFoldfrom sklearn.metrics import mean_squared_error
# 训练特征,使用时label要换为实际标签名称features = [f for f in df.columns if f not in [label]]
# 自定义模型评估方法def evalerror(pred, df): label = df.get_label().values.copy() score = mean_squared_error(label, pred)*0.5 return ('mse', score, False)
# 指定超参数params = { 'learning_rate': 0.01, 'boosting_type': 'gbdt', 'objective': 'regression', 'metric': 'mse', 'sub_feature': 0.7, 'num_leaves': 60, 'colsample_bytree': 0.7, 'feature_fraction': 0.7, 'min_data': 100, 'min_hessian': 1, 'verbose': -1,}
t0 = time.time()train_preds = np.zeros(train.shape[0])
# 五折交叉验证训练kf = KFold(n_splits=5, shuffle=True, random_state=43)for i, (train_index, valid_index) in enumerate(kf.split(train)): print('train for {} epoch...'.format(i)) train2 = train.iloc[train_index] valid2 = train.iloc[valid_index] lgb_train = lgb.Dataset(train2[features], train2['total_cost'], categorical_feature=['hy', 'sex', 'pay_type']) lgb_valid = lgb.Dataset(valid2[features], valid2['total_cost'], categorical_feature=['hy', 'sex', 'pay_type']) model = lgb.train(params, lgb_train, num_boost_round=3000, valid_sets=lgb_valid, verbose_eval=300, feval=evalerror, early_stopping_rounds=100) # 特征重要性排序 feat_importance = pd.Series(model.feature_importance(), index=features).sort_values(ascending=False) train_preds[valid_index] += model.predict(valid2[features], num_iteration=model.best_iteration)
print('Validset score: {}'.format(mean_squared_error(labels, train_preds)*0.5))print('Cross Validation spend {} seconds'.format(time.time() - t0))


    以上就是本篇文章的主要内容。下一篇我们将关注另一种高效的Boosting框架——CatBoost。

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇




下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲
小白学视觉公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲
小白学视觉公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群


欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/152108
 
237 次点击