机器学习算法原理实现——xgboost,核心是加入了正则化和损失函数二阶泰勒展开
先看总的图:
本质上就是在传统gbdt的决策树基础上加入了正则化防止过拟合,以及为了让损失函数求解更方便,加入了泰勒展开,这样计算损失函数更方便了(除了决策树代码有差别,其他都是gbdt一样,本文仅实现xgboost的决策树)。如下:
再解释各个步骤:
。。。
补充下:
让gpt来汇总下:
好了,我们直接写下实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | import numpy as np class XGBoostDecisionTree: def __init__( self , max_depth = 3 ): self .max_depth = max_depth def fit( self , X, y): # X是特征数据,y的第一列是伪残差,第二列是hessians self .tree = self ._grow_tree(X, y, depth = 0 ) def _gain( self , gradients, hessians): return np.square(gradients. sum ()) / (hessians. sum () + 1e - 9 ) def _split( self , X, y): best_gain = 0 best_split = None best_left_y, best_right_y = None , None gradients, hessians = y[:, 0 ], y[:, 1 ] total_gain = self ._gain(gradients, hessians) for i in range (X.shape[ 1 ]): thresholds = np.unique(X[:, i]) for thresh in thresholds: left_mask = X[:, i] < thresh right_mask = ~left_mask left_gradients = gradients[left_mask] right_gradients = gradients[right_mask] left_hessians = hessians[left_mask] right_hessians = hessians[right_mask] left_gain = self ._gain(left_gradients, left_hessians) right_gain = self ._gain(right_gradients, right_hessians) gain = left_gain + right_gain - total_gain if gain > best_gain: best_gain = gain best_split = (i, thresh) best_left_y = y[left_mask] best_right_y = y[right_mask] return best_gain, best_split, best_left_y, best_right_y def _grow_tree( self , X, y, depth): gradients, hessians = y[:, 0 ], y[:, 1 ] predicted_value = - gradients. sum () / (hessians. sum () + 1e - 9 ) node = { "value" : predicted_value} if depth < self .max_depth: gain, split, left_y, right_y = self ._split(X, y) if gain > 0 : # 只有当增益大于0时我们才真正地进行分裂 feature_idx, threshold = split left_mask = X[:, feature_idx] < threshold node[ "feature_idx" ] = feature_idx node[ "threshold" ] = threshold node[ "left" ] = self ._grow_tree(X[left_mask], left_y, depth + 1 ) node[ "right" ] = self ._grow_tree(X[~left_mask], right_y, depth + 1 ) return node def predict( self , X): return np.array([ self ._predict_single(x, self .tree) for x in X]) def _predict_single( self , x, node): if "feature_idx" in node: if x[node[ "feature_idx" ]] < node[ "threshold" ]: return self ._predict_single(x, node[ "left" ]) else : return self ._predict_single(x, node[ "right" ]) return node[ "value" ] import matplotlib.pyplot as plt X = np.linspace( 0 , 10 , 100 )[:, np.newaxis] y_true = np.sin(X).ravel() + np.random.normal( 0 , 0.1 , X.shape[ 0 ]) base_pred = np.ones_like(y_true) * y_true.mean() pseudo_residuals = - 2 * (y_true - base_pred) y = np.c_[pseudo_residuals, np.ones_like(y_true)] model = XGBoostDecisionTree(max_depth = 8 ) model.fit(X, y) predictions = model.predict(X) + base_pred # 加上基学习器的预测 plt.scatter(X, y_true, label = 'True values' , color = 'b' ) plt.plot(X, predictions, label = 'XGBoost Decision Tree' , color = 'r' ) plt.legend() plt.title( 'XGBoost Decision Tree Regression' ) plt.xlabel( 'X' ) plt.ylabel( 'y' ) plt.show() |
效果图:
代码中有几个细节值得注意:
hession矩阵就是二阶导数,就是1,因为损失函数二阶求导(泰勒展开后的)就是1!
此外,代码里对于增益的变化计算(从后面回答看,严格说还有除2):
所以说,实际上这里是Loss函数变化的值来模拟的gain! 变化更大了,loss就更小了!说明分裂效果越好!
使用二阶泰勒展开(而不是三阶或更高阶)来近似损失函数在机器学习,特别是在GBDT和XGBoost中,有几个原因:
-
计算效率:二阶泰勒展开为我们提供了一个关于损失的准确和有用的近似,同时保持了计算的简洁性。随着展开的阶数的增加,需要的计算量也会大幅增加。
-
准确性 vs. 复杂性的权衡:虽然更高阶的泰勒展开可以提供更准确的近似,但二阶展开通常足够用于损失的优化,尤其是在树的分裂点。超过二阶的项可能只会为优化过程带来微小的改进,但计算的复杂性会大幅增加。
-
二阶导数的物理含义:在优化的上下文中,二阶导数(即Hessian)提供了关于函数曲率的信息,这有助于我们了解步长应该是多大。这与牛顿方法的思想相一致,其中二阶导数被用来找到函数的最小值。对于大多数损失函数,二阶信息就足够了。
-
稳定性:引入更高阶的导数可能会导致算法在某些情况下的不稳定性,尤其是当高阶导数的值变化很大或接近零时。
-
经验上的成功:二阶泰勒展开在实践中被证明是非常成功的,特别是在XGBoost这样的算法中。这也给了我们一个实用的理由坚持使用它,而不是更高阶的展开。
总的来说,二阶泰勒展开为我们提供了一个既简洁又足够准确的损失近似,使得其在实际应用中成为了一个非常好的选择。
最后的疑点:
xgboost绕了这么大的圈,最后不就和adaboost是一样的原理吗,无非就是使用了残差,而adaboost直接是函数值。再就是xgboost再加入了所谓树的正则化防止过拟合,从上面各种推导可以看出来。
所以我觉得xgboost的核心优势还是加入了正则化,像adaboost的话,可能因为一个异常值而导致整个预测效果有偏差,而加入正则化以后可以使得这种偏差那边小。
其他工程化方法还包括:由于它可在特征粒度上并行计算, 结构风险和工程实现都做了很多优化, 泛化, 性能和扩展性都比GBDT要好。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
2022-09-16 我的sysmon采集全量数据配置
2022-09-16 Sysmon 使用查询进程名称获取 DNS 查询日志==》看来早些版本是不支持溯源的!
2020-09-16 网络流量画像(IP,主机维度)业界应用调研——time、port、size、rate、网络访问关系、IP归属、是否代理+历史异常情况(ddos常用)