Aller au contenu principal

深度学习 2 — 分类

:::tip Kaggle 笔记本 本章的完整可执行代码在 Kaggle 上:打开 →

法语和英语版本可在首页查看。 :::

在上一章中,我们做了回归——预测一个数字。这里我们攻克分类——预测一个类别。本章的最大惊喜,也是它的美妙之处:从一个到另一个几乎不需要任何改变。梯度保持相同的形式。

为什么要学这一章?

您将看到:

  • 逻辑神经元(线性回归 + sigmoid);
  • 交叉熵的直观解释及从 Bernoulli 分布的推导;
  • 梯度保持相同形式 1nXT(uy)\frac{1}{n} X^T (u - y) 的事实;
  • PyTorch 陷阱BCELoss vs BCEWithLogitsLoss vs CrossEntropyLoss
  • 多类分类:softmax + CrossEntropyLoss

从线性到逻辑

对于二元分类,目标是 y{0,1}y \in \{0, 1\}。我们想要一个概率 p^=P(y=1X)\hat{p} = P(y = 1 \mid X),所以输出在 (0,1)(0, 1) 中。

解决方案:对线性输出应用 sigmoid 函数

z=Xw+b,u=σ(z)=11+ezz = X w + b, \quad u = \sigma(z) = \frac{1}{1 + e^{-z}}

σ(z)\sigma(z) 将任意实数压缩到 (0,1)(0, 1)。这就是逻辑神经元

交叉熵作为"惊讶"

分类的损失是交叉熵

Ei=[yilogui+(1yi)log(1ui)]E_i = -\big[ y_i \log u_i + (1 - y_i) \log(1 - u_i) \big]

直观理解:交叉熵衡量模型面对真相的**"惊讶程度"**。

  • 如果 yi=1y_i = 1ui1u_i \to 1:没有惊讶,Ei=log1=0E_i = -\log 1 = 0
  • 如果 yi=1y_i = 1ui0u_i \to 0:巨大惊讶,Ei+E_i \to +\infty
  • yi=0y_i = 0 对称。

模型对正确答案越自信,损失越小。它对错误答案越自信,损失爆炸越厉害。正是这种不对称性推动模型学习经过校准的概率。

从 Bernoulli 推导

为什么是这个精确公式?它来自最大似然。如果将 yy 建模为参数为 pp 的 Bernoulli 变量:

P(yp)=py(1p)1yP(y \mid p) = p^y (1-p)^{1-y}

观测的似然是乘积。取负对数(得到要最小化的函数),我们刚好落在交叉熵上。

梯度保持相同形式

这是本章的神奇时刻。对于线性神经元:

Ew=1nXT(uy)\frac{\partial E}{\partial w} = \frac{1}{n} X^T (u - y)

对于逻辑神经元,经过计算:

Ew=1nXT(uy)\frac{\partial E}{\partial w} = \frac{1}{n} X^T (u - y)

严格相同的公式。唯一的区别在于 uu 的定义:

  • 线性:u=Xw+bu = X w + b
  • 逻辑:u=σ(Xw+b)u = \sigma(X w + b)

实际后果:将 LinearNeuron 转换为 LogisticNeuron,只需改变 forward() 来应用 sigmoid。其余一切保持不变。

PyTorch 版本

model = nn.Linear(m, 1) # 仅 logits,没有 Sigmoid
criterion = nn.BCEWithLogitsLoss() # 应用 sigmoid + 交叉熵
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

经典陷阱

PyTorch 提供了几种看起来相似但不等价的模型 + 损失组合:

如果损失是...模型必须输出...
nn.BCELoss概率(末尾带 Sigmoid
nn.BCEWithLogitsLosslogit(不带 Sigmoid)
nn.CrossEntropyLoss(多类)logits 向量(不带 Softmax)

:::warning 陷阱 #1 绝对不要在模型中放 Sigmoid 同时使用 BCEWithLogitsLoss。Sigmoid 会被应用两次,模型不会学习。 :::

BCEWithLogitsLoss 是推荐的:在数值上比 Sigmoid + BCELoss 更稳定。

预测类别

训练后,从 logit 到类别:

model.eval()
with torch.no_grad():
logits = model(X_test_t)
proba = torch.sigmoid(logits) # logit → 概率
y_hat = (proba >= 0.5).float() # 概率 → 类别

多类:softmax + CrossEntropyLoss

对于 CC 类,最后一层有 CC 个神经元,产生 logits 向量

z=(z0,z1,,zC1)z = (z_0, z_1, \dots, z_{C-1})

sigmoid 的推广是 softmax

P(y=cX)=ezckezkP(y = c \mid X) = \frac{e^{z_c}}{\sum_k e^{z_k}}

所有概率都为正且和为 1。

多类交叉熵就是:

Ei=logP(y=yiXi)E_i = -\log P(y = y_i \mid X_i)

负号加上为真实类别预测的概率的对数。

model = nn.Sequential(
nn.Linear(m, 64),
nn.ReLU(),
nn.Linear(64, C), # 多类 logits
)
criterion = nn.CrossEntropyLoss() # 应用 LogSoftmax + 交叉熵

y 的预期形状

任务模型输出y 形状类型Loss
回归(n, 1)(n, 1)float32MSELoss
二元分类(n, 1)(n, 1)float32BCEWithLogitsLoss
多类分类(n, C)(n,)longCrossEntropyLoss

:::warning 多类的 y 对于 CrossEntropyLossyy整数的 1D 向量(不是 one-hot)。类型 long,不是 float。 :::

预测类别

with torch.no_grad():
logits = model(X_test_t)
y_hat = torch.argmax(logits, dim=1) # 最可能的类别

在类别维度上 argmax——不需要显式应用 softmax,顺序保持不变。


Kaggle 上的完整笔记本(可分叉)→