深度学习------------------------RNN(循环神经网络)

目录

  • 潜变量自回归模型
  • 循环神经网络
  • 困惑度
  • 梯度剪裁
  • 循环神经网络的从零开始实现
    • 初始化循环神经网络模型的模型参数
    • 初始化隐藏状态
    • 创建一个类来包装这些函数
      • 该部分总代码
    • 定义预测函数
      • 该部分总代码
    • 梯度裁剪
    • 定义一个函数在一个迭代周期内训练模型
    • 训练函数
  • 循环神经网络的简洁实现
    • 加载数据集
    • 定义模型
    • 定义RNNModel类
    • 训练与预测
      • 该部分总代码
    • 使用高级API训练模型
    • 总代码

第一个对序列模型的神经网络叫做循环神经网络

潜变量自回归模型

使用潜变量 h t h_t ht总结过去信息。

在这里插入图片描述


在这里插入图片描述

解释 x t x_t xt与当前的 h t h_t ht x t − 1 x_{t-1} xt1相关。
       h t h_t ht x t − 1 x_{t-1} xt1 h t − 1 h_{t-1} ht1相关




循环神经网络

在这里插入图片描述




在这里插入图片描述

解释 o t o_t ot是根据 h t h_t ht输出的, h t h_t ht不能用 x t x_t xt的东西,用的是 x t − 1 x_{t-1} xt1



在这里插入图片描述

解释:假设我的观察是""的话,那么会更新我的隐变量,然后预测""字( o t o_t ot这行的)。接下来观察到了""( x t x_t xt这行的)。那么去更新我的下一个隐变量,再输出下一个逗号,依次类推。

o t o_t ot是用来match到你的 x t x_t xt的输入,但在生成 o t o_t ot的时候,不能看到 x t x_t xt。即:当前时刻的输出是预测当前时刻的观察,但是输出发生在观察之前。( o t o_t ot是根据 h t h_t ht输出的,但 h t h_t ht用的是 x t − 1 x_{t-1} xt1的东西,然后再计算损失的时候,比较 o t o_t ot x t x_t xt之间的关系计算损失。 x t x_t xt是用来更新 h t h_t ht使它挪到下一个单元。)

在这里插入图片描述
在这里插入图片描述
W h h W_{hh} Whh R h × h R^{h×h} Rh×h用来描述如何在当前时间步中使用前一个时间步的隐藏变量。(最简单的RNN通过 W h h W_{hh} Whh来储存时序信息的。这是和MLP的区别。)
在这里插入图片描述




困惑度

衡量一个语言模型的好坏可以用平均交叉熵

在这里插入图片描述




梯度剪裁

在这里插入图片描述
在这里插入图片描述




循环神经网络的从零开始实现

import torch
from d2l import torch as d2l
from torch.nn import functional as Fbatch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
# torch.tensor([0, 2])是一个长为2的向量,加入len(vocab)会添加一个维度,那么结果就是2*len(vocab)
# 下标0在第零个词中的第一个位置置为1,下标1在此词中的第三个位置置为1
print(F.one_hot(torch.tensor([0, 2]), len(vocab)))

在这里插入图片描述


小批量数据形状是(批量大小,时间步数)

# 先构造一个X重构成批量大小为2,时间步数是5
X = torch.arange(10).reshape((2, 5))
# 转置的目的调换第一维度和第二维度,调换后第一个维度是时间步数、第二个维度是批量大小、第三个维度是特征长度
# 每个时间步(第一个参数)都有一个Xt,就是第二个参数和第三个参数
print(F.one_hot(X.T, 28).shape)

初始化循环神经网络模型的模型参数

# 初始化循环神经网络模型的模型参数
def get_params(vocab_size, num_hiddens, device):# 设置输入和输出的维度为词汇表大小num_inputs = num_outputs = vocab_size# 定义normal函数用于生成服从正态分布的随机张量,并乘以0.01进行缩放def normal(shape):return torch.randn(size=shape, device=device) * 0.01# 初始化模型参数# 输入到隐藏层的权重矩阵,形状为(词汇表大小, 隐藏单元个数)W_xh = normal((num_inputs, num_hiddens))# 隐藏层到隐藏层的权重矩阵,形状为(隐藏单元个数, 隐藏单元个数)W_hh = normal((num_hiddens, num_hiddens))# 隐藏层的偏置向量,形状为(隐藏单元个数,)b_h = torch.zeros(num_hiddens, device=device)# 隐藏层到输出层的权重矩阵,形状为(隐藏单元个数, 词汇表大小)W_hq = normal((num_hiddens, num_outputs))# 输出层的偏置向量,形状为(词汇表大小,)b_q = torch.zeros(num_outputs, device=device)# 将所有参数放入列表中params = [W_xh, W_hh, b_h, W_hq, b_q]# 遍历所有参数for param in params:# 设置参数的requires_grad为True,用于梯度计算param.requires_grad_(True)# 返回模型的参数return params

初始化隐藏状态

一个init_rnn_state函数在初始化时返回隐藏状态。

# 用于在0时刻是给定一个初始化的隐状态
def init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device),)



RNN函数定义了如何在一个时间步内计算隐藏状态和输出。

