news 2026/10/31 6:09:21

北邮自动化机器学习实验:决策树Python代码包与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
北邮自动化机器学习实验:决策树Python代码包与避坑指南

简介:这份资源是北京邮电大学自动化专业机器学习课程实验的配套代码,面向正在学习决策树算法、需要完成课程作业或希望动手实践分类模型的高年级本科生与自学者。实验以Python实现决策树为核心,覆盖数据加载与预处理、基于信息增益或基尼不纯度的特征选择、模型构建与训练、预测评估以及可视化与剪枝等完整流程,帮助读者把课堂理论落到可运行的代码上。资源包为7z压缩格式,内含1个py文件,整体约1KB,文件虽小但结构集中,便于直接阅读与调试。目前已有1055人学习下载,说明该实验在课程学习者中具有一定参考价值。通过这份代码,读者可以对照理解决策树从特征划分到剪枝优化的关键步骤,熟悉pandas与scikit-learn在分类任务中的配合使用,并借助评估指标与可视化手段检验模型效果,适合作为课程实验的起步模板或算法复习的实操素材。

1. 北邮自动化机器学习实验里的决策树:一份能直接跑通的 Python 代码包

如果你正在北邮自动化大三下学期修机器学习这门课,大概率会在某个周末对着 yhh 老师布置的实验要求发愁——决策树实验,要求用 Python 实现,还得跑出可解释的结果。这个 Ex2_DecTree 代码包就是为这个场景准备的:一个 7z 压缩包,里面是一个 Ex2_DecTree.py 脚本,覆盖了从数据加载、特征选择、建树、剪枝到可视化的完整链路。它解决的不是“决策树是什么”这种教科书问题,而是“实验报告怎么交、代码怎么跑、参数怎么调”的落地问题。适合两类人:一是正在赶这个实验的自动化专业学生,二是想拿一个结构清晰的决策树实现来对照自己代码的机器学习入门者。下面我按实际拆包和跑通的顺序,把这份资源讲透。

2. 拆开 Ex2_DecTree.py:从数据加载到建树的完整链路

2.1 先看代码结构,别急着跑

拿到 Ex2_DecTree.py 之后,第一件事不是双击运行,而是用编辑器打开通读一遍。常见的课程实验脚本结构大致是:导入区、数据加载函数、预处理函数、特征选择函数、递归建树函数、预测函数、评估与可视化入口。这个脚本大概率也是这个骨架,因为决策树实验的教学目标就是让你手写一遍 ID3 或 C4.5 的核心逻辑,而不是直接调 sklearn 的 DecisionTreeClassifier 一行了事。

我一般会先看导入区用了哪些库。如果看到 pandas、numpy、math 这几个,说明数据处理和熵计算是手写的;如果还看到 sklearn,那可能是用来做对比实验或者交叉验证的。graphviz 或 matplotlib 出现在导入区,说明有可视化环节。先把这些确认清楚,后面调参和排错才有方向。

提示:如果脚本里同时有手写建树和 sklearn 调用,注意区分哪部分是实验要求手写的,哪部分是对照组。实验报告里通常要求你解释手写部分的每一个公式。

2.2 数据加载与预处理:pandas 读进来之后先做三件事

课程实验的数据集一般是 CSV 格式,特征列加一个标签列。加载逻辑通常长这样:

import pandas as pd import numpy as np def load_data(filepath): # 读取CSV,注意编码,课程数据集常见gbk或utf-8 df = pd.read_csv(filepath, encoding='utf-8') # 打印前5行,确认列名和数据类型 print(df.head()) print(df.dtypes) # 检查缺失值 print(df.isnull().sum()) return df

这段代码的逻辑很直白:读文件、看结构、查缺失。参数上最容易被忽略的是 encoding,课程数据集如果是老师从 Excel 导出的,很可能是 gbk 编码,用 utf-8 读会直接报 UnicodeDecodeError。另一个坑是列名里带空格,后面按列名取数据时会 KeyError,所以 head() 打印出来之后要肉眼确认一遍列名。

