动手学深度学习8.4. 循环神经网络-笔记练习(PyTorch)

devtools/2024/9/20 1:18:59/ 标签: 深度学习, rnn, pytorch, cnn, 梯度裁剪, 隐马尔可夫

本节课程地址:54 循环神经网络 RNN【动手学深度学习v2】_哔哩哔哩_bilibili

本节教材地址:8.4. 循环神经网络 — 动手学深度学习 2.0.0 documentation (d2l.ai)

本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>rnn.ipynb


循环神经网络

在 8.3节 中, 我们介绍了 n 元语法模型, 其中单词 x_t 在时间步 t 的条件概率仅取决于前面 n-1 个单词。 对于时间步 t-(n-1) 之前的单词, 如果我们想将其可能产生的影响合并到 x_t 上, 需要增加 n ,然而模型参数的数量也会随之呈指数增长, 因为词表 \mathcal{V} 需要存储 |\mathcal{V}|^n 个数字, 因此与其将 P(x_t \mid x_{t-1}, \ldots, x_{t-n+1}) 模型化, 不如使用隐变量模型:

P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}), (8.4.1)

其中 h_{t-1} 是隐状态(hidden state), 也称为隐藏变量(hidden variable), 它存储了到时间步 t-1 的序列信息。 通常,我们可以基于当前输入 x_t 和先前隐状态 h_{t-1} 来计算时间步 t 处的任何时间的隐状态:

h_t = f(x_{t}, h_{t-1}). (8.4.2)

对于 (8.4.2)中的函数 f ,隐变量模型不是近似值。 毕竟 h_t 是可以仅仅存储到目前为止观察到的所有数据, 然而这样的操作可能会使计算和存储的代价都变得昂贵。

回想一下,我们在 4节 中 讨论过的具有隐藏单元的隐藏层。 值得注意的是,隐藏层和隐状态指的是两个截然不同的概念。 如上所述,隐藏层是在从输入到输出的路径上(以观测角度来理解)的隐藏的层, 而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入, 并且这些状态只能通过先前时间步的数据来计算。

循环神经网络(recurrent neural networks,RNNs) 是具有隐状态的神经网络。 在介绍循环神经网络模型之前, 我们首先回顾 4.1节 中介绍的多层感知机模型。

无隐状态的神经网络

让我们来看一看只有单隐藏层的多层感知机。 设隐藏层的激活函数为 \phi , 给定一个小批量样本 \mathbf{X} \in \mathbb{R}^{n \times d} , 其中批量大小为 n ,输入维度为 d , 则隐藏层的输出 \mathbf{H} \in \mathbb{R}^{n \times h} 通过下式计算:

\mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{xh} + \mathbf{b}_h). (8.4.3)

在 (8.4.3)中, 我们拥有的隐藏层权重参数为 \mathbf{W}_{xh} \in \mathbb{R}^{d \times h}, 偏置参数为 \mathbf{b}_h \in \mathbb{R}^{1 \times h} , 以及隐藏单元的数目为 h 。 因此求和时可以应用广播机制(见 2.1.3节)。 接下来,将隐藏变量 H 用作输出层的输入。 输出层由下式给出:

\mathbf{O} = \mathbf{H} \mathbf{W}_{hq} + \mathbf{b}_q,(8.4.4)

其中, \mathbf{O} \in \mathbb{R}^{n \times q} 是输出变量, \mathbf{W}_{hq} \in \mathbb{R}^{h \times q} 是权重参数, \mathbf{b}_q \in \mathbb{R}^{1 \times q} 是输出层的偏置参数。 如果是分类问题,我们可以用 \text{softmax}(\mathbf{O}) 来计算输出类别的概率分布。

这完全类似于之前在 8.1节 中解决的回归问题, 因此我们省略了细节。 无须多言,只要可以随机选择“特征-标签”对, 并且通过自动微分和随机梯度下降能够学习网络参数就可以了。