# 下面的rnn函数定义了如何在一个时间步计算隐藏状态和输出
# 第一个参数是给定的输入X,X里包括了所有的时间步X0、X1、X2、...XT。共T个样本
def rnn(inputs, state, params):# 从参数元组中解包获取输入到隐藏层的权重矩阵 W_xh,# 隐藏层到隐藏层的权重矩阵 W_hh,# 隐藏层的偏置向量 b_h,# 隐藏层到输出层的权重矩阵 W_hq,# 输出层的偏置向量 b_qW_xh, W_hh, b_h, W_hq, b_q = params# 从状态元组中解包获取隐藏状态 H# 注意这里使用逗号是为了确保 H 为一个元组H, = state# 创建一个空列表用于存储输出outputs = []# 沿着第一维度进行遍历,inputs的形状:(时间步数量,批量大小,词表大小)for X in inputs:# 首先拿到的是时刻0对应的X,就是批量大小×词表大小# 计算新的隐藏状态 H,使用双曲正切函数作为激活函数# 根据当前输入 X、上一时间步的隐藏状态 H、以及权重矩阵和偏置向量来计算H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# 计算输出 Y,通过隐藏状态 H 与权重矩阵 W_hq 相乘并加上偏置向量 b_q 得到# Y是当前时刻的预测,在当前时刻预测下一个时刻的那个词是谁。# 把所有时刻的输出放在outputs里面Y = torch.mm(H, W_hq) + b_q# 将输出 Y 添加到输出列表中outputs.append(Y)# 输出:对于所有的Y之前每一个时刻的输出就是一个批量大小×vocab_size的一个东西(就是对每一个样本预测的那个向量就是一个vocab长度的向量)# 将输出列表中的输出张量沿着行维度进行拼接,得到一个形状为 (时间步数 * 批量大小, 输出维度) 的张量# 假设Y的形状为(1,Q),那么outputs 为 [(1,Q), (1,Q), ...]共T个,所以拼接后的形状为(T*批量大小,Q)# 返回拼接后的输出张量和最后一个时间步的隐藏状态 Hreturn torch.cat(outputs, dim=0), (H,)



说明一下cat方法

import torch# 第一个批次
batch1 = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(batch1)
# 第二个批次
batch2 = torch.tensor([[7, 8, 9], [10, 11, 12]])
print(batch2)
# 沿着第一个维度(即批次维度)连接这两个批次
combined_batch = torch.cat([batch1, batch2], dim=0)
print(combined_batch)



创建一个类来包装这些函数

# 创建一个类来包装这些函数
class RNNModelScratch:# 初始化模型参数def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):# 保存词汇表大小和隐藏单元个数作为类的属性self.vocab_size, self.num_hiddens = vocab_size, num_hiddens# 调用 get_params 函数初始化模型的参数,并保存为类的属性# 参数包括输入到隐藏层的权重矩阵、隐藏层到隐藏层的权重矩阵、隐藏层的偏置向量、隐藏层到输出层的权重矩阵、输出层的偏置向量self.params = get_params(vocab_size, num_hiddens, device)# 初始化隐藏状态的函数和前向传播函数self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):# 将输入序列 X 进行独热编码,形状为 (时间步数, 批量大小, 词汇表大小)# 并将数据类型转换为浮点型X = F.one_hot(X.T, self.vocab_size).type(torch.float32)# 调用前向传播函数进行模型计算,并返回输出return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):# 返回初始化的隐藏状态,用于模型的初始时间步return self.init_state(batch_size, self.num_hiddens, device)

当调用 predict_ch8 函数时,它会生成一系列的输入(通过 get_input 函数),这些输入被传递给 net(即 RNNModelScratch 的实例)。net 的 call 方法随后被调用,并且 X(在这里是通过 get_input 函数生成的独热编码张量)和当前的隐藏状态 state 作为参数传入。

输入数据 X  ↓  ↓  
One-Hot 编码 (vocab_size=28)  ↓  ↓  
转换为 float32  ↓  
前向传播函数 self.forward_fn  ↓  
输出结果

该部分总代码

import random
import torch
from d2l import torch as d2l
from torch.nn import functional as Fdef get_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device) * 0.01W_xh = normal((num_inputs, num_hiddens))W_hh = normal((num_hiddens, num_hiddens))b_h = torch.zeros(num_hiddens, device=device)W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return paramsdef init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device),)def rnn(inputs, state, params):W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)Y = torch.mm(H, W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)class RNNModelScratch:def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
X = torch.arange(10).reshape((2, 5))
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)
# new_state[0].shape的参数是[批量大小,隐藏源]
print(Y.shape, len(new_state), new_state[0].shape)

在这里插入图片描述

定义预测函数

def predict_ch8(prefix, num_preds, net, vocab, device):"""在 'prefix' 后面生成新字符。"""# 获取模型的初始隐藏状态,批量大小为 1(对一个字符串做预测),设备为指定的设备state = net.begin_state(batch_size=1, device=device)# 将 prefix 的第一个字符索引添加到输出列表中outputs = [vocab[prefix[0]]]# 定义一个函数 get_input,用于获取输入序列的张量表示# 输入序列只包含一个字符,将该字符的索引转换为张量,并进行形状调整为 (1, 1)get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(1, 1)# 对于 prefix 中除第一个字符之外的每个字符 yfor y in prefix[1:]:# 使用当前输入字符和隐藏状态进行前向传播计算,得到输出和更新后的隐藏状态_, state = net(get_input(), state)# 将字符 y 的索引添加到输出列表中outputs.append(vocab[y])# 生成指定数量的新字符for _ in range(num_preds):# 使用当前输入字符和隐藏状态进行前向传播计算,得到输出和更新后的隐藏状态y, state = net(get_input(), state)# 将输出张量中概率最大的字符索引添加到输出列表中outputs.append(int(y.argmax(dim=1).reshape(1)))# 将输出列表中的字符索引转换为对应的字符,并拼接成一个字符串返回return ''.join([vocab.idx_to_token[i] for i in outputs])




解释代码:

    for y in prefix[1:]:# 这里第一次调用使用的是prefix[0]的数据,并且和其隐藏状态丢进net模型,但是不关心输出,只是用来初始化状态。# 假设给出一段词(你好)那么先用"你"来预测"好"的时候不需要把预测存储下来,因为已经告诉标准答案了。唯一干的事情就是通过你好来初始化状态(隐变量),把prefix里面的信息放到state里面,outputs里面是用的真实的prefix而不是用的预测。(这样就不用累计误差了)_, state = net(get_input(), state)# 将字符 y 的索引添加到输出列表中# outputs是用的真实的那个prefix,而不是预测(这样就不会累计误差了)outputs.append(vocab[y])

