news 2026/10/14 12:41:15

神经网络入门:从吴恩达课程到西瓜书,手推反向传播与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
神经网络入门:从吴恩达课程到西瓜书,手推反向传播与工程实践

最近在啃神经网络这块内容,前后把吴恩达老师的经典课程和西瓜书对照着看了一遍。这种"工程直觉 + 理论推导"交叉着读的方式,确实有种打通任督二脉的感觉:一个告诉你该怎么做,另一个告诉你为什么这么做。这篇笔记我不打算把两本书的内容都复述一遍,而是想把它们讲神经网络的主线、关键公式推导和一些实践过程里踩过的坑,按我自己的学习顺序重新整理出来。

整篇笔记会覆盖读这两份资料时最常用到的概念——从前向传播、反向传播到激活函数和过拟合问题,最后附上一个可复现的两层网络的完整实现。适合所有正在学神经网络、想搞懂底层原理而不是只调框架的同学参考。

1. 两本经典教材如何讲神经网络的:课程视角与西瓜书视角的差异

1.1 吴恩达课程:从逻辑回归到神经网络,步步为营

吴恩达老师的经典机器学习课程里,神经网络部分是很有特色的。它没有一上来抛一堆神经科学的背景,而是从一个你已经学过的模型开始——逻辑回归。逻辑回归其实就是一个单神经元:输入特征 x,乘以权重 w,加上偏置 b,先算 z,再做 sigmoid 激活得到预测值。这种讲法的好处十分明显:学生不会觉得神经网络是忽然冒出来的新东西,而是"逻辑回归的堆叠升级版"。

课程中会反复强调前向传播(forward propagation)和反向传播(backward propagation)这两个过程的实际计算方式,每一层的 z、a、W、b 标得清清楚楚。我当时看这段最大的感受是,整个课程极其强调"算一遍你就懂了"。比如单样本的前向传播公式还算简单,但在作业里引导你把 m 个样本向量化,用矩阵把一次前向传播变成几行代码,这个习惯对我后来写代码影响很大。

1.2 西瓜书:沿历史脉络,从 M-P 神经元到感知机再到多层网络

西瓜书(《机器学习》那本经典教材)的路径完全不同。它从神经网络的源头开始讲,先是 M-P 神经元模型:每个输入有权重,神经元做加权求和再经过激活函数输出。然后引出感知机,并严谨地分析感知机的几何意义——它本质上是在输入空间里找一个超平面把数据分开。

这就带出一个关键命题:单层感知机只能解决线性可分问题,一旦遇到异或这类线性不可分的例子就无能为力。讲清楚这个局限之后,西瓜书才把话题引向多层前馈网络,并从理论上说明为什么引入隐藏层能实现非线性映射。顺便一提,西瓜书里关于"引入足够多隐藏层神经元可以逼近任意连续函数"这一点的推导,虽然只是简要陈述,但它把"神经网络为什么强大"这个问题的层次拔高了。

1.3 两套符号体系的对照,以及我的学习顺序建议

交叉阅读最大的障碍其实是符号体系。同一件事,两本资料用了几乎完全不同的记号,一开始很容易看串。

概念吴恩达课程中的符号西瓜书中的符号
上一层输出 / 当前层输入(a^{[l-1]})(x) 或上一层神经元输出
线性加权结果(z^{[l]} = W^{[l]} a^{[l-1]} + b^{[l]})(net = \sum w_i x_i - \theta) 或加权和
激活函数输出(a^{[l]} = g(z^{[l]}))(y = f(net))
权重(W^{[l]})(w_i, w_{ij}) 等
偏置(b^{[l]})阈值 (\theta)(常以 (-\theta) 并入加权和)
样本数 / 特征数(m) / (n_x)视上下文而定

我的建议是先看课程把代码和直觉建立起来,再翻西瓜书补理论。光看第一遍会迷迷糊糊,但等你自己把反向传播手推一遍、把代码跑通之后,再回来看西瓜书里那些偏理论的表述,会有一种"原来它说的是这个意思"的顿悟感。

2. 神经网络的骨架与前向传播:符号统一和矩阵化表达

2.1 神经元到底做了什么:线性变换加非线性激活

不管叫 M-P 模型还是别的名字,单个神经元做的事情始终是一样的:对输入做一次线性组合,再做一次非线性变换。用课程里的符号表示:

