手写xgboost

Posted 出尘呢

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写xgboost相关的知识,希望对你有一定的参考价值。

# 训练


# 1)输入n*dx的numpy矩阵,n个样本,dx个特征。
import numpy as np
x = np.loadtxt("input/train.txt", dtype=np.float64)
y = np.loadtxt("label/train.txt", dtype=np.float64)
print('loaded')
n,dx=x.shape
_,dy=y.shape

# 2)弄一个输出表n*dy,是xgboost的输出,置0,同时还有gihi表
output=np.zeros_like(y)  # 定义n*dy的output表,初始化为0。
from base.lib_grad_hess import func_grad_hess
grad,hess=func_grad_hess(output,y)  # 定义n*dy的gi和hi表grad,hess,按照softmax_output计算。
print('pre down')
##### 树的定义#####

class Tree(object):
    def __init__(self):
        self.root=Node()
    def train(self,n,dyi):
        self.root.id_list = list(range(n))
        self.root.dfs(dyi,list(range(dx)),0)
    def test(self,n_v,dyi):
        self.root.id_list = list(range(n_v))
        self.root.test_dfs(dyi)

import random
import math
from base.lib_hypara import INF
# https://zhuanlan.zhihu.com/p/35061092
lam=1  # L2正则化权重项
gam=0.1  # 指定叶节点个数的正则化权重
max_depth=5  # 指定树的最大深度
min_sample=3  # 最小样本数在3左右
min_child_weight = 2  # 最小样本数改成样本的hi和,min_child_weight
min_gain=0.00  # 最小增益
lr=0.1  # 学习率

class Node(object):
    def __init__(self):
        self.is_leaf=None
        self.leaf_weight=None
        self.left = None
        self.right = None
        self.id_list = []
        self.classify = None

    def dfs(self,dyi,chra_list,depth):  # dyi 是第dyi个标签
        # 这是一个判断分裂函数
        # 4)直到: 到达最大深度 or 当类的样本个数低于阈值 or 当纯度分数的提高低于阈值,则此节点不分裂是叶节点。
        G, H = 0., 0.
        for i in self.id_list:
            G += grad[i][dyi]
            H += hess[i][dyi]
        flag = False  # 是不是叶子
        # if depth>=max_depth or H < min_child_weight:
        #     flag = True
        #     if H < min_child_weight:
        #         print('h:',H,len(self.id_list))
        #     else:
        #         print('depth:',depth,len(self.id_list))
        if depth>=max_depth or len(self.id_list) < min_sample:
            flag = True
            if len(self.id_list) < min_sample:
                print('m:',len(self.id_list))
            else:
                print('depth:',depth,len(self.id_list))
        else:
            gain,max_classify,G,H=self.split_finding(dyi, chra_list)
            if gain<min_gain:
                flag=True
                print('gain:', gain, len(self.id_list))
        if flag:
            # 是叶节点
            self.is_leaf=True
            # w ∗ j = − P i∈Ij gi P i∈Ij hi + λ ,
            self.leaf_weight=-G/(H+lam)
            # 5)计算叶节点的权值w=w'*lr。
            self.leaf_weight*=lr
            # 6)输出表添加权值w
            for i in self.id_list:
                output[i][dyi]+=self.leaf_weight
        else:
            # 是分支节点
            self.is_leaf=False
            # 分裂依据
            self.classify=max_classify
            # 3)若分裂,按照确定了的依据,分样本给子节点。缺失值INF单独处理。
            self.func_classify()
            # 接下来不能用这个特征分类
            chra_list.remove(self.classify[0])
            # 进入子树
            self.left.dfs(dyi,chra_list,depth+1)
            self.right.dfs(dyi,chra_list,depth+1)

        # 把self.id_list清空减少内存
        self.id_list=[]

    def test_dfs(self,dyi):
        if self.is_leaf:
            for i in self.id_list:
                output_v[i][dyi]+=self.leaf_weight
            self.id_list=[]
        else:
            self.func_classify()
            self.left.test_dfs(dyi)
            self.right.test_dfs(dyi)

    def func_classify(self):
        # 得到左右子树
        if self.left==None:self.left = Node()
        if self.right==None:self.right = Node()
        # 一个个分
        k,v,b=self.classify
        for i in self.id_list:
            # 缺失值
            if x[i][k]==INF:
                if b == 0:
                    self.left.id_list.append(i)
                else:
                    self.right.id_list.append(i)
            # 非缺失
            else:
                if x[i][k]<=v:
                    self.left.id_list.append(i)
                else:
                    self.right.id_list.append(i)

    def split_finding(self,dyi,chra_list):
        # 这是一个找分裂函数
        # 2)每个节点随机选择k = sqrt(len(chra_list))个特征,对每个特征进行带缺失值的分裂算法:
        select_chra_list=random.sample(chra_list,math.ceil(math.sqrt(len(chra_list))))
        # Algorithm 3: Sparsity-aware Split Finding
        # Input: I, instance set of current node
        # 是id_list
        # Input: Ik = i ∈ I|xik != missing
        # 这个是包含在值的,是INF与否,每一个弄一个
        # Input: d, feature dimension
        # 这个是select_chra_list
        # gain ← 0
        # gain=0  # 最大增益
        max_score=0  # 最大分数,gain=score/2-gam
        max_classify=None  # 最大增益对应的判断条件(k,v,b)
        # G ← P i∈I , gi,H ← P i∈I hi
        G,H=0,0
        for i in self.id_list:
            G+=grad[i][dyi]
            H+=hess[i][dyi]
        # for k = 1 to m do
        for k in select_chra_list:
            # 所有样本按照第k个特征排序
            def takek(elem):
                return x[elem][k]
            self.id_list.sort(key=takek)
            # 求缺失值sum
            Glack,Hlack=0,0
            p=len(self.id_list)-1
            while(p>=0 and takek(self.id_list[p])==INF):
                Glack += grad[self.id_list[p]][dyi]
                Hlack += hess[self.id_list[p]][dyi]
                p-=1
            p+=1  # p最终是非缺失值的长度
            # GL ← 0, HL ← 0
            GL,HL=0,0
            # for j in sorted(Ik, ascent order by xjk) do
            for pj in range(p):
                # missing value goto right
                # GL ← GL + gj , HL ← HL + hj
                GL+=grad[self.id_list[pj]][dyi]
                HL+=hess[self.id_list[pj]][dyi]
                if pj+1<p and takek(self.id_list[pj])==takek(self.id_list[pj+1]): # 直到有不同
                    continue
                v=takek(self.id_list[pj])  # 对应的值
                # GR ← G − GL, HR ← H − HL
                GR,HR=G-GL,H-HL
                # score ← max(score, G2 L HL+λ + G2 R HR+λ − G2 H+λ )
                score=GL**2/(HL+lam)+GR**2/(HR+lam)-G**2/(H+lam)
                if score>max_score:
                    max_score=score
                    max_classify=(k,v,1)
                #  missing value goto left
                GL+=Glack
                HL+=Hlack
                GR,HR=G-GL,H-HL
                # score ← max(score, G2 L HL+λ + G2 R HR+λ − G2 H+λ )
                score=GL**2/(HL+lam)+GR**2/(HR+lam)-G**2/(H+lam)
                if score>max_score:
                    max_score=score
                    max_classify=(k,v,0)
                GL-=Glack
                HL-=Hlack
        # Split and default directions with max gain
        gain = max_score / 2 - gam
        return gain,max_classify,G,H


            #################

