🐬RNN模型
2021-12-2
| 2023-8-7
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 
元语法模型,单词在时间步的条件概率仅取决于前面个单词。 对于时间步之前的单词, 如果想将其可能产生的影响合并到上, 需要增加,然而模型参数的数量也会随之呈指数增长, 因为词表需要存储个数字, 因此与其将模型化, 不如使用隐变量模型:
其中是隐状态(hidden state),也称为隐藏变量(hidden variable),存储了到时间步的序列信息。
通常可以基于当前输入和先前隐状态来计算时间步处的任何时间的隐状态:
对于函数,隐变量模型不是近似值。 毕竟是可以仅仅存储到目前为止观察到的所有数据, 然而这样的操作可能会使计算和存储的代价都变得昂贵。
 
隐藏层和隐状态指的是两个截然不同的概念:隐藏层是在从输入到输出的路径上(以观测角度来理解)的隐藏的层, 而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入, 并且这些状态只能通过先前时间步的数据来计算。
 

不含隐藏状态的神经网络

考虑一个含单隐藏层的多层感知机
给定样本数为 、特征向量维度为的小批量数据样本 。设隐藏层的激活函数为 ,那么隐藏层的输出 计算为
隐藏层权重参数,隐藏层偏差参数为隐藏单元个数。上式相加的两项形状不同,因此将按照广播机制相加。把隐藏变量作为输出层的输入,且设输出个数为 (如分类问题中的类别数),输出层的输出为
输出变量, 输出层权重参数, 输出层偏差参数 。如果是分类问题,可以使用 来计算输出类别的概率分布。
 

含隐藏状态的循环神经网络

考虑输入数据存在时间相关性的情况
是序列中时间步的小批量输入, 是该时间步的隐藏变量。与多层感知机不同的是,这里保存上一时间步的隐藏变量,并引入一个新的权重参数 ,该参数用来描述在当前时间步如何使用上一时间步的隐藏变量。具体来说,时间步的隐藏变量的计算由当前时间步的输入和上一时间步的隐藏变量共同决定:
与多层感知机相比,这里添加了 一项。由上式中相邻时间步的隐藏变量之间的关系可知,这里的隐藏变量能够捕捉截至当前时间步的序列的历史信息,就像是神经网络当前时间步的状态或记忆一样。因此,该隐藏变量也称为隐藏状态。由于隐藏状态在当前时间步的定义使用了上一时间步的隐藏状态,上式的计算是循环的。使用循环计算的网络即循环神经网络(recurrent neural network)。
循环神经网络有很多种不同的构造方法。含上式所定义的隐藏状态的循环神经网络是极为常见的一种。若无特别说明,本章中的循环神经网络均基于上式中隐藏状态的循环计算。在时间步,输出层的输出和多层感知机中的计算类似:
循环神经网络的参数包括隐藏层的权重和偏差以及输出层的权重 和偏差。值得一提的是,即便在不同时间步,循环神经网络也始终使用这些模型参数。因此,循环神经网络模型参数的数量不随时间步的增加而增长。
notion image
上图展示了循环神经网络在3个相邻时间步的计算逻辑。在时间步,隐藏状态的计算可以看成是将输入和前一时间步隐藏状态连结后输入一个激活函数为的全连接层。该全连接层的输出就是当前时间步的隐藏状态 ,且模型参数为 的连结,偏差为 。当前时间步 的隐藏状态将参与下一个时间步的隐藏状态的计算,并输入到当前时间步的全连接输出层。
 
隐藏状态中的计算等价于 连结后的矩阵乘以连结后的矩阵。
用一个具体的例子来验证这一点:
首先构造矩阵 ,它们的形状分别为(3, 1)、(1, 4)、(3, 4)和(4, 4)。将 分别相乘,再把两个乘法运算的结果相加,得到形状为(3, 4)的矩阵
将矩阵XH按列(维度1)连结,连结后的矩阵形状为(3, 5)。可见,连结后矩阵在维度1的长度为矩阵XH在维度1的长度之和()。然后,将矩阵 按行(维度0)连结,连结后的矩阵形状为(5, 4)。最后将两个连结后的矩阵相乘,得到与上面代码输出相同的形状为(3, 4)的矩阵
 

基于循环神经网络的字符级语言模型

