树回归

项目代码

树回归

CART(Classification And Regression Trees),即分类回归树。顾名思义,该算法使用的是树形结构,既可用于分类,又能用于回归。
线性回归(局部加权线性回归除外)在构建模型时需要拟合所有样本,当数据特征较多并且之间关系较复杂时,构建全局模型就显得太难。相比于线性回归,树回归更适合对复杂、非线性数据进行回归建模。

树构建算法

算法名称特征选择方式分支方式用途
ID3信息增益选取当前最佳的特征分割数据,并按照该特征的所有可能取值来切分。此方法只能只能处理离散型数据。分类
C4.5信息增益比选取当前最佳的特征分割数据,并按照该特征的所有可能取值来切分。此方法只能只能处理离散型数据。分类
CART基尼系数(分类),总方差(回归)二元切分法,每次把数据集切分成两份,如果数据的某特征值等于切分所要求的值,那么这些数据就进入树的左子树,反之进入树的右子树分类、回归

回归树和分类树构建方式类似,只是叶节点的数据类型不是离散型,而是连续型。

两种树回归

树回归有两种方式:回归树和模型树
回归树:叶节点是单个值(当前叶子所有样本标签均值)
模型树:叶节点是一个线性方程(当前叶子所有样本的线性回归模型)

回归树

叶节点是一个值:当前叶子所有样本标签均值
误差衡量:总方差,表示一组数据的混乱度,是本组所有数据与这组数据均值之差的平方和
代码实现:

import numpy as np
import matplotlib.pyplot as plt

# 加载数据
def read_data(path):
    data = np.loadtxt(path)
    return data
    
# if __name__ == '__main__':
#     data = read_data('./data/reg_tree.txt')
#     plt.scatter(data[:,0], data[:,1])
#     plt.show()
import numpy as np

# 二分数据
def bin_split_data_set(dataset, feat, val):
    mat0 = dataset[np.nonzero(dataset[:, feat] > val)[0], :]  # 数组过滤选择特征大于指定值的数据
    mat1 = dataset[np.nonzero(dataset[:, feat] <= val)[0], :]  # 数组过滤选择特征小于指定值的数据
    return mat0, mat1

def choose_best_split(dataset, leafType=None, errType=None, ops=(1, 4)):
    """最佳特征以及最佳特征值选择函数
    leafType为叶节点取值,默认为None,可取regleaf,modelLeaf
    errType为数据误差(混乱度)计算方式,默认为None,可取regErr,modelErr
    ops[0]为以最佳特征及特征值切分数据前后,数据混乱度的变化阈值,若小于该阈值,不切分
    ops[1]为切分后两块数据的最少样本数,若少于该值,不切分
    回归树形状对ops[0],ops[1]很敏感,若这两个值过小,回归树会很臃肿,过拟合
    """
    tol_err = ops[0]
    tol_n = ops[1]
    m, n = dataset.shape
    err = errType(dataset)
    best_err = np.inf
    best_index = 0
    best_val = 0
    if len(set(dataset[:, -1].T)) == 1:  # 若只有一个类别
        return None, leafType(dataset)
    for featIndex in range(n - 1):
        for splitVal in set(dataset[:, featIndex].T):
            mat0, mat1 = bin_split_data_set(dataset, featIndex, splitVal)
            # 若切分后两块数据的最少样本数少于设定值,不切分
            if (mat0.shape[0] < tol_n) or (mat1.shape[0] < tol_n):
                continue
            new_err = errType(mat0) + errType(mat1)
            if new_err < best_err:
                best_index = featIndex
                best_val = splitVal
                best_err = new_err
    # 若以最佳特征及特征值切分后的数据混乱度与原数据混乱度差值小于阈值,不切分
    if (err - best_err) < tol_err:
        return None, leafType(dataset)
    mat0, mat1 = bin_split_data_set(dataset, best_index, best_val)
    # 若以最佳特征及特征值切分后两块数据的最少样本数少于设定值,不切分
    if (mat0.shape[0] < tol_n) or (mat1.shape[0] < tol_n):
        return None, leafType(dataset)
    return best_index, best_val

