当前位置: 首页 > java >正文

吴恩达机器学习补充:决策树和随机森林

数据集:通过网盘分享的文件:sonar-all-data.csv
链接: https://pan.baidu.com/s/1D3vbcnd6j424iAwssYzDeQ?pwd=12gr 提取码: 12gr

学习来源:https://github.com/cabin-w/MLBeginnerHub

文末有完整代码,由于这里的代码和之前的按部就班的风格不同,是将所有函数都写好,然后在主函数里面调用,建议大家可以将完整代码粘贴好后,遇到不知道要做什么的函数就点一下查看用法。

定义

决策树的定义

决策树(Decision Tree)是一种基于树状结构进行决策的监督学习算法,其核心思想是通过对数据特征的逐步划分,模拟人类决策过程,最终实现对目标变量的预测(分类或回归)。

  • 结构组成:

    • 根节点:代表整个数据集,是决策树的起点。

    • 内部节点:每个内部节点对应一个特征,用于对数据进行划分(如 “年龄是否大于 30 岁”“收入是否高于 5 万元”)。

    • 分支:每个分支表示基于特征划分的结果(如 “是” 或 “否”)。

    • 叶节点:树的末端,代表最终的决策结果(如分类问题中的类别,回归问题中的数值)。

  • 工作原理: 从根节点开始,根据特征的重要性(如通过信息增益、基尼系数等指标衡量)选择最优特征,将数据划分为不同子集;对每个子集重复这一过程,直到子集中的样本属于同一类别(分类)或差异足够小(回归),形成叶节点。

  • 示例: 预测 “用户是否购买商品” 时,决策树可能先按 “年龄” 划分,再按 “是否有优惠券” 进一步划分,最终叶节点输出 “购买” 或 “不购买”。

随机森林的定义

随机森林(Random Forest)是一种集成学习算法,通过构建多个决策树并综合其结果来提高预测性能,减少过拟合风险。

  • 核心思想: 基于 “集成思想”—— 多个弱分类器(决策树)的组合可以形成强分类器。具体通过两个随机性实现:

    • 样本随机:从原始数据中通过bootstrap 抽样(有放回抽样)生成多个不同的训练子集,每个子集用于训练一棵决策树。

    • 特征随机:在每棵决策树的每个节点划分时,从所有特征中随机选择部分特征(如总特征数的平方根),仅基于这些特征进行最优划分。

    • 由以上两个随机,我们就可以发现单棵决策树决策树的问题在于结果不稳定。

  • 预测规则:

    • 分类问题:多数投票法(所有决策树的预测结果中,出现次数最多的类别为最终结果)。

    • 回归问题:平均值法(所有决策树的预测结果的平均值为最终结果)。

  • 优势: 相比单一决策树,随机森林泛化能力更强,对噪声数据更稳健,且不易过拟合。

总结

算法本质核心特点应用场景
决策树单一树状模型直观易懂,易过拟合简单分类 / 回归、规则提取
随机森林多棵决策树的集成性能更优,抗过拟合,稳定性高复杂数据预测、特征重要性评估

算法流程

决策树算法流程

1.决策树构建流程(训练阶段)

步骤 1:定义分裂规则(二元分裂)

通过data_spilt函数实现 CART 的核心分裂逻辑:

  • 输入:数据集、待分裂特征索引index、分裂阈值value
  • 流程:遍历所有样本,按「样本特征值 < value」划分为左子集,否则划分为右子集(固定二元分裂,无论特征是连续 / 离散)。
  • 输出:左子集left、右子集right
步骤 2:定义纯度度量(基尼不纯度)

通过spilt_loss函数计算分裂后的「不纯度损失」,衡量分裂优劣:

  • 输入:左右子集、所有类别值class_values(如['R','M'])。
  • 流程:
    ① 对每个类别,计算左子集该类别的占比prop = 该类样本数 / 左子集总样本数
    ② 基尼不纯度公式:prop*(1-prop)(值越小,子集纯度越高);
    ③ 累加左右子集的基尼不纯度,得到总分裂损失。
  • 输出:分裂损失loss(损失越小,分裂效果越好)。
步骤 3:选择最优分裂(特征 + 阈值)

