划水一整天,模型看了仨!不错,虽然现在在打哈欠,还是很想把XGBoost梳理梳理
先从名字开始

XGBoost,eXtreme Gradient Boosting: em。。。。不理解

书上说,XGBoost有很好的性能,在各大比赛中大放异彩,行吧,冲这句,好好看看!

看了几篇,总感觉这个XGBoost不仅仅是对GBDT的改进版,还包含了对CART决策树的改进

1. 首先,GBDT是经过泰勒一阶导出来的,XGBoost则是经过泰勒二阶导,越高阶导越接近原函数值

初始的平方损失函数为Loriginal=(yi−ypre)2L_{original} = (y_i-y_{pre})^2Loriginal=(yiypre)2,由于yprey_{pre}ypre是由ypre=f(x)=∑i=1mfi(x)y_{pre}=f(x)=∑_{i=1}^mf_i(x)ypre=f(x)=i=1mfi(x)

因此,Loriginal=L(y,f(x)),表示由y和f(x)影响L值L_{original}=L(y,f(x)),表示由y和f(x)影响L值Loriginal=L(y,f(x)),表示由yf(x)影响L

L(y,f(x))=Lm−1(y,fm−1(x))+əL(y,fm−1(x))əfm−1(x)∗[f(x)−fm−1(x)]+12∗əL(y,fm−1(x))2əfm−1(x)2∗(f(x)−fm−1(x))2L(y,f(x)) = L_{m-1}(y,f_{m-1}(x))+\frac{ə_{L(y,f_{m-1}(x))}}{ə_{f_{m-1}(x)}}*[f(x)-f_{m-1}(x)]+\frac{1}{2}*\frac{ə^2_{L(y,f_{m-1}(x))}}{ə^2_{f_{m-1}(x)}}*(f(x)-f_{m-1}(x))^2L(y,f(x))=Lm1(y,fm1(x))+əfm1(x)əL(y,fm1(x))[f(x)fm1(x)]+21əfm1(x)2əL(y,fm1(x))2(f(x)fm1(x))2

gi=əL(yi,fm−1(xi))əfm−1(xi)g_i = \frac{ə_{L(y_i,f_{m-1}(x_i))}}{ə_{f_{m-1}(x_i)}}gi=əfm1(xi)əL(yi,fm1(xi))hi=əL(y,fm−1(xi))2əfm−1(xi)2h_i = \frac{ə^2_{L(y,f_{m-1}(x_i))}}{ə^2_{f_{m-1}(x_i)}}hi=əfm1(xi)2əL(y,fm1(xi))2L(y,fm−1(x))L(y,f_{m-1}(x))L(y,fm1(x))这仨都是前m-1轮的,相当于常数

f(x)=fm(x)f(x)=f_m(x)f(x)=fm(x),则有 Tm=fm(x)−fm−1(x)T_m = f_m(x)-f_{m-1}(x)Tm=fm(x)fm1(x)

Lm(y,fm(x))=Lm−1(y,fm−1(x))+∑i=1Ndatagi∗Tm(xi,θm)+12∑i=1Ndatahi∗Tm2(xi,θm)L_m(y,f_m(x)) = L_{m-1}(y,f_{m-1}(x))+∑_{i=1}^{N_{data}}g_i*T_m(x_i,θ_m)+\frac{1}{2}∑_{i=1}^{N_{data}}h_i*T^2_m(x_i,θ_m)Lm(y,fm(x))=Lm1(y,fm1(x))+i=1NdatagiTm(xi,θm)+21i=1NdatahiTm2(xi,θm)

2. 其次,XGBoost的优化①:增加正则化项 Ω(Tm(x))Ω(T_m(x))Ω(Tm(x))

晕了…明天再说!
本来周末把书带回家,准备要看看…果然,美男误我…

这里的Ω(Tm(x))=γ∗N叶+λ∑i=1N叶Ci2(x)Ω(T_m(x)) = γ*N_{叶}+λ∑_{i=1}^{N_{叶}}C_{i}^2(x)Ω(Tm(x))=γN+λi=1NCi2(x),这里的N叶N_叶N表示所有叶子节点的个数,Ci(x)C_{i}(x)Ci(x)是叶子节点的均值