# 4)初始化dy个xgboost森林
classifier=[[]for i in range(dy)]

# 准确率记录
from base.lib_evaluate import evaluate_acc
acc_results=[]

################
# 新增:
# 1)输入n*dx的numpy矩阵,n个样本,dx个特征,有的连续有的离散。
import numpy as np
x_v = np.loadtxt("input/valid.txt", dtype=np.float64)
y_v = np.loadtxt("label/valid.txt", dtype=np.float64)
n_v=x_v.shape[0]
# 2)dy次通过分别的xgboost分别输出dy个n*1的输出。
output_v=np.zeros((n_v,dy))
pred_result_v=[]
from base.lib_evaluate import evaluate_acc
acc_results_v=[]
######

# 5)B轮,每轮
B = 3
for epoch in range(B):
    print('epoch:',epoch)
    # 1)dy次,在分别的xgboost里建一棵树,每一次建一个树:
    for i in range(dy):
        print('tree:', i)
        # 0)建空树,把所有节点放在根节点
        classifier[i].append(Tree())
        # 1)从根节点开始dfs
        classifier[i][-1].train(n,i)
    print('caculating grad hess')
    # 2)重新计算每个样本每个标签的gi,hi,通过softmax。
    grad,hess=func_grad_hess(output,y)  # 定义n*dy的gi和hi表grad,hess,按照softmax_output计算。
    print('caculating acc')
    # 评价现在的准确率怎么样
    acc=evaluate_acc(y,output)
    print('epoch:\\tacc:'.format(epoch,acc))
    acc_results.append(acc)
    ###验证###
    for j in range(dy):
        classifier[j][epoch].test(n_v,j)  # 里面修改output
    # 3)n个样本的标签。
    acc_v = evaluate_acc(y_v, output_v)
    print('valid:',acc_v)
    acc_results_v.append(acc_v)
    prediction_v=np.argmax(output_v, axis=1)
    pred_result_v.append(prediction_v)
print('over')
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 10))
plt.plot(range(len(acc_results_v)),acc_results_v)
plt.show()
plt.gcf().savefig('figure\\\\valid.png')

# 输出成excel
import pandas as pd
df=pd.DataFrame(pred_result_v).T
df.to_csv('output/valid行样本列树数.csv')
df2=pd.DataFrame(pred_result_v[-1])
df2.to_csv('output/valid_last.csv',index=False)
############
# 保存一个输出
prediction=np.argmax(output, axis=1)
np.savetxt("output/train.txt",prediction)

import matplotlib.pyplot as plt
plt.figure(figsize=(20, 10))
plt.plot(range(len(acc_results)),acc_results)
plt.show()
plt.gcf().savefig('figure\\\\train.png')

# 4)保存xgboost树,是python对象可以jsonpickle。
from base.lib_store import serialize
print('storing')
serialize(classifier,'store\\\\classifier.bin')
print('done')




以上是关于手写xgboost的主要内容,如果未能解决你的问题,请参考以下文章

手写xgboost

再谈XGBoost原理

R语言构建xgboost模型:xgb.cv函数交叉验证确定模型的最优子树个数(可视化交叉验证对数损失函数与xgboost模型子树个数的关系)交叉验证获取最优子树之后构建最优xgboost模型

机器学习算法中 GBDT 和 XGBOOST 的区别有哪些?

机器学习算法中 GBDT 和 XGBOOST 的区别有哪些?

xgboost使用