在这里插入图片描述
把所有的前缀存在state里面后,就可以真正的预测了,做num_preds次。

 for _ in range(num_preds):# 使用当前输入字符和隐藏状态进行前向传播计算,得到输出和更新后的隐藏状态# 每一次把前一个时刻的预测做成输入并更新state拿到输出yy, state = net(get_input(), state)# .argmax(dim=1) 方法在指定的维度(这里是第二维,即类别维度)上查找最大值的索引。y的形状为[批量大小,分类数],# 将输出张量中概率最大的字符索引添加到输出列表中outputs.append(int(y.argmax(dim=1).reshape(1)))

解释结束



定义预测函数来生成prefix之后的新字符

# 生成以 'time traveller ' 为前缀的 10 个新字符
# 注意:由于模型尚未训练,这里的预测结果是随机初始化后的预测
print(predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()))

在这里插入图片描述


该部分总代码

import random
import torch
from d2l import torch as d2l
from torch.nn import functional as F# 初始化循环神经网络模型的模型参数
def get_params(vocab_size, num_hiddens, device):# 设置输入和输出的维度为词汇表大小num_inputs = num_outputs = vocab_size# 定义normal函数用于生成服从正态分布的随机张量,并乘以0.01进行缩放def normal(shape):return torch.randn(size=shape, device=device) * 0.01# 初始化模型参数# 输入到隐藏层的权重矩阵,形状为(词汇表大小, 隐藏单元个数)W_xh = normal((num_inputs, num_hiddens))# 隐藏层到隐藏层的权重矩阵,形状为(隐藏单元个数, 隐藏单元个数)# 上一时刻的隐藏变量变换到下一时刻的隐藏变量,上一时刻的隐藏变量和下一时刻的隐藏变量长度是一样的W_hh = normal((num_hiddens, num_hiddens))# 隐藏层的偏置向量,形状为(隐藏单元个数,)b_h = torch.zeros(num_hiddens, device=device)# 隐藏层到输出层的权重矩阵,形状为(隐藏单元个数, 词汇表大小)W_hq = normal((num_hiddens, num_outputs))# 输出层的偏置向量,形状为(词汇表大小,)b_q = torch.zeros(num_outputs, device=device)# 将所有参数放入列表中params = [W_xh, W_hh, b_h, W_hq, b_q]# 遍历所有参数for param in params:# 设置参数的requires_grad为True,用于梯度计算param.requires_grad_(True)# 返回模型的参数return params# 为什么要初始化隐藏状态?因为0时刻的时候没有上一刻的隐藏状态。
# 该函数用于在0时刻给定一个初始化的隐藏状态
def init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device),)# 下面的rnn函数定义了如何在一个时间步计算隐藏状态和输出
# 第一个参数是给定的输入X,X里包括了所有的时间步X0、X1、X2、...XT。
def rnn(inputs, state, params):# 从参数元组中解包获取输入到隐藏层的权重矩阵 W_xh,# 隐藏层到隐藏层的权重矩阵 W_hh,# 隐藏层的偏置向量 b_h,# 隐藏层到输出层的权重矩阵 W_hq,# 输出层的偏置向量 b_qW_xh, W_hh, b_h, W_hq, b_q = params# 从状态元组中解包获取隐藏状态 H# 注意这里使用逗号是为了确保 H 为一个元组H, = state# 创建一个空列表用于存储输出outputs = []# 沿着第一维度进行遍历for X in inputs:# 计算新的隐藏状态 H,使用双曲正切函数作为激活函数# 根据当前输入 X、上一时间步的隐藏状态 H、以及权重矩阵和偏置向量来计算H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# 计算输出 Y,通过隐藏状态 H 与权重矩阵 W_hq 相乘并加上偏置向量 b_q 得到# Y是当前时刻的预测,在当前时刻预测下一个时刻的那个词是谁。Y = torch.mm(H, W_hq) + b_q# 将输出 Y 添加到输出列表中outputs.append(Y)# 将输出列表中的输出张量沿着行维度进行拼接,得到一个形状为 (时间步数 * 批量大小, 输出维度) 的张量# 假设Y的形状为(1,Q),那么outputs 为 [(1,Q), (1,Q), ...]共T个,所以拼接后的形状为(T*批量大小,Q)# 返回拼接后的输出张量和最后一个时间步的隐藏状态 Hreturn torch.cat(outputs, dim=0), (H,)# 创建一个类来包装这些函数
class RNNModelScratch:# 初始化模型参数def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):# 保存词汇表大小和隐藏单元个数作为类的属性self.vocab_size, self.num_hiddens = vocab_size, num_hiddens# 调用 get_params 函数初始化模型的参数,并保存为类的属性# 参数包括输入到隐藏层的权重矩阵、隐藏层到隐藏层的权重矩阵、隐藏层的偏置向量、隐藏层到输出层的权重矩阵、输出层的偏置向量self.params = get_params(vocab_size, num_hiddens, device)# 初始化隐藏状态的函数和前向传播函数self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):# 将输入序列 X 进行独热编码,形状为 (时间步数, 批量大小, 词汇表大小)# 并将数据类型转换为浮点型X = F.one_hot(X.T, self.vocab_size).type(torch.float32)# 调用前向传播函数进行模型计算,并返回输出return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):# 返回初始化的隐藏状态,用于模型的初始时间步# 初始化状态:批量大小为32,隐藏层数量为512return self.init_state(batch_size, self.num_hiddens, device)# 首先定义预测函数来生成用户提供的prefix之后的新字符
def predict_ch8(prefix, num_preds, net, vocab, device):"""在 'prefix' 后面生成新字符。"""# 获取模型的初始隐藏状态,批量大小为 1,设备为指定的设备state = net.begin_state(batch_size=1, device=device)# 将第一个字符(prefix[0])放到vocab里面拿到对应的整型的下标放到outputs里outputs = [vocab[prefix[0]]]# 定义一个函数 get_input,用于获取输入序列的张量表示# 把output最后一个词存下来,当预测完的那个最近预测的词,最近预测的那个词作为下一个预测的输入# 输入序列只包含一个字符,将该字符的索引转换为张量,并进行形状调整为 (1, 1)get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(1, 1)# 对于 prefix 中除第一个字符之外的每个字符 yfor y in prefix[1:]:# 使用当前输入字符和隐藏状态进行前向传播计算,得到输出和更新后的隐藏状态_, state = net(get_input(), state)# 将当前字符youtputs.append(vocab[y])# 生成指定数量的新字符for _ in range(num_preds):# 使用当前输入字符和隐藏状态进行前向传播计算,得到输出和更新后的隐藏状态y, state = net(get_input(), state)# 将输出张量中概率最大的字符索引添加到输出列表中outputs.append(int(y.argmax(dim=1).reshape(1)))# 将输出列表中的字符索引转换为对应的字符,并拼接成一个字符串返回return ''.join([vocab.idx_to_token[i] for i in outputs])batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
X = torch.arange(10).reshape((2, 5))# 检查输出是否具有正确的形状
# 设置隐藏单元个数为 512
num_hiddens = 512
# 创建一个 RNNModelScratch 的实例 net,指定词汇表大小、隐藏单元个数、设备、获取参数函数、初始化隐藏状态函数和前向传播函数
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
# 获取模型的初始隐藏状态,输入的批量大小为 X 的行数,设备使用与 X 相同的设备
state = net.begin_state(X.shape[0], d2l.try_gpu())
# 使用输入 X 和初始隐藏状态进行前向传播计算,得到输出张量 Y 和更新后的隐藏状态 new_state
# 将输入和状态都移动到与 X 相同的设备上进行计算
Y, new_state = net(X.to(d2l.try_gpu()), state)
# 输出 Y 的形状,new_state 的长度(即元素个数)和 new_state 中第一个元素的形状
print(Y.shape, len(new_state), new_state[0].shape)
# 生成以 'time traveller ' 为前缀的 10 个新字符
# 注意:由于模型尚未训练,这里的预测结果是随机初始化后的预测
print(predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()))
import random
import torch
from d2l import torch as d2l
from torch.nn import functional as F# 28,512
def get_params(vocab_size, num_hiddens, device):# 输入和输出都是28num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device) * 0.01W_xh = normal((num_inputs, num_hiddens)) # (vocab_size,num_hiddens)➡(28,512)W_hh = normal((num_hiddens, num_hiddens)) # (num_hiddens, num_hiddens)➡(512,512)b_h = torch.zeros(num_hiddens, device=device) # (num_hiddens,)➡(512,)W_hq = normal((num_hiddens, num_outputs)) # (num_hiddens, vocab_size)➡(512, 28)b_q = torch.zeros(num_outputs, device=device) # (vocab_size,)➡(28,)params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params# 32,512
def init_rnn_state(batch_size, num_hiddens, device):# 生成一个32行512列的零矩阵return (torch.zeros((batch_size, num_hiddens), device=device),)def rnn(inputs, state, params):W_xh, W_hh, b_h, W_hq, b_q = params# H的形状为(batch_size, num_hiddens)➡(32,512)H, = stateoutputs = []# X的形状(batch_size, vocab_size)➡(32,28)for X in inputs:#  (32,28)×(28,512)=(32,512)和(32,512)×(512,512)=(32,512)以及(512)相加的时候对b_h进行广播# 广播到形状 (batch_size, num_hiddens),其中 b_h 中的每个元素都会被复制到 batch_size 次,以便与二维张量的每一行相加。# ∴H的形状为(batch_size, num_hiddens)➡(32,512)H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# (32,512)×(512, 28)=(32,28)和(28,)# Y的形状为(batch_size, vocab_size)➡(32,28)Y = torch.mm(H, W_hq) + b_q# ⭐ outputs是一个列表不是一个张量,但每个元素的维度都是(batch_size, vocab_size)outputs.append(Y)# 按照输入顺序连接,输出的维度是(num_steps * batch_size, vocab_size)return torch.cat(outputs, dim=0), (H,)class RNNModelScratch:# 初始化模型参数def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)def predict_ch8(prefix, num_preds, net, vocab, device):"""在 'prefix' 后面生成新字符。"""#state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]# 预测的时候一次只处理一个字符,[outputs[-1]]是最后一个元素的数据(不是索引),重构为形状(1,1)第一个维度是批量大小,第二个维度是索引数# lambda 函数被用来创建一个匿名函数,这个函数没有参数get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(1, 1)for y in prefix[1:]:# state的形状为(1, num_hiddens)➡(1, 512)_, state = net(get_input(), state)outputs.append(vocab[y])# 预测十个字符:(num_preds)for _ in range(num_preds):# y的形状为(1, vocab_size)➡(1,28)y, state = net(get_input(), state)# outputs是存储整个生成序列的索引,y.argmax(dim=1)将返回每个样本中概率最高的词(这里是一个字符)的索引。outputs.append(int(y.argmax(dim=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])batch_size, num_steps = 32, 35
num_hiddens = 512
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn)
print(predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()))

