同一套 32 个参数,树结构最终隐状态范数 1.6301,串行同序 1.4702, 串行换序 1.2785——换序就差 2.5048。 叶子到 256 个时,树只要 8 层,串行要 256 层,差 32 倍。
上一节的 GAN 是「两个网络对抗」。 这一节先解决一个容易混的概念问题:中文里的「递归神经网络」其实指两种完全不同的东西。
一、两个词,一个翻译
Recurrent(循环):按时间步串成一条链,每一步吃一个输入,隐状态在链上传。这是 8.2.3 讲的那个。 Recursive(递归):按语法树自底向上组合,每个节点吃它下面两个孩子。这是这一节要讲的。 中文都翻成「递归」或「循环」,很多资料混着用。 看到这两个词时,先看上下文是「时间步」还是「树」——这是唯一的判别办法。 书里 8.2.3 讲 Recurrent,8.2.5 讲 Recursive,两节连着排,很容易读串。
二、同一套参数,不同的组合结构
Demo 只用一个组合函数:tanh(W 乘 左孩子和右孩子拼起来的向量),W 是 4×8,一共 32 个参数。 树结构按 ((0 1)(2 3)) 组合:先把 0 和 1 合成 h01,2 和 3 合成 h23,再把 h01 和 h23 合成根。 三次组合。 串行结构按 0 1 2 3 顺序一步吃一个,四次组合。 两者参数完全一样,结果不同:树是 1.6301,串行是 1.4702,差 2.5970。 差别只来自「谁和谁先组合」。
三、顺序敏感度:2.5048
把串行的顺序从 0 1 2 3 改成 2 3 0 1,最终隐状态范数从 1.4702 变成 1.2785, 两者相差 2.5048。 这个数说明 Recurrent 强依赖位置——同样的内容换个顺序,结果就完全不同。 对语言来说这是对的(「狗咬人」和「人咬狗」不一样)。 树结构编码的不是「谁排在前」,是「谁是父节点」。 左孩子和右孩子角色不同,换过来 W 吃到的拼接向量就变了,所以它也不是完全不管顺序。 区别在于:Recurrent 的顺序是「位置」,Recursive 的顺序是「结构角色」。
四、真正的代价在层数上
叶子 4 个时,树的层数是 2,串行是 4。差 2 倍,不明显。 叶子 256 个时,树的层数是 8(log2 256),串行是 256。差 32 倍。 这个差距直接决定了「梯度要穿多少层」。8.2.3 算过,20 步就已经衰减到百万分之一,256 步根本不用想。 所以 Recursive 的价值不只是「适合树形数据」,还有「深度只有对数级」。 代价是必须先有那棵树——句法树得靠额外的解析器给,而 Recurrent 只要按顺序喂,不需要任何额外结构。 这也是为什么 Recursive 只在句法分析、程序语法树这类「结构明确」的任务上用, 通用序列任务还是走链式或注意力。
五、动手 Demo:递归与循环:同样的参数,层数差 32 倍
4 个 4 维叶子,只用一个 4×8 的组合矩阵(32 个参数); 分别按语法树 ((0 1)(2 3)) 和串行 0 1 2 3、2 3 0 1 三种方式组合,比最终隐状态; 再算叶子数从 4 到 256 时两种结构的层数。
def comb(l, r):
return np.tanh(W @ np.concatenate([l, r]))
h01 = comb(leaves[0], leaves[1]) # 树:自底向上
h23 = comb(leaves[2], leaves[3])
h_tree = comb(h01, h23)
for i in range(4): # 串行:一步一个
h = np.tanh(W @ np.concatenate([h, leaves[i]]))
实跑输出:
--------------------------------------------------------------------
递归与循环:同样的参数,不同的组合结构
--------------------------------------------------------------------
叶子 4 个,每个 4 维
组合函数 tanh(W × 拼接(左, 右)),W 是 4 × 8
参数量 32(两种结构完全一样)
结构 组合次数 最长链长 最终隐状态范数
---------------------------------------------------------
树结构 ((0 1)(2 3)) 3 2 1.6301
循环 顺序 0 1 2 3 4 4 1.4702
循环 顺序 2 3 0 1 4 4 1.2785
树 vs 循环(同序) 2.5970
循环(同序)vs 循环(换序) 2.5048
串行那两行的「4、4」不是巧合:n 个叶子就要组合 n 次,第一个叶子到输出要穿过 n 层。树结构要 n-1 次组合,但层数只有 log2(n)。
叶子数 树结构层数 串行层数 层数相差
----------------------------------------
4 2 4 2 倍
8 3 8 2 倍
16 4 16 4 倍
64 6 64 10 倍
256 8 256 32 倍
这一节说的「递归神经网络」指的是 Recursive,不是 Recurrent。中文都翻成「递归/循环」,很容易混。
区别在结构:Recurrent 是按时间步串成一条链,每一步吃一个输入;Recursive 是按语法树自底向上组合,每个节点吃两个孩子。
表一前两行参数完全一样(都是那个 W),结果却不同——因为组合方式不同。第三行更说明问题:同一批叶子换个顺序,串行结果差了 2.5048,说明 Recurrent 强依赖位置。
表二说清了真正的代价:叶子一多,串行的链长线性增长,梯度要穿过 256 层;树结构只长到 8 层。所以 Recursive 天生适合句法分析这类「结构明确」的树形数据,Recurrent 适合「顺序本身有意义」的语音和文本。
代价是 Recursive 必须先把树建出来,而这棵树得靠额外的句法解析器提供;Recurrent 只要按顺序喂,不需要任何额外结构。
另外树结构也不是完全不受顺序影响——左孩子和右孩子角色不同,换过来 W 吃到的拼接向量就变了。它编码的是「谁是父节点」,不是「谁排在前」。
树 1.6301、串行同序 1.4702、串行换序 1.2785。 三个数都不一样,而参数完全相同——差别全在组合方式上。 「树 vs 循环」差 2.5970,「循环同序 vs 换序」差 2.5048。 两个数差不多大,说明「换结构」和「换顺序」对结果的影响是同一量级。 层数表是这一节最该记的:4 个叶子差 2 倍、16 个差 4 倍、256 个差 32 倍。 这个差距随规模扩大,不会收敛。 这里只做前向,没有训练。所以三个数的大小本身没有意义,有意义的是「它们不相等」。 如果三者相等,那说明这个组合函数对结构完全不敏感,那才是设计出了问题。
30 秒实验
- 把叶子数改成 1024,重算两种结构的层数差。
- 给串行的初始隐状态从 0 改成随机值,看换序的差异会不会变小。
- 想一想:为什么 Recursive 需要额外的解析器,而 Recurrent 不需要?
常见误区
- 「递归和循环是一回事」——Recursive 走树,Recurrent 走链,完全两种结构。
- 「树结构不受顺序影响」——左右孩子角色不同,换过来结果照样变。
- 「Recursive 更先进所以该用它」——它要求先有树,通用序列任务给不出树。
自测三问(点开对答案)
1. Recursive 和 Recurrent 的结构差别是什么? —— Recurrent 按时间步串成一条链,Recursive 按语法树自底向上组合。 2. 为什么说递归网络的深度是对数级的? —— 每次组合把两个节点合成一个,叶子数 n 只要 log2(n) 层就到根。 3. Recursive 的主要代价是什么? —— 必须先有那棵树,得额外配一个句法解析器,而 Recurrent 不用。循环网络的问题是记不住远处。下一节讲 LSTM 怎么用门控把这件事解决掉。
- ☐ (本节完,下一节 8.2.6 长短期记忆网络:处理自然语言序列等任务)