BDT(Bootstrap Decision Tree)python实现
代码仅供参考
导入库
import CART树_自己写
import numpy
其中一个库之前写的
分类树主体代码
在原始数据权重基础上使用更新的数据权重以更新BDT模型.
def 分类树(self,BDT_M,setA,标签,D,权重,验标签,验D,验权重,
树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'
):
'''
BDT回归树
由CART回归数合并得到
Loss为指数
CART回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
BDT_M:M个CART树
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],...]
'''
FM=[]
aM=[]
set标签=numpy.array(list(set(标签)))
WM=numpy.ones(len(D))/len(D)
for mi in range(BDT_M):
fmi=CART树_自己写.CART模型(标签,D)
T,acu,output0_var=fmi.训练(setA,标签,D,WM*权重,验标签,验D,验权重,#此处验证集就是原本
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
FM.append(fmi)
# fmibefore=self.函数输出(FM,aM,D)
output0=fmi.叶子输出_批量(T,D)#输出必须是训练集
outputlabel=output0.copy().astype(标签[0])
for i,oi in enumerate(output0):
oilabelindex=numpy.argmin(abs(oi-set标签))
outputlabel[i]=set标签[oilabelindex]
acu=float(sum((outputlabel==标签)*权重))/sum(权重)#真实正确率
if acu>0.9999:acu=0.9999
if acu<0.0001:acu=0.0001
em=1-acu
ami=0.5*numpy.log(acu/em)
aM.append(ami)
WMnew=WM*numpy.exp(-ami*标签*output0);WMnew=WMnew/sum(WMnew)
WM[:]=WMnew[:]
return FM,aM
回归树主体代码
新的基学习器拟合残差。
def 回归树(self,BDT_M,setA,标签,D,权重,验标签,验D,验权重,
树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'
):
'''
BDT回归树
由CART回归数合并得到
Loss为残差平方#此方法下损失函数只能为连续的,不能为Gini!!
CART回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
BDT_M:M个CART树
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],...]
'''
FM=[]
aM=[]
WM=numpy.ones(len(D))
for mi in range(BDT_M):
fmi=CART树_自己写.CART模型(标签,D)
fmibefore=self.函数输出(FM,aM,D)
T,acu,output0=fmi.训练(setA,标签-fmibefore,D,权重,验标签,验D,验权重,#此处验证集就是原本
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
FM.append(fmi)
aM.append(1.0)
return FM,aM
输出函数
对于分类树的输出可以提前算好输出的整体权重
def 函数输出(self,FM:list[CART树_自己写.CART模型],aM,D):
aM=numpy.array(aM);#aM=aM/sum(abs(aM))
F=numpy.zeros(len(D))
for i,x in enumerate(FM):
F+=aM[i]*FM[i].叶子输出_批量(FM[i].Tree,D)
return F
完整代码
import CART树_自己写
import numpy
class BDT():
'''
简单BDT
'''
def __init__(self, 标签, Data):
assert len(标签)==len(Data),'标签应该和数据个数一致!'
self.数据个数=len(标签)
self.维数=len(Data[0])
self.标签=numpy.array(标签)
self.Data=numpy.array(Data)
def 分类树(self,BDT_M,setA,标签,D,权重,验标签,验D,验权重,
树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'
):
'''
BDT回归树
由CART回归数合并得到
Loss为指数
CART回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
BDT_M:M个CART树
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],...]
'''
FM=[]
aM=[]
set标签=numpy.array(list(set(标签)))
WM=numpy.ones(len(D))/len(D)
for mi in range(BDT_M):
fmi=CART树_自己写.CART模型(标签,D)
T,acu,output0_var=fmi.训练(setA,标签,D,WM*权重,验标签,验D,验权重,#此处验证集就是原本
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
FM.append(fmi)
# fmibefore=self.函数输出(FM,aM,D)
output0=fmi.叶子输出_批量(T,D)#输出必须是训练集
outputlabel=output0.copy().astype(标签[0])
for i,oi in enumerate(output0):
oilabelindex=numpy.argmin(abs(oi-set标签))
outputlabel[i]=set标签[oilabelindex]
acu=float(sum((outputlabel==标签)*权重))/sum(权重)#真实正确率
if acu>0.9999:acu=0.9999
if acu<0.0001:acu=0.0001
em=1-acu
ami=0.5*numpy.log(acu/em)
aM.append(ami)
WMnew=WM*numpy.exp(-ami*标签*output0);WMnew=WMnew/sum(WMnew)
WM[:]=WMnew[:]
return FM,aM
def 回归树(self,BDT_M,setA,标签,D,权重,验标签,验D,验权重,
树的最大深度=5,最小样本数=10,最小不纯度=0.1,损失函数='平方',输出='平均'
):
'''
BDT回归树
由CART回归数合并得到
Loss为残差平方#此方法下损失函数只能为连续的,不能为Gini!!
CART回归树(连续变量)
这个是确定了给定Loss下分类模型,确定了输入空间分开的M个区间,
如果输入落入第m个区域,那么输出是其实应该是输出的相应y值的平均
还可以是输出y值的众数(分类问题)
#此函数对Loss最小方式为暴力搜索,后续可能添加剃度下降版本
输入
BDT_M:M个CART树
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],...]
'''
FM=[]
aM=[]
WM=numpy.ones(len(D))
for mi in range(BDT_M):
fmi=CART树_自己写.CART模型(标签,D)
fmibefore=self.函数输出(FM,aM,D)
T,acu,output0=fmi.训练(setA,标签-fmibefore,D,权重,验标签,验D,验权重,#此处验证集就是原本
树的最大深度=树的最大深度,最小样本数=最小样本数,最小不纯度=最小不纯度,损失函数=损失函数,输出=输出)
FM.append(fmi)
aM.append(1.0)
return FM,aM
def 函数输出(self,FM:list[CART树_自己写.CART模型],aM,D):
aM=numpy.array(aM);#aM=aM/sum(abs(aM))
F=numpy.zeros(len(D))
for i,x in enumerate(FM):
F+=aM[i]*FM[i].叶子输出_批量(FM[i].Tree,D)
return F
def 准确矩阵(self,output,标签):
'''
Mij,i是输出
'''
set标签=set(标签)
M=numpy.zeros((len(set标签),len(set标签)),dtype=int)
for i,labeli in enumerate(list(set标签)):
for j,labelj in enumerate(list(set标签)):
M[i,j]=numpy.all([output==labeli,标签==labelj],axis=0)
return M
后续可能添加的功能
- 需要用K-S检验来确定验证和训练输出是否一致.
- K-fold交叉验证.
- 剃度下降法(CART).
- 修正使得定义类的时候就定义好超参数,类内保存训练结果.