有隐状态的循环神经网络

有了隐状态后,情况就完全不同了。 假设我们在时间步 t 有小批量输入 \mathbf{X}_t \in \mathbb{R}^{n \times d}。 换言之,对于 n 个序列样本的小批量, \mathbf{X}_t 的每一行对应于来自该序列的时间步 t 处的一个样本。 接下来,用 \mathbf{H}_t \in \mathbb{R}^{n \times h} 表示时间步 t 的隐藏变量。 与多层感知机不同的是, 我们在这里保存了前一个时间步的隐藏变量 \mathbf{H}_{t-1} , 并引入了一个新的权重参数 \mathbf{W}_{hh} \in \mathbb{R}^{h \times h}, 来描述如何在当前时间步中使用前一个时间步的隐藏变量。 具体地说,当前时间步隐藏变量由当前时间步的输入 与前一个时间步的隐藏变量一起计算得出:

\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h). (8.4.5)

与 (8.4.3) 相比, (8.4.5) 多添加了一项 \mathbf{H}_{t-1} \mathbf{W}_{hh} , 从而实例化了 (8.4.2) 。 从相邻时间步的隐藏变量 \mathbf{H}_{t} 和 \mathbf{H}_{t-1} 之间的关系可知, 这些变量捕获并保留了序列直到其当前时间步的历史信息, 就如当前时间步下神经网络的状态或记忆, 因此这样的隐藏变量被称为隐状态(hidden state)。 由于在当前时间步中, 隐状态使用的定义与前一个时间步中使用的定义相同, 因此 (8.4.5) 的计算是循环的(recurrent)。 于是基于循环计算的隐状态神经网络被命名为 循环神经网络(recurrent neural network)。 在循环神经网络中执行 (8.4.5) 计算的层 称为循环层(recurrent layer)。

有许多不同的方法可以构建循环神经网络, 由 (8.4.5) 定义的隐状态的循环神经网络是非常常见的一种。 对于时间步 t ,输出层的输出类似于多层感知机中的计算:

\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q. (8.4.6)

循环神经网络的参数包括隐藏层的权重 \mathbf{W}_{xh} \in \mathbb{R}^{d \times h}, \mathbf{W}_{hh} \in \mathbb{R}^{h \times h} 和偏置 \mathbf{b}_h \in \mathbb{R}^{1 \times h} , 以及输出层的权重 \mathbf{W}_{hq} \in \mathbb{R}^{h \times q} 和偏置 \mathbf{b}_q \in \mathbb{R}^{1 \times q} 。 值得一提的是,即使在不同的时间步,循环神经网络也总是使用这些模型参数。 因此,循环神经网络的参数开销不会随着时间步的增加而增加。

图8.4.1 展示了循环神经网络在三个相邻时间步的计算逻辑。 在任意时间步 t ,隐状态的计算可以被视为:

  1. 拼接当前时间步 t 的输入 \mathbf{X}_t 和前一时间步 t-1 的隐状态 \mathbf{H}_{t-1} ;
  2. 将拼接的结果送入带有激活函数 \phi 的全连接层。 全连接层的输出是当前时间步 t 的隐状态 \mathbf{H}_{t} 。

在本例中,模型参数是 \mathbf{W}_{xh}  \mathbf{W}_{hh} 的拼接, 以及 \mathbf{b}_h 的偏置,所有这些参数都来自 (8.4.5)。 当前时间步 t 的隐状态 \mathbf{H}_{t} 将参与计算下一时间步 t+1 的隐状态 \mathbf{H}_{t+1} 。 而且 \mathbf{H}_{t} 还将送入全连接输出层, 用于计算当前时间步 t 的输出 \mathbf{O}_t 。

