观文听傑

返回

上一篇的 UMAP 在无标签数据上构建近邻图,再为每个训练样本直接优化一个低维坐标。它能展开复杂形状,却没有用任务损失学到一个快速的多层映射。

神经网络(Neural Network)正是把简单变换逐层组合。但有一个看似反直觉的门槛:如果层与层之间没有激活函数,叠再多线性层也只是一层。 本文只追问这一个核心问题:仿射变换、激活函数(Activation Function)与非线性决策边界究竟怎样连在一起?

01 为什么逻辑回归无法分开 XOR?#

异或(Exclusive OR,XOR)只有四个样本:两个输入不同时标签为 1,相同时为 0。

x1x_1x2x_2yy
000
011
101
110

任何逻辑回归都先计算 z=w1x1+w2x2+bz=w_1x_1+w_2x_2+b,再以 z=0z=0 作为一条直线边界。可 XOR 的两个正类落在对角,无法用一条直线与另两个点分开。

x₂
▲   y=1 ●        ○ y=0

│   y=0 ○        ● y=1
└──────────────────────► x₁

不论直线如何旋转,都会把一个对角点分错。
text

为原始特征手工加入 x1x2x_1x_2 能解决这个例子,但真实任务有数以千计的特征和未知交互,穷举特征工程很快失控。我们需要让模型从数据中学出有用的中间表示。

02 一个人工神经元究竟计算什么?#

人工神经元(Artificial Neuron)先做仿射变换(Affine Transformation),再通过激活函数 ϕ\phi

z=wx+b,h=ϕ(z)z=w^\top x+b,\qquad h=\phi(z)
  • xRDx\in\mathbb R^D:一个样本的 DD 个输入特征;
  • wRDw\in\mathbb R^D:神经元对每个特征的权重;
  • bRb\in\mathbb R:偏置(Bias),让切分不必穿过原点;
  • zRz\in\mathbb R:激活前值(Pre-activation);
  • hRh\in\mathbb R:传给下一层的激活值(Activation)。

一层中并排 HH 个神经元,对 NN 个样本一次计算:

Z=XW+b,H1=ϕ(Z)Z=XW+b,\qquad H_1=\phi(Z)
张量形状每个轴的含义
XX[N,D][N,D]NN 个样本,每个 DD 个特征
WW[D,H][D,H]DD 维输入到 HH 个隐单元
bb[H][H]沿 batch 维广播的偏置
Z,H1Z,H_1[N,H][N,H]每个样本的 HH 个中间特征

03 为什么叠两个线性层仍是一层?#

假设中间没有激活函数:

H1=XW1+b1H_1=XW_1+b_1 Z2=H1W2+b2Z_2=H_1W_2+b_2

代入并合并同类项:

Z2=X(W1W2)+(b1W2+b2)=XW+bZ_2=X(W_1W_2)+(b_1W_2+b_2)=XW_*+b_*

WW_*bb_* 仍只定义了一次仿射变换。层数增加了,可表达的函数类别没有增加;它仍无法分开 XOR。

无激活: X ── W₁,b₁ ── W₂,b₂ ──► Z
                         可折叠为 W*,b*

有激活: X ── W₁,b₁ ── ReLU ── W₂,b₂ ──► Z

                         分段折叠,不可整体合并
text

04 ReLU 怎样创造分段线性区域?#

整流线性单元(Rectified Linear Unit,ReLU)逐元素计算:

ReLU(z)=max(0,z)\operatorname{ReLU}(z)=\max(0,z)

它在 z<0z<0 时关闭单元,在 z>0z>0 时保留线性值。每个隐单元都用 wx+b=0w^\top x+b=0 在输入空间划出一个开关边界;多个开关的组合把空间分成多个区域,每个区域内是线性函数,区域之间可以改变斜率。

z < 0                 z > 0
──────────────●╱──────────────► z
              0
ReLU 输出 0       ReLU 输出 z
text

因此 ReLU 网络虽然由线性片段组成,整体却可以形成弯折的分段线性边界。

05 用两个隐单元手算 XOR#

s=x1+x2s=x_1+x_2,设置两个隐单元:

h1=ReLU(s),h2=ReLU(s1)h_1=\operatorname{ReLU}(s),\qquad h_2=\operatorname{ReLU}(s-1)

输出 logit 为:

z=2h14h21z=2h_1-4h_2-1
(x1,x2)(x_1,x_2)ssh1h_1h2h_2zzz0z\ge0
(0,0)(0,0)000-10
(0,1)(0,1)11011
(1,0)(1,0)11011
(1,1)(1,1)221-10

四个点全部正确。两个 ReLU 不是在“记住四张表格”:第一个编码总和,第二个只在两个输入同时为 1 时开启,输出层用后者把过大的总和拉回负类。这就是一个可解释的中间表示。

06 Sigmoid、tanh 和 ReLU 应该放在哪里?#

激活定义输出范围常见用法主要风险
Sigmoid1/(1+ez)1/(1+e^{-z})(0,1)(0,1)二分类推理概率、门控大绝对值时梯度接近 0
tanhtanhz\tanh z(1,1)(-1,1)需要零中心有界激活的结构两端饱和
ReLUmax(0,z)\max(0,z)[0,)[0,\infty)普通 MLP 隐藏层的强基线负区域长期无梯度
Leaky ReLUmax(αz,z)\max(\alpha z,z)R\mathbb R希望负区域仍有小梯度α\alpha 是新设计选择

隐藏层和输出层的职责不同。二分类训练时,隐藏层可用 ReLU,最后一层应输出无界 logits,直接交给 BCEWithLogitsLoss。该损失在内部合并 Sigmoid 与 BCE,利用 log-sum-exp 获得更稳定的数值计算。只在推理或展示概率时手动 sigmoid(logits)

