Sutskever 30 #10:答案就在输入里

从上一篇留下的问题说起 #09 Bahdanau attention 里 attention 是 decoder 的一件工具:每生成一个词,先对 encoder 的所有位置打分,按权重加权求和,得到一个 context 向量喂给 decoder。最终输出还是走 softmax,对着一张固定词表挑一个词。 翻译任务这么做没问题。词表就那几万个词,目标语言里可能出现的词都在表里。 一旦任务变了,问题就出来了。 有些任务的答案不在固定词表里 给 7 个二维点,让模型按凸包顺序把其中几个点排出来——输出不是词,是"第 2 号点、第 5 号点、第 6 号点…“这种索引。 所谓凸包顺序,就是沿着最外圈走一圈时,外圈那些点出现的次序。内部的点不参与。 每一步的输出都是"看这 7 个输入点中的哪一个”。固定词表在这里就不够用了。 做法 1:把点的坐标离散化成 tokens,放进词表。坐标是连续的,词表会很大,而且每换一批点就得重训。 做法 2:让词表大小等于输入长度,每次动态调整。7 个点词表就是 7;10 个点词表就是 10。这已经碰到 Pointer Networks 的方向了,但标准 seq2seq 里 decoder 输出层的维度是固定的,换个输入长度还得改网络结构。 真正的问题是这一句:有些任务的答案不在固定词表里,答案就在输入本身。 这类任务不少:凸包(输出是输入点的子集,按某种顺序);排序(输出是输入的重排);TSP(输出是城市的访问顺序)。共同点是输出的每一步都是在说"回去看输入里的哪一个位置"。 机制:让 softmax 指回输入 Pointer Networks 做了一件很朴素的事。 Bahdanau attention 里,每一步算出的 attention 分布只是中间产物——过 softmax 得到权重,再乘 annotations 加权求和,得到 context 向量。分布本身被丢掉了。 Pointer Networks 让 softmax 后的 attention 分布直接当输出分布用:原来 softmax 对着固定词表挑词;现在 softmax 对着输入位置挑一个。 ...

April 24, 2026

Sutskever 30 #09:不再只靠最后一个向量

同一年,两组人 2014 年 9 月,两篇论文几乎同时挂上 arXiv。 一篇是 Sutskever、Vinyals、Le 的 Sequence to Sequence Learning with Neural Networks。Google Brain 出品,用两个 LSTM——一个读输入、一个生成输出——直接刷新了英法翻译的纪录。这是 #04 讲过的故事。 另一篇是 Bahdanau、Cho、Bengio 的 Neural Machine Translation by Jointly Learning to Align and Translate。蒙特利尔大学加雅各布大学,用 GRU + 双向 encoder,每生成一个目标词都回头看一遍源句。 两组人没合作,也没引用对方的当年版本,但他们面对的是同一个问题:任意长度的输入最后都要变成一个固定长度的向量,句子一长,信息就会丢。 两篇论文的处理办法不一样。Sutskever 还在固定向量里想办法,倒着读输入,让最先要翻译的信息离 decoder 更近。Bahdanau 把 encoder 的每一步都留下来,decoder 每一步根据当前状态选择要参考哪些 encoder state。 卡在哪里 #04 里有一张图:序列越长,第一个词的信息能存活到最后一个 hidden state 的比例越低。50 个词的句子,开头那个词存活率不到 2%。 LSTM 已经缓解了长距离记忆问题。这里卡住的是另一件事:decoder 只拿到 encoder 最后一步的状态。这个状态是一个固定大小的向量,不管输入是 5 个词还是 50 个词,维度都不变。容量有上限,信息没上限。 Sutskever 论文里有一个做法暴露了这件事:把输入序列倒过来读,BLEU 涨好几个点。“Je suis étudiant” 改成 “étudiant suis Je” 喂进去。倒读没有改变模型容量,只是让 encoder 最后一步离 decoder 最先要生成的词更近。这种局部修补能挤出几个点,但也说明结构本身还没换过。 ...

April 23, 2026

Sutskever 30 #04:记住了,然后呢?

上一篇留下的问题 上一篇里,LSTM 解决了 vanilla RNN 记不远的问题——加法通道让梯度不再衰减,记忆可以跨越几十步传下去。 但我们一直在做同一件事:给定一个序列,预测下一个 token。输入和输出共享同一个时间线——第 1 步输入产生第 1 步输出,第 2 步输入产生第 2 步输出。 现实世界的很多任务不是这样的。翻译:一句中文进去,一句英文出来,长度不同,语序不同。摘要:一段话进去,一句话出来。对话:一个问题进去,一个回答出来。 输入是一个序列,输出是另一个序列。长度可以不同,结构可以不同。怎么办? 两个 LSTM,一根管子 2014 年,Sutskever、Vinyals 和 Le 提出了一个极简的方案:用两个 LSTM,中间接一根管子。 Encoder:一个 LSTM,把输入序列从头读到尾。读完之后,最后一步的 hidden state 就是整个输入的"总结"——一个固定长度的向量。 Decoder:另一个 LSTM,拿到这个向量作为初始状态,一个 token 一个 token 地生成输出。 就这么简单。输入和输出可以不同长度,因为 encoder 把任意长度的输入压缩成一个固定长度的向量,decoder 从这个向量展开成任意长度的输出。 代码的核心就几行(这里用简化的 RNN 展示结构,实际论文用的是 LSTM——gate 的细节上一篇讲过了): # Encoder:读完整个输入,拿到最后的 hidden state h = np.zeros((hidden_size, 1)) for x in input_sequence: concat = np.vstack([x, h]) h = np.tanh(np.dot(W_enc, concat) + b_enc) context = h # 这就是"总结"——整个输入压缩成一个向量 # Decoder:从 context 开始,逐步生成输出 h = context for t in range(max_output_length): concat = np.vstack([prev_output, h]) h = np.tanh(np.dot(W_dec, concat) + b_dec) output = np.dot(W_out, h) + b_out prev_output = output 这个架构有个名字——encoder-decoder,也叫 seq2seq(sequence to sequence)。它是 Sutskever 自己的工作,发表时直接打破了法语-英语翻译的最佳纪录。 ...

April 17, 2026