跳到主要内容

循环网络与词向量(把每个词换成一串数) — 让网络按顺序读

这一章讲三件事: 循环网络怎么用一个「状态」记住序列里发生过的事,以及这个记忆为什么会中途消散、怎么用门守住它; 文字进门的第一道工序——词怎么变成向量;两个落地实例(给每个词判词性——名词还是动词;以及股价预测)各暴露了什么边界。 原书把 CNN 与 RNN(按顺序逐个读数据的循环网络,英文全名 Recurrent Neural Network)并称「三大硬骨头」的前两块;本章一次讲完 RNN 与词向量这两件配套的事。

1. 顶层全景:状态是唯一的记忆载体

U U U U:输入 → 状态
x1 → [s1] → [s2] → [s3] → … W:上一状态 → 下一状态(自循环)
↰ W ↰ W ↰ W V:状态 → 输出
V V V
o1 o2 o3

图说:把带自循环的格子沿时间展开,就是右向的长链。
每个时刻网络结构相同、U/W/V 三组权重相同——与卷积核共享同思路的参数共享,
序列多长,链就展开多长,输入长度不再受限。

为什么卷积不够用:句子的长度不定、且先后顺序携带信息(「狗咬人」与「人咬狗」是两回事)。处理这种序列数据是循环神经网络(Recurrent Neural Network,下文简称循环网络或 RNN)的专长1。它的核心构件是一个自循环:上一时刻算出的状态(state)连进下一时刻——书里对状态的定位一句话:「它是网络的记忆」2;输出只依赖当前这一刻的记忆3

2. 主走查:两步前向,把「记忆」算成数

本章的主走查把记忆走成具体数字(数的口径来自书里的 Python 例,激活用 tanh)4:

初始状态 s0 = [0.0, 0.0] (什么都没读过)

读入 x1 = 1:
s1 = tanh(W·[s0, x1] + b) = [0.537, 0.462] ← 记忆被改写了
输出 o1 = V·s1 + b' = 1.561

再读 x2 = 2:
s2 = tanh(W·[s1, x2] + b) = [0.860, 0.884] ← s1 的一部分还在 s2 里
输出 o2 = 2.727

盯着这两行看:s2 不是 x2 的函数,是 (s1, x2) 的函数;而 s1 又是 (s0, x1) 的函数——「1 这个输入曾经在」这件事,就寄居在 s2 的数值偏移里。训练时误差从输出反向传回,这条链要沿时间逐格反传,即 BPTT(随时间反向传播):普通反向传播按层,它按时间步5

3. 记忆的保质期:梯度为什么消散

主走查里状态只走了两步。问题是走一百步后,第一步的输入还剩多少影响?反传要经过 W 一百次连乘——把 W 做特征值分解(把矩阵拆成几个「方向×伸缩倍数」,连乘多少次后的量级一眼可看),W^t 的量级由特征值的 t 次方决定:特征值大于 1 就爆炸,小于 1 就消散6。消散的后果是「最早的信息传不动了」——标准 RNN 有记忆,但健忘;书里点明这在深度网络里普遍存在,而 RNN 因为同一个 W 反复自乘,比前馈(逐层直进、不带循环)网络更严重,导致短时记忆问题,对语言处理是致命的7

修补清单有三条:换导数不过零的激活函数(ReLU)、加 BN 层、以及改结构——第三条才是主力,LSTM8

4. LSTM:三个门守一条细胞状态

LSTM(长短时记忆网络,Hochreiter 与 Schmidhuber 1997)的结构改动聚焦一点:这套开关机制叫门控(用可学习的「门」控制信息的读写)——在状态之外加一条细胞状态 c 当「主干道」,用三个门控制它9:

管什么直觉
遗忘门上一时刻的 c 有多少保留到当前 c该忘的忘
输入门当前输入有多少写进 c该记的记
输出门c 有多少输出给当前时刻该说的说

每个门是一个 sigmoid 输出的 0~1 开关,对 c 做逐元素的「保留/写入/输出」加权。梯度沿 c 这条加法主干道回传,不再被反复乘小——长期依赖因此保得住。

