Sutskever 30 #19:消息一跳一跳地传,传多了整张图糊成一个点

一张随机树,二十来个节点,其中一个是源点,身上藏着一个 bit。其余节点身上也各有一个 bit,全是噪声。任务只有一句话:每个节点都要报出源点那个 bit。 我先拿一个只看自己特征的逐节点分类器跑了一遍。它在源点上 100% 正确——源点带着标记,那个 bit 本来就在它自己手里——在其余每一个节点上,0.501。非源点身上那个 bit 是我特意撒的噪声,跟答案统计独立,所以一个看不见邻居的网络在这些位置上能做的最好的事就是抛硬币。这道题被设计成必须用图:源点的值只能沿着边一格一格爬到别人那里。 于是问题变成,一轮消息传递到底能把这个 bit 带多远。 一轮消息传递,正好一条边 Gilmer 他们 2017 年那篇 Neural Message Passing for Quantum Chemistry 干的事,是把之前一堆各叫各名字的图网络(GG-NN、GCN、interaction networks……)收进同一个抽象里:每个节点先收邻居发来的 message,把它们 aggregate 成一个向量,再拿这个向量 update 自己的状态,跑几轮之后 readout 出图级或节点级的预测。我把它扒到只剩骨头,一轮长这样: $$h_v' = \tanh\!\Big(W_s\, h_v + W_n \sum_{w \in N(v)} \hat A_{vw}\, h_w + b\Big)$$h_v 是节点 v 的隐状态,求和跑遍它的邻居, 是归一化后的邻接矩阵(下面会细说)。关键在求和那一项:一个节点这一轮只能拿到它直接邻居的状态。所以源点的信息每过一轮只能往外渗一跳。要让距离 d 的节点知道源点的值,至少得跑 d 轮。 这跟 #18 是同一面墙,只是从序列搬到了图上:那边是感受野,一维卷积每层把视野撑大固定几格;这边是跳数,每轮消息传递把视野撑大一跳。把训好的网络按节点到源点的图距离分桶,逐桶看准确率: R=1 的网络在距离 1 以内全对,距离 2 起掉到 0.5;R=2 多撑一格,距离 3 起掉下去;R=3 再多一格。三条断崖整整齐齐,各自落在自己的跳数上。桶内数字是 R=1 视野内 1.000 / 视野外 0.503,R=2 是 1.000 / 0.496,R=3 是 0.997 / 0.483。 ...

June 17, 2026

Sutskever 30 #18:卷积看不到的地方,再怎么训也是抛硬币

先说实验。一条长 64 的随机 0/1 序列,每个位置都要答一道题:y[t] = x[t] XOR x[t-24](前 24 个位置没有过去,答案就是 x[t] 本身)。四个一维因果卷积网络,通道数、优化器、数据全一样,只有接线不同。其中感受野最小的那个训完之后,把每个位置的测试准确率单独画出来,是这样: 红线在 t=24 那里直接断崖。前 24 个位置全对——那段答案就是当前位,谁都会。从 t=24 起,答案开始依赖 24 步之前的那一位,而这个网络的感受野只有 13:往回第 24 位在它的视野外面。视野外的位跟视野内的一切统计独立,所以它能做的最好的事就是抛硬币。0.688 的总准确率也没什么可调的:24/64 × 1.0 + 40/64 × 0.5 = 0.6875,到顶了。 我喜欢这个断崖的地方在于它干净。前几篇的瓶颈都软乎乎的——梯度小了、训练慢了、种子背了——这次是信息论意义上的墙:看不到就是看不到,损失函数上不存在任何下降方向能把那一位变出来。为了确认墙是真的,我做了个更狠的检查:拿一个网络,把输入里恰好在感受野边界外一位的那个 bit 翻转,输出 logit 的变化是精确的 0.0,不是 1e-8,是 0。翻边界内一位的,logit 就动了。 感受野是接线时定死的算术 因果卷积一层的输出是 $$y_t = b + \sum_{j=0}^{k-1} W_j\, x_{t - j\cdot d}$$k 是核宽,d 是 dilation——卷积核的几个 tap 之间隔多远。d=1 是普通卷积,d=2 就是隔一个取一个。每过一层,感受野长 (k-1)·d,整个网络能看多远就是 1 + Σ(k-1)·d,一道小学算术,训练动不了它一毫米。 ...