γ∗N叶γ*N_{叶}γN是对叶子节点个数的惩罚,毕竟如果分裂太多,容易过拟合

λ∑i=1N叶Ci2(x)λ∑_{i=1}^{N_{叶}}C_{i}^2(x)λi=1NCi2(x)是为什么要对叶子均值进行惩罚呢?

哦!

因为在XGBoost中,每个叶子节点的均值,其实都是这组叶子节点的残差均值

但有些残差是正的,有些是负的,那要衡量拟合效果是否好,应该看与0的差距。

  • 残差为0,表示完美拟合
  • 残差为正,表示大于原值
  • 残差为负,表示小于原值

那么为了统一表示拟合效果,直接求平方,可避免正、负判别,且计算起来比绝对值更方便。

因此,λ∑i=1N叶Ci2(x)λ∑_{i=1}^{N_{叶}}C_{i}^2(x)λi=1NCi2(x)主要是对残差的惩罚

所以有Ω(Tm(x))=γ∗N叶+λ∑i=1N叶Ci2(x)Ω(T_m(x)) = γ*N_{叶}+λ∑_{i=1}^{N_{叶}}C_{i}^2(x)Ω(Tm(x))=γN+λi=1NCi2(x),完成了对叶子数量和残差的惩罚

惩罚项也加入到LKL_KLK损失函数里

Lm(y,fm(x))=Lm−1(y,fm−1(x))+∑i=1Ndatagi∗Tm(xi,θm)+12∑i=1Ndatahi∗Tm2(xi,θm)+γ∗N叶+λ∑i=1N叶Ci2(x)L_m(y,f_m(x)) = L_{m-1}(y,f_{m-1}(x))+∑_{i=1}^{N_{data}}g_i*T_m(x_i,θ_m)+\frac{1}{2}∑_{i=1}^{N_{data}}h_i*T^2_m(x_i,θ_m)+ γ*N_{叶}+λ∑_{i=1}^{N_{叶}}C_{i}^2(x)Lm(y,fm(x))=Lm1(y,fm1(x))+i=1NdatagiTm(xi,θm)+21i=1NdatahiTm2(xi,θm)+γN+λi=1NCi2(x)

求这个损失函数的极小值,求极值的时候,常数项不需要参与运算,因此函数里可以去掉常数项Lm−1(y,fm−1(x))L_{m-1}(y,f_{m-1}(x))Lm1(y,fm1(x)),并且为了求极值计算方便,还可以将平方项λ∑i=1N叶Ci2(x)λ∑_{i=1}^{N_{叶}}C_{i}^2(x)λi=1NCi2(x)的系数,设为12\frac{1}{2}21这样后续求极值时可以化简运算

最终Lm(y,fm(x))=∑i=1Ndatagi∗Tm(xi,θm)+12∑i=1Ndatahi∗Tm2(xi,θm)+γ∗N叶+12λ∑i=1N叶Ci2(x)L_m(y,f_m(x)) =∑_{i=1}^{N_{data}}g_i*T_m(x_i,θ_m)+\frac{1}{2}∑_{i=1}^{N_{data}}h_i*T^2_m(x_i,θ_m)+ γ*N_{叶}+\frac{1}{2}λ∑_{i=1}^{N_{叶}}C_{i}^2(x)Lm(y,fm(x))=i=1NdatagiTm(xi,θm)+21i=1NdatahiTm2(xi,θm)+γN+21λi=1NCi2(x)