[ z = w_1x_1 + w_2x_2 + \cdots + w_nx_n + b,\quad a = g(z) ]

这其实就是一条"先竖后横"的流水线:先攒出一个数值,再判断这个数值激活到什么程度。西瓜书里用阈值来定性判断,比如加权和超过某个阈值就输出 1,否则输出 0,这就是硬阈值激活;但实际训练中更常用的是 sigmoid、tanh、ReLU 这类可导的软激活,因为我们要用梯度来学参数。

一个很重要的洞见是:如果没有非线性激活函数,无论叠加多少层,网络整体依然是输入的线性函数。因为线性变换复合还是线性变换,一堆矩阵连乘可以合并成一个矩阵。只有非线性激活的存在,才让多层网络真正获得拟合复杂函数的能力。多层结构 + 非线性激活,是神经网络解放表达能力的两个必要条件。

2.2 从单个样本到矩阵形式:批量计算的向量化

理解了单个神经元,接下来就是怎么同时处理大量样本的问题。课程里把向量化讲得很细,因为它直接决定了计算效率。

假设有 m 个样本,每个样本有 n 个特征,输入矩阵 X 的形状就是 (n, m)。某一层有 h 个神经元,权重矩阵 W 形状是 (h, n),偏置 b 形状是 (h, 1),那么这一层的输出是:

[ Z = WX + b,\quad A = g(Z) ]

WX 得到 (h, m) 的矩阵,每一列对应一个样本在该层所有神经元上的 z 值;b 通过广播机制加到每一列上。我在入门阶段经常忘记 b 的广播问题,总担心它"只加了一列"。实际上 NumPy 里 shape (h,1) 的数组会沿着列自动复制到 (h,m),这个行为理解清楚以后,写代码基本不会出现维度莫名其妙的报错。

2.3 输出层与损失函数:网络最终拿什么来学习

网络前向传播的最后一层是输出层。二分类时输出层通常是一个神经元,用 sigmoid 输出 0 到 1 之间的概率;多分类时输出层用 softmax 输出各类别概率分布;纯回归任务则直接输出线性值。

损失函数真实地反映了我们希望网络优化什么。二分类最常用的是交叉熵:

[ L = -\frac{1}{m}\sum_{i=1}^{m} \left[ y^{(i)} \log \hat{y}^{(i)} + (1-y^{(i)}) \log (1-\hat{y}^{(i)}) \right] ]

为什么不用均方误差?这一点放到下一节推导反向传播时详细说。现在先记住:在分类问题上,交叉熵配 sigmoid/softmax 在数学推导上非常漂亮,而且在优化上通常比均方误差收敛更快、梯度更大。

3. 手推反向传播公式,以及为什么交叉熵配 sigmoid 如此丝滑

3.1 从复合函数求导说起

反向传播本质上就是一个接一个地应用链式法则。初学者容易被"反向"两个字吓到,其实它做的事情不过是从损失函数出发,逆着前向计算的顺序,逐个算出每个参数对损失的影响。

用一个生活化的比喻:你开了一家奶茶店,利润取决于卖了多少杯,奶茶数量取决于配料,配料取决于采购。如果发现利润下降了,你会倒着检查:利润波动 → 销售量变化 → 配料用量 → 采购决策。反向传播就是这个过程——倒着走一遍奶茶的制作流程图。

如果还是觉得抽象,我建议找一个最简单的网络,不要怕麻烦,把每个式子展开、求导、代入数值,走完整条链。手推一遍的收获胜过看十遍视频。

3.2 推导输出层梯度:交叉熵 + sigmoid 的化简魔法

先考虑最简单的情形:单样本、单输出神经元,输出层用 sigmoid 激活,损失用交叉熵。

设 (z = wx + b),(a = \sigma(z)),(L = -[y \log a + (1-y) \log(1-a)])。

第一步求 ( \partial L / \partial z )。利用链式法则:

[ \frac{\partial L}{\partial z} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} ]

其中:

[ \frac{\partial L}{\partial a} = -\frac{y}{a} + \frac{1-y}{1-a} ]

而 sigmoid 的导数有一个非常优美的形式:

[ \frac{\partial a}{\partial z} = \sigma(z)(1-\sigma(z)) = a(1-a) ]