在这里插入图片描述




梯度裁剪

在这里插入图片描述

def grad_clipping(net, theta):"""裁剪梯度。"""# 如果 net 是 nn.Module 的实例(即使用 PyTorch 构建的模型)if isinstance(net, nn.Module):# 获取所有需要计算梯度的参数列表params = [p for p in net.parameters() if p.requires_grad]# 如果 net 是自定义的模型(例如上述的 RNNModelScratch)else:# 获取自定义模型的参数列表params = net.params# 计算参数梯度的范数,即所有参数梯度平方和的平方根norm = torch.sqrt(sum(torch.sum((p.grad**2)) for p in params))# 如果梯度范数超过指定阈值 thetaif norm > theta:# 对于每个参数for param in params:# 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 thetaparam.grad[:] *= theta / norm



定义一个函数在一个迭代周期内训练模型

#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)  # 训练损失之和,词元数量for X, Y in train_iter:if state is None or use_random_iter:# 在第一次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):# state对于nn.GRU是个张量state.detach_() # 读取新的iter后,将隐状态从计算图中分离出来,以避免不必要的梯度计算,从而提高效率和减少内存使用else:# state对于nn.LSTM或对于我们从零开始实现的模型是个元组,每个元素是一个张量,需要遍历每个元素for s in state:s.detach_()# 把Y的形状转为(时间步数×批量大小,词表大小)y = Y.T.reshape(-1)X, y = X.to(device), y.to(device)y_hat, state = net(X, state) # 前向传播l = loss(y_hat, y.long()).mean()if isinstance(updater, torch.optim.Optimizer):updater.zero_grad()l.backward()# 在参数更新前进行梯度裁剪grad_clipping(net, 1)updater.step()else:l.backward()grad_clipping(net, 1)# 因为已经调用了mean函数updater(batch_size=1)metric.add(l * y.numel(), y.numel())# 输出 困惑度,运行速度return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()