代价是参数量:LSTM 比 RNN 多 3 组线性变换,参数是标准 RNN 的 4 倍——书里实测 nn.LSTM 的输入权重矩阵是 [80, 10],正好是 RNN [20, 10] 的 4 倍10;隐状态也从 (h0) 变成 (h0, c0) 一对11

5. GRU 与双向:两个省算力(省计算量)/看未来的变体

GRU(门控循环单元)做减法:三门并成两门(更新门管「保留多少旧状态」,重置门管「忽略多少历史来算候选」),细胞状态与输出状态合并成一个,参数降到 RNN 的 3 倍;实际效果与 LSTM 差别不大,因此更流行12

Bi-RNN(双向)做加法:一个网络正着读序列、一个倒着读,输出层同时拿到两边的信息;两套隐层之间没有信息流(保证展开图无环)。代价是参数翻倍,收益看任务是否「需要未来」——书里举的例子是百度语音识别(把人说话转成文字)用双向结构综合前后文提准确率13

6. PyTorch 的循环层:形状即契约

nn.RNN 的构造参数里最常用四个:input_sizehidden_sizenum_layersbidirectional;输入默认按 (序列长, 批, 特征) 排——与全连接「批在前」的习惯相反,记不住就用 batch_first=True 换序14。隐状态形状是 (层数×方向数, 批, 隐藏维度)。

契约用一次实测核对:建 input_size=10、hidden_size=20、两层的 RNN,喂 (100, 32, 10) 的输入(100 个时间步、批 32、每步 10 维),输出 (100, 32, 20)——每个时间步各出一个状态;h_n 永远只留最后一步15。单元版(RNNCell/LSTMCell/GRUCell)与封装版的差别只在输入:前者吃一个时间步,后者吃整段序列16

7. 词向量:从电报码到分布式表示

这一节做的事属于自然语言处理(让机器处理人类语言)的地基,英文行话叫 NLP。

网络吃的是数,词先得变成数——这一步是把词的语义(含义)变成几何关系。第一条路是独热编码(One-Hot):向量长度=词典大小,该词的位置是 1、其余全 0——书里把更早的「中国=5178」式编号叫电报码,One-Hot 是它的加长版17。缺点致命:向量之间互相孤立,「汽车」和「卡车」的距离与「汽车」和「天气」的距离一样远(语义鸿沟),而且词典几十万维,维度灾难18

分布式表示(distributed representation,Hinton 1986 年提出)换思路:每个词是一个普通向量(常见 50~100 维),意思近的词向量距离近——「麦克」会靠近「话筒」,远离「天气」19。「意思」第一次变成了可计算的几何关系。

8. Word2Vec:两种猜词游戏学出词空间

词向量好,但怎么免费学出来(不用人工标注——给数据贴答案)?

Word2Vec(Google 2013 年开源(代码公开))的答案:用文本自己当老师,玩两种猜词游戏。

两种玩法都围绕上下文(一个词前后的那些词)展开,拿书里的句子「今天 下午 2点钟 搜索 引擎 组 开 组会」看:

模式游戏对应
CBOW用上下文(今天、下午、搜索…)猜中间的「2点钟」上下文 → 目标词
Skip-Gram用「2点钟」猜它的上下文目标词 → 上下文

两种游戏在同一个语料(喂给模型的海量文本)上亿万次重复,词向量就是训练出来的副产品

网络本身只是个 3 层浅模型,「严格说并非深度学习」,但它产出的向量成了 NLP(自然语言处理)深度学习的地基20。工程细节只有一个值得记:词典动辄十几万,输出层 softmax 算不起,用层次 softmax(把扁平分类改成沿二叉树走)省算力21;Skip-Gram 的训练样本就是滑窗切出来的(输入,上下文)对——"the quick brown fox" 用窗口 1 能切出一列词对22

9. 实战:词性标注——从全错到全对

主走查的后半段是一个能亲眼看着变对的完整实验:用「嵌入(把每个词换成一串数)层→LSTM→全连接」三层网络给英文句子标词性(DET 冠词/NN 名词/V 动词)。训练数据只有两句:「The cat ate the fish」「They read that book」,共 9 个不同的词、3 种词性;词各嵌成 10 维向量,隐藏层仅 3 维23