预处理阶段常见做法是:把分类变量做标签编码或独热编码,把连续变量做分箱或者保留原值让树自己找分裂点。如果脚本里用了 pd.get_dummies,注意它会把一列拆成多列,特征维度变了之后,后面特征选择的循环要能适配。如果脚本里是手写的标签编码映射,检查映射字典是否覆盖了所有类别值,漏一个就会在预测时 KeyError。

2.3 特征选择:信息增益、增益率还是基尼指数

决策树的核心就在这一步。ID3 用信息增益,C4.5 用信息增益率,CART 用基尼指数。课程实验通常要求你至少实现一种,有的老师会要求两种做对比。信息增益的计算逻辑是:先算数据集本身的熵,再算按某个特征划分后的条件熵,两者相减。

def calc_entropy(labels): # labels是标签列的值列表 from collections import Counter counts = Counter(labels) total = len(labels) entropy = 0.0 for count in counts.values(): p = count / total entropy -= p * math.log2(p) return entropy def calc_info_gain(df, feature, label_col): # 划分前的熵 base_entropy = calc_entropy(df[label_col]) # 按feature的每个取值分组,算加权条件熵 values = df[feature].unique() cond_entropy = 0.0 for v in values: subset = df[df[feature] == v] weight = len(subset) / len(df) cond_entropy += weight * calc_entropy(subset[label_col]) return base_entropy - cond_entropy

参数说明:calc_entropy 里用 math.log2 而不是 log,是因为信息论里熵的单位是比特。如果数据集某个特征取值特别多(比如 ID 列),信息增益会偏高,这就是 ID3 偏向多值特征的缺陷,C4.5 用增益率来修正。如果你的实验要求实现 C4.5,增益率的公式是信息增益除以该特征本身的熵,分母可能为零,需要加一个极小值或者跳过该特征。

常见翻车点:Counter 统计标签时,如果标签列有 NaN,会被当成一个独立类别参与熵计算,结果看起来正常但实际是错的。所以预处理阶段必须把缺失值处理干净,要么删行要么填充。

2.4 递归建树与剪枝:递归终止条件写错就无限循环

建树函数是递归的,每个节点选当前最优特征,按取值分裂子集,然后对每个子集递归调用。终止条件一般有三个:子集标签全相同、特征用完了、子集为空。少写一个就可能死循环或者报错。

def build_tree(df, features, label_col): # 终止条件1:标签全相同 if len(df[label_col].unique()) == 1: return df[label_col].iloc[0] # 终止条件2:特征用完了,返回多数类 if len(features) == 0: return df[label_col].mode()[0] # 选最优特征 gains = {f: calc_info_gain(df, f, label_col) for f in features} best_feature = max(gains, key=gains.get) tree = {best_feature: {}} remaining_features = [f for f in features if f != best_feature] # 按最优特征的每个取值分裂 for v in df[best_feature].unique(): subset = df[df[best_feature] == v] if subset.empty: tree[best_feature][v] = df[label_col].mode()[0] else: tree[best_feature][v] = build_tree(subset, remaining_features, label_col) return tree

剪枝部分,课程实验常见的是预剪枝(限制最大深度或最小样本数)和后剪枝(用验证集自底向上裁剪)。如果脚本里用的是预剪枝,参数一般是 max_depth 和 min_samples_leaf,直接在建树递归里加判断就行。后剪枝实现起来复杂一些,需要先建完整棵树再遍历子树做替换判断。实验报告里如果要求对比剪枝前后准确率,记得留出一部分数据做验证集,别拿训练集自己评自己。

注意:递归建树时 remaining_features 的传递方式很关键。如果每层都传同一个列表的引用,修改会互相影响。用列表推导式生成新列表是安全的做法。

3. 跑通实验:环境配置、训练评估与可视化

3.1 Python 环境与依赖安装,版本别乱选

课程实验的代码通常是在 Python 3.8 到 3.10 之间写的,太新的版本可能有些库不兼容。我一般会建议用 conda 建一个独立环境,避免和系统 Python 打架。

