七月的精读营打卡走到第三个Task,这篇记录拖了两天才写完,主要是这两天一直在补作业:把决策树和神经网络的公式推导重新过了一遍,又把示例代码从头跑了几轮。前两个Task的内容还算温和,绪论和线性模型主要靠理解概念就能应付,等到Task03进入决策树和神经网络,群里明显热闹了很多,不少人在问信息增益怎么算、BP算法的链式法则怎么展开。这篇打卡我不打算写成知识点罗列,而是把从"看书懵"到"跑通实验"的完整路径梳理出来,给同期打卡和准备自学西瓜书的同学一份更直接的参考。
先交代结论:Task03对应西瓜书第4章决策树和第5章神经网络,内容包括三个纯度指标、剪枝策略、连续值与缺失值处理,以及从感知机到多层网络的演进、BP算法的推导和实操调参。如果之前对线性模型还有模糊的地方,建议先回头把梯度下降和矩阵求导补齐,因为这两个能力在BP推导中会被反复使用。下面按我的学习顺序展开。
1. 这次Task03到底要啃多少东西
1.1 任务范围与前两次学习内容的衔接
学习营的整体进度是跟着西瓜书章节走的,前两次任务覆盖了绪论、模型评估与选择、线性模型,到了Task03突然进入两个大块头。决策树看起来门槛不高,因为不涉及复杂的数学推导,但真正动手写代码、调剪枝参数时才发现细节非常多;神经网络更是公认的难点,尤其是误差逆传播算法的推导,稍不留神就会把符号搞混。
我自己把Task03拆成了三层来消化。第一层是概念层,搞清楚决策树如何递归划分、神经元如何堆叠成网络;第二层是公式层,手推信息增益、增益率、基尼指数以及BP的权重更新表达式;第三层是实践层,用代码在数据集上复现两个模型,观察参数变化对结果的影响。前两层没有做好就急着写代码,很容易出现"代码能跑但完全不知道为什么"的情况。
1.2 打卡机制与一周时间安排
Task03的打卡方式比较统一:在规定时间内完成学习笔记并提交,搭配每周的讨论和互相评审。这里的关键不是提交本身,而是笔记里要有自己的推导痕迹和实验复盘,单纯抄PPT或贴代码没有太大意义。为了不让打卡变成拖延工程,我把一周时间做了切块:
| 时间 | 内容 | 预期产出 |
|---|---|---|
| 周一 | 通读决策树章节,梳理基本流程和纯度概念 | 一页纸的知识框架 |
| 周二 | 手推信息增益、增益率、基尼指数三个指标 | 完整推导笔记 |
| 周三 | 决策树代码实验,观察剪枝对结果的影响 | 实验记录与可视化 |
| 周四 | 通读神经网络章节,理解感知机局限和后继网络结构 | 概念清单 |
| 周五 | 手推BP算法中输出层和隐层的权重更新公式 | 推导笔记 |
| 周六 | 神经网络代码实验,对比不同学习率和隐藏层节点数 | 实验记录 |
| 周日 | 整合内容、处理答疑、写正式打卡笔记 | 最终提交稿 |
前三个Task的经验让我确认了一件事:光看推导视频而不动手算,理解深度远远不够。视频里老师每一步都清楚,自己拿起笔推导时,链式法则中该对哪个变量求导、误差项怎么从输出层传回隐层,这些问题全部暴露出来。这也是我刻意把推导时间排在代码之前的原因。
2. 决策树的核心:三个纯度指标与剪枝的实操权衡
2.1 信息增益、增益率、基尼指数分别解决了什么问题
决策树的本质是递归地选属性、切数据,选属性的依据是"划分之后子集更纯"。所谓纯度,可以理解成一个盒子里装的样本是否属于同一类别:全是同一类就是最纯,各种类别均匀混合就是最不纯。衡量不纯度的工作,就交给了信息熵和基尼指数。
信息熵的定义是从通信理论借来的,表达式是Ent(D) = -Σ p_k * log2(p_k),p_k是第k类样本所占比例。熵越大,表示不确定性越大,样本纯度越低。选属性时计算信息增益Gain(D,a) = Ent(D) - Σ (|Dv|/|D|) * Ent(Dv),它表达的是"划分前后的熵差",差值越大说明这个属性带来的纯度提升越明显,于是优先选它。这是ID3决策树的做法。
但ID3有一个实际使用中很容易踩的坑:信息增益偏爱取值多的属性。例如把样本身份证号当属性,每个取值只有一个样本,划分后每个子集熵为0,信息增益直接拉满。可这样的属性完全没有泛化能力。C4.5的解决方案是增益率,在信息增益基础上除以属性的固有值IV(a) = - Σ (|Dv|/|D|) * log2(|Dv|/|D|),取值多的属性固有值也大,相当于加了一个惩罚项,从而平衡偏好。
CART树则换了一套逻辑,用基尼指数代替熵。基尼值Gini(D) = 1 - Σ p_k^2同样衡量纯度,取值越小越纯。选属性时遍历所有候选划分点,计算基尼指数最小的切分方式。比熵的好处是省去了对数运算,计算更快,工业界的默认实现通常就是它。
2.2 剪枝:为什么预剪枝容易欠拟合、后剪枝更稳但更慢
决策树如果不加限制,会把训练样本的每个细节都记住,最终长出一棵又深又碎的树,训练集准确率接近100%,测试集却惨不忍睹。剪枝是抑制这种过拟合的主要手段,分为预剪枝和后剪枝。
预剪枝是在构建过程中提前判断:如果这个节点划分不能带来验证集准确率提升,就停止分裂,把当前节点标为叶子。优点很明显,计算开销小、训练快;缺点同样真实,很多情况下某个属性单独看提升不明显,但组合起来能产生好效果,预剪枝容易在早期就砍掉这些潜在的好分支,导致欠拟合。我实验时把预剪枝参数设得很严格,训练集和测试集准确率一起下降,就是典型的过早在源头截断。
后剪枝则是先把树完整长出来,然后自底向上考察非叶节点,把那些替换成叶子后验证集准确率不降(甚至提升)的节点剪掉。这种方式更稳妥,泛化能力通常更强,但代价是需要先构建完整树,再逐个节点评估,训练时间明显变长。实际工程里常用的一种折中是代价复杂度剪枝,通过设定惩罚系数来控制树的复杂度,在节点数和错误率之间找平衡。
2.3 连续值和缺失值处理,看起来是细节其实很影响结果
西瓜书里连续值处理的方法是二分法:把属性取值排序,取相邻值的均值作为候选划分点,然后像离散属性一样计算信息增益,选出最优划分点。需要注意这个划分点只参考当前节点的数据,同一属性在不同节点可能被切在不同的值上。我第一次做实验时以为全局共享一个切分点就够了,结果复现出来的树结构跟标准树差别很大,原因是全局切分没有结合当前节点的样本分布,自然会丢失很多信息。
缺失值处理更是决策树工程落地中绕不开的问题。实际数据几乎不可能干干净净,缺失值如果直接丢弃,样本利用率会很低。西瓜书给出的思路是用带权样本:初始给每个样本权重1,选择划分属性时,只计算属性值未缺失的样本子集,然后按比例放大到全体样本上;属性确定之后,缺失样本按不同分支的权重比例分到多个子节点中。这种做法本质上是在"用分布猜测缺失值",工程实践里简单有效。
这里分享一个我实验中得到的小经验:当数据缺失比例不大时,常用机器学习库的默认策略通常足够用;但缺失比例超过30%,就要认真考虑是填充还是带权划分,否则树的稳定性会很差。我试过直接丢弃缺失行,训练集和测试集上结果波动很大,后来改成带权处理才稳定下来。
3. 神经网络与BP推导:让人崩溃的其实是约定和链式法则
3.1 感知机的局限:为什么一个经典例子就能击穿它
神经网络章节从M-P神经元讲起,神经元接收输入信号,加权求和后通过一个激活函数输出。单个感知机能处理线性可分问题,却无法解决异或(XOR)问题。异或本身是个很简单的逻辑关系:两个输入相同时输出0,不同时输出1。但把四种输入组合画在平面上,"同"和"异"两类点无法用一条直线分开。这个例子很直观地说明,单层网络的表达能力有硬边界,必须引入隐藏层。
理解这一层之后,再看多层前馈网络就不难了:输入层接收数据,隐藏层做特征变换,输出层产生结果,层与层之间全连接。理论上有足够多神经元的单隐层网络已经可以逼近任意连续函数,但"能逼近"和"能训练出来"是两回事,如何更新连接权重才是真正困难的部分,这就引出BP算法。
3.2 BP推导的关键步骤和最容易搞混的符号
BP的核心思想是反向传播误差:从输出层开始,计算损失对输出层权重的梯度,再借助链式法则把误差逐层传回隐藏层,最后用梯度下降更新所有权重。我在推导时最容易搞混的是各个符号的上下标:哪一层记为j,哪一层记为k,权重是w_jk还是w_ij,不同教程约定完全不同。只要错一个下标,整个更新公式就对不上,这是大家卡住的常见原因。
我的做法是先固定一套自己的符号表,再开始推导。比如把输入层到隐层的权重记为v,隐层到输出层的权重记为w;隐层输出记为b,输出层输入记为β。输出层第k个神经元的误差项是损失对净输入的偏导,记为g_k;隐层第h个神经元的误差项记为e_h。推导出核心更新式后,再检查每一项的量纲,确保误差项、输入和权重参数在维度上能对上。
从实际体验看,输出层误差项的计算很直接,麻烦的是隐层误差项,它需要把输出层所有神经元的误差按权重加权求和,再乘上隐层激活函数的导数。这里有一个很容易写错的点:隐层激活函数的导数,要代入的是隐层神经元的净输入,而不是它的输出值。我第一遍推导时就写反了,结果更新公式看起来很像,但数值对不上,训练损失怎么调都不收敛。
3.3 全局最小与局部最小:为什么训练结果总是不稳定
误差函数在高维空间里通常有很多局部极小点,梯度下降一旦陷入其中一个,就无法继续下降。神经网络训练的随机性很大一部分来自这里:不同初始权重、不同数据顺序、不同学习率,可能收敛到不同的局部极小值。理论上有一些策略,比如模拟退火、随机梯度下降的随机性、多次随机初始化等,但在工程实践里更常见的态度是接受一个误差可接受的局部最小值。
在打卡群里,有同学问为什么每次跑出来的准确率都不一样,这就是原因。我的建议是:记录固定随机种子保证实验可复现,同时用多次初始化的方式选一个效果最好的模型,再深入分析,而不是执念于找到全局最优。深度学习框架里很多参数默认值也未必适合任务,多试几组隐藏层尺寸和激活函数组合,比反复精调学习率更能提升上限。
3.4 神经网络过拟合:提前停下来比追求低训练误差更重要
神经网络参数数量大,拟合能力强,在小数据集上几乎必定过拟合。实验里最直观的现象是训练损失一路下降,验证损失先降后升,这就是拟合过了头。应对策略很朴素:早停法,在验证损失开始回升时停止训练并恢复最佳参数;或者用正则化,给权重加惩罚项。
我用实验数据试过把迭代次数设得很大,训练集最终准确率几乎100%,但测试集的表现一路走低。改成早停之后,测试集准确率反而更高。这个现象值得在打卡笔记里重点记录,因为它说明了模型训练的核心不是一味降低训练误差,而是保证在未见数据上的表现。
4. 实践环节:决策树和BP模型从跑通到合理
4.1 决策树实验:剪枝策略对结果的影响最直观
实验用的是一个公开的三分类数据,特征维度不算高,样本量也比较适中。先按7:3划分训练集和测试集,保证随机种子一致,然后训练两棵决策树:一棵不设限制,一棵限制最大深度。不设限制的树可以长到很深,在训练集上表现很好,测试集准确率反而低;限制最大深度的树测试表现明显更好。把树的深度画出来后,能清楚看到过拟合的树结构非常复杂,很多叶子节点只覆盖了一两个样本。
在具体调参上,我建议先调整max_depth,再调最小叶子样本数,最后看特征重要性。不要上来就写交叉验证网格搜索,因为决策树对很多超参并不敏感,手动跑几组就能看出趋势,省下来的时间可以做更多分析。
4.2 BP模型实验:标准化、学习率和网络规模如何配合
神经网络的代码实验我单独做了一份。先把数据标准化,因为神经元的激活函数对输入尺度敏感,特征数值范围差异大会让训练变得不稳定。然后固定一个小网络,比如单隐层、16个神经元,分别用大学习率和小学习率训练,对比损失曲线。学习率太大时损失会震荡不下降,太小时收敛很慢,找一个相对适中的值再继续调网络结构,这是效率最高的套路。
我这次踩的最明显的一个坑是忘记打乱数据顺序,按原始顺序切分后,训练集和测试集的类别分布不一致,导致验证结果忽高忽低。后来把数据洗牌并固定种子,结果就稳定下来。这类问题很隐蔽,因为代码不报错、模型也能训练,只有反复看验证曲线才能发现异常。
4.3 打卡笔记组织:把推导错误和实验复盘点写进去
这类学习营的打点不在于字多,而在于证据完整。我的打卡笔记结构是:任务目标、公式推导、实验设计与结果截图、问题记录、下一步计划。推导部分不抄书本原式,而是用自己的符号重新推导一遍,并标注与书本符号的对应关系。问题记录部分最重要,我会把实验过程中的错误判断和修复方式写清楚,比如"第一次因为没标准化导致损失不降,标准化后问题消失"。这类记录对后来者最有用。
写笔记时还要附上复现方式:数据来源、随机种子、关键参数、运行环境。不要只贴一段结果图,没人能脑中复现。能够被别人按步骤跑出相似结果,才是一份合格的打卡笔记。
最后分享两点这次Task03留下的体会。第一,公式推导一定要自己动笔,哪怕推错也比看会强十倍,我的错误索引表里记了三个典型的链式法则错误,后来做其他模型时立刻就能避开。第二,实验记录尽量当天同步整理,不要攒到打卡前才补,否则很容易把不同参数下的结果混在一起。决策树和神经网络这组内容如果能踏踏实实啃完,后面再接触集成学习和更深的网络时会省很多力气,前者会反复用到。