我们刚才提到,隐状态中 \mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} 的计算, 相当于 \mathbf{X}_t 和 \mathbf{H}_{t-1} 的拼接 与 \mathbf{W}_{xh} 和 \mathbf{W}_{hh} 的拼接的矩阵乘法。 虽然这个性质可以通过数学证明, 但在下面我们使用一个简单的代码来说明一下。 首先,我们定义矩阵X、W_xh、H和W_hh, 它们的形状分别为(3,1)、(1,4)、(3,4)和(4,4)。 分别将X乘以W_xh,将H乘以W_hh, 然后将这两个乘法相加,我们得到一个形状为(3,4)的矩阵。

import torch
from d2l import torch as d2l
X, W_xh = torch.normal(0, 1, (3, 1)), torch.normal(0, 1, (1, 4))
H, W_hh = torch.normal(0, 1, (3, 4)), torch.normal(0, 1, (4, 4))
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)

输出结果:
tensor([[ 1.1244, 0.3294, 0.2590, 1.7822],
[ 0.9678, -2.0727, 1.8833, -0.9362],
[-2.6480, 5.5913, 3.1343, 2.0930]])

现在,我们沿列(轴1)拼接矩阵X和H, 沿行(轴0)拼接矩阵W_xh和W_hh。 这两个拼接分别产生形状(3, 5)和形状(5, 4)的矩阵。 再将这两个拼接的矩阵相乘, 我们得到与上面相同形状(3, 4)的输出矩阵。

torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))

输出结果:
tensor([[ 1.1244, 0.3294, 0.2590, 1.7822],
[ 0.9678, -2.0727, 1.8833, -0.9362],
[-2.6480, 5.5913, 3.1343, 2.0930]])


RNN补充:

\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h)

RNN多的一项 \mathbf{H}_{t-1} \mathbf{W}_{hh} 可以建立不同时刻的关联性,赋予网络记忆的能力。

从上图的RNN展开图中可以看出,RNN是多个沿着时序反复迭代的网络组成的大的网络结构,但由于每个时间点的网络和计算都是相同的,因此可以看作是循环计算。


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

回想一下 8.3节 中的语言模型, 我们的目标是根据过去的和当前的词元预测下一个词元, 因此我们将原始序列移位一个词元作为标签。 Bengio等人首先提出使用神经网络进行语言建模 f="https://zh.d2l.ai/chapter_references/zreferences.html#id8">Bengioet al., 2003。 接下来,我们看一下如何使用循环神经网络来构建语言模型。 设小批量大小为1,批量中的文本序列为“machine”。 为了简化后续部分的训练,我们考虑使用 字符级语言模型(character-level language model), 将文本词元化为字符而不是单词。 图8.4.2 演示了 如何通过基于字符级语言建模的循环神经网络, 使用当前的和先前的字符预测下一个字符。

在训练过程中,我们对每个时间步的输出层的输出进行softmax操作, 然后利用交叉熵损失计算模型输出和标签之间的误差。 由于隐藏层中隐状态的循环计算, 图8.4.2中的第3个时间步的输出 O3 由文本序列“m”“a”和“c”确定。 由于训练数据中这个文本序列的下一个字符是“h”, 因此第3个时间步的损失将取决于下一个字符的概率分布, 而下一个字符是基于特征序列“m”“a”“c”和这个时间步的标签“h”生成的。

在实践中,我们使用的批量大小为 n>1 , 每个词元都由一个 d 维向量表示。 因此,在时间步 t 输入 \mathbf X_t 将是一个 n\times d 矩阵, 这与我们在 8.4.2节 中的讨论相同。

困惑度(Perplexity)

最后,让我们讨论如何度量语言模型的质量, 这将在后续部分中用于评估基于循环神经网络的模型。 一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。 考虑一下由不同的语言模型给出的对“It is raining ...”(“...下雨了”)的续写:

  1. "It is raining outside"(外面下雨了);
  2. "It is raining banana tree"(香蕉树下雨了);
  3. "It is raining piouw;kcj pwepoiut"(piouw;kcj pwepoiut下雨了)。