训练前(参数还是随机数):预测 [2, 2, 1, 1, 1] —— 5 个词性全错
训练 400 轮后: 预测 [0, 1, 2, 0, 1] —— DET NN V DET NN,全对
测试没见过的句子 "They ate the fish":
预测 [1, 2, 0, 1] —— NN V DET NN,也对

这个实验把本章两条线拧在一起看:词向量层让「词」变成可训练的点;LSTM 让「the 后面跟名词」这类位置规律被状态记住。9 个词也能学到能泛化的规则,因为规律在位置结构里,不在词典(词表:收录的全部词)有多大里。

10. 实战:股价预测,与它的边界

第二个实例用 LSTM 做时序回归:沪深 300 指数 2295 个交易日,拿前 30 天的最高价预测当天的最高价;滑窗把序列切成 (30 天输入, 第 31 天标签) 的样本,数据标准化(减均值除标准差),模型 LSTM(隐藏 64)接一个全连接输出24。书里的结果图显示预测曲线与真实走势「形似」。

边界必须当面讲清:预测曲线贴着真实曲线走,很大程度上是「用昨天的价格预测今天的价格」——今天的价格本来就≈昨天的价格。 这类模型对「明天涨还是跌」这种有经济价值的预测没有提供证据,书里也没有任何收益验证。时序预测实验的可信度,要看「预测变化量」而不是「预测值贴不贴」。

判断(我们的,不是书里的): 书末提「RNN 在恰当权重下是图灵完备的,可模拟任意程序」,这是理论结论;实践里的 2016-2020 年,RNN 正被注意力机制取代——本章第 11 章的注意力、以及书里只提了一个名字的 Self-Attention,才是这条演化路的下一站。 如果错,会错在: 如果某些嵌入式/低算力场景因 RNN 的省内存而长期存活,「被取代」就只是主流场景的判断,不是全场景的。

11. 作者的判断与证据

给了证据的: 两步前向的数字、RNN/LSTM 权重矩阵 [80,10] 对 [20,10] 的实测、词性标注从全错到全对的两次输出,都是书中真实运行结果。

转述文献的: LSTM(1997)、GRU、Bi-RNN(1997)、Word2Vec(2013)的归属书里都点了名;「百度语音识别用 Bi-RNN」给了实例没给出处。

作者的取舍: 门控机制的内部公式书里一张图带过,没有逐步推——对比 nndl 类教材的详尽推导,这是刻意的取舍:本书走「结构直觉+代码验证」路线,推导另请教材。

12. 边界与局限

  • 全书无 Transformer。 Self-Attention 只在 RNN 变体清单里留了一个名字。

  • 2018 年后的 BERT(谷歌 2018 年预训练的语言模型,一段话双向看上下文)不在书内。

  • GPT(以「接龙」方式预训练的生成式模型)一系同样不在书内。 两者都是 Transformer 的后代;读本书 NLP 部分要自觉补上这一层。

  • 词向量一章没有 Word2Vec 的 PyTorch 训练代码,只有 nn.Embedding 的用法——Embedding 层查表所得的向量能不能训练,书里没有明说(能,而且就是训练对象)。

  • 股价实例没有任何收益/变化量评估,照搬会误判(见 §10)。

  • 梯度裁剪(治爆炸的标配手段)全书未提。

13. 可带走的

  1. RNN 的记忆=一个被反复改写的状态向量;输出只依赖当前状态,「历史」是间接的;
  2. 记忆保质期问题=W 沿时间连乘:特征值 <1 消散、>1 爆炸;RNN 比前馈更严重;
  3. LSTM 三个门各管「忘/记/说」,细胞状态是梯度的加法主干道;参数是 RNN 的 4 倍(实测 80 对 20);
  4. GRU 两门一状态,效果近似、算力更省;Bi-RNN 双向读,适合需要「未来」的任务;
  5. nn.RNN 的输入默认 (序列长, 批, 特征)——与全连接的批优先相反,记这条能省半天调试;
  6. 词向量把「意思」变成「距离」;One-Hot 的语义鸿沟是它存在的全部理由;
  7. Word2Vec 用上下文猜词/词猜上下文两种游戏,向量是训练副产品;Word2Vec 本身是浅层网络;
  8. 词性标注实验说明:小数据也能学到可泛化的规律,只要规律在结构里;
  9. 时序预测的曲线「贴」不代表能预测变化量——对股价类实验保持这个怀疑。