通过get_best_spilt函数在「随机选择的n_features个特征」中找最优分裂点:

  • 输入:数据集、待选特征数n_features(代码中n_features=15)。
  • 流程:
    ① 随机选择n_features个不重复的特征索引(避免特征冗余,为随机森林做铺垫);
    ② 对每个候选特征,遍历该特征的所有样本值作为「分裂阈值value」;
    ③ 按每个(特征,阈值)分裂数据集,计算分裂损失;
    ④ 选择「损失最小」的(特征索引b_index、阈值b_value、左右子集b_left/b_right)。
  • 输出:最优分裂字典(含indexvalueleftright)。
步骤 4:递归构建决策树

通过build_tree(入口)和sub_spilt(递归逻辑)函数构建树结构:

  • 输入:训练集、n_features、树最大深度max_depth(代码中max_depth=15)、叶子节点最小样本数min_size(代码中min_size=1)。
  • 流程:
    ① 根节点:调用get_best_spilt获取最优分裂,作为根节点;
    ② 递归分裂子节点(sub_spilt):
    • 终止条件 1:左 / 右子集为空→将叶子节点设为「左右子集合并后的多数类别」(decide_label函数,多数投票);
    • 终止条件 2:树深度超过max_depth→叶子节点设为对应子集的多数类别;
    • 终止条件 3:子集样本数小于min_size→叶子节点设为该子集的多数类别;
    • 非终止条件:对左右子集重复步骤 3(选最优分裂),继续递归构建子树。
  • 输出:决策树字典(如{'index':5, 'value':0.2, 'left': {...}, 'right':'R'},叶子节点为类别,非叶子节点为分裂信息)。

2. 决策树预测流程

通过predict函数实现单棵树的预测:

  • 输入:已构建的决策树、单个测试样本row
  • 流程:
    ① 从根节点开始,判断样本的「最优分裂特征值」与「阈值value」的大小;
    ② 若特征值 < 阈值→进入左子节点,若左子节点是字典(非叶子)则递归预测,否则返回左子节点的类别;
    ③ 若特征值 ≥ 阈值→进入右子节点,同理递归或返回类别。
  • 输出:单个样本的预测类别。

随机森林算法流程(多棵树集成)

随机森林是「Bagging 集成 + 随机特征选择」的决策树集合,对应get_subsamplerandom_forestbagging_predict函数:

1. 随机森林构建流程(训练阶段)
步骤 1:Bootstrap 有放回抽样

通过get_subsample函数为每棵树生成独立的训练集:

  • 输入:原始训练集、抽样比例ratio(代码中ratio=1,即抽样数 = 原始训练集大小)。
  • 流程:通过「有放回随机抽样」(randrange选索引,不删除原样本)抽取ratio*总样本数个样本,组成子训练集。
  • 输出:单棵树的子训练集(允许样本重复,提升树的多样性)。
步骤 2:构建多棵决策树

通过random_forest函数实现森林构建:

  • 输入:总训练集、测试集、ration_featuresmax_depthmin_size、树的数量n_trees(代码中n_trees=20)。
  • 流程:
    ① 循环n_trees次:
    • 对总训练集做 Bootstrap 抽样,得到子训练集;
    • 用子训练集调用build_tree构建 1 棵决策树,存入trees列表;
      ② 输出:包含n_trees棵决策树的森林trees
2. 随机森林预测流程(集成投票)

通过bagging_predict函数实现多棵树的结果融合:

  • 输入:森林trees、单个测试样本row
  • 流程:
    ① 用森林中每棵树对样本预测,得到n_trees个预测类别;
    ② 对所有预测类别做「多数投票」(max(set(predictions), key=predictions.count)),选择出现次数最多的类别。
  • 输出:单个样本的最终预测类别(集成结果,比单棵树更稳定)。

代码实现

加载数据

def loadCSV(filename):  # 定义loadCSV函数,参数为filenamedataSet = []  # 创建空列表dataSetwith open(filename, 'r') as file:  # 用只读方式打开filename文件并赋值给filecsvReader = csv.reader(file)  # 用csv模块的reader方法读取file文件并赋值给csvReader(分割处理)for line in csvReader:  # 遍历csvReader中的每一行dataSet.append(line)  # 将每一行添加到dataSet列表中return dataSet  # 返回dataSet列表

预处理