就质量而言,例1显然是最合乎情理、在逻辑上最连贯的。 虽然这个模型可能没有很准确地反映出后续词的语义, 比如,“It is raining in San Francisco”(旧金山下雨了) 和“It is raining in winter”(冬天下雨了) 可能才是更完美的合理扩展, 但该模型已经能够捕捉到跟在后面的是哪类单词。 例2则要糟糕得多,因为其产生了一个无意义的续写。 尽管如此,至少该模型已经学会了如何拼写单词, 以及单词之间的某种程度的相关性。 最后,例3表明了训练不足的模型是无法正确地拟合数据的。

我们可以通过计算序列的似然概率来度量模型的质量。 然而这是一个难以理解、难以比较的数字。 毕竟,较短的序列比较长的序列更有可能出现, 因此评估模型产生托尔斯泰的巨著《战争与和平》的可能性 不可避免地会比产生圣埃克苏佩里的中篇小说《小王子》可能性要小得多。 而缺少的可能性值相当于平均数。

在这里,信息论可以派上用场了。 我们在引入softmax回归 ( 3.4.7节 )时定义了熵、惊异和交叉熵, 并在信息论的在线附录 中讨论了更多的信息论知识。 如果想要压缩文本,我们可以根据当前词元集预测的下一个词元。 一个更好的语言模型应该能让我们更准确地预测下一个词元。 因此,它应该允许我们在压缩序列时花费更少的比特。 所以我们可以通过一个序列中所有的 n 个词元的交叉熵损失的平均值来衡量:

\frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1), (8.4.7)

其中 P 由语言模型给出, x_t 是在时间步 t 从该序列中观察到的实际词元。 这使得不同长度的文档的性能具有了可比性。 由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量。 简而言之,它是 (8.4.7) 的指数:

\exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right).

困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”。 我们看看一些案例。

  • 在最好的情况下,模型总是完美地估计标签词元的概率为1。 在这种情况下,模型的困惑度为1。
  • 在最坏的情况下,模型总是预测标签词元的概率为0。 在这种情况下,困惑度是正无穷大。
  • 在基线上,该模型的预测是词表的所有可用词元上的均匀分布。 在这种情况下,困惑度等于词表中唯一词元的数量。 事实上,如果我们在没有任何压缩的情况下存储序列, 这将是我们能做的最好的编码方式。 因此,这种方式提供了一个重要的上限, 而任何实际模型都必须超越这个上限。

在接下来的小节中,我们将基于循环神经网络实现字符级语言模型, 并使用困惑度来评估这样的模型。


补充:

 


小结

  • 对隐状态使用循环计算的神经网络称为循环神经网络(RNN)。
  • 循环神经网络的隐状态可以捕获直到当前时间步序列的历史信息。
  • 循环神经网络模型的参数数量不会随着时间步的增加而增加。
  • 我们可以使用循环神经网络创建字符级语言模型。
  • 我们可以使用困惑度来评价语言模型的质量。

练习

1. 如果我们使用循环神经网络来预测文本序列中的下一个字符,那么任意输出所需的维度是多少?
解:
任意输出的维度=需要预测的文本序列总长度n,因为RNN的目的是预测下一个字符,所以需要为每个可能的字符提供一个概率值,因此,任意输出所需的维度为n。

2. 为什么循环神经网络可以基于文本序列中所有先前的词元,在某个时间步表示当前词元的条件概率?
解:
原因如下:

  • 隐状态 h_t 在每个时间步t都会接收前一时间隐状态 h_{t-1} 和当前输入 x_t 的信息,并更新,再循环传递下去,使得先前所有的词元信息得以循环保留。
  • RNN在处理序列的每个时间步时,都使用相同的权重矩阵,这意味着无论序列中的哪个位置,网络都会以相同的方式处理输入数据,但通过隐状态的更新,网络可以保持对不同时间步的敏感性。
  • 在训练过程中,通过反向传播算法,RNN能够计算损失函数关于每个时间步输入的梯度,这使得网络可以学习如何根据先前词元调整当前词元的预测。

