GBDT算法原理以及实例理解[通俗易懂]

GBDT算法原理以及实例理解[通俗易懂]【尊重原创,转载请注明出处】http://blog.csdn.net/zpalyq110/article/details/79527653  GBDT的全称是GradientBoostingDecisionTree,梯度下降树,在传统机器学习算法中,GBDT算的上TOP3的算法。想要理解GBDT的真正意义,那就必须理解GBDT中的GradientBoosting和Decision…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

【尊重原创,转载请注明出处】http://blog.csdn.net/zpalyq110/article/details/79527653


写在前面: 去年学习GBDT之初,为了加强对算法的理解,整理了一篇笔记形式的文章,发出去之后发现阅读量越来越多,渐渐也有了评论,评论中大多指出来了笔者理解或者编辑的错误,故重新编辑一版文章,内容更加翔实,并且在GitHub上实现了和本文一致的GBDT简易版(包括回归、二分类、多分类以及可视化),供大家交流探讨。感谢各位的点赞和评论,希望继续指出错误。
  Github:https://github.com/Freemanzxp/GBDT_Simple_Tutorial


简介:

  GBDT 的全称是 Gradient Boosting Decision Tree,梯度提升树,在传统机器学习算法中,GBDT算的上TOP3的算法。想要理解GBDT的真正意义,那就必须理解GBDT中的Gradient Boosting 和Decision Tree分别是什么?

1. Decision Tree:CART回归树

  首先,GBDT使用的决策树是CART回归树,无论是处理回归问题还是二分类以及多分类,GBDT使用的决策树通通都是都是CART回归树。为什么不用CART分类树呢?因为GBDT每次迭代要拟合的是梯度值,是连续值所以要用回归树。
  对于回归树算法来说最重要的是寻找最佳的划分点,那么回归树中的可划分点包含了所有特征的所有可取的值。在分类树中最佳划分点的判别标准是熵或者基尼系数,都是用纯度来衡量的,但是在回归树中的样本标签是连续数值,所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。


回归树生成算法:
输入:训练数据集 D D D:
输出:回归树 f ( x ) f(x) f(x).
在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
(1)选择最优切分变量 j j j与切分点 s s s,求解
m i n j , s [ m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2   ] \underset{j,s}{min}\left [ \underset{c_1}{min}\underset{x_i\in R_1(j,s)}{\sum}(y_i-c_1)^2 + \underset{c_2}{min}\underset{x_i\in R_2(j,s)}{\sum}(y_i-c_2)^2\ \right ] j,sminc1minxiR1(j,s)(yic1)2+c2minxiR2(j,s)(yic2)2 遍历变量 j j j,对固定的切分变量 j j j扫描切分点 s s s,选择使得上式达到最小值的对 ( j , s ) (j,s) (j,s).
(2)用选定的对 ( j , s ) (j,s) (j,s)划分区域并决定相应的输出值: R 1 ( j , s ) = x ∣ x ( j ) ≤ s , R 2 ( j , s ) = x ∣ x ( j ) > s R_1(j,s)={x|x^{(j)}\leq s}, R_2(j,s)={x|x^{(j)}> s} R1(j,s)=xx(j)s,R2(j,s)=xx(j)>s c m ^ = 1 N ∑ x 1 ∈ R m ( j , s ) y i , x ∈ R m , m = 1 , 2 \hat{c_m} =\frac{1}{N}\underset{x_1\in R_m(j,s)}{\sum}y_i, x \in R_m, m=1,2 cm^=N1x1Rm(j,s)yi,xRm,m=1,2(3)继续对两个子区域调用步骤(1)和(2),直至满足停止条件。
(4)将输入空间划分为 M M M个区域 R 1 , R 2 , . . . R M R_1,R_2,…R_M R1,R2,...RM,生成决策树:
f ( x ) = ∑ m = 1 M c ^ m I ( x ∈ R m ) f(x)=\sum_{m=1}^{M}\hat{c}_m I(x \in R_m) f(x)=m=1Mc^mI(xRm)


2. Gradient Boosting: 拟合负梯度

  梯度提升树(Grandient Boosting)是提升树(Boosting Tree)的一种改进算法,所以在讲梯度提升树之前先来说一下提升树。


  先来个通俗理解:假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。最后将每次拟合的岁数加起来便是模型输出的结果。