June 13, 2026

Sutskever 30 #17:残差的 skip 上不能随便乘个数

#16 结尾我说那根 skip 现在到处都是,每个 Transformer block 里都躺着一个。那篇把它当成一个干净的 +x 用。可它真得是 +x 吗?把它换成 +0.9·x 或者 +1.1·x,差这么一点,按说无所谓。He、Sun 他们 2016 年的后续论文(Identity Mappings)专门为这点较了真,结论是差这一点,深网络就重新训不动了。原因还是那个老问题——你又在沿着深度乘东西了,而 skip 当初就是为了让你别再乘。 给 skip 乘上一个数 干净的残差块是 x ← x + F(x)。一路堆上去,第 l 层到第 L 层之间,主干就是一串加法: $$x_L = x_l + \sum_{i=l}^{L-1} F(x_i)$$反向也就跟着干净。x_l 收到的梯度是 $$\frac{\partial x_L}{\partial x_l} = I + \sum_{i=l}^{L-1}\frac{\partial F(x_i)}{\partial x_l}$$那个 I 就是 #16 里反复说的直通线,梯度原样递回去,不管中间几十层。 现在在 skip 上压个常数 λ,块变成 x ← λ·x + F(x)。把它展开,主干上每过一块就多乘一次 λ,到第 L 层: $$x_L = \lambda^{\,L-l}\, x_l + \cdots$$梯度也一样,x_l 那一项前面挂上了 λ^{L-l}。λ 只要不等于 1,这就是深度的指数。50 层下来,λ=1.1 是 1.1^50 ≈ 117,λ=0.8 是 0.8^50 ≈ 1.4e-5。skip 本来是用那个 I 把连乘掐断的,你给它乘个 0.9,连乘原封不动地回来了。 ...

June 11, 2026

Sutskever 30 #16:深网络越深越难训,ResNet 怎么解的

按说网络越深应该越强。再不济,多出来的层学成恒等映射,整个网络退回浅的,也不该更差。但真把一个普通深网络堆起来,事情是拧着的:过了某个深度,越加层越难训,连训练集都拟合不动,训练误差不降反升。He、Zhang、Ren、Sun 2015 年的 ResNet 针对的就是这个怪现象。 上一篇 #15 的 RMC 我训得挺狼狈,三个随机种子里两个直接塌到瞎猜,当时只甩了句「深层堆叠难训」就糊弄过去了。这篇算把那句话补完——它的解法,残差连接,如今每个 Transformer block 里都躺着一个。 残差块 改动很小。普通一层从头学一个变换,h ← tanh(Wh);残差块学的是在原来基础上加多少: $$h \leftarrow h + W_2\,\tanh(W_1 h)$$F 没学到有用的东西就退回 0,这层自动成了恒等映射。「至少不会更差」这件事,被写进了结构里。 真正值钱的是反向。梯度经过一个残差块时, $$\frac{\partial y}{\partial x} = I + \frac{\partial F}{\partial x}$$I 那一项把上游梯度原样递回去。哪怕 ∂F/∂x 已经小到忽略不计,梯度也跌不破这个 1。叠几十层,最前面那几层照样收得到信号。普通层没有这一项,它的梯度是几十个雅可比矩阵连乘的结果,乘着乘着就归零了。 在螺旋上跑一遍 我拿三条缠在一起的螺旋臂做二维分类。这任务浅网络随手就解,是我故意挑简单的:这样深网络一旦训不动,锅只能算在优化头上,跟任务难不难没关系。 两个网络宽度相同、权重层数也相同,唯一的区别是那根 skip 线。深度从 2 拉到 48: 深度(权重层数) plain 残差 8 1.000 1.000 16 0.978 1.000 24 0.628 1.000 32 0.520 1.000 48 0.502 1.000 浅的时候两边都满分。plain 过了 16 层开始掉,到 48 层只剩 0.5,离三分类瞎猜的 0.33 没多远了。这是训练准确率——它连自己浅的时候能轻松拟合的那批数据,现在都喂不进去了。残差网络参数一个不差,全程 1.0。 ...