3. 如果基于一个长序列进行反向传播,梯度会发生什么状况?
解:
可能出现梯度爆炸或梯度消失的问题,可以通过梯度裁剪、调整学习率、调整网络结构等方法解决。

4. 与本节中描述的语言模型相关的问题有哪些?
解:
本节中描述的根据一个单词的前几个字符预测下一个字符的语言模型,可以用于预测并纠正单词的拼写错误或者语法错误。 但如果给定的序列过长,也会有梯度消失或爆炸的问题;而且对于具有相同开头字符的单词的预测能力可能有限,比如probability和probably,如果输入是probab,则输出的困惑度可能较大。


http://www.ppmy.cn/devtools/111490.html

相关文章

go 语言常见问题(4)

31. go语言编程的好处是什么 编译和运行都很快。在语言层级支持并行操作。有垃圾处理器。内置字符串和 maps。函数是 go 语言的最基本编程单位。 32. 说说go语言的select机制 select 机制用来处理异步 IO 问题select 机制最大的一条限制就是每个 case 语句里必须是一个 IO 操…

Linux学习-Docker文件系统

Overlayfs Overlayfs 是一种类似 aufs 的一种堆叠文件系统,于 2014 年正式合入 Linux-3.18 主线内核,目前其功能已经基本稳定(虽然还存在一些特性尚未实现)且被逐渐推广。 Overlayfs 是一种堆叠文件系统,它依赖并建立…

芝法酱学习笔记(0.1)——Ubuntu下,Java开发环境的基本搭建

一、本章目标 书接上回,服务器已经安装完成,下面我们需要安装基本的开发环境。本次学习打算以Java后端开发为中心进行拓展,目前先安装Java开发、部署必备的一些软件。 基础部分 gcc gcc是c的编译软件,一些软件的安装包需要源码…

CORS跨域详解

目录 前言 快速说明 详细解释 Access-Control-Allow-Origin Access-Control-Allow-Credentials Access-Control-Allow-Headers Access-Control-Expose-Headers Access-Control-Allow-Methods Access-Control-Max-Age 前言 假设你已经了解服务端处理CORS跨域问题时,会…

Flink快速上手

Flink快速上手 批处理Maven配置pom文件java编写wordcount代码 有界流处理无界流处理 批处理 Maven配置pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://ww…

数据结构(6)哈希表和算法

一、哈希表 哈希表的基本概念 哈希函数&#xff1a; 哈希函数是将输入&#xff08;键&#xff09;转换为固定大小的输出&#xff08;哈希值&#xff09;的函数。这个输出通常是一个整数&#xff0c;表示在哈希表中的索引位置。理想的哈希函数应该能够均匀分布输入&#xff0c;…

华普微特殊“射频芯片”-隔离芯片品质卓越

HOPERF隔离器技术含量“超标”&#xff01; 作为信号链芯片中的重要一环&#xff0c;隔离芯片的主要作用就是在电力配电系统、工业自动化、医疗设备、可再生能源系统以及通信与数据传输等存在弱电控制强电的场景中实现电气隔离、信号传输与安全防护。 在弱电控制强电的过程中…

SprinBoot+Vue民宿预约微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

springboot 的共享session方案?

问&#xff1a;springboot 的共享session方案&#xff1f; 参考&#xff1a; https://juejin.cn/post/7195227930077691963分布式之session共享问题 4种解决方案及spring session的使用_分布式session共享方案-CSDN博客 什么是 Session &#xff1f; 答&#xff1a;因为Http协…

基于开源WQ装备数据的知识图谱全流程构建

