从LeNet看sigmoid与softmax的分工:中间层与输出层的激活函数选择
我在不少入门讨论区里见过同一个问题sigmoid 和 softmax 长得那么像为什么中间层用的是 sigmoid输出层却是 softmax直接拿 sigmoid 当输出层行不行这个问题如果只看结论你可能会背出“sigmoid 用于二分类softmax 用于多分类”但真的从 MLP 一路看到 LeNet 时你会发现事情远没有这么简单LeNet 的卷积中间层明明在用 sigmoid 做非线性输出层却要换成 softmax两者到底分工在哪里这篇文章就围绕这个“换人”的过程展开。我会从 MLP 的结构缺陷讲起把 LeNet 当作一个完整标本掰开来看 sigmoid 和 softmax 各自解决什么问题以及为什么输出层非要它们不可。适合刚学完神经网络基础、正准备上手卷积网络的读者也适合那些会用框架但一直没搞懂损失函数和输出层配合关系的人。1. 为什么拿 LeNet 当标本从 MLP 到卷积结构的第一个完整样本要理解 sigmoid 和 softmax 的分工最好的方式不是单独看数学公式而是放进一个真实网络里看它们的位置。LeNet 恰好是这样一个标本它足够小结构足够经典而且把“中间层用 sigmoid 压缩特征输出层用 softmax 给答案”这件事展现得清清楚楚。1.1 MLP 到 LeNet 的跨越问题不是“网络变深了”而是“特征提取方式变了”先回顾一下 MLP。一个典型的多层感知机输入是一个向量中间若干个全连接层最后接一个输出层。全连接意味着每个输入节点和下一层的每个节点都有连接参数数量随节点数量平方增长。处理图像时一张 32×32 的三通道图拉直就是 3072 维第一层隐层如果放 256 个神经元光这一层就是接近 78 万个参数而且这种拉直操作把图像的二维空间结构完全丢掉了。LeNet 的突破在于它不再把图像当作一维向量而是用卷积核在二维平面上滑动提取局部特征。一个卷积核只看一个小邻域多个卷积核叠加起来就能识别边缘、纹理、局部形状。参数数量大幅下降同时特征的位置信息被保留下来。这个思路直接影响了后来几乎所有视觉网络。但要注意卷积、池化只是在改变“特征怎么提取”网络最后仍然需要一个决策模块。LeNet 的做法是卷积特征图经过若干轮提取之后展平成一维向量再接两层全连接最后输出 10 个类别的分数。也就是说网络的前半段负责“看懂图像”后半段的全连接和输出层负责“给出判断”。sigmoid 出现在前半段和中间段softmax 住在最后一步。1.2 LeNet 输出端的设计为什么卷积网络也要靠“全连接 softmax”收尾LeNet-5 的经典结构大致是输入 32×32 灰度图经过第一个卷积层得到 6 张特征图然后经过一个“带 sigmoid 的下采样层”其实就是带可学习参数的池化再经过第二个卷积层得到 16 张特征图再下采样最后展平接两个全连接层输出 10 个数字对应的分数。这里有个容易忽略的细节早期 LeNet-5 的输出层并非我们现在常用的 softmax 层而是某种基于径向基函数的距离度量层。但在后来的复现和教学中大家普遍把输出层简化为“全连接层 softmax 交叉熵损失”因为这种做法更直观、更好训练也能解释清楚输出层激活函数的意义。这篇文章讨论的就是这个简化后依然成立的经典模式。你可能会问既然卷积网络已经完成了特征提取为什么最后不直接用每个类别的得分做判断得分不是越高越好吗问题在于“得分”这个东西在训练时没法直接告诉网络“差了多少”。两个类别的得分差是 0.3 还是 3.0对权重更新来说应该有不同的含义。可如果得分是任意实数这个差值本身没有清晰的概率含义。softmax 就是用来把这些得分转换成“可信度分布”的这事我们后面细说。2. sigmoid 在这里不是“压缩”而是神经元的“模拟开关”很多教材喜欢说 sigmoid 的作用是“把输入压缩到 0 到 1 之间”。这句话没错但它掩盖了真正重要的东西sigmoid 不是一个数值转换器而是一个可微分的、带饱和区的决策开关。理解这一点你才能真正明白为什么 LeNet 会把它放在卷积和池化的中间。2.1 没有非线性堆多少层都白搭线性叠加的数学事实先从最根本的问题说起神经网络为什么要激活函数如果不是为了“压缩”那目的是什么假设你只有一个线性层输出是y Wx b。如果你堆两层线性层第二层是y2 W2(W1x b1) b2展开之后依然是y2 (W2W1)x (W2b1 b2)。看两个线性变换叠加本质上还是一个线性变换根本没有引入新的表达能力。你堆一百层和只堆一层的表达能力完全一样。非线性的作用就在这里体现没有它网络无论多深都只能拟合线性关系连异或问题都解决不了。sigmoid 这个 S 型曲线把线性变换的输出做了一个非常柔和、可导的“弯折”每弯一次网络就能表示更复杂的函数形状。LeNet 里每一轮特征提取之后都需要这个弯折来增强表达力。2.2 sigmoid 的曲线直觉三个区间的不同手感sigmoid 的公式是σ(z) 1 / (1 exp(-z))它的输出永远在 0 到 1 之间而且是对称的σ(0)正好是 0.5。这条曲线可以分成三个有代表性的区间输入 z 远小于 0 时输出贴近 0神经元处于“关闭”状态输入 z 远大于 0 时输出贴近 1神经元处于“开启”状态。这两种状态之间有一个过渡带大约在 -4 到 4 之间在这个区间里输出对输入的变化最敏感导数值最大。用开关来类比最合适sigmoid 不是一个非开即闭的数字开关而是一个带有“渐亮”过程的模拟开关。灯光从熄灭到亮起不是瞬间完成的中间那段就是 sigmoid 的过渡区。网络在训练时恰恰需要这段渐变区间来微调“打开的力度”如果它是一个硬开关梯度根本传不过去训练也就无从谈起。2.3 LeNet 中 sigmoid 被用在哪里不只是卷积后的装饰回到 LeNet 的具体结构。现在很多人复现 LeNet 时会在卷积层之后用 ReLU但最早的 LeNet-5 在下采样层里用的是 sigmoid。具体来说它的下采样层会先对特征图的一个小邻域求和再乘一个可学习系数、加一个偏置然后过 sigmoid 做非线性压缩。这个设计在当时很自然卷积抽取局部特征下采样降低分辨率sigmoid 在降采样后把特征值限制在一个温和的范围内避免特征值无限制地增长。从现代视角看这种非线性的位置有些“偏弱”因为真正强的非线性来自层间的激活函数。但无论位置怎么变核心思想是一致的每一轮特征变换之后都要有一个可导的非线性函数来增加表达能力。2.4 sigmoid 带来的“梯度饱和”LeNet 时代能忍后面忍不了sigmoid 有一个无法回避的缺点饱和区的梯度几乎为零。输入一旦到了 10 以上sigmoid 的输出无限接近 1导数却已经小到可以忽略。这意味着网络反传时这个神经元收到的误差信号会被“截断”权重几乎得不到更新。在 LeNet 这种层数不多的网络里这个问题还不算致命因为网络浅梯度经过几层衰减后还能传回来。但后续网络越堆越深sigmoid 的饱和问题被无限放大这也是后来 ReLU 这类分段线性激活函数取代它的直接原因。你可以这样理解sigmoid 做“决策开关”的直觉是很好的但它这个开关在两端太钝了。LeNet 用它是因为当时没有更好的选择而它承担的职责——给线性变换引入非线性——是所有激活函数共同的使命。3. softmax 的真相它不是归一化工具而是“概率投票器”如果说 sigmoid 是神经元的开关那 softmax 就是整个网络最后一场“投票”。它把输出层的一堆原始得分转换为一个和为 1 的概率分布。但如果你只是把它当作“归一化”那又把事情看浅了。softmax 最核心的细节在于那个exp它决定了这张票怎么投。3.1 先把 softmax 的公式拆到不能再拆softmax 的公式长这样softmax(z)_i exp(z_i) / Σ_j exp(z_j)其中z是输出层神经元的原始得分也叫 logitsi是当前类别。分母是所有类别得分取 exp 之后的和。显然输出所有分量都是正数而且总和为 1所以它可以被解释为“这个输入属于各类别的概率”。我见过很多初学者问一个问题如果只是为了“总和为 1”为什么不用最简单的归一化就是每个数除以所有数的和比如得分是 [1, 2, 3]直接除以 6 不就得到概率了吗这个问题问得很好答案藏在 exp 里而且 exp 至少带来了三个好处。3.2 为什么必须用 exp放大差异、保证正数、让梯度计算顺滑第一个好处是“保证正数”。神经网络输出的得分可能是负的比如 [-1, 2, -0.5]。如果直接做除法负得分会得到负概率这在概率意义上完全没有意义。指数函数天然把负值映射成正数这个障碍直接绕过去了。第二个好处是“放大差异”。exp 是一个单调递增的凸函数它对更大的输入给予不成比例的放大。得分 [1, 2, 3]对应的 exp 是 [2.718, 7.389, 20.085]比值从 1:2:3 变成了约 1:2.72:7.39。这意味着最大的那个得分被进一步“凸显”出来。直觉上这就像一场投票计票时给第一名的选票额外加了权重让赢者更突出。第三个好处是“让梯度变得干净”。稍后讲交叉熵时你会看到softmax 和交叉熵组合在一起时梯度形式极其简洁甚至不需要手工计算分母的联合作用。这是神经网络设计里少有的“天然配合”靠着这个组合输出层和损失函数之间的信息传递变得异常顺畅。3.3 数值稳定性为什么所有框架里都要减最大值很多人在推 softmax 公式时会在实现层面遇到一个大坑logits 的数值稍微大一点exp 就可能爆炸。比如得分是 [100, 101, 102]exp(102)已经是一个天文数字直接算会溢出。解决办法非常巧妙给所有得分同时减去一个常数 C最常见的是减去最大值。因为 softmax 的分子分母同时除以exp(C)结果不变softmax(z)_i exp(z_i - C) / Σ_j exp(z_j - C)取 C 为最大值后最大的指数项变成exp(0)1其余项都小于等于 1计算完全稳定。这个技巧在 PyTorch 的CrossEntropyLoss里是默认实现的但如果你自己写 softmax 或是做量化推理一定要记得这一步。这是那种“不踩一次不知道痛”的坑。3.4 sigmoid 和 softmax 的关系二分类的 softmax 就是 sigmoid你可能已经隐约感觉到 sigmoid 和 softmax 之间有某种联系。确实如此对于一个只有两个类别的分类问题如果你对两个 logits 做 softmax然后取第一个类别的概率化简到最后你会发现它恰好等于对一个 logit 差值做 sigmoid。推导大致是这样的设两个类别的 logits 为z_1和z_2那么p_1 exp(z_1) / (exp(z_1) exp(z_2))分子分母同时除以exp(z_1)得到p_1 1 / (1 exp(z_2 - z_1)) sigmoid(z_1 - z_2)。这个关系说明一个事实sigmoid 本质上就是“两个类别的 softmax”。它并不像很多人觉得的那样和 softmax 是平行概念而是后者在二分类情形下的退化形式。多分类时你只能用 softmax二分类时你用 sigmoid 还是 softmax在数学上最终等价只是 sigmoid 写法更省参数。4. 输出层为什么要它们最后一公里决定了网络学不学得动前面两章分别讲清了 sigmoid 和 softmax 的职责但还有一个关键问题没回答为什么输出层“非要”它们网络前面已经有那么多非线性激活了输出层直接给原始得分不行吗答案是原始得分不是不能用但用它会导致训练信号非常难设计。输出层是损失函数和网络之间的唯一通道这最后一公里如果设计不当前面的所有特征提取都白搭。4.1 分类任务的本质输出层给出的是“可信度分布”不是“分数”先想清楚分类任务要什么。手写数字识别给一张图网络要判断它是 0 到 9 中的哪一个。最自然的表达方式是输出一个 10 维向量每一维代表“属于这一类别的概率”最终取概率最大的那个类别作为预测结果。这个“概率”视角很重要。如果输出层直接给得分 [5.2, -1.3, 4.8, ...]我们只能说“第一个类别的得分更高”但高多少算高如果两类的得分只差 0.1网络其实并不确定如果差了 8网络就很有把握。得分本身没有语义边界训练时我们很难定义一个合理的损失函数来利用这种“模糊的自信度”。softmax 把得分转换成概率后一切都变得可以度量。五个类别的概率为 0.9 和五个类别概率都接近 0.2明显代表了截然不同的置信度。这种可度量的不确定性正是把输出层变成“分布”的核心动机。4.2 交叉熵的梯度为什么干净softmax 交叉熵的误差信号损失的常用选择是交叉熵。对单个样本如果它的真实类别是c预测概率是p_1, ..., p_K交叉熵就是L -log(p_c)当网络对正确类别给出的概率是 0.9 时损失很小概率只有 0.1 时损失很大。这和我们对“错误程度”的直觉一致。现在看最精彩的部分如果网络的输出层是“全连接层 softmax”且损失函数用交叉熵那么输出层每个神经元关于得分z_i的梯度是多少最终你会得到一个非常干净的式子∂L/∂z_i p_i - y_i其中y_i是真实标签的 one-hot 编码当i等于真实类别时取 1否则取 0。也就是说梯度就是“预测概率和真实标签之间的差值”。预测高了梯度为正权重被往下拉预测低了梯度为负权重被往上抬。误差信号直接就是预测和真实的偏差没有任何复杂的系数缠绕。这个性质是输出层用 softmax 的最大理由。如果你不用 softmax只拿原始得分配合别的损失梯度形式会繁琐得多训练稳定性也会更差。可以说softmax 交叉熵是概率输出和梯度传导这对需求共同逼出来的最优解。4.3 输出层的选型对照什么时候用 sigmoid、什么时候用 softmax实际任务里输出层的选型其实有一套非常明确的规则。下面这张表可以当作速查任务类型输出层结构损失函数例子说明二分类1 个神经元 sigmoid二分类交叉熵输出代表“属于正类的概率”单标签多分类K 个神经元 softmax多分类交叉熵所有输出概率和为 1多标签分类K 个神经元各自独立 sigmoid逐个二分类交叉熵之和每个标签独立判断概率和不为 1回归任务不接激活或恒等输出均方误差 MSE目标是连续值不需要概率化重点说下多标签场景。很多人会犯错以为多标签就是“多个类别的 softmax”其实完全不是。多标签任务里一张图可以同时属于“天空”和“白天”这两个标签并不互斥。如果硬用 softmax模型会被迫在“天空”和“白天”之间做非此即彼的选择这会直接破坏标签之间的独立性。正确做法是每个输出神经元各自过 sigmoid各自判断“这个标签是否成立”。4.4 回归到 LeNet它的输出层设计如何体现这些原则再看 LeNet 的输出端10 个输出神经元代表 0 到 9 十个类别后面接 softmax 得到概率分布配合多分类交叉熵训练。这是一个典型的“单标签多分类”范式。你可能会注意一个问题LeNet 中间层用 sigmoid输出层用 softmax那中间层的 sigmoid 会不会和输出层的 softmax 冲突不会。中间层的 sigmoid 是在“特征空间”里做非线性变换它的输出不要求概率归一化只要求增加表达力。输出层的 softmax 是在“决策空间”里做概率化它只关心 10 个候选者谁更可信。两者服务的层次不同职责完全不重叠。理解这一点是彻底搞懂这篇标题的关键。5. 实际训练里的手感差异这两个输出函数的坑和技巧前面讲的都是原理层面的内容。但真正到了训练网络的时候你会发现 sigmoid 和 softmax 的坑一个接一个。下面这些经验是我在实际调试网络时积累下来的希望对你有帮助。5.1 直接在输出层用 sigmoid 会发生什么有人会想既然 sigmoid 也是输出 0 到 1 的值那多分类任务里直接给 10 个输出各自过 sigmoid 行不行技术上当然可以但结果会很别扭。10 个 sigmoid 输出每个都在 0 到 1 之间但它们加起来不等于 1。你怎么解释这些值它们是概率吗每个类别独立判断可分类任务要求“这张图只能属于一个数字”10 个独立概率加在一起超过 1模型就无法表达“各候选之间的排他性”。训练时你还会发现另一个问题没有 softmax 的概率竞争机制交叉熵的梯度就不再有p_i - y_i那么干净的形态输出层的误差信号会变得浑浊。震荡、收敛慢、最终精度上不去都是可能的后果。所以我的建议很简单单标签多分类任务输出层老老实实用 softmax不要自作聪明。5.2 logits、温度参数与置信度控制还有一个容易被忽视的细节softmax 的输入是 logits也就是未经激活的原始得分。很多框架里的CrossEntropyLoss会直接吞原始 logits内部帮你做 softmax。这意味着你在搭建网络时输出层前面不要自己提前加激活函数把全连接层的原始输出直接交给损失函数就行。如果在其他场合需要手工控制概率分布的“锐利程度”你可以引入温度参数 T把 softmax 改成softmax(z / T)。T 越小概率分布越尖锐最大概率越突出T 越大分布越平滑各类别概率越接近。这个技巧在知识蒸馏里很常用如果你只做常规分类记住这个参数的存在即可。我调试时偶尔会用较高的温度来观察模型对不同类别的“犹豫程度”这比直接看 logits 更直观。5.3 多标签分类的正确姿势每个输出各自 sigmoid上面表格里提到过这里单独展开。我碰到过一个实际案例某任务要同时判断图片中是否存在多个物体一共有 5 个候选物体类别。最初实现的人给 5 个输出接了一层 softmax结果训练收敛很慢而且模型总是倾向于只给出一个判断。后来改成 5 个独立的 sigmoid每个输出配二进制交叉熵损失效果立刻改善。原因很简单softmax 强制五个输出和为 1这在“多个物体可以同时存在”的任务里是错误先验。sigmoid 则完全没有这种约束每个标签独立判断网络可以自由地同时打开多个输出。所以选 sigmoid 还是 softmax本质上是在问一个问题你的任务标签之间是互斥还是独立互斥用 softmax独立用 sigmoid。5.4 一个快速验证的小实验把 softmax 换成 sigmoid 看损失曲线如果你想亲自体会两者差异我建议做一个只需要几分钟的小实验。取一个公开的手写数字识别小数据集搭建一个最简单的两层网络输入层、一个隐藏层比如 64 个神经元sigmoid 激活、一个 10 维输出层。第一次实验输出层接 softmax配合交叉熵。观察训练损失的下降曲线通常很平滑最终准确率也比较稳定。第二次实验把输出层换成 10 个独立 sigmoid配上每个维度的二分类交叉熵即把多分类强行当成多标签来训练。你会发现损失下降变慢最终准确率会略低于 softmax 版本而且模型的预测表现会出现“多个输出同时高”的怪现象。这个实验不需要跑很多 epoch几十个回合就能看出差别。通过亲手对比你会比背一百遍公式都更清楚为什么输出层非要 softmax 不可。说回到开头的那个问题sigmoid 和 softmax 长得像是因为它们本质上是同一个家族——把实数映射到有界的概率区间。但它们在网络中的角色完全不同sigmoid 是特征空间里的非线性开关softmax 是决策空间里的概率投票器。一个是过程工具一个是终极表达。理解了这层分工再去看 LeNet 乃至后续的各种分类网络你会发现输出层的设计其实一直都在沿用一个非常稳定的范式中间靠非线性激活增强表达最后靠 softmax 把得分变概率再配合交叉熵把误差干净地传回去。我自己在最初上手神经网络时曾经花了很多时间纠结输出层的函数形式其实不如多做几次上面那样的替换实验来得快。如果你也在这个阶段建议找个手写数字识别的小网络把输出层的激活函数来回换几次亲眼看看损失曲线的变化比任何理论解释都有效。