def create_tree(dataset, leafType=None, errType=None, ops=(1, 4)):
    """构建回归树"""
    feat, val = choose_best_split(dataset, leafType, errType, ops)
    if feat == None:
        return val
    reg_tree = {}
    reg_tree['spFeat'] = feat
    reg_tree['spVal'] = val
    lSet, rSet = bin_split_data_set(dataset, feat, val)
    reg_tree['left'] = create_tree(lSet, leafType, errType, ops)
    reg_tree['right'] = create_tree(rSet, leafType, errType, ops)
    return reg_tree

实现回归树代码:

import numpy as np
from utils import *

def reg_leaf(dataset):
    '''定义回归树的叶子(该叶子上各样本标签的均值)'''
    return np.mean(dataset[:, -1])

def reg_err(dataset):
    '''定义连续数据的混乱度(总方差,即连续数据的混乱度=(该组各数据-该组数据均值)**2,即方差*样本数)'''
    return np.var(dataset[:-1]) * dataset.shape[0]
回归树剪枝

当设置的最小分离叶节点样本数、最小混乱度减小值等参数过小,生成的模型可能包含大量叶子节点,就可能产生过拟合现象,这就需要对树进行剪枝,以防止过拟合。
剪枝分为预剪枝和后剪枝
预剪枝:在chooseBestSplit函数中的几个提前终止条件(切分样本小于阈值、混乱度减弱小于阈值),都是预剪枝(参数敏感)。
后剪枝:使用测试集对训练出的回归树进行剪枝(由于不需要用户指定,后剪枝是一种更为理想化的剪枝方法)

后剪枝逻辑:对训练好的回归树,自上而下找到叶节点,用测试集来判断将这些叶节点合并是否能降低测试误差,若能,则合并。
后剪枝代码及测试:

# 判断是否是一棵树(字典)
def isTree(obj):
    return (type(obj).__name__ == 'dict')

# 得到树所有叶节点的均值
def getMean(tree):
    # 若子树仍然是树,则递归调用getMeant直到叶节点
    if isTree(tree['left']):
        tree['left'] = getMean(tree['left'])
    if isTree(tree['right']):
        tree['right'] = getMean(tree['right'])
    return (tree['left'] + tree['right']) / 2.0

"""剪枝函数:对训练好的回归树,自上而下找到叶节点,用测试集来判断将这些叶节点合并是否能降低测试误差,若能则合并"""
def prune(tree, testData):
    # 若无测试数据,则直接返回树所有叶节点的均值(塌陷处理)
    if testData.shape[0] == 0:
        return getMean(tree)
    # 若存在任意子集是树,则将测试集按当前树的最佳切分特征和特征值切分(子集剪枝用)
    if isTree(tree['left']) or isTree(tree['right']):
        lSet, rSet = bin_split_data_set(testData, tree['spFeat'], tree['spVal'])
    # 若存在任意子集是树,则该子集递归调用剪枝过程(利用刚才切分好的训练集)
    if isTree(tree['left']):
        tree['left'] = prune(tree['left'], lSet)
    if isTree(tree['right']):
        tree['right'] = prune(tree['right'], rSet)
    # 若当前子集都是叶节点,则计算该二叶节点合并前后的误差,决定是否合并
    if not isTree(tree['left']) and not isTree(tree['right']):
        lSet, rSet = bin_split_data_set(testData, tree['spFeat'], tree['spVal'])
        errNotMerge = sum(np.power(lSet[:, -1].T.tolist()[0] - tree['left'], 2)) + sum(np.power(rSet[:, -1].T.tolist()[0] - tree['right'], 2))
        treeMean = (tree['left'] + tree['right']) / 2.0
        errMerge = sum(np.power(testData[:, -1].T.tolist()[0] - treeMean, 2))
        if errMerge < errNotMerge:
            print("merging")
            return treeMean
        else:
            return tree
    else:
        return tree
