• seq2seq翻译实战-Pytorch复现


    🍨 本文为[🔗365天深度学习训练营学习记录博客 🍦 参考文章:365天深度学习训练营 🍖 原作者:[K同学啊 | 接辅导、项目定制]\n🚀 文章来源:[K同学的学习圈子](https://www.yuque.com/mingtian-fkmxf/zxwb45)

    一、前期准备 

    1. from __future__ import unicode_literals, print_function, division
    2. from io import open
    3. import unicodedata
    4. import string
    5. import re
    6. import random
    7. import torch
    8. import torch.nn as nn
    9. from torch import optim
    10. import torch.nn.functional as F
    11. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    12. print(device)

    1.1 搭建语言类
     

    定义了两个常量 SOS_token 和 EOS_token,其分别代表序列的开始和结束。 Lang 类,用于方便对语料库进行操作:
    ●word2index 是一个字典,将单词映射到索引
    ●word2count 是一个字典,记录单词出现的次数
    ●index2word 是一个字典,将索引映射到单词
    ●n_words 是单词的数量,初始值为 2,因为序列开始和结束的单词已经被添加

    1. SOS_token = 0
    2. EOS_token = 1
    3. # 语言类,方便对语料库进行操作
    4. class Lang:
    5. def __init__(self, name):
    6. self.name = name
    7. self.word2index = {}
    8. self.word2count = {}
    9. self.index2word = {0: "SOS", 1: "EOS"}
    10. self.n_words = 2 # Count SOS and EOS
    11. def addSentence(self, sentence):
    12. for word in sentence.split(' '):
    13. self.addWord(word)
    14. def addWord(self, word):
    15. if word not in self.word2index:
    16. self.word2index[word] = self.n_words
    17. self.word2count[word] = 1
    18. self.index2word[self.n_words] = word
    19. self.n_words += 1
    20. else:
    21. self.word2count[word] += 1

    1.2 文本处理函数

    1. def unicodeToAscii(s):
    2. return ''.join(
    3. c for c in unicodedata.normalize('NFD', s)
    4. if unicodedata.category(c) != 'Mn'
    5. )
    6. # 小写化,剔除标点与非字母符号
    7. def normalizeString(s):
    8. s = unicodeToAscii(s.lower().strip())
    9. s = re.sub(r"([.!?])", r" \1", s)
    10. s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
    11. return s

    1.3 文件读取函数

    1. def readLangs(lang1, lang2, reverse=False):
    2. print("Reading lines...")
    3. # 以行为单位读取文件
    4. lines = open('%s-%s.txt' % (lang1, lang2), encoding='utf-8'). \
    5. read().strip().split('\n')
    6. # 将每一行放入一个列表中
    7. # 一个列表中有两个元素,A语言文本与B语言文本
    8. pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
    9. # 创建Lang实例,并确认是否反转语言顺序
    10. if reverse:
    11. pairs = [list(reversed(p)) for p in pairs]
    12. input_lang = Lang(lang2)
    13. output_lang = Lang(lang1)
    14. else:
    15. input_lang = Lang(lang1)
    16. output_lang = Lang(lang2)
    17. return input_lang, output_lang, pairs
    18. MAX_LENGTH = 10 # 定义语料最长长度
    19. eng_prefixes = (
    20. "i am ", "i m ",
    21. "he is", "he s ",
    22. "she is", "she s ",
    23. "you are", "you re ",
    24. "we are", "we re ",
    25. "they are", "they re "
    26. )
    27. def filterPair(p):
    28. return len(p[0].split(' ')) < MAX_LENGTH and \
    29. len(p[1].split(' ')) < MAX_LENGTH and p[1].startswith(eng_prefixes)
    30. def filterPairs(pairs):
    31. # 选取仅仅包含 eng_prefixes 开头的语料
    32. return [pair for pair in pairs if filterPair(pair)]
    33. def prepareData(lang1, lang2, reverse=False):
    34. # 读取文件中的数据
    35. input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse)
    36. print("Read %s sentence pairs" % len(pairs))
    37. # 按条件选取语料
    38. pairs = filterPairs(pairs[:])
    39. print("Trimmed to %s sentence pairs" % len(pairs))
    40. print("Counting words...")
    41. # 将语料保存至相应的语言类
    42. for pair in pairs:
    43. input_lang.addSentence(pair[0])
    44. output_lang.addSentence(pair[1])
    45. # 打印语言类的信息
    46. print("Counted words:")
    47. print(input_lang.name, input_lang.n_words)
    48. print(output_lang.name, output_lang.n_words)
    49. return input_lang, output_lang, pairs
    50. input_lang, output_lang, pairs = prepareData('eng', 'fra', True)
    51. print(random.choice(pairs))

    常量 MAX_LENGTH,表示语料中句子的最大长度。

    元组 eng_prefixes,包含一些英语句子的前缀。这些前缀用于筛选语料,只选择以这些前缀开头的句子

    filterPair 函数用于过滤语料对。它的返回值是一个布尔值,表示是否保留该语料对。这里的条件是:两个句子的长度都不超过 MAX_LENGTH,并且输出语句(第二个句子)以 eng_prefixes 中的某个前缀开头

    filterPairs 函数接受一个语料对列表,然后调用 filterPair 函数过滤掉不符合条件的语料对,返回一个新的语料对列表。

    prepareData 函数是主要的数据准备函数。它调用了之前定义的 readLangs 函数来读取语言对,然后使用 filterPairs 函数按条件过滤语料对。接着,它打印读取的句子对数、过滤后的句子对数,并统计语料中的词汇量。最后,它将语料保存到相应的语言类中,并返回这些语言类对象以及过滤后的语料对。

    二、Seq2Seq 模型

     2.1 编码器(Encoder)

    1. class EncoderRNN(nn.Module):
    2. def __init__(self, input_size, hidden_size):
    3. super(EncoderRNN, self).__init__()
    4. self.hidden_size = hidden_size
    5. self.embedding = nn.Embedding(input_size, hidden_size)
    6. self.gru = nn.GRU(hidden_size, hidden_size)
    7. def forward(self, input, hidden):
    8. embedded = self.embedding(input).view(1, 1, -1)
    9. output = embedded
    10. output, hidden = self.gru(output, hidden)
    11. return output, hidden
    12. def initHidden(self):
    13. return torch.zeros(1, 1, self.hidden_size, device=device)

    2.2 解码器(Decoder)

    1. class DecoderRNN(nn.Module):
    2. def __init__(self, hidden_size, output_size):
    3. super(DecoderRNN, self).__init__()
    4. self.hidden_size = hidden_size
    5. self.embedding = nn.Embedding(output_size, hidden_size)
    6. self.gru = nn.GRU(hidden_size, hidden_size)
    7. self.out = nn.Linear(hidden_size, output_size)
    8. self.softmax = nn.LogSoftmax(dim=1)
    9. def forward(self, input, hidden):
    10. output = self.embedding(input).view(1, 1, -1)
    11. output = F.relu(output)
    12. output, hidden = self.gru(output, hidden)
    13. output = self.softmax(self.out(output[0]))
    14. return output, hidden
    15. def initHidden(self):
    16. return torch.zeros(1, 1, self.hidden_size, device=device)

    三、训练

    3.1 数据预处理

    1. def indexesFromSentence(lang, sentence):
    2. return [lang.word2index[word] for word in sentence.split(' ')]
    3. # 将数字化的文本,转化为tensor数据
    4. def tensorFromSentence(lang, sentence):
    5. indexes = indexesFromSentence(lang, sentence)
    6. indexes.append(EOS_token)
    7. return torch.tensor(indexes, dtype=torch.long, device=device).view(-1, 1)
    8. # 输入pair文本,输出预处理好的数据
    9. def tensorsFromPair(pair):
    10. input_tensor = tensorFromSentence(input_lang, pair[0])
    11. target_tensor = tensorFromSentence(output_lang, pair[1])
    12. return (input_tensor, target_tensor)

    3.2 训练函数

    使用use_teacher_forcing 的目的是在训练过程中平衡解码器的预测能力和稳定性。以下是对两种策略的解释:
    1. Teacher Forcing:在每个时间步(di循环中),解码器的输入都是目标序列中的真实标签。这样做的好处是,解码器可以直接获得正确的输入信息,加快训练速度,并且在训练早期提供更准确的梯度信号,帮助解码器更好地学习。然而,过度依赖目标序列可能会导致模型过于敏感,一旦目标序列中出现错误,可能会在解码器中产生累积的误差。
    2. Without Teacher Forcing:在每个时间步,解码器的输入是前一个时间步的预测输出。这样做的好处是,解码器需要依靠自身的预测能力来生成下一个输入,从而更好地适应真实应用场景中可能出现的输入变化。这种策略可以提高模型的稳定性,但可能会导致训练过程更加困难,特别是在初始阶段。一般来说,Teacher Forcing策略在训练过程中可以帮助模型快速收敛,而Without Teacher Forcing策略则更接近真实应用中的生成场景。通常会使用一定比例的Teacher Forcing,在训练过程中逐渐减小这个比例,以便模型逐渐过渡到更自主的生成模式。
    综上所述,通过使用use_teacher_forcing 来选择不同的策略,可以在训练解码器时平衡模型的预测能力和稳定性,同时也提供了更灵活的生成模式选择。

    1. teacher_forcing_ratio = 0.5
    2. def train(input_tensor, target_tensor,
    3. encoder, decoder,
    4. encoder_optimizer, decoder_optimizer,
    5. criterion, max_length=MAX_LENGTH):
    6. # 编码器初始化
    7. encoder_hidden = encoder.initHidden()
    8. # grad属性归零
    9. encoder_optimizer.zero_grad()
    10. decoder_optimizer.zero_grad()
    11. input_length = input_tensor.size(0)
    12. target_length = target_tensor.size(0)
    13. # 用于创建一个指定大小的全零张量(tensor),用作默认编码器输出
    14. encoder_outputs = torch.zeros(max_length, encoder.hidden_size, device=device)
    15. loss = 0
    16. # 将处理好的语料送入编码器
    17. for ei in range(input_length):
    18. encoder_output, encoder_hidden = encoder(input_tensor[ei], encoder_hidden)
    19. encoder_outputs[ei] = encoder_output[0, 0]
    20. # 解码器默认输出
    21. decoder_input = torch.tensor([[SOS_token]], device=device)
    22. decoder_hidden = encoder_hidden
    23. use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False
    24. # 将编码器处理好的输出送入解码器
    25. if use_teacher_forcing:
    26. # Teacher forcing: Feed the target as the next input
    27. for di in range(target_length):
    28. decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
    29. loss += criterion(decoder_output, target_tensor[di])
    30. decoder_input = target_tensor[di] # Teacher forcing
    31. else:
    32. # Without teacher forcing: use its own predictions as the next input
    33. for di in range(target_length):
    34. decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
    35. topv, topi = decoder_output.topk(1)
    36. decoder_input = topi.squeeze().detach() # detach from history as input
    37. loss += criterion(decoder_output, target_tensor[di])
    38. if decoder_input.item() == EOS_token:
    39. break
    40. loss.backward()
    41. encoder_optimizer.step()
    42. decoder_optimizer.step()
    43. return loss.item() / target_length
    44. import time
    45. import math
    46. def asMinutes(s):
    47. m = math.floor(s / 60)
    48. s -= m * 60
    49. return '%dm %ds' % (m, s)
    50. def timeSince(since, percent):
    51. now = time.time()
    52. s = now - since
    53. es = s / (percent)
    54. rs = es - s
    55. return '%s (- %s)' % (asMinutes(s), asMinutes(rs))
    56. def trainIters(encoder,decoder,n_iters,print_every=1000,
    57. plot_every=100,learning_rate=0.01):
    58. start = time.time()
    59. plot_losses = []
    60. print_loss_total = 0 # Reset every print_every
    61. plot_loss_total = 0 # Reset every plot_every
    62. encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
    63. decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
    64. # 在 pairs 中随机选取 n_iters 条数据用作训练集
    65. training_pairs = [tensorsFromPair(random.choice(pairs)) for i in range(n_iters)]
    66. criterion = nn.NLLLoss()
    67. for iter in range(1, n_iters + 1):
    68. training_pair = training_pairs[iter - 1]
    69. input_tensor = training_pair[0]
    70. target_tensor = training_pair[1]
    71. loss = train(input_tensor, target_tensor, encoder,
    72. decoder, encoder_optimizer, decoder_optimizer, criterion)
    73. print_loss_total += loss
    74. plot_loss_total += loss
    75. if iter % print_every == 0:
    76. print_loss_avg = print_loss_total / print_every
    77. print_loss_total = 0
    78. print('%s (%d %d%%) %.4f' % (timeSince(start, iter / n_iters),
    79. iter, iter / n_iters * 100, print_loss_avg))
    80. if iter % plot_every == 0:
    81. plot_loss_avg = plot_loss_total / plot_every
    82. plot_losses.append(plot_loss_avg)
    83. plot_loss_total = 0
    84. return plot_losses

    四、训练与评估

    1. hidden_size = 256
    2. encoder1 = EncoderRNN(input_lang.n_words, hidden_size).to(device)
    3. attn_decoder1 = DecoderRNN(hidden_size, output_lang.n_words).to(device)
    4. plot_losses = trainIters(encoder1, attn_decoder1, 100000, print_every=5000)

     

    1. import matplotlib.pyplot as plt
    2. #隐藏警告
    3. import warnings
    4. warnings.filterwarnings("ignore") # 忽略警告信息
    5. # plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
    6. plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
    7. plt.rcParams['figure.dpi'] = 100 # 分辨率
    8. epochs_range = range(len(plot_losses))
    9. plt.figure(figsize=(8, 3))
    10. plt.subplot(1, 1, 1)
    11. plt.plot(epochs_range, plot_losses, label='Training Loss')
    12. plt.legend(loc='upper right')
    13. plt.title('Training Loss')
    14. plt.show()

  • 相关阅读:
    vue watch 侦听器 监视器
    ubuntu20.04安装webots仿真
    【MATLAB】史上最全的9种频谱分析算法全家桶
    Spark作业串行与并行提交job
    传奇外网架设常见的问题及解决办法-传奇创建人物失败/不开门/PAK显示密码错误/脚本错误
    LeetCode 22. 括号生成【字符串,回溯;动态规划】中等
    Nature、science、cell旗下刊物
    以太坊智能合约的技术与组件
    技能大赛dhcp服务训练题
    【状语从句练习题】although vs but
  • 原文地址:https://blog.csdn.net/qq_60245590/article/details/136549068