14. 原文地图

主题原书章原文位置
序列数据 CNN 不擅长第7章 自然语言处理基础text/08-ch07.txt:6(搜「序列数据」)
自循环与三组权重第7章 自然语言处理基础text/08-ch07.txt:26(搜「自循环」) · text/08-ch07.txt:31(搜「状态到隐含层的权重矩阵」)
状态=网络的记忆第7章 自然语言处理基础text/08-ch07.txt:50(搜「它是网络的记忆」)
参数共享同卷积第7章 自然语言处理基础text/08-ch07.txt:36(搜「过滤器机制」)
两步前向的数第7章 自然语言处理基础text/08-ch07.txt:90(搜「0.0,0.0」) · text/08-ch07.txt:120(搜「0.53704957」) · text/08-ch07.txt:123(搜「2.72707101」)
BPTT 按时间反传第7章 自然语言处理基础text/08-ch07.txt:125(搜「随时间反向传播」)
特征值与消失爆炸第7章 自然语言处理基础text/08-ch07.txt:143(搜「特征值」) · text/08-ch07.txt:144(搜「梯度消失或爆炸问题」)
RNN 短时记忆第7章 自然语言处理基础text/08-ch07.txt:153(搜「短时记忆」)
LSTM 三门第7章 自然语言处理基础text/08-ch07.txt:171(搜「1997」) · text/08-ch07.txt:174(搜「遗忘门」)
LSTM 参数 4 倍实测第7章 自然语言处理基础text/08-ch07.txt:347(搜「4倍」) · text/08-ch07.txt:362(搜「80, 10」)
GRU 两门 3 倍第7章 自然语言处理基础text/08-ch07.txt:191(搜「更新门」) · text/08-ch07.txt:424(搜「3倍」)
Bi-RNN 与百度第7章 自然语言处理基础text/08-ch07.txt:203(搜「Schuster」) · text/08-ch07.txt:208(搜「百度语音识别」)
batch_first 与形状第7章 自然语言处理基础text/08-ch07.txt:253(搜「batch_first」) · text/08-ch07.txt:302(搜「torch.Size([100, 32, 20])」)
单元版 vs 封装版第7章 自然语言处理基础text/08-ch07.txt:221(搜「单元版」)
One-Hot 与电报码第7章 自然语言处理基础text/08-ch07.txt:535(搜「电报」)
语义鸿沟第7章 自然语言处理基础text/08-ch07.txt:551(搜「语义鸿沟」)
分布式表示与话筒第7章 自然语言处理基础text/08-ch07.txt:556(搜「1986」) · text/08-ch07.txt:560(搜「话筒」)
Word2Vec 定位第7章 自然语言处理基础text/08-ch07.txt:564(搜「2013」) · text/08-ch07.txt:568(搜「NLP领域的基础」)
CBOW 与 Skip-Gram第7章 自然语言处理基础text/08-ch07.txt:573(搜「2点钟」) · text/08-ch07.txt:588(搜「CBOW」)
层次 softmax第7章 自然语言处理基础text/08-ch07.txt:602(搜「Hierarchical」)
Skip-Gram 词对第7章 自然语言处理基础text/08-ch07.txt:611(搜「brown」)
词性标注数据与超参第7章 自然语言处理基础text/08-ch07.txt:657(搜「The cat ate the fish」) · text/08-ch07.txt:729(搜「HIDDEN_DIM」)
全错到全对第7章 自然语言处理基础text/08-ch07.txt:757(搜「2, 2, 1, 1, 1」) · text/08-ch07.txt:793(搜「0, 1, 2, 0, 1」) · text/08-ch07.txt:816(搜「1, 2, 0, 1」)
股价实例第7章 自然语言处理基础text/08-ch07.txt:820(搜「沪深300」) · text/08-ch07.txt:852(搜「2295」) · text/08-ch07.txt:907(搜「df_numpy_std」)
RNN 应用五模式第7章 自然语言处理基础text/08-ch07.txt:997(搜「图片字幕」)
图灵完备说法第7章 自然语言处理基础text/08-ch07.txt:1016(搜「图灵完备」)

