首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《动手学深度学习》读书笔记—9.3深度循环神经网络

《动手学深度学习》读书笔记—9.3深度循环神经网络

原创
作者头像
用户11764705
发布2025-08-04 21:19:07
发布2025-08-04 21:19:07
1830
举报

本文记录了自己在阅读《动手学深度学习》时的一些思考,仅用来作为作者本人的学习笔记,不存在商业用途。

到目前为止,我们只讨论了具有一个单向隐藏层的循环神经网络。 其中,隐变量和观测值与具体的函数形式的交互方式是相当随意的。 只要交互类型建模具有足够的灵活性,这就不是一个大问题。 然而,对一个单层来说,这可能具有相当的挑战性。 在线性模型中,我们通过添加更多的层来解决这个问题。 而在循环神经网络中,我们首先需要确定如何添加更多的层, 以及在哪里添加额外的非线性,因此这个问题有点棘手。

事实上,我们可以将多层循环神经网络堆叠在一起, 通过对几个简单层的组合,产生了一个灵活的机制。 特别是,数据可能与不同层的堆叠有关。 例如,我们可能希望保持有关金融市场状况 (熊市或牛市)的宏观数据可用, 而微观数据只记录较短期的时间动态。

下图展示了一个具有L 个隐藏层的深度循环神经网络,每个隐状态都连续地传递到当前层的下一个时间步和下一层的当前时间步。

9.3.1 函数依赖关系

我们可以将深度架构中的函数依赖关系形式化,这个架构是由上图中描述了L个隐藏层构成的。后续的讨论主要集中在经典的循环神经网络模型上, 但是这些讨论也适应于其他序列模型。

假设在时间步t 有一个小批量的输入数据 \mathbf{X}_t \in \mathbb{R}^{n \times d} (样本数:n ,每个样本中的输入数:d )。 同时,将l^{th} 隐藏层(l=1,...,L ) 的隐状态设为\mathbf{H}_t^{(l)} \in \mathbb{R}^{n \times h} (隐藏单元数:h ), 输出层变量设为\mathbf{O}_t \in \mathbb{R}^{n \times q} (输出数:q )。 设置\mathbf{H}_t^{(0)} = \mathbf{X}_t , 第l 个隐藏层的隐状态使用激活函数\phi_l ,则:

\mathbf{H}_t^{(l)} = \phi_l(\mathbf{H}_t^{(l-1)} \mathbf{W}_{xh}^{(l)} + \mathbf{H}_{t-1}^{(l)} \mathbf{W}_{hh}^{(l)} + \mathbf{b}_h^{(l)}),

其中,权重\mathbf{W}_{hq} \in \mathbb{R}^{h \times q} 和偏置\mathbf{b}_q \in \mathbb{R}^{1 \times q} 都是第l 个隐藏层的模型参数。

最后,输出层的计算仅基于第l 个隐藏层最终的隐状态:

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

其中,权重\mathbf{W}_{hq} \in \mathbb{R}^{h \times q} 和偏置\mathbf{b}_q \in \mathbb{R}^{1 \times q} 都是输出层的模型参数。

与多层感知机一样,隐藏层数目

和隐藏单元数目

都是超参数。 也就是说,它们可以由我们调整的。 另外,用门控循环单元\mathbf{H}_t = \mathbf{Z}_t \odot \mathbf{H}_{t-1} + (1 - \mathbf{Z}_t) \odot \tilde{\mathbf{H}}_t 或长短期记忆网络的隐状态\mathbf{H}_t = \mathbf{O}_t \odot \tanh(\mathbf{C}_t) 来代替\mathbf{H}_t^{(l)} = \phi_l(\mathbf{H}_t^{(l-1)} \mathbf{W}_{xh}^{(l)} + \mathbf{H}_{t-1}^{(l)} \mathbf{W}_{hh}^{(l)} + \mathbf{b}_h^{(l)}) 中的隐状态进行计算, 可以很容易地得到深度门控循环神经网络或深度长短期记忆神经网络。

9.3.2 简洁实现