这里要梳理一下Ndata和N叶N_{data}和N_{叶}NdataN的关系
在这里插入图片描述
所以,可以将损失函数里式子进行转化

  • ∑i=1Ndatagi∗Tm(xi,θm)=∑j=1N叶(∑i∈I(j)gi)Cj(x)∑_{i=1}^{N_{data}}g_i*T_m(x_i,θ_m)=∑_{j=1}^{N_{叶}}(∑_{i∈I(j)}g_i)C_{j}(x)i=1NdatagiTm(xi,θm)=j=1NiI(j)giCj(x),用Gj表示∑i∈I(j)giG_j表示∑_{i∈I(j)}g_iGj表示iI(j)gi
  • ∑i=1Ndatahi∗Tm2(xi,θm)=∑j=1N叶(∑i∈I(j)hi)Cj2(x)∑_{i=1}^{N_{data}}h_i*T^2_m(x_i,θ_m)=∑_{j=1}^{N_{叶}}(∑_{i∈I(j)}h_i)C_{j}^2(x)i=1NdatahiTm2(xi,θm)=j=1NiI(j)hiCj2(x),用Hj表示∑i∈I(j)hiH_j表示∑_{i∈I(j)}h_iHj表示iI(j)hi

则损失函数为
Lm(y,fm(x))=∑j=1N叶GjCj(x)+12∑j=1N叶HjCj2(x)+γ∗N叶+12λ∑j=1N叶Cj2(x)+λN叶L_m(y,f_m(x)) =∑_{j=1}^{N_{叶}}G_jC_{j}(x)+\frac{1}{2}∑_{j=1}^{N_{叶}}H_jC_{j}^2(x)+ γ*N_{叶}+\frac{1}{2}λ∑_{j=1}^{N_{叶}}C_{j}^2(x)+λN_{叶}Lm(y,fm(x))=j=1NGjCj(x)+21j=1NHjCj2(x)+γN+21λj=1NCj2(x)+λN

合并同类项:

Lm(y,fm(x))L_m(y,f_m(x))Lm(y,fm(x))

=∑j=1N叶GjCj(x)+12∑j=1N叶(Hj+λ)Cj2(x)+γ∗N叶=∑_{j=1}^{N_{叶}}G_jC_{j}(x)+\frac{1}{2}∑_{j=1}^{N_{叶}}(H_j+λ)C_{j}^2(x)+ γ*N_{叶}=j=1NGjCj(x)+21j=1NHj+λCj2(x)+γN

=∑j=1N叶[GjCj(x)+12(Hj+λ)Cj2(x)+γ]=∑_{j=1}^{N_{叶}}[G_jC_{j}(x)+\frac{1}{2}(H_j+λ)C_{j}^2(x)+ γ]=j=1N[GjCj(x)+21(Hj+λ)Cj2(x)+γ]

  • Gj=∑i∈I(j)gi=∑i∈I(j)əL(yi,fm−1(xi))əfm−1(xi)G_j=∑_{i∈I(j)}g_i=∑_{i∈I(j)} \frac{ə_{L(y_i,f_{m-1}(x_i))}}{ə_{f_{m-1}(x_i)}}Gj=iI(j)gi=iI(j)əfm1(xi)əL(yi,fm1(xi)),是常数项
  • Hj=∑i∈I(j)hi=∑i∈I(j)əL(y,fm−1(xi))2əfm−1(xi)2H_j=∑_{i∈I(j)}h_i=∑_{i∈I(j)}\frac{ə^2_{L(y,f_{m-1}(x_i))}}{ə^2_{f_{m-1}(x_i)}}Hj=iI(j)hi=iI(j)əfm1(xi)2əL(y,fm1(xi))2,也是常数项
  • γ也是我们提前设置的常数项
  • 只要计算出每个叶子节点中的GjCj(x)+12(Hj+λ)Cj2(x)+γG_jC_{j}(x)+\frac{1}{2}(H_j+λ)C_{j}^2(x)+ γGjCj(x)+21(Hj+λ)Cj2(x)+γ极小值,就可以算出所有叶子节点∑j=1N叶GjCj(x)+12∑j=1N叶(Hj+λ)Cj2(x)+γ∗N叶∑_{j=1}^{N_{叶}}G_jC_{j}(x)+\frac{1}{2}∑_{j=1}^{N_{叶}}(H_j+λ)C_{j}^2(x)+ γ*N_{叶}j=1NGjCj(x)+21j=1NHj+λCj2(x)+γN的极小值
  • Lj=12(Hj+λ)Cj2(x)+GjCj(x)+γL_j =\frac{1}{2}(H_j+λ)C_{j}^2(x)+ G_jC_{j}(x)+ γLj=21(Hj+λ)Cj2(x)+GjCj(x)+γ相当于一元二次方程y=ax2+bx+cy = ax^2+bx+cy=ax2+bx+c,在x=−b2ax=-\frac{b}{2a}x=2ab处可以取到极值4ac−b24a\frac{4ac-b^2}{4a}4a4acb2
  • 因此当Cj(x)=−GjHj+λC_{j}(x) = -\frac{G_j}{H_j+λ}Cj(x)=Hj+λGj时,可以求到单个叶子节点的损失函数极小值min:Lj=2γ(Hj+λ)−Gj22(Hj+λ)=γ−Gj22(Hj+λ)min:L_j=\frac{2γ(H_j+λ)-G_j^2}{2(H_j+λ)}=γ-\frac{G_j^2}{2(H_j+λ)}min:Lj=2(Hj+λ)2γ(Hj+λ)Gj2=γ2(Hj+λ)Gj2
  • 那么第m次迭代时所有样本的损失函数为,min:Lm(y,fm(x))=∑j=1N叶[γ−Gj22(Hj+λ)]min:L_m(y,f_m(x))=∑_{j=1}^{N_{叶}}[γ-\frac{G_j^2}{2(H_j+λ)}]min:Lm(y,fm(x))=j=1N[γ2(Hj+λ)Gj2]