看一下如何使用循环神经网络来构建语言模型。 设小批量大小为1,批量中的那个文本序列为“machine”。 为了简化后续部分的训练,考虑使用字符级语言模型(character-level language model), 将文本词元化为字符而不是单词。下图演示了如何通过基于字符级语言建模的循环神经网络, 使用当前的和先前的字符预测下一个字符
notion image
在训练过程中,对每个时间步的输出层的输出进行softmax操作, 然后利用交叉熵损失计算模型输出和标签之间的误差。 由于隐藏层中隐状态的循环计算,第3个时间步的输出 由文本序列“m”、“a”和“c”确定。 由于训练数据中这个文本序列的下一个字符是“h”, 因此第3个时间步的损失将取决于下一个字符的概率分布, 而下一个字符是基于特征序列“m”、“a”、“c”和这个时间步的标签“h”生成的。
实践中,我们使用的批量大小为, 每个词元都由一个维向量表示。 因此,在时间步输入将是一个矩阵
 

困惑度(Perplexity)

如何度量语言模型的质量?一个好的语言模型能够用高度准确的词元来预测接下来会看到什么。
考虑一下由不同的语言模型给出的对“It is raining …”(“…下雨了”)的续写:
  1. “It is raining outside”(外面下雨了)
  1. “It is raining banana tree”(香蕉树下雨了)
  1. “It is raining piouw;kcj pwepoiut”(piouw;kcj pwepoiut下雨了)
就质量而言,例1显然是最合乎情理、在逻辑上最连贯的。 虽然这个模型可能没有很准确地反映出后续词的语义, 比如,“It is raining in San Francisco”(旧金山下雨了) 和“It is raining in winter”(冬天下雨了) 可能才是更完美的合理扩展, 但该模型已经能够捕捉到跟在后面的是哪类单词。 例2则要糟糕得多,因为其产生了一个无意义的续写。 尽管如此,至少该模型已经学会了如何拼写单词, 以及单词之间的某种程度的相关性。 最后,例3表明了训练不足的模型是无法正确地拟合数据的。
 
我们可以通过计算序列的似然概率来度量模型的质量。 然而这是一个难以理解、难以比较的数字。 毕竟,较短的序列比较长的序列更有可能出现, 因此评估模型产生托尔斯泰的巨著《战争与和平》的可能性不可避免地会比产生圣埃克苏佩里的中篇小说《小王子》可能性要小得多。 而缺少的可能性值相当于平均数。
在这里,信息论可以派上用场了。如果想要压缩文本,我们可以根据当前词元集预测的下一个词元。 一个更好的语言模型应该能让我们更准确地预测下一个词元。 因此,它应该允许我们在压缩序列时花费更少的比特。 所以可以通过一个序列中所有的n个词元的交叉熵损失的平均值来衡量:
其中由语言模型给出, 是在时间步从该序列中观察到的实际词元。 这使得不同长度的文档的性能具有了可比性。
由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量:
困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”:
  • 在最好的情况下,模型总是完美地估计标签词元的概率为1,模型的困惑度为1
  • 在最坏的情况下,模型总是预测标签词元的概率为0,困惑度是正无穷大
  • 在基线上,该模型的预测是词表的所有可用词元上的均匀分布。 在这种情况下,困惑度等于词表中唯一词元的数量。 事实上,如果在没有任何压缩的情况下存储序列, 这将是能做的最好的编码方式。 因此,这种方式提供了一个重要的上限, 而任何实际模型都必须超越这个上限。
 
 

长程依赖问题

循环神经网络在学习过程中的主要问题是由于梯度消失或爆炸问题,很难建模长时间间隔的状态之间的依赖关系.
一般而言,循环网络的梯度爆炸问题比较容易解决,一般通过权重衰减或梯度截断来避免。梯度消失是循环网络的主要问题,除了使用一些优化技巧外,更有效的方式就是改变模型。
 

循环神经网络的梯度分析

在简化模型中,我们将时间步 的隐状态表示为 , 输入表示为,输出表示为。输入和隐状态可以拼接后与隐藏层中的一个权重变量相乘。 因此,分别使用来表示隐藏层和输出层的权重。 每个时间步的隐状态和输出可以写为:
分别是隐藏层和输出层的变换。
因此有一个链 , 它们通过循环计算彼此依赖。 前向传播相当简单,一次一个时间步的遍历三元组, 然后通过一个目标函数在所有个时间步内评估输出 和对应的标签之间的差异:
 
对于反向传播,问题则有点棘手, 特别是当计算目标函数关于参数的梯度时。 具体来说,按照链式法则:
乘积的第一项和第二项很容易计算, 而第三项是使事情变得棘手的地方, 因为需要循环地计算参数的影响。 根据递归计算, 既依赖于又依赖于, 其中 的计算也依赖于。 因此,使用链式法则产生:
假设有三个序列 , 当 时,序列满足 。 对于 ,就很容易得出:
基于下列公式替换
梯度计算满足。 因此,对于每个,可以使用下面的公式移除循环计算
虽然可以使用链式法则递归地计算 , 但当很大时这个链就会变得很长。 需要想想办法来处理这一问题。
 

完全计算