提升树算法:
(1)初始化 f 0 ( x ) = 0 f_0(x)=0 f0(x)=0
(2)对 m = 1 , 2 , . . . , M m=1,2,…,M m=1,2,...,M
 (a)计算残差 r m i = y i − f m − 1 ( x ) , i = 1 , 2 , . . . , N r_{mi}=y_i-f_{m-1}(x), i=1,2,…,N rmi=yifm1(x),i=1,2,...,N (b)拟合残差 r m i r_{mi} rmi学习一个回归树,得到 h m ( x ) h_m(x) hm(x)
 (c)更新 f m ( x ) = f m − 1 + h m ( x ) f_m(x) = f_{m-1}+h_m(x) fm(x)=fm1+hm(x)
(3)得到回归问题提升树 f M ( x ) = ∑ m = 1 M h m ( x ) f_M(x)=\sum_{m=1}^Mh_m(x) fM(x)=m=1Mhm(x)


上面伪代码中的残差是什么?
在提升树算法中,假设我们前一轮迭代得到的强学习器是 f t − 1 ( x ) f_{t−1}(x) ft1(x)损失函数是 L ( y , f t − 1 ( x ) ) L(y,f_{t−1}(x)) L(y,ft1(x))我们本轮迭代的目标是找到一个弱学习器 h t ( x ) h_t(x) ht(x)最小化让本轮的损失 L ( y , f t ( x ) ) = L ( y , f t − 1 ( x ) + h t ( x ) ) L(y,f_t(x))=L(y,f_{t−1}(x)+h_t(x)) L(y,ft(x))=L(y,ft1(x)+ht(x))当采用平方损失函数时 L ( y , f t − 1 ( x ) + h t ( x ) ) L(y,f_{t−1}(x)+h_t(x)) L(y,ft1(x)+ht(x)) = ( y − f t − 1 ( x ) − h t ( x ) ) 2 =(y-f_{t−1}(x)-h_t(x))^2 =(yft1(x)ht(x))2 = ( r − h t ( x ) ) 2 =(r-h_t(x))^2 =(rht(x))2这里, r = y − f t − 1 ( x ) r=y-f_{t−1}(x) r=yft1(x)是当前模型拟合数据的残差(residual)所以,对于提升树来说只需要简单地拟合当前模型的残差。
  回到我们上面讲的那个通俗易懂的例子中,第一次迭代的残差是10岁,第二 次残差4岁……


  当损失函数是平方损失和指数损失函数时,梯度提升树每一步优化是很简单的,但是对于一般损失函数而言,往往每一步优化起来不那么容易,针对这一问题,Friedman提出了梯度提升树算法,这是利用最速下降的近似方法,其关键是利用损失函数的负梯度作为提升树算法中的残差的近似值。
那么负梯度长什么样呢?
第t轮的第i个样本的损失函数的负梯度为: − [ ∂ L ( y , f ( x i ) ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x )      -\bigg[\frac{\partial L(y, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}(x)\;\;} [f(xi)L(y,f(xi)))]f(x)=ft1(x)此时不同的损失函数将会得到不同的负梯度,如果选择平方损失 L ( y , f ( x i ) ) = 1 2 ( y − f ( x i ) ) 2 L(y,f(x_i)) = \frac{1}{2}(y-f(x_i))^2 L(y,f(xi))=21(yf(xi))2负梯度为 − [ ∂ L ( y , f ( x i ) ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x )      = y − f ( x i ) -\bigg[\frac{\partial L(y, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{t-1}(x)\;\;}=y-f(x_i) [f(xi)L(y,f(xi)))]f(x)=ft1(x)=yf(xi)  此时我们发现GBDT的负梯度就是残差,所以说对于回归问题,我们要拟合的就是残差。
  那么对于分类问题呢?二分类和多分类的损失函数都是 l o g l o s s log loss logloss本文以回归问题为例进行讲解


3. GBDT算法原理

  上面两节分别将Decision Tree和Gradient Boosting介绍完了,下面将这两部分组合在一起就是我们的GBDT了。


GBDT算法:
(1)初始化弱学习器
f 0 ( x ) = a r g    m i n c ∑ i = 1 N L ( y i , c ) f_0(x) = {arg\;min}_{c}\sum\limits_{i=1}^{N}L(y_i, c) f0(x)=argminci=1NL(yi,c)(2)对 m = 1 , 2 , . . . , M m=1,2,…,M m=1,2,...,M有:
 (a)对每个样本 i = 1 , 2 , . . . , N i=1,2,…,N i=1,2,...,N,计算负梯度,即残差
r i m = − [ ∂ L ( y i , f ( x i ) ) ) ∂ f ( x i ) ] f ( x ) = f m − 1 ( x ) r_{im} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{m-1} (x)} rim=[f(xi)L(yi,f(xi)))]f(x)=fm1(x)
 (b)将上步得到的残差作为样本新的真实值,并将数据 ( x i , r i m ) , i = 1 , 2 , . . N (x_i,r_{im}), i=1,2,..N (xi,rim)i=1,2,..N作为下棵树的训练数据,得到一颗新的回归树 f m ( x ) f_{m} (x) fm(x)其对应的叶子节点区域为 R j m , j = 1 , 2 , . . . , J R_{jm}, j =1,2,…, J Rjm,j=1,2,...,J。其中 J J J为回归树t的叶子节点的个数。
 (c)对叶子区域 j = 1 , 2 , . . J j =1,2,..J j=1,2,..J计算最佳拟合值
