跟李沐搓一个RNN

本文详细介绍了如何从头构建一个基于PyTorch的RNN模型,包括其结构、前向传播过程以及如何将其用于语言模型训练。文章还涵盖了梯度剪裁技术在防止梯度爆炸中的应用。
摘要由CSDN通过智能技术生成

 导入环境

%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

 定义RNNScratch 类

class RNNScratch(d2l.Module):  #@save
    """The RNN model implemented from scratch."""
    def __init__(self, num_inputs, num_hiddens, sigma=0.01):
        super().__init__()
        self.save_hyperparameters()
        # 保存超参数
        self.W_xh = nn.Parameter(
            torch.randn(num_inputs, num_hiddens) * sigma)
        #表示连接输入层和隐藏层的权重矩阵。
        self.W_hh = nn.Parameter(
            torch.randn(num_hiddens, num_hiddens) * sigma)
        self.b_h = nn.Parameter(torch.zeros(num_hiddens))
        # 表示隐藏层的偏差向量,并使用零初始化。
        

 RNN的前向传播

前向方法定义了如何在给定当前输入和上一个时间步的模型状态的情况下计算任何时间步的输出和隐藏状态。请注意,RNN 模型循环遍历输入的最外层维度,一次更新一个步骤的隐藏状态。这里的模型使用

@d2l.add_to_class(RNNScratch)  #@save
def forward(self, inputs, state=None):
# 使用条件语句来检查是否提供了隐藏状态 state。如果没有,则使用 torch.zeros 函数初始化一个全零的隐藏状态;否则,直接使用提供的 state。
# 隐藏状态是 RNN 中一个关键的概念,它在每个时间步之间传递信息,并记录了序列数据的过去信息。
    if state is None:
        # Initial state with shape: (batch_size, num_hiddens)
        state = torch.zeros((inputs.shape[1], self.num_hiddens),
                            device=inputs.device)
    else:
        state, = state
    outputs = []
    for X in inputs:  # 输入形状: (num_steps, batch_size, num_inputs) 对于每个输入 计算当前的输入与隐藏层的乘积
        state = torch.tanh(torch.matmul(X, self.W_xh) +
                           torch.matmul(state, self.W_hh) + self.b_h)
        outputs.append(state)
    return outputs, state
#计算出的所有时间步的输出 outputs 和最后的隐藏状态 state。

 将一小批输入序列输入 RNN 模型

batch_size, num_inputs, num_hiddens, num_steps = 2, 16, 32, 100
rnn = RNNScratch(num_inputs, num_hiddens)
X = torch.ones((num_steps, batch_size, num_inputs))
outputs, state = rnn(X)

检查 RNN 模型是否产生正确形状的结果,以确保隐藏状态的维数保持不变。

ef check_len(a, n):  #@save
    # 检查数据长度和形状,并随后利用它们对之前计算的结果进行校验。
    assert len(a) == n, f'list\'s length {len(a)} != expected length {n}'

def check_shape(a, shape):  #@save
    
    assert a.shape == shape, \
        f'tensor\'s shape {a.shape} != expected shape {shape}'


#函数使用 assert 语句来检查张量 a 的形状是否等于 shape。如果不相等,它会抛出一个异常并打印一条错误信息,其中包含实际形状和期望形状。
check_len(outputs, num_steps)
check_shape(outputs[0], (batch_size, num_hiddens))
check_shape(state, (batch_size, num_hiddens))

使用RNN来训练一个语言模型 

训练语言模型时,输入和输出来自相同的词汇表。因此,它们具有相同的维度,等于词汇量的大小。

class RNNLMScratch(d2l.Classifier):  

    def __init__(self, rnn, vocab_size, lr=0.01):

        super().__init__()
        self.save_hyperparameters()
        self.init_params()

    def init_params(self):
        """初始化模型的参数。"""
        self.W_hq = nn.Parameter(
            torch.randn(
                self.rnn.num_hiddens, self.vocab_size) * self.rnn.sigma)
        self.b_q = nn.Parameter(torch.zeros(self.vocab_size))

    def training_step(self, batch):
        """执行模型的训练步骤。
        batch : tuple
            包含输入序列和对应标签的批次数据。

        Returns:
        -------
        torch.Tensor
            训练损失。
        """
        l = self.loss(self(*batch[:-1]), batch[-1])
        self.plot('ppl', torch.exp(l), train=True)
        return l

    def validation_step(self, batch):
        """执行模型的验证步骤。

        Parameters:
        ----------
        batch : tuple
            包含输入序列和对应标签的批次数据。

        Returns:
        -------
        torch.Tensor
            验证损失。
        """
        l = self.loss(self(*batch[:-1]), batch[-1])
        self.plot('ppl', torch.exp(l), train=False)
        return l

梯度剪裁

 梯度剪裁是一种用于控制梯度大小的技术,主要用于防止训练过程中出现梯度爆炸的情况。当神经网络的参数更新过程中,梯度的数值过大,导致参数更新过大,进而导致模型不稳定或者无法收敛时,就需要采用梯度剪裁来限制梯度的大小。

@d2l.add_to_class(d2l.Trainer)  #@save
def clip_gradients(self, grad_clip_val, model):# 梯度裁剪
    params = [p for p in model.parameters() if p.requires_grad]
    # 提取所有需要梯度更新的模型参数,
#首先对所有参数的梯度张量进行逐元素平方,然后将结果相加得到每个参数的平方梯度总和
#最后对所有参数的平方梯度总和进行求和并取平方根,得到所有参数的梯度 L2 范数。
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    # 计算所有参数梯度的 L2 范数
    if norm > grad_clip_val:
        for param in params:
            param.grad[:] *= grad_clip_val / norm

训练语言模型



# 加载时间机器数据集,设置批次大小为 1024,序列长度为 32
data = d2l.TimeMachine(batch_size=1024, num_steps=32)

# 初始化自定义的循环神经网络模型(RNNScratch),输入维度为词汇表大小,隐藏层维度为 32
rnn = RNNScratch(num_inputs=len(data.vocab), num_hiddens=32)

# 初始化自定义的 RNN 语言模型(RNNLMScratch),传入循环神经网络模型、词汇表大小和学习率
model = RNNLMScratch(rnn, vocab_size=len(data.vocab), lr=1)

# 初始化训练器(Trainer),设置最大训练周期为 100,梯度剪裁阈值为 1,使用一个 GPU
trainer = d2l.Trainer(max_epochs=100, gradient_clip_val=1, num_gpus=1)

# 开始训练模型
trainer.fit(model, data)

预测

def predict(self, prefix, num_preds, vocab, device=None):



    state, outputs = None, [vocab[prefix[0]]]


    for i in range(len(prefix) + num_preds - 1):
        # 将上一步预测的词转换为独热编码
        X = torch.tensor([[outputs[-1]]], device=device)
        embs = self.one_hot(X)
        # 使用 RNN 模型进行前向计算
        rnn_outputs, state = self.rnn(embs, state)

        # Warm-up 阶段:添加词语前缀中的下一个词
        if i < len(prefix) - 1:
            outputs.append(vocab[prefix[i + 1]])
        # 预测阶段:使用模型输出预测下一个词
        else:
            Y = self.output_layer(rnn_outputs)
            outputs.append(int(Y.argmax(axis=2).reshape(1)))



    return ''.join([vocab.idx_to_token[i] for i in outputs])

效果

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值