07 完整数据流与张量形状#

NN 个二维样本、H=4H=4 个隐单元:

X [N,2]
  │ Linear(2,4): X @ W₁ᵀ + b₁

Z₁ [N,4]
  │ ReLU,形状不变

H₁ [N,4]
  │ Linear(4,1): H₁ @ W₂ᵀ + b₂

logits [N,1]
  │ squeeze(-1),只删除最后的单例维

logits [N] ── BCEWithLogitsLoss(y [N]) ──► loss []
text

PyTorch 的 nn.Linear(in_features, out_features) 把权重存成 [out_features,in_features],因此数学上计算 XW+bXW^\top+b。不要因为纸上把 WW 写成 [D,H] 就手动改变模块参数的维度约定。

08 训练和推理的最小伪代码#

本篇聚焦前向表示;“求所有参数的梯度”不是黑箱口号,下一篇会沿计算图逐边手算。

09 用 PyTorch 2.13 实现可检查的两层网络#

当前稳定 API 中,nn.Linear 完成仿射变换,nn.ReLU 保持输入输出形状,BCEWithLogitsLoss 要求输入与目标形状一致。

这个数据集小到只能验证表达能力,不能证明泛化。随机初始化和优化路径也可能使极小网络未在固定步数内学会 XOR;因此测试代码应固定种子并检查最终预测,不要只看损失打印。

10 从第一个失败开始调试#

  1. 先查形状。 打印每层输入输出;二分类的 logitstarget 必须完全同形。
  2. 再查数值。 在前向、损失、梯度和更新后分别查验 isfinite,定位第一个 NaN/Inf。
  3. 查激活占比。 记录 (hidden > 0).float().mean();所有 ReLU 长期为 0 表示“死亡 ReLU”。
  4. 查最后一层。 BCEWithLogitsLoss 前不能再加 Sigmoid;否则损失把概率误当成 logit。
  5. 尝试过拟合一个极小 batch。 若一个有可学信号的小 batch 都无法拟合,优先怀疑实现、标签和学习率,而非泛化。

可以用显式模块捕获中间值:

linear1 = model[0]
relu = model[1]

with torch.no_grad():
    preactivation = linear1(X)                       # [4,4]
    hidden = relu(preactivation)                     # [4,4]
    print('active_fraction:', (hidden > 0).float().mean().item())
    print('weight_shapes:', [tuple(p.shape) for p in model.parameters()])
python

11 常见错误、工程边界与失败场景#

  • squeeze() 删掉所有单例维。 batch size 为 1 时可能连 batch 维也消失;优先 squeeze(-1)
  • 利用广播隐藏了标签形状错误。 [N,1][N] 的组合可能在其他损失中扩展成 [N,N];每次明确断言。
  • 为所有层都加 ReLU。 最后 logit 被截断为非负后,模型对负类的表达会被破坏。
  • 用很大学习率把所有 ReLU 推入负区域。 降低学习率,检查初始化,或在确有需要时比较 Leaky ReLU。
  • 把两层 MLP 当成万能结构。 它没有显式利用图像平移、时序因果或图邻接等结构,往往需要更多样本和参数。
  • 忽视数据边界。 网络容量更大,并不会自动阻止泄漏、标签错位或分布漂移。

12 与相近方法的边界#

方法非线性从哪里来学习对象主要特点
逻辑回归原始特征上没有一组线性权重几何清晰,表达有限
多项式特征 + 线性模型人工交互项扩展后的线性权重可解释,特征数可爆炸
核 SVM核函数隐式特征支持向量系数中小数据强,大样本可扩展性受限
决策树轴对齐分裂递归规则表格数据强,不用梯度
ReLU MLP可学仿射 + 分段开关多层特征与输出表示灵活,优化与调试更复杂

理论上,足够宽的单隐藏层网络可以近似很广的连续函数类;但这不保证有限数据下能学到、优化器能找到,或分布外能正确。“能表达”、“能训练”和“能泛化”是三个不同问题。

13 今天真正需要记住什么?#

  1. 仿射层先计算 XW+bXW+b;多个无激活的仿射层可合并为一层。
  2. ReLU 为每个隐单元引入开关边界,多个边界组合出分段线性的非线性模型。
  3. XOR 用两个 ReLU 隐单元就能手算分开,关键是学中间表示,而不是单纯增加层数。
  4. PyTorch nn.Linear 的权重存储为 [out,in];二分类训练应输出 logits 并交给 BCEWithLogitsLoss
  5. 表达能力不等于可优化性或泛化;形状、数值、激活占比和小 batch 过拟合是最先的调试证据。

14 思考题与小练习#

  1. 删掉示例中的 nn.ReLU(),保留两个 nn.Linear,多次改变隐藏宽度和种子。为什么它无法稳定把 XOR 四点全部分对?
  2. 在手算网络中把输入改为 (0.2,0.9)(0.2,0.9)(0.8,0.8)(0.8,0.8),计算 h1,h2,zh_1,h_2,z 与概率。画出 z=0z=0[0,1]2[0,1]^2 中的分段边界。
  3. 分别使用 ReLU、tanh 和 LeakyReLU 训练同一个小网络,记录损失、隐单元零值比例和每层梯度范数;先固定数据、初始权重与优化器。

相关工作#

15 下一篇预告#

现在一个两层网络已经能把 XOR 折成可分的中间表示,但手算权重不会扩展到真实数据。下一篇将把前向计算展开成有向计算图,用链式法则追踪输出误差如何分配给每一层的权重与偏置。

叠得更深为何仍只是一条直线?激活函数如何让神经网络表达 XOR
https://zwjcode.cn/blog/neural-network-activation-xor-nonlinearity
作者
发布于 2026年8月31日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。