模型树

叶节点是一个线性回归模型:当前叶子所有样本的线性回归模型
误差衡量:平方误差类比线性回归误差,用线性模型对数据拟合,计算真实值与拟合值之差,求差值的平方和。其比回归树有更好的可解释性、更高的预测准确度

import numpy as np

def linear_solve(dataset):
    '''叶节点计算方法:该叶节点所有样本的标准线性回归模型'''
    m, n = dataset.shape
    x_mat = np.matrix(np.ones((m, n)))
    x_mat[:, 1:n] = dataset[:, 0:n - 1]  # x_mat第一列为常数项1
    y_mat = dataset[:, -1]
    y_mat = np.matrix(y_mat).T
    xTx = x_mat.T * x_mat
    if np.linalg.det(xTx) == 0:
        print("矩阵为奇异矩阵,不可逆,尝试增大ops的第二个参数")
        return
    ws = xTx.I * (x_mat.T * y_mat)
    return ws, x_mat, y_mat

def model_leaf(dataset):
    ws, X, Y = linear_solve(dataset)
    return ws

def model_err(dataset):
    '''误差计算方法:用线性模型对数据拟合,计算真实值与拟合值之差,求差值的平方和'''
    ws, X, Y = linear_solve(dataset)
    y_hat = X * ws
    return sum(np.power(Y - y_hat, 2))
模型树剪枝

模型树也会出现过拟合,也需要剪枝,原理与回归树剪枝一样,只需要替换其中的误差计算方式,然后微调一下剪枝代码,让每次递归时,对训练数据也递归切分。

# 判断是否是一棵树(字典)
def isTree(obj):
    return (type(obj).__name__ == 'dict')


# 剪枝函数:对训练好的模型树,自上而下找到叶节点,用测试集来判断将这些叶节点合并是否能降低测试误差,若能则合并
def model_prune(tree, trainData, testData):
    m, n = testData.shape
    # 若无测试数据,则直接返回树所有叶节点的均值(塌陷处理)
    if m == 0:
        return tree
    # 若存在任意子集是树,则将测试集按当前树的最佳切分特征和特征值切分(子集剪枝用)
    # 同时将训练集也按当前树的最佳切分特征和特征值切分(子集剪枝用)
    if isTree(tree['left']) or isTree(tree['right']):
        lSet, rSet = bin_split_data_set(testData, tree['spFeat'], tree['spVal'])
        lTrain, rTrain = bin_split_data_set(trainData, tree['spFeat'], tree['spVal'])
    # 若存在任意子集是树,则该子集递归调用剪枝过程(利用刚才切分好的训练集)
    if isTree(tree['left']):
        tree['left'] = model_prune(tree['left'], lTrain, lSet)
    if isTree(tree['right']):
        tree['right'] = model_prune(tree['right'], rTrain, rSet)

    # 若当前子集都是叶节点,则计算该二叶节点合并前后的误差,决定是否合并
    """
    模型树,两个叶节点合并前的误差=((左叶子真实值-拟合值)的平方和+(右叶子真实值-拟合值)的平方和)
    模型树,两个叶节点合并后的误差=(左右真实值-左右拟合值)的平方和
    难点在于如何求左右拟合值,即求上层节点的回归系数wsMerge:用上层节点的traindata,通过linearSolve(traindata)求得
    上层节点的traindata在lTrain,rTrain的递归中已经求好了
    """
    if not isTree(tree['left']) and not isTree(tree['right']):
        lSet, rSet = bin_split_data_set(testData, tree['spFeat'], tree['spVal'])
        lSetX = np.matrix(np.ones((lSet.shape[0], n)))
        rSetX = np.matrix(np.ones((rSet.shape[0], n)))
        lSetX[:, 1:n] = lSet[:, 0:n - 1]
        rSetX[:, 1:n] = rSet[:, 0:n - 1]

        errNotMerge = np.sum(np.power(np.array(lSet[:, -1].T) - lSetX * tree['left'], 2)) + np.sum(
            np.power(np.array(rSet[:, -1].T) - rSetX * tree['right'], 2))
        # 难点在于求上层节点的回归系数wsMerge:用上层节点的traindata,通过linearSolve(traindata)求得
        wsMerge = model_leaf(trainData)
        testDataX = np.matrix(np.ones((m, n)));
        testDataX[:, 1:n] = testData[:, 0:n - 1]
        errMerge = np.sum(np.power(np.array(testData[:, -1].T) - testDataX * wsMerge, 2))
        if errMerge < errNotMerge:
            print("merging")
            return wsMerge
        else:
            return tree
    else:
        return tree