conda create -n ml_exp python=3.9 conda activate ml_exp pip install pandas numpy scikit-learn matplotlib graphviz

参数说明:python=3.9 是稳妥选择,3.8 也行,3.11 以上有些老库的 wheel 可能没有。graphviz 这个包分两部分,pip 装的是 Python 接口,系统层面还需要装 Graphviz 软件本身,否则画图时会报 ExecutableNotFound。Windows 上要去官网下安装包,装完把 bin 目录加到 PATH;Linux 上直接 apt install graphviz 或 yum install graphviz。

如果实验要求用 sklearn 做对比,DecisionTreeClassifier 的 criterion 参数可以设 'entropy' 或 'gini',分别对应 ID3 和 CART 的划分标准。max_depth 控制树深,min_samples_split 控制节点分裂的最小样本数,这两个是预剪枝最常用的参数。

3.2 训练、预测与评估指标

手写建树完成后,预测函数就是沿着树往下走,到叶子节点返回类别。评估一般用准确率、精确率、召回率、F1 分数,sklearn 的 classification_report 一行就能出。

from sklearn.metrics import classification_report, accuracy_score def predict(tree, sample): # 沿树递归查找 if not isinstance(tree, dict): return tree feature = list(tree.keys())[0] value = sample[feature] subtree = tree[feature].get(value) if subtree is None: # 遇到训练集没见过的取值,返回一个默认类 return None return predict(subtree, sample) # 假设 X_test 是测试集特征,y_test 是真实标签 preds = [predict(tree, row) for _, row in X_test.iterrows()] print(classification_report(y_test, preds)) print("Accuracy:", accuracy_score(y_test, preds))

参数说明:predict 函数里对未见取值的处理是个关键点。训练集里某个特征没有取过某个值,测试集出现了,subtree 就是 None,这时候要么返回多数类,要么走一个默认分支。课程实验里如果没处理这种情况,测试时可能直接报错或者返回 None 导致评估指标计算失败。

评估阶段常见做法是留出法或 K 折交叉验证。留出法简单,70% 训练 30% 测试;交叉验证更稳,但手写建树跑 5 折可能比较慢。如果数据量不大(课程实验通常几百行),交叉验证完全跑得动。

3.3 决策树可视化:graphviz 出图与中文乱码

可视化是实验报告里加分项,也是理解模型决策逻辑最直观的方式。手写树的可视化需要自己写递归函数导出 DOT 格式,sklearn 的树可以用 export_graphviz 直接导出。

from sklearn.tree import export_graphviz import graphviz # 假设 clf 是训练好的 DecisionTreeClassifier export_graphviz(clf, out_file='tree.dot', feature_names=feature_names, class_names=class_names, filled=True, rounded=True) with open('tree.dot') as f: dot_graph = f.read() graphviz.Source(dot_graph).render('tree', format='png', cleanup=True)

参数说明:filled=True 让节点按类别填充颜色,rounded=True 圆角边框,这两个参数让图更易读。feature_names 和 class_names 要按实际数据传入,否则图上显示的是 feature_0、class_0 这种。中文乱码是高频问题,graphviz 默认字体不支持中文,需要在 DOT 文件里指定 fontname="SimHei" 或 "Microsoft YaHei",Linux 下可能要装中文字体并指定路径。

提示:如果 render 报错说找不到 dot 命令,说明系统层面的 Graphviz 没装好或者 PATH 没配。先在命令行跑 dot -V 确认版本能打印出来。

3.4 剪枝参数怎么调:从过拟合到泛化

决策树不剪枝几乎必然过拟合,训练集准确率能到 100%,测试集可能只有 60%。预剪枝的参数调整是个经验活:

参数作用典型取值范围调大后果
max_depth限制树的最大深度3~10太浅欠拟合,太深过拟合
min_samples_split节点分裂所需最小样本数2~20太大树长不起来
min_samples_leaf叶子节点最小样本数1~10太大叶子太少
max_features每次分裂考虑的最大特征数sqrt/None限制后增加随机性