# 除了判别列,其他列都转换为float类型
def column_to_float(dataSet):  # 定义column_to_float函数,参数为dataSetfeatLen = len(dataSet[0]) - 1  # 获取dataSet中最后一列的索引for data in dataSet:  # 遍历dataSet中的每一行for column in range(featLen):  # 遍历每一行中除最后一列之外的每一列data[column] = float(data[column].strip())  # 将每一列的字符串转换为浮点数并赋值回原位置

将数据集分成N块,方便交叉验证

def spiltDataSet(dataSet, n_folds):fold_size = int(len(dataSet) / n_folds)  # 计算每份数据的大小dataSet_copy = list(dataSet)  # 复制数据集dataSet_spilt = []  # 创建用于存储分割后数据集的列表for i in range(n_folds):  # 循环n_folds次,将数据集分割成n份fold = []  # 创建一个空列表用于存储每份数据while len(fold) < fold_size:  # 当该份数据未达到应有的大小时index = randrange(len(dataSet_copy) - 1)  # 随机选择数据集中的索引(不放回)fold.append(dataSet_copy.pop(index))  # 将随机选择的数据加入该份数据,并从数据集中移除dataSet_spilt.append(fold)  # 将该份数据添加到分割后数据集列表中return dataSet_spilt  # 返回分割后的数据集列表

构造数据子集

# 从原始数据集中随机抽取指定比例的样本,构建一个数据子集,
#且采用的是有放回抽样(即同一个样本可能被多次选中)
def get_subsample(dataSet, ratio):subdataSet = []  # 创建空列表用于存储子样本lenSubdata = round(len(dataSet) * ratio)  # 计算子样本的大小while len(subdataSet) < lenSubdata:  # 当子样本未达到应有大小时index = randrange(len(dataSet) - 1)  # 随机选择数据集中的索引(有放回)subdataSet.append(dataSet[index])  # 将随机选择的数据加入子样本return subdataSet  # 返回子样本

这是随机森林通过「有放回随机抽样」(randrange选索引,不删除原样本)抽取ratio*总样本数个样本,组成子训练集。

分割数据集

#根据指定特征索引和分割值,将数据集划分为两个子集,是决策树等算法中用于划分节点的核心操作
# 分割数据集
def data_spilt(dataSet, index, value):left = []  # 创建存储左子集的列表right = []  # 创建存储右子集的列表for row in dataSet:  # 遍历数据集中的每一行if row[index] < value:  # 如果行中指定索引的值小于给定值left.append(row)  # 将该行添加到左子集else:right.append(row)  # 否则将该行添加到右子集return left, right  # 返回左右的子集

数据集中一般行代表样本点,列代表特征,这里的index是确定比较的是哪一个特征。

损失函数

#计算数据集分割后的 “不纯度”(Impurity),
# 用于衡量分割的优劣。它实现的是基尼不纯度(Gini Impurity) 的计算逻辑,是分类问题中评估决策树分割质量的常用指标。
def spilt_loss(left, right, class_values):loss = 0.0  # 初始化分割代价为0for class_value in class_values:  # 遍历每个类别的取值left_size = len(left)  # 计算左子集的大小if left_size != 0:  # 防止除数为零prop = [row[-1] for row in left].count(class_value) / float(left_size)  # 计算左子集中该类别的占比loss += (prop * (1.0 - prop))  # 根据占比计算损失right_size = len(right)  # 计算右子集的大小if right_size != 0:  # 防止除数为零prop = [row[-1] for row in right].count(class_value) / float(right_size)  # 计算右子集中该类别的占比loss += (prop * (1.0 - prop))  # 根据占比计算损失return loss  # 返回分割代价

选取分割时的最优特征

def get_best_spilt(dataSet, n_features):features = []  # 用于存储随机选择的特征的列表class_values = list(set(row[-1] for row in dataSet))  # 获取数据集中的类别值b_index, b_value, b_loss, b_left, b_right = 999, 999, 999, None, None  # 初始化最佳分割的特征索引、值、损失和左右子集while len(features) < n_features:  # 当特征列表中的特征数量小于n_features时index = randrange(len(dataSet[0]) - 1)  # 随机选择一个特征索引if index not in features:  # 如果选择的特征索引不在特征列表中features.append(index)  # 将特征索引添加到特征列表中for index in features:  # 遍历随机选择的特征for row in dataSet:  # 遍历数据集中的每一行left, right = data_spilt(dataSet, index, row[index])  # 根据特征和特征值将数据集分割成左右子集loss = spilt_loss(left, right, class_values)  # 计算分割代价if loss < b_loss:  # 如果当前分割代价比最小代价还小b_index, b_value, b_loss, b_left, b_right = index, row[index], loss, left, right  # 更新最佳分割信息return {'index': b_index, 'value': b_value, 'left': b_left, 'right': b_right}  # 返回最佳分割特征的索引、值、左右子集