训练函数

循环神经网络模型的训练函数即支持从零开始实现,也可以使用高级API来实现。

import math
import torch
from torch import nn
from d2l import torch as d2l
from torch.nn import functional as F# 28,512
def get_params(vocab_size, num_hiddens, device):# 输入和输出都是28num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device) * 0.01W_xh = normal((num_inputs, num_hiddens))  # (vocab_size,num_hiddens)➡(28,512)W_hh = normal((num_hiddens, num_hiddens))  # (num_hiddens, num_hiddens)➡(512,512)b_h = torch.zeros(num_hiddens, device=device)  # (num_hiddens,)➡(512,)W_hq = normal((num_hiddens, num_outputs))  # (num_hiddens, vocab_size)➡(512, 28)b_q = torch.zeros(num_outputs, device=device)  # (vocab_size,)➡(28,)params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params# 32,512
def init_rnn_state(batch_size, num_hiddens, device):# 生成一个32行512列的零矩阵return (torch.zeros((batch_size, num_hiddens), device=device),)def rnn(inputs, state, params):W_xh, W_hh, b_h, W_hq, b_q = params# H的形状为(batch_size, num_hiddens)➡(32,512)H, = stateoutputs = []# X的形状(batch_size, vocab_size)➡(32,28)for X in inputs:#  (32,28)×(28,512)=(32,512)和(32,512)×(512,512)=(32,512)以及(512)相加的时候对b_h进行广播# 广播到形状 (batch_size, num_hiddens),其中 b_h 中的每个元素都会被复制到 batch_size 次,以便与二维张量的每一行相加。# ∴H的形状为(batch_size, num_hiddens)➡(32,512)H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# (32,512)×(512, 28)=(32,28)和(28,)# Y的形状为(batch_size, vocab_size)➡(32,28)Y = torch.mm(H, W_hq) + b_q# ⭐ outputs是一个列表不是一个张量,但每个元素的维度都是(batch_size, vocab_size)outputs.append(Y)# 按照输入顺序连接,输出的维度是(num_steps * batch_size, vocab_size)这里步数为1,批量大小也是1。这里的torch.cat(outputs, dim=0)形状是[1,28]return torch.cat(outputs, dim=0), (H,)class RNNModelScratch:# 初始化模型参数def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)def predict_ch8(prefix, num_preds, net, vocab, device):"""在 'prefix' 后面生成新字符。"""#state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]# 预测的时候一次只处理一个字符,[outputs[-1]]是最后一个元素的数据(不是索引),重构为形状(1,1)第一个维度是批量大小,第二个维度是索引数# lambda 函数被用来创建一个匿名函数,这个函数没有参数get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(1, 1)for y in prefix[1:]:# state的形状为(1, num_hiddens)➡(1, 512)_, state = net(get_input(), state)outputs.append(vocab[y])# 预测十个字符:(num_preds)for _ in range(num_preds):# y的形状为(1, vocab_size)➡(1,28)y, state = net(get_input(), state)# outputs是存储整个生成序列的索引,y.argmax(dim=1)将返回每个样本中概率最高的词(这里是一个字符)的索引。outputs.append(int(y.argmax(dim=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])def grad_clipping(net, theta):"""裁剪梯度"""if isinstance(net, nn.Module):  # 如果是用nn.Module的情况# 把它的参数列表提取出来在有梯度的情况下params = [p for p in net.parameters() if p.requires_grad]else:# 获取自定义模型的参数列表params = net.params# 把所有层的p,然后p的梯度平方求和,再开根号(等价于是说把所有的层的梯度拉成一个向量,把把这些向量全部拼在一起,再对该向量求范数)norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))# 如果梯度范数超过指定阈值 theta,将所有参数的梯度 × theta / normif norm > theta:for param in params:# 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 thetaparam.grad[:] *= theta / norm# @save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)  # 训练损失之和,词元数量for X, Y in train_iter:# state初始化if state is None or use_random_iter:# 在第一次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):# state对于nn.GRU是个张量# 分离隐藏状态的计算图(不把state里面的值改掉,而是说做backward的时候前面的计算图就detach掉了)state.detach_()# 读取新的iter后,将隐状态从计算图中分离出来,以避免不必要的梯度计算,从而提高效率和减少内存使用else:# state对于nn.LSTM或对于我们从零开始实现的模型是个元组,每个元素是一个张量,需要遍历每个元素for s in state:s.detach_()# 把Y的形状从(批量大小,时间步数)转置为(时间步数,批量大小)然后重构为(时间步数*批量大小)y = Y.T.reshape(-1)X, y = X.to(device), y.to(device)# 使用输入序列和隐藏状态进行前向传播计算,得到预测值和更新后的隐藏状态y_hat, state = net(X, state)# y_hat的形状为(时间步数*批量大小)的样本l = loss(y_hat, y.long()).mean()if isinstance(updater, torch.optim.Optimizer):# 清空优化器中的梯度updater.zero_grad()l.backward()# 在参数更新前进行梯度裁剪grad_clipping(net, 1)updater.step()else:l.backward()grad_clipping(net, 1)# 执行自定义的参数更新函数updater(batch_size=1)# 累加损失和样本数量metric.add(l * y.numel(), y.numel())# 输出 困惑度=平均损失的指数形式(以 e 为底),运行速度(每秒样本处理速度)return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()# @save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False):"""训练模型(定义见第8章)"""loss = nn.CrossEntropyLoss()animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',legend=['train'], xlim=[10, num_epochs])# 初始化if isinstance(net, nn.Module):updater = torch.optim.SGD(net.parameters(), lr)else:# 它基于给定的批量大小调用d2l.sgd函数来更新神经网络的参数。updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)# 定义一个预测函数,用于生成给定前缀之后的新字符序列predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)# 训练和预测for epoch in range(num_epochs):ppl, speed = train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter)# 每隔 10 个迭代周期生成if (epoch + 1) % 10 == 0:# 打印以 'time traveller' 为前缀的新字符序列print(predict('time traveller'))# 将当前迭代周期的困惑度添加到动画中进行可视化animator.add(epoch + 1, [ppl])# 打印最终的困惑度和每秒样本处理速度print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('time traveller'))print(predict('traveller'))batch_size, num_steps = 32, 35
num_hiddens = 512
num_epochs, lr = 500, 1
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
d2l.plt.show()

