<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:ap="https://www.grepcode.cn/ns/ai-papers">
  <channel>
    <title>SameTime WMT 专题 on GrepCode</title>
    <link>https://www.grepcode.cn/wmt.html</link>
    <description>Recent content in SameTime WMT 专题 on GrepCode</description>
    <image>
      <title>GrepCode</title>
      <url>https://www.grepcode.cn/img/treeheap-research-series.png</url>
      <link>https://www.grepcode.cn/img/treeheap-research-series.png</link>
    </image>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 09 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.grepcode.cn/wmt/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[WMT-008] Phase 6 Transformer——从 3 到 11 的跃迁</title>
      <link>https://www.grepcode.cn/wmt/008-wmt-phase6-transformer.html</link>
      <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/008-wmt-phase6-transformer.html</guid>
      <description>Attention 天花板 3.77，Transformer 词级 11.49，BPE 小模型 10.66。下一步：BPE d512 大模型追学术基线 20-25。</description>
      <content:encoded><![CDATA[<h1 id="phase-6-transformer从-3-到-11再到学术基线">Phase 6 Transformer——从 3 到 11，再到学术基线</h1>
<blockquote>
<p>Attention 跑了 15 组实验，最高 3.77。Transformer 第一轮就 11.49。</p></blockquote>
<h2 id="为什么-transformer-秒杀-attention">为什么 Transformer 秒杀 Attention</h2>
<p>不是因为 Transformer 更聪明——是因为 Attention 的 decoder 是 Python for-loop，而 Transformer 是<strong>全向量化</strong>的。</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Phase 2 Attention</th>
          <th>Phase 6 Transformer</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Decoder</td>
          <td>for t in range(T): attention per step</td>
          <td><code>torch.matmul(Q, K.T)</code> 一气呵成</td>
      </tr>
      <tr>
          <td>速度</td>
          <td>~15 min/epoch</td>
          <td>~3 min/epoch</td>
      </tr>
      <tr>
          <td>最优 BLEU</td>
          <td>3.77 (A15, 双头+α=0.8)</td>
          <td>11.49 (词级, ep8)</td>
      </tr>
  </tbody>
</table>
<p>速度差 5 倍，BLEU 差 3 倍。这两个差距其实同源：<strong>计算效率决定了你能跑多少 epoch、多少数据、多深的网络。</strong></p>
<h2 id="phase-6-实验矩阵">Phase 6 实验矩阵</h2>
<p>全部 30 epoch, BPE 8K, label smoothing 0.1, warmup scheduler：</p>
<table>
  <thead>
      <tr>
          <th>E-#</th>
          <th>实验</th>
          <th>配置</th>
          <th>Params</th>
          <th>BLEU</th>
          <th>分析</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>E-024</td>
          <td>B0 词级</td>
          <td>d256/3L/4H</td>
          <td>53.6M</td>
          <td>11.49</td>
          <td>词级 baseline</td>
      </tr>
      <tr>
          <td>E-025</td>
          <td>B1 BPE</td>
          <td>d256/3L/4H</td>
          <td>11.6M</td>
          <td>10.66</td>
          <td>BPE 小模型反降</td>
      </tr>
      <tr>
          <td>E-026</td>
          <td>B2 BPE</td>
          <td>d512/6L/8H</td>
          <td>56.4M</td>
          <td>8.18</td>
          <td>过拟合——参数过多</td>
      </tr>
      <tr>
          <td>E-028</td>
          <td>B4 BPE</td>
          <td>d512/3L/8H</td>
          <td>~30M</td>
          <td>8.57</td>
          <td>宽不加深，仍过拟合</td>
      </tr>
      <tr>
          <td>E-029</td>
          <td>B5 BPE</td>
          <td>d256/4L/4H</td>
          <td>~14M</td>
          <td>11.52</td>
          <td>B1→B3 中间点</td>
      </tr>
      <tr>
          <td><strong>E-027</strong></td>
          <td><strong>B3 BPE</strong></td>
          <td><strong>d256/6L/4H</strong></td>
          <td><strong>17.2M</strong></td>
          <td><strong>11.70</strong></td>
          <td>✅ <strong>最优</strong></td>
      </tr>
  </tbody>
</table>
<h3 id="b3-的启示">B3 的启示</h3>
<p>B3 (d256/6L) 用 17.2M 参数跑出 11.70 BLEU——<strong>BPE 首次超过词级 baseline (11.49)</strong>。</p>
<p>关键发现：</p>
<ul>
<li><strong>深度 &gt; 宽度</strong>：6 层 256 维 (17M) &gt; 3 层 512 维 (56M) ——参数效率差 3 倍</li>
<li><strong>160K 句子匹配 ~15M 参数</strong>：B2 的 56M 严重过拟合，B3 的 17M 刚好</li>
<li>BPE 的真正优势在中型模型：vocab 效率释放了 embedding 参数，让每层能更宽更深</li>
</ul>
<h2 id="通往学术基线的路">通往学术基线的路</h2>
<p>IWSLT14 De-En 上，标准 Transformer 论文配置（d512, 6 layers, 8 heads, BPE 8K）通常达到 20-25 BLEU。我们的路线：</p>
<table>
  <thead>
      <tr>
          <th>步骤</th>
          <th>改动</th>
          <th>预期 BLEU</th>
          <th>E-#</th>
          <th>状态</th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>B0</td>
          <td>词级 d256/3L</td>
          <td>11.49</td>
          <td>E-024</td>
          <td>✅</td>
          <td></td>
      </tr>
      <tr>
          <td>B1</td>
          <td>BPE d256/3L</td>
          <td>10.66</td>
          <td>E-025</td>
          <td>✅</td>
          <td></td>
      </tr>
      <tr>
          <td>B2</td>
          <td>BPE d512/6L/8H</td>
          <td>8.18</td>
          <td>E-026</td>
          <td>✅ 过拟合</td>
          <td></td>
      </tr>
      <tr>
          <td><strong>B3</strong></td>
          <td><strong>BPE d256/6L</strong></td>
          <td><strong>11.70</strong></td>
          <td><strong>E-027</strong></td>
          <td>✅ <strong>最优</strong></td>
          <td></td>
      </tr>
      <tr>
          <td>B4</td>
          <td>BPE d512/3L</td>
          <td>8.57</td>
          <td>E-028</td>
          <td>✅ 宽过浅</td>
          <td></td>
      </tr>
      <tr>
          <td>B5</td>
          <td>BPE d256/4L</td>
          <td>11.52</td>
          <td>E-029</td>
          <td>✅</td>
          <td></td>
      </tr>
      <tr>
          <td><strong>B6</strong></td>
          <td><strong>BPE d256/6L + K_lang</strong></td>
          <td><strong>~8.1</strong></td>
          <td><strong>E-030</strong></td>
          <td>❌ 反降</td>
          <td></td>
      </tr>
      <tr>
          <td>—</td>
          <td>TW/AW 系列 (WMT14)</td>
          <td>进行中</td>
          <td>E-031+</td>
          <td>🔄</td>
          <td></td>
      </tr>
  </tbody>
</table>
<h2 id="从-bleu-3-到-11我们学到了什么">从 BLEU 3 到 11，我们学到了什么</h2>
<p><strong>1. 架构和损失函数是一体两面——都是梯度的产生方式</strong></p>
<p>Phase 2 花了 15 组实验 (A1-A15) 在损失函数上反复调试。SoftBLEU、K_lang、双头梯度相乘——最好的一组 A15 达到 3.77 (+9.3% vs CE-only)。Transformer 第一轮就用纯 CE 跑到 11.49。</p>
<p>看起来像是&quot;架构 &gt; 损失函数&quot;。但其实不是。架构和损失函数是同一个东西的两面：<strong>都是定义了梯度的产生方式。</strong></p>
<ul>
<li>架构改变 → <code>hidden_seq</code> 的表示能力变了 → <code>∂L/∂W</code> 经由不同路径回传 → 梯度的<strong>接收面</strong>不同</li>
<li>损失函数改变 → <code>L</code> 的定义变了 → <code>∂L/∂W</code> 的量级和方向变了 → 梯度的<strong>来源</strong>不同</li>
</ul>
<p>Attention 上用 K_lang + 0.18 BLEU，Transformer 上一轮架构跳变就 +7.7 BLEU——这不是因为 Architecture &gt; Loss，而是因为 Attention 的瓶颈在表示能力（LSTM 时序链），不是损失函数的梯度。换个场景，当架构的表达力已经够用，但数据分布有偏斜，调损失函数就是唯一的出路。</p>
<p><strong>梯度的多寡和方向才是学习的唯一原料。架构和损失函数只是不同的食谱。</strong></p>
<p><strong>2. BPE 在小模型上反效果——但不是永远的</strong></p>
<p>BPE 的 8K vocab 把 embedding 参数从 33.5M 砍到 4M——在 d256/3L 模型上，总参数从 53.6M 缩到 11.6M。浅模型容量被压过了 BPE 的子词泛化收益。</p>
<p>但 B3 (d256/6L, 17.2M) 用 BPE 跑到 11.70，首次盖过词级 baseline (11.49)。<strong>BPE 的桶压缩需要深度补偿——这一点恰恰印证了 K_lang 框架。</strong></p>
<p><strong>3. K_lang 在 Transformer 上失效——SoftBLEU 是缺陷补偿器</strong></p>
<p>B6 (d256/6L + K_lang, k=170, α=0.8)：BLEU 止步于 ~8.1。同样架构不用 K_lang 跑 11.70。</p>
<p>K_lang 在 RNN (BLEU 3.0→3.21, +7%) 上有效，在 Attention (3.45→3.77, +9%) 上有效。但在 Transformer (11.70→8.1, −31%) 上反降。模式很清楚：</p>
<ul>
<li><strong>基线越强，K_lang 增益越小</strong>。RNN 的 CE 梯度是弱鸡（BLEU 3），K_lang 的 333× 放大确有作用。Transformer 的 CE 梯度已经足够精准，SB 头的梯度穿过 6 层 Transformer，<strong>打乱了 CE 辛苦优化的 attention 模式</strong>。</li>
<li>SoftBLEU 不是通用增强器，是缺陷补偿器。<strong>当模型本身表达力不足时它帮忙，当模型够强时它捣乱。</strong></li>
</ul>
<p><strong>4. 梯度训练 = Hash 编码 + Riemann 积分</strong></p>
<p>训练一个 Transformer，本质上就是把语言<strong>逐词逐句 hash 编码进权重张量</strong>，每一步梯度是当前 batch 的语言统计快照，训练过程就是把这些快照永久存储：</p>
$$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$$<p>每一个 <code>optimizer.step()</code> 的执行流程：</p>
<ol>
<li><strong>词 → embedding hash</strong>：token → $\mathbb{R}^d$ 向量，查表</li>
<li><strong>前向撞桶</strong>：hidden 层把向量推过 6 层 attention + FFN，到 output 层做 softmax 查桶</li>
<li><strong>比对碰撞</strong>：预测桶 vs 目标桶 → <code>CrossEntropy</code> 算出误差</li>
<li><strong>反向修正</strong>：$\nabla\mathcal{L}$ 告诉你桶壁该往哪边挪</li>
<li><strong>积分存储</strong>：$W \mathrel{-}= \eta \cdot \nabla\mathcal{L}$ 把这次碰撞经验<strong>热记录</strong>进权重</li>
</ol>
<p>50 epoch × 2500 batch/epoch = 125,000 次积分。这 125,000 次 hash 碰撞的逐次累积，把 160K 句 IWSLT14 的全部语言统计模式寄存到了 17M 个参数里。</p>
<p>在这个框架下，所有我们调试的变量都有了统一的物理意义：</p>
<table>
  <thead>
      <tr>
          <th>变量</th>
          <th>物理意义</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>K_lang（k）</strong></td>
          <td>每次积分时，多少个桶参与修正（桶太少→信息丢失，桶太多→噪声稀释）</td>
      </tr>
      <tr>
          <td><strong>α（SB 权重）</strong></td>
          <td>SB head 的碰撞修正力度</td>
      </tr>
      <tr>
          <td><strong>lr（η）</strong></td>
          <td>Riemann 积分的步长（dt）</td>
      </tr>
      <tr>
          <td><strong>深度（n_layers）</strong></td>
          <td>hash 链的长度——每个 token 经历多少次碰撞</td>
      </tr>
      <tr>
          <td><strong>宽度（d_model）</strong></td>
          <td>hash 桶的容量——每个桶能存多少信息</td>
      </tr>
      <tr>
          <td><strong>BPE vs 词级</strong></td>
          <td>hash 桶的大小选择——大桶少量 vs 小桶多量</td>
      </tr>
  </tbody>
</table>
<p><strong>3. 早停 + 倒 U 是所有模型的共有特征</strong></p>
<p>无论是 Phase 2 Attention (ep2 峰) 还是 Phase 6 词级 Transformer (ep8 峰)，BLEU 都呈现峰值后回落。这不是过拟合——loss 和 BLEU 同时变好。这是 teacher forcing 的 exposition bias。</p>
<p><strong>4. 日志就是 infrastructure</strong></p>
<p>Loki 驱动 + Grafana 看板 = 看一眼就知道实验跑到哪了。没有基础设施，3 个实验 15 个 epoch 的结果能让你翻半天日志。花 1 天修基础设施省 10 天找日志。</p>
<h2 id="更新日志">更新日志</h2>
<table>
  <thead>
      <tr>
          <th>日期</th>
          <th>实验</th>
          <th>结果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2026-05-05</td>
          <td>BPE d256/3L (B1)</td>
          <td>10.66</td>
      </tr>
      <tr>
          <td>2026-05-05</td>
          <td>词级 d256/3L (B0)</td>
          <td>11.49</td>
      </tr>
      <tr>
          <td>2026-05-06</td>
          <td>BPE d512/6L (B2)</td>
          <td>8.18 — 过拟合</td>
      </tr>
      <tr>
          <td>2026-05-06</td>
          <td>BPE d256/6L (B3)</td>
          <td><strong>11.70</strong> — 最优</td>
      </tr>
      <tr>
          <td>2026-05-06</td>
          <td>BPE d512/3L (B4)</td>
          <td>8.57</td>
      </tr>
      <tr>
          <td>2026-05-06</td>
          <td>BPE d256/4L (B5)</td>
          <td>11.52</td>
      </tr>
      <tr>
          <td>2026-05-06</td>
          <td>BPE d256/6L + K_lang (B6)</td>
          <td>8.02 — SoftBLEU 缺陷补偿器</td>
      </tr>
  </tbody>
</table>
<p><strong>核心发现</strong>：深度 &gt; 宽度。d256/3L→4L→6L 步步上升 (10.66→11.52→11.70)，但 d512/3L 和 d512/6L 双双过拟合。160K 句的 IWSLT14 上，17M 参数的 d256/6L 是甜点。</p>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发，但必须继承相同的开源协议，承认在算力宇宙中所有的迭代与变异。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-010] Linear = Hash 函数——输出层碰撞是翻译的信息瓶颈</title>
      <link>https://www.grepcode.cn/wmt/010-wmt-linear-hash.html</link>
      <pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/010-wmt-linear-hash.html</guid>
      <description>NMT 的输出 Linear 层不只是投影——它是 hidden space 到 vocab space 的 Hash 函数。碰撞发生在这里，K_lang 控制碰撞程度，梯度从碰撞桶里提取信息。</description>
      <content:encoded><![CDATA[<h1 id="linear--hash-函数输出层碰撞是翻译的信息瓶颈">Linear = Hash 函数——输出层碰撞是翻译的信息瓶颈</h1>
<blockquote>
<p>512 维 hidden 撞进 32K 维词汇表：碰撞率 K_lang ≈ 0.003，有效碰撞桶 ~170 个。Linear 不是模型最后一步投影——它是翻译信息从连续空间到离散词汇的 Hash 函数。</p></blockquote>
<h2 id="碰撞在哪里">碰撞在哪里？</h2>
<p>NMT 的输出流程三步：</p>
<pre tabindex="0"><code>① Transformer:  输入序列 → h_t ∈ ℝ^512       （hidden space）
② Linear:       h_t → z = W·h_t + b         （logits in ℝ^{32000}）
③ Softmax:      z → p ∈ Δ^{31999}           （概率单纯形）
④ Decode:       argmax / beam → token        （离散词）
</code></pre><p>每个人都在关注 ③ 和 ④。但<strong>碰撞发生在 ②</strong>。</p>
<h2 id="linear--hash-function">Linear = Hash Function</h2>
<p><code>W</code> 是一个 </p>
$$\mathbb{R}^{32000 \times 512}$$<p> 的矩阵。每一行 </p>
$$W_i \in \mathbb{R}^{512}$$<p> 是第 i 个 token 的嵌入向量（output embedding）。</p>
$$W·h_t$$<p> 做的是内积：</p>
$$z_i = \langle W_i, h_t \rangle + b_i$$<p>把 512 维的 hidden 投影到 32K 维——32K 个内积。</p>
<p><strong>这是 Hash 函数的标准定义：</strong> 把高维向量压缩到低维离散空间。</p>
$$h_t \xrightarrow{W} z \xrightarrow{\text{argmax}} \text{token}$$<p>参数 <code>W</code> 决定了 hidden 空间中哪些方向对应哪些 token。512 维的 hidden 无法精确表示 32K 个 token 的语义——<strong>碰撞是必然的。</strong></p>
<h2 id="碰撞率--k_lang">碰撞率 = K_lang</h2>
<p>32K 个 token 共享 512 个维度，内积 </p>
$$W_i·h_t$$<p> 受限于 hidden 的信息容量。有意义地区分的 token 数：</p>
$$N_{\text{collision}} = K_{\text{lang}} \times |V| = 0.003 \times 32000 \approx 96$$<p>BPE 词表下（8K）：</p>
$$N_{\text{collision}} = 0.003 \times 8000 \approx 24$$<p>碰撞桶从 170 缩到 24——但每个桶承担的语义负载叠加了 7 倍。</p>
<table>
  <thead>
      <tr>
          <th>词表类型</th>
          <th>|V|</th>
          <th>K_lang×|V|</th>
          <th>每桶语义负载</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Word-level (IWSLT)</td>
          <td>56K</td>
          <td><strong>170</strong></td>
          <td>轻</td>
      </tr>
      <tr>
          <td>BPE (IWSLT)</td>
          <td>8K</td>
          <td><strong>24</strong></td>
          <td>重</td>
      </tr>
  </tbody>
</table>
<p>IWSLT14 的 BPE 实验证实：8K 词表下，3 个 transformer 层不够深（B0, BLEU=10.66），但 6 层可以（B3, BLEU=11.70）。<strong>深度补偿了桶压缩。</strong></p>
<h2 id="梯度怎么从碰撞桶里提取信息">梯度怎么从碰撞桶里提取信息？</h2>
<p>CE loss 对 </p>
$$z_i$$<p> 的梯度是 </p>
$$p_i - y_i$$<p>。对 </p>
$$W_i$$<p> 的梯度是：</p>
$$\frac{\partial \mathcal{L}}{\partial W_i} = (p_i - y_i) \cdot h_t$$<p>每个 token 的梯度向量与 </p>
$$h_t$$<p> 平行。<strong>梯度只沿 hidden 方向更新</strong>，而 hidden 本身是高维压缩——所有 32K 个 token 的梯度都投影到同一个 512 维空间上，再分配回各自的 </p>
$$W_i$$<p> 行。</p>
<p><code>y_i</code> 为 1 的 token（真实标签）和 <code>p_i</code> 较大的 token（top-k collision set）得到显著梯度更新。其余大多数 token 的 </p>
$$p_i \approx 0$$<p>，</p>
$$(p_i - y_i) \approx 0$$<p>，近乎 0 更新。</p>
<p><strong>碰撞桶里的 token 共享 hidden 空间的方向信息。</strong> Softmax 不做的事情是：它不分配方向——它只归一化大小。方向是 Linear 在投影时决定的。</p>
<h2 id="回到我们实验的倒-u-曲线">回到我们实验的倒 U 曲线</h2>
<p>A8-A12 的 K_lang 扫描结果就是对这个理论最好的验证：</p>
<pre tabindex="0"><code>A8(CE-only)  BLEU=3.45     ← K_lang 全部分流失（无 collision 意识）
A9(k=50)     BLEU=3.35     ← 桶太少，过拟合
A10(k=170)   BLEU=3.63     ← K_lang 精确匹配碰撞桶数
A11(k=500)   BLEU=3.09     ← 噪音开始回流
A12(k=56652) BLEU=3.35     ← 全表 softmax，无约束，信号消失
</code></pre><p>线性层的输出 <code>z_i</code> 的 top-k 选择（k = K_lang×|V| ≈ 170）不是随意选的——它是碰撞常数决定的。把 softmax 分母限制在碰撞桶内，梯度不再被稀释到无关 token 上。</p>
<h2 id="碰撞是双向的正反向-hash-不对称">碰撞是双向的——正反向 Hash 不对称</h2>
<p>Linear 的碰撞不在单方向上。正向和反向的 Hash 路径是<strong>同一组参数</strong>，但经历<strong>不同的约束条件</strong>：</p>
<pre tabindex="0"><code>Token (DE) → Encoder → h_t → W·h_t + b → Token (EN)
                              ↑ 碰撞发生在这里
                    向量回传 ← z_i = &lt;W_i, h_t&gt;
</code></pre><h3 id="正向释放">正向：释放</h3>
<p>hidden 编码了一段包含多种语义的向量。Linear 把它释放到 32K 维词空间——<strong>每个 token 得到自己的内积分量</strong>。这个过程是稀疏的：一次翻译只输出一个 token，其他 31,999 个概率接近 0。</p>
<h3 id="反向碰撞">反向：碰撞</h3>
<p>反向看同一个 W。在训练中，<strong>同一个 hidden 表示对应多个目标 token</strong>——不同的语言对同一个概念映射到同一个 hidden 空间。</p>
<p>以名词为例。名词的翻译大多是一一对应的：</p>
<pre tabindex="0"><code>德语 &#34;Haus&#34;  →  英语 &#34;house&#34;    同一个概念
德语 &#34;Buch&#34;  →  英语 &#34;book&#34;     同一个概念
</code></pre><p>当 &ldquo;Haus&rdquo; 和 &ldquo;house&rdquo; 分别用不同的 token ID 表示、但经过同一组 hidden → Linear 的重量时，梯度会把这两个 token 的 </p>
$$W_i$$<p> 推向同一个方向——<strong>因为它们的 hidden 表示在语义空间里是相同的位置。</strong></p>
<p>但 W 只有 512 维。两个名词挤在一起、三个名词挤在一起……所有翻译中一一对应的名词，都把 W 的对应行推到 hidden 空间的同一个区域。<strong>碰撞就此发生。</strong></p>
<p>反向碰撞不是坏事——它恰恰是翻译模型学会 &ldquo;Haus = house&rdquo; 的方式。但如果碰撞太重（桶太小），token 之间开始混淆；如果碰撞太轻（全表），梯度被稀释到无关 token 上。</p>
<p>翻译的全过程：</p>
<pre tabindex="0"><code>① 编码: 源语言 → hidden      (压缩语义到连续空间)
② Hash: hidden → W·h_t       (连续→离散投影，碰撞发生)
③ 释放: z → 概率 → token     (稀疏表达在词空间)
</code></pre><p>碰撞不在第 ① 步（Transformer 本体），不在第 ③ 步（softmax 归一化），就在第 ② 步——<strong>Linear 把连续的 hidden 映射到离散的词汇空间</strong>。这是正反向不对称的核心。</p>
<h2 id="结论">结论</h2>
<p><code>Linear(h_t) = logits_z</code> 在代码里就一行。但它下面是：</p>
$$\mathbb{R}^{512} \xrightarrow{\text{Hash}} \{1, 2, \dots, 32000\}$$<p>翻译的整个信息瓶颈就在这里——<strong>不是在 Transformer 的 attention 层，不是 decoder 的交叉注意力，是编码器最后一层 linear 把 continuous hidden 投射到 discrete vocab 的那一步。</strong></p>
<p>K_lang 不是一个调参经验值——它是这个 Hash 函数的理论碰撞常数。</p>
<h2 id="过拟合--碰撞桶太散">过拟合 = 碰撞桶太散</h2>
<p>从朴素的线性回归看过拟合：</p>
$$\hat{y} = w_0 + w_1x + w_2x^2 + \dots + w_{10}x^{10}$$<p>问题：</p>
$$w_8, w_9, w_{10}$$<p> 这些高频项的参数<strong>变得太大</strong>——它们不是在学习信号，是在记忆每个训练点的位置。曲线扭来扭去，拟合了噪声。</p>
<p>NMT 里的过拟合本质上同一个东西，只是表现形式不同：</p>
<pre tabindex="0"><code>线性回归:  高频项系数太大 → 记住了噪声的多项式
NMT:       碰撞桶太散 → 每个训练句独占一个 hash 桶 → 记住了训练样本本身
</code></pre><p>证据来自 B 系列的深度和宽度对比：</p>
<table>
  <thead>
      <tr>
          <th>实验</th>
          <th>配置</th>
          <th>参数</th>
          <th>BLEU</th>
          <th>现象</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>B3</td>
          <td>d256/6L</td>
          <td>17M</td>
          <td><strong>11.70</strong> ✅</td>
          <td>碰撞有效，泛化正常</td>
      </tr>
      <tr>
          <td>B2</td>
          <td>d512/6L</td>
          <td>56M</td>
          <td>8.18</td>
          <td>参数太多，过拟合</td>
      </tr>
      <tr>
          <td>B4</td>
          <td>d512/3L</td>
          <td>56M</td>
          <td>8.57</td>
          <td>参数多+浅度，双倍失败</td>
      </tr>
  </tbody>
</table>
<p>B2 有 56M 参数——在 IWSLT 的 160K 句上，每个训练样本可以独占几乎一个 hash 桶。W 有足够维度同时记住每对的德语句子和英语翻译。高频词（&ldquo;的&rdquo;、&ldquo;了&rdquo;、&ldquo;is&rdquo;、&ldquo;the&rdquo;）的梯度主导了反向传播，低频但有语义的名词<strong>被淹没在参数冗余里</strong>。</p>
<p>B3 只有 17M 参数——因为碰撞被强制发生，&ldquo;Haus&rdquo; 和 &ldquo;Gebäude&rdquo; 必须共享参数。模型被迫去学习：&ldquo;这两个德语词在语义上是相近的&rdquo;，而不是死记 &ldquo;Haus = house&rdquo;。</p>
<p><strong>K_lang 就是要保证碰撞桶够密、参数不能太大——让模型被迫去学语义共性，而不是死记训练样本。</strong> 这条线和线性回归里&quot;控制多项式项数防止过拟合&quot;完全平行。</p>
<hr>
<h2 id="moe--词频--按语言特征路由的碰撞桶">MoE + 词频 = 按语言特征路由的碰撞桶</h2>
<p>Hidden 不一定必须是一个矩阵。MoE（Mixture of Experts）早就把一个 FFN 拆成多个专家：</p>
<pre tabindex="0"><code>标准 Transformer:   h → 一个 FFN → 输出
MoE Transformer:    h → 路由 → 选专家 → 专家 FFN → 输出

             h ─┬─→ 专家1 (第0-2层共享)
                ├─→ 专家2 (第3-4层共享)    ← gating 网络根据 h 选
                └─→ 专家3 (第5-6层共享)
</code></pre><p>已有工作（Shazeer et al. 2017, GShard, Switch Transformer）按 <strong>token 级别</strong>做路由：每个 token 被分配到一个专家 FFN。但没有人按<strong>语言特征</strong>来划分专家：</p>
<ul>
<li>专家 A：短句（1-10 词）专用的 hidden 矩阵</li>
<li>专家 B：长句（30+ 词）专用的 hidden 矩阵</li>
<li>专家 C：高频词专用（&ldquo;的&rdquo;、&ldquo;了&rdquo;、&ldquo;is&rdquo;）</li>
</ul>
<p>如果路由的 gating 信号不是从 <code>h</code> 学到的，而是从<strong>可观测的语言特征</strong>（句长、词频、TF-IDF）直接控制，MoE 就从&quot;黑盒分配&quot;变成了&quot;语言学驱动的碰撞桶分配&quot;。这等价于把 K_lang 的碰撞理论作为 MoE 架构的设计指南——每个专家就是一个碰撞桶簇。</p>
<p>这一思路至今未见发表。</p>
<h2 id="encoder--decoder--两个独立的-hash-网络">Encoder / Decoder = 两个独立的 Hash 网络</h2>
<p>Encoder 和 Decoder 的哈希不共享参数：</p>
<pre tabindex="0"><code>Encoder:  DE tokens → embedding → self-attn × N → h_enc
         参数: enc W_Q/K/V/O, enc FFN W₁/W₂    (hash 德语 → hidden space)

Decoder:  h_enc + EN tokens → self/cross-attn × N → W_out → logits
         参数: dec W_Q/K/V/O, dec FFN W₁/W₂, W_out  (hash hidden + 英语 → 词表)
</code></pre><table>
  <thead>
      <tr>
          <th></th>
          <th>Encoder Hash</th>
          <th>Decoder Hash</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>输入</td>
          <td>源语言（德语）</td>
          <td>目标语言（英语）+ encoder 输出</td>
      </tr>
      <tr>
          <td>输出</td>
          <td>$$h_{enc} \in \mathbb{R}^{512}$$</td>
          <td>$$p \in \Delta^{31999}$$</td>
      </tr>
      <tr>
          <td>参数</td>
          <td>独立的 W_enc</td>
          <td>独立的 W_dec</td>
      </tr>
      <tr>
          <td>作用</td>
          <td>把德语压缩到连续语义空间</td>
          <td>把语义空间释放到英语词表</td>
      </tr>
  </tbody>
</table>
<p>唯一共享的是 token embedding（BPE 同词表），但哈希参数本身完全独立。交叉注意力是两者的对接点——decoder 用自己的 Key/Value 连接 encoder 的输出。</p>
<h2 id="推理时-autoencoding源句本身即标签">推理时 Autoencoding——源句本身即标签</h2>
<p>每次推理都可以同时做 autoencoding。源句本身就是免费的标签：</p>
<pre tabindex="0"><code>用户输入 DE 句子:

① autoencoding: DE → Encoder → h → Decoder_DE → DE
                              ↓
                 loss = CE(预测DE, 原文DE) → ∇W_enc, ∇W_dec_de

② 翻译:        DE → Encoder(已更新) → Decoder_EN → EN
</code></pre><p>架构上只需多一个 DE 输出头——Phase 6 的 dual-head（CE+SB）已经证明双头可行：</p>
<pre tabindex="0"><code>h ─┬─→ Linear_EN → softmax → EN token   (翻译)
   └─→ Linear_DE → softmax → DE token   (autoencoding, 自监督更新)
</code></pre><p>每次推理都同时完成 autoencoding——encoder 的德语 hash 随着使用越来越密。</p>
<blockquote>
<p><strong>TODO (待实验):</strong> 在现有 dual-head 架构基础上加 autoencoding 头，对比推理阶段开启/关闭 autoencoding 时，长文本 DE→EN 翻译 BLEU 的变化曲线。预期：autoencoding 开启后，encoder hidden 聚类质量随推理量上升，BLEU 持续增长。</p></blockquote>
<h3 id="自学习与翻译训练的比例">自学习与翻译训练的比例</h3>
<p>Autoencoding 和翻译训练对参数的影响不对称：</p>
<pre tabindex="0"><code>autoencoding:   DE → Encoder → Decoder_DE → DE     → ∇W_enc + ∇W_dec_de  (双端更新)
翻译训练:       DE → Encoder → Decoder_EN → EN     → ∇W_enc + ∇W_dec_en  (双端更新)
</code></pre><p>两者都更新 Encoder——但 Decoder 端各管各的语言。如果在推理阶段只做 autoencoding 而不做翻译训练，Encoder 的德语 hash 会越来越密，但 Decoder_EN 长时间未更新，翻译能力退化。</p>
<p>所以需要<strong>资源配比</strong>：不是&quot;一直做 autoencoding&quot;，而是&quot;按参数变化率配比两种学习路径&quot;。</p>
<blockquote>
<p><strong>推理阶段配比约束:</strong> 设 </p>
$$\alpha$$<p> 为 autoencoding 步数对翻译步数的比率。当 </p>
$$\alpha > \alpha^*$$<p> 时，Decoder_EN 退化速度超过 Encoder 受益速度，翻译 BLEU 反降。</p>
$$\alpha^*$$<p> 可通过实验测量。</p></blockquote>
<h3 id="其他自监督方式">其他自监督方式</h3>
<p>在没有平行语料时，还有几条路可以单独更新 Encoder 或 Decoder：</p>
<ul>
<li><strong>contrastive:</strong> 相似句子（短的、主题相近的）的 hidden 拉近，不相似的推远</li>
<li><strong>masked LM:</strong> 挖掉一个词，模型猜它是什么（BERT 预训练的方式）</li>
<li><strong>autoencoding:</strong> DE→DE 自我重建，双端同时学习</li>
</ul>
<p>Teacher forcing 需要目标语言的标准答案 </p>
$$y_{true}$$<p> 来算梯度。推理阶段没有——所以参数不动。</p>
<p>但有源句数据本身可以更新 encoder。不需要英文标准答案——<strong>只需要德语句子自己</strong>：</p>
<pre tabindex="0"><code>推理时有的:  DE 源句 → Encoder → h_enc       ✅
推理时没有的: EN 译文                         ❌ (teacher forcing 无法算)

可以更新 encoder 的自监督方式:
  contrastive: 相似 DE 句的 h_enc 拉近，不相似的推远
  masked LM:   DE 句挖掉一个词，encoder 猜它是什么
</code></pre><p>这些全不需要 EN 标准答案——只需要 DE 源句自己的结构。Encoder 在学习<strong>源语言的空间结构</strong>，而不是翻译映射。</p>
<blockquote>
<p><strong>注意：</strong> back-translation（DE→EN→DE）只更新 Encoder 参数。更直接的自监督方式是 <strong>autoencoding</strong>——DE→Encoder→h→Decoder→DE，让模型自己翻译自己。因为解码器的输出仍是 DE，梯度可以同时更新 Encoder 和 Decoder，两端都参与 hash 碰撞校准。</p>
<p><strong>迁移：</strong> Autoencoding 预训练的 Encoder 可以直接用于 DE→EN 翻译——换掉 Decoder 即可。Encoder 学的是&quot;德语怎么压缩到 hidden 空间&quot;，与目标语言无关。这等价于给翻译模型的 encoder 一个冷启动优势：它已经知道德语的内部结构，只需 Decoder_EN 从头学&quot;hidden 怎么释放成英语&quot;。</p></blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># 推理时更新 encoder（不需要 y_true）</span>
</span></span><span style="display:flex;"><span>h_enc <span style="color:#f92672">=</span> encoder(de_sentence)                    <span style="color:#75715e"># 有源句，可以算 hidden</span>
</span></span><span style="display:flex;"><span>loss <span style="color:#f92672">=</span> contrastive_loss(h_enc, h_enc_neighbor)  <span style="color:#75715e"># 自监督信号</span>
</span></span><span style="display:flex;"><span>loss<span style="color:#f92672">.</span>backward()                                  <span style="color:#75715e"># 梯度只更新 encoder 参数</span>
</span></span></code></pre></div><p>这与西瓜书第二章的自助法（bootstrap）一脉相承——用已有数据构造伪标签来训练自己。</p>
<h2 id="单语言训练更新-encoderphase-0-的骨架就是证明">单语言训练更新 Encoder——Phase 0 的骨架就是证明</h2>
<p>DE 和 EN 都是现实世界的矩阵映射。德语的&quot;Buch&quot;和英语的&quot;book&quot;在物质宇宙中指同一个东西——只是投影到两个不同的词表坐标系里。</p>
<p><strong>既然是同一个世界的两次投影，hash 的碰撞自然双向调整距离。</strong></p>
<p>Phase 0 骨架实验（<a href="/wmt/003-wmt-phase0/">003</a>）证明了这个假设。骨架模型只做了一件事——用 IWSLT14 的德语文本训练 Encoder 的词嵌入，用英语文本训练 Decoder 的词嵌入。没有翻译任务，没有平行句子，就是单语言训练。</p>
<p>Training loop 的每一步都在双向更新 hash 的参数：</p>
<pre tabindex="0"><code>Encoder:  DE → Embedding → hidden   | ∇W_enc ← 德语的内部结构
Decoder:  EN → Embedding → hidden   | ∇W_dec ← 英语的内部结构
</code></pre><p>Hash 的碰撞约束了参数运动的方向——&ldquo;Buch&quot;在德语空间里紧挨着&quot;lesen&rdquo;（阅读），在英语空间里&quot;book&quot;紧挨着&quot;read&quot;。两个语言的内部距离满足同一种几何约束。嵌入的几何被现实世界的物理结构制约——碰撞桶在两端同时被校准。</p>
<p>这就是为什么你<strong>可以在没有平行语料时优化 Encoder 参数</strong>——因为单语言本身已经包含了&quot;哪些词和哪些词近&quot;的结构信息。hash 碰撞从两端调整距离——编码一端和译码一端在各自的空间里学习物理世界的几何投影。</p>
<blockquote>
<p><strong>推论（后期实验验证）：</strong> 如果取两个完全相同的预训练模型实例——A 只做推理（冻结），B 在每次中文输入上做 masked LM 自监督更新——定期测试 DE→EN 翻译质量，预期 B 的 BLEU 持续高于 A，且 encoder hidden 的 K-means 聚类质量随时间上升。</p></blockquote>
<h2 id="实验设计测量-k_lang">实验设计：测量 K_lang</h2>
<p>K_lang 不是调出来的——是可以从第一性原理里测出来的。</p>
<h3 id="核心假设">核心假设</h3>
<p>存在一个常数 </p>
$$K_{\text{lang}}$$<p> 使得：对于任意词汇量 </p>
$$|V|$$<p>，最优的 softmax 限制 k 为：</p>
$$k^* = K_{\text{lang}} \times |V|$$<p>如果 K_lang 是物理常数，那么用不同词表大小跑同一组实验，算出来的 </p>
$$k^*/|V|$$<p> 应该收敛到同一个值。</p>
<h3 id="扫描法多词表验证">扫描法：多词表验证</h3>
<p><strong>自变量：</strong> 三个词表大小。</p>
<p><a id="tbl-klang-experiment"></a>
<strong>表</strong> 测 K_lang 的词表</p>
<table>
  <thead>
      <tr>
          <th>词表类型</th>
          <th>|V|</th>
          <th>说明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BPE 8K</td>
          <td>8,000</td>
          <td>IWSLT BPE</td>
      </tr>
      <tr>
          <td>BPE 32K</td>
          <td>32,000</td>
          <td>WMT14 BPE</td>
      </tr>
      <tr>
          <td>Word-level</td>
          <td>56,652</td>
          <td>IWSLT word-level</td>
      </tr>
  </tbody>
</table>
<p><strong>因变量：</strong> 对每个词表，扫描 k 值：</p>
$$k \in \{32, 50, 64, 100, 128, 170, 200, 256, 400, 500, |V|\}$$<p><strong>控制变量：</strong> d_model=256, 3 层 Transformer, 5 epoch, batch=128, seed=42。</p>
<p><strong>输出：</strong> 每个词表画一条 BLEU(k) 曲线。曲线的峰值给出 </p>
$$k^*$$<p>。</p>
<h3 id="预期结果">预期结果</h3>
<pre tabindex="0"><code>BPE 8K:     k* ≈ 24       → k*/|V| ≈ 0.003
BPE 32K:    k* ≈ 96       → k*/|V| ≈ 0.003
Word 56K:   k* ≈ 170      → k*/|V| ≈ 0.003    ← A10 已确认
</code></pre><p>如果三条曲线的峰值都落在同一个比例上——K_lang = 0.003 就是可复现的物理常数。</p>
<h3 id="替代方法梯度分布法">替代方法：梯度分布法</h3>
<p>不靠扫描 k 值，直接从训练中测量。在大词表（56K）上做几轮全量 softmax 的 forward，收集每个 token 的梯度幅值，排序后找到 &ldquo;有效梯度&rdquo; 和 &ldquo;噪声梯度&rdquo; 的分界点——那个位置的 token 数就是 </p>
$$k^*$$<p>，不需要扫描。</p>
<h3 id="压缩还原法从-hash-容量直接测量">压缩还原法：从 Hash 容量直接测量</h3>
<p>这是最根本的测法——<strong>不需要多词表，不需要扫描，只需要一个自重建模型</strong>。</p>
<p>思路：hash 碰撞桶的信息容量是有上限的。用一个过大的 hidden 空间（2× 容器）做同语言自学习——骨架实验已经证明 autoencoding BLEU 可达 90+。然后<strong>逐层压缩 d_model</strong>，观察 BLEU 的衰减曲线。</p>
<p><strong>实验设计：</strong></p>
<pre tabindex="0"><code>① 起点: DE → Encoder(d=1024) → Decoder(1024) → DE
        多语自学习 → BLEU≈90 (整个 hash 空间足够稀疏)

② 压缩: 1024 → 768 → 512 → 384 → 256 → 128 → 64
        每压缩一次 → 重新评估 BLEU
        
③ 观察: 压缩到某个点 → BLEU 开始明显下跌
        那个膝盖点的压缩比 = 信息容量的上限
        = K_lang
</code></pre><p><strong>背后的原理：</strong></p>
<p>Hash 空间的每个维度承载信息。当维度过高（1024），信息极度稀疏——桶太多，每个桶只装极少数 token，BLEU 很高但不是&quot;泛化好&quot;而是&quot;记住了&quot;。当维度过低（128），信息被压缩到极限——hash 碰撞变得过于密，BLEU 暴跌。</p>
<p>K_lang 就出现在 <strong>BLEU 曲线的膝盖处</strong>——那个恰好在&quot;记住&quot;和&quot;压缩&quot;之间的维度数。</p>
<p>如果 K_lang 真的是物理常数，那这个膝盖点应该对不同的语言、不同的词表大小都稳定。一个 DE→DE 自重建模型就够了——不需要平行语料，不需要翻译训练。</p>
<blockquote>
<p><strong>TODO (待实验):</strong> Phase 0 骨架验证。d_model 从 1024 逐层压缩到 64（10 个点），每层 autoencoding 10 epoch 测 BLEU。画出 BLEU(d_model) 曲线，膝盖点即 K_lang。与扫描法（A8-A12 已做）和梯度分布法交叉验证。预期：三种方法收敛到同一个 K_lang ≈ 0.003。</p></blockquote>
<hr>
<h2 id="k_lang-量化下限int8-训练的理论下界">K_lang 量化下限：INT8 训练的理论下界</h2>
<p>碰撞桶理论不仅控制哈希参数密度——它还给了训练精度一个硬下限。</p>
<p>每个碰撞桶只需区分 </p>
$$\approx 170$$<p> 个有效 token。INT8 有 256 个离散级别——<strong>恰好覆盖这个下限</strong>。FP32 的 7 位有效数字（</p>
$$\sim 2^{23}$$<p> 个级别）完全过量——多出来的级别存在噪声，不是信号。</p>
<p><strong>量化梯度净化效应：</strong> <code>(p−y)·h_t</code> 里低于 1/256 的梯度分量被 INT8 截断——等于自动滤掉了碰撞桶外的噪声。桶内有效信号更干净。</p>
<h3 id="精度下限公式">精度下限公式</h3>
<p>每维度至少需要 </p>
$$k$$<p> 个精度级别，其中：</p>
$$k = K_{\text{lang}} \times |V| \approx 170$$<p><a id="tbl-quant"></a>
<strong>表</strong> 精度 vs K_lang 下限</p>
<table>
  <thead>
      <tr>
          <th>精度</th>
          <th>可分辨级数</th>
          <th>vs 170 下限</th>
          <th>T2 显存</th>
          <th>推论</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>FP32</td>
          <td>~16M</td>
          <td>极度过量</td>
          <td>23.8 GB</td>
          <td>噪声存太多</td>
      </tr>
      <tr>
          <td>FP16</td>
          <td>~65K</td>
          <td>过量 380×</td>
          <td>~15 GB</td>
          <td>仍有浪费</td>
      </tr>
      <tr>
          <td>INT8</td>
          <td>256</td>
          <td><strong>恰好过 170</strong></td>
          <td>~12 GB</td>
          <td>理论最优</td>
      </tr>
  </tbody>
</table>
<p>INT8 能满足碰撞桶的分辨需求——不多不少。K_lang 不是调参值，是衡量必要模型精度的物理常数。</p>
<blockquote>
<p><strong>实验验证：</strong> FP32 / autocast FP16 / QAT INT8 三组对照训练 T2。预期：INT8 的 BLEU 与 FP32 差异 &lt; 0.5%，同时 batch 可增至 512。若 INT8 的 BLEU 反而更高，则证实 &ldquo;FP32 存储了噪声，量化截断净化了梯度&rdquo;。</p></blockquote>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime WMT》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-007] Attention——从时序链到全连接邻接图</title>
      <link>https://www.grepcode.cn/wmt/007-wmt-phase2-attention.html</link>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/007-wmt-phase2-attention.html</guid>
      <description>RNN 用单条时间链关押所有词的关系——Attention 把每个词放进独立桶，用邻接矩阵保留全连接图。SoftBLEU 的下一个瓶颈：vocab 稀释梯度。</description>
      <content:encoded><![CDATA[<h1 id="attention-phase-2从时序链到邻接矩阵再到-k_lang-碰撞">Attention Phase 2——从时序链到邻接矩阵，再到 K_lang 碰撞</h1>
<blockquote>
<p>RNN 的悲剧不是 hash 桶不够大——是 50 个词共用一条链。Attention 解决了图的问题。但 SoftBLEU 遇到了新问题。</p></blockquote>
<h2 id="phase-2-实验总览softbleu-在-attention-上失效">Phase 2 实验总览：SoftBLEU 在 Attention 上失效</h2>
<p>H=256, E=256, epochs=5, 64 batch, Attention decoder：</p>
<table>
  <thead>
      <tr>
          <th>实验</th>
          <th>损失函数</th>
          <th>最佳 BLEU</th>
          <th>注</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A1</td>
          <td>纯 CE</td>
          <td><strong>3.76</strong></td>
          <td>peak ep2, 倒U过拟合</td>
      </tr>
      <tr>
          <td>A2</td>
          <td>纯 CE, ep=10</td>
          <td>3.76</td>
          <td>ep2后持平不涨</td>
      </tr>
      <tr>
          <td>A3</td>
          <td>纯 CE, H=512</td>
          <td><strong>3.94</strong></td>
          <td>大模型有收益</td>
      </tr>
      <tr>
          <td>A4</td>
          <td>CE+SB 50/50</td>
          <td>3.50</td>
          <td>梯度拉锯，反降</td>
      </tr>
      <tr>
          <td>A5</td>
          <td>纯 SB (−log BLEU)</td>
          <td>0.00</td>
          <td>soft匹配 vs argmax解码 鸿沟</td>
      </tr>
      <tr>
          <td>A6</td>
          <td>CE+0.5×(1−BLEU) E6配方</td>
          <td>3.74</td>
          <td>未破CE天花板</td>
      </tr>
      <tr>
          <td>A7</td>
          <td>CE+0.1×(1−BLEU)</td>
          <td>待补</td>
          <td>微量混合</td>
      </tr>
      <tr>
          <td>A8</td>
          <td>双头梯度相乘</td>
          <td>3.76</td>
          <td>与纯CE完全一致</td>
      </tr>
  </tbody>
</table>
<p><strong>关键发现：</strong> SoftBLEU 在 RNN 上 +0.15 (3.06→3.21)。在 Attention 上增益归零。不是 SoftBLEU 的公式错了——是在 56K vocab 上梯度被稀释到了噪声水平。</p>
<h2 id="问题不在-softbleu-的函数形式在梯度量级">问题不在 SoftBLEU 的函数形式——在梯度量级</h2>
<p>CE 梯度：<code>dCE/dlogit[target] = softmax(target) − 1 ≈ −1</code></p>
<p>SoftBLEU 梯度：</p>
$$\frac{\partial \text{SB}}{\partial \text{logit}[i]} = \sum_{c \in \text{ref}} \text{softmax}(c) \times (\delta_{ic} - \text{softmax}(i))$$<p>其中 <code>softmax(c) ≈ 1/56652 ≈ 1.8×10⁻⁵</code>。20 个 ref token 累加后 ≈ <code>3.6×10⁻⁴</code>。</p>
<p><strong>SoftBLEU 梯度 / CE 梯度 ≈ 1/2800。</strong></p>
<p>在 RNN 上 SB 有 0.15 增益，因为 RNN 的 CE 本身只有 3.06——有足够空间被纠正。在 Attention 上 CE 已到 3.76，2800:1 的梯度差让 SB 信号完全沉没。</p>
<h2 id="k_lang语言系统的碰撞常数">K_lang：语言系统的碰撞常数</h2>
<p>第 4 层 hash (vocab) 的碰撞空间：</p>
<pre tabindex="0"><code>K_lang ≈ 0.003
有效碰撞词数 = K_lang × |V| = 0.003 × 56652 ≈ 170
</code></pre><p>56K 词汇表里，有意义的碰撞只发生在 ~170 个词之间。其余 56K−170=56482 个词对翻译质量无影响——但在 softmax 分母里均匀稀释梯度。</p>
<p><strong>SoftBLEU 不该在全表上做 softmax——应该在 170 个碰撞桶上做。</strong></p>
<h2 id="restricted-softmax把分母从-56k-缩到-170">Restricted Softmax：把分母从 56K 缩到 170</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># 旧：softmax 分母在全表 56652 上求和</span>
</span></span><span style="display:flex;"><span>probs <span style="color:#f92672">=</span> softmax(logits, dim<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>)  <span style="color:#75715e"># 每个 token 概率 ≈ 1.8e-5</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 新：softmax 分母只在 topk + ref ∪ 上求和  </span>
</span></span><span style="display:flex;"><span>topk_idx <span style="color:#f92672">=</span> topk(logits, k<span style="color:#f92672">=</span><span style="color:#ae81ff">170</span>)
</span></span><span style="display:flex;"><span>restricted <span style="color:#f92672">=</span> fill(<span style="color:#f92672">-</span>inf) <span style="color:#f92672">+</span> topk <span style="color:#f92672">+</span> ref_tokens
</span></span><span style="display:flex;"><span>probs <span style="color:#f92672">=</span> softmax(restricted, dim<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>)  <span style="color:#75715e"># 每个 token 概率 ≈ 0.006</span>
</span></span></code></pre></div><p><strong>梯度放大 333 倍：</strong></p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>分母</th>
          <th>每 token 概率</th>
          <th>梯度放大</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全表 softmax</td>
          <td>56652</td>
          <td>~1.8×10⁻⁵</td>
          <td>1×</td>
      </tr>
      <tr>
          <td>Restricted k=50</td>
          <td>50</td>
          <td>~0.02</td>
          <td>~1130×</td>
      </tr>
      <tr>
          <td><strong>Restricted k=170</strong></td>
          <td><strong>170</strong></td>
          <td><strong>~0.006</strong></td>
          <td><strong>~333×</strong></td>
      </tr>
      <tr>
          <td>Restricted k=500</td>
          <td>500</td>
          <td>~0.002</td>
          <td>~113×</td>
      </tr>
  </tbody>
</table>
<h2 id="双头架构与损失函数">双头架构与损失函数</h2>
<h3 id="定义">定义</h3>
<p>设 $T$ 为目标句长，$V$ 为词表大小（56,652），$\alpha$ 为 SB 权重：</p>
$$\mathcal{L} = \mathcal{L}_{\text{CE}} \cdot \big(1 + \alpha \cdot (1 - \text{SB})\big)$$<p>其中：</p>
$$\mathcal{L}_{\text{CE}} = -\frac{1}{T}\sum_{t=1}^{T} \log p_{\text{CE}}(y_t^* \mid y_{\lt t}, x)$$$$\text{SB} = \left(\prod_{n=1}^{4} P_n\right)^{1/4}, \quad P_n = \frac{\sum_{t=1}^{T}\sum_{c \in \text{ref}_n} p_{\text{SB}}(\vec{c} \mid t, x)}{T - n + 1}$$<p>这里 $p_{\text{CE}}$ 是 CE 头产生的概率分布，$p_{\text{SB}}$ 是 SB 头产生的概率分布，$\text{ref}_n$ 是参考译文中长度为 $n$ 的 n-gram 集合。</p>
<h3 id="受限-softmax">受限 softmax</h3>
<p>全表 softmax 在 $V$=56,652 上计算分母，每个 token 的平均概率 $\approx 1/V \approx 1.8\times10^{-5}$，梯度被均匀稀释。语言系统碰撞常数 $K_{\text{lang}} \approx 0.003$ 指出有效碰撞空间仅有 $k = K_{\text{lang}} \cdot V \approx 170$ 个词。</p>
<p>受限 softmax 只在 top-$k$ + 参考词 ∪ 上归一化：</p>
$$p_{\text{SB}}(v \mid t, x) = \frac{\exp(z_{t,v}) \cdot \mathbf{1}(v \in \mathbb{K})}{\sum_{v' \in \mathbb{K}} \exp(z_{t,v'})}$$<p>其中 $\mathbb{K} = \text{topk}(z_t, k) \cup \text{ref}$ 为碰撞桶集合。</p>
<h3 id="梯度分析">梯度分析</h3>
<p>对 SB 头输出权重 $W_{\text{SB}}$ 的梯度：</p>
$$\frac{\partial \mathcal{L}}{\partial W_{\text{SB}}} = \mathcal{L}_{\text{CE}} \cdot (-\alpha) \cdot \frac{\partial \text{SB}}{\partial W_{\text{SB}}}$$<p>其中 $\partial \text{SB} / \partial W_{\text{SB}}$ 通过受限 softmax 反传。由于分母 $|\mathbb{K}| \approx 190$（vs 全表 56,652），<strong>梯度量级放大约 300 倍</strong>，从噪声水平（$\sim 10^{-5}$）提升到与 CE 可比（$\sim 10^{-2}$）。</p>
<p>对共享 LSTM 参数 $\theta$ 的梯度：</p>
$$\frac{\partial \mathcal{L}}{\partial \theta} = \underbrace{\vphantom{\frac{\partial}{\partial}}\big(1 + \alpha(1-\text{SB})\big) \frac{\partial \mathcal{L}_{\text{CE}}}{\partial \theta}}_{\text{CE 主导，因子 1.0--1.5}} + \underbrace{\vphantom{\frac{\partial}{\partial}}\mathcal{L}_{\text{CE}}(-\alpha) \frac{\partial \text{SB}}{\partial \theta}}_{\text{SB 辅助信号}}$$<p>早期 $\text{SB} \approx 0$ 时 SB 贡献项 $\propto \mathcal{L}_{\text{CE}} \approx 11$（放大），后期 $\text{SB} \to 0.35$ 时 $\partial \text{SB}/\partial \theta \to 0$（梯度衰减）。<strong>α 从 0.5 提到 0.8 有效补偿了衰减，维持 $\nabla_{\text{SB}} \neq 0$ 到训练晚期。</strong></p>
<pre tabindex="0"><code>          ┌─ out_ce (Linear) ── L_CE ──┐
LSTM ─ hidden_seq ─┤                     ├─ L = L_CE × (1 + α × (1−SB))
                    └─ out_sb (Linear) ── SB ──┘
</code></pre><p>两个独立的 <code>out</code> 层共享同一个 LSTM。CE 头负责精确 token 学习，SB 头通过碰撞空间内的受限 softmax 提供 n-gram 覆盖度信号。</p>
<h2 id="a8a12-k_lang-扫描结果">A8–A12 K_lang 扫描结果</h2>
<p>4 epoch, H=256, E=256, seed=42：</p>
<table>
  <thead>
      <tr>
          <th>实验</th>
          <th>k</th>
          <th>BLEU ep0</th>
          <th>ep1</th>
          <th>ep2</th>
          <th>ep3</th>
          <th><strong>峰值</strong></th>
          <th>vs CE</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A8 CE-only</td>
          <td>—</td>
          <td>2.80</td>
          <td>3.02</td>
          <td>3.27</td>
          <td>3.45 ↗</td>
          <td><strong>3.45</strong></td>
          <td>baseline</td>
      </tr>
      <tr>
          <td>A9 DH</td>
          <td>50</td>
          <td>3.27</td>
          <td><strong>3.35</strong></td>
          <td>2.75</td>
          <td>2.87</td>
          <td>3.35</td>
          <td>−0.10</td>
      </tr>
      <tr>
          <td><strong>A10 DH</strong></td>
          <td><strong>170</strong></td>
          <td>3.18</td>
          <td>3.31</td>
          <td><strong>3.63</strong></td>
          <td>3.43</td>
          <td><strong>3.63</strong></td>
          <td><strong>+0.18</strong></td>
      </tr>
      <tr>
          <td>A11 DH</td>
          <td>500</td>
          <td>2.93</td>
          <td>2.84</td>
          <td>3.09</td>
          <td>2.92</td>
          <td>3.09</td>
          <td>−0.36</td>
      </tr>
      <tr>
          <td>A12 DH</td>
          <td>56652</td>
          <td>3.03</td>
          <td>3.35</td>
          <td>3.24</td>
          <td>3.21</td>
          <td>3.35</td>
          <td>−0.10</td>
      </tr>
  </tbody>
</table>
<h3 id="k-值画出倒-u-曲线">k 值画出倒 U 曲线</h3>
<p><img alt="K_lang sweep" decoding="async" loading="lazy" src="/img/k_lang_sweep.png"></p>
$$\text{BLEU}(k) = \begin{cases}
3.35 & k = 50 \quad\text{(太窄，梯度震荡)} \\
\mathbf{3.63} & k = 170 \quad\text{(K\_lang 甜点)} \\
3.09 & k = 500 \quad\text{(开始稀释)} \\
3.35 & k = 56652 \quad\text{(全表，无信号)}
\end{cases}$$<p>梯度放大 333 倍生效了——但只在合适的桶数上。桶太少（50），梯度太猛导致超调；桶太多（&gt;500），稀释回噪声。</p>
<h3 id="核心对比a10-vs-a12">核心对比：A10 vs A12</h3>
<p>同样的双头结构，同样的 CE×SB 乘法损失，唯一变量是 softmax 分母——170 vs 56652。</p>
<ul>
<li>A12（全表）：SB_head 的 <code>∇out_sb</code> 始终在 0.01-0.04 量级，而 <code>∇out_ce</code> 在 0.24-0.29——<strong>信号完全被 CE 淹没</strong></li>
<li>A10（170）：早期 <code>∇sb=0.10</code> vs <code>∇ce=0.71</code>（7×），SB 头真正参与了学习</li>
</ul>
<p><strong>SoftBLEU 在 Attention 上首次显著超越 CE 基线。K_lang 碰撞理论验证成立。</strong></p>
<h3 id="a13a15梯度衰减修复">A13–A15：梯度衰减修复</h3>
<p>A10 的 SB 头梯度在 ep3 衰减到 <code>∇sb=0.00</code>。测试两种修复方向——换函数形式 vs 加大 α：</p>
<table>
  <thead>
      <tr>
          <th>实验</th>
          <th>配置</th>
          <th>BLEU ep0</th>
          <th>ep1</th>
          <th>ep2</th>
          <th>ep3</th>
          <th>ep4</th>
          <th><strong>峰值</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A10</td>
          <td>linear α=0.5</td>
          <td>3.18</td>
          <td>3.31</td>
          <td><strong>3.63</strong></td>
          <td>3.43</td>
          <td>—</td>
          <td>3.63</td>
      </tr>
      <tr>
          <td>A13</td>
          <td>sqrt β=0.5 α=0.5</td>
          <td>2.99</td>
          <td>3.17</td>
          <td>3.24</td>
          <td>3.07</td>
          <td>2.89</td>
          <td>3.24</td>
      </tr>
      <tr>
          <td>A14</td>
          <td>sqrt β=0.3 α=0.3</td>
          <td>2.55</td>
          <td>3.07</td>
          <td>3.24</td>
          <td>3.20</td>
          <td>2.94</td>
          <td>3.24</td>
      </tr>
      <tr>
          <td><strong>A15</strong></td>
          <td><strong>linear α=0.8</strong></td>
          <td><strong>3.28</strong></td>
          <td><strong>3.47</strong></td>
          <td><strong>3.74</strong></td>
          <td><strong>3.77</strong></td>
          <td><strong>3.68</strong></td>
          <td><strong>3.77</strong></td>
      </tr>
  </tbody>
</table>
<p><strong>结论：换函数形式（sqrt）反降——梯度因子太小（β=0.5 时仅 0.5-0.63×线性）。直接加大 α 从 0.5 到 0.8，SB 头梯度晚期 <code>∇sb=0.02-0.11</code> 未衰减到零，BLEU 从 3.63 飙到 3.77（+9.3% vs CE-only）。</strong></p>
<p>当前最优：<strong>A15</strong>：k=170 (K_lang), multiply_linear, α=0.8, BLEU=3.77。</p>
<h3 id="未解问题">未解问题</h3>
<ol>
<li><strong>峰后衰退</strong>——即使 α=0.8，BLEU 仍从 ep3=3.77 滑到 ep4=3.68。可能需 α 渐进升温（<code>--sb-alpha-step</code>）</li>
<li><strong>真实 n-gram</strong>——当前 2/3/4-gram 仍是几何衰减占位，只有 1-gram 是 scatter_add</li>
</ol>
<h2 id="相关文献可微-bleu-的学术谱系">相关文献：可微 BLEU 的学术谱系</h2>
<p>可微 BLEU 有两条路——RL 采样估计和直接可微替代。我们的 K_lang restricted softmax 属于后者，但与已有工作在关键细节上不同。</p>
<h3 id="核心对比">核心对比</h3>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>作者/发表</th>
          <th>可微方式</th>
          <th>softmax 分母</th>
          <th>架构</th>
          <th>场景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Ours (K_lang)</strong></td>
          <td>—</td>
          <td>1-gram soft 匹配, 限制梯度到碰撞桶</td>
          <td><strong>170</strong> (topk+ref)</td>
          <td>双头 CE×SB</td>
          <td>自回归 Attention</td>
      </tr>
      <tr>
          <td>BoN Difference</td>
          <td>Shao et al. AAAI 2020</td>
          <td>Bag-of-Ngrams 直接最小化</td>
          <td>全表 56K</td>
          <td>单头混合</td>
          <td>非自回归 NAT</td>
      </tr>
      <tr>
          <td>Probabilistic N-gram Matching</td>
          <td>Shao et al. EMNLP 2018</td>
          <td>概率 n-gram 匹配可微目标</td>
          <td>全表</td>
          <td>单头</td>
          <td>自回归 NMT</td>
      </tr>
      <tr>
          <td>Seq-Training for NAT</td>
          <td>Shao et al. CL 2021</td>
          <td>BoN + REINFORCE 混合</td>
          <td>全表</td>
          <td>单头</td>
          <td>NAT</td>
      </tr>
      <tr>
          <td>MRT</td>
          <td>Shen et al. ACL 2016</td>
          <td>采样 BLEU 期望梯度 (REINFORCE)</td>
          <td>—</td>
          <td>单头 CE</td>
          <td>自回归 NMT</td>
      </tr>
  </tbody>
</table>
<h3 id="详细分析">详细分析</h3>
<p><strong>BoN Difference</strong> (arXiv:1911.09320)：将 Bag-of-Ngrams (BoN) 差异直接作为可微 loss，在 NAT 上 +5 BLEU。与我们的 1-gram soft 匹配核心思想一致，但他们在全 vocabulary 上做 softmax——NAT 的基线 BLEU 只有 10-15，梯度稀释问题不如我们的 3.x BLEU 底线明显。<strong>无人提出限制 softmax 分母到碰撞空间。</strong></p>
<p><strong>Probabilistic N-gram Matching</strong> (arXiv:1809.03132)：EMNLP 2018，用概率 n-gram 匹配计算训练时未来词的匹配度，同时做 greedy search 缓解 exposure bias。+1.5 BLEU。<strong>我们的 SoftBLEU 可以视为其简化版（省去搜索，保留概率匹配）——但他们也没限制分母。</strong></p>
<p><strong>MRT</strong> (arXiv:1606.02006)：ACL 2016，采样 k 个翻译候选，用 BLEU 加权梯度。n-gram 上的 BLEU 精确但梯度方差高。我们的方案恰好相反——梯度确定性低方差（soft 匹配），但丢失 n-gram 结构。</p>
<h3 id="k_lang-restricted-softmax-的新颖性">K_lang Restricted Softmax 的新颖性</h3>
<p>三条分叉点：</p>
<ol>
<li>
<p><strong>碰撞空间限制</strong>：BoN 和 N-gram Matching 都假设 softmax 在整个 vocabulary 上是有效的——这在 BLEU 15+ 的系统上成立（概率峰值集中）。但在 BLEU 3.x 的小模型上，softmax 概率被 56K 分母均摊到噪声水平。K_lang 理论指出有效碰撞桶只有 170 个，把分母从 56652 砍到 170——<strong>这在前人工作中未见。</strong></p>
</li>
<li>
<p><strong>双头独立梯度</strong>：前人用单头（同一个 out layer 接收 CE+BLEU 混合梯度），导致 CE 的强梯度压制 BLEU 的弱梯度。我们的双头架构（<code>out_ce</code> 和 <code>out_sb</code> 两个独立的 Linear 层共享 LSTM）让两个信号在 LSTM 层面乘法叠加，不在 out 层打架。</p>
</li>
<li>
<p><strong>乘法损失 <code>CE × f(1−SB)</code></strong>：CE+SB 的加法混合假设两个信号独立，乘法假设条件依赖——这与 n-gram 的马尔可夫链统计性质一致。但形式 <code>f(x) = 1 + 0.5x</code> 在 SB→0.35 时退化，需要更激进的函数形式（指数等）。</p>
</li>
</ol>
<h2 id="k_lang-框架的最终形态四层-hash桶数与深度互补">K_lang 框架的最终形态：四层 hash，桶数与深度互补</h2>
<p>Phase 2 到 Phase 6 跨越了 Attention → Transformer 的架构鸿沟，但 K_lang 碰撞理论跨架构成立：</p>
<h3 id="四层-hash-压缩表">四层 hash 压缩表</h3>
<table>
  <thead>
      <tr>
          <th>层</th>
          <th>操作</th>
          <th>词级 (56K)</th>
          <th>BPE (8K)</th>
          <th>碰撞桶数变化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. Embedding</td>
          <td>token → R^d</td>
          <td>56,652</td>
          <td>8,000</td>
          <td><strong>7× 压缩</strong></td>
      </tr>
      <tr>
          <td>2. Hidden (LSTM/Attention)</td>
          <td>R^d → R^h</td>
          <td>256/512</td>
          <td>256/512</td>
          <td>参数量决定</td>
      </tr>
      <tr>
          <td>3. Decoder/Output</td>
          <td>R^h → R^V</td>
          <td>56,652</td>
          <td>8,000</td>
          <td><strong>7× 压缩</strong></td>
      </tr>
      <tr>
          <td>4. Vocab 碰撞</td>
          <td><code>softmax(z)</code></td>
          <td>`</td>
          <td>V</td>
          <td>=56K → K_lang×</td>
      </tr>
  </tbody>
</table>
<p>BPE 以 7× 因子压缩了第 1、3、4 层的桶数。</p>
<h3 id="桶越少深度要求越高">桶越少，深度要求越高</h3>
<p>Phase 6 实验结果直接验证了这一点：</p>
$$\text{d256/3L (11.6M) → 10.66 BLEU}$$<p>
</p>
$$\text{d256/6L (17.2M) → 11.70 BLEU}$$<p>
</p>
$$\text{d512/6L (56.4M) → 8.18 BLEU (过拟合)}$$<p>BPE 把 vocab 从 56K 压到 8K——第 4 层的碰撞桶从 170 缩到 24。在 24 个桶里区分 token，每个桶承担 7× 更多的语义负载。<strong>浅模型（3L）的 hidden 层没有足够深度把这些语义分离开。深模型（6L）每层递进一步，在 hidden 维度里重建了被压缩的分辨率。</strong></p>
<p>这就是 hash 碰撞框架的完整闭环：</p>
$$K_{\text{lang}} \approx 0.003$$<p>
</p>
$$N_{\text{collision}} = K_{\text{lang}} \times |V|$$<p>
</p>
$$N_{\text{word-level}} = 170, \quad N_{\text{BPE}} = 24$$<p><strong>170→24 的桶压缩需要 3L→6L 的深度补偿。深度不是越大越好——d512/6L 的 56M 参数在 160K 句 IWSLT14 上过拟合，反向证明桶的容量有一个上限：桶再多，没有足够数据灌满就成了空洞。</strong></p>
<p>推论：对于给定的训练数据量，存在<strong>最优桶数 × 最优深度</strong>的乘积——这解释了 B3 (d256/6L, 17.2M, 24 桶) 为什么是最优甜点。</p>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发，但必须继承相同的开源协议，承认在算力宇宙中所有的迭代与变异。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-009] 实验总览——从 RNN 到 Transformer 的技术组合与天花板</title>
      <link>https://www.grepcode.cn/wmt/009-wmt-expr-overview.html</link>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/009-wmt-expr-overview.html</guid>
      <description>Phase 1 RNN 和 Phase 2 Attention 的全部实验编号与最佳 BLEU 总结。</description>
      <content:encoded><![CDATA[<h1 id="实验总览">实验总览</h1>
<blockquote>
<p><strong>全局实验 ID</strong>：全部实验跨系列统一编号 E-001 ~ E-030。E 系列=R01-08, A 系列=E09-23, TI 系列=E24-30。</p></blockquote>
<h2 id="phase-1rnn纯-tanh">Phase 1：RNN（纯 tanh）</h2>
<table>
  <thead>
      <tr>
          <th>编号</th>
          <th>实验</th>
          <th>损失</th>
          <th>关键参数</th>
          <th>Best BLEU</th>
          <th>E-#</th>
          <th>发现</th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>E1</td>
          <td>hidden 扫描</td>
          <td>CE</td>
          <td>H=E∈{16~1024}</td>
          <td>3.02</td>
          <td>E-001</td>
          <td>倒 U，天花板 3.0</td>
          <td></td>
      </tr>
      <tr>
          <td>E2</td>
          <td>embed 解耦</td>
          <td>CE</td>
          <td>H=512, vary E</td>
          <td>3.06</td>
          <td>hash 碰撞最优 ~580 词/维</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E3</td>
          <td>epoch 深度</td>
          <td>CE</td>
          <td>H=512/1024, 20ep</td>
          <td>3.02</td>
          <td>epoch 不改变 K_lang</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E4</td>
          <td>数据翻倍</td>
          <td>CE</td>
          <td>H=1024, ×2 data</td>
          <td>2.50</td>
          <td>epoch 0 缓解，过拟合照旧</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E5</td>
          <td>sin 激活</td>
          <td>CE</td>
          <td>H=512, E=128</td>
          <td><strong>0.76</strong></td>
          <td>梯度方向自毁</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E6</td>
          <td>SoftBLEU 混合</td>
          <td>CE+SB λ=0.5</td>
          <td>H=512, E=128</td>
          <td><strong>3.21</strong></td>
          <td>突破纯 CE 天花板</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E7</td>
          <td>SoftBLEU 纯</td>
          <td>纯 SB</td>
          <td>H=512, E=128</td>
          <td>🔄</td>
          <td>CE=1-gram 不插一手</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>E8</td>
          <td>BLEU Function</td>
          <td>0/1 STE</td>
          <td>H=512, E=128</td>
          <td>❌</td>
          <td>backward 循环太慢</td>
          <td></td>
          <td></td>
      </tr>
  </tbody>
</table>
<h2 id="phase-2attentionlstm--bahdanau">Phase 2：Attention（LSTM + Bahdanau）</h2>
<table>
  <thead>
      <tr>
          <th>编号</th>
          <th>实验</th>
          <th>损失</th>
          <th>关键参数</th>
          <th>Best BLEU</th>
          <th>发现</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A1</td>
          <td>CE 基线</td>
          <td>CE</td>
          <td>H=256, E=256</td>
          <td>3.57</td>
          <td>BLEU 未衰退（小模型）</td>
      </tr>
      <tr>
          <td>A2</td>
          <td>CE 大模型</td>
          <td>CE</td>
          <td>H=512, E=256</td>
          <td>3.76</td>
          <td>peak ep2 后衰退</td>
      </tr>
      <tr>
          <td>A3</td>
          <td>CE 深 epoch</td>
          <td>CE</td>
          <td>H=512, E=256, 10ep</td>
          <td>3.76→3.02</td>
          <td>与 RNN 相同的倒 U</td>
      </tr>
      <tr>
          <td>A4</td>
          <td>SoftBLEU 混合</td>
          <td>CE+SB</td>
          <td>H=512, E=256</td>
          <td>3.50</td>
          <td>反降——梯度拉锯</td>
      </tr>
      <tr>
          <td>A5</td>
          <td>SoftBLEU 纯</td>
          <td>纯 -log SB</td>
          <td>H=512, E=256</td>
          <td>0.00</td>
          <td>soft 匹配与 argmax 鸿沟</td>
      </tr>
      <tr>
          <td>A6</td>
          <td>E6 on Attention</td>
          <td>CE+SB λ=0.5</td>
          <td>H=512, E=256, 10ep</td>
          <td><strong>3.74</strong></td>
          <td>振荡不崩，但未破 3.76</td>
      </tr>
  </tbody>
</table>
<h2 id="全局结论">全局结论</h2>
<ol>
<li><strong>换 hash 架构（RNN→Attention）不改变过拟合模式</strong>——loss ↓ 时 BLEU ↓</li>
<li><strong>SoftBLEU 是缺陷补偿器</strong>：RNN 上 +0.15，Attention 上 −0.02。基线越好增益越小</li>
<li><strong>纯 BLEU 梯度永远无法替代 CE</strong>：1-BLEU 梯度太小，-log(BLEU) 梯度够但 soft 匹配和 argmax 之间存在结构性鸿沟（BLEU=0）</li>
<li><strong>E6（CE+SB λ=0.5）在 RNN 上是最优方案</strong>，但在 Attention 上不产生增益</li>
</ol>
<h2 id="全局结论-1">全局结论</h2>
<p>换 hash 架构（RNN→Attention）不改变过拟合模式。loss 持续下降时 BLEU 仍在衰退——梯度方向问题比架构问题更深。</p>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong></p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-006] 可微 BLEU 的算法代价——从黑板公式到 GPU 现实</title>
      <link>https://www.grepcode.cn/wmt/006-wmt-softbleu-cost.html</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/006-wmt-softbleu-cost.html</guid>
      <description>SoftBLEU 理论是对的——但在 GPU 上跑 Python 循环不属于这份正确。</description>
      <content:encoded><![CDATA[<h1 id="可微-bleu-的算法代价">可微 BLEU 的算法代价</h1>
<blockquote>
<p>黑板上的公式是对的。Python 循环是错的。</p></blockquote>
<h2 id="问题的起点">问题的起点</h2>
<p><a href="/wmt/005-wmt-gradient-theory.html">上一篇</a> 提出了 SoftBLEU——给 BLEU 套一层 softmax，让翻译质量的评价直接参与梯度计算。</p>
<p>写完公式，写完伪代码，写完 <code>base/soft_bleu.py</code>。</p>
<p>然后跑了两小时，0 个 epoch 完成。</p>
<h2 id="不是你代码写得丑是-python-循环不该碰-loss-函数">不是你代码写得丑——是 Python 循环不该碰 loss 函数</h2>
<p>CrossEntropy 的计算复杂度是：</p>
$$\text{CE} = O(B \times T \times V)$$<p>一行 <code>F.cross_entropy(logits, targets)</code>。PyTorch 底层是 CUDA kernel——所有操作在 GPU 上并行，一步完成。每 step 约 0.5 毫秒。</p>
<p>SoftBLEU 的 Python 实现：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">soft_bleu</span>(logits, ref_ids, vocab, max_n<span style="color:#f92672">=</span><span style="color:#ae81ff">4</span>):
</span></span><span style="display:flex;"><span>    B, T, V <span style="color:#f92672">=</span> logits<span style="color:#f92672">.</span>shape
</span></span><span style="display:flex;"><span>    probs <span style="color:#f92672">=</span> F<span style="color:#f92672">.</span>softmax(logits, dim<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> b <span style="color:#f92672">in</span> range(B):                    <span style="color:#75715e"># Python 循环 ← 64 次</span>
</span></span><span style="display:flex;"><span>        ref_set <span style="color:#f92672">=</span> set()                   <span style="color:#75715e"># Python 数据结构</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> r <span style="color:#f92672">in</span> ref_ids[b]:              <span style="color:#75715e"># Python 循环 ← ~50 次</span>
</span></span><span style="display:flex;"><span>            ref_set<span style="color:#f92672">.</span>add(r<span style="color:#f92672">.</span>item())         <span style="color:#75715e"># tensor→Python 转换</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> t <span style="color:#f92672">in</span> range(T):                <span style="color:#75715e"># Python 循环 ← ~50 次</span>
</span></span><span style="display:flex;"><span>            prob_t <span style="color:#f92672">=</span> probs[b, t, :]       <span style="color:#75715e"># 切片</span>
</span></span><span style="display:flex;"><span>            matched[b] <span style="color:#f92672">+=</span> prob_t[list(ref_set)]<span style="color:#f92672">.</span>sum()  <span style="color:#75715e"># Python list→tensor</span>
</span></span></code></pre></div><p>四层嵌套 Python 循环。每一个 <code>probs[b, t, :]</code> 都在触发 tensor→Python→tensor 的往返——这种往返在 GPU 和 CPU 之间产生了几百次数据传输。</p>
<p>每 step 约 500 毫秒。同等条件下 CE 只需要 0.5 毫秒。</p>
<p><strong>差距不是 10 倍或 100 倍——是 1000 倍。</strong></p>
<h2 id="ce-为什么那么快">CE 为什么那么快</h2>
<p>CE 从 logits 到 loss 走的全是 GPU 原生操作：</p>
<pre tabindex="0"><code>logits (B,T,V) → reshape → GPU 矩阵索引 → CrossEntropyLoss kernel → 标量
     ↑                                                              ↑
    全是浮点运算，没有 Python 插足                                  梯度一步到位
</code></pre><h2 id="softbleu-为什么那么慢">SoftBLEU 为什么那么慢</h2>
<p>每行 Python 代码都在打断 GPU 流水线：</p>
<pre tabindex="0"><code>GPU:  [softmax 算完] → wait...
CPU:  [for b in B] → [for t in T] → [构建 ref_set] → [tensor 切片]
         ↑
    GPU 在等 CPU 把数据从显存拉回来
    每一毫秒的等待 = 几百个 CUDA core 空转
</code></pre><p>这不是 &ldquo;Python 慢&rdquo;。这是一个结构性错误：<strong>把 GPU 当成 CPU 在用</strong>。Python 循环里切片 tensor，等价于把 CUDA 的并行管线切成一段一段的串行任务——每个 <code>prob_t[list(ref_set)].sum()</code> 都在 GPU 上起一个全新的 kernel launch，而 launch 的时间开销本身就大于计算本身。</p>
<h2 id="结论不在于不用-softbleu在于正确实现">结论不在于&quot;不用 SoftBLEU&quot;——在于&quot;正确实现&quot;</h2>
<p>SoftBLEU 的理论是对的：给 BLEU 套 softmax，可微，能参与梯度。错的不是理论——是把一个应该在 CUDA 内核里完成的计算写成了四层 Python 循环。</p>
<p>解决方案不是放弃 SoftBLEU。是<strong>用向量化操作重写</strong>——去掉 Python 循环，用 <code>torch.scatter</code>、<code>torch.where</code>、<code>torch.einsum</code> 这些可以在 GPU 上一步完成的算子。</p>
<p>下一篇：SoftBLEU 的向量化实现——把 1000 倍的差距压回个位数。</p>
<hr>
<h2 id="softbleu-公式剖析为什么-234-gram-是假的">SoftBLEU 公式剖析——为什么 2/3/4-gram 是假的</h2>
<p>E6 实验使用的 SoftBLEU（<code>bdf4a63</code> 版本）的实际公式：</p>
<p><strong>1-gram（真实的）</strong>：</p>
$$\text{prec}_1 = \frac{1}{B} \sum_b \frac{\sum_t \sum_{r \in \text{ref}} P(hyp_t = r)}{\max(1, T)}$$<p>对每个位置 t，求和参考 token 的 softmax 概率 → 除以句子长度 → 得到软 1-gram 精度。这个是真的，可导的。</p>
<p><strong>2/3/4-gram（假的）</strong>：</p>
$$\text{prec}_n = \text{prec}_1 \times (1 - 0.5^{n-1}) \quad n \in \{2,3,4\}$$<p>只是 1-gram 精度乘以一个衰减系数。<strong>没有真正的 bigram/trigram/4-gram 匹配。</strong> 等于把 DC 分量复制三份乘以衰减——高频信息全丢了。</p>
<p><strong>BLEU 合成</strong>：</p>
$$\text{SoftBLEU} = (\text{prec}_1 \times \text{prec}_2 \times \text{prec}_3 \times \text{prec}_4)^{1/4}$$<p><strong>损失函数（E6 混合模式）</strong>：</p>
$$L = 0.5 \times \text{CE} + 0.5 \times (1 - \text{SoftBLEU})$$<p>CE 和 1-gram 等价 → 两者本质上在做同一件事 → E6 只是在 CE 上套了个衰减壳。<strong>这就是 SoftBLEU 在 RNN 上只加了 +0.15、在 Attention 上只加了 -0.26 的根本原因。</strong></p>
<p>要真正发挥 SoftBLEU 的作用，必须把真实的 2/3/4-gram 软匹配算出来——相邻位置的概率乘积，或者用卷积在 GPU 上一步完成。</p>
<hr>
<h2 id="时间线">时间线</h2>
<table>
  <thead>
      <tr>
          <th>日期</th>
          <th>事件</th>
          <th>关键发现</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2026-04-26</td>
          <td>Phase 0 底座骨架完成</td>
          <td>DummyModel BLEU=91/0/0（相位差 0/1/2）</td>
      </tr>
      <tr>
          <td>2026-04-27</td>
          <td>Phase 1.0 RNN 代码拆分</td>
          <td>拆为 1.0（vanilla RNN）+ 1.1（LSTM）</td>
      </tr>
      <tr>
          <td>2026-05-01</td>
          <td>7 组 hidden_size 对照实验</td>
          <td>BLEU 天花板 ≈ 3.0，倒 U 峰值 H=512</td>
      </tr>
      <tr>
          <td>2026-05-01</td>
          <td>解耦合 embed 实验</td>
          <td>最佳 E=128，hash 碰撞密度 ~580 词/维</td>
      </tr>
      <tr>
          <td>2026-05-01</td>
          <td>K_lang 碰撞密度模型提出</td>
          <td>BLEU ∝</td>
      </tr>
      <tr>
          <td>2026-05-01</td>
          <td>预测 3 证伪：epoch 深度</td>
          <td>H=512/1024 ep20：epoch 不改变 K_lang</td>
      </tr>
      <tr>
          <td>2026-05-02</td>
          <td>sin RNN 实验</td>
          <td>BLEU=0.76：cos 梯度方向翻转自毁</td>
      </tr>
      <tr>
          <td>2026-05-02</td>
          <td>梯度函数决定学习规律</td>
          <td>独立文章发表</td>
      </tr>
      <tr>
          <td>2026-05-02</td>
          <td>GPU 稳定性诊断</td>
          <td>ROG 3090 锁 1800MHz 稳定，390W 不掉卡</td>
      </tr>
      <tr>
          <td>2026-05-03</td>
          <td>SoftBLEU Python 循环版</td>
          <td>2 小时 0 epoch——1000 倍差距</td>
      </tr>
      <tr>
          <td>2026-05-03</td>
          <td><strong>SoftBLEU 向量化版</strong></td>
          <td><strong>BLEU=3.21，突破 CE 天花板 3.06</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="前方的路">前方的路</h2>
<p><strong>SoftBLEU 已验证可行。现在有三条路：</strong></p>
<table>
  <thead>
      <tr>
          <th>路线</th>
          <th>实验</th>
          <th>预期</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>A. 深挖 SoftBLEU</strong></td>
          <td>调 λ（0.3/0.5/0.7），多组 seed，H=1024 + SoftBLEU</td>
          <td>找最佳混合比，大模型上验证</td>
      </tr>
      <tr>
          <td><strong>B. 换 hash 架构</strong></td>
          <td>Phase 2 Bahdanau Attention</td>
          <td>Attention 替代 RNN 时序压缩，预期 BLEU 10-15</td>
      </tr>
      <tr>
          <td><strong>C. 跳级</strong></td>
          <td>Phase 6 Transformer</td>
          <td>并行 hash，多头碰撞，预期 BLEU 25-35</td>
      </tr>
  </tbody>
</table>
<p>路线 B 最合理——Attention 是自然演进。路线 C 最激进——跳过中间 Phase 直接验证终极架构。</p>
<p><strong>建议先跑 B（Attention + SoftBLEU），验证&quot;更好的 hash 策略 + BLEU 感知梯度&quot;的联合效果。</strong></p>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong></p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-005] 梯度函数决定学习规律</title>
      <link>https://www.grepcode.cn/wmt/005-wmt-gradient-theory.html</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/005-wmt-gradient-theory.html</guid>
      <description>从 sin(0.76) 到 tanh(3.06) 的距离，不是函数好坏——是梯度方向的自洽性决定了一个模型能走多远。</description>
      <content:encoded><![CDATA[<h1 id="梯度函数决定学习规律">梯度函数决定学习规律</h1>
<blockquote>
<p>sin 和 tanh 之间的距离不是 0.76 到 3.06。是&quot;自己打自己&quot;和&quot;方向从不出错&quot;之间的距离。</p></blockquote>
<h2 id="一个失败的实验一条意外的规律">一个失败的实验，一条意外的规律</h2>
<p>Phase 1.0 的 RNN 实验测了 7 组 hidden_size，最佳 BLEU 3.06。到此为止，这是一个标准的调参实验。</p>
<p>然后我们做了一个看似无厘头的尝试：<strong>把 RNN 的激活函数从 tanh 换成 sin。</strong></p>
<p>理由很朴素——sin 是周期函数。如果翻译本质是 hash 碰撞，周期函数天然支持&quot;多桶&quot;碰撞——同一个输出值可以从无限多个输入周期产生。tanh 只有一个桶（全局单调），sin 有无限个桶。理论上，多桶应该提供更多自由度。</p>
<p><strong>结果：sin RNN 的最佳 BLEU = 0.76。</strong> 远低于 tanh 的 3.06。loss 从 6.32 跌到 6.19 就几乎不动了。</p>
<p>这不是&quot;sin 比 tanh 差&quot;这么简单。这个 0.76 里藏着一个更根本的问题。</p>
<h2 id="梯度函数就是学习规律">梯度函数就是学习规律</h2>
<p>神经网络的每一步训练都遵循同一个模式：</p>
<pre tabindex="0"><code>loss → 计算梯度 → 用梯度更新参数
</code></pre><p>而&quot;计算梯度&quot;这一步，激活函数的选择是决定性的一环。<strong>换个激活函数，就是换一套梯度规则——也就是换一种学习方式。</strong></p>
<table>
  <thead>
      <tr>
          <th>激活函数</th>
          <th>梯度函数</th>
          <th>梯度方向</th>
          <th>学习特征</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>tanh</td>
          <td>tanh&rsquo;(x) ∈ (0,1]</td>
          <td><strong>永远为正</strong>，单调衰减</td>
          <td>笨但稳——方向从不出错</td>
      </tr>
      <tr>
          <td>ReLU</td>
          <td>0 或 1</td>
          <td>永远非负</td>
          <td>莽但快——不衰减，但会死</td>
      </tr>
      <tr>
          <td>sin</td>
          <td>cos(x) ∈ [-1,1]</td>
          <td><strong>正负交替</strong></td>
          <td>聪明但自毁——自己否定自己</td>
      </tr>
  </tbody>
</table>
<p>tanh&rsquo; 的范围是 (0,1]。值可以很小（接近饱和区），但<strong>符号不变</strong>。每一步梯度指向的方向，和全局最优方向一致。衰减只是让步子变小，不会让方向反了。</p>
<p>cos(x) 在每一个 π/2 的倍数处翻转符号。前半句学的方向，后半句亲手推翻。</p>
<h2 id="模拟为什么方向会反转">模拟：为什么方向会反转</h2>
<p>假设一段 RNN 的前激活值 z 随句子长度逐步偏移（模拟 W·h_{t-1} 的累积）：</p>
<table>
  <thead>
      <tr>
          <th>时间步</th>
          <th>z_t</th>
          <th>tanh&rsquo;(z)</th>
          <th>方向</th>
          <th>cos(z)</th>
          <th>方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>0.2</td>
          <td><strong>+0.96</strong></td>
          <td>→</td>
          <td><strong>+0.98</strong></td>
          <td>→</td>
      </tr>
      <tr>
          <td>2</td>
          <td>0.8</td>
          <td><strong>+0.50</strong></td>
          <td>→</td>
          <td><strong>+0.70</strong></td>
          <td>→</td>
      </tr>
      <tr>
          <td>3</td>
          <td>1.8</td>
          <td><strong>+0.12</strong></td>
          <td>→</td>
          <td><strong>−0.23</strong></td>
          <td>←</td>
      </tr>
      <tr>
          <td>4</td>
          <td>2.5</td>
          <td><strong>+0.02</strong></td>
          <td>→</td>
          <td><strong>−0.80</strong></td>
          <td>←</td>
      </tr>
      <tr>
          <td>5</td>
          <td>3.0</td>
          <td><strong>+0.005</strong></td>
          <td>→</td>
          <td><strong>−0.99</strong></td>
          <td>←</td>
      </tr>
  </tbody>
</table>
<p>tanh 所有时间步都往同一个方向推——力度越来越小，但方向一致。箱子被慢慢推到目标位置。</p>
<p>sin 在第 3 步翻转了方向。第 1-2 步往东推，第 3-5 步往西推。净位移接近零——箱子在原点附近来回跳舞。</p>
<p>这就是 BLEU=0.76 的根因。<strong>不是学不到东西，是学到的东西被自己亲手毁掉了。</strong></p>
<h2 id="好的梯度函数--自洽的向量场">好的梯度函数 = 自洽的向量场</h2>
<p>把梯度想象成地形图上的箭头。每个参数位置都有一个箭头指向&quot;下山&quot;的方向——这就是向量场。</p>
<ul>
<li><strong>tanh 的向量场</strong>：所有箭头指向同一个象限。可以弱，但方向一致。优化器只要跟着走，迟早到谷底。</li>
<li><strong>sin 的向量场</strong>：箭头来回翻转。优化器往前走两步，发现箭头指向回头了——刚走的路被否定。</li>
</ul>
<p><strong>&ldquo;好的梯度函数&quot;的标准不是&quot;梯度够不够大&rdquo;，而是&quot;梯度方向是否和全局最优方向一致&quot;。</strong></p>
<p>tanh 的梯度小不是缺陷——它宁愿走得慢，也不走错方向。这是工程上的&quot;慢就是快&quot;。</p>
<h2 id="loss-和-bleu两把不同的尺子">loss 和 BLEU——两把不同的尺子</h2>
<p>如果梯度函数的问题是方向，那还有一个更隐蔽的问题：<strong>梯度的方向是谁定的？</strong></p>
<p>答案是 loss——CrossEntropy。但 CrossEntropy 追求的是&quot;逐 token 预测精准&quot;，而 BLEU 考察的是&quot;整句 n-gram 碰撞率&quot;。<strong>两把尺子量的是不同的事。</strong></p>
<p>H=512 跑 20 epoch 的铁证：</p>
<table>
  <thead>
      <tr>
          <th>epoch</th>
          <th>loss</th>
          <th>BLEU</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>5.69</td>
          <td>1.84</td>
      </tr>
      <tr>
          <td>2</td>
          <td>4.76</td>
          <td><strong>3.02</strong></td>
      </tr>
      <tr>
          <td>10</td>
          <td>4.22</td>
          <td>1.79</td>
      </tr>
      <tr>
          <td>19</td>
          <td>4.46</td>
          <td>1.35</td>
      </tr>
  </tbody>
</table>
<p>loss 持续下降（模型自认为越来越好），BLEU 在 epoch 2 达峰后一路衰退（实际翻译质量越来越差）。<strong>模型在 teacher forcing 的镜子里觉得自己很美，但别人看到的是东施效颦。</strong></p>
<p>这意味着：即使我们选了一个&quot;方向自洽&quot;的梯度函数（如 tanh），梯度函数追的目标（loss）和最终评价标准（BLEU）之间<strong>存在结构性分歧</strong>。</p>
<h2 id="bleu-为什么不能直接当-loss">BLEU 为什么不能直接当 loss</h2>
<p>BLEU 的公式里有一个不可微的步骤——argmax。模型输出 logits 后，必须选一个词（argmax），然后在 n-gram 表里计数。argmax 是离散跳变：无论你把 <code>cat</code> 的概率从 0.4 提到 0.49，选出来的词不变——选的都是其他词。直到概率超过 0.51，argmax 的结果才会突然跳变。</p>
<p>所以 BLEU 对每个参数 W 的偏导链中，<code>∂argmax/∂logits</code> 这一项处处为零。<strong>整个梯度链直接断了。</strong></p>
<p>全行业用 CrossEntropy 当 loss 不是因为 CE 比 BLEU &ldquo;好&rdquo;——是<strong>只有 CE 能传梯度</strong>。BLEU 更好，但数学不让你用。</p>
<h2 id="soft-bleu让-bleu-听到梯度的声音">Soft BLEU——让 BLEU 听到梯度的声音</h2>
<p>如果把 argmax 换成 softmax，把硬 n-gram 计数换成概率加权：</p>
$$\text{SoftBLEU} = \frac{\sum_{w} \min(p(w|ctx), c_{ref}(w))}{\sum_{w} p(w|ctx)}$$<p>$p(w|ctx)$ 是模型预测 w 的连续概率。整条链可微——<strong>梯度可以直接从 BLEU 近似值流回模型参数。</strong></p>
<p>训练时与 CrossEntropy 混合：</p>
$$L_{total} = \lambda \cdot L_{CE} + (1-\lambda) \cdot (1 - \text{SoftBLEU})$$<p>当前仅测试了 λ=0.5（均衡权重）。λ 的最优值、λ 的退火策略（从 1.0 逐步降）仍需实验验证。</p>
<h2 id="四层-hash-碰撞框架">四层 Hash 碰撞框架</h2>
<p>把翻译过程看作四次 hash 碰撞：</p>
<table>
  <thead>
      <tr>
          <th>层次</th>
          <th>hash 空间</th>
          <th>碰撞判定</th>
          <th>参与梯度？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. Embedding</td>
          <td>E 维向量</td>
          <td>token→向量</td>
          <td>✅ CrossEntropy</td>
      </tr>
      <tr>
          <td>2. Hidden (RNN) 或 Attention (Transformer)</td>
          <td>H 维向量</td>
          <td>整句→压缩向量 / 选择性碰撞</td>
          <td>✅ CrossEntropy</td>
      </tr>
      <tr>
          <td>3. Decoder output</td>
          <td>V 维 logits</td>
          <td>向量→token</td>
          <td>✅ CrossEntropy</td>
      </tr>
      <tr>
          <td>4. BLEU</td>
          <td>n-gram 表</td>
          <td>输出←→参考</td>
          <td>❌ 不可微</td>
      </tr>
  </tbody>
</table>
<p>前三次碰撞都参与梯度。第四次——BLEU 碰撞——<strong>模型根本听不到</strong>。</p>
<p>RNN 的悲剧是第 2 层（时间轴压缩破坏信息结构），Transformer 换了第 2 层（Attention 选择性碰撞），但第 4 层的问题——BLEU 不参与梯度——Transformer 也没解决。</p>
<h2 id="实验结果softbleu-突破-ce-天花板">实验结果：SoftBLEU 突破 CE 天花板</h2>
<p>H=512, E=128, seed=42, batch=64, 在纯 RNN 上对比 CE-only 和 SoftBLEU 混合损失：</p>
$$L_{total} = \lambda \cdot L_{CE} + (1-\lambda) \cdot (1 - \text{SoftBLEU}), \quad \lambda = 0.5$$<table>
  <thead>
      <tr>
          <th>epoch</th>
          <th>CE-only BLEU</th>
          <th>CE</th>
          <th>SoftBLEU BLEU</th>
          <th>mixed loss</th>
          <th>SoftBLEU 值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>3.06</td>
          <td>6.14</td>
          <td><strong>2.49</strong></td>
          <td>3.47</td>
          <td>0.204</td>
      </tr>
      <tr>
          <td>1</td>
          <td>2.31</td>
          <td>5.38</td>
          <td><strong>3.21</strong></td>
          <td>3.07</td>
          <td>0.234</td>
      </tr>
      <tr>
          <td>2</td>
          <td>1.73</td>
          <td>5.08</td>
          <td>1.81</td>
          <td>2.92</td>
          <td>0.237</td>
      </tr>
      <tr>
          <td>3</td>
          <td>2.40</td>
          <td>4.88</td>
          <td>2.88</td>
          <td>2.82</td>
          <td>0.238</td>
      </tr>
      <tr>
          <td>4</td>
          <td>1.91</td>
          <td>4.74</td>
          <td>2.59</td>
          <td>2.75</td>
          <td>0.239</td>
      </tr>
  </tbody>
</table>
<p><strong>SoftBLEU best BLEU = 3.21（epoch 1），CE-only best = 3.06（epoch 0）。首次突破纯 CE 天花板。</strong></p>
<p>SoftBLEU 值自身从 0.204 → 0.239 逐步提升——模型在学会优化 BLEU 的 soft 近似。但提升幅度只有 +0.15，随后也陷入了过拟合。</p>
<p><strong>向量化是关键</strong>。最初的 Python 循环版 2 小时跑不出一个 epoch（详见 <a href="/wmt/006-wmt-softbleu-cost.html">第六篇</a>）。用 <code>scatter_add_</code> 替代 4 层 Python 循环后，5 epoch 仅需 5 分钟，速度提升 1000 倍。</p>
<h2 id="这条路通向哪里">这条路通向哪里</h2>
<p>sin 实验的失败，不是告诉我们&quot;sin 不好&quot;。它告诉我们：<strong>梯度函数的选择不是调参——是选择一种学习规律。</strong> 一个不能自洽的梯度函数，再大的模型也救不回来。</p>
<p>tanh 的梯度方向自洽，但目标是 loss 而非 BLEU。模型沿着 loss 的梯度方向走得太远，BLEU 反而掉进了过拟合的沟。这条沟就是 Soft BLEU 要填的。</p>
<p>后来的实验完整验证了这条规律——从 RNN (BLEU=3.0) 到 Attention (3.77) 再到 Transformer (11.70)，架构在变，但 hash 编码 + 梯度积分的底层逻辑不变。K_lang 在弱模型上补缺陷，在强模型上多此一举。<strong>梯度方向的正确性，从 sin→tanh 到 CE→SoftBLEU 到词级→BPE，是一条贯穿 9 篇文章的红线。</strong></p>
<p>详见 <a href="/wmt/008-wmt-phase6-transformer.html">008 — 从 3 到 11 的跃迁</a>。</p>
<hr>
<h2 id="dag-的血为什么多项式不是前提可用-backward-才是">DAG 的血——为什么多项式不是前提，可用 backward() 才是</h2>
<p>神经网络的训练本质是一个<strong>图上的血液系统</strong>：</p>
<pre tabindex="0"><code>DAG 骨架 = 拓扑结构（矩阵运算的依赖关系）
grad 血液 = 方向信号（∂L/∂W 沿树往回流动）
矩阵肌肉 = 存储信息（被梯度反复拉扯，最终成形）
</code></pre><p>ReLU 不是多项式——分段线性，x=0 有折角，数学上不可导。但它能进 DAG——PyTorch 约定折角处梯度为 0，一根人造血管填了那个洞。BLEU 的 argmax 进不了 DAG——不是因为它不光滑，是因为它的 backward 被设计成返回全零。血凝固了。但如果给 argmax 一个人造的 backward：</p>
<pre tabindex="0"><code>argmax backward (0/1 风格):
  预测词 ∈ reference → grad = 1
  预测词 ∉ reference → grad = 0
</code></pre><p>它就有了血。矩阵就能动。<strong>数学纯度不重要，方向正确才重要。</strong> sin 失败不是因为进不了 DAG——它是真导数。它失败是因为 cos 正负交替，方向自己打自己。</p>
<p>神经网络的训练，只需要两个条件：血在流（不全零），血的方向不对冲（不反转）。满足这两条，不管信号来自微积分、次梯度、还是 0/1 人造血，矩阵都会进化。</p>
<hr>
<h2 id="梯度训练--hash-编码--riemann-积分">梯度训练 = Hash 编码 + Riemann 积分</h2>
<p>神经网络的训练，本质上是把语言<strong>逐词逐句 hash 编码进权重张量</strong>。每一步梯度是当前 batch 的语言统计快照，训练过程就是把这些快照永久存储：</p>
$$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$$<p>每一个 <code>optimizer.step()</code> 的执行流程：</p>
<ol>
<li><strong>词 → embedding hash</strong>：token → $\mathbb{R}^d$ 向量，查表</li>
<li><strong>前向撞桶</strong>：hidden 层把向量推过 attention/FFN，到 output 层做 softmax 查桶</li>
<li><strong>比对碰撞</strong>：预测桶 vs 目标桶 → 算出误差</li>
<li><strong>反向修正</strong>：$\nabla\mathcal{L}$ 告诉你桶壁该往哪边挪</li>
<li><strong>积分存储</strong>：$W \mathrel{-}= \eta \cdot \nabla\mathcal{L}$ 把这次碰撞经验<strong>热记录</strong>进权重</li>
</ol>
<p>这就是 DAG 之血的完整循环：<strong>血液（梯度）在骨架（DAG）上流动，每次循环把一次 hash 碰撞的教训写入肌肉（权重矩阵）。</strong></p>
<h3 id="架构和损失函数同一枚硬币的两面">架构和损失函数——同一枚硬币的两面</h3>
<p>在 hash 编码框架下，架构和损失函数不再是竞争关系：</p>
<ul>
<li><strong>架构改变</strong> → hidden_seq 的表示能力变了 → <code>∂L/∂W</code> 经由不同路径回传 → 梯度的<strong>接收面</strong>不同</li>
<li><strong>损失函数改变</strong> → L 的定义变了 → <code>∂L/∂W</code> 的量级和方向变了 → 梯度的<strong>来源</strong>不同</li>
</ul>
<p>Attention 上用 K_lang +0.18 BLEU，Transformer 上一轮架构跳变就 +7.7 BLEU。不是因为 Architecture &gt; Loss，而是因为 Attention 的瓶颈在表示能力（LSTM 时序链），不是梯度方向。当架构表达力够了，但梯度目标离 BLEU 太远——这就是 SoftBLEU / K_lang 该上场的时候。</p>
<p><strong>梯度的多寡和方向才是学习的唯一原料。架构和损失函数只是不同的食谱。</strong></p>
<p>在这个框架下，所有调试的变量都有了统一的物理意义：</p>
<table>
  <thead>
      <tr>
          <th>变量</th>
          <th>物理意义</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>激活函数（tanh/sin/ReLU）</strong></td>
          <td>梯度方向的符号——是自洽推进还是自我否定</td>
      </tr>
      <tr>
          <td><strong>损失函数（CE/SoftBLEU）</strong></td>
          <td>碰撞的&quot;靶心&quot;定义——逐 token 精准 vs n-gram 覆盖</td>
      </tr>
      <tr>
          <td><strong>K_lang（k）</strong></td>
          <td>每次积分时，多少个桶参与修正——噪声滤波器</td>
      </tr>
      <tr>
          <td><strong>lr（η）</strong></td>
          <td>Riemann 积分的步长（dt）</td>
      </tr>
      <tr>
          <td><strong>深度（n_layers）</strong></td>
          <td>hash 链的长度——每个 token 经历多少次碰撞</td>
      </tr>
      <tr>
          <td><strong>宽度（d_model）</strong></td>
          <td>hash 桶的容量——每个桶能存多少信息</td>
      </tr>
      <tr>
          <td><strong>BPE vs 词级</strong></td>
          <td>hash 桶的大小选择——大桶少量 vs 小桶多量</td>
      </tr>
  </tbody>
</table>
<p>其中 lr 和 K_lang 是耦合的——同一个参数更新公式 <code>ΔW = η · ∂L/∂W</code> 里，η 决定走多远，K_lang 决定梯度分配到哪些桶。桶数错了，lr 再调也白搭。</p>
<hr>
<h2 id="频域梯度用-ifft-把离散-bleu-变成可微曲面">频域梯度——用 IFFT 把离散 BLEU 变成可微曲面</h2>
<p>HM 提出一个新的视角：离散函数不可导，不是因为&quot;离散不好&quot;——是微积分不自洽。如果换个数学基底，离散也许就可导了。</p>
<p>傅里叶逆变换就是把时域的离散方波变成频域的正弦波叠加：</p>
$$\text{BLEU}(t) \approx \sum_k c_k \cdot e^{i\omega_k t}$$<p>原信号是二值的（0 或 1，n-gram 碰上了没有），IFFT 重建后变成一个带限连续函数——在采样点之间自动做了光滑插值。</p>
<p><strong>梯度会学到什么？</strong> 不是每个 token 该改什么——是整句的频谱结构应该向哪个方向偏移：</p>
<table>
  <thead>
      <tr>
          <th>频段</th>
          <th>物理意义</th>
          <th>梯度信号</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>低频</td>
          <td>句长、全局流畅度</td>
          <td>brevity penalty 方向</td>
      </tr>
      <tr>
          <td>中频</td>
          <td>2~4 gram 词组边界</td>
          <td>词序排列方向</td>
      </tr>
      <tr>
          <td>高频</td>
          <td>token 噪声</td>
          <td>过拟合指标</td>
      </tr>
  </tbody>
</table>
<p>IFFT 的梯度天然多尺度——同时看低频和高频，不会像 CE 那样在 token 级别上过拟合。本质上是给 BLEU 套了一个频域的正则化外壳——在频率空间里，离散跳变被展开成了光滑的正弦基底。</p>
<p><strong>待验证</strong>：Phase 1 RNN 上跑 CE + DCT 频谱损失对照实验。</p>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发，但必须继承相同的开源协议，承认在算力宇宙中所有的迭代与变异。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-004] Phase 1 从 RNN 记忆到 LSTM 门控</title>
      <link>https://www.grepcode.cn/wmt/004-wmt-phase1.html</link>
      <pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/004-wmt-phase1.html</guid>
      <description>SameTime WMT Phase 1 学习记录——拆分为 1.0 vanilla RNN（理解记忆原理）和 1.1 LSTM（解决梯度消失），逐级对比。</description>
      <content:encoded><![CDATA[<h1 id="sametime-wmt-专题phase-1-从-rnn-记忆到-lstm-门控">SameTime WMT 专题：Phase 1 从 RNN 记忆到 LSTM 门控</h1>
<blockquote>
<p>&ldquo;不要跳过推车直接开跑车。先造一辆吱嘎作响的木板车，体会它为什么散架，再理解锻造淬火的钢架好在哪。&rdquo;</p></blockquote>
<h2 id="phase-1-拆分为两步">Phase 1 拆分为两步</h2>
<p>Phase 1 原计划走 &ldquo;RNN Seq2Seq&rdquo;，但代码里全是 <code>nn.LSTM</code>——中间缺了一环。</p>
<p>现在拆成两步：</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Phase 1.0</th>
          <th>Phase 1.1</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>目录</td>
          <td><code>phase1_0_rnn/</code></td>
          <td><code>phase1_1_lstm/</code></td>
      </tr>
      <tr>
          <td>模型</td>
          <td>2层 BiRNN + 2层 RNN</td>
          <td>2层 BiLSTM + 2层 LSTM</td>
      </tr>
      <tr>
          <td>状态</td>
          <td>只有 h_t</td>
          <td>h_t + c_t（细胞状态）</td>
      </tr>
      <tr>
          <td>门控</td>
          <td>无（纯 tanh）</td>
          <td>三扇门（f/i/o）</td>
      </tr>
      <tr>
          <td>梯度路径</td>
          <td>连乘 tanh(W) → 消失</td>
          <td>加性直通 → 保持</td>
      </tr>
      <tr>
          <td>参数</td>
          <td>~8M</td>
          <td>~16M</td>
      </tr>
      <tr>
          <td>目的</td>
          <td>理解记忆原理</td>
          <td>体会门控解决了什么</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="phase-10vanilla-rnn--推车">Phase 1.0：Vanilla RNN — 推车</h2>
<h3 id="rnn-如何记忆">RNN 如何&quot;记忆&quot;</h3>
$$h_t = \tanh(W_{hh} \cdot h_{t-1} + W_{ih} \cdot x_t + b)$$<p>每个时间步，RNN 把 <strong>上一个隐藏状态 $h_{t-1}$</strong>（历史）和 <strong>当前输入 $x_t$</strong>（现在）揉在一起，过一层 tanh，得到新的隐藏状态 $h_t$。</p>
<p><strong>直观类比</strong>：h_t 像一张便签。你每读一个词，擦掉便签上的一部分旧内容，写上当前词的摘要。</p>
<ul>
<li>读第 1 个词 → h_1 记着 &ldquo;I&rdquo;</li>
<li>读第 5 个词 → h_5 记着一些语法信息 + 部分语义</li>
<li>读第 50 个词 → h_50 中，第 1 个词的信息早已被 tanh(W) 连乘 50 次碾成粉末</li>
</ul>
<p><strong>tanh 的罪</strong>：<code>|tanh'(…)| ≤ 1</code>，但实际值通常 &laquo; 1。乘以一个权重矩阵 W（通常元素也 &lt; 1），50 步连乘后 ≈ 0。</p>
<h3 id="bptt为什么梯度消失了">BPTT：为什么梯度消失了</h3>
<p>反向传播需要计算 $\partial L / \partial W$。对于时间步 $t=1$ 的单词，梯度需要穿过 $T-1$ 个时间步才能到达损失函数：</p>
$$\frac{\partial L}{\partial h_1} = \frac{\partial L}{\partial h_T} \cdot \prod_{k=1}^{T-1} \frac{\partial h_{k+1}}{\partial h_k}$$<p>其中 $\frac{\partial h_{k+1}}{\partial h_k} = W_{hh}^T \cdot \mathrm{diag}(\tanh'(\dots))$</p>
<p>连乘项中 $W_{hh}$ 的特征值如果 $\lt 1 \to$ 梯度消失；如果 $\gt 1 \to$ 梯度爆炸。tanh 的导数范围是 $(0, 1]$，进一步压低梯度。</p>
<p><strong>结论</strong>：50 个词的句子，前 10 个词 ≈ 没有梯度信号。训练只学到了靠 target 端最近的词。</p>
<h3 id="代码级变化vs-phase-0">代码级变化（vs. Phase 0）</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Phase 0: 只处理 target</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">DummyModel</span>(nn<span style="color:#f92672">.</span>Module):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(self, src, tgt_in):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>out(self<span style="color:#f92672">.</span>embed(tgt_in))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Phase 1.0: Encoder 处理 src，Decoder 处理 tgt</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Seq2Seq</span>(nn<span style="color:#f92672">.</span>Module):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">forward</span>(self, src, tgt, src_len):
</span></span><span style="display:flex;"><span>        enc_out, hidden <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>encoder(src, src_len)  <span style="color:#75715e"># 编码源句</span>
</span></span><span style="display:flex;"><span>        logits, _ <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>decoder(tgt, hidden)          <span style="color:#75715e"># hidden 是唯一的信息传递者</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> logits
</span></span></code></pre></div><p>注意 <code>nn.RNN</code> 只返回 <code>hidden</code>（没有 <code>cell</code>），Decoder 签名更简洁。代价就是&quot;便签&quot;容量极其有限。</p>
<h3 id="对照分析实验-start">对照分析实验 start</h3>
<p><strong>实验目的</strong>：控制 <code>hidden_size</code> 为唯一自变量，观测 RNN Seq2Seq 的容量-质量关系，验证&quot;信息瓶颈&quot;假说。</p>
<p><strong>固定变量</strong>：</p>
<table>
  <thead>
      <tr>
          <th>变量</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>epochs</td>
          <td>5</td>
      </tr>
      <tr>
          <td>lr</td>
          <td>1e-3</td>
      </tr>
      <tr>
          <td>embed</td>
          <td>= hidden</td>
      </tr>
      <tr>
          <td>layers</td>
          <td>2 (双向 Encoder + 单向 Decoder)</td>
      </tr>
      <tr>
          <td>dropout</td>
          <td>0.3</td>
      </tr>
      <tr>
          <td>batch_size</td>
          <td>64</td>
      </tr>
      <tr>
          <td>seed</td>
          <td>42</td>
      </tr>
      <tr>
          <td>optimizer</td>
          <td>Adam</td>
      </tr>
      <tr>
          <td>dataset</td>
          <td>IWSLT14 de-en</td>
      </tr>
      <tr>
          <td>hardware</td>
          <td>RTX 3090 24GB, CUDA 12.1</td>
      </tr>
      <tr>
          <td>代码</td>
          <td><code>phase1_0_rnn/train.py</code> + <code>exp_hidden_scale.sh</code></td>
      </tr>
  </tbody>
</table>
<p><strong>自变量</strong>：hidden ∈ {16, 32, 64, 128, 256, 512, 1024}（$2^4 \sim 2^{10}$）</p>
<p><strong>运行时间</strong>：74 分钟，7 组全通过，无一 OOM。</p>
<h4 id="实验前假设2026-05-01-修订">实验前假设（2026-05-01 修订）</h4>
<p><strong>hash 碰撞发生在 embedding lookup，而非 hidden。</strong></p>
<p>从 hash 碰撞的视角：</p>
<pre tabindex="0"><code>token &#34;der&#34; → lookup(embedding[74360×E]) → [e₁, e₂, ..., e_E]      ← hash 碰撞在这里
                                                    ↓
RNN 逐词读取 → hidden 随时间步推移，前面的词被 tanh 碾碎              ← 不是碰撞，是擦除
                                                    ↓
最后 hidden₅₁₂ → 前 40 个词的信息 ≈ 消失                         ← 时序破坏，非容量不足
</code></pre><p>hidden 拥有 512 维空间，但 RNN 不会利用——不是&quot;装不下&quot;，而是前面的内容在时间轴上被后续写入覆盖了。<strong>hash 空间够大，写入方式却是有损的</strong>。</p>
<p>因此假设：</p>
<ol>
<li><strong>增大 hidden 对 BLEU 的提升应极小</strong>——hash 碰撞不发生在 hidden 维度，hidden 再大也不能阻止时间轴上的信息擦除</li>
<li><strong>真正的瓶颈在两方面</strong>：
<ul>
<li>a) embedding 宽度（hash key 长度）：74K 德文词挤在 E 维空间，E 太小则碰撞频繁</li>
<li>b) RNN 的时序写入方式：Attention 才能解决（Phase 2）</li>
</ul>
</li>
<li><strong>下一步应拆耦合实验</strong>：固定 hidden=512（最优容量），vary embed，验证 hash 宽度才是硬瓶颈</li>
</ol>
<hr>
<h4 id="实验结果">实验结果</h4>
<table>
  <thead>
      <tr>
          <th>hidden</th>
          <th>params</th>
          <th>epoch 0 BLEU</th>
          <th>best BLEU</th>
          <th>epoch 4 BLEU</th>
          <th>耗时</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>16</td>
          <td>3.1M</td>
          <td>0.00</td>
          <td>2.13 (ep4)</td>
          <td>2.13</td>
          <td>285s</td>
      </tr>
      <tr>
          <td>32</td>
          <td>6.1M</td>
          <td>1.05</td>
          <td>2.94 (ep4)</td>
          <td>2.94</td>
          <td>290s</td>
      </tr>
      <tr>
          <td>64</td>
          <td>12.1M</td>
          <td>2.97</td>
          <td>2.97 (ep0)</td>
          <td>2.55</td>
          <td>307s</td>
      </tr>
      <tr>
          <td>128</td>
          <td>24.3M</td>
          <td>2.71</td>
          <td>2.71 (ep0)</td>
          <td>2.17</td>
          <td>379s</td>
      </tr>
      <tr>
          <td>256</td>
          <td>49.0M</td>
          <td>2.02</td>
          <td>2.32 (ep2)</td>
          <td>2.24</td>
          <td>528s</td>
      </tr>
      <tr>
          <td>512</td>
          <td>99.8M</td>
          <td>1.84</td>
          <td><strong>3.02 (ep2)</strong></td>
          <td>2.48</td>
          <td>895s</td>
      </tr>
      <tr>
          <td>1024</td>
          <td>206.9M</td>
          <td>1.57</td>
          <td>2.42 (ep1)</td>
          <td>1.60</td>
          <td>1741s</td>
      </tr>
  </tbody>
</table>
<p>完整 jsonl 数据存储于 io: <code>/data/homecicd/sametime/results/metrics_h*.jsonl</code></p>
<h4 id="实验结论">实验结论</h4>
<p><strong>1. BLEU 天花板 ≈ 3.0 —— 信息瓶颈真实存在</strong></p>
<p>所有配置中最高 BLEU 仅 3.02（H=512, epoch 2）。这验证了 Phase 1.0 理论：无 Attention 的 Seq2Seq 中，Encoder 必须把整个源句压缩成一个固定维度向量，无论 hidden 多大，信息丢失不可逆。<strong>增大 hidden 只能缓解，不能突破</strong>。</p>
<p>看完整曲线：</p>
<ul>
<li>H=16→64：BLEU 随容量从 0 涨到接近 3，这是&quot;容量不足→够用&quot;的阶段</li>
<li>H=128→512：BLEU 在 2.3~3.0 之间震荡，增长停止——遇到硬天花板</li>
<li>H=1024：BLEU 反而降到 2.42——<strong>过参数化开始损害泛化</strong></li>
</ul>
<p><strong>2. Loss 下降 ≠ BLEU 提升 —— 过拟合的硬证据</strong></p>
<p>所有 7 组 loss 随 epoch 单调递减（收敛正常），但 BLEU 曲线不是：</p>
<table>
  <thead>
      <tr>
          <th>hidden</th>
          <th>BLEU 趋势</th>
          <th>解释</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>16/32</td>
          <td>持续上升到 epoch 4</td>
          <td>小模型容量不足，需要更多 epoch 学会基础映射</td>
      </tr>
      <tr>
          <td>64/128</td>
          <td>早期峰值后下降</td>
          <td>容量刚好时，teacher forcing 过拟合起效快</td>
      </tr>
      <tr>
          <td>256/512/1024</td>
          <td>中期峰值后衰退</td>
          <td>大模型快速过拟合训练集，Greedy Decode 泛化差</td>
      </tr>
  </tbody>
</table>
<p>这就是经典过拟合信号：模型在训练集上 loss 越来越低，但评估时 BLEU 却往下掉。</p>
<p><strong>3. 最佳容量出现在 H=512</strong></p>
<p>H=256→512→1024 形成倒 U 曲线：</p>
<ul>
<li>H=256: 49M, BLEU=2.32</li>
<li>H=512: 100M, BLEU=<strong>3.02</strong></li>
<li>H=1024: 207M, BLEU=2.42</li>
</ul>
<p>对 IWSLT14 de-en（160K 训练句），100M 参数恰好匹配数据量。再翻倍参数只是给过拟合喂了更多自由度。</p>
<p><strong>4. 小模型 BLEU 低但&quot;诚实&quot;</strong></p>
<p>H=16 和 H=32 在 epoch 0 时 BLEU 接近 0——容量太小，还没学会任何翻译。但它们的好处是：<strong>BLEU 持续上升</strong>，不会过拟合，训练到 20 个 epoch 可能最终追上大模型。</p>
<p><strong>5. 时间成本基本线性</strong></p>
<p>206M 参数（H=1024）耗时 29 分钟，3M 参数（H=16）耗时 5 分钟。规模-时间近似 $T \propto N^{0.85}$（接近线性），瓶颈在 vocab embedding （V×H）上而非 RNN 计算。</p>
<p><strong>6. 假设验证：hash 碰撞不在 hidden</strong></p>
<p>64 倍的 hidden 宽度（16→1024），BLEU 仅从 2.13→2.42。这验证了实验前假设：<strong>hidden 不是 hash 碰撞的现场</strong>。hidden=16 时信息容量才 16 维，但 BLEU 仍达 2.13——说明即使极端压缩，信息仍在传递。更大的 hidden 只是给了 RNN 更多&quot;便签空间&quot;，但没有改变&quot;便签被不断重写&quot;的本质。</p>
<p>因此 Phase 2（Attention）才是突破点，不是更大的 hidden。</p>
<hr>
<h4 id="实验二解耦合固定-hidden512vary-embed">实验二：解耦合——固定 hidden=512，vary embed</h4>
<p><strong>目的</strong>：验证 hash 碰撞究竟发生在 embedding 宽度还是 hidden 容量。</p>
<p><strong>固定</strong>：hidden=512（实验一最佳点），epochs=5, lr=1e-3, layers=2, seed=42</p>
<p><strong>自变量</strong>：embed ∈ {32, 64, 128, 256, 512, 1024}</p>
<table>
  <thead>
      <tr>
          <th>embed</th>
          <th>params</th>
          <th>epoch 0 BLEU</th>
          <th>best BLEU</th>
          <th>epoch 4 BLEU</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>32</td>
          <td>36.2M</td>
          <td>2.28</td>
          <td>2.64 (ep1)</td>
          <td>2.12</td>
      </tr>
      <tr>
          <td>64</td>
          <td>40.4M</td>
          <td>2.89</td>
          <td>2.89 (ep0)</td>
          <td>2.45</td>
      </tr>
      <tr>
          <td>128</td>
          <td>48.9M</td>
          <td>3.06</td>
          <td><strong>3.06 (ep0)</strong></td>
          <td>1.91</td>
      </tr>
      <tr>
          <td>256</td>
          <td>65.9M</td>
          <td>2.88</td>
          <td>2.88 (ep0)</td>
          <td>2.25</td>
      </tr>
      <tr>
          <td>512</td>
          <td>99.8M</td>
          <td>1.84</td>
          <td>3.02 (ep2)</td>
          <td>2.48</td>
      </tr>
      <tr>
          <td>1024</td>
          <td>167.7M</td>
          <td>2.28</td>
          <td>2.74 (ep1)</td>
          <td>2.21</td>
      </tr>
  </tbody>
</table>
<h4 id="实验二结论倒-u-形非单调">实验二结论：倒 U 形，非单调</h4>
<p>修正此前判断——<strong>embed 对 BLEU 有显著影响，但呈倒 U 形</strong>：</p>
<ol>
<li><strong>E=32→128：hash 宽度主导</strong>。BLEU 从 2.64 涨到 3.06（+0.42）。74K 德文词挤在 32 维时碰撞频繁，扩到 128 维后每维仅承载 ~580 词，碰撞锐减。</li>
<li><strong>E=128→1024：过拟合主导</strong>。BLEU 从 3.06 降到 2.74。即使 hash 碰撞更低（1024 维时每维仅 73 词），过大的 embedding 矩阵（168M 参数中的 134M 来自两个 embedding table）让 160K 训练句无法有效训练。</li>
<li><strong>最佳 hash 宽度 = 128 维</strong>。对 74K 词表，~580 词/维是最优碰撞密度。</li>
<li><strong>两块天花板合并</strong>：增加 hidden 上限 ~3.02（实验一），增强 embed 上限 ~3.06（实验二）。<strong>RNN 的 BLEU 硬上限 ≈ 3.0，无论怎么调参都破不了</strong>。</li>
</ol>
<h4 id="两张实验合并解读">两张实验合并解读</h4>
<pre tabindex="0"><code>实验一: hidden vary, embed=hidden (耦合)
        16→512:  BLEU ↑ (容量不足到够用)
        512→1024: BLEU ↓ (过拟合)

实验二: hidden=512, embed vary (解耦合)
        32→128:  BLEU ↑ (hash 碰撞减少)
        128→1024: BLEU ↓ (过拟合)
</code></pre><p><strong>共同规律</strong>：IWSLT14 de-en（160K 句）的最佳模型容量约 50M 参数（H=256，E=128 均在此附近）。超过此值的参数对 BLEU 贡献为负。</p>
<hr>
<h4 id="深度解释为什么是倒-u而不是越大越好">深度解释：为什么是倒 U，而不是&quot;越大越好&quot;？</h4>
<p>直觉告诉你：hidden 越大，模型越强，BLEU 越高。但实验事实是倒 U。错在哪儿？</p>
<p><strong>翻译是 hash 碰撞问题。增大 hidden 没有让碰撞变容易——它让碰撞变难了。</strong></p>
<p>把翻译过程看作 hash 运算：</p>
<pre tabindex="0"><code>Encoder(德语 &#34;der Mann&#34;)  ──→  hidden₅₁₂  ──→  Decoder  ──→  &#34;the man&#34;
      ↑                                   ↑
  把德文&#34;打散&#34;进 512 维空间       从 512 维&#34;还原&#34;出英文
</code></pre><p>hidden 就是 hash 的密钥空间。问题在于：<strong>翻译不是找&quot;任意碰撞&quot;，而是找恰好对应的那个语义映射</strong>。</p>
<ul>
<li>H=16：hash 空间极小（16 维），但目标也少——梯度下降很容易探索完整个空间，很快找到可行解。代价：空间太小，碰撞是粗糙的（&ldquo;随便一碰就对上&rdquo;，翻译不准）。</li>
<li>H=512：hash 空间恰好——梯度能在 160K 样本指引下找到足够精细的碰撞映射。</li>
<li>H=1024：hash 空间太大。每个训练的 step 梯度给你一点方向，但 1024 维空间里方向太多，160K 个训练信号根本不够覆盖。梯度在迷雾中摸索，沿着训练集的特征走错了岔路——把训练噪声当成了规律。这就是过拟合的物理本质：<strong>hash 空间太大，梯度迷路了</strong>。</li>
</ul>
<p>类比：你在北京找一个地址。</p>
<ul>
<li>H=16 是&quot;朝阳区&quot;——很小，你随便走几步就撞到了目的地。但地址过于模糊。</li>
<li>H=512 是&quot;朝阳区望京街道&quot;——刚好。你有 160K 个路人给你指方向，能精确定位。</li>
<li>H=1024 是&quot;朝阳区望京街道阜通东大街 6 号院 3 号楼 2 单元 1501 室&quot;——太细了。160K 个路人只能给你含糊的&quot;大概在那边&quot;。你走进了一个巨大的 hash 迷宫，而指点方向的样本量没有变多。你最终停在了一个看起来很像但其实是错误的位置——这就是过拟合的 hash 解释。</li>
</ul>
<p><strong>直觉的错位</strong>：我们混淆了&quot;表示能力&quot;和&quot;学习难度&quot;。更大的 hidden 确实能表示更精细的映射（hash 分辨率更高），但梯度下降未必能找到它。<strong>表示能力是线性的，搜索难度是超线性的</strong>——两条线交叉处就是倒 U 的顶点。</p>
<hr>
<h4 id="形式化碰撞密度模型">形式化：碰撞密度模型</h4>
<p>HM 提出以下模型：BLEU 由碰撞密度决定，分子控制碰撞概率，分母控制碰撞精度。</p>
$$BLEU(N_{\text{data}}, N_{\text{params}}) \propto \left| \frac{N_{\text{data}}}{N_{\text{params}}} - K_{\text{lang}} \right|^{-1}$$<p>其中：</p>
<ul>
<li>$N_{\text{data}}$ = 分子 = 训练数据量。增大分子 → hash 碰撞概率增大 → 更容易找到映射。但过大则碰撞太容易，映射粗糙。</li>
<li>$N_{\text{params}}$ = 分母 = 模型参数量。增大分母 → 碰撞概率减小 → 需要更精确的碰撞。但过大则找不到碰撞。</li>
<li>$K_{\text{lang}}$ = 语言常数 = 该语言对的最优碰撞密度。这是<strong>语言对的固有属性</strong>——由源语言词表大小、目标语言词表大小、语法复杂度差异共同决定。当前的实验任务（IWSLT14 de-en）中 $K_{\text{lang}} \approx 0.003$。</li>
</ul>
<p>epoch 不在公式里。epoch 是&quot;逼近精度&quot;——给定分子分母后，用多少步迭代去逼近理论 BLEU 上限。epoch 提高相当于计算 π 时用更多项——能提高精度，但不改变圆周率本身。</p>
<p><strong>实验验证</strong>：</p>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>H</th>
          <th>E</th>
          <th>N_params</th>
          <th>P = N_data/N_params</th>
          <th>BLEU</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>16</td>
          <td>16</td>
          <td>3M</td>
          <td>0.053</td>
          <td>2.13</td>
      </tr>
      <tr>
          <td>1</td>
          <td>32</td>
          <td>32</td>
          <td>6M</td>
          <td>0.027</td>
          <td>2.94</td>
      </tr>
      <tr>
          <td>1</td>
          <td>64</td>
          <td>64</td>
          <td>12M</td>
          <td>0.013</td>
          <td>2.97</td>
      </tr>
      <tr>
          <td>1</td>
          <td>128</td>
          <td>128</td>
          <td>24M</td>
          <td>0.0067</td>
          <td>2.71</td>
      </tr>
      <tr>
          <td>1</td>
          <td>256</td>
          <td>256</td>
          <td>49M</td>
          <td>0.0033</td>
          <td>2.32</td>
      </tr>
      <tr>
          <td>1</td>
          <td>512</td>
          <td>512</td>
          <td>100M</td>
          <td><strong>0.0016</strong></td>
          <td><strong>3.02</strong></td>
      </tr>
      <tr>
          <td>1</td>
          <td>1024</td>
          <td>1024</td>
          <td>207M</td>
          <td>0.0008</td>
          <td>2.42</td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>32</td>
          <td>36M</td>
          <td>0.0044</td>
          <td>2.64</td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>64</td>
          <td>40M</td>
          <td>0.0040</td>
          <td>2.89</td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>128</td>
          <td>49M</td>
          <td><strong>0.0033</strong></td>
          <td><strong>3.06</strong></td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>256</td>
          <td>66M</td>
          <td>0.0024</td>
          <td>2.88</td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>512</td>
          <td>100M</td>
          <td>0.0016</td>
          <td>3.02</td>
      </tr>
      <tr>
          <td>2</td>
          <td>512</td>
          <td>1024</td>
          <td>168M</td>
          <td>0.00095</td>
          <td>2.74</td>
      </tr>
  </tbody>
</table>
<p><strong>峰值聚集</strong>：两个实验的最佳 BLEU 均出现在 P ≈ 0.003 附近（49M 参数 / 160K 句）。</p>
<p><strong>待证伪的预测</strong>：</p>
<ol>
<li>若 $N_{\text{data}}$ 加倍到 320K，最佳 $N_{\text{params}}$ 应翻倍到 ~98M，$K_{\text{lang}}$ 不变。</li>
<li>换语言对（如 en-fr），$K_{\text{lang}}$ 应不同——语法越相似，最优 P 越大。</li>
<li>epoch 增大仅逼近理论 BLEU，不改变 $K_{\text{lang}}$ 的位置。</li>
</ol>
<h4 id="预测-3-证伪测试epoch-深度">预测 3 证伪测试：epoch 深度</h4>
<table>
  <thead>
      <tr>
          <th>实验</th>
          <th>hidden</th>
          <th>best BLEU</th>
          <th>peak epoch</th>
          <th>epoch 19 BLEU</th>
          <th>trend</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>H=512 ep20</td>
          <td>512</td>
          <td><strong>3.02</strong></td>
          <td>2</td>
          <td>1.35</td>
          <td>peak 后一路下滑</td>
      </tr>
      <tr>
          <td>H=1024 ep20</td>
          <td>1024</td>
          <td>2.42</td>
          <td>1</td>
          <td>1.94</td>
          <td>epoch 1 即巅峰，loss ep6 触底后反弹</td>
      </tr>
  </tbody>
</table>
<p><strong>H=1024 完整 20 轮（350W 功耗限制下稳定跑完，零故障）</strong>：</p>
<table>
  <thead>
      <tr>
          <th>epoch</th>
          <th>0</th>
          <th>1</th>
          <th>2</th>
          <th>6</th>
          <th>14</th>
          <th>17</th>
          <th>19</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>loss</td>
          <td>5.98</td>
          <td>5.37</td>
          <td>5.12</td>
          <td>4.80</td>
          <td>5.54</td>
          <td>5.89</td>
          <td>5.79</td>
      </tr>
      <tr>
          <td>BLEU</td>
          <td>1.57</td>
          <td><strong>2.42</strong></td>
          <td>1.77</td>
          <td>1.78</td>
          <td>1.41</td>
          <td>1.92</td>
          <td>1.94</td>
      </tr>
  </tbody>
</table>
<p>H=1024 过拟合更猛烈——epoch 1 即达峰，随后彻底崩溃。Loss 在 epoch 6 见底后反弹至 epoch 19，BLEU 持续震荡在 1.4~2.1 之间低位徘徊。</p>
<p><strong>结论：预测 3 成立，双重验证。</strong> epoch 不改变 $K_{\text{lang}}$。增加 epoch 对 BLEU 的提升为零甚至为负——模型在 teacher forcing 强约束下迅速过拟合训练集，泛化能力 epoch 2 即达上限，之后都是噪声学习。</p>
<hr>
<h4 id="预测-1-初步测试数据加倍">预测 1 初步测试：数据加倍</h4>
<p><strong>假设</strong>：若 $N_{\text{data}}$ 加倍（160K→320K），$N_{\text{params}}$ 的最佳点应右移。H=1024（207M）在大数据下应摆脱过拟合，BLEU 从 2.42 上升。</p>
<p><strong>结果</strong>（3/5 epoch，GPU 中途掉卡）：</p>
<table>
  <thead>
      <tr>
          <th>epoch</th>
          <th>0</th>
          <th>1</th>
          <th>2</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>data×2 BLEU</td>
          <td><strong>2.50</strong></td>
          <td>2.33</td>
          <td>2.29</td>
      </tr>
      <tr>
          <td>data×1 BLEU</td>
          <td>1.57</td>
          <td>2.42</td>
          <td>1.77</td>
      </tr>
  </tbody>
</table>
<p>epoch 0 BLEU 从 1.57 跳到 2.50（同 epoch 对比 +0.93），数据加倍大幅缓解了训练初期的冷启动——模型在更多梯度步数的指引下，epoch 0 就能学到不错的映射。但 epoch 1 之后 BLEU 依然衰退（2.33 vs data×1 的 2.42），<strong>过拟合路径没有改变</strong>——只是起始点提高了。</p>
<p><strong>结论</strong>：预测 1 方向正确——数据加倍在 epoch 0 产生了 +0.93 的显著提升。但过拟合路径未被改变，epoch 1 后 BLEU 依然衰退。$K_{\text{lang}}$ 可能不是纯碰撞密度问题——当参数多到 207M 时，即使是 320K 样本也不够填充 hash 空间。需要至少 10 倍数据量（1.6M+ 句）才能在 1024 维 hidden 空间里提供足够的梯度信号。</p>
<h3 id="对照分析实验-end">对照分析实验 end</h3>
<h3 id="对照分析实验-end-1">对照分析实验 end</h3>
<hr>
<h2 id="phase-11lstm--锻造淬火">Phase 1.1：LSTM — 锻造淬火</h2>
<h3 id="lstm-的三扇门--细胞状态">LSTM 的三扇门 + 细胞状态</h3>
$$f_t = \sigma(W_f \cdot [h_{t-1}, x_t])$$<p>
</p>
$$i_t = \sigma(W_i \cdot [h_{t-1}, x_t])$$<p>
</p>
$$o_t = \sigma(W_o \cdot [h_{t-1}, x_t])$$<p>
</p>
$$\tilde{c}_t = \tanh(W_c \cdot [h_{t-1}, x_t])$$$$c_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t$$<p>
</p>
$$h_t = o_t \odot \tanh(c_t)$$<p><strong>关键洞察</strong>：$c_t$ 的更新是 <strong>加性更新</strong>——$f_t \cdot c_{t-1} + i_t \cdot \tilde{c}_t$。没有连乘 tanh！</p>
<ul>
<li>忘了 0.3 旧记忆 + 写入 0.7 新信息 = 新细胞状态</li>
<li>梯度通过 c_{t-1}→c_t 可以&quot;直线传递&quot;（LSTM 论文称 &ldquo;Constant Error Carousel&rdquo;）</li>
<li>遗忘门 f_t 可以在训练中学到 &ldquo;保留重要信息&rdquo; vs &ldquo;丢弃无关信息&rdquo;</li>
</ul>
<p><strong>类比</strong>：c_t 是一张有&quot;编辑权限&quot;的便签。RNN 只能整张擦掉重写，LSTM 可以选择性地划掉某些字、补上新字。因此 50 步后，第一步的&quot;签名&quot;仍然可能清晰可辨。</p>
<h3 id="rnn-vs-lstm-实战对比">RNN vs LSTM 实战对比</h3>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Phase 1.0 (RNN)</th>
          <th>Phase 1.1 (LSTM)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>隐状态</td>
          <td>h_t (1 份)</td>
          <td>(hidden, cell) (2 份)</td>
      </tr>
      <tr>
          <td>参数</td>
          <td>H×H (单矩阵)</td>
          <td>H×H×4 (三扇门 + 候选)</td>
      </tr>
      <tr>
          <td>Decoder 接口</td>
          <td><code>decoder(tgt, hidden)</code></td>
          <td><code>decoder(tgt, (enc_out, (hidden, cell)))</code></td>
      </tr>
      <tr>
          <td>长句梯度</td>
          <td>趋近于 0</td>
          <td>可保持</td>
      </tr>
      <tr>
          <td>BLEU 预期</td>
          <td>很低</td>
          <td>高于 RNN，但 &lt; 5（仍信息瓶颈）</td>
      </tr>
  </tbody>
</table>
<h3 id="代码级变化">代码级变化</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Phase 1.0: RNN</span>
</span></span><span style="display:flex;"><span>self<span style="color:#f92672">.</span>rnn <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>RNN(input_size, hidden_size, num_layers, <span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>output, hidden <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>rnn(embedded, hidden)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># hidden: (num_layers, B, H)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Phase 1.1: LSTM</span>
</span></span><span style="display:flex;"><span>self<span style="color:#f92672">.</span>rnn <span style="color:#f92672">=</span> nn<span style="color:#f92672">.</span>LSTM(input_size, hidden_size, num_layers, <span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>output, (hidden, cell) <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>rnn(embedded, (hidden, cell))
</span></span><span style="display:flex;"><span><span style="color:#75715e"># hidden: (num_layers, B, H)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># cell:   (num_layers, B, H)  ← 细胞状态，梯度高速通道</span>
</span></span></code></pre></div><p><strong>PyTorch 层面</strong>: <code>nn.RNN</code> → <code>nn.LSTM</code> 仅改一行。但 LSTM 需要额外维护 cell state，Decoder 的 <code>forward</code> 签名从 <code>(tgt, hidden)</code> 变成 <code>(tgt, (hidden, cell))</code>。</p>
<hr>
<h2 id="提问环节">提问环节</h2>
<h3 id="q1-rnn-记忆的本质">Q1: RNN 记忆的本质</h3>
<p>RNN 把历史信息&quot;折叠&quot;进一个向量 h_t。这更像&quot;压缩/蒸馏&quot;还是更像&quot;遗忘/丢弃&quot;？如果用信息论的语言描述：h_t 的信息容量由 hidden_size 决定——256 维的向量能无损&quot;记住&quot;多长的句子？</p>
<p>Nio：参考 Phase 0 Q2 的 hash 碰撞理论——如果 h_t 是源语言的 hash 摘要，Decoder 需要从这个固定大小 hash 中&quot;解压&quot;出目标语言。信息容量随句子变长指数下降，这就是信息瓶颈。</p>
<p>HM: 如果只看RNN的问题，也就是h_t的形成过程。公式是</p>
$$h_t = \tanh(W_{hh} \cdot h_{t-1} + W_{ih} \cdot x_t + b)$$<p>
首先是梯度消失，可以看到tanh是单调的，随着刻意的梯度下降，W的元素会越来越小，tanh的输入也会越来越小，tanh的输出也会越来越小，最终导致梯度消失。其次是信息瓶颈，h_t的维度是固定的，无论输入多长的句子，h_t只能提供有限的信息容量。重点来了，我认为梯度下降的操作，就是本质的压缩操作。而这个过程在压缩时，没有一个衡量标准，确认多少信息量丢失了。最终压缩到至极，以至于没有信息增加，梯度消失了。后面的LSTM的设计，就是为了在压缩的过程中，用cell的恒定参数，保存所有信息量，可以看到随着梯度下降，cell是没有压缩的，保持恒定的维度和信息量。这样就解决了梯度消失带来的信息丢失的问题。这也告诉我们，压缩的过程开始写入信息，所以信息的写入顺序也注定了hidden的训练效果。也就是说，越靠近输入端的词，越早被写入hidden，越容易被压缩掉；越靠近输出端的词，越晚被写入hidden，越不容易被压缩掉。这就是为什么RNN更容易记住句子末尾的信息，而忘记句子开头的信息。所以神经网络是一种天然包含时序的压缩方式。</p>
<h3 id="q2-tanh-的驯服">Q2: tanh 的驯服</h3>
<p>RNN 用 tanh 激活 → 输出在 (-1, 1) 之间 → 导数最大为 1。LSTM 用 sigmoid (遗忘/输入/输出门) + tanh (候选记忆 + 输出门)。</p>
<p>为什么门控函数用 sigmoid 而不是 tanh？如果所有门都换成 tanh 会发生什么？</p>
<p>HM：看不懂，嚼不动，先放着</p>
<h3 id="q3-细胞状态的加法">Q3: 细胞状态的加法</h3>
<p>LSTM 的核心创新是 <code>c_t = f_t*c_{t-1} + i_t*c̃_t</code>——加法而非连乘。除了梯度直通，这种&quot;选择性遗忘 + 选择性写入&quot;与你之前提到的&quot;最低频率过滤&quot;（min_freq=2）有什么本质相似之处？</p>
<p>HM：这里的假设都是抽象的。什么是选择性遗忘 选择性写入？我觉得AI在乱提问。我写点我的理解，首先，一个h_t的容量是有限的，而RNN的选择是，不断覆盖，在h_t域内，只留下最近的信息，这个信息怎么进入h_t的？信息在h_t中选中的概率是不同的。改变参数就是改变获得decode结果的概率。每一次正确的hash碰撞，就是一次BLEU的增加。所以增加层数，能增加参数，容量就越大。而参数越多，encoding的过程需要的时间算力就越大，因为是一个压缩过程。所以为了在合适尺度获得更好的效果，需要找到训练数据和训练顺序的完美搭配。为什么LSTM比朴素RNN好？答案是，并不是一定好，如果h_t本身很小，两者应该没有区别，熵增速度太大，h_t根本存不下，所以完全没有差别。在h_t尺度足够的情况下，LSTM因为encoding的方式不同，hash的效果就有变化。cell存在，使得信息能均匀分布到h_t的每个hash位上，而不是重复覆盖在所有位上，这样就能更好地利用h_t的容量，hash碰撞的概率更高效，从而提高BLEU分数。理论上讲，应该可以通过计算h_t的熵增量，来提前评价要一个 encoder 的性能，比如一个长句的熵增显著大于一个短句，一个重复句子的熵增应该更小。而对于h_t的直接评价算法，目前没有。只有loss评价算法。</p>
<h3 id="q4-rnnlstm-的参数膨胀">Q4: RNN→LSTM 的参数膨胀</h3>
<p>LSTM 比 RNN 参数多了约 4 倍。在 IWSLT14 这种小数据集（160K 句）上，这个参数冗余是浪费还是必要？如果用 GRU（2 扇门，参数约为 LSTM 的 3/4），BLEU 会不会差不多？</p>
<h3 id="q5-信息瓶颈仍然存在">Q5: 信息瓶颈仍然存在</h3>
<p>即使换上 LSTM，&ldquo;上下文向量 c&rdquo;（Encoder 最后 hidden state）仍然是固定 512 维。Phase 2 引入 Attention 后，Decoder 可以&quot;跳过 c，直接看 Encoder 所有时间步的输出&quot;。</p>
<p>从 LSTM 的 c_t（细胞内记忆）到 Attention 的 c（动态加权上下文），这两个 &ldquo;c&rdquo; 从命名到作用有什么本质不同？LSTM 的 c_t 能不能替代 Attention？如果不能，为什么？</p>
<h3 id="q6-训练速度与算力">Q6: 训练速度与算力</h3>
<table>
  <thead>
      <tr>
          <th>Phase</th>
          <th>参数量</th>
          <th>每 epoch 时间（预估）</th>
          <th>梯度瓶颈</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1.0 (RNN)</td>
          <td>~8M</td>
          <td>快</td>
          <td>消失严重</td>
      </tr>
      <tr>
          <td>1.1 (LSTM)</td>
          <td>~16M</td>
          <td>中</td>
          <td>缓解</td>
      </tr>
      <tr>
          <td>2.0 (LSTM+Attn)</td>
          <td>~20M</td>
          <td>慢</td>
          <td>Attention 的计算代价</td>
      </tr>
  </tbody>
</table>
<p>在显存限制下（GTX 3090 24GB），如果 batch_size 从 64 降到 16 才能跑 LSTM，低 batch 引入的噪音和 LSTM 门控的稳定性之间如何权衡？</p>
<hr>
<h3 id="与nio的讨论-start">与Nio的讨论 start</h3>
<h4 id="梯度函数的本质选择一种学习规律">梯度函数的本质：选择一种学习规律</h4>
<p>HM 提出：换个激活函数 = 换个梯度函数 = 换种学习方式。sin 是周期函数——周期桶让 hash 碰撞有更多自由度。但实验结果 BLEU=0.76（tanh 3.06），彻底失败。</p>
<p><strong>失败原因</strong>：sin 的梯度 <code>cos(x)</code> 正负交替——前半句学的方向，后半句自己推翻。余弦每穿过 $\pi/2, 3\pi/2...$ 就翻转符号，模型在 hash 桶之间来回跳跃，净位移为零。tanh&rsquo; 永远正、永远衰减——笨但方向从不出错。</p>
<p><strong>好的梯度函数 = 自洽的向量场</strong>：每一步梯度指向的方向必须和全局最优方向一致。sin 的梯度在周期桶里反复翻转——不是没力气走，是走回来的路正好抵消了走出去的路。</p>
<h4 id="loss-和-bleu-不是一回事训练听不到翻译质量">loss 和 BLEU 不是一回事——训练听不到翻译质量</h4>
<p>H=512 ep20 的实证：</p>
<table>
  <thead>
      <tr>
          <th>epoch</th>
          <th>loss</th>
          <th>BLEU</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>5.69</td>
          <td>1.84</td>
      </tr>
      <tr>
          <td>2</td>
          <td>4.76</td>
          <td><strong>3.02</strong></td>
      </tr>
      <tr>
          <td>10</td>
          <td>4.22</td>
          <td>1.79</td>
      </tr>
      <tr>
          <td>19</td>
          <td>4.46</td>
          <td>1.35</td>
      </tr>
  </tbody>
</table>
<p>loss 持续下降（5.69→4.46），BLEU 在 epoch 2 达峰后一路衰退。<strong>模型在 teacher forcing 的镜子里觉得自己越来越好，但实际翻译质量越来越差。</strong></p>
<p>根因：CrossEntropy 度量的是&quot;逐 token 预测是否和 tgt 一致&quot;，BLEU 度量的是&quot;整句 n-gram 和 ref 的碰撞率&quot;——<strong>两个不同的 hash 空间</strong>。loss 在训练 hash 桶里找一模一样的 token，BLEU 在测试 hash 桶里考 n-gram 的碰撞。train 的碰撞太完美（teacher forcing 提供完美答案），test 的碰撞太稀疏（greedy decode 没有完美参考）。</p>
<h4 id="bleu-为何不能直接当-loss">BLEU 为何不能直接当 loss</h4>
<p>BLEU 的公式里有 <code>argmax</code>——把 logits 变成离散的词序列。这一步数学上不可导：</p>
$$ \frac{\partial \text{BLEU}}{\partial W} = \frac{\partial \text{BLEU}}{\partial \text{argmax}} \times \frac{\partial \text{argmax}}{\partial \text{logits}} \times \frac{\partial \text{logits}}{\partial W} $$<p>第二项 $\partial \text{argmax}/\partial \text{logits}$ 处处为零——无论你预测 <code>cat</code> 的概率是 0.4 还是 0.49，选出来的词不变，直到 0.51 才跳变。梯度在跳变之前为零，在跳变点不存在。</p>
<p>CrossEntropy 能传梯度是因为它不选词——它直接用整个概率分布和 one-hot 目标比对，每个词的预测概率都参与计算。</p>
<h4 id="解决方案soft-bleu">解决方案：Soft BLEU</h4>
<p>把硬 BLEU 的 argmax 换成 softmax，把硬 n-gram 计数换成概率加权：</p>
$$\text{SoftBLEU} = \frac{\sum_{w \in hyp} \min(p(w|ctx), c_{ref}(w))}{\sum_{w \in hyp} p(w|ctx)}$$<p>$p(w|ctx)$ 是模型预测 w 的连续概率，不是 agrmax 的离散结果。整个链条可微——<strong>梯度可以直接从 BLEU 近似值流回模型参数</strong>。</p>
<p>训练时与 CrossEntropy 混合使用：</p>
$$L_{total} = \lambda \cdot L_{CE} + (1-\lambda) \cdot (1 - \text{SoftBLEU})$$<p>$\lambda$ 从 1.0 逐步降到 0.5，让模型从&quot;学 token&quot;平滑过渡到&quot;学翻译&quot;。</p>
<h4 id="四层-hash-碰撞统一框架">四层 hash 碰撞统一框架</h4>
<p>HM 提出：翻译本质是四次 hash 碰撞的链式查找：</p>
<table>
  <thead>
      <tr>
          <th>层次</th>
          <th>hash 空间</th>
          <th>碰撞判定</th>
          <th>参与梯度？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. Embedding</td>
          <td>E 维向量</td>
          <td>token→向量</td>
          <td>✅ CS</td>
      </tr>
      <tr>
          <td>2. Hidden</td>
          <td>H 维向量</td>
          <td>整句→压缩向量</td>
          <td>✅ CS</td>
      </tr>
      <tr>
          <td>3. Decoder output</td>
          <td>V 维 logits</td>
          <td>向量→token</td>
          <td>✅ CS</td>
      </tr>
      <tr>
          <td>4. BLEU</td>
          <td>n-gram 表</td>
          <td>输出←→参考</td>
          <td>❌ 不可微</td>
      </tr>
  </tbody>
</table>
<p>前三次碰撞都参与梯度，模型拼了命优化。第四次——BLEU 碰撞——模型根本听不到。整个训练的悲剧在于：<strong>优化器在追第 1~3 层的碰撞，但评分用第 4 层。</strong></p>
<p>Transformer 改了第 2 层（Attention 替代 RNN 的时序压缩），但第 4 层的问题——BLEU 不参与梯度——Transformer 也没解决。Soft BLEU 是填这条沟的可行方向。</p>
<h4 id="sin-激活函数实验">sin 激活函数实验</h4>
<table>
  <thead>
      <tr>
          <th>激活</th>
          <th>best BLEU</th>
          <th>loss 变化</th>
          <th>关键观察</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>tanh</td>
          <td><strong>3.06</strong></td>
          <td>5.69→4.46</td>
          <td>baseline，稳定学习</td>
      </tr>
      <tr>
          <td>sin</td>
          <td>0.76</td>
          <td>6.32→6.19</td>
          <td>loss 几乎不动，梯度方向翻转</td>
      </tr>
  </tbody>
</table>
<p>sin 的多桶 hash 理论方向对——周期桶提供了更多自由度。但 cos 梯度的方向交替翻转让参数在桶之间跳跃，净学习量为零。</p>
<h3 id="与nio的讨论-end">与Nio的讨论 end</h3>
<hr>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime》系列采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发，但必须继承相同的开源协议，承认在算力宇宙中所有的迭代与变异。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[WMT-003] Phase 0 实验底座骨架</title>
      <link>https://www.grepcode.cn/wmt/003-wmt-phase0.html</link>
      <pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://www.grepcode.cn/wmt/003-wmt-phase0.html</guid>
      <description>开启 SameTime 的 WMT 学习专题，聚焦 benchmark/wmt/phase0 的骨架实验与翻译管线构建。</description>
      <content:encoded><![CDATA[<h1 id="sametime-wmt-专题phase-0-底座骨架">SameTime WMT 专题：Phase 0 底座骨架</h1>
<blockquote>
<p>&ldquo;千里之行，始于足下。用最简模型跑通全流程，才能洞察系统的每一寸肌理。&rdquo;</p></blockquote>
<h2 id="专题介绍">专题介绍</h2>
<p>本专题聚焦 <code>holds/SameTime/benchmark/wmt/</code> 目录下的渐进式 WMT 学习路线。
从 Phase 0 开始，逐级深入翻译模型的构建与演化。</p>
<h2 id="phase-0-是什么">Phase 0 是什么</h2>
<p>Phase 0 (<code>phase0_skeleton/</code>) 是整套 WMT Benchmark 的实验底座骨架——用最简单的 DummyModel（复制模型）跑通数据加载、训练循环、评估评测的完整管线。</p>
<h3 id="核心组件">核心组件</h3>
<table>
  <thead>
      <tr>
          <th>组件</th>
          <th>说明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>数据</strong></td>
          <td>IWSLT14 De-En，word-level tokenizer，Vocab 统一管理</td>
      </tr>
      <tr>
          <td><strong>模型</strong></td>
          <td>DummyModel（Embedding → Linear），不做翻译，仅验证管线</td>
      </tr>
      <tr>
          <td><strong>评估</strong></td>
          <td>sacreBLEU</td>
      </tr>
      <tr>
          <td><strong>训练</strong></td>
          <td>Teacher Forcing，CrossEntropyLoss</td>
      </tr>
  </tbody>
</table>
<h3 id="学习要点">学习要点</h3>
<ol>
<li>IWSLT14 的数据格式（translation 字段）</li>
<li>word-level vocab 的构建与 OOV 处理</li>
<li>collate_fn 如何对齐 batch 长度</li>
<li>BLEU 的数值范围与意义</li>
</ol>
<h2 id="提问环节">提问环节</h2>
<p>在深入代码之前，我想先了解你对以下几个问题的看法，以此确立学习起点：</p>
<h3 id="q1-整体认知">Q1: 整体认知</h3>
<p>你如何理解 Phase 0 在整个 WMT 渐进路线（Phase 0 → RNN → Attention → BPE → Transformer）中的定位？它只是一个调试工具，还是另有更深层的工程哲学意义？</p>
<p>从0开始一般都是从简单开始，一则搭建环境，二则明白任务。即是工具的熟练，也是技能的开始。</p>
<p>从环境来说，有以下的几个概念：</p>
<ol>
<li>数据集，IWSLT14，数据集分为三部分 train+valid+test。其中主要语言是德语和英语。其中德语→英语（de-en） 是最受研究者欢迎的基准方向。</li>
<li>拿到数据该干嘛。
<ol>
<li>数据转换，xml转你需要的格式，json text等等</li>
<li>分词 Tokenization ，没错，token大概就是这个词的缩写，就是&quot;我一把把把把住了&quot;-&gt; &ldquo;我 一把 把 把 把住 了&rdquo;。分词的方式有很多，最简单的就是空格分词，当然还有更复杂的BPE分词，WordPiece分词等等。</li>
<li>过滤,我也没看懂为啥要过滤</li>
<li>分集，就是训练集和验证集划分，很多技巧，比如抽样划分等等，可按需使用</li>
<li>BPE Byte-Pair Encoding 处理，BPE是一种基于统计的分词方法，主要用于处理未登录词（OOV）问题。它通过将频率较高的子词组合成新的词来减少词汇表的大小，从而提高模型的泛化能力。HM:用颗粒度小的词来组成颗粒度大的词，这样可以减少原子量。<code>这也告诉我们，也许本来的向量化过程，已经被向量复合过一次了</code></li>
<li>格式化，把数据转换成模型需要的输入格式，比如很多，这里就不多说了</li>
<li>写代码，写代码，写代码&hellip;</li>
</ol>
</li>
<li>代码的开车步骤是什么? 这个可以看看西瓜书的原理说明，一般来说是这样的：
<ol>
<li>建立词表
<ul>
<li>先读训练集合train_raw</li>
<li>从训练集json中抽取de语和en语文本list,de是src，en是target</li>
<li>Vocab构建词表。就是量化的前一步，把文本转成数字的过程。这个过程会把文本中的每个词映射到一个唯一的整数ID上，这样模型就可以处理这些数字了。</li>
</ul>
</li>
<li>DataLoader
<ul>
<li>加载数据，我的理解，就是每次不能处理全量数据，内存放不下，所以要分批加载，每次加载一部分数据进行训练，这就是batch的概念。</li>
<li>collate_fn的作用是把每个batch的数据进行处理，使得它们的长度一致，方便模型进行训练。比如说，句子长度不一样，那么就需要进行padding，collate_fn就可以帮我们完成这个工作。</li>
<li>然后选是否shuffle，打乱数据的顺序，这样可以增加模型的泛化能力。<code>我的理解，这里存在较大性能问题，因为句序可能影响句子含义，shuffle可能会打乱句子顺序，导致模型学习到错误的语义关系。</code></li>
</ul>
</li>
<li>选模型
<ul>
<li>选模型，选个简单的模型，DummyModel，Embedding层和Linear层组成的简单模型，不做翻译，仅验证管线。</li>
<li>详细内容Q2记录</li>
</ul>
</li>
<li>训练
<ul>
<li>可参考西瓜书，训练过程一般包括前向传播、计算损失、反向传播和参数更新。前向传播是指将输入数据通过模型进行计算，得到输出结果；计算损失是指将模型的输出结果与真实标签进行比较，得到一个数值来衡量模型的性能；反向传播是指根据损失值计算梯度，并将梯度传递回模型的参数；参数更新是指根据计算得到的梯度来调整模型的参数，以使得损失值最小化。<code>内容机器生成</code></li>
<li>这里的训练过程使用了 Teacher Forcing，Teacher Forcing 是一种训练技巧，在训练过程中，模型在每个时间步都会使用真实的目标输出作为输入</li>
</ul>
</li>
<li>验证 <code>内容机器生成</code>
<ul>
<li>验证，验证过程一般包括评估模型的性能和调整模型的参数。评估模型的性能可以使用各种指标，如准确率、精确率、召回率、F1分数等；调整模型的参数可以使用各种优化算法，如随机梯度下降（SGD）、Adam等。</li>
<li>这里的评估使用了 sacreBLEU，sacreBLEU 是一种用于评估机器翻译系统性能的指标，它通过比较机器翻译输出与参考翻译之间的 n-gram 重叠来计算得分。sacreBLEU 的得分范围通常在 0 到 100 之间，得分越高表示翻译质量越好。</li>
</ul>
</li>
</ol>
</li>
</ol>
<h3 id="q2-dummymodel-的设计">Q2: DummyModel 的设计</h3>
<p>DummyModel 使用 teacher forcing + next-token prediction。如果错误地将 tgt[:, :-1] 改为 tgt（即直接预测整句而非 next-token），训练会如何崩溃？这对理解自回归模型的因果结构有什么启示？如果去掉 teacher forcing 中的 shift 操作（即用 tgt 同时作为输入和输出），模型会学到什么？为什么这违背了自回归模型的自回归假设？</p>
<p>先学习：</p>
<ol>
<li>
<p>teacher forcing 的概念，teacher forcing 是一种训练技巧，在训练过程中，模型在每个时间步都会使用真实的目标输出作为输入，而不是使用模型之前预测的输出。这种方法可以加速训练过程，并帮助模型更快地收敛。<code>内容机器生成</code></p>
</li>
<li>
<p>我的理解</p>
<ol>
<li>首先，DummyModel和src语言无关，就是分析target语言而已，<code>请注意，输入的训练数据是target语言</code>，对实验来说，是用自己翻译成自己。</li>
<li>如果没有相位移动，直接原相位计算，应该和shift差别很大，自己翻译成自己，误差应该是0。因为依然是target语言到target语言的信息分布，相位差0等于没有丢失或者差异，但相位差大于0带来的差别应该很大，尤其是距离越远差距越大。</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>logits <span style="color:#f92672">=</span> model<span style="color:#f92672">(</span>src, tgt<span style="color:#f92672">[</span>:, :-1<span style="color:#f92672">])</span>  <span style="color:#75715e"># (B, T, V)</span>
</span></span><span style="display:flex;"><span>loss <span style="color:#f92672">=</span> criterion<span style="color:#f92672">(</span>logits.reshape<span style="color:#f92672">(</span>-1, logits.size<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>, tgt<span style="color:#f92672">[</span>:, :-1<span style="color:#f92672">]</span>.reshape<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> device<span style="color:#f92672">=</span>cuda
</span></span><span style="display:flex;"><span>building vocab...
</span></span><span style="display:flex;"><span>|vocab_src|<span style="color:#f92672">=</span><span style="color:#ae81ff">74360</span>  |vocab_tgt|<span style="color:#f92672">=</span><span style="color:#ae81ff">56652</span>
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>  loss<span style="color:#f92672">=</span>1.483  BLEU<span style="color:#f92672">=</span>91.23
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>  loss<span style="color:#f92672">=</span>0.292  BLEU<span style="color:#f92672">=</span>97.32
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logits <span style="color:#f92672">=</span> model<span style="color:#f92672">(</span>src, tgt<span style="color:#f92672">[</span>:, :-1<span style="color:#f92672">])</span>  <span style="color:#75715e"># (B, T, V)</span>
</span></span><span style="display:flex;"><span>loss <span style="color:#f92672">=</span> criterion<span style="color:#f92672">(</span>logits.reshape<span style="color:#f92672">(</span>-1, logits.size<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>, tgt<span style="color:#f92672">[</span>:, 1:<span style="color:#f92672">]</span>.reshape<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> device<span style="color:#f92672">=</span>cuda
</span></span><span style="display:flex;"><span>building vocab...
</span></span><span style="display:flex;"><span>|vocab_src|<span style="color:#f92672">=</span><span style="color:#ae81ff">74360</span>  |vocab_tgt|<span style="color:#f92672">=</span><span style="color:#ae81ff">56652</span>
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>  loss<span style="color:#f92672">=</span>7.006  BLEU<span style="color:#f92672">=</span>1.80
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>  loss<span style="color:#f92672">=</span>5.777  BLEU<span style="color:#f92672">=</span>1.88
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logits <span style="color:#f92672">=</span> model<span style="color:#f92672">(</span>src, tgt<span style="color:#f92672">[</span>:, :-2<span style="color:#f92672">])</span>  <span style="color:#75715e"># (B, T, V)</span>
</span></span><span style="display:flex;"><span>loss <span style="color:#f92672">=</span> criterion<span style="color:#f92672">(</span>logits.reshape<span style="color:#f92672">(</span>-1, logits.size<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>, tgt<span style="color:#f92672">[</span>:, 2:<span style="color:#f92672">]</span>.reshape<span style="color:#f92672">(</span>-1<span style="color:#f92672">))</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> device<span style="color:#f92672">=</span>cuda
</span></span><span style="display:flex;"><span>building vocab...
</span></span><span style="display:flex;"><span>|vocab_src|<span style="color:#f92672">=</span><span style="color:#ae81ff">74360</span>  |vocab_tgt|<span style="color:#f92672">=</span><span style="color:#ae81ff">56652</span>
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>  loss<span style="color:#f92672">=</span>7.559  BLEU<span style="color:#f92672">=</span>0.00
</span></span><span style="display:flex;"><span>epoch<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>  loss<span style="color:#f92672">=</span>6.696  BLEU<span style="color:#f92672">=</span>0.00
</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>Phase 0<span style="color:#f92672">]</span> <span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>如上测试数据，分别是相位差0 1 2的数据结果</p>
</li>
</ol>
<h3 id="q3-bleu-的意义">Q3: BLEU 的意义</h3>
<p>Phase 0 的 BLEU 会极低（接近 0），你如何看待&quot;明知 BLEU 很低仍要跑评估&quot;的行为？这在工程验证中是否必要？</p>
<ol>
<li>
<p>对于DummyModel的相位0的情况，BLEU会很高。因为DummyModel的设计就是让模型直接复制输入的文本作为输出，所以在相位0的情况下，模型的输出与目标文本完全一致，因此BLEU得分会非常高，接近100。</p>
</li>
<li>
<p>本质上，翻译问题是一个hash碰撞的问题。可以这样认为，一个语言模型描述世界模型的过程，是一次hash运算，将世界知识hash存储到一个矩阵中。而翻译问题，是找到一个算法，可以实现一次巧妙的hash碰撞，使得两个语言模型的hash值，获得同一个矩阵中的存储知识。一般来说，embedding的相似性搜索，就是找到hash碰撞的算法之一。但算法并不唯一，且并不高明。<code>找到hash碰撞的算法</code>，也就是找到一个翻译算法，使得输入和输出的hash值相同，这样就实现了翻译。</p>
</li>
<li>
<p>问题中说，BLEU很低，这个过程，在上述实验结果中可以看出。0相位 1相位 2相位，BLUE快速降低。我们知道0相位很高是因为自己和自己发生hash碰撞是很容易的，属于同一个语言模型。而1相位，是整个语言模型做了一次相位位移，可以认为，如果存在一个客观知识矩阵，整体距离偏移了1，那么hash碰撞的概率就会大幅降低。2相位，距离偏移了2，hash碰撞的概率就更低了。<code>请注意，如果存在客观知识矩阵的说法</code>并不是已知问题，这里有巨大的危险性，需要慎重对待。</p>
</li>
<li>
<p>工程必要性，是必须的，因为我们需要验证整个评测流程的正确性。即使我们知道BLEU得分会很低，但如果评测流程有任何错误，我们可能会得到一个不合理的BLEU得分，这会误导我们对模型性能的评估。因此，运行评测是必要的，以确保我们的评测流程是正确的，并且能够正确地反映模型的性能。<code>内容机器生成</code></p>
</li>
</ol>
<p>HM：这个假设就是一个假设，就是客观知识矩阵，比如也许存在某种外星语言，他们的语言是支持相位位移的，就是相位n之后，依然能保持BLEU很高，那么&hellip;我们就得到一个奇怪的teacher forcing的结果，模型在相位n之后，依然能保持高BLEU，这就说明了这个假设的危险性，因为我们并没有任何证据表明存在这样的客观知识矩阵，所以我们需要谨慎对待这个假设。比如《誓食狮》。</p>
<h3 id="q4-数据管线">Q4: 数据管线</h3>
<p>IWSLT14 的 word-level vocab 用了 min_freq=2 过滤低频词，这意味着什么？OOV 词在编码时被映射为 UNK token——在后续 Phase 中这个问题会如何被解决？</p>
<p><code>内容机器生成</code>过滤低频词的意思是，在构建词汇表时，只有那些在训练数据中出现至少两次的词才会被包含在词汇表中。那些只出现一次的词会被视为低频词，并且不会被包含在词汇表中。这种过滤方法可以帮助减少词汇表的大小，从而降低模型的复杂度和计算资源的需求。</p>
<p>HM:我的理解，如果需要计算低频词，需要足够的信息量。例如一个完型填空问题，用周围的词场，重新扰动word bag的概率分布，是需要有价值的计算。低频词往往代表很大或者几乎全部的word bag，也就无法做预测了。也就是说，预测低频词和从全部word bag中选词是等价的，这样的处理方式，没有意义。</p>
<p>OOV词是什么？OOV（Out-Of-Vocabulary）词是指在训练数据中没有出现过的词。当模型遇到这些词时，由于它们不在词汇表中，无法为它们分配一个唯一的ID，因此会将它们映射为一个特殊的UNK（Unknown）token。这样，模型就可以处理这些未知词，但它无法区分不同的未知词，这可能会影响模型的性能。<code>内容机器生成</code></p>
<h3 id="q5-代码结构">Q5: 代码结构</h3>
<p><code>base/dataset.py</code>、<code>base/eval.py</code>、<code>base/utils.py</code> 被所有 Phase 共享。这种设计模式叫什么？它给后续递增式开发带来了哪些好处与风险？</p>
<p>这种设计模式叫做模块化设计（Modular Design）。模块化设计是一种软件设计方法，将系统划分为独立的模块，每个模块负责特定的功能。这些模块可以被多个不同的部分共享和重用。<code>内容机器生成</code></p>
<p>HM：我觉得是组成的设计模式，就是一个功能，外挂一个组成的模块，这个模块可以被多个功能共享和重用。比如说，数据集模块，评测模块，工具模块，这些都是组成的模块，可以被不同的功能共享和重用。这个问题没啥糖分，不甜，不说了。</p>
<hr>
<p>请逐一回答上述问题。你的回答将作为本专题的第一篇学习记录，在后续 Phase 中被持续引用和迭代。</p>
<p><em>May the Code be with us.</em></p>
<hr>
<blockquote>
<p><strong>License: GPLv3</strong><br>
本文《SameTime》系列 采用 GNU 通用公共许可证第三版 (GNU General Public License v3.0) 协议进行开源发布与分发。允许任何形式的复制、修改和分发，但必须继承相同的开源协议，承认在算力宇宙中所有的迭代与变异。</p></blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