为什么要 “随机选 n_features 个特征”,而不是用所有特征?

这是随机森林的核心优化设计,目的是降低多棵树之间的 “相关性”,从而提升集成后的泛化能力,具体原因如下:

  1. 普通决策树的问题:若用所有特征选最优分裂,每棵树的分裂逻辑会高度依赖 “信息增益最大的特征”(比如某特征区分度极强,所有树的根节点都会用它分裂),导致多棵树的预测结果高度相似 —— 集成后相当于 “多棵相同的树投票”,无法降低过拟合。
  2. 随机选特征的作用
    • 强制每棵树的每个节点 “在不同的特征子集里找最优”,让不同树的分裂逻辑产生差异(比如树 1 的根节点用特征 5 分裂,树 2 的根节点用特征 12 分裂)。
    • 多棵 “差异化” 的树通过 “多数投票” 集成后,既能保留单棵树的拟合能力,又能抵消单棵树的过拟合风险(某棵树因噪声特征过拟合,其他树可通过不同特征纠正)。

确定最终的输出标签

#这个函数 decide_label 的作用是从输入数据中确定最终的输出标签,采用的是 “多数投票” 原则 —— 即选择数据中出现次数最多的标签作为结果
# 这在决策树等模型中常用于叶子节点的标签确定(当数据集无法再分割或达到停止条件时,用多数样本的标签作为该节点的预测结果)
def decide_label(data):output = [row[-1] for row in data]  # 获取数据中所有标签的列表return max(set(output), key=output.count)  # 返回出现次数最多的标签

注意:这里不是特征,是标签。

子分割,不断地构建叶节点的过程

def sub_spilt(root, n_features, max_depth, min_size, depth):left = root['left']right = root['right']del (root['left'])del (root['right'])if not left or not right:root['left'] = root['right'] = decide_label(left + right)  # 如果左节点或右节点为空,则将左右节点设为出现次数最多的标签returnif depth > max_depth:root['left'] = decide_label(left)  # 如果深度超过最大深度,则将左节点设为出现次数最多的标签root['right'] = decide_label(right)  # 将右节点设为出现次数最多的标签returnif len(left) < min_size:root['left'] = decide_label(left)  # 如果左节点数据量小于最小数据量,则将左节点设为出现次数最多的标签else:root['left'] = get_best_spilt(left, n_features)  # 否则,根据最佳分割点构建左节点sub_spilt(root['left'], n_features, max_depth, min_size, depth + 1)  # 递归构造左子树if len(right) < min_size:root['right'] = decide_label(right)  # 如果右节点数据量小于最小数据量,则将右节点设为出现次数最多的标签else:root['right'] = get_best_spilt(right, n_features)  # 否则,根据最佳分割点构建右节点sub_spilt(root['right'], n_features, max_depth, min_size, depth + 1)  # 递归构造右子树

构造决策树

def build_tree(dataSet, n_features, max_depth, min_size):root = get_best_spilt(dataSet, n_features)  # 找到最佳分割点作为根节点sub_spilt(root, n_features, max_depth, min_size, 1)  # 构建整棵决策树return root  # 返回根节点

预测

def predict(tree, row):predictions = []  # 初始化一个存储预测结果的列表if row[tree['index']] < tree['value']:  # 如果数据的某个特征值小于当前节点的划分值if isinstance(tree['left'], dict):  # 如果左子树仍然是一个字典(即非叶子节点)return predict(tree['left'], row)  # 递归地对左子树进行预测else:  # 如果左子树是叶子节点return tree['left']  # 返回左子树的预测结果else:  # 如果数据的某个特征值大于或等于当前节点的划分值if isinstance(tree['right'], dict):  # 如果右子树仍然是一个字典(即非叶子节点)return predict(tree['right'], row)  # 递归地对右子树进行预测else:  # 如果右子树是叶子节点return tree['right']  # 返回右子树的预测结果

得到最终的预测值

