《动手学深度学习(PyTorch版)》笔记8.4

时间:2024-03-02 11:56:24

注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过,同时对于书上部分章节也做了整合。

Chapter8 Recurrent Neural Networks

8.4 Recurrent Neural Networks

n n n元语法模型,单词 x t x_t xt在时间步 t t t的条件概率仅取决于前面 n − 1 n-1 n1个单词。对于时间步 t − ( n − 1 ) t-(n-1) t(n1)之前的单词,如果我们想将其可能产生的影响合并到 x t x_t xt上,需要增加 n n n,然而模型参数的数量也会随之呈指数增长,因为词表 V \mathcal{V} V需要存储 ∣ V ∣ n |\mathcal{V}|^n Vn个数字(每个可能的序列对应一个概率值),因此不如使用隐变量模型:

P ( x t ∣ x t − 1 , … , x 1 ) ≈ P ( x t ∣ h t − 1 ) , P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}), P(xtxt1,,x1)P(xtht1),

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

h t = f ( x t , h t − 1 ) . h_t = f(x_{t}, h_{t-1}). ht=f(xt,ht1).

对于上式中的函数 f f f,隐变量模型可以不是近似值,因为 h t h_t ht可以存储到目前为止观察到的所有数据,然而这样的操作可能会使计算和存储的代价都变得昂贵。值得注意的是,隐藏层和隐状态是两个不同的概念,隐藏层是在从输入到输出的路径上(以观测角度来理解)的隐藏的层,而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入,并且这些状态只能通过先前时间步的数据来计算。

8.4.1 Neural Networks without Hidden States

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

H = ϕ ( X W x h + b h ) . \mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{xh} + \mathbf{b}_h). H=ϕ(XWxh+bh).

输出层由下式给出:

O = H W h q + b q , \mathbf{O} = \mathbf{H} \mathbf{W}_{hq} + \mathbf{b}_q, O=HWhq+bq,

8.4.2 RNN with Hidden States

假设我们在时间步 t t t有小批量输入 X t ∈ R n × d \mathbf{X}_t \in \mathbb{R}^{n \times d} XtRn×d,换言之,对于 n n n个序列样本的小批量, X t \mathbf{X}_t Xt的每一行对应于来自该序列的时间步 t t t处的一个样本。用 H t ∈ R n × h \mathbf{H}_t \in \mathbb{R}^{n \times h} HtRn×h表示时间步 t t t的隐藏变量,计算公式如下:

H t = ϕ ( X t W x h + H t − 1 W h h + b h ) . (1) \mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h).\tag{1} Ht=ϕ(XtWxh+Ht1Whh+bh).(1)

由于在当前时间步中,隐状态使用的定义与前一个时间步中使用的定义相同,因此称式(1)的计算是循环的(recurrent),基于循环计算的隐状态神经网络称为循环神经网络(recurrent neural network),在循环神经网络中执行循环计算的层称为循环层(recurrent layer)。对于时间步 t t t,输出层的输出类似于多层感知机中的计算:

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

下图展示了循环神经网络在三个相邻时间步的计算逻辑。

在这里插入图片描述

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)
torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))

8.4.3 Character-Level Language Models Based on RNN

接下来介绍如何使用循环神经网络来构建语言模型。设小批量大小为1,批量中的文本序列为"machine"。为了简化后续部分的训练,考虑使用字符级语言模型(character-level language model),将文本词元化为字符而不是单词。下图演示了如何通过基于字符级语言建模的循环神经网络,使用当前的和先前的字符预测下一个字符。

在这里插入图片描述

在实践中,我们使用的批量大小为 n > 1 n>1 n>1,每个词元都由一个 d d d维向量表示。因此,在时间步 t t t输入 X t \mathbf X_t Xt将是一个 n × d n\times d n×