随机森林(Random Forest)

发布时间:2024年01月24日

随机森林

随机森林是决策树的升级版

????????随机指树的生长过程随机。

? ? ? ? 构建决策树时,从训练数据中有放回地选取一部分样本,且随机选取部分特征进行训练。

? ? ? ? 每棵树使用的样本和特征不同,这样就可以降低异常样本和特征差异性对结果的影响,且不容易过拟合。

????????最终输出结果由投票决定。

Python代码

? ? ? ? 详见注释

# 随机森林需要调整的参数有:
# (1)决策树的个数
# (2)特征属性的个数
# (3)递归次数(即决策树的深度)

from numpy import inf
from numpy import zeros
import numpy as np
from sklearn.model_selection import train_test_split


# 生成数据集。数据集包括标签,全包含在返回值的dataset上
def get_Datasets():
    from sklearn.datasets import make_classification
    dataSet, classLabels = make_classification(n_samples=200, n_features=100, n_classes=2)
    # print(dataSet.shape, classLabels.shape)
    # print('dataSet:',dataSet)
    # print('classLabels:',classLabels)
    return np.concatenate((dataSet, classLabels.reshape((-1, 1))), axis=1)


# 原理如下:
# 第一步,将训练集划分为大小相同的K份;
# 第二步,我们选择其中的K-1份训练模型,将用余下的那一份计算模型的预测值,这一份通常被称为交叉验证集;
# 第三步,我们对所有考虑使用的参数建立模型并做出预测,然后使用不同的K值重复这一过程。
# 然后是关键,我们利用在不同的K下平均准确率最高所对应的决策树个数作为算法决策树个数
def splitDataSet(dataSet, n_folds):  # 将训练集划分为大小相同的n_folds份;
    fold_size = len(dataSet) / n_folds  # 计算每个交叉验证折叠(fold)的大小。
    data_split = []  # 创建一个空列表存储划分后的数据。
    begin = 0
    end = fold_size  # 用于追踪每个折叠的起始和结束索引。
    for i in range(n_folds):
        data_split.append(dataSet[begin:end, :])  # 将训练集的当前折叠(范围从 begin 到 end)添加到 data_split 列表中。
        begin = end
        end += fold_size
    return data_split


# 构建n个子集
def get_subsamples(dataSet, n):
    subDataSet = []  # 创建一个空列表subDataSet用于存储子样本。
    for i in range(n):  # 循环 n 次,以获取指定数量的子样本。
        index = []  # 在每次循环中创建一个空列表 index用于存储随机选择的索引
        for k in range(len(dataSet)):  # 循环遍历数据集
            index.append(np.random.randint(len(dataSet)))  # 在每次循环中,随机生成一个在 (0, len(dataSet)) 范围内的整数,并将其添加到 index 列表中。
        subDataSet.append(dataSet[index, :])  # 使用随机选择的索引从原始数据集中提取子样本,并将其添加到 subDataSet 列表中。
    return subDataSet


# 根据某个特征及值对数据进行分类
def binSplitDataSet(dataSet, feature, value):
    mat0 = dataSet[np.nonzero(dataSet[:, feature] > value)[0], :]
    #  np.nonzero(dataSet[:, feature] > value) 返回了满足条件的行的索引。
    # dataSet[np.nonzero(dataSet[:, feature] > value)[0], :] 使用这些索引获取了符合条件的子集
    mat1 = dataSet[np.nonzero(dataSet[:, feature] < value)[0], :]
    return mat0, mat1


'''
feature = 2
value = 1
dataSet = get_Datasets()
mat0, mat1 = binSplitDataSet(dataSet, 2, 1)
print('mat0:', mat0)
print('mat1:', mat1)
'''


# 计算方差,回归时使用
def regErr(dataSet):
    return np.var(dataSet[:, -1]) * np.shape(dataSet)[0]


# np.var(dataSet[:, -1]): 这部分计算数据集最后一列的方差。dataSet[:, -1] 选择了数据集的最后一列。
# np.shape(dataSet)[0]:这部分获取数据集的行数。np.shape(dataSet) 返回数据集的形状,其中 [0] 表示行数。

# 计算平均值,回归时使用
def regLeaf(dataSet):
    return np.mean(dataSet[:, -1])


# dataSet[:, -1]:选择数据集的最后一列,假设这是回归目标的列。


