Word2Vec 细节 负采样导致溢出的防范
问题
公式:
如果负样本碰巧是和中心词c极度相关的,向量相乘就会得到一个极大的数,sigmoid函数作用下就会变成0,而最终log0是一个无穷小,最终会不会导致数据溢出或崩溃?
解答
结论先行:理论上确实存在 \(-\infty\) 的风险,但在实际工程实现中,这个问题通过“数值稳定技巧”被彻底解决了,不会导致崩溃。 而且从优化角度看,这种情况恰恰是模型在“正确地惩罚错误”。
下面从三个层面详细解答:
1. 数学层面:为什么 \(-\infty\) 反而是"正确的"?
假设一个负样本 \(n_k\) 碰巧与中心词 \(c\) 语义极度相关(比如同义词),那么 \(\mathbf{u}_{n_k}^\top \mathbf{v}_c\) 是一个很大的正数。
此时目标函数中的这一项:
这意味着损失变得极大(因为我们是在最大化 \(J\),\(-\infty\) 是最差的情况)。这恰恰是正确的行为:
💡 负采样的设计意图就是"把不相关的词推开"。如果一个本该是负样本的词实际上和中心词很相似,模型就应该受到强烈惩罚,梯度会非常大,迫使 \(\mathbf{u}_{n_k}\) 远离 \(\mathbf{v}_c\)。
回顾第5节推导的梯度:
当点积很大时,\(\sigma(\cdot) \to 1\),梯度 \(\to -\mathbf{v}_c\),这是一个有限值!也就是说:
| 量 | 当负样本意外相似时的行为 | 是否危险 |
|---|---|---|
| 目标函数值 \(J\) | \(\to -\infty\) | ⚠️ 理论上有问题 |
| 梯度 \(\nabla J\) | \(\to -\mathbf{v}_c\)(有限) | ✅ 安全,且方向正确 |
梯度是有限的,所以 SGD 更新不会爆炸。模型会在下一步把这个负样本推开,下一轮迭代时该项就不再是 \(-\infty\) 了。
2. 工程层面:如何防止 \(\log(0)\) 导致的 NaN/Inf?
尽管梯度安全,但目标函数值变成 -inf 仍可能导致日志记录、早停判断等环节出错。所有成熟的实现都采用了以下数值稳定技巧:
技巧一:Clamp / Clip(最常用)
在计算 \(\log \sigma(x)\) 之前,对 \(\sigma(x)\) 的输出做截断:
# Gensim / PyTorch 等框架的标准做法
EPS = 1e-7
score = sigmoid(dot_product)
score = np.clip(score, EPS, 1.0 - EPS) # 永远不为 0 或 1
loss = np.log(score) # 安全!最小值为 log(1e-7) ≈ -16.1
这样 \(\log \sigma(\cdot)\) 的下界被锁定在约 \(-16.1\),永远不会出现 -inf。
技巧二:Log-Sigmoid 的稳定实现
直接计算 \(\log \sigma(x)\) 而不经过 \(\sigma\) 的中间步骤:
当 \(x\) 很大时,\(e^{-x} \to 0\),上式 \(\to 0\),数值安全。
当 \(x\) 很小时(即负样本意外相似,\(-x\) 是很大的负数),改写为:
PyTorch 的 F.logsigmoid 和 NumPy 的 scipy.special.logsumexp 内部就是用这种分段策略实现的:
# PyTorch 内部等价实现
def stable_log_sigmoid(x):
return -F.softplus(-x) # softplus(z) = log(1+exp(z)),数值稳定
技巧三:梯度裁剪(Gradient Clipping)
作为最后一道防线:
grad = np.clip(grad, -5.0, 5.0) # 即使梯度异常也限制在合理范围
3. 概率层面:这种情况发生的频率有多低?
负样本是从噪声分布 \(P_n(w) \propto U(w)^{3/4}\) 中随机采样的。对于一个给定的中心词 \(c\):
- 词表大小 \(V\) 通常为 \(10^5 \sim 10^6\)
- 与 \(c\) 真正语义相似的词最多几十到几百个
- 随机采样 \(K=5 \sim 20\) 个负样本,恰好采到语义相似词的概率约为:
即使发生了,如前所述,大的梯度会在一步之内把它推开,不会持续累积。
📌 总结
| 你的担忧 | 实际情况 |
|---|---|
| \(\log(0) = -\infty\) 会导致崩溃? | 理论上是的,但工程中用 clip / log-sigmoid 彻底规避 |
| 梯度会不会爆炸? | 不会。梯度公式中是 \(\sigma(\cdot)\) 而非 \(\log \sigma(\cdot)\),始终有界 |
| 负样本意外相似是 bug 吗? | 不是。这正是模型学习的机会,大梯度会快速纠正 |
| 需要自己处理吗? | 如果用 Gensim/PyTorch,框架已内置保护;如果纯 NumPy 手写,记得加 np.clip |
如果你正在手写 NumPy 实现,建议在 log(sigmoid(x)) 处加上 clamp,这是唯一一个需要手动防护的地方。其余部分的梯度计算天然数值稳定。

浙公网安备 33010602011771号