June 8, 2026

Sutskever 30 #15:把注意力当记忆用,可惜很娇气

#14 结尾留了个尾巴:RN 把每一对对象等权求和,差一步就是 self-attention——把等权换成 softmax 加权。Santoro 那帮人 2018 年的 Relational Recurrent Neural Networks(模型叫 Relational Memory Core,简称 RMC)就把这一步迈了出去,还顺手做了件事:把这套加权的 all-pairs 塞进一块循环记忆里。不是对输入对象两两算一次,而是养一组「记忆槽」,每来一个输入就让这些槽互相 attend、互相更新一遍。 拆到底,RMC 就是拿一个 Transformer block 当记忆的更新规则用。想法很漂亮。但我训下来最大的收获,是它在最小版里出乎意料地难伺候。 它长什么样 记忆是 S 个槽、每个宽 D 的一块矩阵 M。每来一个输入,嵌成一行 e,拼到记忆后面,然后对这 S+1 行做一个标准的 Transformer encoder block: $$\tilde M = \mathrm{LN}\big(M + \mathrm{Attn}(M,\ [M; e])\big), \qquad M' = \mathrm{LN}\big(\tilde M + \mathrm{MLP}(\tilde M)\big)$$attention 的 query 来自记忆槽,key 和 value 来自「槽加上这一步的输入」,所以一个槽既能读别的槽,也能读新来的输入。M' 就是下一步的记忆。整段序列走完,把最后的记忆拍平读出答案。跟 #12 NTM 那套手工设计的读写头比,这里没有专门的读写机制,记忆的更新规则就是 self-attention 本身——这也正是它跟 #05 Transformer 的关系:同一个 block,Transformer 拿它当整个模型的主干,RMC 拿它当循环记忆的更新规则。 ...

June 7, 2026

Sutskever 30 #14:不挑了,每一对都算一遍

#13 Memory Networks 里 attention 干的是挑:拿问题去跟每条事实比一比,挑出该读的那条,多跳就是顺着挑好几次。从 #09 Bahdanau 起,这一整条线都在把「挑」做得更软、更可微。 Santoro 那帮人 2017 年的 A simple neural network module for relational reasoning(模型常被叫 Relation Network,下面简称 RN)问了句反过来的话:能不能不挑?它的答案是不挑。把每一对对象 (o_i, o_j) 都丢进同一个小网络 g,全部加起来,再用 f 从这个和里读出答案: $$RN(O) = f_\phi\Big(\sum_{i,j} g_\theta(o_i, o_j, q)\Big)$$没有 attention 权重,没有路由,没有谁比谁重要。所有对一视同仁,各算一遍,求和。听着挺笨,但这一笨恰好是后来 self-attention 的骨架——self-attention 不过是把这个等权的和换成了 softmax 加权的和。 为什么求和就够 三件事撑着这个模块。一个 relation 本来就是「两个对象的函数」,同一个 g 用在每一对上,参数省,还逼着模型把关系当成通用的东西,而不是给每对配一套。一堆对象本来没有先后,先求和再读出,意味着打乱顺序结果一个字都不变——置换不变是白送的。问题 q 拼进每一对里,g 看到的是 (o_i, o_j, q),于是「关心哪种关系」由问题说了算。 读出那一步就是过个 f 再 softmax, $$\hat a = \mathrm{softmax}\big(f_\phi(r)\big), \qquad r = \sum_{i,j} g_\theta(o_i, o_j, q)$$跟 #12 NTM 那套 location addressing、erase/add 比,这里干净得不像话:两个 MLP,一次求和。论文标题里的 simple 不是谦虚。 ...

June 5, 2026

Sutskever 30 #13:只读,但读好几遍