用树回归进行预测的代码
import numpy as np
from model_tree import *

def reg_tree_eval(model, inDat):
    return float(model)

def model_tree_eval(model, inDat):
    n = inDat.shape[1]
    X = np.matrix(np.ones((1, n + 1)))
    X[:, 1:n + 1] = inDat
    return float(X * model)


def tree_fore_cast(tree, inData, modelEval=reg_tree_eval):
    if not isTree(tree): return modelEval(tree, inData)
    if inData[tree['spFeat']] > tree['spVal']:
        if isTree(tree['left']):
            return tree_fore_cast(tree['left'], inData, modelEval)
        else:
            return modelEval(tree['left'], inData)
    else:
        if isTree(tree['right']):
            return tree_fore_cast(tree['right'], inData, modelEval)
        else:
            return modelEval(tree['right'], inData)

def create_fore_cast(tree, testData, modelEval=reg_tree_eval):
    m = len(testData)
    yHat = np.matrix(np.zeros((m, 1)))
    for i in range(m):
        yHat[i, 0] = tree_fore_cast(tree, np.matrix(testData[i]), modelEval)
    return yHat
import numpy as np
import matplotlib.pyplot as plt
from load_data import read_data
from regression_tree import *
from model_tree import *
from predict import *

if __name__ == '__main__':
    train = read_data('./data/bikeSpeedVsIq_train.txt')
    test = read_data('./data/bikeSpeedVsIq_test.txt')
    reg_tree = create_tree(dataset=train, leafType=reg_leaf, errType=reg_err)
    y_hat = create_fore_cast(reg_tree,test[:,0])
    corr = np.corrcoef(y_hat,test[:,1],rowvar=0)
    print(corr)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在sklearn中,决策回归是通过构建回归来进行预测的。回归的结构与分类类似,但在叶节点上存储的是一个连续值,而不是离散的类别。通过对特征的逐层划分,回归可以将输入的样本分成不同的区域,并为每个区域预测一个连续的输出值。 决策回归的基本概念包括: 1. 回归的构建:从根节点开始,按照决策的分类属性逐层往下划分,直到叶节点,获得分类结果。 2. 分裂准则:决策回归使用的分裂准则一般是最小化平方误差(MSE)或平均绝对误差(MAE)。 3. 剪枝:为了防止过拟合,决策回归可以通过剪枝操作来降低模型复杂度。 在sklearn中,可以使用DecisionTreeRegressor类来构建决策回归模型。通过fit方法传入训练数据,模型会自动学习并构建回归。然后可以使用predict方法对新的数据进行预测,得到连续的输出值。 总结起来,sklearn的决策回归是一种基于回归的预测方法,通过对特征的逐层划分,将输入的样本分成不同的区域,并为每个区域预测一个连续的输出值。它是一种灵活且易于解释的预测模型,适用于处理连续型的目标变量。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [sklearn中的决策回归)](https://blog.csdn.net/qq_33761152/article/details/123976106)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [机器学习--sklearn之决策(分类+回归)](https://blog.csdn.net/qq_36758914/article/details/104298371)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值