#集成学习中 Bagging( bootstrap aggregating)方法的预测函数,用于结合多棵决策树的预测结果,得到最终的预测值。
# 其核心思想是 **“多数投票”**—— 即综合多棵树的预测结果,选择出现次数最多的结果作为最终预测。
def bagging_predict(trees, row):predictions = [predict(tree, row) for tree in trees]  # 使用每棵树对数据进行预测,并将预测结果存储在列表中# 找到预测结果中出现次数最多的值作为最终预测结果return max(set(predictions), key=predictions.count)  # 找到预测结果中出现次数最多的值作为最终预测结果

创建随机森林

def random_forest(train, test, ratio, n_features, max_depth, min_size, n_trees):trees = []  # 用于存储每棵树的列表for i in range(n_trees):train = get_subsample(train, ratio)  # 从训练集中进行子采样tree = build_tree(train, n_features, max_depth, min_size)  # 构建一棵树print('tree %d: '%i,tree)trees.append(tree)  # 将构建好的树添加到列表中# predict_values = [predict(trees,row) for row in test]predict_values = [bagging_predict(trees, row) for row in test]  # 对测试集中的每一行数据进行预测return predict_values  # 返回预测结果列表

计算准确率

def accuracy(predict_values, actual):correct = 0  # 初始化正确预测的数量for i in range(len(actual)):  # 遍历每一个预测结果if actual[i] == predict_values[i]:  # 如果预测结果与实际结果相符correct += 1  # 正确预测数量加一return correct / float(len(actual))  # 返回正确预测的比例

主函数

if __name__ == '__main__':seed(1)  # 设置随机种子,以确保结果的可重复性dataSet = loadCSV('sonar-all-data.csv')  # 载入数据集column_to_float(dataSet)  # 将数据集中的数据类型转换为浮点型n_folds = 5  # 设置交叉验证的折数max_depth = 15  # 决策树的最大深度min_size = 1  # 叶子节点的最小样本数ratio = 1  # 子采样比例n_features = 15  # 特征数量n_trees = 20  # 随机森林中树的数量folds = spiltDataSet(dataSet, n_folds)  # 将数据集划分为指定折数的子集scores = []  # 用于存储每次交叉验证的准确率# 实现了k折交叉验证(k - foldcross - validation) 的核心逻辑,用于评估随机森林模型的性能。它将数据集分成多个子集(fold),# 每次用其中一个子集作为测试集,其余作为训练集,循环验证并计算模型准确率,最终得到多个准确率分数用于评估模型稳定性。for fold in folds:  # 对每个子集进行交叉验证train_set = folds[:]  # 复制数据集train_set.remove(fold)  # 从训练集中移除当前折的数据train_set = sum(train_set, [])  # 将多个fold列表组合成一个train_set列表test_set = [list(row)[:-1] for row in fold]  # 从当前折中提取测试数据actual = [row[-1] for row in fold]  # 获取当前折的实际结果predict_values = random_forest(train_set, test_set, ratio, n_features, max_depth, min_size,n_trees)  # 使用随机森林进行预测accur = accuracy(predict_values, actual)  # 计算准确率scores.append(accur)  # 将准确率添加到列表中print('Trees is %d' % n_trees)  # 打印树的数量print('scores:%s' % scores)  # 打印每次交叉验证的准确率print('mean score:%s' % (sum(scores) / float(len(scores))))  # 打印平均准确率