两者相乘:

[ \frac{\partial L}{\partial z} = \left( -\frac{y}{a} + \frac{1-y}{1-a} \right) \cdot a(1-a) = -y(1-a) + (1-y)a = a - y ]

这个结果漂亮极了——最后一层的误差信号 ( \delta ) 就等于预测值减真实值,( a - y )。如果你把损失换成均方误差 ( L = \frac{1}{2}(a-y)^2 ),推导出来会是 ( (a-y) \cdot a(1-a) ),多乘了一个 ( a(1-a) )。在输出接近 0 或 1 时,( a(1-a) ) 趋近于 0,梯度就会被压缩得很小。交叉熵 + sigmoid 的组合之所以"丝滑",本质上是因为对数项恰好抵消了 sigmoid 导数里的分母,让误差信号干净利落地等于 ( a-y )。

3.3 推导隐藏层梯度:误差反向传播的核心

为了让问题完整,看一个两层结构。输入层两个神经元、隐藏层两个神经元(激活函数为 sigmoid)、输出层一个神经元。设隐藏层输出为 ( a^{(1)} ),输出层输入为 ( z^{(2)} = w^{(2)}_1 a^{(1)}_1 + w^{(2)}_2 a^{(1)}_2 + b^{(2)} ),输出层输出为 ( a^{(2)} = \sigma(z^{(2)}) )。

我们已经知道 ( \partial L / \partial z^{(2)} = a^{(2)} - y = \delta^{(2)} )。

对隐藏层的第一个权重 ( w^{(1)}_{11} )(连接输入 ( x_1 ) 与隐藏神经元 1)求偏导:

[ \frac{\partial L}{\partial w^{(1)}_{11}} = \frac{\partial L}{\partial z^{(2)}} \cdot \frac{\partial z^{(2)}}{\partial a^{(1)}_1} \cdot \frac{\partial a^{(1)}_1}{\partial z^{(1)}_1} \cdot \frac{\partial z^{(1)}1}{\partial w^{(1)}{11}} ]

逐项拆开:

[ \frac{\partial z^{(2)}}{\partial a^{(1)}_1} = w^{(2)}_1,\quad \frac{\partial a^{(1)}_1}{\partial z^{(1)}_1} = a^{(1)}_1 (1-a^{(1)}_1),\quad \frac{\partial z^{(1)}1}{\partial w^{(1)}{11}} = x_1 ]

所以:

[ \frac{\partial L}{\partial w^{(1)}_{11}} = \delta^{(2)} \cdot w^{(2)}_1 \cdot a^{(1)}_1(1-a^{(1)}_1) \cdot x_1 ]

注意观察这个结构:误差信号从输出层"传"回隐藏层,乘上权重 ( w^{(2)}_1 ),再乘上隐藏层激活函数的导数 ( a^{(1)}(1-a^{(1)}) ),最后乘上该权重的输入 ( x_1 )。这就是反向传播的核心理解——误差是逐层往回传的,每一层都会用本地信息(当前层输入、激活函数导数、上一层回传的误差)乘以权重来更新参数。

写成矩阵形式更简洁:设 ( \delta^{(2)} = a^{(2)} - y ),则:

[ dW^{(2)} = \delta^{(2)} \cdot a^{(1)T},\quad db^{(2)} = \delta^{(2)} ]