显然可以仅仅计算 全部总和, 然而,这样的计算非常缓慢,并且可能会发生梯度爆炸, 因为初始条件的微小变化就可能会对结果产生巨大的影响。这对于我们想要估计的模型而言是非常不可取的。 在实践中,这种方法几乎从未使用过。

截断时间步

或者可以在 步后截断 求和计算。 这会带来真实梯度的近似, 只需将求和终止为 。 在实践中,这种方式工作得很好。 它通常被称为截断的通过时间反向传播。 这样做导致该模型主要侧重于短期影响,而不是长期影响。 这在现实中是可取的,因为它会将估计值偏向更简单和更稳定的模型。

随机截断

可以用一个随机变量替换, 该随机变量在预期中是正确的,但是会截断序列。 这个随机变量是通过使用序列 来实现的, 序列预定义了 , 其中, 因此。 使用它来替中的梯度 得到:
的定义中推导出来 。 每当 时,递归计算终止在这个 时间步。 这导致了不同长度序列的加权和,其中长序列出现的很少, 所以将适当地加大权重。 这个想法是由塔莱克和奥利维尔提出的。

比较策略

notion image
上图说明了当基于循环神经网络使用通过时间反向传播分析《时间机器》书中前几个字符的三种策略:
  • 第一行采用随机截断,方法是将文本划分为不同长度的片断
  • 第二行采用常规截断,方法是将文本分解为相同长度的子序列
  • 第三行采用通过时间的完全反向传播,结果是产生了在计算上不可行的表达式
遗憾的是,虽然随机截断在理论上具有吸引力, 但很可能是由于多种因素在实践中并不比常规截断更好。 首先,在对过去若干个时间步经过反向传播后, 观测结果足以捕获实际的依赖关系。 其次,增加的方差抵消了时间步数越多梯度越精确的事实。 第三,我们真正想要的是只有短范围交互的模型。 因此,模型需要的正是截断的通过时间反向传播方法所具备的轻度正则化效果。
 
 

通过时间反向传播的细节

这里看一下通过时间反向传播问题的细节,考虑一个没有偏置参数的循环神经网络, 其在隐藏层中的激活函数使用恒等映射( )。 对于时间步 ,设单个样本的输入及其对应的标签分别为 。 计算隐状态 和输出的方式为:
其中权重参数为 。 用 表示时间步处 (即从序列开始起的超过 个时间步)的损失函数, 则目标函数的总体损失是:
为了在循环神经网络的计算过程中可视化模型变量和参数之间的依赖关系, 模型绘制一个计算图, 如下图所示。 例如,时间步3的隐状态 的计算取决于模型参数, 以及最终时间步的隐状态以及当前时间步的输入
notion image
正如刚才所说,模型参数是 通常,训练该模型需要对这些参数进行梯度计算: 。 根据依赖关系,可以沿箭头的相反方向遍历计算图,依次计算和存储梯度。 为了灵活地表示链式法则中不同形状的矩阵、向量和标量的乘法,继续使用运算符。
首先,在任意时间步 , 目标函数关于模型输出的微分计算是相当简单的:
可以计算目标函数关于输出层中参数的梯度: 。 目标函数 通过 依赖于。 依据链式法则得到
在最后的时间步 ,目标函数 仅通过 依赖于隐状态 。 因此通过使用链式法可以很容易地得到梯度
当目标函数 通过 依赖 时, 对于任意时间步 来说都变得更加棘手。 根据链式法则,隐状态的梯度 在任何时间步骤 时都可以递归地计算为:
为了进行分析,对于任何时间步 展开递归计算得
可以看到, 这个简单的线性例子已经展现了长序列模型的一些关键问题: 它陷入到 的潜在的非常大的幂。 在这个幂中,小于1的特征值将会消失,大于1的特征值将会发散。 这在数值上是不稳定的,表现形式为梯度消失或梯度爆炸。 解决此问题的一种方法是按照计算方便的需要截断时间步长的尺寸。 实际上,这种截断是通过在给定数量的时间步之后分离梯度来实现的。 更复杂的序列模型(如长短期记忆模型) 会进一步缓解这一问题的。
最后,图中表明: 目标函数 通过隐状态 依赖于隐藏层中的模型参数 。 为了计算有关这些参数的梯度 , 应用链式规则得:
其中 是影响数值稳定性的关键量
由于通过时间反向传播是反向传播在循环神经网络中的应用方式, 所以训练循环神经网络交替使用前向传播和通过时间反向传播。 通过时间反向传播依次计算并存储上述梯度。 具体而言,存储的中间值会被重复使用,以避免重复计算, 例如存储 以便在计算时使用。
  • PyTorch
  • 语言模型和数据集RNN的实现
    目录