[TOC]
9.1 RNN改进
由于循环神经网络使用的非线性激活函数为 $Logistic$ 函数或 $Tanh$ 函数,其导数值都小于1,并且权重矩阵 $\Vert \mathbf{W}_{hh}\Vert$ 也不会太大,因此如果时间间隔 $T-u$ 过大,$\delta_{T,u}$ 会趋于0,因而经常出现梯度消失问题
若将模型修改为
$\mathbf{h}_{t}$ 与 $\mathbf{h}_{t-1}$ 为线性依赖关系,且权重系数为 $\mathbf{I}$ ,这样可以消除梯度爆炸或梯度消失问题。但同时丢失了隐状态神经元在自反馈边上的非线性激活性质。因此,将模型进一步修改为
类似于残差网络的形式
同样,在计算梯度时,仍存在梯度爆炸问题
此外,还存在 记忆容量 问题:
- 随着 $h_t$ 的不断累计存储,会发生饱和线性
- 若 $g(\cdot)$ 为 $Logistic$ 函数,随时间 $t$ 的增长,$h_t$ 会变得越来越大,从而导致 $h$ 变得饱和。随着记忆单元存储的内容越来越多,其(“随机”)丢失的信息也越来越多
9.1.1 基于门控机制的循环神经网络GRU
为改善长程依赖问题,使用一种基于 残差网络思想 的模型修改方法,但梯度爆炸、记忆容量问题仍存在,通过引入门控机制进一步改进模型
门控机制:控制信息的累计速度(有选择地加入新信息,并选择性地遗忘之前累计的信息)
在数字电路中,门 为一个二值变量 $\{0,1\}$
- $0$ 表示关闭状态,不允许任何信息通过
- $1$ 表示开放状态,允许所有信息通过
这类网络称为 基于门控的循环神经网络 (Gated RNN)
遗忘门与更新门
门控的实现
门的输入:当前时间步的输入 $\mathbf{X}_{t}\in \mathbb{R}^{B\times V}$ 和上一时刻隐状态 $\mathbf{H}_{t-1}\in \mathbb{R}^{B\times h}$ ,两个门的经过 siogmoid 函数输出
重置门 $\mathbf{R}_{t}\in \mathbb{R}^{B\times h}$ ,更新门 $\mathbf{Z}_{t}\in \mathbb{R}^{B\times h}$
其中,$\mathbf{W}_{rx}\in \mathbb{R}^{h\times V},\mathbf{W}_{zx}\in \mathbb{R}^{h\times V}$ ,$\mathbf{W}_{rh},\mathbf{W}_{zh}\in \mathbb{R}^{h\times h}$ ,$\mathbf{b}_r,\mathbf{b}_h\in \mathbb{R}^{1\times h}$ 是可学习的参数
候选隐状态
重置门,控制当前时刻隐状态对历史隐状态的响应程度
- 当 $\mathbf{R}_t=\mathbf{I}$ 时,对历史隐状态响应程度最大,保留全部的历史信息,变为RNN;
- 当 $\mathbf{R}_t=\mathbf{0}$ 时,对历史隐状态响应程度最小,丢弃全部的历史信息,从当前时刻重置为输入 $\mathbf{X}_{t}$ 的状态;
隐状态
$\mathbf{Z}_t$ 决定当前时刻的隐状态 $\mathbf{H}_{t}\in \mathbb{R}^{B\times h}$ 对上一时刻隐状态 $\mathbf{H}_{t-1}$ 与新候选状态 $\tilde{\mathbf{H}}_{t}$ 的响应程度
- $\mathbf{Z}_t=\mathbf{I}$ ,对历史隐状态响应程度大,候选隐状态响应程度小,所以对当前时刻的隐状态的更新程度小
- $\mathbf{Z}_t=\mathbf{0}$ ,对历史隐状态响应程度小,候选隐状态响应程度大,所以对当前时刻的隐状态的更新程度大
门的作用
在一个子序列中,不是每个观测值都同等重要
一些状态并没有观测值或者与目标输出状态无关,我们希望有一些机制从隐状态中 遗忘 这些状态
序列之间各部分存在逻辑中断,最好能有一种办法来 重置 内部状态
存在早期观测值对预测未来所有观测值有非常重要意义:如第一个观测值包含整个序列的校验和
重置门(reset gate)、遗忘门:控制历史隐状态的保留程度,若当前输入信息比较重要,则下一个时间步尽量不响应历史隐状态
- $R_t$ 越小,对历史隐状态的重置(遗忘)程度越大,有助于捕获序列中的短期依赖
更新门(update gate):控制隐状态的更新程度,即对过去隐状态更新多少成为新的隐状态,跳过不相关的状态
- $Z_t$ 越小,对隐状态的更新程度越大 ,有助于捕获序列中的长期依赖
相当于新引进了两种极端情况
- 只关注当下输入(短期依赖),即 $R_t=0,Z_t=0$
- 只关注历史信息(长期依赖),即 $R_t=1,Z_t=1$
通过对门参数的学习,使得网络对子序列时序信息的学习介于这两种极端情况之间
可学习参数是RNN的三倍
实现
1 | import torch |
参数初始化
1 | def get_params(vocab_size, num_hiddens, device): |
起始隐状态初始化
返回一个形状为 $(B,h)$ 的全零张量作为起始隐状态的值
1 | def init_gru_state(batch_size, num_hiddens, device): |
门控循环神经网络正向传播
1 | def gru(inputs, state, params): |
训练
1 | vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu() |
循环神经网络
理论上GRU相对于RNN计算量多了进3倍,所以此处GRU用时是RNN的3倍
简洁实现
1 | #@save |
1 | num_inputs = vocab_size |
RNN
虽然理论上GRU相对于RNN计算量多了进3倍,但基于torch框架的实际训练速度相差不大
因为同一层的参数,如 $R_t$ 与 $Z_t$ ,$H_t$ 的计算形式上相似,所以基于torch框架的RNN模型会将这三组矩阵并行计算,所以性能上相差不大
1
2
3
4
5
6
7 for name, param in gru_layer.named_parameters():
print(f"参数名:{name}-参数形状:{param.shape}")
# 参数名:weight_ih_l0-参数形状:torch.Size([768, 28])
# 参数名:weight_hh_l0-参数形状:torch.Size([768, 256])
# 参数名:bias_ih_l0-参数形状:torch.Size([768])
# 参数名:bias_hh_l0-参数形状:torch.Size([768])$B=32,T=35,h=256$ ,两个门控参数和一个连接参数,即 $\begin{aligned}\mathbf{W}_{hx}\\\mathbf{W}_{zx}\\\mathbf{W}_{rx}\end{aligned}\in \mathbb{R}^{(3\times 256)\times 28}$
9.1.2 长短期记忆网络LSTM
隐变量模型存在 信息长期保存 和 输入短期缺失 的问题。最早的解决方法是长短期存储器(long short-term memory)。
为每个输入、输出、隐状态增加记忆单元,为控制记忆单元增加了许多门
- 输入门:决定何时将数据读入记忆单元
- 输出门:从记忆单元读出
- 遗忘门:重置记忆单元内容
门机制
输入信息:当前时间步的输入 $\mathbf{X}_{t}\in \mathbb{R}^{B\times V}$ 和上一时刻隐状态 $\mathbf{H}_{t-1}\in \mathbb{R}^{B\times h}$
输入门 $\mathbf{I}_t\in \mathbb{R}^{B\times h}$ ,遗忘门 $\mathbf{F}_t\in \mathbb{R}^{B\times h}$ ,输出门 $\mathbf{O}_t\in \mathbb{R}^{B\times h}$
其中,$\mathbf{W}_{ix},\mathbf{W}_{fx},\mathbf{W}_{ox}\in \mathbb{R}^{h\times V}$ ,$\mathbf{W}_{ih},\mathbf{W}_{fh},\mathbf{W}_{oh}\in \mathbb{R}^{h\times h}$ ,$\mathbf{b}_i,\mathbf{b}_f,\mathbf{b}_o\in \mathbb{R}^{1\times h}$ 是可学习参数
候选记忆元
候选记忆元 $\tilde{\mathbf{C}}_{t}\in \mathbb{R}^{B\times h}$
其中,$\mathbf{W}_{cx}\in\R^{h\times V}$ ,$\mathbf{W}_{ch}\in \mathbb{R}^{h\times h}$ ,$\mathbf{b}_c\in \mathbb{R}^{1\times h}$ 都是可学习的参数
记忆元
类似于GRU的重置门(遗忘门)和更新门,LSTM中,遗忘门 $\mathbf{F}_{t}$ 控制遗忘(保留)多少历史信息,输入门 $\mathbf{I}_t$ 控制更新多少状态信息
区别于GRU的是
由于 $\mathbf{F}_{t}$ 与 $\mathbf{I}_{t}$ 是独立的,当 $\mathbf{F}_{t},\mathbf{I}_{t}\rightarrow 1$ 时,长期记忆既可以保留历史记忆,又能更新历史记忆;
而GRU的 $\mathbf{Z}_{t},1-\mathbf{Z}_{t}$ 分别控制保留历史状态和更新历史状态,只能二选一
隐状态
隐状态 $\mathbf{H}_{t}\in\R^{B\times h}$ ,
输出门 $\mathbf{O}_t$ 的作用相当于GRU 的重置门,当 $\mathbf{O}_t\rightarrow 0$ ,则忽略历史信息
关于 $\tanh\left(\mathbf{C}_{t}\right)$ 的理解,确保 $\mathbf{H}_{t}$ 压缩在 $[-1,1]$
$\mathbf{F}_t$ 和 $\mathbf{I}_t$ 都在 $[0,1]$ 区间内,所以 $\mathbf{C}_{t}\in [-2,2]$ ,为了将 $\mathbf{H}_{t}$ 恢复到 $[-1,1]$ 内,使用 $\tanh$
$C$ 长期记忆:因为取值范围更大,所以可以存储更多的时序特征
$H$ 短期记忆:取值范围在 $[-1,1]$ ,存储的时序信息少一些
实际上,LSTM与GRU都是在 极远视 和 极近视 两种极端情况的平衡,实际的效果差不多
实现
加载数据
1 | import torch |
参数初始化
1 | def get_lstm_params(vocab_size, num_hiddens, device): |
起始隐状态初始化
长短期记忆网络的隐状态需要返回一个额外的记忆元,长期记忆 $\mathbf{C}_{t}$ ,短期记忆 $\mathbf{H}_{t}$ ,形状为 $(B,h)$
1 | def init_lstm_state(batch_size, num_hiddens, device): |
LSTM前向传播
只有隐状态 $\mathbf{H}_{t}$ 才会传递到输出层,$\mathbf{C}_{t}$ 不直接参与输出
1 | def lstm(inputs, state, params): |
训练
1 | vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu() |
简洁实现
1 | num_inputs = vocab_size |
9.1.3 深度循环神经网络
隐变量与观测值之间的关系不仅是单向非线性的,实际上隐变量和观测值与具体的函数形式的交互方式是多种多样的
深度RNN用多个隐藏层获得更多的非线性信息
若深度RNN有 $L$ 个隐藏层构成
在 $t$ 步有一个小批量的输入数据 $\mathbf{X}_t\in \mathbb{R}^{B\times V}$ ,第 $l$ 个隐藏层的隐状态 $\mathbf{H}^{(l)}_t\in \mathbb{R}^{B\times h}$ ,输出层变量 $\mathbf{O}_t\in \mathbb{R}^{B\times q}$ 。可认为 $\mathbf{H}_t^{(0)}=\mathbf{X}_t$ ,第 $l$ 个隐藏层的隐状态使用的激活函数为 $\phi_l(\cdot)$
其中,
- 当 $l\ge 2$ 时,$\mathbf{W}_{hx}^{(l)}\in \mathbb{R}^{h\times h}$ ,$\mathbf{W}_{hh}^{(l)}\in \mathbb{R}^{h\times h}$ ,$\mathbf{b}_h^{(l)}\in \mathbb{R}^{1\times h}$ 是第 $l$ 层可学习的参数
- 当 $l=1$ 时,$\mathbf{W}_{hx}^{(l)}\in \mathbb{R}^{h\times V}$ ,$\mathbf{W}_{hh}^{(l)}\in \mathbb{R}^{h\times h}$,$\mathbf{b}_h^{(l)}\in \mathbb{R}^{1\times h}$ 是第 $1$ 层可学习的参数
输出层仅基于第 $L$ 层的隐状态
其中,$\mathbf{W}_{qh}^{(L)}\in \mathbb{R}^{q\times h}$ ,$\mathbf{b}_q\in \mathbb{R}^{1\times q}$ 都是输出层可学习的参数
RNN不会用很深,一般2,3层
同时,GRU和LSTM也可以堆叠多个隐藏层
实现
加载数据
1 | import torch |
初始化参数
num_layers
:$L=2$ ,有2个隐藏层
num_inputs
:输入层神经元数为词表大小 $V$
lstm
循环层:输入神经元数为 $V$ ,隐藏层神经元数都为 $h$
d2l.RNNModel(lstm_layer, len(vocab))
输出层:输出层输入神经元数量为 $h$ ,输出神经元数量为 $V$
1 | vocab_size, num_hiddens, num_layers = len(vocab), 256, 2 |
训练和预测
1 | num_epochs, lr = 500, 2 |
由于模型容量大,所以迭代轮数很少就收敛
参数
1 | for name, param in lstm_layer.named_parameters(): |
第 $0$ 隐藏层参数:
- $B=32,h=256$ ,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\mathbf{W}^{(0)}_{ix}\\\mathbf{W}^{(0)}_{fx}\\\mathbf{W}^{(0)}_{ox}\\\mathbf{W}^{(0)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times V}=\left((4\times 256), 28\right)$
第 $1$ 隐藏层参数:
- $B=32,h=256$ ,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\mathbf{W}^{(1)}_{ix}\\\mathbf{W}^{(1)}_{fx}\\\mathbf{W}^{(1)}_{ox}\\\mathbf{W}^{(1)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times h}=\left((4\times 256), 256\right)$
9.1.4 双向深度循环神经网络
对于时序预测模型,单向RNN面向的任务是给定观测的情况下,对下一个输出进行建模
而NLP中还存在一种填空任务,给定上下文,对中间缺失状态的进行填充
如:
- 我
___
。- 我
___
饿了。- 我
___
饿了,我可以吃半头猪。在下文未知的情况下,可能会填入与 “饿” 没有关系的内容,但有下文的情况下,内容会限制在 “饿” 相关的内容
单向RNN只看过去信息,即上文。在下文已知的情况下,当前的时刻的信息量会增加许多,很大程度上会提高当前时刻填充内容的质量
不同长度的上下文范围重要性都是相同的 —— 通过隐马尔科夫模型的DP说明
隐马尔科夫模型中的动态规划
为什么使用 双向 深度循环网络,为什么选择深度网络架构
在任意时间步 $t$ ,会有一个隐变量 $\mathbf{h}_t$ ,每个隐状态都有一定概率 $p(\mathbf{x}_t\vert \mathbf{h}_t)$ 得到观测 $\mathbf{x}_t$。另外,隐状态之间存在状态转移概率 $p(\mathbf{h}_{t+1}\vert \mathbf{h}_t)$ ,因此获取一个序列 $\{\mathbf{x}_1,\mathbf{x}_2,\cdots,\mathbf{x}_T\}$ 的概率服从一个联合概率
假设序列缺失 $\mathbf{x}_j$ ,变为一个填空任务,则目标函数变为 $\max p\left(\mathbf{x}_j\vert \{\mathbf{x}_{-j}\}\right)=\max p(\mathbf{x}_{j}\vert \mathbf{x}_{1},\mathbf{x}_{2},\cdots,\mathbf{x}_{j-1},\mathbf{x}_{j+1},\cdots,\mathbf{x}_T)$
隐状态 $\mathbf{h}_t$ 是一个与时间有关的变量,若所有隐状态的状态值是有限的,即每个 $\mathbf{h}_t$ 都可以有 $k$ 个不同的值,则相邻两个时刻间的状态转移 $p(\mathbf{h}_{t+1}\vert \mathbf{h}_t)\in \mathbb{R}^{k\times k}$ 是一个状态转移矩阵 ,要得到 $p(\mathbf{x}_1,\cdots,\mathbf{x}_T)$ 是 $k^T$ 个路径的求和
基于动态规划,可以得到两种递归形式
前向递归
后向递归
两种递归方式,都允许我们在 $\mathcal{O}(kT)$ 的时间内对 $T$ 个隐变量序列 $(h_1,\cdots,h_T)$ 的所有值求和,避免指数级循环嵌套即指数级运算
为预测当前状态,前向需要结合历史状态,后向需要结合未来状态 ,将二者结合
可以计算
前向递归推导
后向递归推导
双向循环网络
知道未来观测对隐马尔科夫模型中是有益的。
为了更好的完成 填空 任务,我们希望在RNN中加入这种前瞻能力(结合下文推导上文的能力),使得我们可以 使用来自序列两端的信息来估计中间的输出
双向循环网络可以实现需求,实际上是对隐马尔可夫统计模型的一种实现,使用统计模型的函数依赖类型,将其参数化为通用形式
1.双向递归
在任意时间步 $t$ ,有小批量数据 $\mathbf{X}_{t}\in \mathbb{R}^{B\times V}$ 。该时间步的前向隐状态 $\overrightarrow{\mathbf{H}}_t\in \mathbb{R}^{B\times h}$ 和反向隐状态 $\overleftarrow{\mathbf{H}}_t\in \mathbb{R}^{B\times h}$
其中,权重$\mathbf{W}_{hx}^{(f)} \in \mathbb{R}^{h \times V}, \mathbf{W}_{hh}^{(f)} \in \mathbb{R}^{h \times h}, \mathbf{W}_{hx}^{(b)} \in \mathbb{R}^{h \times V}, \mathbf{W}_{hh}^{(b)} \in \mathbb{R}^{h \times h}$ 和偏置 $\mathbf{b}_h^{(f)} \in \mathbb{R}^{1 \times h}, \mathbf{b}_h^{(b)} \in \mathbb{R}^{1 \times h}$ 都是模型的可学习参数
正向递归,结合 $\mathbf{X}_t$ 与 $\mathbf{H}_{t-1}$ 计算 $\mathbf{H}_t$
反向递归 ,将输入序列 $\{\mathbf{X}_1,\mathbf{X}_2,\cdots,\mathbf{X}_T\}$ 翻转,变为 $\{\mathbf{X}_T,\mathbf{X}_{T-1},\cdots,\mathbf{X}_1\}$ ,将 $\{\overleftarrow{\mathbf{H}}_1,\overleftarrow{\mathbf{H}}_2,\cdots,\overleftarrow{\mathbf{H}}_T\}$ 序列翻转为 $\{\overleftarrow{\mathbf{H}}_T,\overleftarrow{\mathbf{H}}_{T-1},\cdots,\overleftarrow{\mathbf{H}}_1\}$
利用前向递归函数计算 $\overleftarrow{\mathbf{H}}_{t-1}=\phi\left(\mathbf{X}_{t-1}\cdot \mathbf{W}_{hx}^{(b)}+\overleftarrow{\mathbf{H}}_t\cdot \mathbf{W}_{hh}^{(b)}+\mathbf{b}_h^{(b)}\right)$ ,得到 $\{\overleftarrow{\mathbf{H}}_T,\overleftarrow{\mathbf{H}}_{T-1},\cdots,\overleftarrow{\mathbf{H}}_1\}$ ,在将其翻转得到 $\{\overleftarrow{\mathbf{H}}_1,\overleftarrow{\mathbf{H}}_2,\cdots,\overleftarrow{\mathbf{H}}_T\}$
2. 输出
再将前向隐状态 $\overrightarrow{\mathbf{H}}_t$ 和反向隐状态 $\overleftarrow{\mathbf{H}}_t$ 连接起来,获取需要送入输出层的隐状态 $\mathbf{H}_t\in \mathbb{R}^{B\times 2h}$ 得到输出 $\mathbf{O}_t\in \mathbb{R}^{B\times q}$
- 若是深度双向循环网络,将 $\mathbf{H}_t$ 作为下一层的输入
这里,权重矩阵$\mathbf{W}_{qh} \in \mathbb{R}^{q \times 2h}$ 和偏置 $\mathbf{b}_q \in \mathbb{R}^{1 \times q}$ 是输出层的模型参数
- $\mathbf{W}_{qh}$ 也可以分别拆分为两个不同方向上的输出
双向循环神经网络的代价
双向RNN不适合做预测(推理)
在预测下一个词元时,我们终究无法知道下一个词元的下文是什么, 所以将不会得到很好的精度
在训练期间,我们能够利用过去和未来的数据来估计现在空缺的词; 而在测试期间,我们只有过去的数据,因此精度将会很差
双向循环神经网络的计算速度非常慢
主要原因是网络的前向传播需要在双向层中进行前向和后向递归, 并且网络的反向传播还依赖于前向传播的结果。 因此,梯度求解将有一个非常长的链
双向层的使用在实践中非常少,并且仅仅应用于部分场合
双向RNN主要用于对序列的 时序特征进行提取(编码器) 、填空。例如,填充缺失的单词、词元注释、以及作为序列处理流水线中的一个步骤对序列进行编码
双向深度LSTM实现
1 | import torch |
可见,模型容量加大,在训练数据上很快就过拟合
但用作预测并没有任何意义
参数与输出形状
输出
1 | import torch |
1 | out.shape, out.reshape((num_steps, batch_size,2, num_hiddens)).shape |
双向RNN的输出分为前向输出和后向输出,即 $\overrightarrow{\mathbf{O}}\in \mathbb{R}^{T\times B\times h}=(35\times 32\times 256)$ ,$\overleftarrow{\mathbf{O}}\in \mathbb{R}^{T\times B\times h}=(35, 32, 256)$ ,所以 $\mathbf{O}=\begin{bmatrix}\overrightarrow{\mathbf{O}}\\\overleftarrow{\mathbf{O}}\end{bmatrix}\in \mathbb{R}^{T\times B\times 2h}=(32,32,512)$
隐状态
1 | state[0].shape,state[1].shape |
$\overrightarrow{\mathbf{H}}_T\in \mathbb{R}^{L\times B\times h }=(3,32,256),\overleftarrow{\mathbf{H}}_T\in \mathbb{R}^{L\times B\times h }=(3,32,256)$
$\mathbf{H}_T=\begin{bmatrix}\overrightarrow{\mathbf{H}}_T\\\overleftarrow{\mathbf{H}}_T\end{bmatrix}\in \mathbb{R}^{2L\times B\times h }$
同理,$\mathbf{C}_T=\begin{bmatrix}\overrightarrow{\mathbf{C}}_T\\\overleftarrow{\mathbf{C}}_T\end{bmatrix}\in \mathbb{R}^{2L\times B\times h }$
参数
1 | for name, param in lstm_layer.named_parameters(): |
$B=32,h=256$
第 $0$ 隐藏层参数:
- 正向,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\overrightarrow{\mathbf{W}}^{(0)}_{ix}\\\overrightarrow{\mathbf{W}}^{(0)}_{fx}\\\overrightarrow{\mathbf{W}}^{(0)}_{ox}\\\overrightarrow{\mathbf{W}}^{(0)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times V }=\left((4\times 256), 28\right)$
- 反向,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\overleftarrow{\mathbf{W}}^{(0)}_{ix}\\\overleftarrow{\mathbf{W}}^{(0)}_{fx}\\\overleftarrow{\mathbf{W}}^{(0)}_{ox}\\\overleftarrow{\mathbf{W}}^{(0)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times V }=\left((4\times 256), 28\right)$
第 $1$ 隐藏层参数:
- 正向,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\overrightarrow{\mathbf{W}}^{(0)}_{ix}\\\overrightarrow{\mathbf{W}}^{(0)}_{fx}\\\overrightarrow{\mathbf{W}}^{(0)}_{ox}\\\overrightarrow{\mathbf{W}}^{(0)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times h }=\left((4\times 256), 256\right)$
- 反向,三个门控参数和一个候选状态的连接参数,即 $\begin{aligned}\overleftarrow{\mathbf{W}}^{(0)}_{ix}\\\overleftarrow{\mathbf{W}}^{(0)}_{fx}\\\overleftarrow{\mathbf{W}}^{(0)}_{ox}\\\overleftarrow{\mathbf{W}}^{(0)}_{hx}\end{aligned}\in \mathbb{R}^{(4\times h)\times h }=\left((4\times 256), 256\right)$
9.2 机器翻译
机器翻译是一种 序列转换模型,也是语言模型的基准测试。机器翻译解决的问题是将 输入序列转换为输出序列。可以追溯到计算机破解语言编码
机器翻译是指将句子序列从一种语言翻译成另一种语言
- 统计机器翻译:涉及翻译模型和语言模型等组成部分的统计分析
- 神经网络翻译
9.2.1 数据集
1 | import os |
加载数据集
下载双语句子,“英-法”数据集。
按字符类型读入内存
数据集的每一行由制表符分割两种语言,前半部分为英语句子后半部分为法语句子。
1 | #@save |
预处理
1 | #@save |
词元化与词表
词元化
对文本进行 单词级的词元化,
实现对前 num_examples
个文本的进行词元化,一个词元要么是一个英文单词 / 法文单词,要么是一个标点符号
返回值:词元化的英文句子序列集 source
和法文句子序列集 target
1 | #@save |
查看句子序列集中的各种长度(token数)的句子数
绘制每个文本序列所包含的词元数量的直方图
1 | #@save |
词表
对句子序列进行单词集的词元化时,词表大小明显大于字符词元。
将出现频率少于2的低频单词进行忽略,视为 <unk>
,指定保留字 <pad>,<bos>,<eos>
用于填充句子和标识句子开始和解决
1 | # d2l.Vocab:获取源句子语料集的词表 |
数据集迭代器
根据时序步长调整句子
在机器翻译中,每个样本都是由源和目标组成的文本序列对,为提高计算效率,在封装为批量前,需要对每行句子进行调整,使每个小批量的形状相同。首先,需要使所有的句子子序列都等于时序步长
时序步长(词元数量)由 num_steps
指定,
- 截断
truncation
- 填充
padding
如果文本序列的词元数目少于num_steps
时, 我们将继续在其末尾添加特定的 '<pad>'
词元, 直到其长度达到num_steps
; 反之,我们将截断文本序列时,只取其前num_steps
个词元, 并且丢弃剩余的词元。
1 | #@save |
当模型通过一个词元接一个词元地生成序列进行预测时, 生成的 <eos>
词元说明完成了序列输出工作。所以对于每一个句子子序列,需要将特定的 <eos>
词元添加到所有序列的末尾
其次,为后续参数更新,记录每个句子子序列的有效长度(排除 <pad>
)
1 | #@save |
返回数据迭代器
1 | #@save |
查看输出
1 | train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8) |
9.2.2 编码器-解码器
可以理解为一种设计模式
编码器:将输入数据转换为中间表达形式(特征),且这个中间表达形式对机器学习友好
解码器:将中间表达形式转换为输出数据(标号)
CNN中的编码器-解码器
RNN中的编码器-解码器
编码器:将文本转换为隐状态向量
解码器:将隐状态向量转换为输出
编码器-解码器架构
编码器:处理输入,输出为解码器的初始隐状态特征张量
解码器:生成输出,基于中间特征张量生成输出
以 “英语-法语” 机器翻译这样的 序列-序列 为例,给定词元化的英文句子序列 'They' 'are' 'watching' '.'
,将长度可变的输入序列编码成一个隐状态。然后对该状态解码,一个 token
接一个 token
生成翻译后的序列 'Ils' 'regordent' '.'
实现
编码器
在编码器中,只需要输入源序列 $X$ ,与神经网络的前向传递类似
1 | from torch import nn |
解码器
在神经网络前向传递的基础上,需要接收编码器的输出状态 init_state()
,并将其转为解码器的初始状态
若对于解码器的输出序列有额外的信息,(如:输出序列的长度),解码器还可接受输入 enc_outputs
编码器会逐个地生成长度可变的词元序列,在每个时间步都会将 输入 和 编码后的状态 映射成当前时间步的输出词元
1 | #@save |
合并
1 | #@save |
9.2.3 Seq2Seq
Seq2Seq可以解决 变长序列-变长序列 的学习任务
编码器解码器架构
编码器是一个RNN,读取整个句子
- 输入序列的信息被 编码 到RNN的隐状态中
- 可以双向RNN,因为不需要用编码器做预测
解码器用另一个RNN输出
- 解码器基于 输入序列的编码信息 的 输出序列已经生成的词元 来预测下一个词元
<eos>
是序列结束的标识,一旦输出序列生成此词元,模型停止预测<bos>
是序列开始的标识,是解码器输入的第一个词元
编码器获取序列的上下文
假设编码器的输入序列是 $\{x_1,x_2,\cdots,x_T\}$ ,在第 $t$ 个时间步,RNN的循环层将词元 $x_t$ 的输入词元向量 $\mathbf{x}_t$ 和上一时刻隐状态 $\mathbf{h}_{t-1}$ 转换为 $\mathbf{h}_t$
可以将编码器理解为 $q()$ ,将输入序列与隐状态转换为序列的上下文变量,在序列的最后一个时间步 $T$ ,可以获得输入序列完整的上下文信息
解码器
来自训练数据集的输出序列 $\{y_1,\cdots,y_{T’}\}$ ,对于时间步 $t’$ ,解码器输出 $y_{t’}$ 的概率取决于输出子序列的上文与输入序列的上下文变量 $\mathbf{c}$ ,即 $P(y_{t’}\vert y_1,\cdots,y_{t’-1},\mathbf{c})$
- 上下文变量需要在所有时间步与解码器的输入拼接
所以在解码器中,对于任一时间步 $t’$ ,RNN将来自上一时间步的输出词元向量 $\mathbf{y}_{t’-1}$ 、输入序列的上下文变量 $\mathbf{c}$ 、输出序列的上文 $\mathbf{s}_{t’-1}$ 转换为本层隐状态 $\mathbf{s}_{t’}$ 和本层输出词元变量 $\mathbf{y}_{t’}$
编码器与解码器间的状态传递
将编码器最后一个时间步的输出隐状态 $\mathbf{h}_T$ 用作解码器的初始隐状态 $\mathbf{s}_0$
即基于RNN实现的编码器与解码器需要有相同的循环层数及每个隐状态数的神经元数相等
输出
在得到解码器的隐状态后,用输出层和 softmax 计算 $t’$ 时的输出 $y_{t’}$ 的条件概率 $P(y_{t’}\vert y_1,\cdots,y_{t’-1},\mathbf{c})$
损失函数
在每个时间步,解码器都预测了输出词元的概率分布,使用softmax来获得分布, 并通过计算交叉熵损失函数来进行优化
在生成批量子序列时,不同长度的子序列通过填充或截取 truncate_pad()
组成长度相同的批量子序列,但填充词元的预测不应该算入损失函数,需要 通过零值化屏蔽不相关的项
训练
强制教学:特定的序列开始词元 <bos>
和 真实的标签序列 拼接在一起作为解码器的输入
预测
一个词元接一个词元的方式预测输出序列,每个解码器当前时间步的输入都来自于上一时间步的预测词元。
在预测开始,序列开始词元 <bos>
需要被送入解码器
预测序列的评估
预测序列 $\hat{\mathbf{Y}}$ 通过与真实的标签序列 $\mathbf{Y}$ 比较来评估预测序列的质量。BLEU是最早用于评估机器翻译结果的指标。
对于预测序列 $\hat{\mathbf{Y}}$ 中的 n-元语法 ,BLEU评估的是这个 n-元语法 是否出现在真实的标签序列 $\mathbf{Y}$ 中
$\text{len}_{label},\text{len}_{pred}$ 分别是标签序列中的词元数和预测序列中的词元数,$k$ 是检测的最大语法单元,即最多检测预测序列中的 $k-gram$ 属于真实序列 $\mathbf{Y}$ 的概率
具体地说,给定标签序列$A$、$B$、$C$、$D$、$E$、$F$ 和预测序列$A$、$B$、$B$、$C$、$D$,
- 1-gram :$\{A,B,B,C,D\}$ 所以1元语法有5个,$A,B,C,D$ 在 $\mathbf{Y}$ 中出现一次,$B$ 只出现一次,所以 $p_1=\frac{4}{5}$
- 2-gram :$\{AB,BB,BC,CD\}$ ,2元语法有4个,其中,$BB$ 未在 $\mathbf{Y}$ 中出现,所以 $p_2=\frac{3}{4}$
- 3-gram :$\{ABB,BBC,BCD\}$ ,3元语法有3个,只有 $BCD$ 在 $\mathbf{Y}$ 中出现,所以 $\mathbf{p}_3=\frac{1}{3}$
当预测序列与标签序列完全相同时,BLEU 是1。分别从预测序列的长度可语法匹配度衡量 Encoder-Decoder 的预测质量
$n-gram$ 越长,则匹配难度越大,因此为更长的 $n$ 元语法分配更大的权重,即对于 $0\le p_n\le 1$ 固定时,$p_n^{\frac{1}{2^n}}$ 会随着 $n$ 的增大而增大。同时,对于同样的 $n$ ,$p_n$ 越大,$p_n^{\frac{1}{2^n}}$ 也越大。
同时,由于生成的预测序列越短,与真实标签序列匹配的可能性 $p_n$ 越大,为避免过短的预测序列,采用惩罚项系数
当 $\mathrm{len}_{\text{label}}>\mathrm{len}_{\text{pred}}$ 时,惩罚项系数小于1
当 $\mathrm{len}_{\text{label}}\le \mathrm{len}_{\text{pred}}$ 时,惩罚项系数等于1
实现
1 | import collections |
编码器
1 | #@save |
嵌入层是一个矩阵,行数等于输入词表的大小(vocab_size
),列数等于特征向量的维度(embed_size
)。对于任意输入词元的索引 $i$ , 嵌入层获取权重矩阵的第 $i$ 行返回其特征向量
encoder测试
1 | encoder = Seq2SeqEncoder(vocab_size=10, embed_size=8, num_hiddens=16, |
解码器
李沐的示例代码实现的实下图,”lls” 的上下文变量为编码器生成的原始上下文变量,而 “regardent” 的上下文变量不再是编码器得到原始上下文变量。变为 $s_{t’-1}$ ,是对 $\mathbf{y}_1$ 与 $\mathbf{c}$ 的组合提取出的时序特征
1 | class Seq2SeqDecoder(d2l.Decoder): |
1 | class Seq2SeqDecoder(d2l.Decoder): |
解码器测试
1 | decoder = Seq2SeqDecoder(vocab_size=10, embed_size=8, num_hiddens=16, |
损失函数
所有预测词元的掩码设置为1,对于有效长度外的序列,其掩码设置为0
1 | #@save |
通过带屏蔽的softmax交叉熵损失遮蔽不相关损失:将预测序列中所有词元的损失乘掩码,过滤掉填充词元生成的不相关预测产生的损失
1 | #@save |
训练
1 | #@save |
1 | #@save |
1 | embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1 |
出现过拟合
预测
1 | #@save |
质量评估
1 | def bleu(pred_seq, label_seq, k): #@save |
修改为原始context后
1 | 111 |
9.2.4 束搜索
解码器逐个词元地生成输出序列,在任意时间步 $t’$ ,解码器输出 $\mathbf{y}_{t’}$ 的概率取决于 $\{\mathbf{y}_{t’-1},\cdots,\mathbf{y}_{1}\}$ 和源序列上下文变量 $\mathbf{c}$ 。若输出词表 $\mathcal{Y}$ ,输出序列的最大词元数为 $T’$ ,则解码器生成序列时相当于从所有 $\mathcal{O}\left(\vert \mathcal{Y}\vert^{T’} \right)$ 个可能序列中找出最理想的组合。
其他搜索方法
贪心搜索
argmax
是使用贪心算法选择当前看到的可能性最大的词元,对于输出序列的每一时间步 $t’$ ,基于贪心搜索从 $\mathcal{Y}$ 中找出最高条件概率的词元
如:序列 $\{A,B,C,
实际上,最优序列应该是最大化 $\prod\limits_{t’=1}^{T’}p\left(y_{t’}\vert y_1,\cdots,y_{t’-1},\mathbf{c}\right)$ 的输出序列,贪心搜索并不能保证条件概率的连乘最大
时间步 $2$ 中,选择条件概率第二高的词元 $C$ ,由于时间步 $3$ 基于时间步 $1,2$ 处的输出子序列生成词元,$1,2$ 步的词元从 $A,B$ 变为 $A,C$ ,时间步 $3$ 处每个词元的条件概率也会相应变化。同理,时间步 $4$ 每个词元的条件概率也发生变化。此时,变化后的序列条件概率为 $0.5\times 0.3\times 0.6\times 0.6=0.054$
穷举搜索
若目标是获得全局最优序列,不考虑解码器的前提下,使用 穷举搜索 可以获得全局最优的条件概率
但是,计算每个序列的条件概率时间复杂度为 $\mathcal{O}\left(\vert \mathcal{Y}\vert^{T’} \right)$ ,指数级复杂度现有的计算机不可能计算
束搜索
为了在精度与计算成本之间做权衡,有束搜索
超参数:束宽 $k$
输出序列生成
在每个时间步 $t’$ 选择条件概率最高的前 $k$ 个词元,成为 $k$ 个候选输出序列的第一个词元 。
在下一个时间步 $t’+1$ ,从 $k\vert \mathcal{Y}\vert$ 个可能的词元中选择条件概率最高的 $k$ 个候选词元
假设输出的词表只包含五个元素$\mathcal{Y} = \{A, B, C, D, E\}$,其中有一个是“<eos>”。设置 $k=2$,输出序列的最大长度为 $T’=3$
在时间步$1$,假设具有最高条件概率$ P(y_1 \vert \mathbf{c})$ 的词元是 $A$ 和 $C$
在时间步$2$,我们计算所有 $y_2 \in \mathcal{Y}$ 为:
从这十个值中选择最大的两个,比如$P(A, B \vert \mathbf{c})$和$P(C, E \vert \mathbf{c})$。
然后在时间步$3$,计算所有 $y_3 \in \mathcal{Y}$ 为:
从这十个值中选择最大的两个,即$P(A, B, D \vert \mathbf{c})$和$P(C, E, D \vert \mathbf{c})$
我们会得到六个候选输出序列:
(1)$A$;(2)$C$;(3)$A,B$;(4)$C,E$;(5)$A,B,D$;(6)$C,E,D$。
最后,基于这六个序列(例如,丢弃包括“<eos>”和之后的部分),我们获得最终候选输出序列集合。
考虑输出序列长度
计算候选序列的分数
其中$L$是候选序列的长度,$\alpha$通常设置为$0.75$。
然后我们选择其中候选序列的分数最高的即条件概率乘积最高的序列作为输出序列
因为长序列的条件概率都很小,且求和的对数项更多,所以用 $L^\alpha$ 奖励长序列
- 序列越长,条件概率越小 $\Rightarrow \log P<0$ 越小,直接比较对数条件概率的和会造成长序列分数永远小于短序列分数。
- 长度越长 $\frac{1}{L^{\alpha}}$ 越小,$\frac{1}{L^{\alpha}}\sum \log P<0$ 越大
束搜索的计算量为$\mathcal{O}(k\times \vert \mathcal{Y}\vert \times T’)$
9.3 应用
9.3.1 应用到机器学习
循环神经网络可以应用到很多不同类型的机器学习任务,分为三种模式:
- 序列到类别模式
- 同步的序列到序列模式
- 异步的序列到序列模式
序列到类别模式
主要用于序列数据的分类问题:输入为序列,输出为类别
如:语言的情感分类
序列样本 $x_{1:T}=(x_1,\cdots,x_T)$ 为一个长度为 $T$ 的序列,输出为一个类别 $y\in \{1,\cdots,C\}$ ,将样本按不同时刻输入到RNN中,得到不同时刻的隐状态 $h_1,\cdots,h_T$ ,将 $h_T$ 作为整个序列的最终表示,输入给分类器 $g(\cdot)$ 进行分类
- 其中 $g(\cdot)$ 可以是简单的线性分类器(Logistic)或者复杂的分类器(多层前馈神经网络)
除了将最后时刻的状态作为整个序列的表示外(正常模式),还可以对整个序列的平均状态作为整个序列的表示(平均采样模式)
同步的序列到序列模式
主要用于 序列标注 任务,即每一时刻的输入和输出一一对应,输入序列和输出序列的长度相同
输入为序列样本 $x_{1:T}=(x_1,\cdots,x_T)$ ,输出为序列 $y_{1:T}=(y_1,\cdots,y_T)$ 。样本按不同时刻输入到RNN中,得到不同时刻的隐状态 $h_1,\cdots,h_T$ ,每个时刻的隐状态 $h_t$ 代表了当前时刻和历史信息,并输入给分类器 $g(\cdot)$ 得到当前时刻的标签 $\hat{y}_t$
应用
词性标注问题:中文分词
信息抽取:从无结构的文本中抽取结构化的信息,形成知识
异步的序列到序列模式
编码器-解码器模型:输入序列和输出序列不需要有严格的对应关系,也不需要保持长度相同
输入为长度为 $T$ 的序列样本 $x_{1:T}=(x_1,\cdots,x_T)$ ,输出为长度为 $M$ 的序列 $y_{1:M}=(y_1,\cdots,x_M)$ 。
一般通过 先编码后半解码 的方式实现
先将样本 $x$ 按不同时刻输入到一个循环神经网络(编码器)中,并得到其编码 $h_T$ 。然后再使用另一个循环神经网络(解码器)得到输出序列 $\hat{y}_{1:M}$
- EOS表示输入序列结束
- $g(\cdot)$ 为分类器
- $\hat{y}_t$ 为预测输出
- 解码器通常使用非线性的自回归模型,每一时刻输入为上一时刻的预测结果 $\hat{y}_{t-1}$
应用
如:机器翻译,输入为源语言的单词序列,输出为目标语言的单词序列
9.3.2 相关领域
生成语言模型
自然语言理解:一个句子的可能性,合理性
作词机
机器翻译
传统统计学习机器翻译
基于序列到序列的机器翻译
- 一个RNN用来编码
- 一个RNN用来解码