文章目录
BDT(Bootstrap Decision Tree)简介
问题
在高能物理中,BDT用于对事例的分类,将信号和本底分开。
对于简单例子,比如有一个信号,和一种本底,目前把BDT想象成一个函数,它的输入是事例的一些特征(例如: 质心能量s)[(N,)形状变量],输出是一个实数[(1,)形状变量],对于给定输入,输出是确定的。
其实这个问题就转化成
构建一个映射Y:X->R,使得预测结果最优
神经网络也是相同的问题,BDT只是不同的范式,但是它的模型适合于分类问题。
解决方法
BDT(Bootstrap Decision Tree)是一种集成学习方法,它通过构建多个决策树,并通过投票机制来决定最终的预测结果。
BDT的基本思想是通过构建多个决策树,并通过投票机制来决定最终的预测结果。具体来说,BDT的过程如下:
- 首先,对训练数据集进行采样,得到多个子集。
- 然后,对每个子集,构建一棵决策树。
- 最后,对每个子树的预测结果进行投票,得出最终的预测结果。
BDT的优点是可以有效地克服单一决策树的偏差,并通过投票机制来集成多个决策树的预测结果,从而提高预测精度。
简单来说,最终模型对应的函数 F ( x ) = ∑ i = 1 n α i φ i ( x ) F(x)=\sum_{i=1}^n\alpha_i\varphi_i(x) F(x)=∑i=1nαiφi(x),其中 φ i ( x ) \varphi_i(x) φi(x)是第 i i i个决策树的预测函数, α i \alpha_i αi是第 i i i个决策树的权重。
算法
https://root.cern/manual/tmva/
https://blog.csdn.net/weixin_45666566/article/details/107942444
https://www.cnblogs.com/LeftNotEasy/archive/2011/03/07/random-forest-and-gbdt.html
总结:
- BDT的基学习器用CART决策树,CART是一种二叉树,给定Loss寻求分类参数最优,训练问题为最优化问题.
- 将多个基学习器和到一起的方法,决定使用:(回归)平方损失函数下的拟合残差,(分类)更新数据权重.
参考
https://www.cnblogs.com/kukri/p/8458236.html
https://blog.csdn.net/zhaodedong/article/details/103590540
https://www.cnblogs.com/keye/p/10564914.html
https://blog.csdn.net/m2xgo/article/details/109457705
BDT
https://blog.csdn.net/weixin_45666566/article/details/107942444
GBDT
https://www.cnblogs.com/LeftNotEasy/archive/2011/03/07/random-forest-and-gbdt.html
CART(Classification and Regression Tree)python实现
代码仅供参考
判别标准
Gini系数:
G i n i ( D ) = 1 − s u m ( p k ∗ ∗ 2 ) Gini(D)=1-sum(pk**2) Gini(D)=1−sum(pk∗∗2)
def Gini(self,标签,权重,select:numpy.ndarray=None) -> float:
'''
计算Gini系数
Gini(D)=1-sum(pk**2)
Gini(D,A)=D1/D*Gini(D1)+D2/D*Gini(D2)
#仅适用与离散标签!!!
输入
标签,D:数据
select:划分bool
'''
CN=numpy.sum(权重)
if type(select)==type(None):
G=1.0
for i,label in enumerate(set(标签)):
Ci=numpy.sum(权重[标签==label])
G-=(float(Ci)/CN)**2
else:
CI=numpy.sum(权重[select])
G=self.Gini(标签[select],权重[select])*CI/CN\
+self.Gini(标签[~select],权重[~select])*(CN-CI)/CN
return G
方差:
V a r ( D ) = 1 N − 1 ∑ i = 1 N ( y i − y ˉ ) 2 Var(D)=\frac{1}{N-1}\sum_{i=1}^N(y_i-\bar{y})^2 Var(D)=N−11i=1∑N(yi−yˉ)2
def 平方误差(self,标签,权重,select:numpy.ndarray=None) -> float:
'''
计算平方误差
平方误差(D)=sum (y-y.mean())**2
平方误差(D,A)=平方误差(D1)+平方误差(D2)
#可以支持多维输出[[y1,y2],evtoutarr2,...]
这部分加权做法:以如果0.1,那么平方差贡献为0.1,
输入
标签,D:数据
A:划分bool
'''
CN=numpy.sum(权重)
if type(select)==type(None):
mean标签=标签*权重/sum(权重)
G=sum((标签-mean标签)**2*权重)
else:
G=self.平方误差(标签[select],权重[select])+self.平方误差(标签[~select],权重[~select])
return G
主体构建Tree过程
采用递归的方式构建树,每个Tree被分为左子,右子.
值的注意的是求最优分别参数过程为暴力搜索,输入已经被画为一个个小格子(setA
)(不能比这个区域更小了,否则很有可能过拟合)
def 分类回归树(self,setA,标签,D,权重,树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'):
'''
回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
setA:划分的步长,按分bin来
(binN,xmin,xmax) (区间划分点数N-1)
[(50,0,1,0.01),(2,-0.1,1.1),...]
最小样本数:划分阶段实现
最小不纯度:###还在施工###
损失函数:str|function,'平方':平方差,'Gini',Gini函数
其他可定义,格式为 float=fun(标签,权重,select:numpy.ndarray=None)
当有select表示划分后Loss
输出:str|function,'平均':标签平均,'众数':最大标签数(离散输出)
其他可定义,格式为 number=fun(标签,权重)
输出
Tree: ([左子,右子],besti,bestj,xj,output)
output:[[-1,sumevent],[标签i,counti],...]
'''
assert len(setA)==self.维数
CN=len(标签)
if 损失函数=='平方':
损失函数=self.平方误差
elif 损失函数=='Gini':
损失函数=self.Gini
else:
print('使用用户函数!')
if 输出=='平均':
输出值=sum(标签*权重)/sum(权重)
elif 输出=='众数':
最大计数=0.0
for i,label in enumerate(set(标签)):
CI=sum(权重[标签==label])
if CI>最大计数:
输出值=label
最大计数=CI
else:
输出值=输出(标签,权重)
print()
theGini=损失函数(标签,权重)
Ginimin=numpy.inf
besti=-1;bestj=(-1,None)
for i,fAi in enumerate(setA):#最优划分变量
for j,xj in enumerate(numpy.linspace(fAi[1],fAi[2],fAi[0]+1)[1:-1]):#最优划分区间
fun=lambda x:x[:,i]<xj
select=fun(D)
CI=numpy.sum(select);
if CI< 最小样本数 or CN-CI < 最小样本数:continue
newGini=损失函数(标签,权重,select)
if newGini<Ginimin:
print('newmin',newGini)
Ginimin=newGini
besti=i
bestj=(j,xj)
bestselect=select.copy()
if 树的最大深度>1 and besti>=0:
setA左=deepcopy(setA)
setA左[besti][0]=bestj[0]+1
setA左[besti][1]=setA[besti][1]
setA左[besti][2]=bestj[1]
setA右=deepcopy(setA)
setA右[besti][0]=setA[besti][0]-bestj[0]-1
setA右[besti][1]=bestj[1]
setA右[besti][2]=setA[besti][2]
左子=self.分类回归树(setA左,标签[bestselect],D[bestselect],权重[bestselect],树的最大深度=树的最大深度-1,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
右子=self.分类回归树(setA右,标签[~bestselect],D[~bestselect],权重[~bestselect],树的最大深度=树的最大深度-1,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
return [[左子,右子],theGini,Ginimin,besti,bestj[0],bestj[1],输出值]
else:
return [[],theGini,Ginimin,besti,bestj[0],bestj[1],输出值]
剪枝
def 剪枝(self,T0):
'''
给Tree剪枝
输出:
cut:
'''
galist=self.获得树剪枝阈值序列(T0,'')
位置映射={}
ga=numpy.zeros(len(self.Tree节点分析信息ga))
for i,x in enumerate(self.Tree节点分析信息ga):
ga[i]=self.Tree节点分析信息ga[x]
位置映射[i]=x
sortindex=numpy.argsort(ga)
cut=[]
gainv=[]
for i in range(len(ga)):
x=位置映射[sortindex[i]]
if not self.位置匹配(cut,x) and len(x)>=self.树的最小深度:
gainv.append(ga[sortindex[i]])
cut.append(x)
完整训练函数
def 训练(self,setA,标签,D,权重,验标签,验D,验权重,树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'):
'''
完整流程
'''
Tree=self.分类回归树(setA,标签,D,权重,
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
cut,gainv=self.剪枝(Tree)
#cut=[]##################################3剪枝策略有问题
#问题1剪枝条件中err判断方法
Treelist=[Tree]
a0,output0=self.计算准确率(Treelist[-1],验标签,验D,权重=验权重)
outputall=[output0]
alist=[a0]
for i,dcut in enumerate(cut):
Treelist.append(self.copytree(Treelist[-1],cut=dcut))
ai,output=self.计算准确率(Treelist[-1],验标签,验D,权重=验权重)#此处用验证集
alist.append(ai)
outputall.append(output)
if alist[-1]>alist[-2]:break
bestindex=numpy.argmin(alist)
self.Tree=Treelist[bestindex]
return Treelist[bestindex],alist[bestindex],outputall[bestindex]
输出函数
def 叶子输出_批量(self,T,D):
'''
输出分类树的输出
'''
output=numpy.zeros(len(D))
for i in range(len(D)):
output[i]=self.叶子输出(T,D[i])
return output
完整代码
'''
CART树的一些算法
定义数据结构
标签:[0,0,0,1,...]:int(分类) [[1,2,...],outarr,...]float(回归)
D:array([EVt1,evt2,...])
Evi:[变量1,变量2,...]
'''
import numpy
def deepcopy(A:list):
'''
深度复制
'''
A0=[]
for x in A:
if type(x)==type([]):
A0.append(deepcopy(x))
else:
A0.append(x)
return A0
class CART模型:
'''
CART分类树
'''
def __init__(self,标签,Data,树的最小深度=5) -> None:
assert len(标签)==len(Data),'标签应该和数据个数一致!'
self.数据个数=len(标签)
self.维数=len(Data[0])
self.标签=numpy.array(标签)
self.类数=len(set(标签))
assert self.类数<=9,'目前位置编码方式不支持这个'
self.Data=numpy.array(Data)
self.Tree节点分析信息={}#节点位置[]表示root,[0]表示root/左子
self.Tree节点分析信息ga={}#节点位置[]表示root,[0]表示root/左子
self.树的最小深度=树的最小深度
#一些损失函数
def Gini(self,标签,权重,select:numpy.ndarray=None) -> float:
'''
计算Gini系数
Gini(D)=1-sum(pk**2)
Gini(D,A)=D1/D*Gini(D1)+D2/D*Gini(D2)
#仅适用与离散标签!!!
输入
标签,D:数据
select:划分bool
'''
CN=numpy.sum(权重)
if type(select)==type(None):
G=1.0
for i,label in enumerate(set(标签)):
Ci=numpy.sum(权重[标签==label])
G-=(float(Ci)/CN)**2
else:
CI=numpy.sum(权重[select])
G=self.Gini(标签[select],权重[select])*CI/CN\
+self.Gini(标签[~select],权重[~select])*(CN-CI)/CN
return G
def 平方误差(self,标签,权重,select:numpy.ndarray=None) -> float:
'''
计算平方误差
平方误差(D)=sum (y-y.mean())**2
平方误差(D,A)=平方误差(D1)+平方误差(D2)
#可以支持多维输出[[y1,y2],evtoutarr2,...]
这部分加权做法:以如果0.1,那么平方差贡献为0.1,
输入
标签,D:数据
A:划分bool
'''
CN=numpy.sum(权重)
if type(select)==type(None):
mean标签=标签*权重/sum(权重)
G=sum((标签-mean标签)**2*权重)
else:
G=self.平方误差(标签[select],权重[select])+self.平方误差(标签[~select],权重[~select])
return G
def 分类回归树(self,setA,标签,D,权重,树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'):
'''
回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
setA:划分的步长,按分bin来
(binN,xmin,xmax) (区间划分点数N-1)
[(50,0,1,0.01),(2,-0.1,1.1),...]
最小样本数:划分阶段实现
最小不纯度:###还在施工###
损失函数:str|function,'平方':平方差,'Gini',Gini函数
其他可定义,格式为 float=fun(标签,权重,select:numpy.ndarray=None)
当有select表示划分后Loss
输出:str|function,'平均':标签平均,'众数':最大标签数(离散输出)
其他可定义,格式为 number=fun(标签,权重)
输出
Tree: ([左子,右子],besti,bestj,xj,output)
output:[[-1,sumevent],[标签i,counti],...]
'''
assert len(setA)==self.维数
CN=len(标签)
if 损失函数=='平方':
损失函数=self.平方误差
elif 损失函数=='Gini':
损失函数=self.Gini
else:
print('使用用户函数!')
if 输出=='平均':
输出值=sum(标签*权重)/sum(权重)
elif 输出=='众数':
最大计数=0.0
for i,label in enumerate(set(标签)):
CI=sum(权重[标签==label])
if CI>最大计数:
输出值=label
最大计数=CI
else:
输出值=输出(标签,权重)
print()
theGini=损失函数(标签,权重)
Ginimin=numpy.inf
besti=-1;bestj=(-1,None)
for i,fAi in enumerate(setA):#最优划分变量
for j,xj in enumerate(numpy.linspace(fAi[1],fAi[2],fAi[0]+1)[1:-1]):#最优划分区间
fun=lambda x:x[:,i]<xj
select=fun(D)
CI=numpy.sum(select);
if CI< 最小样本数 or CN-CI < 最小样本数:continue
newGini=损失函数(标签,权重,select)
if newGini<Ginimin:
print('newmin',newGini)
Ginimin=newGini
besti=i
bestj=(j,xj)
bestselect=select.copy()
if 树的最大深度>1 and besti>=0:
setA左=deepcopy(setA)
setA左[besti][0]=bestj[0]+1
setA左[besti][1]=setA[besti][1]
setA左[besti][2]=bestj[1]
setA右=deepcopy(setA)
setA右[besti][0]=setA[besti][0]-bestj[0]-1
setA右[besti][1]=bestj[1]
setA右[besti][2]=setA[besti][2]
左子=self.分类回归树(setA左,标签[bestselect],D[bestselect],权重[bestselect],树的最大深度=树的最大深度-1,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
右子=self.分类回归树(setA右,标签[~bestselect],D[~bestselect],权重[~bestselect],树的最大深度=树的最大深度-1,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
return [[左子,右子],theGini,Ginimin,besti,bestj[0],bestj[1],输出值]
else:
return [[],theGini,Ginimin,besti,bestj[0],bestj[1],输出值]
def 获取叶节点个数(self,T,节点位置:str):
'''
节点位置:'0101'#表示从根开始移动到这里
#当子数大于10会出问题!#################################
'''
if 节点位置 in self.Tree节点分析信息:return self.Tree节点分析信息[节点位置]
if len(T[0])==0:
self.Tree节点分析信息[节点位置]=1
return 1
k=0
for i in range(len(T[0])):
know=self.获取叶节点个数(T[0][i],节点位置+str(i))
k+=know
self.Tree节点分析信息[节点位置]=k
return k
def 获得树剪枝阈值序列(self,T,节点位置:str):
if len(T[0])==0:return []
Ginimin=T[2]
theGini=T[1]
N=self.获取叶节点个数(T,节点位置)
if N<=1:print('what?')
ga=(theGini-Ginimin)/(N-1)
k=[]
for i in range(len(T[0])):
know=self.获得树剪枝阈值序列(T[0][i],节点位置+str(i))
k+=know
k.append(ga)
self.Tree节点分析信息ga[节点位置]=ga
return k
def 位置匹配(self,xlist,x0):
'''
判断xlist中有没有比x0高阶的存在
'''
for xl in xlist:
if len(xl)>=len(x0):continue
k=True
for j in range(len(xl)):
if xl[j]!=x0[j]:
k=False
break
if k:return k
return False
def 剪枝(self,T0):
'''
给Tree剪枝
输出:
cut:
'''
galist=self.获得树剪枝阈值序列(T0,'')
位置映射={}
ga=numpy.zeros(len(self.Tree节点分析信息ga))
for i,x in enumerate(self.Tree节点分析信息ga):
ga[i]=self.Tree节点分析信息ga[x]
位置映射[i]=x
sortindex=numpy.argsort(ga)
cut=[]
gainv=[]
for i in range(len(ga)):
x=位置映射[sortindex[i]]
if not self.位置匹配(cut,x) and len(x)>=self.树的最小深度:
gainv.append(ga[sortindex[i]])
cut.append(x)
return cut,gainv
def 叶子输出(self,T,x):
if len(T[0])==0:
return T[6]#输出值
else:
if x[T[3]]<T[5]:#x[besti]<xj
return self.叶子输出(T[0][0],x)
else:
return self.叶子输出(T[0][1],x)
def 叶子输出_批量(self,T,D):
'''
输出分类树的输出
'''
output=numpy.zeros(len(D))
for i in range(len(D)):
output[i]=self.叶子输出(T,D[i])
return output
def 计算准确率(self,T,标签,D,权重:numpy.ndarray|float=1):
'''
'''
output=self.叶子输出_批量(T,D)
print('警告,计算准确率只用int!')
set标签=numpy.array(list(set(标签)))
outputlabel=output.copy().astype(标签[0])
for i,oi in enumerate(output):
oilabelindex=numpy.argmin(abs(oi-set标签))
outputlabel[i]=set标签[oilabelindex]
正确率=float(sum((outputlabel==标签)*权重))/sum(权重)
return 正确率,output
def copytree(self,T,cut=None):
if len(T[0])==0:
new=[[],]
for j in range(len(T)):
if j!=0:new.append(T[j])
return new
else:
new=[[],]
if type(cut)==type(None):
for i in range(len(T[0])):
new[0].append(self.copytree(T[0][i]))
elif len(cut)!=0:
for i in range(len(T[0])):
if str(i)==cut[0]:
new[0].append(self.copytree(T[0][i],cut=cut[1:]))
else:
new[0].append(self.copytree(T[0][i]))
for j in range(len(T)):
if j!=0:new.append(T[j])
return new
def 训练(self,setA,标签,D,权重,验标签,验D,验权重,树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'):
'''
完整流程
'''
Tree=self.分类回归树(setA,标签,D,权重,
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
cut,gainv=self.剪枝(Tree)
Treelist=[Tree]
a0,output0=self.计算准确率(Treelist[-1],验标签,验D,权重=验权重)
outputall=[output0]
alist=[a0]
for i,dcut in enumerate(cut):
Treelist.append(self.copytree(Treelist[-1],cut=dcut))
ai,output=self.计算准确率(Treelist[-1],验标签,验D,权重=验权重)#此处用验证集
alist.append(ai)
outputall.append(output)
if alist[-1]>alist[-2]:break
bestindex=numpy.argmin(alist)
self.Tree=Treelist[bestindex]
return Treelist[bestindex],alist[bestindex],outputall[bestindex]
if __name__=='__main__':
pass