从 #12 的"写"接过来 #12 Neural Turing Machine 让 attention 既能读也能写一块外部 memory。代价是那套寻址机器很重:内容寻址之外还要 interpolation、convolutional shift、sharpening 一条流水线,写的时候又要 erase 再 add。东西能跑,但训练很 brittle,换个任务就得重调。 Sukhbaatar、Szlam、Weston、Fergus 2015 End-To-End Memory Networks(这一篇下面简称 MemN2N)问了个反方向的问题:这些机器是不是大部分都可以不要? 它的答案是:把写砍掉,把 location addressing 砍掉,只留 NTM 里最朴素的那一半——content-based 的读。memory 不再是一块要反复擦改的矩阵,就是输入本身,写一次,之后只读。 只读会不会太弱?MemN2N 的补法是:读好几遍。一遍不够就拿这一遍的结果当新问题再读一遍。下面把这事拆开看。 记忆就是输入本身 NTM 要专门维护一块 N×M 的矩阵,每步擦改。MemN2N 干脆不写:一段话里的每个事实,就是 memory 里的一行。 把每个事实 x_i(一句话的 bag-of-words)用一个嵌入矩阵投成向量 m_i = A x_i,问题 q 投成 u = B q。读一遍就是一次 content addressing——跟 #11 Luong 的点积、#12 的内容寻址是同一个动作: $$p_i = \mathrm{softmax}(u^\top m_i), \qquad o = \sum_i p_i\, c_i$$p 是 attention 权重,o 是读出来的内容。注意读出用的是另一套嵌入 c_i = C x_i——一套嵌入管"拿什么去匹配",一套管"匹配上了读回什么"。后来 Transformer 里 key 和 value 分开,是同一个分工。 ...

June 4, 2026

Sutskever 30 #12:让 attention 能写

从 #10 留下的问题接过来 #10 Pointer Networks 让 attention 第一次直接当输出——softmax 不再对着固定词表挑词,而是对着输入位置挑一个。decoder 每一步指向输入里的某个地方。 不过那还是 read-only 的指针。模型只能指过去读,不能在那个位置上留下新内容。 Graves、Wayne、Danihelka 2014 Neural Turing Machines 把指针往前推了一步:attention 不只能读,还能写。模型旁边放一块外部 memory,就是一张可读可写的矩阵。每一步 controller 算出两类指针:一组用来读,一组用来擦+加。 这就是 NTM 的主问题:如果 attention 已经能指向输入里的位置,下一步自然就是:它能不能指向一块可读可写的记忆? 一块 N×M 的矩阵 把 memory 想成一个矩阵 M,N 行(slot 数),每行 M 维(slot 大小)。这就是 NTM 的"外部 memory"。 读操作很直接。给一个 attention 权重 w(一个长度为 N 的概率分布),读出来的就是按 w 对所有行的加权求和: $$r_t = \sum_i w_t(i) \cdot M_t(i)$$跟 Bahdanau attention 算 context vector 是一样的——只是这里的"key/value"不是 encoder 的 annotations,而是 memory 矩阵本身。 写操作多一步:先按 w 擦掉旧内容,再按 w 加上新内容。controller 输出两个 M 维向量:一个 erase vector e,一个 add vector a: ...

April 27, 2026

Sutskever 30 #11:让 attention 算得快一点

从 #10 留下的尾巴说起 #10 Pointer Networks 结尾留了一句:2015 年 attention 沿两条线继续长——一条是 Pointer Networks,让 attention 直接当输出;另一条是 Luong,让 attention 算得更便宜。 这一篇走第二条。 Bahdanau 2014 把 attention 接进了 NMT,效果好。但 attention 这一步本身是有成本的。每生成一个目标词,decoder 都要对 encoder 的所有位置打一遍分。源句 50 个词,decoder 生成 50 个词,光是算 score 就要 2500 次。 每一次 score 怎么算,决定了这件事到底贵不贵。 Bahdanau 的算分方式 Bahdanau 用的是加性形式: $$e_{ij} = v^\top \tanh(W s_{i-1} + U h_j)$$s 是 decoder 的上一步状态,h 是 encoder 第 j 个位置的 annotation。 这里面有三组参数:W(把 s 投到打分空间)、U(把 h 投到打分空间)、v(把打分空间压成一个标量)。算一次 score 要做两次矩阵乘、一个 tanh、一个内积。 ...

April 25, 2026

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