3. 最后,XGBoost的决策树分裂的特征及特征值,与CART决策树选取标准是不同的

CART决策树是根据基尼系数最小,选取的特征及特征值来分裂树
而XGBoost是可以采用贪心算法,根据特征及特征值分裂后的损失函数增益最大值,来选取的特征及特征值来分裂树

  • 损失函数增益,指的是,每次分裂一个节点时,损失值减小的程度
    • 当前节点的损失值会发生改变,而其他节点的损失值不变。
    • 如果当前节点的损失值比分裂前非常非常小,说明整体的损失值也会变小,增益程度也会更大
    • 如果当前节点的损失值比分裂前差不多,说明整体的损失值没有太大改变,增益程度不大
    • 因此,应该选择损失值增益最大的特征及特征值,作为分裂的节点
      在这里插入图片描述

因此,
Gain=[γ−Gj父22(Hj父+λ)]−[γ−Gj左22(Hj左+λ)]−[γ−Gj右22(Hj右+λ)]Gain =[γ-\frac{G_{j父}^2}{2(H_{j父}+λ)}]-[γ-\frac{G_{j左}^2}{2(H_{j左}+λ)}]-[γ-\frac{G_{j右}^2}{2(H_{j右}+λ)}]Gain=[γ2(Hj+λ)Gj2][γ2(Hj+λ)Gj2][γ2(Hj+λ)Gj2]

=Gj左22(Hj左+λ)+Gj右22(Hj右+λ)−Gj父22(Hj父+λ)−γ=\frac{G_{j左}^2}{2(H_{j左}+λ)}+\frac{G_{j右}^2}{2(H_{j右}+λ)}-\frac{G_{j父}^2}{2(H_{j父}+λ)}-γ=2(Hj+λ)Gj2+2(Hj+λ)Gj22(Hj+λ)Gj2γ

其中Gj父22(Hj父+λ)\frac{G_{j父}^2}{2(H_{j父}+λ)}2(Hj+λ)Gj2

Gj父=∑i∈I(j左+j右)gi=∑i∈I(j左)gi+∑i∈I(j右)gi=Gj左+Gj右G_{j父}=∑_{i∈I(j左+j右)}g_i=∑_{i∈I(j左)}g_i+∑_{i∈I(j右)}g_i = G_{j左}+G_{j右}Gj=iI(j+j)gi=iI(j)gi+iI(j)gi=Gj+Gj
Hj父=∑i∈I(j左+j右)hi=∑i∈I(j左)hi+∑i∈I(j右)hi=Hj左+Hj右H_{j父}=∑_{i∈I(j左+j右)}h_i=∑_{i∈I(j左)}h_i+∑_{i∈I(j右)}h_i = H_{j左}+H_{j右}Hj=iI(j+j)hi=iI(j)hi+iI(j)hi=Hj+Hj