我一般会先设 max_depth=5 跑一遍看准确率,然后逐步加到 8 或 10,观察测试集准确率的变化曲线。如果训练集和测试集准确率差距在 5% 以内,说明当前深度基本合适。后剪枝的话,用验证集自底向上遍历,如果剪掉某棵子树后验证集准确率不降反升,就剪掉。

血泪经验:调参时不要盯着测试集反复调,那样测试集就变成了验证集,最终报告里的泛化性能是虚的。正确做法是训练集切一部分出来做验证集,调参在验证集上做,测试集只在最后评一次。

4. 避坑与排查:课程实验里最容易翻车的五个点

4.1 报错 UnicodeDecodeError:编码不对

现象:pd.read_csv 直接抛 UnicodeDecodeError,提示某字节无法解码。 原因:课程数据集从 Excel 导出时默认 gbk 编码,而代码里写的是 utf-8。 解决:先试 encoding='gbk',如果还不行就试 'gb18030',这个兼容性更好。或者用记事本打开 CSV 另存为 UTF-8 格式。

4.2 建树递归深度超限:RecursionError

现象:RecursionError: maximum recursion depth exceeded。 原因:递归终止条件没写全,或者特征取值太多导致树极深。 解决:先检查终止条件是否覆盖了标签全同、特征用完、子集为空三种情况。如果确认逻辑没问题,可以在脚本开头加 sys.setrecursionlimit(10000),但这只是治标,根本原因可能是某个特征取值过多(比如 ID 列),需要在预处理阶段把这类列删掉。

4.3 预测时 KeyError:测试集出现训练集没见过的特征取值

现象:predict 函数里 tree[feature][value] 报 KeyError。 原因:训练集某个特征没有取过某个值,测试集出现了。 解决:在 predict 里用 .get(value) 代替直接索引,取不到时返回多数类或 None,然后在评估前把 None 预测值替换成多数类。

4.4 graphviz 画图中文变方块

现象:生成的决策树图里中文标签显示为方块或乱码。 原因:graphviz 默认字体不含中文字形。 解决:在 export_graphviz 里加 fontname="SimHei"(Windows)或 fontname="WenQuanYi Micro Hei"(Linux),同时确认系统里确实装了这个字体。如果还不行,把 feature_names 和 class_names 临时换成英文再画图。

4.5 准确率异常高或异常低

现象:训练集准确率 100%,测试集 50%;或者两者都低得离谱。 原因:前者是过拟合,树太深或没剪枝;后者可能是标签编码错了,比如把类别标签当成了连续值,或者数据划分时没打乱。 解决:过拟合就加 max_depth 或 min_samples_leaf;准确率整体低就先检查标签列的数据类型和取值分布,用 df[label_col].value_counts() 看一眼,确认没有异常值混入。

5. 从实验代码到可复用的决策树模块:我的封装习惯

把 Ex2_DecTree.py 跑通之后,如果只是交完实验就扔,其实挺可惜的。我一般会做一步封装:把数据加载、特征选择、建树、预测、评估拆成独立函数,用一个 main 函数串起来,这样下次遇到类似分类任务可以直接改数据集路径和参数就能跑。

具体做法是定义一个 DecisionTree 类,fit 方法里做建树,predict 方法里做递归查找,score 方法里算准确率。参数方面,把 criterion('info_gain' 或 'gini')、max_depth、min_samples_leaf 作为构造参数传入。这样封装之后,和 sklearn 的接口风格一致,切换对比也方便。

class MyDecisionTree: def __init__(self, criterion='info_gain', max_depth=None, min_samples_leaf=1): self.criterion = criterion self.max_depth = max_depth self.min_samples_leaf = min_samples_leaf self.tree = None def fit(self, df, label_col): features = [c for c in df.columns if c != label_col] self.tree = self._build(df, features, label_col, depth=0) def _build(self, df, features, label_col, depth): # 终止条件里加入max_depth和min_samples_leaf判断 if len(df[label_col].unique()) == 1: return df[label_col].iloc[0] if not features or (self.max_depth and depth >= self.max_depth): return df[label_col].mode()[0] if len(df) < self.min_samples_leaf: return df[label_col].mode()[0] # 后续选特征、分裂逻辑同上 ...