def MostNumber(dataSet):  # 返回数据集中出现最多的类别
    len0 = len(np.nonzero(dataSet[:, -1] == 0)[0])
    # 这部分计算数据集中目标变量为0的样本的数量。
    # np.nonzero(dataSet[:, -1] == 0) 返回目标变量为0的索引数组,然后通过 len 函数得到其长度。
    len1 = len(np.nonzero(dataSet[:, -1] == 1)[0])
    if len0 > len1:
        return 0
    else:
        return 1


# 计算基尼指数,值越低表示数据集的不纯度越低。
# 一个随机选中的样本在子集中被分错的可能性,是被选中的概率乘以被分错的概率
def gini(dataSet):
    corr = 0.0
    for i in set(dataSet[:, -1]):  # i 是这个特征下的 某个特征值
        # 通过迭代数据集的目标变量的唯一值(即不同的类别)来计算基尼指数。
        corr += (len(np.nonzero(dataSet[:, -1] == i)[0]) / len(dataSet)) ** 2
    return 1 - corr


# 最佳分裂特征和值的函数
def select_best_feature(dataSet, m, alpha="huigui"):
    f = dataSet.shape[1]  # 获取数据集的特征数量
    index = []  # 存储随机选择的特征索引
    bestS = inf  # 初始化最佳分裂的指标为正无穷
    bestfeature = 0  # 初始化最佳分裂的特征
    bestValue = 0  # 初始化最佳分裂的特征值
    # 根据选择的alpha类型计算初始的不纯度指标S
    if alpha == "huigui":
        S = regErr(dataSet)
    else:
        S = gini(dataSet)
    # 随机选择m个特征
    for i in range(m):
        index.append(np.random.randint(f))
    # 遍历选定的特征
    for feature in index:
        # 遍历选定特征的所有可能取值
        for splitVal in set(dataSet[:, feature]):
            # set() 函数创建一个无序不重复元素集,用于遍历这个特征下所有的值
            # 根据特征和特征值进行数据集的二元划分
            mat0, mat1 = binSplitDataSet(dataSet, feature, splitVal)
            if alpha == "huigui":
                newS = regErr(mat0) + regErr(mat1)  # 计算每个分支的回归方差
            else:
                newS = gini(mat0) + gini(mat1)  # 计算被分错率
            # 更新最佳分裂条件
            if bestS > newS:
                bestfeature = feature
                bestValue = splitVal
                bestS = newS
    # 根据alpha类型判断是否继续分裂,或者返回叶子节点的值
    if (S - bestS) < 0.001 and alpha == "huigui":  # 对于回归来说,方差足够了,那就取这个分支的均值
        return None, regLeaf(dataSet)
    elif (S - bestS) < 0.001:
        # print(S,bestS)
        return None, MostNumber(dataSet)  # 对于分类来说,被分错率足够下了,那这个分支的分类就是大多数所在的类。
    # 返回最佳分裂的特征和特征值
    return bestfeature, bestValue


# 实现决策树,使用20个特征,深度为10.
def createTree(dataSet, alpha="huigui", m=20, max_level=10):
    bestfeature, bestValue = select_best_feature(dataSet, m, alpha=alpha)
    # 调用select_best_feature函数来确定最佳特征和对应的拆分值。
    if bestfeature is None:
        return bestValue
    # 检查是否没有找到最佳特征。如果是,则将bestValue作为叶节点返回。
    # 如果没有找到最佳特征,则返回bestValue作为叶节点值。
    retTree = {}
    # 初始化一个空字典retTree来表示决策树。
    # 控制树的深度。
    max_level -= 1
    if max_level < 0:
        return regLeaf(dataSet)
    # 在达到最大深度限制时,使用regLeaf函数返回叶节点值。
    retTree['bestFeature'] = bestfeature
    retTree['bestVal'] = bestValue
    # 将最佳特征和对应的最佳值存储在决策树字典中。
    lSet, rSet = binSplitDataSet(dataSet, bestfeature, bestValue)
    # lSet是根据特征bestfeature分到左边的向量,rSet是根据特征bestfeature分到右边的向量
    retTree['right'] = createTree(rSet, alpha, m, max_level)
    retTree['left'] = createTree(lSet, alpha, m, max_level)
    # 根据最佳特征和值将数据集分为左右子集。
    return retTree


