梯度函数决定学习规律
sin 和 tanh 之间的距离不是 0.76 到 3.06。是"自己打自己"和"方向从不出错"之间的距离。
一个失败的实验,一条意外的规律
Phase 1.0 的 RNN 实验测了 7 组 hidden_size,最佳 BLEU 3.06。到此为止,这是一个标准的调参实验。
然后我们做了一个看似无厘头的尝试:把 RNN 的激活函数从 tanh 换成 sin。
理由很朴素——sin 是周期函数。如果翻译本质是 hash 碰撞,周期函数天然支持"多桶"碰撞——同一个输出值可以从无限多个输入周期产生。tanh 只有一个桶(全局单调),sin 有无限个桶。理论上,多桶应该提供更多自由度。
结果:sin RNN 的最佳 BLEU = 0.76。 远低于 tanh 的 3.06。loss 从 6.32 跌到 6.19 就几乎不动了。
这不是"sin 比 tanh 差"这么简单。这个 0.76 里藏着一个更根本的问题。
梯度函数就是学习规律
神经网络的每一步训练都遵循同一个模式:
loss → 计算梯度 → 用梯度更新参数
而"计算梯度"这一步,激活函数的选择是决定性的一环。换个激活函数,就是换一套梯度规则——也就是换一种学习方式。
| 激活函数 | 梯度函数 | 梯度方向 | 学习特征 |
|---|---|---|---|
| tanh | tanh’(x) ∈ (0,1] | 永远为正,单调衰减 | 笨但稳——方向从不出错 |
| ReLU | 0 或 1 | 永远非负 | 莽但快——不衰减,但会死 |
| sin | cos(x) ∈ [-1,1] | 正负交替 | 聪明但自毁——自己否定自己 |
tanh’ 的范围是 (0,1]。值可以很小(接近饱和区),但符号不变。每一步梯度指向的方向,和全局最优方向一致。衰减只是让步子变小,不会让方向反了。
cos(x) 在每一个 π/2 的倍数处翻转符号。前半句学的方向,后半句亲手推翻。
模拟:为什么方向会反转
假设一段 RNN 的前激活值 z 随句子长度逐步偏移(模拟 W·h_{t-1} 的累积):
| 时间步 | z_t | tanh’(z) | 方向 | cos(z) | 方向 |
|---|---|---|---|---|---|
| 1 | 0.2 | +0.96 | → | +0.98 | → |
| 2 | 0.8 | +0.50 | → | +0.70 | → |
| 3 | 1.8 | +0.12 | → | −0.23 | ← |
| 4 | 2.5 | +0.02 | → | −0.80 | ← |
| 5 | 3.0 | +0.005 | → | −0.99 | ← |
tanh 所有时间步都往同一个方向推——力度越来越小,但方向一致。箱子被慢慢推到目标位置。
sin 在第 3 步翻转了方向。第 1-2 步往东推,第 3-5 步往西推。净位移接近零——箱子在原点附近来回跳舞。
这就是 BLEU=0.76 的根因。不是学不到东西,是学到的东西被自己亲手毁掉了。
好的梯度函数 = 自洽的向量场
把梯度想象成地形图上的箭头。每个参数位置都有一个箭头指向"下山"的方向——这就是向量场。
- tanh 的向量场:所有箭头指向同一个象限。可以弱,但方向一致。优化器只要跟着走,迟早到谷底。
- sin 的向量场:箭头来回翻转。优化器往前走两步,发现箭头指向回头了——刚走的路被否定。
“好的梯度函数"的标准不是"梯度够不够大”,而是"梯度方向是否和全局最优方向一致"。
tanh 的梯度小不是缺陷——它宁愿走得慢,也不走错方向。这是工程上的"慢就是快"。
loss 和 BLEU——两把不同的尺子
如果梯度函数的问题是方向,那还有一个更隐蔽的问题:梯度的方向是谁定的?
答案是 loss——CrossEntropy。但 CrossEntropy 追求的是"逐 token 预测精准",而 BLEU 考察的是"整句 n-gram 碰撞率"。两把尺子量的是不同的事。
H=512 跑 20 epoch 的铁证:
| epoch | loss | BLEU |
|---|---|---|
| 0 | 5.69 | 1.84 |
| 2 | 4.76 | 3.02 |
| 10 | 4.22 | 1.79 |
| 19 | 4.46 | 1.35 |
loss 持续下降(模型自认为越来越好),BLEU 在 epoch 2 达峰后一路衰退(实际翻译质量越来越差)。模型在 teacher forcing 的镜子里觉得自己很美,但别人看到的是东施效颦。
这意味着:即使我们选了一个"方向自洽"的梯度函数(如 tanh),梯度函数追的目标(loss)和最终评价标准(BLEU)之间存在结构性分歧。
BLEU 为什么不能直接当 loss
BLEU 的公式里有一个不可微的步骤——argmax。模型输出 logits 后,必须选一个词(argmax),然后在 n-gram 表里计数。argmax 是离散跳变:无论你把 cat 的概率从 0.4 提到 0.49,选出来的词不变——选的都是其他词。直到概率超过 0.51,argmax 的结果才会突然跳变。
所以 BLEU 对每个参数 W 的偏导链中,∂argmax/∂logits 这一项处处为零。整个梯度链直接断了。
全行业用 CrossEntropy 当 loss 不是因为 CE 比 BLEU “好”——是只有 CE 能传梯度。BLEU 更好,但数学不让你用。
Soft BLEU——让 BLEU 听到梯度的声音
如果把 argmax 换成 softmax,把硬 n-gram 计数换成概率加权:
$$\text{SoftBLEU} = \frac{\sum_{w} \min(p(w|ctx), c_{ref}(w))}{\sum_{w} p(w|ctx)}$$$p(w|ctx)$ 是模型预测 w 的连续概率。整条链可微——梯度可以直接从 BLEU 近似值流回模型参数。
训练时与 CrossEntropy 混合:
$$L_{total} = \lambda \cdot L_{CE} + (1-\lambda) \cdot (1 - \text{SoftBLEU})$$当前仅测试了 λ=0.5(均衡权重)。λ 的最优值、λ 的退火策略(从 1.0 逐步降)仍需实验验证。
四层 Hash 碰撞框架
把翻译过程看作四次 hash 碰撞:
| 层次 | hash 空间 | 碰撞判定 | 参与梯度? |
|---|---|---|---|
| 1. Embedding | E 维向量 | token→向量 | ✅ CrossEntropy |
| 2. Hidden (RNN) 或 Attention (Transformer) | H 维向量 | 整句→压缩向量 / 选择性碰撞 | ✅ CrossEntropy |
| 3. Decoder output | V 维 logits | 向量→token | ✅ CrossEntropy |
| 4. BLEU | n-gram 表 | 输出←→参考 | ❌ 不可微 |
前三次碰撞都参与梯度。第四次——BLEU 碰撞——模型根本听不到。
RNN 的悲剧是第 2 层(时间轴压缩破坏信息结构),Transformer 换了第 2 层(Attention 选择性碰撞),但第 4 层的问题——BLEU 不参与梯度——Transformer 也没解决。
实验结果:SoftBLEU 突破 CE 天花板
H=512, E=128, seed=42, batch=64, 在纯 RNN 上对比 CE-only 和 SoftBLEU 混合损失:
$$L_{total} = \lambda \cdot L_{CE} + (1-\lambda) \cdot (1 - \text{SoftBLEU}), \quad \lambda = 0.5$$| epoch | CE-only BLEU | CE | SoftBLEU BLEU | mixed loss | SoftBLEU 值 |
|---|---|---|---|---|---|
| 0 | 3.06 | 6.14 | 2.49 | 3.47 | 0.204 |
| 1 | 2.31 | 5.38 | 3.21 | 3.07 | 0.234 |
| 2 | 1.73 | 5.08 | 1.81 | 2.92 | 0.237 |
| 3 | 2.40 | 4.88 | 2.88 | 2.82 | 0.238 |
| 4 | 1.91 | 4.74 | 2.59 | 2.75 | 0.239 |
SoftBLEU best BLEU = 3.21(epoch 1),CE-only best = 3.06(epoch 0)。首次突破纯 CE 天花板。
SoftBLEU 值自身从 0.204 → 0.239 逐步提升——模型在学会优化 BLEU 的 soft 近似。但提升幅度只有 +0.15,随后也陷入了过拟合。
向量化是关键。最初的 Python 循环版 2 小时跑不出一个 epoch(详见 第六篇)。用 scatter_add_ 替代 4 层 Python 循环后,5 epoch 仅需 5 分钟,速度提升 1000 倍。
这条路通向哪里
sin 实验的失败,不是告诉我们"sin 不好"。它告诉我们:梯度函数的选择不是调参——是选择一种学习规律。 一个不能自洽的梯度函数,再大的模型也救不回来。
tanh 的梯度方向自洽,但目标是 loss 而非 BLEU。模型沿着 loss 的梯度方向走得太远,BLEU 反而掉进了过拟合的沟。这条沟就是 Soft BLEU 要填的。
后来的实验完整验证了这条规律——从 RNN (BLEU=3.0) 到 Attention (3.77) 再到 Transformer (11.70),架构在变,但 hash 编码 + 梯度积分的底层逻辑不变。K_lang 在弱模型上补缺陷,在强模型上多此一举。梯度方向的正确性,从 sin→tanh 到 CE→SoftBLEU 到词级→BPE,是一条贯穿 9 篇文章的红线。
DAG 的血——为什么多项式不是前提,可用 backward() 才是
神经网络的训练本质是一个图上的血液系统:
DAG 骨架 = 拓扑结构(矩阵运算的依赖关系)
grad 血液 = 方向信号(∂L/∂W 沿树往回流动)
矩阵肌肉 = 存储信息(被梯度反复拉扯,最终成形)
ReLU 不是多项式——分段线性,x=0 有折角,数学上不可导。但它能进 DAG——PyTorch 约定折角处梯度为 0,一根人造血管填了那个洞。BLEU 的 argmax 进不了 DAG——不是因为它不光滑,是因为它的 backward 被设计成返回全零。血凝固了。但如果给 argmax 一个人造的 backward:
argmax backward (0/1 风格):
预测词 ∈ reference → grad = 1
预测词 ∉ reference → grad = 0
它就有了血。矩阵就能动。数学纯度不重要,方向正确才重要。 sin 失败不是因为进不了 DAG——它是真导数。它失败是因为 cos 正负交替,方向自己打自己。
神经网络的训练,只需要两个条件:血在流(不全零),血的方向不对冲(不反转)。满足这两条,不管信号来自微积分、次梯度、还是 0/1 人造血,矩阵都会进化。
梯度训练 = Hash 编码 + Riemann 积分
神经网络的训练,本质上是把语言逐词逐句 hash 编码进权重张量。每一步梯度是当前 batch 的语言统计快照,训练过程就是把这些快照永久存储:
$$W_T = W_0 + \sum_{t=0}^{T} (-\eta \cdot \nabla \mathcal{L}_t) \quad \xrightarrow{\eta \to 0} \quad W_0 + \int_0^T \nabla \mathcal{L}(t) \, dt$$每一个 optimizer.step() 的执行流程:
- 词 → embedding hash:token → $\mathbb{R}^d$ 向量,查表
- 前向撞桶:hidden 层把向量推过 attention/FFN,到 output 层做 softmax 查桶
- 比对碰撞:预测桶 vs 目标桶 → 算出误差
- 反向修正:$\nabla\mathcal{L}$ 告诉你桶壁该往哪边挪
- 积分存储:$W \mathrel{-}= \eta \cdot \nabla\mathcal{L}$ 把这次碰撞经验热记录进权重
这就是 DAG 之血的完整循环:血液(梯度)在骨架(DAG)上流动,每次循环把一次 hash 碰撞的教训写入肌肉(权重矩阵)。
架构和损失函数——同一枚硬币的两面
在 hash 编码框架下,架构和损失函数不再是竞争关系:
- 架构改变 → hidden_seq 的表示能力变了 →
∂L/∂W经由不同路径回传 → 梯度的接收面不同 - 损失函数改变 → L 的定义变了 →
∂L/∂W的量级和方向变了 → 梯度的来源不同
Attention 上用 K_lang +0.18 BLEU,Transformer 上一轮架构跳变就 +7.7 BLEU。不是因为 Architecture > Loss,而是因为 Attention 的瓶颈在表示能力(LSTM 时序链),不是梯度方向。当架构表达力够了,但梯度目标离 BLEU 太远——这就是 SoftBLEU / K_lang 该上场的时候。
梯度的多寡和方向才是学习的唯一原料。架构和损失函数只是不同的食谱。
在这个框架下,所有调试的变量都有了统一的物理意义:
| 变量 | 物理意义 |
|---|---|
| 激活函数(tanh/sin/ReLU) | 梯度方向的符号——是自洽推进还是自我否定 |
| 损失函数(CE/SoftBLEU) | 碰撞的"靶心"定义——逐 token 精准 vs n-gram 覆盖 |
| K_lang(k) | 每次积分时,多少个桶参与修正——噪声滤波器 |
| lr(η) | Riemann 积分的步长(dt) |
| 深度(n_layers) | hash 链的长度——每个 token 经历多少次碰撞 |
| 宽度(d_model) | hash 桶的容量——每个桶能存多少信息 |
| BPE vs 词级 | hash 桶的大小选择——大桶少量 vs 小桶多量 |
其中 lr 和 K_lang 是耦合的——同一个参数更新公式 ΔW = η · ∂L/∂W 里,η 决定走多远,K_lang 决定梯度分配到哪些桶。桶数错了,lr 再调也白搭。
频域梯度——用 IFFT 把离散 BLEU 变成可微曲面
HM 提出一个新的视角:离散函数不可导,不是因为"离散不好"——是微积分不自洽。如果换个数学基底,离散也许就可导了。
傅里叶逆变换就是把时域的离散方波变成频域的正弦波叠加:
$$\text{BLEU}(t) \approx \sum_k c_k \cdot e^{i\omega_k t}$$原信号是二值的(0 或 1,n-gram 碰上了没有),IFFT 重建后变成一个带限连续函数——在采样点之间自动做了光滑插值。
梯度会学到什么? 不是每个 token 该改什么——是整句的频谱结构应该向哪个方向偏移:
| 频段 | 物理意义 | 梯度信号 |
|---|---|---|
| 低频 | 句长、全局流畅度 | brevity penalty 方向 |
| 中频 | 2~4 gram 词组边界 | 词序排列方向 |
| 高频 | token 噪声 | 过拟合指标 |
IFFT 的梯度天然多尺度——同时看低频和高频,不会像 CE 那样在 token 级别上过拟合。本质上是给 BLEU 套了一个频域的正则化外壳——在频率空间里,离散跳变被展开成了光滑的正弦基底。
待验证:Phase 1 RNN 上跑 CE + DCT 频谱损失对照实验。
May the Code be with us.
License: GPLv3
本文《SameTime》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发,但必须继承相同的开源协议,承认在算力宇宙中所有的迭代与变异。