Blog
Essay
深度学习:Basics·Chapter 1 of 12

从逻辑回归到神经元

从“分数怎样变成概率”讲起,理解 Sigmoid、二元交叉熵,以及逻辑回归为何可以看作一个神经元。

deep-learningneural-networklogistic-regressionnumpy

第一次看到神经元的结构图时,它很容易显得比逻辑回归复杂:左边有许多输入,中间汇聚到一个圆圈,右边再输出一个数。图里还会出现权重、偏置、激活函数,仿佛我们已经跨进了一个全新的模型世界。

这一篇先不搭建多层网络。我们只拆开一个最小的计算单元,跟着一条样本走完预测、计算误差和更新参数的过程,看看它与逻辑回归究竟是什么关系。

读完以后,你应该能够从输入开始写出一个神经元的前向计算与参数更新,并说清楚每一步解决了什么问题。

第一步:模型先给出一个分数

假设我们要判断一封邮件是否为垃圾邮件,并从每封邮件中取出两个特征:

  • x1x_1:某类关键词出现的频率;
  • x2x_2:邮件中链接的数量。

一封邮件的这两个值组成输入 x\boldsymbol{x}

x=[x1x2]\boldsymbol{x} = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}

x1x_1x2x_2 来自样本。模型还需要判断每个特征对结果有多大影响,因此为它们分别准备一个可以调整的数:w1w_1 对应 x1x_1w2w_2 对应 x2x_2。这两个数叫作权重,合在一起记为 w\boldsymbol{w}

w=[w1w2]\boldsymbol{w} = \begin{bmatrix} w_1 \\ w_2 \end{bmatrix}

权重不是样本提供的,而是模型自己的参数。训练开始前,我们先给它们一个初始值;在后面的训练过程中,再根据预测误差反复调整它们。本文的代码会从 w1=w2=0w_1=w_2=0 开始。

模型还有一个不与特定输入相乘的参数 bb,叫作偏置。它控制没有任何特征贡献时的基础分数,也会和权重一起在训练中被调整。

现在,模型把每个特征乘上对应的权重,再加上偏置,得到一个分数:

z=w1x1+w2x2+bz = w_1x_1 + w_2x_2 + b

这里的两个特征是 x1x_1x2x_2w1w_1w2w_2 是分别控制它们影响力的两个参数。得到的 zz 只是一个分数,它可以是 20-200.70.7100100,不能直接解释成概率。我们还需要一个“压缩器”,把任意大小的分数压到 0011 之间。

Sigmoid:把分数压成概率

这个压缩器就是 Sigmoid 函数。它的图像像一条平滑的 S 形曲线:

σ(z)=11+ez,p=σ(z)\sigma(z) = \frac{1}{1 + e^{-z}}, \qquad p = \sigma(z)
Sigmoid 函数
z 0.0 0.500
0.0
Sigmoid 在 z = 0 时输出 0.5;输入越大,输出越接近 1,输入越小,输出越接近 0。

这里的 ee 是一个约等于 2.7182.718 的数学常数,就像圆周率 π\pi 一样。实际写代码时,NumPy 会替我们完成指数运算,不需要手算。

也不必先记住公式的每个符号。先观察它的输入和输出:

输入分数 zzSigmoid 输出 pp直观含义
4-40.0180.018很接近类别 00
1-10.2690.269更偏向类别 00
000.5000.500两个类别之间的分界点
110.7310.731更偏向类别 11
440.9820.982很接近类别 11

Sigmoid 有三个重要性质:

  • 无论输入多大或多小,输出始终在 0011 之间。
  • 输入越大,输出也越大,不会打乱模型原来的排序。
  • 它是平滑、可导的,因此可以通过梯度来调整前面的权重。

如果任务是判断一封邮件是否为垃圾邮件,我们可以把 pp 解释为“属于类别 11 的概率”。例如 p=0.9p=0.9 表示模型非常倾向于类别 11p=0.1p=0.1 表示它更倾向于类别 00

不过,一个数落在 0011 之间,并不会自动成为可靠的概率。 为了训练模型,我们还需要一个评分规则:答对时给出较小的惩罚,答错时给出较大的惩罚。

这条链在两种语境中有两套名字:

逻辑回归中的说法神经元中的说法实际计算
特征输入xx
系数权重ww
截距偏置bb
线性预测值 / logit预激活值z=wTx+bz = w^{\mathsf T}x + b
Logistic 逆链接函数 / Sigmoid激活函数p=σ(z)p = \sigma(z)
预测概率神经元输出pp