预测过程:(共50行)

time traveller the the the the the the the the the the the the t
time traveller the the the the the the the the the the the the t
time traveller the the the the the the the the the the the the t
time traveller and the the the the the the the the the the the t
time travellere and and and and and and and and and and and and 
time traveller and the the the the the the the the the the the t
time traveller and the the this the the the the the the the the 
time traveller and the the the the the the the the the the the t
time traveller and the the the the the the the the the the the t
time traveller and the the the the the the the the the the the t
time traveller and and the the that and and the the that and and
time traveller the this the this the this the this the this the 
time traveller and and and and and and and and and and and and a
time travellere athere are the that the enothe siont of the that
time travellere at fire wer cal meand the the thas ghas the and 
time traveller the andimensions at meathe that athe this the gra
time traveller the cines of the onge to the other this thes toun
time traveller a sexplane coners and mur all cand the pryche sim
time traveller pathere ic to sent a four wiokne some time travel
time traveller of the rime traveller of the ravellly bout in the
time traveller pat lasse thing in y i that loss the geime bsare 
time traveller than then thime time as ie mint and the thene thi
time travellerit noupse wo he save tous in time bly lempanced an
time travelleris ne iniend for mome time travelleris fourd chere
time traveller oul chand man losmedtand wishou urank that by con
time traveller cofce soime sist allithe timere abshree begrented
time traveller for this that spase time ar ar were attravexattir
time travellericknely i and in way a sat in so way urofit syis f
time traveller but now you begin to seethe object of my investig
time traveller proceeded any thatwer a comuraus that very yount 
time traveller follsoie thisnd so sinitarnt fofelyithan ubrict o
time traveller smiled aroug the notmare sorecurastilit fore ins 
time traveller for so it will be convenient to speak of himwas e
time traveller so d ascome roos move follighe that upen smave at
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller fron in counslon mo wables that flashed andpassed
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time travelleryou can show black is white by argument said filby
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time traveller for so it will be convenient to speak of himwas e
time travelleryou can show black is white by argument said filby
time travelleryou can show black is white by argument said filby
time traveller with a slight accession ofcheerfulness really thi

在这里插入图片描述
在这里插入图片描述




使用随机抽样方法的效果。