随着大数据和人工智能技术的快速发展&#xff0c;构建领域特定的知识图谱已成为信息管理和决策支持的重要手段。武器装备知识图谱不仅能够对复杂的武器系统进行结构化展示&#xff0c;还可以通过关系推理揭示武器与装备之间的潜在联系。 1、技术路线 本文将详细介绍如何基于开…

数据库系列之GaussDB数据库中逻辑对象关系简析

初次接触openGauss或GaussDB数据库的逻辑对象&#xff0c;被其中的表空间、数据库、schema和用户之间的关系&#xff0c;以及授权管理困惑住了&#xff0c;与熟悉的MySQL数据库的逻辑对象又有明显的不同。本文旨在简要梳理下GaussDB数据库逻辑对象之间的关系&#xff0c;以加深…

python 函数 封装

封装 函数的参数是&#xff1a;变量 def 函数(参数):print(参数)if __name__ __main__:函数(参数)函数(参数2)函数的参数是&#xff1a; 字典 import requests# 定义一个字典 data {} 地址 "https://webdriveruniversity.com/" 请求方法 getdata["url"…

测试-Gatling 与性能测试

Gatling 与性能测试详解 一、什么是性能测试&#xff1f; 性能测试是一种软件测试类型&#xff0c;旨在评估系统在负载下的响应时间、吞吐量和资源利用率等性能指标。通过性能测试&#xff0c;开发者和运维团队能够识别出系统的瓶颈、优化系统性能&#xff0c;并确保其在实际…

JSON报文根据正则过滤消息

有时候业务系统在接收外部传过来的JSON报文&#xff0c;可能需要根据某个标识来判断是否是自己系统的消息&#xff0c;不是需要过滤。正常我们可能是先将JSON反序列化为具体实体类(例: A a JSON.parseObject(body,A.class))&#xff0c;然后获取具体字段来判断。此方法面对接收…

从0开始学习RocketMQ:领域模型

队列是先进先出&#xff08;FIFO&#xff09;的线性表&#xff0c;通常用链表或者数组来实现。队列只允许在后端&#xff08;称为 rear&#xff09;进行插入操作&#xff0c;在前端&#xff08;称为 front&#xff09;进行删除操作。 主流的消息中间件的传输模型主要为队列模型…

ARCGIS PRO DSK MapTool

MapTool用于自定义地图操作工具&#xff0c;使用户能够在ArcGIS Pro中执行特定的地图交互操作。添加 打开MapTool1.vb文件&#xff0c;可以看到系统已经放出MapTool1类&#xff1a; Public Sub New()将 IsSketchTool 设置为 true 以使此属性生效IsSketchTool TrueSketchTyp…

MySQL中的约束

约束概述 1.1 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是指数据的精确性&#xff08;Accuracy&#xff09;和可靠性&#xff08;Reliability&#xff09;。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信…

Python自带日志库实现springboot彩色效果

整体目标 涉及的库均为Python3自带库实现 loggingsysenum 终端显示彩色基本原理参考&#x1f449;Terminal里的颜色的那些事 Python打印日志可以直接借用logging自带的库实现&#xff0c;但是默认的打印实在太丑了&#xff0c;长下面这样 这只是一条日志看着还好比较清爽&…

elementUI中el-form 嵌套el-from 如何进行表单校验?

在el-form中嵌套另一个el-form进行表单校验和添加规则&#xff0c;首先&#xff0c;需要确保每个嵌套的el-form都有自己的model、rules和ref。 以下是一个简化的示例&#xff1a; <template><el-form :model"parentForm" :rules"parentRules" r…

Tomcat目录及测试

Tomcat目录及测试 C:\Program4java\apache-tomcat-10.1.7 这个目录下直接包含Tomcat的bin目录&#xff0c;conf目录等&#xff0c;我们称之为Tomcat的安装目录或根目录。 bin&#xff1a;该目录下存放的是二进制可执行文件&#xff0c;如果是安装版&#xff0c;那么这个目录下会…