深度学习实战笔记5循环神经网络“读取时光机器数据集””输入字符预测完整句子“完整代码详解

完整代码解释

#导入数据库
import math
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 #这里设置了两个重要的超参数:batch_size: 每个批次中的样本量,这个例子中,它被设置为 32。
#num_steps: 每个样本中的序列长度。在这个例子中,它被设置为 35。
# d2l.load_data_time_machine 函数,它是一个自定义函数,用于加载和处理数据集。
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)#train_iter: 一个迭代器,用于在训练过程中逐批次提供数据。
#vocab: 词汇表,通常是一个字典或列表,将文本中的词映射到整数索引。




#初始化模型参数
def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size #num_inputs 和 num_outputs 被设置为 vocab_size,这意味着输入和输出层的大小与词汇表的大小相同。这对于处理词汇表中的每个词是必要的。

#定义了一个内部函数 normal,它使用 PyTorch 的 torch.randn 函数生成具有标准正态分布的随机数,并将它们乘以一个小数 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]#创建一个列表 params,包含所有的参数 
    for param in params:    #遍历 params 列表,对每个参数调用 requires_grad_(True) 方法,这告诉 PyTorch 在自动微分过程中计算这些参数的梯度。这是训练过程中优化参数所必需的。
        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函数定义了如何在一个时间步内计算隐状态和输出。] 循环神经网络模型通过inputs最外层的维度实现循环, 以便逐时间步更新小批量数据的隐状态H。 此外,这里使用 tanh
 函数作为激活函数
def rnn(inputs, state, params):#inputs: 输入数据,形状为 (时间步数量,批量大小,词表大小)。这通常是 one-hot 编码的文本序列。
state: 隐藏状态,通常包含上一个时间步的隐藏状态 H。
params: 模型参数的列表
    # inputs的形状:(时间步数量,批量大小,词表大小)
    W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state   #H: 从 state 中提取当前的隐藏状态。
    outputs = []
    # X的形状:(批量大小,词表大小)
    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,)  #使用 torch.cat(outputs, dim=0) 将 outputs 列表中的所有输出按第 0 维(时间步)连接起来,形成一个连续的输出序列。






#创建一个类来包装这些函数], 并存储从零开始实现的循环神经网络模型的参数。
class RNNModelScratch: #@save
    """从零开始实现的循环神经网络模型"""
    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 和状态 state。输入数据 X 被转换为 one-hot 编码,然后转置 (T) 并转换为浮点数类型。调用 self.forward_fn 函数来执行前向传播,传入 one-hot 编码的输入 X,状态 state 和模型参数 self.params。
        X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
        return self.forward_fn(X, state, self.params)
#这个方法用于初始化或重置模型的隐藏状态。
接收 batch_size 和 device 作为参数,调用 self.init_state 函数来创建一个新的隐藏状态,并返回它。
    def begin_state(self, batch_size, device):
        return self.init_state(batch_size, self.num_hiddens, device)






#预测让我们[首先定义预测函数来生成prefix之后的新字符], 其中的prefix是一个用户提供的包含多个字符的字符串。 在循环遍历prefix中的开始字符时, 我们不断地将隐状态传递到下一个时间步,但是不生成任何输出。 这被称为预热(warm-up)期, 因为在此期间模型会自我更新(例如,更新隐状态), 但不会进行预测。 预热期结束后,隐状态的值通常比刚开始的初始值更适合预测, 从而预测字符并输出它们。
def predict_ch8(prefix, num_preds, net, vocab, device):  #@save
    """在prefix后面生成新字符"""
    state = net.begin_state(batch_size=1, device=device) #使用 net.begin_state 方法初始化隐藏状态 state,batch_size 设置为 1,因为我们一次只生成一个字符。
    outputs = [vocab[prefix[0]]] #初始化 outputs 列表,首先添加 prefix 的第一个字符对应的索引
    get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1)) #用于获取当前要生成的字符的输入。
    for y in prefix[1:]:  # 预热期 #遍历 prefix 的剩余部分,使用 RNN 模型的前向传播更新状态,并将每个字符的索引添加到 outputs 列表。
        _, state = net(get_input(), state)
        outputs.append(vocab[y])
    for _ in range(num_preds):  # 预测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])


#现在我们可以测试predict_ch8函数。 我们将前缀指定为time traveller , 并基于这个前缀生成10个后续字符。 鉴于我们还没有训练网络,它会生成荒谬的预测结果。
predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())