# 树的个数
def RondomForest(dataSet, n, alpha="huigui"):
    Trees = []
    # 初始化一个空列表Trees,用于存储生成的决策树。
    for i in range(n):  # 循环生成指定数量(n)的决策树。
        X_train, X_test, y_train, y_test = train_test_split(dataSet[:, :-1], dataSet[:, -1], test_size=0.33,
                                                            random_state=42)
        # 使用train_test_split函数将数据集划分为训练集(X_train, y_train)和测试集(X_test, y_test)。这里将特征和目标变量分开。
        X_train = np.concatenate((X_train, y_train.reshape((-1, 1))), axis=1)
        # 将训练集中的特征和目标变量合并,形成新的训练集X_train。
        Trees.append(createTree(X_train, alpha=alpha))
        # 调用CreateTree函数,传入合并后的训练集X_train,生成一颗决策树,并将其添加到Trees列表中。
    return Trees


# 预测单个数据样本,重头!!
# 如何利用已经训练好的随机森林对单个样本进行 回归或分类!
def treeForecast(trees, data, alpha="huigui"):
    if alpha == "huigui":
        # 根据alpha参数的值,选择不同的逻辑执行回归("huigui")的部分。
        if not isinstance(trees, dict):  # isinstance() 函数来判断一个对象是否是一个已知的类型
            return float(trees)
        # 如果trees不是字典类型,说明已经到达叶子节点,直接返回该叶子节点的值(转换为浮点数)。
        if data[trees['bestFeature']] > trees['bestVal']:
            # 检查数据的某个特征值是否大于决策树节点的阈值,如果数据的这个特征大于阈值,那就调用左支
            if type(trees['left']) == 'float':
                # 如果左支已经是节点了,就返回数值。如果左支还是字典结构,那就继续调用, 用此支的特征和特征值进行选支。
                return trees['left']
            else:
                return treeForecast(trees['left'], data, alpha)
        else:
            if type(trees['right']) == 'float':
                return trees['right']
            else:
                return treeForecast(trees['right'], data, alpha)
    else:
        if not isinstance(trees, dict):  # 分类和回归是同一道理
            return int(trees)

        if data[trees['bestFeature']] > trees['bestVal']:
            if type(trees['left']) == 'int':
                return trees['left']
            else:
                return treeForecast(trees['left'], data, alpha)
        else:
            if type(trees['right']) == 'int':
                return trees['right']
            else:
                return treeForecast(trees['right'], data, alpha)


# 随机森林 对 数据集打上标签   0、1 或者是 回归值
def createForeCast(trees, test_dataSet, alpha="huigui"):
    cm = len(test_dataSet)
    yhat = np.mat(zeros((cm, 1)))
    # 创建一个由零填充的矩阵 yhat,其维度为 (cm, 1),其中 cm 是 test_dataSet 的长度。该矩阵将用于存储预测值。
    for i in range(cm):
        yhat[i, 0] = treeForecast(trees, test_dataSet[i, :], alpha)
    return yhat


# 随机森林预测
def predictTree(Trees, test_dataSet, alpha="huigui"):
    cm = len(test_dataSet)
    yhat = np.mat(zeros((cm, 1)))
    for trees in Trees:
        yhat += createForeCast(trees, test_dataSet, alpha)
        # 把每次的预测结果相加
    if alpha == "huigui":
        yhat /= len(Trees)
        # 如果是回归的话,每棵树的结果应该是回归值,相加后取平均
    else:
        for i in range(len(yhat)):
            # 如果是分类的话,每棵树的结果是一个投票向量,相加后,
            # 看每类的投票是否超过半数,超过半数就确定为1
            if yhat[i, 0] > len(Trees) / 2:
                yhat[i, 0] = 1
            else:
                yhat[i, 0] = 0
    return yhat


if __name__ == '__main__':
    # 表示以下代码块只有在脚本直接执行时才会运行,而不是被导入为模块时运行。
    dataSet = get_Datasets()
    print(dataSet[:, -1].T)
    # 打印标签,与后面预测值对比
    # .T是对一个矩阵的转置
    RomdomTrees = RondomForest(dataSet, 4, alpha="fenlei")
    # 用于训练一个随机森林,其中包含多棵决策树。
    print("---------------------RomdomTrees------------------------")
    # print(RomdomTrees[0])
    test_dataSet = dataSet
    # 得到数据集和标签
    yhat = predictTree(RomdomTrees, test_dataSet, alpha="fenlei")
    # 调用训练好的那些树。综合结果,得到预测值。
    print(yhat.T)
    # 打印预测值,并对其进行转置。
    # get_Datasets()
    print(dataSet[:, -1].T-yhat.T)
    # 打印标签与预测值之间的差异。这可以提供有关模型性能的信息,例如误差或残差。

文章来源:https://blog.csdn.net/qq_43010742/article/details/135826834
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。