查看全课大纲(第 79 / 82 节)
- 1.函数与数据泛化
- 2.向量:从几何直觉到高维空间
- 3.矩阵与张量
- 4.特征值与特征向量:矩阵变换的不动方向
- 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
- 6.概率与统计:机器学习的数学基础
- 7.独立性与随机变量:贝叶斯公式与二项分布
- 8.期望、方差与协方差:随机变量的数字画像
- 9.联合概率:当一条轴不够用的时候
- 10.概率分布(一):从伯努利到伽马函数
- 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
- 12.机器学习的模型:找输入与输出的关系
- 13.机器学习的应用
- 14.过拟合
- 15.机器学习的分类
- 16.有监督学习:从房价预测说起
- 17.损失函数
- 18.梯度下降(上):更新规则与学习速率
- 19.梯度下降(2)
- 20.牛顿方法
- 21.KNN简介
- 22.距离测算
- 23.朴素贝叶斯
- 24.朴素贝叶斯的实际应用
- 25.信息熵:决策树构建的度量基础
- 26.信息增益与决策树的构建
- 27.ID3算法
- 28.决策树节点的划分
- 29.CART树
- 30.Bagging
- 31.Boosting:自适应提升算法
- 32.Gradient Boosting
- 33.线性模型:回归与分类的同一副骨架
- 34.最小二乘:把最优参数直接解出来
- 35.最小二乘法的几何与概率视角
- 36.极大似然:给最小二乘一个概率出身
- 37.逻辑回归(1):把线性模型接到分类上
- 38.逻辑回归(2):损失函数与多分类
- 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
- 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
- 41.优化方法(1):坐标下降与前向选择
- 42.优化方法(2):前向策略与最小角回归
- 43.拉格朗日乘子法(1):从无约束到等式约束
- 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
- 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
- 46.最大间隔:把分类问题变成优化问题
- 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
- 48.支持向量:边界上的少数派
- 49.SVM 计算过程:一笔到底算完,再走进核方法
- 50.贝叶斯公式:概率即信念
- 51.隐含变量和概率模型:给看不见的原因建模
- 52.EM 算法:期望与最大化的迭代
- 53.琴生不等式:EM 的数学钥匙
- 54.三个硬币问题:EM 的复盘与衔接
- 55.高斯混合模型:用隐变量解释双峰数据
- 56.隐马尔可夫模型
- 57.隐马尔可夫模型(2):前向算法与动态混合模型
- 58.隐马尔可夫模型在 NLP 中的运用
- 59.EM 算法:把「为什么收敛」推到底
- 60.HMM 在 NLP 中的运用:从分词到机器翻译
- 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
- 62.高维下的高斯模型
- 63.主题模型:从词袋到隐含主题
- 64.Dirichlet 分布(1)
- 65.Dirichlet 分布(2)
- 66.统计学习内容回顾
- 67.K-means 算法
- 68.PCA 降维
- 69.SVD 降维
- 70.从聚类到流形
- 71.局部线性嵌入
- 72.随机邻域嵌入:SNE 与 t-SNE
- 73.无参模型:让数据自己说话
- 74.高斯过程:函数空间里的正态分布
- 75.高斯过程(2):噪声、正则化与核的线性组合
- 76.神经网络简介:从感知器到自编码器
- 77.反向传播:把误差一层层传回去
- 78.卷积神经网络(CNN):让特征自己学出来
- 79.循环神经网络(RNN):给网络装上记忆
- 80.深度学习回顾:静态一类,动态一类
- 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
- 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传
循环神经网络(RNN):给网络装上记忆
约 36 分钟
小象实战讲义 · 机器学习算法精讲
CNN 之后,我们面对一类新数据:动态的、持续的特性——这是循环神经网络(RNN,recurrent neural networks)与 CNN 最大的区别所在。本节路线:先把一个普通网络放到时间轴上,看清「隐层记住上一时刻」这一个改动;再对照 HMM——讲师的判词是「HMM 可以认为是 RNN 的一种特例」;然后进入本节主角 LSTM:细胞状态这条传送带,加上遗忘、输入、输出三道门,把「信息记多久」变成可学的参数;最后从图像那边的 pix2pix 桥接到序列这边的 Seq2Seq,看注意力机制怎么让每一步输出直接看到输入,以及这套东西生成出来的诗、假评论和嘻哈歌词。
💡 核心导读
- 时序特征:语言、声音、股价都有强烈的前后依赖——说了「我爱北京」,后面就不会是鼠标、茶叶或垃圾桶;明天的股价也不能从 5 毛到 300 块之间随便估一个数。
- RNN 核心机制:同一个网络在三个时间段做同一件事,但 时刻隐层的输入里,除了当前的 ,还有上一时刻的隐层输出;课件公式 。
- 与 HMM 的关系:一个用概率的状态转移矩阵描述时序,一个用隐层之间的权重关联描述时序——后者 capacity 更大、更 flexible。
- LSTM:细胞状态 像传送带,三道门(forget / input / output)决定信息留多少、写多少、放多少;「不是你来知道重要不重要,是训练数据告诉你的」。
- 从 pix2pix 到 Seq2Seq:图像的 autoencoder 是 pixels to pixels,序列的 autoencoder 就是 sequence to sequence;注意力机制再给每一步输出配上对输入的权重。
- 生成的诚实面:华丽但没有思想的诗、以假乱真的淘宝评论、押韵顺口的嘻哈歌词——规律学得到,中心思想没有。
一类新数据:时序特征
RNN 里的「循环」,跟前面讲 deep learning 时 CNN 的做法有蛮大的区别——它主要处理动态的、持续的特性,这是 CNN 有的时候做不到的。
比如时间序列、声音,包括我们的语言,都有明显的时序特征。讲师的例子:我说「我爱北京天安门」——说了「我爱北京」之后,后边就不会说「我爱北京鼠标」「我爱北京茶叶」或者「我爱北京垃圾桶」这种话。前面这些话一出口,后面出现这个词的概率就发生了变化。再比如预测明天某一只股票的价格——你认为它跟以往的价格会没有关系吗?能从大概 5 毛钱到 300 块钱之间随便估一个数吗?当然不是,它一定跟以前的价格有密切的关系。
这种持续信息,我们以前用 HMM(隐马尔可夫模型)处理过;现在看 RNN 怎么描述它。
把一个网络放到时间轴上
讲师在黑板上一步步画。先想象一个最简单的普通神经网络:有输出层,有一个隐层跟它相连,隐层再和输入节点相连——两层网络。因为处理的是时序信息,时刻必须标在上面: 时刻输入 ,输出 ;接着画第二个网络,跟第一个长得一模一样,权重也相同(该是 的还是 ,该是 的还是 ),处理 时刻的输入; 时刻同理。三个网络完全一样——就是一个网络在三个时间段所做的同一件事。
那时间上的关联体现在哪?关键一步: 时刻隐层的输入,除了输入层经权重送来的当前信号,还跟上一时刻隐层的输出相关——把上一时刻的隐层输出接到这一时刻的输入上;这一时刻的输出,又变为下一时刻的输入。
按板书的记号走一遍:某一时刻的隐层有两组输入——一组是 乘上当前的 (设结果叫 );另一组是上一时刻的 乘上一个新的矩阵 。两组信息合在一起,构成这一时刻隐藏的输入;再经过输出权重 ,得到输出 。所以每两个时间节点之间,我们手里有三组系数。
换成课件上的简记(把隐藏状态记作 ,输出记作 ),循环神经网络的基本形式:
三个矩阵各管一段: 是输入到隐层、 是隐层之间、 是隐层到输出。课件上的原话很形象:一个 RNN 可以看作同一个网络的多份拷贝,每一份给后一份传递消息——把循环按时间展开,看到的就是这条链。用代码把「记忆」跑出来看:
import math
def rnn_forward(x_seq, U, W):
"""朴素 RNN 前向(标量版): S_t = tanh(U*x_t + W*S_{t-1}), 只看隐藏状态。
U: 输入到隐层的权重; W: 隐层到自身的循环权重(记忆); S_0 初始为 0。"""
s_prev = 0.0
states = []
for t, x_t in enumerate(x_seq):
z = U * x_t + W * s_prev
z = max(min(z, 500), -500) # 数值稳健限幅, 防极端溢出
s_t = math.tanh(z)
states.append((t, x_t, s_t))
s_prev = s_t # 当前状态传给下一时间步
return states
x_seq = [1.0, 0.5, 0.0, -0.5]
for label, W in [("W=0 (没有循环, 普通网络: 只看当前输入)", 0.0),
("W=-0.2(有记忆的 RNN: 当前输入 + 过去状态)", -0.2)]:
print(label)
for t, x_t, s_t in rnn_forward(x_seq, U=0.5, W=W):
print(f" t={t}: 输入 x={x_t:+.1f} 隐藏状态 S={s_t:+.4f}")
print()W=0 (没有循环, 普通网络: 只看当前输入)
t=0: 输入 x=+1.0 隐藏状态 S=+0.4621
t=1: 输入 x=+0.5 隐藏状态 S=+0.2449
t=2: 输入 x=+0.0 隐藏状态 S=+0.0000
t=3: 输入 x=-0.5 隐藏状态 S=-0.2449
W=-0.2(有记忆的 RNN: 当前输入 + 过去状态)
t=0: 输入 x=+1.0 隐藏状态 S=+0.4621
t=1: 输入 x=+0.5 隐藏状态 S=+0.1563
t=2: 输入 x=+0.0 隐藏状态 S=-0.0312
t=3: 输入 x=-0.5 隐藏状态 S=-0.2390对照着看: 那一行,输入是 ——没有记忆的网络,隐藏状态直接归零(它只能看到当前这一刻);有记忆的 RNN 还带着 ,这是上一时刻的余温。再到 ,两边的输入相同,但 RNN 的 是拿上一步的 又乘了一次循环权重之后算出来的。当前时刻的隐藏状态,同时包含当前输入和过去信息的影响——这就是 RNN 捕捉时序依赖的基本方式。
RNN 与 HMM:同一个时序问题的两种写法
讲师让大家想象以前学过的隐马尔可夫链,马上能找到相通的东西。HMM 里有隐含的状态序列 ,我们观察不到;能看到的是它们输出的观测序列 (阴影部分)。 和状态之间靠释放矩阵 连接;状态与状态之间靠状态转移矩阵 演进。
跟 RNN 摆在一起看:RNN 的隐藏状态对应 HMM 的隐含状态;RNN 的输出对应可观察的 ;隐层之间由循环权重定义的转换,对应状态转移矩阵;隐藏到输出的映射,对应释放矩阵。讲师的结论:「我们至少可以认为,HMM 可以简单地看作 RNN 的一种特例」——如果 RNN 里的一个神经网络用来逼近那个概率的状态转移矩阵,那它就是一个普通的 HMM。而且 RNN 在每一时刻还有单独的 输入,加上隐藏关系,最后变成实际能观察到的输出。
两者最主要的相关,是因为它们都是时序模型:一个用概率的状态转移矩阵描述,一个用隐层之间的权重关联描述;都有隐含状态、都有可被观察的状态。但 RNN 的模型 capacity(复杂度)更大、更 flexible——不要求严格的概率解释,能 model 的事情更多一些。
顺带一提结构的两种早期分岔。Elman network:把这一时刻的隐藏信息放到下一时刻的隐藏,就是前面画的连法。Jordan network:唯一的区别是,把这一时刻的隐藏信息不保留、算出输出,把输出作为下一时刻的输入放到下一时刻的隐藏里——中间其实就多乘了一个输出矩阵的区别。讲师提到他们曾经做过对比工作:对一个简单网络来讲,两者的区别非常小,大家也并没有特别关注该用哪种,表现是类似的。
LSTM:把「记多久」变成可学的参数
反而是有一种 RNN,是大家关注最多、讨论最多、经常使用的——讲师说,今天说用一个 RNN 的时候,基本用的都是 LSTM(long short-term memory)。
长期依赖抓不住
一个特别有意思的例子(讲师注明「这是别人的例子」):一句话 “I grew up in France”,中间隔了好长一段,最后一句是 “I speak fluent French”。最后的 French 跟前面的 France 相不相关?怎么能不相关。可如果只用 trigram 模型看 “speak fluent”,多半只能断定这是一种 language——could be Chinese、German、French、Dutch 都行;但给定前面那句,French 的概率要远远大于其他。这个 dependency,普通的 RNN 没办法 capture 到——而语料库里大量存在这种长期的、常关联的现象。
这里有一对值得单独记住的判断:short term dependency 告诉你,它一定是个语言(不会是杯子、鼠标、电脑或类似的词);long term dependency 告诉你,它是某一种语言。所以需要把 RNN 加以改进、使自由度增大——有长期的关联,考虑长期;有短期的关联,考虑短期——看谁的关联比较强,通过参数自动地学习到。这就是 LSTM 所做的事。
细胞状态:一条传送带
LSTM 是 97 年由德国人做出来的结果(Hochreiter 与 Schmidhuber,1997)。它整体还是一个 RNN,只是分解成了几个更细的子模块,子模块合在一起,使模型有更大的复杂度。
它新增的核心 idea 是 ——细胞状态。 代表信息流,像一条传送带:它告诉你,前面的信息以多大的程度要往后保留。上一时刻来的 输入到当前节点,这一时刻通过它的权重和训练数据调整出「保留多少」,作为下一时刻的输入。如果当前信息跟后面关联不大,就用这一时刻的信息顶替上一时刻的;如果关联大——比如 France 这件事,跟后面 French 关联会比较大——那这个信息就在传送带上得以保留。
那你哪知道这个重要不重要?讲师的原话是「其实不是你来知道的,是训练数据告诉你的」。大量的训练数据会告诉你:碰到这种类型的信息,它对后面有影响——它们之间的关系不独立,有关系的权重就会加大;其他的词对后面不会影响,权重自然变小。模型只提供了这种可能和 capacity,让你去调整这些系数,这些系数的调整会使长期依赖的现象在模型里得到体现——以前的 RNN 做不到。
三道门,逐个看
每一个 LSTM 单元里有一个 forget 门、一个 input 门、一个 output 门。共同点先记住:每个门的输入,仍然是上一时刻的隐藏和这一时刻的输入——跟所有 RNN 一样,只是各自乘上自己的权重、加上偏置,再过激活函数。
- 遗忘门 :决定以多大程度 forget(get rid of)旧信息。过 sigmoid,输出在 0 到 1 之间——像概率一样可以解释:1 是「完全保留」,0 是「完全丢弃」。
- 输入门 与候选值 :同样输入构造出 0 到 1 的 ;同时经 tanh 造一个新的候选值 (讲师:那个波浪线「我不知道怎么读,比如叫 c t bar」),作为这一时刻要写入的内容。
- 细胞状态更新:( 是逐元素相乘)。讲师给了两个极端读法——若 为 0, 全是这一时刻更新出来的,跟前面没有任何关系;若 为 0,这一时刻的信息没被写入, 全部来源于上一时刻。
- 输出门 :由 权重控制;最后隐藏的结果就是 。
把一个标量版的 LSTM 单元完整算一遍(向量运算里门输入是 与 的拼接,此处以相加简化):
import math
def sigmoid(x):
x = max(min(x, 500), -500) # 防 exp 溢出
return 1.0 / (1.0 + math.exp(-x))
def lstm_cell_scalar(h_prev, C_prev, x_t, W_f, W_i, W_C, W_o, b_f, b_i, b_C, b_o):
"""标量版 LSTM 单元前向(仅为演示机制)。"""
gate_in = h_prev + x_t # 门输入 = 上一时刻隐藏 + 当前输入
f_t = sigmoid(W_f * gate_in + b_f) # 遗忘门: 0~1
i_t = sigmoid(W_i * gate_in + b_i) # 输入门: 0~1
C_tilde = math.tanh(W_C * gate_in + b_C) # 候选值: -1~1
o_t = sigmoid(W_o * gate_in + b_o) # 输出门: 0~1
C_t = f_t * C_prev + i_t * C_tilde # 细胞状态: 传送带上的新旧混合
h_t = o_t * math.tanh(C_t) # 隐藏输出
return f_t, i_t, C_tilde, o_t, C_t, h_t
W_f, b_f = 0.2, 1.5 # 遗忘门: 偏保留
W_i, b_i = 0.4, 0.05 # 输入门: 看当前输入行事
W_C, b_C = 0.8, 0.1 # 候选值
W_o, b_o = 0.9, 0.1 # 输出门
h_prev, C_prev, x_t = 0.3, 0.5, 0.7
f_t, i_t, C_tilde, o_t, C_t, h_t = lstm_cell_scalar(
h_prev, C_prev, x_t, W_f, W_i, W_C, W_o, b_f, b_i, b_C, b_o)
print("标量 LSTM 单元单步计算:")
print(f" 门输入 = h_prev + x_t = {h_prev} + {x_t} = {h_prev + x_t:.1f}")
print(f" 遗忘门 f_t = sigmoid({W_f}*1.0 + {b_f}) = {f_t:.4f}")
print(f" 输入门 i_t = sigmoid({W_i}*1.0 + {b_i}) = {i_t:.4f}")
print(f" 候选值 C~_t = tanh({W_C}*1.0 + {b_C}) = {C_tilde:.4f}")
print(f" 输出门 o_t = sigmoid({W_o}*1.0 + {b_o}) = {o_t:.4f}")
print(f" 细胞状态 C_t = {f_t:.4f}*{C_prev} + {i_t:.4f}*{C_tilde:.4f} = {C_t:.4f}")
print(f" 隐藏输出 h_t = {o_t:.4f} * tanh({C_t:.4f}) = {h_t:.4f}")标量 LSTM 单元单步计算:
门输入 = h_prev + x_t = 0.3 + 0.7 = 1.0
遗忘门 f_t = sigmoid(0.2*1.0 + 1.5) = 0.8455
输入门 i_t = sigmoid(0.4*1.0 + 0.05) = 0.6106
候选值 C~_t = tanh(0.8*1.0 + 0.1) = 0.7163
输出门 o_t = sigmoid(0.9*1.0 + 0.1) = 0.7311
细胞状态 C_t = 0.8455*0.5 + 0.6106*0.7163 = 0.8602
隐藏输出 h_t = 0.7311 * tanh(0.8602) = 0.5091这一组手设的小参数下:遗忘门开到 0.85(偏保留),旧状态贡献 ;输入门 0.61,候选值贡献 ——下一时刻的 ,就是由上一时刻和这一时刻按这两个比例组成的。再把「France 场景」压到更新式上看极端情形:
def update_C(f, i, c_prev, c_tilde):
"""LSTM 细胞状态更新: C_t = f*C_prev + i*C_tilde (标量演示)。"""
return f * c_prev + i * c_tilde
c_prev, c_tilde = 0.8, 0.05 # 细胞里已存着重要信息(比如"France"), 新来的词不重要
cases = [
("f=1.0, i=0.0: 遗忘门全开 + 输入门全关 -> 旧信息原样保留", 1.0, 0.0),
("f=0.0, i=1.0: 遗忘门全关 + 输入门全开 -> 完全换成这一时刻", 0.0, 1.0),
("f=0.7, i=0.3: 按比例混合(七分旧、三分新)", 0.7, 0.3),
]
for name, f, i in cases:
c_t = update_C(f, i, c_prev, c_tilde)
print(f"{name}")
print(f" C_t = {f}*{c_prev} + {i}*{c_tilde} = {c_t:.4f}")
print()f=1.0, i=0.0: 遗忘门全开 + 输入门全关 -> 旧信息原样保留
C_t = 1.0*0.8 + 0.0*0.05 = 0.8000
f=0.0, i=1.0: 遗忘门全关 + 输入门全开 -> 完全换成这一时刻
C_t = 0.0*0.8 + 1.0*0.05 = 0.0500
f=0.7, i=0.3: 按比例混合(七分旧、三分新)
C_t = 0.7*0.8 + 0.3*0.05 = 0.5750第一种情形就是「France 被长期保留」:网络遇到 France 时学会把遗忘门开到接近 1、输入门关小,这条信息就在细胞状态的传送带上原样流过很多个时间步,直到要预测 French 时再经输出门放出来;对不重要的中间词,输入门小、写不进去,扰动不了长期记忆。
更省的变体:把两道门捆成一道
很多人在 LSTM 之后又造了更简单的模型:把 input 门的系数全部扔掉,根本不要它——用 替换 。道理在于:旧信息和这一时刻要写入的新信息,两者是「你死我活」的博弈关系——这边大、那边就小,这边小、那边就大。 本身就是 0 到 1 之间的数, 正好把另一半补上,由它来决定「以前时刻的信息保留多少」。课件把这层关系写成两句话:只在要输入新东西的时候才遗忘;只在忘掉旧东西的时候才输入。(这一「耦合门」的思路,正是后来各种简化变体——如 GRU——的核心做法,本课不展开。)
从 pix2pix 到 Seq2Seq
先看图像那边是怎么做的
还记得讲 CNN 时的 autoencoder 形式:一个卷积网络压到隐层,再接一个反卷积的网络展开回来。这种成对的图像变换模型,讲师叫它 pix to pix(pixel to pixel):给定大量成对图像——比如白天和黑天——训练之后,权重就学会了「白是如何变黑的」;再比如戴着眼镜和摘了眼镜的成对照片,训练好之后,一个新的人戴着眼镜进来,就能生成出他没有眼镜的样子。图像风格变换也是同一族玩法:把一张普通照片变成毕加索式、或者莫奈式的风格——莫奈是法国印象派画家,画的都是模模糊糊的东西(有人开玩笑说他晚年是个近视眼,看所有东西都是模糊的,画出来的正是人脑中模模糊糊的印象)。做法上:把图像放进卷积神经网络,风格都是高层特征——让新照片的高层特征去逼近莫奈那些画的风格(把距离或 cos 距离变小);同时底层细节跟原始照片很相近——细节没有大变化、风格接近给定画家。这类工作很多,甚至已经有现成的 app。
讲师还展示了自己团队做过的漫画自动上色:反卷积过程根据一对一对的训练数据(素描图和颜色图),把颜色做到最好的估计再上上去。用的 UNet 结构有一个关键点——底层的线条特征在反卷积层里仍然有体现,所以上色图像的线条跟原黑白图是一致的;反卷积层的权重负责涂上黄色、蓝色各种颜色——它学的就是「黑白到彩色」这个变换关系,而且这个关系很大程度上由线条特征决定。于是有了那个有趣的细节:小黄人的皮肤是黄的、背带裤是蓝色的——因为训练数据里的小黄人都是这个模样。换日本动漫做训练,又能生成不同风格的动漫图像。(这套框架里还用了对抗网络等等。)
序列这边:sequence to sequence
回到 RNN。RNN 做的 autoencoder 过程,叫 sequence to sequence model——图像那边是 pixels to pixels,这边是一个序列到另一个序列:说一句汉语翻译成英文、一段声音变成其他的声音、弹一段音乐变成另外一种音乐。
讲师用一个小例子逐步推演:输入是 ABC 这种语言,输出是 XYZ 这种语言。A、B、C 是不同时刻的输入(每一个字就是一个时刻),它们之间是 LSTM 的关系;链走到最后,输到 EOS(end of sentence),把最后这个节点看成 C 的节点,它要生成下一个数据 W——W 是根据 A、B、C 和 EOS 所有的输入、以及它们之间的 LSTM 关系,得到的一个最好的估计。有了这个估计,把 W 再输入到下一步:W 做输入、隐藏做输入,找到最合适的词是 X;再往下把 X 输进去,得到 Y;最后到 Z 和 EOS——于是序列「W X Y Z EOS」和「A B C EOS」之间是更 match 的。因为在所有的训练库里,它们服从这样的特征和关系。
结构上分两半:前半是编码(encoding)的过程,中间是隐藏,后半是解码(decoding)的过程——encoder 和 decoder 都是一个 LSTM。课件把解码循环总结成六步:把输入序列编码成状态向量;从长度为 1 的目标序列(只有起始符)开始;把状态向量和目标序列喂给 decoder,产生下一个字符的预测;用 argmax 采样出下一个字符;把它拼回目标序列;重复,直到生成结束符或达到长度上限。另外还有很多不同的结构,会把 encoder 换成双向 RNN(双向 LSTM)——不但考虑跟前面的关系,还考虑所有双向的关系;而中间的 c 是隐层,对于所有的输出,是隐层 c 的作用加上它上一时刻的输出结果,共同作为下一时刻的重要信息。
注意力机制:让每一步输出直接看到输入
Seq2Seq 里有一个「致命的问题」:任意时刻的输出,都跟上一时刻有直接的关联—— 跟上一个状态相关,同时跟上一时刻得出的 相关。我们希望有一个简单的办法,把输出跟输入之间的关系直接找到——这个关系用另外一个权重来表示。这就是注意力机制(attention mechanism)。
讲师给的语言翻译例子:英文 “the agreement on the european economic area was designed in the august 1992”,翻译成法语。模型的底层结构:输入的 encoding 是一个双向 RNN——两向的双向 LSTM,向左向右两个箭头,产生强关联;输出层是一个单向的 LSTM,从左到右;它们之间由注意力权重描述。效果上:生成法语里对应 economic 的那个词时,跟它最相关的输入词,就是英文里的 economic;它跟 european 或其他词也都可能有关系,但那个特别重的关系,代表它们之间的关系特别强——就像图上有的连线粗、有的连线细。某一个输出词跟前面输入 sequence 之间的权重关系,加上它前面隐藏 LSTM 的关系,共同选择,得到最好的结果——翻译的质量非常地提高。
注意力怎么训练?每一个连接的权重,就通过训练数据得到 : 是关于 的函数,是 的 softmax 形式;而 通过权重 、,由目前的隐藏和现在的状态来调节—— 也是一个需要不断调整的权重,根据它们之间的关系确定。写成课件上的公式( 是编码器各时刻的隐藏状态, 是解码器状态):
上下文向量 是各隐藏状态的加权和——每个解码步都有一份自己的、按相关性分配的输入视图;最终的输出概率 。
再往后一个有意思的结果:既然输入端已经用 attention 直接建立了联系,为什么还需要输出端那个 LSTM 关系?讲师提到有一个研究说「All you need is attention」——把输出端的 LSTM 关系都去掉,只用 attention 的关系,就能得到一个比较好的翻译。(这就是后来著名的 Transformer 架构的起点,本课不展开。)
生成:华丽但没有思想的诗
Seq2Seq 这套模型还能做很多生成的事情,讲师现场念了三组例子。
生成诗歌:
满怀风月一枝春,未见梅花亦可人。不畏东风无此客,世间何处是前身。
白鹭归于地,青山照水开。夜来风不动,明月见楼台。
看起来蛮好的——非常华丽、押韵。原因也很直接:它所有的训练数据,都是古代大诗人的杰出句子。但讲师点出一个诚实的问题:生成的诗自己并没有感觉,它不明白自己的中心思想是什么——「没有思想的诗」,只是为了追求字数的华丽。古人书里,一旦出现春花,你就对秋月;一遥望,你就扶栏杆——这些特别潜在的规律被模型提取到,就能组成特别漂亮、特别华丽的诗。
自动评论(讲师团队做过的例子):拿淘宝做训练——爬了淘宝、亚马逊等一共三个数据集的评论。生成的评论长这样:「高端大气上档次,质量很好,衣服摸起来挺舒服,挺凉爽的。客服人员服务非常好。」讲师特别强调:「想告诉大家,这些评论都是假的。」——都是根据爬下来的评论生成的,看起来跟真实的评论一模一样。
嘻哈歌词(讲师和同学们前两天刚做的新工作):爬「中国有嘻哈」里的歌词作为训练数据,还用 topic model 做了规范——什么类型的词用在什么位置,韵脚也可以处理。以爱情为主题生成的歌词:「是否不在乎这感情,导致我留在这梦境,我坠入深渊的证明,寻找下一段自己从来不需要想起,让自己看不起,忘记他们就是玩玩而已。」再来一段:「成长需要他描述在你身上,让我错过它的翅膀。经历和青春一场,陪我度过多少的风和雨,各自风雨里的过去,能让我战胜所有崎岖。本来属于我们的道路,献给我们的兄弟。」听起来朗朗上口、比较顺。讲师的彩蛋:大家还没听到这个 idea——「尽量不要先把我们的 idea 抢完」——下一步准备写个 paper。也留给你:这些技术还能用到什么地方,可以根据自己的思路想想。
📝 动手练一练
- 信息瓶颈:Seq2Seq 的编码器要把整个输入序列的信息送进解码。如果输入序列非常长,只靠「上一时刻」这条链传递,会出什么问题?注意力机制是怎么缓解的?
👉 点击查看参考答案
问题:解码端任意时刻的输出都只跟上一时刻的状态、上一时刻的输出直接相关——序列开头的信息要沿着整条时间链一步步传到结尾,途中不断被后来的信息稀释、覆盖,等到生成靠后的词时,开头那些关键信息(比如 France)早已磨损,这就是信息瓶颈。
注意力的缓解:给解码的每一步配上对输入所有时刻的权重 ——按相关性加权求和,得到这一步自己的上下文向量 。这样无论输入多长,每一步都能直接看到输入里最相关的部分(生成法语 economic 时,权重最重的连线就连在英文 economic 上),不必只依赖链末端那个固定向量,长序列的翻译质量显著提高。
- 门的读法:对照细胞状态更新式 (标量情形即正文代码块 3)。(a) 若 、, 变成什么?(b) 若 、 呢?(c) 哪种情形对应「France 被长期保留到预测 French」?(d) 取 、、、,算出 ,并说出新旧信息各占多少。
👉 点击查看参考答案
(a) ——细胞状态完全换成这一时刻的候选值,跟前面没有任何关系。(b) ——这一时刻的信息没被写入,全部来源于上一时刻。(c) 情形 (b):France 写入之后,遗忘门保持接近 1、输入门保持接近 0,这条信息在传送带上原样流过很多时间步。(d) (正文代码块 3 第三种情形同款)——其中旧信息贡献 0.56、占七成,新信息贡献 0.015、占三成权重。
本章小结
本节把「记忆」装进网络。RNN 的核心一步:同一个网络在各个时间段做同一件事,但 时刻隐层的输入里有上一时刻的隐层输出——,// 三个矩阵分别管输入、隐层之间、输出。与 HMM 对照:都是时序模型,一个用概率状态转移矩阵描述、一个用隐层权重关联描述;HMM 可看作 RNN 的一种特例,而 RNN 的 capacity 更大、更 flexible。LSTM:新增细胞状态 这条传送带,遗忘、输入、输出三道门各自由「上一时刻隐藏 + 当前输入」算出, 决定新旧信息的配比——f 全开则旧信息原样保留(France)、i 全开则完全顶替;门值不是人定的,是训练数据调出来的。更省的变体干脆用 顶替 ,两门捆一门。从 pix2pix 到 Seq2Seq:图像的成对变换是 pixels to pixels,序列的 autoencoder 就是 encoder-decoder 两个 LSTM;注意力机制再给每个解码步配上对输入的 softmax 权重 (),甚至「All you need is attention」。生成的诚实面:华丽但没有思想的诗、以假乱真的假评论、押韵的嘻哈歌词——统计规律学得到,中心思想没有。
收官再补两句(本课深度学习部分的最后一节)。课件后两页还有 word2vec(CBOW、Skip-Gram),讲师不展开:用 vector 的形式去表示自然语言,是生活里特别常用的一件事。把深度学习最基础的东西收成三句话:普通的神经网络,是一层层的非线性关系;卷积网络,是通过多层次的层次去做特征学习,使各种不同的特征满足你分类或其他的需求;RNN,一般来讲用一个 LSTM 加上 attention,就可以做很多很多不同的事情。无论在自然语言、在 image、在图像到文字、文字到图像,都有特别多的应用,也正是现在特别多的研究重点。
📋 行动清单
- 手推一遍 LSTM 单步:对照代码块 2,在纸上从「上一时刻隐藏 + 当前输入」出发,把三道门、候选值、、 全部手算一遍。
- 口述 RNN 与 HMM 的异同:都用什么描述时序、各自的隐含状态和可观察状态在哪、为什么说 HMM 是 RNN 的特例。
- 改代码做实验:把代码块 1 的输入序列换成全 1.0,对比有记忆与无记忆两条轨迹的差别;再改循环权重 的正负,看记忆是「正向放大」还是「衰减」。
- 找一首生成诗找规律:对照「春花对秋月、遥望对扶栏杆」,在生成诗里再找出两组「潜在的规律」。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第19章课件:神经网络(BP/CNN/RNN)