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

news2024/9/29 19:36:37

目录

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

batch_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_q
    W_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)
    # 返回拼接后的输出张量和最后一个时间步的隐藏状态 H
    return 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_fn
        
    def __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 F


def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size

    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:
        param.requires_grad_(True)
    return params


def 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 = params
    H, = state
    outputs = []

    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_q
        outputs.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_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __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 中除第一个字符之外的每个字符 y
    for 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拿到输出y
        y, 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_q
    W_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)
    # 返回拼接后的输出张量和最后一个时间步的隐藏状态 H
    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_hiddens
        # 调用 get_params 函数初始化模型的参数,并保存为类的属性
        # 参数包括输入到隐藏层的权重矩阵、隐藏层到隐藏层的权重矩阵、隐藏层的偏置向量、隐藏层到输出层的权重矩阵、输出层的偏置向量
        self.params = get_params(vocab_size, num_hiddens, device)
        # 初始化隐藏状态的函数和前向传播函数
        self.init_state, self.forward_fn = init_state, forward_fn

    def __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,隐藏层数量为512
        return 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 中除第一个字符之外的每个字符 y
    for 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])


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):
    # 输入和输出都是28
    num_inputs = num_outputs = vocab_size

    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    W_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, = state
    outputs = []
    # 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_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __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))
    # 如果梯度范数超过指定阈值 theta
    if norm > theta:
        # 对于每个参数
        for param in params:
            # 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 theta
            param.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:
            # 在第一次迭代或使用随机抽样时初始化state
            state = 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):
    # 输入和输出都是28
    num_inputs = num_outputs = vocab_size

    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    W_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, = state
    outputs = []
    # 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_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __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 / norm
    if norm > theta:
        for param in params:
            # 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 theta
            param.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:
            # 在第一次迭代或使用随机抽样时初始化state
            state = 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):
    # 输入和输出都是28
    num_inputs = num_outputs = vocab_size

    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    W_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, = state
    outputs = []
    # 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_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __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 / norm
    if norm > theta:
        for param in params:
            # 将参数的梯度值裁剪至指定范围内,保持梯度范数不超过 theta
            param.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:
            # 在第一次迭代或使用随机抽样时初始化state
            state = 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 d2l

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)



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

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_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 = 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, 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))



训练与预测

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

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_layer
        self.vocab_size = vocab_size
        # 之前的RNN模型包括了输出层,但这里没有rnn_layer只包括那个隐藏层,没有包括输出层
        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 = 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, 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))


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_layer
        self.vocab_size = vocab_size
        # 之前的RNN模型包括了输出层,但这里没有rnn_layer只包括那个隐藏层,没有包括输出层
        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 = 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, 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))


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.coloradmin.cn/o/2177638.html

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

相关文章

JS设计模式之观察者模式:观察者与可观察对象的巧妙互动

一. 前言 在前端开发中,我们经常会遇到需要对用户的操作进行响应的场景,例如页面上的按钮点击、输入框内容变化等。为了实现这种响应式的设计,我们可以使用观察者模式来解耦各个组件之间的依赖关系。 本文将详细介绍观察者模式的原理和实现…

【FaceFusion3.0.0】全新升级,重磅发布!

FaceFusion 3.0.0 版本引入了许多新特性和改进,其中包括: 重新设计架构,使所有操作都作为“任务”进行处理。在面部交换功能中引入像素增强(pixel boost)。向面部检测器添加多角度处理功能。引入年龄修正处理器(age modifier processor)。引…

前端学习笔记-JS进阶篇-02

构造函数&数据常用函数 1、深入对象 1.1、创建对象三种方式 1. 利用对象字面量创建对象 2. 利用new Object 创建对象 3. 利用构造函数创建对象 1.2、构造函数 构造函数:是一种特殊的函数,主要用来初始化对象 使用场景:常规的{...} 语…

MES系统实现制造业生产自动化、智能化与透明化

万界星空科技MES系统通过集成硬件和软件,实现对生产过程的实时监控、数据采集、任务调度、资源分配、质量控制、文档管理等功能,旨在优化企业的生产流程,提高生产效率,降低成本,并确保产品质量。涵盖了离散制造、流程制…

RK3588主板PCB设计学习

DCDC电路可以直接参考数据手册: 电源输出3A,回流GND也应该是3A,回流路径和输出路径的电流是一致的,不要输出路径布线很粗,GND回流路径很细,并且应该保证回流面积最小: 这一点讲的很到位&#xf…

一款基于 RBAC 的 Net8 后台管理框架,权限管理,前后台分离,支持多站点单点登录(附源码)