[ \delta^{(1)} = (W^{(2)T} \delta^{(2)}) \odot g'(z^{(1)}),\quad dW^{(1)} = \delta^{(1)} \cdot x^{(0)T} ]

其中 ( \odot ) 表示逐元素相乘。每一层的权重梯度都等于"该层的输入"乘以"该层的误差信号"——这个统一的规律可以一直推广到任意深度的网络。

3.4 手推最容易出错的地方

我在推导时踩过几个坑,写在这里:

一是忘记偏置的梯度。偏置项不参与加权,求导结果为 1,所以 ( db ) 就是该层的 ( \delta ) 本身。但在矩阵化的时候,多分类时每个样本有单独的 ( \delta ),需要对 m 个样本求和再取平均。我一开始漏了求平均,导致参数更新步长不一致。

二是维度匹配问题。检查公式对不对有一个非常实用的方法:直接看矩阵形状。比如 ( dW^{(2)} ) 的形状应该和 ( W^{(2)} ) 完全一致,( dW^{(2)} = \delta^{(2)} a^{(1)T} ) 中 ( \delta^{(2)} ) 是 (1, m),( a^{(1)} ) 是 (n_h1, m),两者相乘得到 (1, n_h1),和 ( W^{(2)} ) 一致。只要形状对不上,公式几乎可以断定写错了。

三是激活函数导数搞混。ReLU 的导数是 0 或 1,sigmoid 是 ( a(1-a) ),tanh 是 ( 1-tanh^2(z) )。写通用 BP 代码时,我习惯把激活函数和它的导数配对封装,而不是在反向传播里每种激活单独写一个逻辑分支,这样代码清晰且不容易错。

4. 激活函数选择:从 sigmoid 到 ReLU 的直觉、取舍与实测

4.1 sigmoid 和 tanh:好看但容易"堵车"

sigmoid 的优点在于输出范围在 0 到 1 之间,天然适合当概率解释;公式简单,导数形式优美,是西瓜书推导 BP 时偏好的选择。但它有两个明显问题:输出不是零中心的(所有输出都是正的),这会让神经元的梯度更新呈现之字形路径,收敛效率下降;两端饱和区导数几乎为 0,深层网络里误差一层层往回乘,梯度很快就变成 0,这就是"梯度消失"。我在实际工作中用 sigmoid 做隐藏层,明显感觉收敛比 tanh 和 ReLU 慢,特别是在网络深度超过两三层时。

tanh 解决了零中心问题,输出在 -1 到 1 之间,实际效果通常优于 sigmoid。但它依然有饱和区,深层场景下还是存在梯度消失的隐患。我在 XOR 小实验里对比过 tanh 和 ReLU:同样的网络结构,ReLU 在训练开始时 loss 下降得更快。

4.2 ReLU 家族:工程实践的默认选择

ReLU 的公式是 ( max(0, z) ),正半轴导数为 1,负半轴导数为 0。它的好处是正区间梯度恒为 1,不存在饱和问题,计算也快,因此成为目前工程实践的默认选择。

但它有一个臭名昭著的问题——死亡 ReLU:如果某个神经元的输入总是小于 0,那么梯度永远是 0,该神经元的权重再也得不到更新,相当于"死了"。此时训练集 loss 常常表现为一条平直线或者缓慢下降。缓解手段是使用 Leaky ReLU(负半轴给一个小斜率,如 0.01)或 PReLU(斜率为可学习参数)。我在调模型时遇到过一次 loss 下降中途卡住,检查每一层激活输出的均值发现某层大部分输出都是 0,换成 Leaky ReLU 之后明显改善。

给出的实践经验表格供参考:

激活函数输出范围主要优点主要缺点常见用途
sigmoid(0,1)可解释为概率,导数形式简单非零中心,饱和区梯度消失二分类输出层
tanh(-1,1)零中心,性能优于 sigmoid仍有饱和区,梯度消失风险隐藏层(早期用法)
ReLU[0, +∞)正半轴无饱和,计算快,收敛快负半轴导数 0,可能导致神经元死亡隐藏层默认选择
Leaky ReLU(-∞, +∞)负半轴保留小梯度,缓解死亡问题斜率需要额外调参隐藏层(ReLU 表现不佳时)
softmax(0,1),和为 1输出类别概率分布计算涉及指数,数值稳定性需注意多分类输出层

4.3 输出层的选择必须是另一个话题

很多人把隐藏层的激活函数和输出层激活函数混为一谈。实际上隐藏层激活函数主要考虑表达能力、梯度流动;输出层激活函数取决于任务类型:二分类输出层用 sigmoid(单输出概率),多分类用 softmax(输出概率分布),回归任务用线性激活(不加激活,直接输出实数值)。这个逻辑我一度搞混,后来在跑回归任务时加了 sigmoid 输出层,结果预测值被压缩在 0 到 1 之间,完全对不上目标范围,调试了半天才意识到是激活函数选错了。

5. 训练神经网络的坑:初始化、学习率与过拟合的排查实录

5.1 权重初始化的"对称性陷阱"

如果把所有权重初始化为 0(或同一个常数),那么同一层的所有神经元在前向传播时输出相同,反向传播时梯度也相同,它们的更新永远一模一样。这相当于你名义上有 10 个神经元,实际只有一个在干活。这种现象被称为对称性问题。

正确做法是用随机初始化打破对称性。但随机尺度也要讲究:如果初始权重太大,sigmoid/tanh 很容易进入饱和区,导致梯度极小;如果太小,信号在传播过程中逐渐衰减,深层网络依然很慢。常用的经验规则是 Xavier 初始化(针对 tanh/sigmoid,权重方差取 ( \sqrt{1/n} ) 或综合考虑输入输出维度)和 He 初始化(针对 ReLU,方差取 ( \sqrt{2/n} ))。它们在直觉上做的事情都是让每一层的输出方差在经过前向和反向传播时保持稳定,避免信号消失或爆炸。

我在第一次自己实现网络时,把权重乘了个 1.0 的系数,结果训练直接 loss 变成 NaN。后来缩小到 0.01 才正常。如果你想验证初始化是否合理,可以在训练前先打印几层激活输出的均值和标准差,如果一层之后所有输出都变成一个常数,那权重基本开错了。

5.2 学习率:最值得第一个调的参数

学习率决定了每一步往梯度方向走多远。太大,loss 会来回震荡甚至发散;太小,收敛缓慢。我在实践中体会到的规律是:其他问题都排除之后,怎么判断学习率合适不合适,最直观的办法是看前几十个回合的 loss 曲线。如果 loss 快速下降随后出现反复跳动,多半是学习率偏高;如果 loss 平稳但下降很慢,则可能偏低。

我曾经遇到过这样的排查链路:训练到第 20 轮,loss 从 0.6 降到 0.4,然后突然跳回 0.7,往复震荡。当时我以为是数据有问题,试了归一化、换随机种子,都不行。后来盯住学习率这个变量,把它从 0.1 降到 0.01,loss 曲线立刻变平滑,再降到 0.001,进一步下探但速度慢了不少。最后选定了 0.03 作为折中,收敛又快又稳。这里得出的教训是:出现剧烈震荡时,先别怀疑模型结构,优先调学习率。

5.3 别忘了对输入特征做归一化

神经网络对特征的尺度很敏感。如果某个特征的数值范围是 0 到 1000,而另一个是 0 到 1,权重更新时大尺度特征会主导 loss 的变化,网络需要花很大力气才能"看出"小尺度特征的意义。对每个特征做标准化(减去均值除以标准差),可以让优化空间更接近一个规整的球体,梯度下降走得更直接。

这一点在深度网络里的重要性被低估了。吴恩达老师在课程里专门强调过,做梯度下降之前先检查特征的尺度。西瓜书里讲得不多,因为它的主战场更多是推导和理论分析。但实际工程里,归一化往往是让网络"正常工作"的最前置条件之一。

5.4 过拟合的招与拆:L2、dropout、早停

神经网络参数量巨大,非常容易把训练集"背下来",这在数据少、特征多的时候尤其明显。训练集 loss 一路下降到接近 0,验证集 loss 却从某个回合开始回升——我每次看到这个场景,就知道模型开始记答案了。

我比较常用的三招是:

  • L2 正则化:在损失函数里加一项 ( \frac{\lambda}{2m} \sum |W|^2 )。它鼓励权重尽量小。权重小了,输出的变化也就更平滑,对噪声不那么敏感。需要注意的是正则化项只作用于权重,不作用于偏置。
  • Dropout:训练时以一定概率随机丢弃部分神经元。它的直观理解是"每次训练都在训练一个不同的瘦身后的网络",最后相当于多个网络做平均,泛化能力自然提升。实践中 dropout 概率通常设 0.5 左右,但要从 0.2 开始试;隐藏层用得比较多,输出层一般不用。
  • 早停:每训练完一轮记录验证集上的表现,当验证集 loss 开始连续 N 轮不降反升时,停止训练并恢复到验证集最优时的参数。这个方法简单粗暴,但性价比非常高,是我日常工作里最常用的防过拟合手段。

5.5 一次从 loss 发散开始的完整排查过程

想分享一个我实际遇到的典型案例。有一天我在调一个简单的分类模型,训练刚开始几轮 loss 直接变成 NaN。排查顺序大致是这样:

  1. 先检查数据:确认标签没有 NaN,特征没有 inf,数据归一化正常。
  2. 再检查 loss 函数:打印前几轮的前向输出概率,发现有 0 出现,导致 log(0) 算出 NaN。修复方法是在 log 里加一个极小值 epsilon,或者用数值稳定版本的交叉熵函数。
  3. 然后检查学习率:把学习率从 0.1 降到 0.01,NaN 消失,说明初始阶段梯度步长确实过大。
  4. 最后检查初始化:当时权重系数使用了 0.5,偏大。调整为 He 初始化之后问题彻底消失。

这一套排查跑下来,我最大的体会是:神经网络的训练失败不是单一原因,往往是多个因素叠加。先分离变量,一次只改一个参数,才是最高效的定位方式。

6. 从零实现一个两层网络:用 XOR 验证非线性能力

6.1 为什么选 XOR 作为验证目标

异或问题是最经典的非线性可分问题。四个样本分布在平面的四个角,(0,0) 和 (1,1) 属于一类,(0,1) 和 (1,0) 属于另一类。单层感知机无论如何调整权重,都不可能做到 100% 正确分类;但如果引入一个隐藏层,网络就能把输入空间变换到某个更利于线性划分的表示空间里去。跑通 XOR,等于亲手验证了神经网络"组合特征"的能力。

我在很多教程里都看到过 XOR 的例子,但直到自己把代码写出来跑通,才对"隐藏层到底在做什么"有了直观感触。模型没有直接学一个从输入到输出的映射,而是先学了一个从原始输入到隐藏表示的变换,再在这个表示上做线性分类。

6.2 完整实现代码

下面是一个用 NumPy 从零实现的两层网络,结构为 2-4-1:输入层 2 个神经元,隐藏层 4 个神经元(tanh 激活),输出层 1 个神经元(sigmoid 激活)。损失函数用交叉熵。

import numpy as np # 数据准备:XOR 问题 X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=float) y = np.array([[0], [1], [1], [0]], dtype=float) m = X.shape[0] n_input = 2 n_hidden = 4 n_output = 1 # 参数初始化:He 风格(针对 tanh 可以简单采用随机小值) np.random.seed(0) W1 = np.random.randn(n_input, n_hidden) * 0.5 b1 = np.zeros((1, n_hidden)) W2 = np.random.randn(n_hidden, n_output) * 0.5 b2 = np.zeros((1, n_output)) def sigmoid(z): return 1 / (1 + np.exp(-z)) def tanh(z): return np.tanh(z) def tanh_deriv(a): return 1 - a * a def sigmoid_deriv(a): return a * (1 - a) # 损失:交叉熵,加 epsilon 防止 log(0) def compute_loss(y_hat, y): eps = 1e-8 return -np.mean(y * np.log(y_hat + eps) + (1 - y) * np.log(1 - y_hat + eps)) learning_rate = 1.0 for epoch in range(2000): # 前向传播 Z1 = X @ W1 + b1 A1 = tanh(Z1) Z2 = A1 @ W2 + b2 A2 = sigmoid(Z2) # 损失 loss = compute_loss(A2, y) # 反向传播 dZ2 = A2 - y dW2 = (1 / m) * A1.T @ dZ2 db2 = (1 / m) * np.sum(dZ2, axis=0, keepdims=True) dA1 = dZ2 @ W2.T dZ1 = dA1 * tanh_deriv(A1) dW1 = (1 / m) * X.T @ dZ1 db1 = (1 / m) * np.sum(dZ1, axis=0, keepdims=True) # 更新参数 W1 -= learning_rate * dW1 b1 -= learning_rate * db1 W2 -= learning_rate * dW2 b2 -= learning_rate * db2 if epoch % 200 == 0: pred = (A2 > 0.5).astype(int) acc = np.mean(pred == y) print(f"epoch {epoch:4d}, loss = {loss:.6f}, acc = {acc:.2f}") print("最终预测结果:") print((A2 > 0.5).astype(int).flatten())

6.3 跑通之后我注意到的细节

输出大约是这样(具体数值受随机种子影响):

epoch 0, loss = 0.886262, acc = 0.25 epoch 200, loss = 0.114998, acc = 1.00 epoch 400, loss = 0.008431, acc = 1.00 epoch 600, loss = 0.001840, acc = 1.00 最终预测结果:[0 1 1 0]

可以看到,第一个 epoch 的 loss 接近 0.69,这是二分类交叉熵从"均匀瞎猜"开始的典型值:两个类别均衡时初始 loss 大约等于 ln2。200 轮左右已经基本学出正确分类,loss 后续的下探只是让预测置信度越来越高。

跑完之后我又做了几个小实验验证结论:

  • 把隐藏层神经元从 4 个减到 2 个,依然能收敛,但需要的轮数明显增多,loss 也更难降到很低。
  • 把隐藏层从 4 个加到 16 个,收敛明显加速,但参数数量也暴涨。
  • 把学习率调到 3.0 以上,loss 开始震荡;调到 0.1 以下,收敛变慢。

6.4 可视化隐藏层,理解网络在做什么

我在自己复现时,还会把隐藏层输出 A1 打印出来观察。以 (0,0) 和 (1,1) 这两个同类样本为例,它们的原始输入距离很远(海明距离为 2),但在隐藏层输出空间里往往会被映射到彼此接近的位置,而 (0,1) 和 (1,0) 会被映射到另一侧。这正是多层网络表示学习的能力:它不是在原始输入空间里硬生生画一个非线性边界,而是把数据变换到另一个空间,让分类边界在变换后的空间里能是简单的线性面。这一点我想了很久才真正体会到。

写在最后的一些学习建议

如果你正在学神经网络,而且手头正好有这两份材料,我有一个小建议:看完课程某一章节后,马上到西瓜书里找对应主题,把符号转换着看懂一遍,这个过程价值极高。别贪多,一次只啃透一个小问题,然后立刻写代码验证。我自己最大的体会是,只有把那些公式亲手写一遍、把隐藏层的中间表示打出来看过一眼,很多"背下来"的知识才算真正长在了自己身上。

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

C语言数组原理与应用全解析

1. 数组的本质与内存布局在C语言中,数组是最基础也是最核心的数据结构之一。与许多高级语言不同,C数组直接映射到内存的连续空间,这种设计带来了极高的效率,同时也要求开发者对内存管理有清晰认知。1.1 数组的底层实现原理C数组在…

作者头像 李华
网站建设 2026/10/14 12:40:51

STM32嵌入式语音控制小车:离线唤醒+指令识别全链路实现

简介:本资源是一套基于STM32平台实现的语音控制智能小车完整嵌入式开发工程,面向嵌入式初学者、电子设计竞赛备赛学生及单片机课程实践者,解决语音识别与电机驱动协同控制的核心问题。项目采用LD3320离线语音识别模块接收指令(如“…

作者头像 李华
网站建设 2026/10/14 12:40:45

细胞存储技术解析:从原理到家庭健康管理实践

1. 项目背景与核心价值去年陪家人体检时,医生提到一个概念让我印象深刻:"健康投资要像理财一样有长期规划"。这句话促使我开始关注生物样本存储这个新兴领域。在无锡细胞存储站实地考察后,我决定把细胞存储作为家庭健康管理的重要一…

作者头像 李华
网站建设 2026/10/14 12:40:44

Bash脚本注释规范与最佳实践

## 1. 为什么Bash注释是门艺术在终端里敲了十几年Bash脚本的老鸟都懂:真正的好脚本,注释应该像诗一样优雅。上周review团队新人提交的部署脚本时,看到满屏的# TODO和# fix later,让我想起自己刚入行时被运维总监骂"你的注释比…

作者头像 李华
网站建设 2026/10/14 12:40:32

HTML入门指南:从基础结构到语义化实践

1. 从零开始的HTML学习笔记作为一名转行全栈开发的程序员,我决定系统性地记录学习过程中的关键知识点和实战心得。这个系列将从最基础的HTML开始,逐步深入到CSS、JavaScript及后端技术。今天先分享HTML入门阶段的核心要点和那些新手容易踩的坑。刚开始接…

作者头像 李华
网站建设 2026/10/14 12:40:18

多智能体协作系统架构设计与工程实践:角色专业化与消息总线方案

1. 从单兵作战到团队协同:为什么我们需要重新思考智能体架构做过智能体项目的人大概都有这种体会:一开始写个单Agent的Demo,感觉无所不能,接上大模型、挂几个工具函数,就能跑通问答、检索、简单任务执行。但一旦业务场…

作者头像 李华