• 【深度学习实验】循环神经网络(四):基于 LSTM 的语言模型训练


    目录

    一、实验介绍

    二、实验环境

    1. 配置虚拟环境

    2. 库版本介绍

    三、实验内容

    0. 导入必要的工具包

    1. RNN与梯度裁剪

    2. LSTM模型

    3. 训练函数

    a. train_epoch

    b. train

    4. 文本预测

    5. GPU判断函数

    6. 训练与测试

    7. 代码整合


            经验是智慧之父,记忆是智慧之母。

    ——谚语

    一、实验介绍

            本实验实现了基于 LSTM 的语言模型训练及测试

    • 基于门控的循环神经网络(Gated RNN)
      • 门控循环单元(GRU)
        • 门控循环单元(GRU)具有比传统循环神经网络更少的门控单元,因此参数更少,计算效率更高。GRU通过重置门更新门来控制信息的流动,从而改善了传统循环神经网络中的长期依赖问题。
      • 长短期记忆网络(LSTM)
        • 长短期记忆网络(LSTM)是另一种常用的门控循环神经网络结构。LSTM引入了记忆单元输入门输出门以及遗忘门等门控机制,通过这些门控机制可以选择性地记忆、遗忘和输出信息,有效地处理长期依赖和梯度问题。
    • LSTM示意图

    二、实验环境

            本系列实验使用了PyTorch深度学习框架,相关操作如下:

    1. 配置虚拟环境

    conda create -n DL python=3.7 
    conda activate DL
    pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
    
    conda install matplotlib
     conda install scikit-learn

    2. 库版本介绍

    软件包本实验版本目前最新版
    matplotlib3.5.33.8.0
    numpy1.21.61.26.0
    python3.7.16
    scikit-learn0.22.11.3.0
    torch1.8.1+cu1022.0.1
    torchaudio0.8.12.0.2
    torchvision0.9.1+cu1020.15.2

    三、实验内容

    0. 导入必要的工具包

    1. import torch
    2. from torch import nn
    3. from d2l import torch as d2l

    1. RNN与梯度裁剪

    【深度学习实验】循环神经网络(一):循环神经网络(RNN)模型的实现与梯度裁剪_QomolangmaH的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_63834988/article/details/133742433?spm=1001.2014.3001.5501

    2. 自定义LSTM模型RNNModel

    【深度学习实验】循环神经网络(三):门控制——自定义循环神经网络LSTM(长短期记忆网络)模型-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_63834988/article/details/133864731?spm=1001.2014.3001.5501

    3. 训练函数

    a. train_epoch

    1. def train_epoch(net, train_iter, loss, updater, device, use_random_iter):
    2. state, timer = None, d2l.Timer()
    3. metric = d2l.Accumulator(2) # 训练损失之和,词元数量
    4. for X, Y in train_iter:
    5. if state is None or use_random_iter:
    6. # 在第一次迭代或使用随机抽样时初始化state
    7. state = net.begin_state(batch_size=X.shape[0], device=device)
    8. if isinstance(net, nn.Module) and not isinstance(state, tuple):
    9. # state对于nn.GRU是个张量
    10. state.detach_()
    11. else:
    12. # state对于nn.LSTM或对于我们从零开始实现的模型是个张量
    13. for s in state:
    14. s.detach_()
    15. y = Y.T.reshape(-1)
    16. X, y = X.to(device), y.to(device)
    17. y_hat, state = net(X, state)
    18. l = loss(y_hat, y.long()).mean()
    19. if isinstance(updater, torch.optim.Optimizer):
    20. updater.zero_grad()
    21. l.backward()
    22. grad_clipping(net, 1)
    23. updater.step()
    24. else:
    25. l.backward()
    26. grad_clipping(net, 1)
    27. # 因为已经调用了mean函数
    28. updater(batch_size=1)
    29. metric.add(l * d2l.size(y), d2l.size(y))
    30. return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
    • 参数:

      • net:神经网络模型
      • train_iter:训练数据迭代器
      • loss:损失函数
      • updater:更新模型参数的方法(如优化器)
      • device:计算设备(如CPU或GPU)
      • use_random_iter:是否使用随机抽样
    • 函数内部定义了一些辅助变量:

      • state:模型的隐藏状态变量
      • timer:计时器,用于记录训练时间
      • metric:累加器,用于计算训练损失之和和词元数量
    • 函数通过迭代train_iter中的数据进行训练。每次迭代中,执行以下步骤:

      • 如果是第一次迭代或者使用随机抽样,则初始化隐藏状态state
      • 如果netnn.Module的实例并且state不是元组类型,则将state的梯度信息清零(detach_()函数用于断开与计算图的连接,并清除梯度信息)
      • 对于其他类型的模型(如nn.LSTM或自定义模型),遍历state中的每个元素,将其梯度信息清零
      • 将输入数据X和标签Y转移到指定的计算设备上
      • 使用神经网络模型net和当前的隐藏状态state进行前向传播,得到预测值y_hat和更新后的隐藏状态state
      • 计算损失函数loss对于预测值y_hat和标签y的损失,并取均值
      • 如果updatertorch.optim.Optimizer的实例,则执行优化器的相关操作(梯度清零、梯度裁剪、参数更新)
      • 否则,仅执行梯度裁剪和模型参数的更新(适用于自定义的更新方法)
      • 将当前的损失值乘以当前批次样本的词元数量,累加到metric
    • 训练完成后,函数返回以下结果:

      • 对数似然损失的指数平均值(通过计算math.exp(metric[0] / metric[1])得到)
      • 平均每秒处理的词元数量(通过计算metric[1] / timer.stop()得到)

    b. train

    1. def train(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False):
    2. loss = nn.CrossEntropyLoss()
    3. animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
    4. legend=['train'], xlim=[10, num_epochs])
    5. if isinstance(net, nn.Module):
    6. updater = torch.optim.SGD(net.parameters(), lr)
    7. else:
    8. updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
    9. for epoch in range(num_epochs):
    10. ppl, speed = train_epoch(
    11. net, train_iter, loss, updater, device, use_random_iter)
    12. if (epoch + 1) % 10 == 0:
    13. animator.add(epoch + 1, [ppl])
    14. print('Train Done!')
    15. torch.save(net.state_dict(), 'chapter6.pth')
    16. print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
    • 参数
      • net(神经网络模型)
      • train_iter(训练数据迭代器)
      • vocab(词汇表)
      • lr(学习率)
      • num_epochs(训练的轮数)
      • device(计算设备)
      • use_random_iter(是否使用随机抽样)。
    • 在函数内部,它使用交叉熵损失函数(nn.CrossEntropyLoss())计算损失,创建了一个动画器(d2l.Animator)用于可视化训练过程中的困惑度(perplexity)指标。
    • 根据net的类型选择相应的更新器(updater
      • 如果netnn.Module的实例,则使用torch.optim.SGD作为更新器;
      • 否则,使用自定义的更新器(d2l.sgd)。
    • 通过迭代训练数据迭代器train_iter来进行训练。在每个训练周期(epoch)中
      • 调用train_epoch函数来执行训练,并得到每个周期的困惑度和处理速度。
      • 每隔10个周期,将困惑度添加到动画器中进行可视化。
    • 训练完成后,打印出训练完成的提示信息,并将训练好的模型参数保存到文件中('chapter6.pth')。
    • 打印出困惑度和处理速度的信息。

    4. predict(文本预测)

            定义了给定前缀序列,生成后续序列的predict函数。

    1. def predict(prefix, num_preds, net, vocab, device):
    2. state = net.begin_state(batch_size=1, device=device)
    3. outputs = [vocab[prefix[0]]]
    4. get_input = lambda: torch.reshape(torch.tensor(
    5. [outputs[-1]], device=device), (1, 1))
    6. for y in prefix[1:]: # 预热期
    7. _, state = net(get_input(), state)
    8. outputs.append(vocab[y])
    9. for _ in range(num_preds): # 预测num_preds步
    10. y, state = net(get_input(), state)
    11. outputs.append(int(y.argmax(dim=1).reshape(1)))
    12. return ''.join([vocab.idx_to_token[i] for i in outputs])
    • 使用指定的device和批大小为1调用net.begin_state(),初始化state变量。
    • 使用vocab[prefix[0]]将第一个标记在prefix中对应的索引添加到outputs列表中。
    • 定义了一个get_input函数,该函数返回最后一个输出标记经过reshape后的张量,作为神经网络的输入。
    • 对于prefix中除第一个标记外的每个标记,通过调用net(get_input(), state)进行前向传播。忽略输出的预测结果,并将对应的标记索引添加到outputs列表中。

    5. GPU判断函数

    1. def try_gpu(i=0):
    2. """如果存在,则返回gpu(i),否则返回cpu()"""
    3. if torch.cuda.device_count() >= i + 1:
    4. return torch.device(f'cuda:{i}')
    5. return torch.device('cpu')

    6. 训练与测试

    1. batch_size, num_steps = 32, 35
    2. train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
    3. vocab_size, num_hiddens, num_epochs, lr= 28, 256, 200, 1
    4. device = try_gpu()
    5. lstm_layer = nn.LSTM(vocab_size, num_hiddens)
    6. model_lstm = RNNModel(lstm_layer, vocab_size)
    7. train(model_lstm, train_iter, vocab, lr, num_epochs, device)
    8. print(predict('time ', 10, model_lstm, vocab, device))
    • 训练中每个小批次(batch)的大小和每个序列的时间步数(time step)的值分别为32,25

    • 加载的训练数据迭代器和词汇表

    • vocab_size 是词汇表的大小,num_hiddens 是 LSTM 隐藏层中的隐藏单元数量,num_epochs 是训练的迭代次数,lr 是学习率。

    • 选择可用的 GPU 设备进行训练,如果没有可用的 GPU,则会使用 CPU。

    • 训练模型

    7. 代码整合

    1. # 导入必要的库
    2. import torch
    3. from torch import nn
    4. import torch.nn.functional as F
    5. from d2l import torch as d2l
    6. import math
    7. class RNNModel(nn.Module):
    8. def __init__(self, rnn_layer, vocab_size, **kwargs):
    9. super(RNNModel, self).__init__(**kwargs)
    10. self.rnn = rnn_layer
    11. self.vocab_size = vocab_size
    12. self.num_hiddens = self.rnn.hidden_size
    13. self.num_directions = 1
    14. self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
    15. def forward(self, inputs, state):
    16. X = F.one_hot(inputs.T.long(), self.vocab_size)
    17. X = X.to(torch.float32)
    18. Y, state = self.rnn(X, state)
    19. # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
    20. # 它的输出形状是(时间步数*批量大小,词表大小)。
    21. output = self.linear(Y.reshape((-1, Y.shape[-1])))
    22. return output, state
    23. # 在第一个时间步,需要初始化一个隐藏状态,由此函数实现
    24. def begin_state(self, device, batch_size=1):
    25. if not isinstance(self.rnn, nn.LSTM):
    26. # nn.GRU以张量作为隐状态
    27. return torch.zeros((self.num_directions * self.rnn.num_layers,
    28. batch_size, self.num_hiddens),
    29. device=device)
    30. else:
    31. # nn.LSTM以元组作为隐状态
    32. return (torch.zeros((
    33. self.num_directions * self.rnn.num_layers,
    34. batch_size, self.num_hiddens), device=device),
    35. torch.zeros((
    36. self.num_directions * self.rnn.num_layers,
    37. batch_size, self.num_hiddens), device=device))
    38. def train(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False):
    39. loss = nn.CrossEntropyLoss()
    40. animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
    41. legend=['train'], xlim=[10, num_epochs])
    42. if isinstance(net, nn.Module):
    43. updater = torch.optim.SGD(net.parameters(), lr)
    44. else:
    45. updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
    46. for epoch in range(num_epochs):
    47. ppl, speed = train_epoch(
    48. net, train_iter, loss, updater, device, use_random_iter)
    49. if (epoch + 1) % 10 == 0:
    50. animator.add(epoch + 1, [ppl])
    51. print('Train Done!')
    52. torch.save(net.state_dict(), 'chapter6.pth')
    53. print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
    54. # 在notebook中,若在训练后直接预测,会直接使用训练后的参数。
    55. # 若想保存模型参数,请解除注释下面的代码,在实例化新的模型进行预测时,请记得读入模型。
    56. # 读入模型的方法请参考前几章实现的Runner类。
    57. # torch.save(net.state_dict(), 'chapter6.pth')
    58. def train_epoch(net, train_iter, loss, updater, device, use_random_iter):
    59. state, timer = None, d2l.Timer()
    60. metric = d2l.Accumulator(2) # 训练损失之和,词元数量
    61. for X, Y in train_iter:
    62. if state is None or use_random_iter:
    63. # 在第一次迭代或使用随机抽样时初始化state
    64. state = net.begin_state(batch_size=X.shape[0], device=device)
    65. if isinstance(net, nn.Module) and not isinstance(state, tuple):
    66. # state对于nn.GRU是个张量
    67. state.detach_()
    68. else:
    69. # state对于nn.LSTM或对于我们从零开始实现的模型是个张量
    70. for s in state:
    71. s.detach_()
    72. y = Y.T.reshape(-1)
    73. X, y = X.to(device), y.to(device)
    74. y_hat, state = net(X, state)
    75. l = loss(y_hat, y.long()).mean()
    76. if isinstance(updater, torch.optim.Optimizer):
    77. updater.zero_grad()
    78. l.backward()
    79. grad_clipping(net, 1)
    80. updater.step()
    81. else:
    82. l.backward()
    83. grad_clipping(net, 1)
    84. # 因为已经调用了mean函数
    85. updater(batch_size=1)
    86. metric.add(l * d2l.size(y), d2l.size(y))
    87. return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
    88. def predict(prefix, num_preds, net, vocab, device):
    89. state = net.begin_state(batch_size=1, device=device)
    90. outputs = [vocab[prefix[0]]]
    91. get_input = lambda: torch.reshape(torch.tensor(
    92. [outputs[-1]], device=device), (1, 1))
    93. for y in prefix[1:]: # 预热期
    94. _, state = net(get_input(), state)
    95. outputs.append(vocab[y])
    96. for _ in range(num_preds): # 预测num_preds步
    97. y, state = net(get_input(), state)
    98. outputs.append(int(y.argmax(dim=1).reshape(1)))
    99. return ''.join([vocab.idx_to_token[i] for i in outputs])
    100. def grad_clipping(net, theta):
    101. if isinstance(net, nn.Module):
    102. params = [p for p in net.parameters() if p.requires_grad]
    103. else:
    104. params = net.params
    105. norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    106. if norm > theta:
    107. for param in params:
    108. param.grad[:] *= theta / norm
    109. def try_gpu(i=0):
    110. """如果存在,则返回gpu(i),否则返回cpu()"""
    111. # if torch.cuda.device_count() >= i + 1:
    112. # return torch.device(f'cuda:{i}')
    113. return torch.device('cpu')
    114. batch_size, num_steps = 32, 35
    115. train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
    116. vocab_size, num_hiddens, num_epochs, lr= 28, 256, 200, 1
    117. device = try_gpu()
    118. lstm_layer = nn.LSTM(vocab_size, num_hiddens)
    119. model_lstm = RNNModel(lstm_layer, vocab_size)
    120. train(model_lstm, train_iter, vocab, lr, num_epochs, device)
    121. print(predict('time ', 10, model_lstm, vocab, device))

  • 相关阅读:
    Machine learning week 9(Andrew Ng)
    深度学习落地实战:基于UNet实现血管瘤超声图像分割
    牛客网AI面试第五轮
    Thread类的用法
    static关键字续、继承、重写、多态
    14 bs对象.节点名称.name attrs string 获取节点名称 属性 内容
    [毕业设计源码】PHP计算机信息管理学院网站
    新一代杂志新一代杂志社新一代编辑部2022年第13期目录
    python读取文件并处理转化为list然后输出
    Spring中Bean的作用域
  • 原文地址:https://blog.csdn.net/m0_63834988/article/details/133869012