import random
import math
import torch
from torch import nn
from d2l import torch as d2l
from torch.nn import functional as F# 28,512
def get_params(vocab_size, num_hiddens, device):# 输入和输出都是28num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device) * 0.01W_xh = normal((num_inputs, num_hiddens))  # (vocab_size,num_hiddens)➡(28,512)W_hh = normal((num_hiddens, num_hiddens))  # (num_hiddens, num_hiddens)➡(512,512)b_h = torch.zeros(num_hiddens, device=device)  # (num_hiddens,)➡(512,)W_hq = normal((num_hiddens, num_outputs))  # (num_hiddens, vocab_size)➡(512, 28)b_q = torch.zeros(num_outputs, device=device)  # (vocab_size,)➡(28,)params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params# 32,512
def init_rnn_state(batch_size, num_hiddens, device):# 生成一个32行512列的零矩阵return (torch.zeros((batch_size, num_hiddens), device=device),)def rnn(inputs, state, params):W_xh, W_hh, b_h, W_hq, b_q = params# H的形状为(batch_size, num_hiddens)➡(32,512)H, = stateoutputs = []# X的形状(batch_size, vocab_size)➡(32,28)for X in inputs:#  (32,28)×(28,512)=(32,512)和(32,512)×(512,512)=(32,512)以及(512)相加的时候对b_h进行广播# 广播到形状 (batch_size, num_hiddens),其中 b_h 中的每个元素都会被复制到 batch_size 次,以便与二维张量的每一行相加。# ∴H的形状为(batch_size, num_hiddens)➡(32,512)H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# (32,512)×(512, 28)=(32,28)和(28,)# Y的形状为(batch_size, vocab_size)➡(32,28)Y = torch.mm(H, W_hq) + b_q# ⭐ outputs是一个列表不是一个张量,但每个元素的维度都是(batch_size, vocab_size)outputs.append(Y)# 按照输入顺序连接,输出的维度是(num_steps * batch_size, vocab_size)这里步数为1,批量大小也是1。这里的torch.cat(outputs, dim=0)形状是[1,28]return torch.cat(outputs, dim=0), (H,)class RNNModelScratch:# 初始化模型参数def __init__(self, vocab_size, num_hiddens, device, get_params,init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)def predict_ch8(prefix, num_preds, net, vocab, device):"""在 'prefix' 后面生成新字符。"""#state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]# 预测的时候一次只处理一个字符,[outputs[-1]]是最后一个元素的数据(不是索引),重构为形状(1,1)第一个维度是批量大小,第二个维度是索引数# lambda 函数被用来创建一个匿名函数,这个函数没有参数get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape(1, 1)for y in prefix[1:]:# state的形状为(1, num_hiddens)➡(1, 512)_, state = net(get_input(), state)outputs.append(vocab[y])# 预测十个字符:(num_preds)for _ in range(num_preds):# y的形状为(1, vocab_size)➡(1,28)y, state = net(get_input(), state)# outputs是存储整个生成序列的索引,y.argmax(dim=1)将返回每个样本中概率最高的词(这里是一个字符)的索引。outputs.append(int(y.argmax(dim=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])def grad_clipping(net, theta):"""裁剪梯度"""if isinstance(net, nn.Module):  # 如果是用nn.Module的情况# 把它的参数列表提取出来在有梯度的情况下params = [p for p in net.parameters() if p.requires_grad]else:# 获取自定义模型的参数列表params = net.params# 把所有层的p,然后p的梯度平方求和,再开根号(等价于是说把所有的层的梯度拉成一个向量,把把这些向量全部拼在一起,再对该向量求范数)norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))# 如果梯度范数超过指定阈值 theta,将所有参数的梯度 × theta / normif norm > theta:for param in params:# 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 thetaparam.grad[:] *= theta / norm# @save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)  # 训练损失之和,词元数量for X, Y in train_iter:# state初始化if state is None or use_random_iter:# 在第一次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):# state对于nn.GRU是个张量# 分离隐藏状态的计算图(不把state里面的值改掉,而是说做backward的时候前面的计算图就detach掉了)state.detach_()# 读取新的iter后,将隐状态从计算图中分离出来,以避免不必要的梯度计算,从而提高效率和减少内存使用else:# state对于nn.LSTM或对于我们从零开始实现的模型是个元组,每个元素是一个张量,需要遍历每个元素for s in state:s.detach_()# 把Y的形状从(批量大小,时间步数)转置为(时间步数,批量大小)然后重构为(时间步数*批量大小)y = Y.T.reshape(-1)X, y = X.to(device), y.to(device)# 使用输入序列和隐藏状态进行前向传播计算,得到预测值和更新后的隐藏状态y_hat, state = net(X, state)# y_hat的形状为(时间步数*批量大小)的样本l = loss(y_hat, y.long()).mean()if isinstance(updater, torch.optim.Optimizer):# 清空优化器中的梯度updater.zero_grad()l.backward()# 在参数更新前进行梯度裁剪grad_clipping(net, 1)updater.step()else:l.backward()grad_clipping(net, 1)# 执行自定义的参数更新函数updater(batch_size=1)# 累加损失和样本数量metric.add(l * y.numel(), y.numel())# 输出 困惑度=平均损失的指数形式(以 e 为底),运行速度(每秒样本处理速度)return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()# @save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False):"""训练模型(定义见第8章)"""loss = nn.CrossEntropyLoss()animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',legend=['train'], xlim=[10, num_epochs])# 初始化if isinstance(net, nn.Module):updater = torch.optim.SGD(net.parameters(), lr)else:# 它基于给定的批量大小调用d2l.sgd函数来更新神经网络的参数。updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)# 定义一个预测函数,用于生成给定前缀之后的新字符序列predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)# 训练和预测for epoch in range(num_epochs):ppl, speed = train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter)# 每隔 10 个迭代周期生成if (epoch + 1) % 10 == 0:# 打印以 'time traveller' 为前缀的新字符序列print(predict('time traveller'))# 将当前迭代周期的困惑度添加到动画中进行可视化animator.add(epoch + 1, [ppl])# 打印最终的困惑度和每秒样本处理速度print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('time traveller'))print(predict('traveller'))batch_size, num_steps = 32, 35
num_hiddens = 512
num_epochs, lr = 500, 1
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(), use_random_iter=True)
d2l.plt.show()

在这里插入图片描述
在这里插入图片描述




循环神经网络的简洁实现

加载数据集

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lbatch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)



定义模型

num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)



使用张量来初始化隐状态,它的形状是(隐藏层数,批量大小,隐藏单元数)。

state = torch.zeros((1, batch_size, num_hiddens))
print(state.shape)

在这里插入图片描述

通过一个隐状态和一个输入,我们就可以用更新后的隐状态计算输出需要强调的是,rnn_layer的“输出”(即:Y)不涉及输出层的计算: 它是指每个时间步的隐状态,这些隐状态可以用作后续输出层的输入。

X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
print(Y.shape, state_new.shape)



定义RNNModel类

#@save
class RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_sizeself.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1# 构造输出层self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)def forward(self, inputs, state):X = F.one_hot(inputs.T.long(), self.vocab_size)X = X.to(torch.float32)Y, state = self.rnn(X, state)# 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)# 它的输出形状是(时间步数*批量大小,词表大小)。output = self.linear(Y.reshape((-1, Y.shape[-1])))return output, statedef 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))



训练与预测

基于一个具有随机权重的模型进行预测

device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
d2l.predict_ch8('time traveller', 10, net, vocab, device)

该部分总代码

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l# @save
class RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_size# 之前的RNN模型包括了输出层,但这里没有rnn_layer只包括那个隐藏层,没有包括输出层self.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的,num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1# 线性层的输入大小为隐藏状态大小,输出大小为词汇表大小# 构造输出层self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)def forward(self, inputs, state):X = F.one_hot(inputs.T.long(), self.vocab_size)X = X.to(torch.float32)Y, state = self.rnn(X, state)# reshape做成一个2D,全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)# 它的输出形状是(时间步数*批量大小,词表大小)。output = self.linear(Y.reshape((-1, Y.shape[-1])))return output, statedef 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))batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
print(d2l.predict_ch8('time traveller', 10, net, vocab, device))



使用高级API训练模型

很明显,这种模型根本不能输出好的结果。然后使用定义的超参数调用train_ch8,并且使用高级API训练模型

num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)



总代码

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l# @save
class RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_size# 之前的RNN模型包括了输出层,但这里没有rnn_layer只包括那个隐藏层,没有包括输出层self.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的,num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1# 线性层的输入大小为隐藏状态大小,输出大小为词汇表大小# 构造输出层self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)def forward(self, inputs, state):X = F.one_hot(inputs.T.long(), self.vocab_size)X = X.to(torch.float32)Y, state = self.rnn(X, state)# reshape做成一个2D,全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)# 它的输出形状是(时间步数*批量大小,词表大小)。output = self.linear(Y.reshape((-1, Y.shape[-1])))return output, statedef 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))batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)
d2l.plt.show()

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1549006.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Redis高级特性及应用