因此,就前向计算而言,一个采用 Sigmoid 激活的单个神经元与逻辑回归相同。 “神经元”这个词本身并不保证它一定使用 Sigmoid,也不意味着所有神经元都等于逻辑回归。

一次前向传播到底发生了什么

取一个具体例子:

x=(0.8,0.3),w=(1.2,0.7),b=0.1\boldsymbol{x} = (0.8, 0.3), \quad \boldsymbol{w} = (1.2, -0.7), \quad b = -0.1

第一步是加权求和:

z=1.2×0.80.7×0.30.1=0.65z = 1.2 \times 0.8 - 0.7 \times 0.3 - 0.1 = 0.65

第二步是激活:

p=σ(0.65)0.657p = \sigma(0.65) \approx 0.657

到这里,前向传播已经结束。神经元没有在内部进行某种难以观察的“思考”,它只完成了两个明确的变换:

输入 x
  ↓
线性组合 z = wᵀx + b
  ↓
Sigmoid p = σ(z)
  ↓
概率 p

如果我们规定 p0.5p \ge 0.5 时预测为类别 11,这条样本会被分到类别 11。但要注意,阈值判断通常是推理阶段的决策规则,不是训练过程本身。训练需要一个连续、可微的量来衡量预测到底错了多少。

二元交叉熵:给概率预测打分

这个评分规则就是二元交叉熵。先拆开这个名字。

  • 二元表示答案只有两种:y=0y=0y=1y=1
  • 交叉熵在这里可以先理解为:比较“真实答案”和“模型给出的概率分布”有多不一致。

假设真实标签是 11。模型给类别 11 的概率是 0.90.9,说明它既答对了,也很有把握;如果只给出 0.10.1,说明它非常自信地答错了,理应受到更大的惩罚。

如果真实标签是 00,判断方向就反过来。此时 p=0.1p=0.1 是一个好预测,因为留给类别 00 的概率是 1p=0.91-p=0.9

我们可以先只看模型分给“正确类别”的概率 qq

真实标签 yy类别 1 概率 pp正确类别概率 qq损失 logq-\log q判断
110.90.90.90.90.1050.105答对且比较自信
110.10.10.10.12.3032.303自信地答错
000.10.10.90.90.1050.105答对且比较自信
000.90.90.10.12.3032.303自信地答错

log\log 是自然对数。这里暂时不需要掌握对数运算,只需要记住它制造的评分规则:log(1)=0-\log(1)=0,而 qq 越接近 00logq-\log q 就越大。

logq-\log q 的作用很直观:正确类别的概率越接近 11,损失越接近 00;正确类别的概率越接近 00,损失就会急剧增大。它不仅关心模型有没有跨过 0.50.5 的分类线,也关心模型有多确定。

现在再看完整公式:

L=[ylogp+(1y)log(1p)]L = -\left[y\log p + (1-y)\log(1-p)\right]

它看起来有两项,但每条样本实际上只会使用一项:

  • y=1y=1 时,(1y)=0(1-y)=0,公式只剩 L=logpL=-\log p
  • y=0y=0 时,y=0y=0,公式只剩 L=log(1p)L=-\log(1-p)

这就是代码里把 y1 - y 同时写进去的原因。它只是在用一个公式同时处理两种标签。

损失函数本身并不负责修改参数。它只是把“预测概率有多不好”变成一个标量。真正让模型学习的是下一步:计算这个标量相对于每个参数会怎样变化。

最关键的梯度:预测减去标签

把 Sigmoid 与二元交叉熵放在一起,会得到一个非常简洁的结果:

Lz=py\frac{\partial L}{\partial z} = p-y

可以沿着计算链验证它:

Lp=yp+1y1p\frac{\partial L}{\partial p} = -\frac{y}{p} + \frac{1-y}{1-p}pz=p(1p)\frac{\partial p}{\partial z} = p(1-p)

将两项相乘并整理:

Lz=Lppz=py\frac{\partial L}{\partial z} = \frac{\partial L}{\partial p} \frac{\partial p}{\partial z} = p-y

由于 z=wTx+bz = \boldsymbol{w}^{\mathsf T}\boldsymbol{x}+b,继续使用链式法则:

Lw=(py)x,Lb=py\frac{\partial L}{\partial \boldsymbol{w}} = (p-y)\boldsymbol{x}, \qquad \frac{\partial L}{\partial b} = p-y

pyp-y 可以看作从输出端返回的误差信号:

  • 若真实标签是 11,预测只有 0.20.2,则 py=0.8p-y=-0.8。梯度下降会推动参数让下一次的 zz 变大。
  • 若真实标签是 00,预测却是 0.90.9,则 py=0.9p-y=0.9。更新会推动下一次的 zz 变小。