验证封装是否正确,我习惯用鸢尾花数据集跑一遍,因为它的特征都是连续值,需要先做分箱或者用阈值分裂,能顺带测出代码对连续特征的处理能力。如果鸢尾花上准确率能到 90% 以上,说明建树和预测逻辑没问题。然后再换回课程数据集,对比手写实现和 sklearn 的结果,如果准确率差距在 2% 以内,基本可以确认实现是对的。

从那以后我每次做完课程实验,都会把核心代码抽成一个独立模块,加一个 ifname== 'main' 的入口,下次遇到同类问题直接 import 就能用。这个习惯帮我省了不少重复劳动,也让实验报告里的代码看起来更工程化。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 6:09:07

5G Polar码MATLAB仿真:SCL译码与CRC辅助实战

简介&#xff1a;这份资源是面向通信工程学习者与科研人员的Polar码MATLAB仿真实现&#xff0c;聚焦5G控制信道编码中广泛应用的极化码原理与SCL解码算法。包内共16个文件&#xff0c;全部为.m脚本&#xff0c;压缩包约13KB&#xff0c;涵盖编码器构造、信道传输模拟、似然率计…

作者头像 李华
网站建设 2026/10/31 6:08:31

IP地址与子网划分:原理、计算与Ubuntu配置实战

1. IP地址基础与子网划分核心原理在计算机网络中&#xff0c;IP地址就像现实世界中的门牌号码&#xff0c;是设备在网络中的唯一标识。当前主流的IPv4地址由32位二进制数组成&#xff0c;通常以点分十进制表示&#xff08;如192.168.1.1&#xff09;。理解IP地址的结构是网络配…

作者头像 李华
网站建设 2026/10/31 6:08:22

Ponytail插件:数据属性驱动的页面底部交互轻量化方案

1. 先搞明白 ponytail 到底是什么1.1 这个名字起得相当形象听到“ponytail”这个词&#xff0c;很多人第一反应是马尾辫。我第一次接触这个插件时也觉得奇怪&#xff0c;一个前端工具怎么起了这么个名字&#xff1f;用了一段时间才反应过来&#xff0c;这名字其实点中了它的核心…

作者头像 李华
网站建设 2026/10/31 6:07:36

SSM+微信小程序设备故障报修系统毕设项目拆解与部署指南

简介&#xff1a;本资源为基于SSM框架与微信小程序的设备故障报修管理系统毕业设计完整资料包&#xff0c;面向计算机相关专业需要完成毕业设计或课程设计的学生。项目已通过导师指导与答辩评审&#xff0c;获得97分高分评价&#xff0c;在Windows 10/11环境下严格调试&#xf…

作者头像 李华
网站建设 2026/10/31 6:06:48

AP微积分FRQ评分逻辑与答题模板:从会算到会写

每年五月考季一过&#xff0c;留言区里最容易刷屏的一类话就是&#xff1a;选择题对完答案心里还算有底&#xff0c;可一到自由作答题&#xff08;Free Response Questions&#xff0c;简称 FRQ&#xff09;&#xff0c;写完自我感觉良好&#xff0c;出分却莫名其妙少了一截。A…

作者头像 李华
网站建设 2026/10/31 6:06:35

生产级智能体:平台边界与运维治理的8大坑

智能体这东西&#xff0c;Demo 阶段谁都能做&#xff0c;生产级才是照妖镜。上篇写了8个坑里的前四个&#xff0c;从架构选型聊到工具调用&#xff1b;这篇把剩下的坑补完&#xff0c;重心放在平台边界和运维治理。平台边界解决的是“这个智能体到底该建在哪个平台上、哪些事该…

作者头像 李华