一、Redis慢查询 1.1 Redis命令流程 1.2 慢查询配置: 可以通过以下命令配置慢查询时间阈值(慢查询值得是上图中执行命令的时间,不包含其他时间) config set slowlog-log-slower-than 10000 //单位微秒 config rewrite //写入…

大模型与智能体的市场调研分析

2024年,很多人都在谈论智能体,我老婆这样的美术老师,也让我给她科普一下,于是我花了几天时间,系统学习和深入调研了一下,在此分享给大家。 时代背景 人工智能就像电力一样,如果你的竞争对手正…

木材检测系统源码分享

木材检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

C# 字符串(String)的应用说明一

一.字符串(String)的应用说明: 在 C# 中,更常见的做法是使用 string 关键字来声明一个字符串变量,也可以使用字符数组来表示字符串。string 关键字是 System.String 类的别名。 二.创建 String 对象的方法说明&#x…

$attrs 和 $listeners

通常情况下,父子组件之间的数据是通过 props 由父向子传递的,当子组件想要修改数据时,则需要通过 $emit 以事件形式交由父组件完成,而这种交互方式只存在于父子组件之间,多层嵌套的时候,处于内层的组件想要…

SQL进阶技巧:如何获取状态一致的分组? | 最大、最小值法

目录 0 需求描述 1 数据准备 2 问题分析 方法1:最大、最小值法(技巧) 方法2:常规思路 3 小结 如果觉得本文对你有帮助,那么不妨也可以选择去看看我的博客专栏 ,部分内容如下: 数字化建设通…

(JAVA)队列 和 符号表 两种数据结构的实现

1. 队列 1.1 队列的概述 队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表。 它按照先进先出的原则存储数据,先进入的数据,在读取时先被读出来 1.2 …

【anki】显示 “连接超时,请更换网络后重试” 怎么办

文章目录 前言一、问题描述二、解决方案 前言 在 anki同步 时遇到的问题 一、问题描述 二、解决方案 从电信换为了移动热点,电脑手机都同步成功了

图像超分辨率(SR)

图像超分辨率(Image Super-Resolution, SR)是一种图像处理技术,旨在从低分辨率(LR)图像中恢复出高分辨率(HR)图像。这种技术通过增加图像中的细节和清晰度来提高图像的视觉质量,从而…

Stable Diffusion扩散模型【详解】新手也能看懂!!

前言 文章目录 1、Diffusion的整体过程2、加噪过程 2.1 加噪的具体细节2.2 加噪过程的公式推导 3、去噪过程 3.1 图像概率分布 4、损失函数5、 伪代码过程 此文涉及公式推导,需要参考这篇文章: Stable Diffusion扩散模型推导公式的基础知识 1、Diffu…

数据结构 ——— 顺序表oj题:编写函数,删除有序数组中的重复项

目录 题目要求 代码实现 题目要求 一个升序排列的数组 nums ,要求原地删除重复出现的元素,使每个元素只出现一次,并返回删除后数组的新长度,元素的相对顺序应该保持一致 代码实现 代码演示: int removeDuplicate…

你要的Air201录音和播放录音功能?直接拿去!

最近拼拼收到同学们的疑问:Air201是否支持录音、播放录音功能? 必须支持!Air201可是高集成化设计,并且Air201自带了ES8311音频解码芯片(Audio Codec)及MIC麦克,可支持本地的录音功能&#xff1…

SAP Message - self-explanatory 自身说明

SAP Message 解释、创建和应用可见如下文章:SAP Abap】SE91 - SAP MESSAGE 消息类创建与应用-CSDN博客 SE91 SAP消息类型 - tongxiaohu - 博客园 这里主要想聊一下常用的SE91 中不常用的功能 - 自身说明 选项的作用。 以 VF - 004 为例: 我们都知道自…

《凡人歌》中的IT职业启示录

《凡人歌》是由中央电视台、正午阳光、爱奇艺出品,简川訸执导,纪静蓉编剧,侯鸿亮任制片,殷桃、王骁领衔主演,章若楠、秦俊杰、张哲华、陈昊宇主演的都市话题剧 ,改编自纪静蓉的小说《我不是废柴》。该剧于2…

上位机通讯汇川Plc3U和5U

开发过程中需要调用到汇川官网的两个动态库(ModbusTcpAPI.dll;StandardModbusApi.dll) 解压完成后找到上面的动态库复制到自己项目的根目录下面然后就可以进行下一步操作了 UI界面: 通讯类集成了3U和5U的连接断开以及读写方法: public clas…

如何巧妙运用Shell变量:掌握脚本编程的核心技巧

目录 前言一、Shell变量——变量类型1、用户自定义变量2、环境变量用./ 启动脚本文件记得加权限哦 二、Shell变量——变量赋值和访问(一)变量定义(二)变量的使用(三)删除变量(四)添加环境变量(五)内部变量(六&#xff…

开源链动2+1模式AI智能名片小程序源码:放大特色,构建独特竞争力

摘要:本文探讨在当今社会背景下,开源链动21模式AI智能名片小程序源码如何通过坚持与众不同来构建独特竞争力。在信息传播便捷但个体易同质化的时代,拥有特色是脱颖而出的关键,而这种模式下的小程序源码具有独特的发展潜力。 一、引…

有效解决配置管理混乱,麒麟桌面操作系统V10 sp1 2403最新版本推出统一配置系统

了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://documentkylinos.cn 当前桌面操作系统中可通过配置定义的应用有限&a…

使用 Git 帮助文档

聊聊如何更好地查阅官方文档。 ‍ git help 学习某个工具&#xff0c;官方文档是少不了的&#xff0c;也是最权威的。我们可以使用 git help 来查看帮助&#xff0c;该命令会列举出常用的命令和介绍&#xff1a; > git help usage: git [--version] [--help] [-C <pa…