#梯度裁剪
def grad_clipping(net, theta):  #@save    #net: 神经网络模型,可以是 PyTorch 的 nn.Module 类的实例,或者是自定义模型(例如之前定义的 RNNModelScratch),只要它有 params 属性。
theta: 阈值,用于确定是否需要裁剪梯度。
    """裁剪梯度"""
    if isinstance(net, nn.Module):   #如果 net 是 nn.Module 的实例,使用 net.parameters() 获取模型的所有参数,并使用列表推导式提取那些需要梯度的参数。
        params = [p for p in net.parameters() if p.requires_grad]
    else:      #如果 net 是自定义模型,直接使用 net.params 获取参数列表。
        params = net.params
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    if norm > theta:     #如果梯度范数 norm 大于阈值 theta,则遍历所有参数。
对每个参数的梯度进行裁剪,使用 param.grad[:] 访问参数的梯度,并将其乘以 (theta / norm) 进行缩放。
        for param in params:
            param.grad[:] *= theta / norm






#训练
#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
    """训练网络一个迭代周期(定义见第8章)"""
#初始化state, timer = None, d2l.Timer() #state 初始化为 None,表示模型的初始隐藏状态。
timer 使用 d2l.Timer() 初始化,用于测量训练周期的时间。
metric 使用 d2l.Accumulator(2) 初始化,用于累积训练损失和处理的词元数量。
    metric = d2l.Accumulator(2)  # 训练损失之和,词元数量
    for X, Y in train_iter:
        if state is None or use_random_iter: #如果 state 是 None 或 use_random_iter 为 True,则调用 net.begin_state 方法初始化隐藏状态 state。
            # 在第一次迭代或使用随机抽样时初始化state
            state = net.begin_state(batch_size=X.shape[0], device=device)
        else:#如果 state 不是 None 且不是使用随机抽样,则根据模型类型(nn.Module 或自定义模型)适当地从上一个时间步的 state 断开梯度。
            if isinstance(net, nn.Module) and not isinstance(state, tuple):
                # state对于nn.GRU是个张量
                state.detach_()
            else:
                # state对于nn.LSTM或对于我们从零开始实现的模型是个张量
                for s in state:
                    s.detach_()
        y = Y.T.reshape(-1)
        X, y = X.to(device), y.to(device)#将输入 X 和标签 y 移动到指定的 device。
        y_hat, state = net(X, state)    #调用模型 net 进行前向传播,获取预测 y_hat 和新的隐藏状态 state。
        l = loss(y_hat, y.long()).mean()  #计算损失 l 并取平均值。
        if isinstance(updater, torch.optim.Optimizer):  #如果 updater 是 torch.optim.Optimizer 的实例 
            updater.zero_grad()  #清除梯度。
            l.backward()    #执行反向传播。
            grad_clipping(net, 1) #调用 grad_clipping 函数裁剪梯度
            updater.step()   #更新模型参数
        else:           #如果 updater 不是优化器
            l.backward() 
            grad_clipping(net, 1)
            # 因为已经调用了mean函数
            updater(batch_size=1)
        metric.add(l * y.numel(), y.numel())  #更新模型参数,传入 batch_size=1。
    return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()







#[循环神经网络模型的训练函数既支持从零开始实现, 也可以使用高级API来实现。]
#@save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
              use_random_iter=False):
    """训练模型(定义见第8章)"""
    loss = nn.CrossEntropyLoss()   #定义损失函数 loss 使用 nn.CrossEntropyLoss。
    animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
                            legend=['train'], xlim=[10, num_epochs]) #创建一个 d2l.Animator 实例,用于绘制训练过程中的困惑度(perplexity)曲线。
    # 初始化 根据 net 的类型,初始化优化器 updater。如果 net 是 nn.Module 的实例,使用 torch.optim.SGD;否则,使用自定义的 d2l.sgd 函数。
    if isinstance(net, nn.Module):
        updater = torch.optim.SGD(net.parameters(), lr)
    else:
        updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
    predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)#定义 predict 函数,用于生成文本。
    # 训练和预测
    for epoch in range(num_epochs): #遍历指定数量的 num_epochs
        ppl, speed = train_epoch_ch8( #在每个迭代周期中,调用 train_epoch_ch8 函数进行训练,并计算困惑度(perplexity)和处理速度。
            net, train_iter, loss, updater, device, use_random_iter)
        if (epoch + 1) % 10 == 0:  #如果当前迭代周期是每 10 个周期的倍数,使用 predict 函数生成文本样本,并使用 animator 绘制当前的困惑度。
            print(predict('time traveller'))
            animator.add(epoch + 1, [ppl])
    print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
    print(predict('time traveller'))#用 predict 函数生成两个不同的文本样本,展示模型的生成能力。
    print(predict('traveller'))





#设置了训练迭代周期 num_epochs 为 500 和学习率 lr 为 1,并调用了 train_ch8 函数来训练神经网络模型
num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值