模型输出head(尤其是分类模型)有个常见的组合是余弦相似度(Cosine Similarity)+ Sigmoid 配合交叉熵损失(CE/BCE),即:
P(I,L)=σ(w⋅cos(vI,vL)+b) P(I, L) = \sigma(w\cdot\cos(\bold{v}_I,\bold{v}_L)+b) P(I,L)=σ(w⋅cos(vI,vL)+b)
使用Sigmoid的原因主要有:
1. 解决高位空间的相似度坍缩与分布对齐
在深度神经网络的高维特征空间中,任意两个随机向量的余弦相似度往往趋近于 000(正交),而模型经过训练后,正样本和负样本的余弦相似度分布往往会挤在一个很窄的区间内
例如,匹配的样本对相似度可能是 0.80.80.8,不匹配的样本对相似度可能是 0.60.60.6
- 如果直接用线性映射,不仅很难用一个绝对阈值(比如 0.50.50.5)去切分它们,而且给出的"概率"区分度极低
- 引入可学习的 www(也常被称为倒数温度系数 1/τ1/\tau1/τ)和 bbb(偏置项),可以拉伸并平移这个狭窄的分布。模型可能会学到 w=20,b=−14w=20, b=-14w=20,b=−14。此时,0.60.60.6 的相似度输入 Sigmoid 前变成了 −2-2−2,Sigmoid 输出约为 0.110.110.11;而 0.80.80.8 的相似度变成了 222,Sigmoid 输出约为 0.880.880.88。这样不仅将分值完美拉伸到了完整的 0,10, 10,1 概率空间,还自动学到了区分正负样本的最佳决策边界(由 bbb 决定)
2. Sigmoid 的非线性"饱和"特性 (抑制过度优化)
Sigmoid 是一条"S"型曲线,在中间(接近 000)时梯度很大,在两端(接近 111 或 000)时梯度极小甚至饱和:
- 对于容易的样本(例如极其不匹配的标签,余弦相似度极低):Sigmoid 的输出已经无限接近 000,此时梯度几乎为 000。模型会停止在这个已经做对的样本上浪费精力
- 对于困难/模糊的样本(刚好在匹配与不匹配的边缘):输入值处于 Sigmoid 的陡峭区,梯度很大,模型会集中精力优化(推开)这些难以区分的样本
- 如果不加 Sigmoid 直接用余弦相似度算 L1/L2 损失,模型就会不断去强行优化那些已经很完美的极端负样本,反而破坏了整体度量空间的结构
3. 匹配交叉熵损失的数学要求
单个样本的交叉熵(BCE)损失定义为:
L=−ylog(y\^)+(1−y)log(1−y\^) L = -\left y \\log(\\hat{y}) + (1 - y) \\log(1 - \\hat{y}) \\right L=−ylog(y\^)+(1−y)log(1−y\^)
直观上 BCE Loss 要求预测值必须在 (0,1) 范围内,而 Sigmoid 输出正好符合这一要求。而从数学上 Sigmoid+BCE 其实有更好的解释性:
现在我们对 BCE Loss 关于原始输入 zzz 求导:
∂L∂z=∂L∂y^⋅∂y^∂z \frac{\partial L}{\partial z} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} ∂z∂L=∂y^∂L⋅∂z∂y^
- 先算 BCE 对 y^\hat{y}y^ 的导数:
∂L∂y^=−(yy^−1−y1−y^)=y^−yy^(1−y^) \frac{\partial L}{\partial \hat{y}} = -\left( \frac{y}{\hat{y}} - \frac{1-y}{1-\hat{y}} \right) = \frac{\hat{y} - y}{\hat{y}(1-\hat{y})} ∂y^∂L=−(y^y−1−y^1−y)=y^(1−y^)y^−y
- 再乘上 Sigmoid 的导数:
∂L∂z=(y^−yy^(1−y^))⋅y\^(1−y\^) \frac{\partial L}{\partial z} = \left( \frac{\hat{y} - y}{\hat{y}(1-\hat{y})} \right) \cdot \\hat{y}(1-\\hat{y}) ∂z∂L=(y^(1−y^)y^−y)⋅y\^(1−y\^)
- 分母和分子完美约掉,得到最终极其简洁的梯度公式:
∂L∂z=y^−y \frac{\partial L}{\partial z} = \hat{y} - y ∂z∂L=y^−y
这说明模型底层的梯度,等于"模型的预测概率"与"真实标签"之间的线性差值! 正是因为这种数学上的巧妙抵消,避免了 Sigmoid 在两端饱和区域"梯度消失"的致命缺陷,使得模型训练既快又稳。这也是为什么在深度学习框架(如 PyTorch 的 BCEWithLogitsLoss)中,通常强烈建议将 Sigmoid 和 BCE 放在一起作为一个底层算子来计算,而不是在代码里分成两步写