Υ j m = a r g    m i n ⏟ Υ ∑ x i ∈ R j m L ( y i , f m − 1 ( x i ) + Υ ) \Upsilon_{jm} = \underbrace{arg\; min}_{\Upsilon}\sum\limits_{x_i \in R_{jm}} L(y_i,f_{m-1}(x_i) +\Upsilon) Υjm=Υ


argmin
xiRjmL(yi,fm1(xi)+
Υ)
 (d)更新强学习器
f m ( x ) = f m − 1 ( x ) + ∑ j = 1 J Υ j m I ( x ∈ R j m ) f_{m}(x) = f_{m-1}(x) + \sum\limits_{j=1}^{J}\Upsilon_{jm}I(x \in R_{jm}) fm(x)=fm1(x)+j=1JΥjmI(xRjm)(3)得到最终学习器
f ( x ) = f M ( x ) = f 0 ( x ) + ∑ m = 1 M ∑ j = 1 J Υ j m I ( x ∈ R j m ) f(x) = f_M(x) =f_0(x) + \sum\limits_{m=1}^{M}\sum\limits_{j=1}^{J}\Upsilon_{jm}I(x \in R_{jm}) f(x)=fM(x)=f0(x)+m=1Mj=1JΥjmI(xRjm)


4. 实例详解

本人用python以及pandas库实现GBDT的简易版本,在下面的例子中用到的数据都在github可以找到,大家可以结合代码和下面的例子进行理解,欢迎star~
  Github:https://github.com/Freemanzxp/GBDT_Simple_Tutorial


数据介绍:

  如下表所示:一组数据,特征为年龄、体重,身高为标签值。共有5条数据,前四条为训练样本,最后一条为要预测的样本。

编号 年龄(岁) 体重(kg) 身高(m)(标签值)
0 5 20 1.1
1 7 30 1.3
2 21 70 1.7
3 30 60 1.8
4(要预测的) 25 65

训练阶段:


参数设置:

  • 学习率:learning_rate=0.1
  • 迭代次数:n_trees=5
  • 树的深度:max_depth=3