在一批 NN 条样本上,我们通常取平均梯度:

wL=1NXT(py),Lb=1Ni=1N(piyi)\nabla_{\boldsymbol{w}}L = \frac{1}{N}\boldsymbol{X}^{\mathsf T}(\boldsymbol{p}-\boldsymbol{y}), \qquad \frac{\partial L}{\partial b} = \frac{1}{N}\sum_{i=1}^{N}(p_i-y_i)

然后执行一次梯度下降

wwηwL,bbηLb\boldsymbol{w} \leftarrow \boldsymbol{w} - \eta\nabla_{\boldsymbol{w}}L, \qquad b \leftarrow b - \eta\frac{\partial L}{\partial b}

其中 η\eta 是学习率。至此,“学习”不再是一个抽象动词:它就是根据误差计算梯度,再对 w\boldsymbol{w}bb 做一次很小的修改。

用 NumPy 训练一个神经元

下面的实验只使用 NumPy 完成前向传播、损失计算和梯度更新。两团二维点分别代表类别 00 和类别 11;橙色直线是神经元最终学到的 p=0.5p=0.5 决策边界。

点击“运行”后,Python 会在浏览器的 Web Worker 中执行,数据和代码不会发送到服务器。第一次需要下载 Pyodide、NumPy 与 Matplotlib,因此会比后续运行慢。

Output

运行代码后,文本与图表会显示在这里。

代码中最值得盯住的不是画图部分,而是训练循环里的四行:

z = x @ w + b
probability = sigmoid(z)
error = probability - y
dw = x.T @ error / len(x)

它们正好对应刚才的推导:线性组合、Sigmoid、输出端误差和权重梯度。框架以后会替我们自动完成求导,但自动微分并没有改变这条计算链。

运行结果里还有两件事值得观察。

第一,交叉熵随着更新逐渐下降。这说明当前参数产生的预测越来越符合训练标签。第二,二维平面上出现了一条直线。令 p=0.5p=0.5 等价于令 z=0z=0,所以决策边界满足:

w1x1+w2x2+b=0w_1x_1+w_2x_2+b=0

无论训练多久,这个神经元都只能移动或旋转这条直线,不能把它弯曲。

既然计算相同,为什么还要叫神经元

因为接下来的关注点不再是“完成一次二分类”,而是“把计算单元连接起来”。

单独看时:

x → Linear → Sigmoid → p

它是一套完整的逻辑回归模型。放入网络以后,一个单元的输出可以成为下一层许多单元的输入:

x₁ ─┐                 ┌→ h₁ ─┐
    ├→ weighted sum ─→│      ├→ next layer
x₂ ─┘                 └→ h₂ ─┘

此时,权重不再只是在原始特征上画一条最终的分类边界。前面的单元可以先把输入变换成新的表示,后面的单元再基于这些表示做判断。“神经元”这个名称强调的正是它作为网络组成部分的身份。

不过,复制许多个线性计算还不够。如果层与层之间没有非线性激活,无论叠多少层,整个网络仍然可以合并成一次线性变换。这也是下一篇必须讨论激活函数的原因。

这一步还没有解决什么

理解“逻辑回归就是一个 Sigmoid 神经元”并不等于已经理解了神经网络。至少还有三条边界需要保留:

  1. 单个神经元只能学习线性决策边界。 两类数据如果无法被一条直线或一个超平面分开,它就无能为力。
  2. 神经元不一定使用 Sigmoid。 ReLU、Tanh、GELU 都可能出现在网络中,它们有不同的输出形状与梯度性质。
  3. “神经元”只是计算抽象。 生物学类比有助于命名输入、连接和激活,但人工神经元远不是生物神经元的忠实模型。

第一条边界会把我们带到 XOR:为什么一个线性模型永远无法解决某些看起来很小的问题?第二条会把我们带到激活函数:非线性究竟为表示能力增加了什么?

留下一个可以复述的模型

这一篇真正需要带走的不是某一段 NumPy 代码,而是下面这条完整链条:

输入 x
  ↓
z = wᵀx + b
  ↓
p = sigmoid(z)
  ↓
L = binary_cross_entropy(p, y)
  ↓
error = p - y
  ↓
计算 dw、db
  ↓
更新 w、b

当它被当作一个完整模型时,我们叫它逻辑回归;当它被当作可连接的计算单元时,我们叫它一个 Sigmoid 神经元。

神经元并没有凭空引入一种新的学习方法。它继承了已经熟悉的线性模型、概率、损失、梯度和更新。真正的新问题从这里才刚刚出现:当一条线不够时,我们怎样把许多这样的单元组合成非线性的表示?