import csv
from random import seed
from random import randrange# 加载数据
def loadCSV(filename):  # 定义loadCSV函数,参数为filenamedataSet = []  # 创建空列表dataSetwith open(filename, 'r') as file:  # 用只读方式打开filename文件并赋值给filecsvReader = csv.reader(file)  # 用csv模块的reader方法读取file文件并赋值给csvReader(分割处理)for line in csvReader:  # 遍历csvReader中的每一行dataSet.append(line)  # 将每一行添加到dataSet列表中return dataSet  # 返回dataSet列表# 除了判别列,其他列都转换为float类型
def column_to_float(dataSet):  # 定义column_to_float函数,参数为dataSetfeatLen = len(dataSet[0]) - 1  # 获取dataSet中最后一列的索引for data in dataSet:  # 遍历dataSet中的每一行for column in range(featLen):  # 遍历每一行中除最后一列之外的每一列data[column] = float(data[column].strip())  # 将每一列的字符串转换为浮点数并赋值回原位置# 将数据集分成N块,方便交叉验证
def spiltDataSet(dataSet, n_folds):fold_size = int(len(dataSet) / n_folds)  # 计算每份数据的大小dataSet_copy = list(dataSet)  # 复制数据集dataSet_spilt = []  # 创建用于存储分割后数据集的列表for i in range(n_folds):  # 循环n_folds次,将数据集分割成n份fold = []  # 创建一个空列表用于存储每份数据while len(fold) < fold_size:  # 当该份数据未达到应有的大小时index = randrange(len(dataSet_copy) - 1)  # 随机选择数据集中的索引(有放回)fold.append(dataSet_copy.pop(index))  # 将随机选择的数据加入该份数据,并从数据集中移除dataSet_spilt.append(fold)  # 将该份数据添加到分割后数据集列表中return dataSet_spilt  # 返回分割后的数据集列表# 构造数据子集
# 从原始数据集中随机抽取指定比例的样本,构建一个数据子集,且采用的是有放回抽样(即同一个样本可能被多次选中)
def get_subsample(dataSet, ratio):subdataSet = []  # 创建空列表用于存储子样本lenSubdata = round(len(dataSet) * ratio)  # 计算子样本的大小while len(subdataSet) < lenSubdata:  # 当子样本未达到应有大小时index = randrange(len(dataSet) - 1)  # 随机选择数据集中的索引(有放回)subdataSet.append(dataSet[index])  # 将随机选择的数据加入子样本return subdataSet  # 返回子样本#根据指定特征索引和分割值,将数据集划分为两个子集,是决策树等算法中用于划分节点的核心操作
# 分割数据集
def data_spilt(dataSet, index, value):left = []  # 创建存储左子集的列表right = []  # 创建存储右子集的列表for row in dataSet:  # 遍历数据集中的每一行if row[index] < value:  # 如果行中指定索引的值小于给定值left.append(row)  # 将该行添加到左子集else:right.append(row)  # 否则将该行添加到右子集return left, right  # 返回左右的子集# 损失函数
#计算数据集分割后的 “不纯度”(Impurity),
# 用于衡量分割的优劣。它实现的是基尼不纯度(Gini Impurity) 的计算逻辑,是分类问题中评估决策树分割质量的常用指标。
def spilt_loss(left, right, class_values):loss = 0.0  # 初始化分割代价为0for class_value in class_values:  # 遍历每个类别的取值left_size = len(left)  # 计算左子集的大小if left_size != 0:  # 防止除数为零prop = [row[-1] for row in left].count(class_value) / float(left_size)  # 计算左子集中该类别的占比loss += (prop * (1.0 - prop))  # 根据占比计算损失right_size = len(right)  # 计算右子集的大小if right_size != 0:  # 防止除数为零prop = [row[-1] for row in right].count(class_value) / float(right_size)  # 计算右子集中该类别的占比loss += (prop * (1.0 - prop))  # 根据占比计算损失return loss  # 返回分割代价# 选取任意的n个特征,在这n个特征中,选取分割时的最优特征
#从随机选取的 n_features 个特征中,找到最佳的分割特征和分割值,是随机森林、决策树等树模型中构建节点的核心逻辑。
# 其核心目标是:在限定的特征范围内,选择能让数据集分割后 “纯度最高”(损失最小)的特征和阈值。
def get_best_spilt(dataSet, n_features):features = []  # 用于存储随机选择的特征的列表class_values = list(set(row[-1] for row in dataSet))  # 获取数据集中的类别值b_index, b_value, b_loss, b_left, b_right = 999, 999, 999, None, None  # 初始化最佳分割的特征索引、值、损失和左右子集while len(features) < n_features:  # 当特征列表中的特征数量小于n_features时index = randrange(len(dataSet[0]) - 1)  # 随机选择一个特征索引if index not in features:  # 如果选择的特征索引不在特征列表中features.append(index)  # 将特征索引添加到特征列表中for index in features:  # 遍历随机选择的特征for row in dataSet:  # 遍历数据集中的每一行left, right = data_spilt(dataSet, index, row[index])  # 根据特征和特征值将数据集分割成左右子集loss = spilt_loss(left, right, class_values)  # 计算分割代价if loss < b_loss:  # 如果当前分割代价比最小代价还小b_index, b_value, b_loss, b_left, b_right = index, row[index], loss, left, right  # 更新最佳分割信息return {'index': b_index, 'value': b_value, 'left': b_left, 'right': b_right}  # 返回最佳分割特征的索引、值、左右子集#这个函数 decide_label 的作用是从输入数据中确定最终的输出标签,采用的是 “多数投票” 原则 —— 即选择数据中出现次数最多的标签作为结果
# 这在决策树等模型中常用于叶子节点的标签确定(当数据集无法再分割或达到停止条件时,用多数样本的标签作为该节点的预测结果)
def decide_label(data):output = [row[-1] for row in data]  # 获取数据中所有标签的列表return max(set(output), key=output.count)  # 返回出现次数最多的标签# 子分割,不断地构建叶节点的过程
def sub_spilt(root, n_features, max_depth, min_size, depth):left = root['left']right = root['right']del (root['left'])del (root['right'])if not left or not right:root['left'] = root['right'] = decide_label(left + right)  # 如果左节点或右节点为空,则将左右节点设为出现次数最多的标签returnif depth > max_depth:root['left'] = decide_label(left)  # 如果深度超过最大深度,则将左节点设为出现次数最多的标签root['right'] = decide_label(right)  # 将右节点设为出现次数最多的标签returnif len(left) < min_size:root['left'] = decide_label(left)  # 如果左节点数据量小于最小数据量,则将左节点设为出现次数最多的标签else:root['left'] = get_best_spilt(left, n_features)  # 否则,根据最佳分割点构建左节点sub_spilt(root['left'], n_features, max_depth, min_size, depth + 1)  # 递归构造左子树if len(right) < min_size:root['right'] = decide_label(right)  # 如果右节点数据量小于最小数据量,则将右节点设为出现次数最多的标签else:root['right'] = get_best_spilt(right, n_features)  # 否则,根据最佳分割点构建右节点sub_spilt(root['right'], n_features, max_depth, min_size, depth + 1)  # 递归构造右子树# 构造决策树
def build_tree(dataSet, n_features, max_depth, min_size):root = get_best_spilt(dataSet, n_features)  # 找到最佳分割点作为根节点sub_spilt(root, n_features, max_depth, min_size, 1)  # 构建整棵决策树return root  # 返回根节点#预测
def predict(tree, row):predictions = []  # 初始化一个存储预测结果的列表if row[tree['index']] < tree['value']:  # 如果数据的某个特征值小于当前节点的划分值if isinstance(tree['left'], dict):  # 如果左子树仍然是一个字典(即非叶子节点)return predict(tree['left'], row)  # 递归地对左子树进行预测else:  # 如果左子树是叶子节点return tree['left']  # 返回左子树的预测结果else:  # 如果数据的某个特征值大于或等于当前节点的划分值if isinstance(tree['right'], dict):  # 如果右子树仍然是一个字典(即非叶子节点)return predict(tree['right'], row)  # 递归地对右子树进行预测else:  # 如果右子树是叶子节点return tree['right']  # 返回右子树的预测结果#集成学习中 Bagging( bootstrap aggregating)方法的预测函数,用于结合多棵决策树的预测结果,得到最终的预测值。
# 其核心思想是 **“多数投票”**—— 即综合多棵树的预测结果,选择出现次数最多的结果作为最终预测。
def bagging_predict(trees, row):predictions = [predict(tree, row) for tree in trees]  # 使用每棵树对数据进行预测,并将预测结果存储在列表中# 找到预测结果中出现次数最多的值作为最终预测结果return max(set(predictions), key=predictions.count)  # 找到预测结果中出现次数最多的值作为最终预测结果# 创建随机森林
def random_forest(train, test, ratio, n_features, max_depth, min_size, n_trees):trees = []  # 用于存储每棵树的列表for i in range(n_trees):train = get_subsample(train, ratio)  # 从训练集中进行子采样tree = build_tree(train, n_features, max_depth, min_size)  # 构建一棵树print('tree %d: '%i,tree)trees.append(tree)  # 将构建好的树添加到列表中# predict_values = [predict(trees,row) for row in test]predict_values = [bagging_predict(trees, row) for row in test]  # 对测试集中的每一行数据进行预测return predict_values  # 返回预测结果列表# 计算准确率
def accuracy(predict_values, actual):correct = 0  # 初始化正确预测的数量for i in range(len(actual)):  # 遍历每一个预测结果if actual[i] == predict_values[i]:  # 如果预测结果与实际结果相符correct += 1  # 正确预测数量加一return correct / float(len(actual))  # 返回正确预测的比例if __name__ == '__main__':seed(1)  # 设置随机种子,以确保结果的可重复性dataSet = loadCSV('sonar-all-data.csv')  # 载入数据集column_to_float(dataSet)  # 将数据集中的数据类型转换为浮点型n_folds = 5  # 设置交叉验证的折数max_depth = 15  # 决策树的最大深度min_size = 1  # 叶子节点的最小样本数ratio = 1  # 子采样比例n_features = 15  # 特征数量n_trees = 20  # 随机森林中树的数量folds = spiltDataSet(dataSet, n_folds)  # 将数据集划分为指定折数的子集scores = []  # 用于存储每次交叉验证的准确率# 实现了k折交叉验证(k - foldcross - validation) 的核心逻辑,用于评估随机森林模型的性能。它将数据集分成多个子集(fold),# 每次用其中一个子集作为测试集,其余作为训练集,循环验证并计算模型准确率,最终得到多个准确率分数用于评估模型稳定性。for fold in folds:  # 对每个子集进行交叉验证train_set = folds[:]  # 复制数据集train_set.remove(fold)  # 从训练集中移除当前折的数据train_set = sum(train_set, [])  # 将多个fold列表组合成一个train_set列表test_set = [list(row)[:-1] for row in fold]  # 从当前折中提取测试数据actual = [row[-1] for row in fold]  # 获取当前折的实际结果predict_values = random_forest(train_set, test_set, ratio, n_features, max_depth, min_size,n_trees)  # 使用随机森林进行预测accur = accuracy(predict_values, actual)  # 计算准确率scores.append(accur)  # 将准确率添加到列表中print('Trees is %d' % n_trees)  # 打印树的数量print('scores:%s' % scores)  # 打印每次交叉验证的准确率print('mean score:%s' % (sum(scores) / float(len(scores))))  # 打印平均准确率