1.初始化弱学习器: f 0 ( x ) = a r g    m i n c ∑ i = 1 N L ( y i , c ) f_0(x) = {arg\;min}_{c}\sum\limits_{i=1}^{N}L(y_i, c) f0(x)=argminci=1NL(yi,c)  损失函数为平方损失,因为平方损失函数是一个凸函数,直接求导,倒数等于零,得到 c c c
∑ i = 1 N ∂ L ( y i , c ) ) ∂ c = ∑ i = 1 N ∂ ( 1 2 ( y i − c ) 2 ) ∂ c = ∑ i = 1 N c − y i \sum\limits_{i=1}^{N}\frac{\partial L(y_i,c))}{\partial c}=\sum\limits_{i=1}^{N}\frac{\partial (\frac{1}{2}(y_i-c)^2)}{\partial c}=\sum\limits_{i=1}^{N}c-y_i i=1NcL(yi,c))=i=1Nc(21(yic)2)=i=1Ncyi令导数等于0
∑ i = 1 N c − y i = 0 \sum\limits_{i=1}^{N}c-y_i=0 i=1Ncyi=0 c = ( ∑ i = 1 N y i ) / N c=(\sum\limits_{i=1}^{N}y_i)/N c=(i=1Nyi)/N所以初始化时, c c c取值为所有训练样本标签值的均值。 c = ( 1.1 + 1.3 + 1.7 + 1.8 ) / 4 = 1.475 c=(1.1+1.3+1.7+1.8)/4=1.475 c=(1.1+1.3+1.7+1.8)/4=1.475,此时得到初始学习器 f 0 ( x ) f_0(x) f0(x)
f 0 ( x ) = c = 1.475 f_0(x) = c=1.475 f0(x)=c=1.475


2.对迭代轮数m=1,2,…,M:
  由于我们设置了迭代次数:n_trees=5,这里的 M = 5 M=5 M=5
  计算负梯度,根据上文损失函数为平方损失时,负梯度就是残差残差,再直白一点就是 y y y与上一轮得到的学习器 f m − 1 f_{m-1} fm1的差值
   r i 1 = − [ ∂ L ( y i , f ( x i ) ) ) ∂ f ( x i ) ] f ( x ) = f 0 ( x ) r_{i1} = -\bigg[\frac{\partial L(y_i, f(x_i)))}{\partial f(x_i)}\bigg]_{f(x) = f_{0} (x)} ri1=[f(xi)L(yi,f(xi)))]f(x)=f0(x)
残差在下表列出:

编号 真实值 f 0 ( x ) f_{0} (x) f0(x) 残差
0 1.1 1.475 -0.375
1 1.3 1.475 -0.175
2 1.7 1.475 0.225
3 1.8 1.475 0.325

此时将残差作为样本的真实值来训练弱学习器 f 1 ( x ) f_{1} (x) f1(x),即下表数据

编号 年龄(岁) 体重(kg) 标签值
0 5 20 -0.375
1 7 30 -0.175
2 21 70 0.225
3 30 60 0.325

  接着,寻找回归树的最佳划分节点,遍历每个特征的每个可能取值。从年龄特征的5开始,到体重特征的70结束,分别计算分裂后两组数据的平方损失(Square Error), S E l SE_l SEl左节点平方损失, S E r SE_r SEr右节点平方损失,找到使平方损失和 S E s u m = S E l + S E r SE_{sum}=SE_l+SE_r SEsum=SEl+SEr最小的那个划分节点,即为最佳划分节点。
  例如:以年龄7为划分节点,将小于7的样本划分为到左节点,大于等于7的样本划分为右节点。左节点包括 x 0 x_0 x0,右节点包括样本 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3 S E l = 0 , S E r = 0.140 , S E s u m = 0.140 SE_l = 0,SE_r=0.140,SE_{sum}=0.140 SEl=0,SEr=0.140,SEsum=0.140,所有可能划分情况如下表所示:

划分点 小于划分点的样本 大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
年龄5 / 0,1,2,3 0 0.327 0.327
年龄7 0 1,2,3 0 0.140 0.140
年龄21 0,1 2,3 0.020 0.005 0.025
年龄30 0,1,2 3 0.187 0 0.187
体重20 / 0,1,2,3 0 0.327 0.327
体重30 0 1,2,3 0 0.140 0.140
体重60 0,1 2,3 0.020 0.005 0.025
体重70 0,1,3 2 0.260 0 0.260

  以上划分点是的总平方损失最小为0.025有两个划分点:年龄21和体重60,所以随机选一个作为划分点,这里我们选 年龄21
  现在我们的第一棵树长这个样子:
在这里插入图片描述
  我们设置的参数中树的深度max_depth=3,现在树的深度只有2,需要再进行一次划分,这次划分要对左右两个节点分别进行划分:


  对于左节点,只含有0,1两个样本,根据下表我们选择年龄7划分