因此,Gj父22(Hj父+λ)=(Gj左+Gj右)22(Hj左+Hj右+λ)\frac{G_{j父}^2}{2(H_{j父}+λ)}=\frac{(G_{j左}+G_{j右})^2}{2(H_{j左}+H_{j右}+λ)}2(Hj+λ)Gj2=2(Hj+Hj+λ)(Gj+Gj)2

所以最终的
Gain=Gj左22(Hj左+λ)+Gj右22(Hj右+λ)−Gj父22(Hj父+λ)−γGain=\frac{G_{j左}^2}{2(H_{j左}+λ)}+\frac{G_{j右}^2}{2(H_{j右}+λ)}-\frac{G_{j父}^2}{2(H_{j父}+λ)}-γGain=2(Hj+λ)Gj2+2(Hj+λ)Gj22(Hj+λ)Gj2γ

=Gj左22(Hj左+λ)+Gj右22(Hj右+λ)−(Gj左+Gj右)22(Hj左+Hj右+λ)−γ=\frac{G_{j左}^2}{2(H_{j左}+λ)}+\frac{G_{j右}^2}{2(H_{j右}+λ)}-\frac{(G_{j左}+G_{j右})^2}{2(H_{j左}+H_{j右}+λ)}-γ=2(Hj+λ)Gj2+2(Hj+λ)Gj22(Hj+Hj+λ)(Gj+Gj)2γ

因此,最终是根据Gain最大的结果,来选取最优的分裂特征及特征值

完美!

程序设计

1. 数据结构:一棵二叉树

  • 每个节点存储的数据:
    • 当前节点的样本残差集
    • 选择分裂的特征及特征值

2. 实现流程:核心步骤

  • 获取当前节点的所有特征及特征值

  • 遍历每个特征及特征值

    • 根据当前特征及特征值分两组
    • 计算G左、G右
      • Gi=∑i∈I(j)əL(yi,fm−1(xi))əfm−1(xi)G_i=∑_{i∈I(j)} \frac{ə_{L(y_i,f_{m-1}(x_i))}}{ə_{f_{m-1}(x_i)}}Gi=iI(j)əfm1(xi)əL(yi,fm1(xi))
      • L(yi,fm−1(xi))=(yi−ypre)2=[yi−fm−1(xi)]2L(y_i,f_{m-1}(x_i))=(y_i-y_{pre})^2=[y_i-f_{m-1}(x_i)]^2L(yi,fm1(xi))=(yiypre)2=[yifm1(xi)]2
      • Gi=∑i∈I(j)əL(yi,fm−1(xi))əfm−1(xi)=∑i∈I(j)[−2(yi−fm−1(xi))]G_i=∑_{i∈I(j)} \frac{ə_{L(y_i,f_{m-1}(x_i))}}{ə_{f_{m-1}(x_i)}}=∑_{i∈I(j)} [-2(y_i-f_{m-1}(x_i))]Gi=iI(j)əfm1(xi)əL(yi,fm1(xi))=iI(j)[2(yifm1(xi))]
    • 计算H左、H右
      • Hi=∑i∈I(j)əL(yi,fm−1(xi))2əfm−1(xi)=∑i∈I(j)[−2(yi−fm−1(xi))]′=∑i∈I(j)2yiH_i=∑_{i∈I(j)} \frac{ə^2_{L(y_i,f_{m-1}(x_i))}}{ə_{f_{m-1}(x_i)}}=∑_{i∈I(j)} [-2(y_i-f_{m-1}(x_i))]'=∑_{i∈I(j)} 2y_iHi=iI(j)əfm1(xi)əL(yi,fm1(xi))2=iI(j)[2(yifm1(xi))]=iI(j)2yi
    • 计算分组后的Gain值,记录最大值及对应的特征、特征值
      • Gain=Gj左22(Hj左+λ)+Gj右22(Hj右+λ)−(Gj左+Gj右)22(Hj左+Hj右+λ)−γGain=\frac{G_{j左}^2}{2(H_{j左}+λ)}+\frac{G_{j右}^2}{2(H_{j右}+λ)}-\frac{(G_{j左}+G_{j右})^2}{2(H_{j左}+H_{j右}+λ)}-γGain=2(Hj+λ)Gj2+2(Hj+λ)Gj22(Hj+Hj+λ)(Gj+Gj)2γ
  • 判断Gain最大值情况下,是否可以分裂左右组

    • 条件:Gain大于0 则可以分裂,否则停止分裂
  • 将最终划分的两个组,设置为左右节点分裂,再分别递归划分