前言 在当今快速发展的互联网时代,一款强大的后台管理系统对于提升工作效率、简化cao作流程具有重要意义。然而,在众多开源项目中,大部分是以Java语言编写的,这对于专注于.NET平台的开发者来说,存在一定的使用门槛。 …

云+AI时代下,Alibaba Cloud Linux 如何进一步演进?

编者按:云AI 浪潮势不可挡,服务器操作系统如何进一步演进?阿里云智能集团高级技术专家张金利在 2024 云栖大会操作系统开源专场上,分享了《Alibaba Cloud Linux 技术路线演进思考》。内容见下: (图/张金利&…

MacOS安装MindSpore(2024年最新)

大家好,我是邵奈一,一个不务正业的程序猿、正儿八经的斜杠青年。 1、世人称我为:被代码耽误的诗人、没天赋的书法家、五音不全的歌手、专业跑龙套演员、不合格的运动员… 2、这几年,我整理了很多IT技术相关的教程给大家&#xff0…

项目管理系统中的风险管理:如何识别和应对项目风险?

在现代项目管理中,风险管理是确保项目成功的关键因素之一。无论是技术、资源还是市场的变化,风险无处不在。有效的风险管理能够帮助团队识别潜在问题并制定应对策略,从而避免项目延误和预算超支。项目管理系统在这一过程中扮演着重要角色&…

从 TCP Reno 经 BIC 到 CUBIC

重读 TCP拥塞控制算法-从BIC到CUBIC 以及 cubic 的 tcp friendliness 与拐点控制 这两篇文章,感觉还是啰嗦了,今日重新一气呵成这个话题。 reno 线性逼近管道容量 Wmax,相当于一次查询(capacity-seeking),但长肥管道从 0.5*Wmax …

试填+组合数学,CF 1648C - Tyler and Strings

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1648C - Tyler and Strings 二、解题报告 1、思路分析 考虑枚举相同前缀…

MySQL约束:外键约束

下面先创建两张表用来作为实验样例 1.创建dept表 create table dept(id int auto_increment comment ID primary key,name varchar(50) not null comment 部门名称 ) comment 部门表;INSERT INTO dept (id, name) VALUES (1, 研发部), (2, 市场部), (3, 财务部), (4, 销售部…

【C语言软开面经】

C语言软开面经 malloc calloc realloc free动态分配内存malloccalloc函数:realloc 函数:free函数: 堆栈-内存分区栈区(Stack):堆区(Heap):全局(静态&#xff…

哪个待办事项提醒软件推荐?待办事项提醒软件哪个合适?

在快节奏的现代生活中,我们每个人每天都会被各种待办事项所包围。从工作计划到个人生活,从学习任务到家庭琐事,这些事项往往繁杂且紧急,稍不留神就可能错过重要的截止日期。因此,选择一款合适的待办事项提醒软件&#…

Docker-2.如何保存数据退出

在使用Docker时,我们常常需要修改容器中的文件,并且希望在容器重启后这些修改能够得到保留。 0.简介 使用Docker时有一个需要注意的问题:当你修改了容器中的文件后,重启容器后这些修改将会被重置,深入研究这个问题。 …

如何高效运营知识产权,实现企业价值最大化?

知识产权的运营管理是指企业对其所拥有的专利、商标、著作权等知识产权进行规划、运用、保护和管理的一系列活动。这些活动旨在最大化知识产权的价值,促进企业的技术创新和市场竞争力的提升。通过有效的知识产权运营管理,企业可以实现技术成果的快速转化…

猝发传输和非猝发传输

猝发传输和非猝发传输是两种不同的数据传输方式,主要区别在于数据传输的连续性以及数据包的发送方式。 猝发传输 (Burst Transmission): 定义: 猝发传输是指在一段时间内,大量数据包集中发送,然后在一段时间内没有数据传输,这种…

全流程SWAP农业模型数据制备、敏感性分析及气候变化影响技术应用

SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了W…

HAL+M4学习记录_1

一、简介 基于Arm Cortex-M4的STM32F4 MCU系列采用了意法半导体的NVM工艺和ART Accelerator™,在高达180 MHz的工作频率下通过闪存执行时其处理性能达到225 DMIPS/608 CoreMark,这是迄今所有基于Cortex-M内核的微控制器产品所达到的最高基准测试分数。 这…

奖金高达 110 万元,Spatial Joy 2024 全球 AR 应用开发大赛启动

今年是AR应用开发大赛第三届,恰逢Rokid成立十周年,我们推出全新的大赛品牌“Spatial Joy”,引领开发者享受开发乐趣,为其打造充满挑战和惊喜的开发之旅,逐渐成为空间计算时代全球最大AR应用开发大赛。回顾大赛发展&…