http://www.xdnf.cn/news/19647.html

相关文章:

  • 中越跨境物流管理系统的设计与实现(原创)
  • DiffusionGPT-LLM驱动的文本生成图像系统
  • 【高等数学】第十章 重积分——第五节 含参变量的积分
  • 焦耳热技术助力顶刊研究:薄层质子交换膜实现高效水电解制氢
  • 【STM32】在链接脚本中指定DMA Buffer的地址
  • 智慧班牌系统基于Java+Vue技术栈构建,实现教育信息化综合管理。
  • shell脚本编辑(小白基础学习)
  • 从拿起简历(resume)重新找工作开始聊起
  • 【算法】算法题核心类别与通用解题思路
  • git基础命令
  • React中纯 localStorage 与 Context + useReducer + localStorage对比
  • HTML应用指南:利用GET请求获取MSN财经股价数据并可视化
  • IDEA Spring属性注解依赖注入的警告 Field injection is not recommended 异常解决方案
  • 【0426】insert into 内核实现之 找到 buffe, 插入 tuple (2)
  • YOLO 目标检测:YOLOv4数据增强、CIoU Loss、网络结构、CSP、SPPNet、FPN和PAN
  • 模型量化(Model Quantization) 和低精度计算(Low-Precision Computing)
  • 程序员与杀毒软件:一场不必要的“战争”?程序员用什么杀毒软件?-优雅草卓伊凡
  • pandas自学笔记16 pandas可视化
  • 2025年职场人士专业证书选择与分析
  • 免费GIS服务器方案:OGC标准3DTiles服务发布与跨平台渲染实践
  • word运行时错误‘53’,文件未找到:MathPage.WLL,更改加载项路径完美解决
  • 漏洞挖掘 渗透测试思路图总结
  • 洛谷 P1115 最大子段和
  • Onion-LO(已开源)——LIDAR里程计的统一框架
  • LeetCode 面试经典 150_滑动窗口_串联所有单词的子串(32_30_C++_困难)(滑动窗口:控制起点和滑动距离)
  • GPS:开启定位时代的科技魔杖
  • 四、操作系统
  • 松灵斯坦福Mobile ALOHA同款 | 通过低成本全身远程操作实现双手机器人移动操控学习
  • Docker的应用
  • 机器学习通关秘籍|Day 05:过拟合和欠拟合、正则化、岭回归、拉索回归、逻辑回归、Kmeans聚类