实践遇到的问题

问题1:XGBoost到底是一棵树还是多棵树?

显然是多棵树

问题2:那第一棵树的第一个分裂节点,没有yprey_{pre}ypre怎么计算G值,怎么计算Gain值?
没有Gain值,怎么选择分裂节点?

直击灵魂深处,万事开头难,古人诚不欺我也

所以,为了踏出第一步,需要提前设置一个ypre0y_{pre0}ypre0初始预测值
这里,可以设置为ypre0=average(ytrue)y_{pre0}=average(y_{true})ypre0=average(ytrue),表示第0棵树的所有样本预测值为所有样本真实值的均值,并记录当前预测值f0(x)=ypre0f_0(x)=y_{pre0}f0(x)=ypre0,计算出初始残差值r0r_0r0

  • 1、计算出初始残差值r0r_0r0后,开始建立第一棵树

    • 先分裂节点:
      • ①获取当前节点的所有特征及特征值
      • ②遍历特征及特征值,计算出最大gain
      • ③判断是否可以分裂
      • ④完成分裂,左右树递归
    • 再进行预测:
      • ①预测所有样本的预测值ypre1y_{pre1}ypre1
      • ②计算当前所有树的预测结果f1(x)=f0(x)+ypre1f_{1}(x)=f_0(x)+y_{pre1}f1(x)=f0(x)+ypre1
  • 2、计算出第一棵树的残差值r1=y−f1(x)r_1=y-f_{1}(x)r1=yf1(x)后,开始建立第二棵树

    • 先分裂节点:
      • ①获取当前节点的所有特征及特征值
      • ②遍历特征及特征值,计算出最大gain
      • ③判断是否可以分裂
      • ④完成分裂,左右树递归
    • 再进行预测:
      • ①预测所有样本的预测值ypre2y_{pre2}ypre2
      • ②计算当前所有树的预测结果f2=f1(x)+ypre2f_{2}=f_1(x)+y_{pre2}f2=f1(x)+ypre2
  • 这里要区分fm(x)和ypref_m(x)和y_{pre}fm(x)ypre的定义

    • fm(x)f_m(x)fm(x)是对实际y值拟合的预测值,yprey_{pre}ypre是对上一轮的残差拟合的预测值,T(x)=ypreT(x)=y_{pre}T(x)=ypre

应该是这样的,估计要创建树的多个对象,然后维护一个全局的数据样本残差表,然后依次根据每棵树对象来更新这个样本残差表

最后模型保留的,就是每棵树以及树的结构,树里每个节点都保留分裂的特征及特征值,保留叶子节点的均值

最终代码

最终我只建了10棵树,我还没想好树的数量标准
但10棵树的预测效果,还是不错的,虽然不确定会不会过拟合,但以后有时间再验证

终于可以进入聚类了~~~~~

在这里插入图片描述

import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_rows',None)
# 获取所需数据:'推荐分值', '专业度','回复速度','服务态度','推荐类型'
datas = pd.read_excel('./datas4.xlsx')
important_features = ['专业度','回复速度','服务态度','推荐分值'] #
datas_1 = datas[important_features]
Y_features = '推荐分值'
X_features = X.columns