实现多层循环神经网络所需的许多逻辑细节在高级API中都是现成的。简单起见,我们仅示范使用此类内置函数的实现方式。以长短期记忆网络模型为例,该代码与长短期记忆网络一节中的代码非常相似,实际上唯一的区别是我们指定了层的数量,而不是使用单一层这个默认值。仍然先加载数据集

代码语言:python
复制
import torch
from torch import nn
from d2l import torch as d2l

# 批量大小为32(每个批量使用32个子序列), 时间长度为35(子序列的长度为35)
batch_size, num_steps = 32, 35
# 读取时间机器数据集
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

选择超参数这类架构决策也跟长短期记忆网络一节中的决策非常相似,唯一的区别是现在通过num_layers的值来设定隐藏层数。

nn.LSTM函数

代码语言:python
复制
# 获得词表大小, 隐藏层维数为256, 隐藏层数量为2
vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
# 输入维数是词表大小
num_inputs = vocab_size
# 尝试使用GPU, 失败了就使用CPU
device = d2l.try_gpu()
# 设置长短期记忆网络lstm层
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers)
# 构建模型, rnn_layer选择lstm_layer, vocab_size赋值为len(vocab)
model = d2l.RNNModel(lstm_layer, len(vocab))
# 尝试将模型转移到GPU, 失败了就使用CPU
model = model.to(device)

补充一下d2L.RNNModel函数(在简洁实现RNN中使用过)

代码语言:python
复制
#@save
class RNNModel(nn.Module):
    """循环神经网络模型"""
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.num_hiddens = self.rnn.hidden_size
        # 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1
        if not self.rnn.bidirectional:
            self.num_directions = 1
            # 线性层的输入是隐藏层维数,输出是词表大小
            self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
        else:
            self.num_directions = 2
            # 线性层的输入是隐藏层维数,输出是词表大小
            self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)

    def forward(self, inputs, state):
        # 输入X从批量大小*时间长度变为时间长度*批量大小*词表大小
        X = F.one_hot(inputs.T.long(), self.vocab_size)
        # 将独热编码转换为float32类型
        X = X.to(torch.float32)
        # 将输入和初始状态放入rnn中得到各个时间步的隐状态Y和最后一个时间步的隐状态state
        # Y的尺寸:时间长度*批量大小*隐藏层维数, state的尺寸:1*批量大小*隐藏层维数
        Y, state = self.rnn(X, state)
        # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
        # 输出output的形状是(时间步数*批量大小,词表大小)。
        # output和从零开始实现是一样的,dim1是词表大小,表示输入的每个词元产生的输出属于词表中不同词元的概率(可以看成概率)
        # 调用predict_ch8获得每个输入产生的最大输出的索引,去词表中查找该索引对应的词元作为输出词元
        output = self.linear(Y.reshape((-1, Y.shape[-1])))
        return output, state

    def begin_state(self, device, batch_size=1):
        if not isinstance(self.rnn, nn.LSTM):
            # nn.GRU以张量作为隐状态
            return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                 batch_size, self.num_hiddens),
                                device=device)
        else:
            # nn.LSTM以元组作为隐状态
            return (torch.zeros((
                self.num_directions * self.rnn.num_layers,
                batch_size, self.num_hiddens), device=device),
                    torch.zeros((
                        self.num_directions * self.rnn.num_layers,
                        batch_size, self.num_hiddens), device=device))

9.3.3 训练与预测

由于使用了长短期记忆网络模型来实例化两个层,因此训练速度被大大降低了。

代码语言:python
复制
# 训练轮次500轮, 学习率2
num_epochs, lr = 500, 2
# 训练
d2l.train_ch8(model, train_iter, vocab, lr*1.0, num_epochs, device)
训练结果
训练结果

总结

  1. 在深度循环神经网络中,隐状态的信息被传递到当前层的下一时间步和下一层的当前时间步。
  2. 有许多不同风格的深度循环神经网络, 如长短期记忆网络、门控循环单元、或经典循环神经网络。 这些模型在深度学习框架的高级API中都有涵盖。
  3. 总体而言,深度循环神经网络需要大量的调参(如学习率和修剪) 来确保合适的收敛,模型的初始化也需要谨慎。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 9.3.1 函数依赖关系
  • 9.3.2 简洁实现
  • 9.3.3 训练与预测
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档