划分点 小于划分点的样本 大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
年龄5 / 0,1 0 0.020 0.020
年龄7 0 1 0 0 0
体重20 / 0,1 0 0.020 0.020
体重30 0 1 0 0 0

  对于右节点,只含有2,3两个样本,根据下表我们选择年龄30划分(也可以选体重70

划分点 小于划分点的样本 大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
年龄21 / 2,3 0 0.005 0.005
年龄30 2 3 0 0 0
体重60 / 2,3 0 0.005 0.005
体重70 3 2 0 0 0

  现在我们的第一棵树长这个样子:
在这里插入图片描述
  此时我们的树深度满足了设置,还需要做一件事情,给这每个叶子节点分别赋一个参数 Υ \Upsilon Υ,来拟合残差。 Υ j 1 = a r g    m i n ⏟ Υ ∑ x i ∈ R j 1 L ( y i , f 0 ( x i ) + Υ ) \Upsilon_{j1} = \underbrace{arg\; min}_{\Upsilon}\sum\limits_{x_i \in R_{j1}} L(y_i,f_{0}(x_i) +\Upsilon) Υj1=Υ


argmin
xiRj1L(yi,f0(xi)+
Υ)
  这里其实和上面初始化学习器是一个道理,平方损失,求导,令导数等于零,化简之后得到每个叶子节点的参数 Υ \Upsilon Υ,其实就是标签值的均值。这个地方的标签值不是原始的 y y y,而是本轮要拟合的标残差 y − f 0 ( x ) y-f_0(x) yf0(x).
  根据上述划分结果,为了方便表示,规定从左到右为第 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4个叶子结点 ( x 0 ∈ R 11 ) , Υ 11 = − 0.375 (x_0 \in R_{11}),\Upsilon_{11}=-0.375 (x0R11)Υ11=0.375 ( x 1 ∈ R 21 ) , Υ 21 = − 0.175 (x_1 \in R_{21}),\Upsilon_{21}=-0.175 (x1R21)Υ21=0.175 ( x 2 ∈ R 31 ) , Υ 31 = 0.225 (x_2 \in R_{31}),\Upsilon_{31}=0.225 (x2R31)Υ31=0.225 ( x 3 ∈ R 41 ) , Υ 41 = 0.325 (x_3 \in R_{41}),\Upsilon_{41}=0.325 (x3R41)Υ41=0.325
  此时的树长这个样子:
在这里插入图片描述


  此时可更新强学习器,需要用到参数学习率:learning_rate=0.1,用 l r lr lr表示。
f 1 ( x ) = f 0 ( x ) + l r ∗ ∑ j = 1 4 Υ j 1 I ( x ∈ R j 1 ) f_{1}(x) = f_{0}(x) + lr *\sum\limits_{j=1}^{4}\Upsilon_{j1}I(x \in R_{j1}) f1(x)=f0(x)+lrj=14Υj1I(xRj1)
为什么要用学习率呢?这是Shrinkage的思想,如果每次都全部加上(学习率为1)很容易一步学到位导致过拟合。


重复此步骤,直到 m > 5 m>5 m>5 结束,最后生成5棵树。
下面将展示每棵树最终的结构,这些图都是GitHub上的代码生成的,感兴趣的同学可以去一探究竟
Github:https://github.com/Freemanzxp/GBDT_Simple_Tutorial
第一棵树:
在这里插入图片描述
第二棵树:
在这里插入图片描述
第三棵树:
在这里插入图片描述
第四棵树:
在这里插入图片描述
第五棵树:
在这里插入图片描述


4.得到最后的强学习器:
f ( x ) = f 5 ( x ) = f 0 ( x ) + ∑ m = 1 5 ∑ j = 1 4 Υ j m I ( x ∈ R j m ) f(x) = f_{5}(x) =f_0(x) + \sum\limits_{m=1}^{5}\sum\limits_{j=1}^{4}\Upsilon_{jm}I(x \in R_{jm}) f(x)=f5(x)=f0(x)+m=15j=14ΥjmI(xRjm)


5.预测样本5:
   f 0 ( x ) = 1.475 f_0(x)=1.475 f0(x)=1.475
  在 f 1 ( x ) f_1(x) f1(x)中,样本4的年龄为25,大于划分节点21岁,又小于30岁,所以被预测为0.2250
  在 f 2 ( x ) f_2(x) f2(x)中,样本4的…此处省略…所以被预测为0.2025
   为什么是0.2025?这是根据第二颗树得到的,可以GitHub简单运行一下代码
  在 f 3 ( x ) f_3(x) f3(x)中,样本4的…此处省略…所以被预测为0.1823
  在 f 4 ( x ) f_4(x) f4(x)中,样本4的…此处省略…所以被预测为0.1640
  在 f 5 ( x ) f_5(x) f5(x)中,样本4的…此处省略…所以被预测为0.1476
最终预测结果: f ( x ) = 1.475 + 0.1 ∗ ( 0.225 + 0.2025 + 0.1823 + 0.164 + 0.1476 ) = 1.56714 f(x) =1.475+ 0.1 * (0.225+0.2025+0.1823+0.164+0.1476) = 1.56714 f(x)=1.475+0.1(0.225+0.2025+0.1823+0.164+0.1476)=1.56714


4. 总结

本文章从GBDT算法的原理到实例详解进行了详细描述,但是目前只写了回归问题,GitHub上的代码也是实现了回归、二分类、多分类以及树的可视化,希望大家继续批评指正,感谢各位的关注。


参考资料

  1. 李航 《统计学习方法》
  2. Friedman J H . Greedy Function Approximation: A Gradient Boosting Machine[J]. The Annals of Statistics, 2001, 29(5):1189-1232.

【尊重原创,转载请注明出处】 http://blog.csdn.net/zpalyq110/article/details/79527653

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182628.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • vue解决跨域的几种办法_前端跨域解决方案

    vue解决跨域的几种办法_前端跨域解决方案什么是跨域  跨域指浏览器不允许当前页面的所在的源去请求另一个源的数据。源指协议,端口,域名。只要这个3个中有一个不同就是跨域。这里列举一个经典的列子:#协议跨域http://a.baidu.com访问https://a.baidu.com;#端口跨域http://a.baidu.com:8080访问http://a.baidu.com:80;#域名跨域http://a.baidu.com访问http://b.baidu.com;  现在很多公司都是采用前后分离的方式开发。那么出

  • spring jar包 以及 jdbcTemplate 相关jar包下载[通俗易懂]

    spring jar包 以及 jdbcTemplate 相关jar包下载[通俗易懂]下面是阿帕奇官网下载spring相关的jar包链接:http://commons.apache.org/proper/commons-logging/download_logging.cgi如果你是要找jdbcTemplate相关jar包,下面是网盘分享。若是文件里没有你想要的jar包,可以点击上面的链接找到你想要的jar包。链接:https://pan.baidu.com/s/1…

  • SpringBoot自动装配原理(简单易懂)

    SpringBoot自动装配原理(简单易懂)1、什么是自动装配自动装配就是把别人(官方)写好的config配置类加载到spring容器,然后根据这个配置类生成一些项目需要的bean对象。(小声逼逼:就像我们自己在项目了写的config配置类一样的,只不过这个是别人写好的,你什么都不用管)2、自动装配的开关在哪里@SpringBootApplication|–@EnableAutoConfiguration|–@Import({AutoConfigurationImportSelector.class})在@Spri

  • OnTouch关于performClick的Warning

    OnTouch关于performClick的WarningOnTouch关于performClick的Warning当你对一个控件(例如FloatingActionButton)使用setOnTouchListener()或者是对你的自定义控件重写onTouchEvent方法时会出现这个警告,警告内容全文如下IfaViewthatoverridesonTouchEventorusesanOnTouchListenerd…

  • JavaSE综合项目演练

    JavaSE综合项目演练光阴似箭日月如梭,大家学习已经有了一段时间了,转眼间,从刚开始如何配置JDK已经到了现在快学完网络编程了。学了这么多,眼看就要进入下一个阶段了,数据库编程了,那么在进入下个阶段前,我们来完成一个综合性比较强的结业项目,告别JavaSE阶段,学完JavaSE,大家已经对编程这块相信已经有了一个很深的理解,但是仅仅是JavaSE还是不够的,我们还需要学习更多的,更全面知识才足以在接下来的生活中过五关斩…

  • 百度之星资格赛,hdu 4825 XOR SUM

    百度之星资格赛,hdu 4825 XOR SUM

    2021年11月23日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号