class Node():
    def __init__(self,datas):
        self.datas = datas
        self.all_feat_and_point = self.get_feat_points(datas)
        self.feat = None
        self.point = None
        self.mean = None
        self.left = None
        self.right = None
    def get_feat_points(self,datas):
        """计算出每个节点的特征及特征值"""
        feats = X_features
        feat_and_point = {}
        for feat in feats:
            feat_and_point[feat]= datas[feat].unique()
        return feat_and_point

class Tree():
    def __init__(self,datas,gama=1):
        self.datas = datas
        self.gama = gama
        self.root = Node(self.datas)
    def devide(self,node=None):
        """选择特征及特征值,进行递归分裂"""
        node.mean = node.datas['r'].mean(axis=0)
        max_gain = None
        for feat,points in node.all_feat_and_point.items():
            for point in points:
                value = self.get_gain(feat,point,node.datas)
                if max_gain==None or value['gain']>max_gain:
                    max_gain = value['gain']
                    temp_feat = feat
                    temp_point = point
                    left_datas = value['left_datas']
                    right_datas = value['right_datas']
        if max_gain<10**-7 or left_datas.empty or right_datas.empty:
            return
        node.feat = temp_feat
        node.point = temp_point
        node.left = Node(left_datas)
        node.right = Node(right_datas)
        self.devide(node.left)
        self.devide(node.right)
    
    def get_gain(self,feat,point,datas):
        """计算Gain值"""
        value = {}
        left_datas = datas[datas[feat]<=point]
        right_datas = datas[datas[feat]>point]
        G_left = 2*left_datas['Fm'].sum(axis=0)-2*left_datas[Y_features].sum(axis=0)
        G_right = 2 * right_datas['Fm'].sum(axis=0) - 2 * right_datas[Y_features].sum(axis=0)
        H_left = 2 * left_datas[Y_features].sum(axis=0)
        H_right = 2 * right_datas[Y_features].sum(axis=0)
        gain = G_left**2/(2*H_left+2*self.gama)+G_right**2/(2*H_right+2*self.gama)+(G_left+G_right)**2/(2*H_left+2*H_right+2*self.gama)
        value['gain'] = gain
        value['left_datas'] = left_datas
        value['right_datas'] = right_datas
        return value
    def get_new_datas(self):
        Y_pre = []
        for index,data in self.datas.iterrows():
            temp = self.find_Y(data,self.root)
            Y_pre.append(temp)
        self.datas['r'] = self.datas['r']-Y_pre
        self.datas['Fm'] = self.datas['Fm']+Y_pre
        return self.datas
    def find_Y(self,data,node=None):
        feat = node.feat
        point = node.point
        if feat == None:
            return node.mean
        if data[feat]<=point:
            return self.find_Y(data,node.left)
        else:
            return self.find_Y(data,node.right)

class XGB():
    def __init__(self,datas,gama=1):
        self.datas = datas
        self.gama = gama
        self.end = 10 # 建多少棵树
        self.trees = []
        self.Fm = datas[Y_features].mean(axis=0)
        self.datas['Fm'] = [self.Fm for i in range(len(self.datas))]
        self.datas['r'] = self.datas[Y_features] - self.Fm
    def learning(self):
        for i in range(self.end):
            tree = Tree(self.datas,self.gama)
            tree.devide(tree.root)
            self.datas = tree.get_new_datas()  # 更新r和fm值
            self.trees.append(tree)
        return self.trees
    def predict(self,datas):
        Y_pre = []
        for index,data in datas.iterrows():
            fm = self.Fm
            for tree in self.trees:
                temp = tree.find_Y(data,tree.root)
                fm += temp # 累计每棵树的fm,作为最终的预测值
            Y_pre.append(fm)
        return Y_pre

tree = XGB(datas_1)
tree.learning()
Y_pre = tree.predict(datas_1)

datas_1['Y_pre'] = Y_pre
datas_1['r_final'] = datas_1[Y_features]-datas_1['Y_pre']
print(datas_1[[Y_features,'Y_pre','r_final']])

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