随机森林是决策树的升级版
随机指树的生长过程随机。
构建决策树时,从训练数据中有放回地选取一部分样本,且随机选取部分特征进行训练。
每棵树使用的样本和特征不同,这样就可以降低异常样本和特征差异性对结果的影响,且不容易过拟合。
最终输出结果由投票决定。
详见注释
# 随机森林需要调整的参数有:
# (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)
# 打印标签与预测值之间的差异。这可以提供有关模型性能的信息,例如误差或残差。