Footnotes

  1. 出处:「第7章 自然语言处理基础」第 6 段(text/08-ch07.txt:6,搜「序列数据」)。

  2. 出处:「第7章 自然语言处理基础」第 50 段(text/08-ch07.txt:50,搜「它是网络的记忆」)。

  3. 出处:「第7章 自然语言处理基础」第 58 段(text/08-ch07.txt:58,搜「依赖于时刻t的记忆」)。

  4. 出处:「第7章 自然语言处理基础」第 90 段(text/08-ch07.txt:90,搜「0.0,0.0」)与第 120 段(text/08-ch07.txt:120,搜「0.53704957」)、第 123 段(text/08-ch07.txt:123,搜「2.72707101」)。各数值均为原文代码的运行输出。

  5. 出处:「第7章 自然语言处理基础」第 125 段(text/08-ch07.txt:125,搜「随时间反向传播」)。

  6. 出处:「第7章 自然语言处理基础」第 143 段(text/08-ch07.txt:143,搜「特征值」)与第 144 段(text/08-ch07.txt:144,搜「梯度消失或爆炸问题」)。

  7. 出处:「第7章 自然语言处理基础」第 153 段(text/08-ch07.txt:153,搜「短时记忆」)。

  8. 出处:「第7章 自然语言处理基础」第 162 段(text/08-ch07.txt:162,搜「LSTM结构」)。

  9. 出处:「第7章 自然语言处理基础」第 171 段(text/08-ch07.txt:171,搜「1997」)与第 174 段(text/08-ch07.txt:174,搜「遗忘门」)。

  10. 出处:「第7章 自然语言处理基础」第 347 段(text/08-ch07.txt:347,搜「4倍」)与第 362 段(text/08-ch07.txt:362,搜「80, 10」)。

  11. 出处:「第7章 自然语言处理基础」第 349 段(text/08-ch07.txt:349,搜「c0」)。

  12. 出处:「第7章 自然语言处理基础」第 191 段(text/08-ch07.txt:191,搜「更新门」)与第 424 段(text/08-ch07.txt:424,搜「3倍」)。

  13. 出处:「第7章 自然语言处理基础」第 203 段(text/08-ch07.txt:203,搜「Schuster」)与第 208 段(text/08-ch07.txt:208,搜「百度语音识别」)。

  14. 出处:「第7章 自然语言处理基础」第 253 段(text/08-ch07.txt:253,搜「batch_first」)。

  15. 出处:「第7章 自然语言处理基础」第 294 段(text/08-ch07.txt:294,搜「100,32,10」)与第 302 段(text/08-ch07.txt:302,搜「torch.Size([100, 32, 20])」)。

  16. 出处:「第7章 自然语言处理基础」第 221 段(text/08-ch07.txt:221,搜「单元版」)。

  17. 出处:「第7章 自然语言处理基础」第 535 段(text/08-ch07.txt:535,搜「电报」)。

  18. 出处:「第7章 自然语言处理基础」第 551 段(text/08-ch07.txt:551,搜「语义鸿沟」)。

  19. 出处:「第7章 自然语言处理基础」第 556 段(text/08-ch07.txt:556,搜「1986」)与第 560 段(text/08-ch07.txt:560,搜「话筒」)。

  20. 出处:「第7章 自然语言处理基础」第 564 段(text/08-ch07.txt:564,搜「2013」)与第 568 段(text/08-ch07.txt:568,搜「NLP领域的基础」)。

  21. 出处:「第7章 自然语言处理基础」第 602 段(text/08-ch07.txt:602,搜「Hierarchical」)。

  22. 出处:「第7章 自然语言处理基础」第 611 段(text/08-ch07.txt:611,搜「brown」)。

  23. 出处:「第7章 自然语言处理基础」第 657 段(text/08-ch07.txt:657,搜「The cat ate the fish」)与第 729 段(text/08-ch07.txt:729,搜「HIDDEN_DIM」);词表 9 词见第 673 段(text/08-ch07.txt:673,搜「9个不同单词」)。

  24. 出处:「第7章 自然语言处理基础」第 820 段(text/08-ch07.txt:820,搜「沪深300」)、第 852 段(text/08-ch07.txt:852,搜「2295」)、第 907 段(text/08-ch07.txt:907,搜「df_numpy_std」)与第 921 段(text/08-ch07.txt:921,搜「hidden_size=64」)。