当前位置:   article > 正文

python 大模型生成任务中的贪婪解码和集束搜索,贪婪解码和集束搜索和区别,大模型中贪婪解码的实现;用贪婪解码实现机器翻译任务;transformer模型源码;LLMs的贪婪解码_贪婪解码算法

贪婪解码算法

1.贪婪解码和集束搜索

贪婪解码和集束搜索都是用于生成文本的解码算法。它们在效果和计算复杂度上有所区别。

        贪婪解码是一种简单直接的方法,每次选择具有最高概率的下一个字符或单词作为输出。它的优点是计算效率高,因为只需进行一次预测,而不需要考虑其他可能的路径。然而,贪婪解码可能会导致输出不够流畅和连贯,因为它只关注当前步骤的最佳选择,而忽略了整体的序列优化。

        相比之下,集束搜索考虑了多个可能的路径。它维护一个候选集束,每一步都根据概率进行扩展和剪枝。集束搜索能够在全局范围内搜索更多的可能性,因此可以生成更准确、连贯的文本。但是,集束搜索的计算复杂度较高,需要更多的时间和计算资源。

        因此,贪婪解码适用于快速生成文本的应用,但可能牺牲一定的准确性和流畅性。而对于更重视生成质量的任务,集束搜索提供了更好的性能,尽管生成速度可能较慢。实际应用中,可以根据具体需求和资源限制选择合适的解码策略。

2.贪婪解码的实现

首先我们要先复习一下transformer的结构,如果烂熟于心的同学可以直接移步到后面:

2.1定义缩放点积注意力类

其实就是QKV的计算过程

  1. import numpy as np # 导入 numpy 库
  2. import torch # 导入 torch 库
  3. import torch.nn as nn # 导入 torch.nn 库
  4. d_k = 64 # K(=Q) 维度
  5. d_v = 64 # V 维度
  6. # 定义缩放点积注意力类
  7. class ScaledDotProductAttention(nn.Module):
  8. def __init__(self):
  9. super(ScaledDotProductAttention, self).__init__()
  10. def forward(self, Q, K, V, attn_mask):
  11. #------------------------- 维度信息 --------------------------------
  12. # Q K V [batch_size, n_heads, len_q/k/v, dim_q=k/v] (dim_q=dim_k)
  13. # attn_mask [batch_size, n_heads, len_q, len_k]
  14. #----------------------------------------------------------------
  15. # 计算注意力分数(原始权重)[batch_size,n_heads,len_q,len_k]
  16. scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)
  17. #------------------------- 维度信息 --------------------------------
  18. # scores [batch_size, n_heads, len_q, len_k]
  19. #-----------------------------------------------------------------
  20. # 使用注意力掩码,将 attn_mask 中值为 1 的位置的权重替换为极小值
  21. #------------------------- 维度信息 --------------------------------
  22. # attn_mask [batch_size, n_heads, len_q, len_k], 形状和 scores 相同
  23. #-----------------------------------------------------------------
  24. scores.masked_fill_(attn_mask, -1e9)
  25. # 对注意力分数进行 softmax 归一化
  26. weights = nn.Softmax(dim=-1)(scores)
  27. #------------------------- 维度信息 --------------------------------
  28. # weights [batch_size, n_heads, len_q, len_k], 形状和 scores 相同
  29. #-----------------------------------------------------------------
  30. # 计算上下文向量(也就是注意力的输出), 是上下文信息的紧凑表示
  31. context = torch.matmul(weights, V)
  32. #------------------------- 维度信息 --------------------------------
  33. # context [batch_size, n_heads, len_q, dim_v]
  34. #-----------------------------------------------------------------
  35. return context, weights # 返回上下文向量和注意力分数

2.2 定义多头自注意力类 

这一步主要为:经过线性变换得到qkv,然后分割为多头,然后计算点积注意力(承接上面代码),然后把多头结果concat,经过线性层,残差连接,归一化

  1. # 定义多头自注意力类
  2. d_embedding = 512 # Embedding 的维度
  3. n_heads = 8 # Multi-Head Attention 中头的个数
  4. batch_size = 3 # 每一批的数据大小
  5. class MultiHeadAttention(nn.Module):
  6. def __init__(self):
  7. super(MultiHeadAttention, self).__init__()
  8. self.W_Q = nn.Linear(d_embedding, d_k * n_heads) # Q的线性变换层
  9. self.W_K = nn.Linear(d_embedding, d_k * n_heads) # K的线性变换层
  10. self.W_V = nn.Linear(d_embedding, d_v * n_heads) # V的线性变换层
  11. self.linear = nn.Linear(n_heads * d_v, d_embedding)
  12. self.layer_norm = nn.LayerNorm(d_embedding)
  13. def forward(self, Q, K, V, attn_mask):
  14. #------------------------- 维度信息 --------------------------------
  15. # Q K V [batch_size, len_q/k/v, embedding_dim]
  16. #-----------------------------------------------------------------
  17. residual, batch_size = Q, Q.size(0) # 保留残差连接
  18. # 将输入进行线性变换和重塑,以便后续处理
  19. q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)
  20. k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2)
  21. v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2)
  22. #------------------------- 维度信息 --------------------------------
  23. # q_s k_s v_s: [batch_size, n_heads, len_q/k/v, d_q=k/v]
  24. #-----------------------------------------------------------------
  25. # 将注意力掩码复制到多头 attn_mask: [batch_size, n_heads, len_q, len_k]
  26. attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)
  27. #------------------------- 维度信息 --------------------------------
  28. # attn_mask [batch_size, n_heads, len_q, len_k]
  29. #-----------------------------------------------------------------
  30. # 使用缩放点积注意力计算上下文和注意力权重
  31. context, weights = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
  32. #------------------------- 维度信息 --------------------------------
  33. # context [batch_size, n_heads, len_q, dim_v]
  34. # weights [batch_size, n_heads, len_q, len_k]
  35. #-----------------------------------------------------------------
  36. # 通过调整维度将多个头的上下文向量连接在一起
  37. context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v)
  38. #------------------------- 维度信息 --------------------------------
  39. # context [batch_size, len_q, n_heads * dim_v]
  40. #-----------------------------------------------------------------
  41. # 用一个线性层把连接后的多头自注意力结果转换,原始地嵌入维度
  42. output = self.linear(context)
  43. #------------------------- 维度信息 --------------------------------
  44. # output [batch_size, len_q, embedding_dim]
  45. #-----------------------------------------------------------------
  46. # 与输入 (Q) 进行残差链接,并进行层归一化后输出
  47. output = self.layer_norm(output + residual)
  48. #------------------------- 维度信息 --------------------------------
  49. # output [batch_size, len_q, embedding_dim]
  50. #-----------------------------------------------------------------
  51. return output, weights # 返回层归一化的输出和注意力权重

2.3 定义逐位置前馈网络类 

经过多头注意力机制的输出output,进入前馈神经网络,conv1->relu->conv1,其实就是把特征投影到高维然后再降回来,最后残差连接output + residual,然后归一化

  1. # 定义逐位置前馈网络类
  2. class PoswiseFeedForwardNet(nn.Module):
  3. def __init__(self, d_ff=2048):
  4. super(PoswiseFeedForwardNet, self).__init__()
  5. # 定义一维卷积层 1,用于将输入映射到更高维度
  6. self.conv1 = nn.Conv1d(in_channels=d_embedding, out_channels=d_ff, kernel_size=1)
  7. # 定义一维卷积层 2,用于将输入映射回原始维度
  8. self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_embedding, kernel_size=1)
  9. # 定义层归一化
  10. self.layer_norm = nn.LayerNorm(d_embedding)
  11. def forward(self, inputs):
  12. #------------------------- 维度信息 --------------------------------
  13. # inputs [batch_size, len_q, embedding_dim]
  14. #----------------------------------------------------------------
  15. residual = inputs # 保留残差连接
  16. # 在卷积层 1 后使用 ReLU 激活函数
  17. output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))
  18. #------------------------- 维度信息 --------------------------------
  19. # output [batch_size, d_ff, len_q]
  20. #----------------------------------------------------------------
  21. # 使用卷积层 2 进行降维
  22. output = self.conv2(output).transpose(1, 2)
  23. #------------------------- 维度信息 --------------------------------
  24. # output [batch_size, len_q, embedding_dim]
  25. #----------------------------------------------------------------
  26. # 与输入进行残差链接,并进行层归一化
  27. output = self.layer_norm(output + residual)
  28. #------------------------- 维度信息 --------------------------------
  29. # output [batch_size, len_q, embedding_dim]
  30. #----------------------------------------------------------------
  31. return output # 返回加入残差连接后层归一化的结果

2.4 生成正弦位置编码表的函数

用于在 Transformer 中引入位置信息 

  1. # 生成正弦位置编码表的函数,用于在 Transformer 中引入位置信息
  2. def get_sin_enc_table(n_position, embedding_dim):
  3. #------------------------- 维度信息 --------------------------------
  4. # n_position: 输入序列的最大长度
  5. # embedding_dim: 词嵌入向量的维度
  6. #-----------------------------------------------------------------
  7. # 根据位置和维度信息,初始化正弦位置编码表
  8. sinusoid_table = np.zeros((n_position, embedding_dim))
  9. # 遍历所有位置和维度,计算角度值
  10. for pos_i in range(n_position):
  11. for hid_j in range(embedding_dim):
  12. angle = pos_i / np.power(10000, 2 * (hid_j // 2) / embedding_dim)
  13. sinusoid_table[pos_i, hid_j] = angle
  14. # 计算正弦和余弦值
  15. sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i 偶数维
  16. sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1 奇数维
  17. #------------------------- 维度信息 --------------------------------
  18. # sinusoid_table 的维度是 [n_position, embedding_dim]
  19. #----------------------------------------------------------------
  20. return torch.FloatTensor(sinusoid_table) # 返回正弦位置编码表

2.5 定义填充注意力掩码函数

主要用于做padding mask

  1. # 定义填充注意力掩码函数
  2. def get_attn_pad_mask(seq_q, seq_k):
  3. #------------------------- 维度信息 --------------------------------
  4. # seq_q 的维度是 [batch_size, len_q]
  5. # seq_k 的维度是 [batch_size, len_k]
  6. #-----------------------------------------------------------------
  7. batch_size, len_q = seq_q.size()
  8. batch_size, len_k = seq_k.size()
  9. # 生成布尔类型张量
  10. pad_attn_mask = seq_k.data.eq(0).unsqueeze(1) # <PAD>token 的编码值为 0
  11. #------------------------- 维度信息 --------------------------------
  12. # pad_attn_mask 的维度是 [batch_size,1,len_k]
  13. #-----------------------------------------------------------------
  14. # 变形为与注意力分数相同形状的张量
  15. pad_attn_mask = pad_attn_mask.expand(batch_size, len_q, len_k)
  16. #------------------------- 维度信息 --------------------------------
  17. # pad_attn_mask 的维度是 [batch_size,len_q,len_k]
  18. #-----------------------------------------------------------------
  19. return pad_attn_mask

 2.6 定义编码器层类

即每一层的编码器结构,其实就两个结构:多头自注意力机制和前馈神经网络

  1. # 定义编码器层类
  2. class EncoderLayer(nn.Module):
  3. def __init__(self):
  4. super(EncoderLayer, self).__init__()
  5. self.enc_self_attn = MultiHeadAttention() # 多头自注意力层
  6. self.pos_ffn = PoswiseFeedForwardNet() # 位置前馈神经网络层
  7. def forward(self, enc_inputs, enc_self_attn_mask):
  8. #------------------------- 维度信息 --------------------------------
  9. # enc_inputs 的维度是 [batch_size, seq_len, embedding_dim]
  10. # enc_self_attn_mask 的维度是 [batch_size, seq_len, seq_len]
  11. #-----------------------------------------------------------------
  12. # 将相同的 Q,K,V 输入多头自注意力层 , 返回的 attn_weights 增加了头数
  13. enc_outputs, attn_weights = self.enc_self_attn(enc_inputs, enc_inputs,
  14. enc_inputs, enc_self_attn_mask)
  15. #------------------------- 维度信息 --------------------------------
  16. # enc_outputs 的维度是 [batch_size, seq_len, embedding_dim]
  17. # attn_weights 的维度是 [batch_size, n_heads, seq_len, seq_len]
  18. # 将多头自注意力 outputs 输入位置前馈神经网络层
  19. enc_outputs = self.pos_ffn(enc_outputs) # 维度与 enc_inputs 相同
  20. #------------------------- 维度信息 --------------------------------
  21. # enc_outputs 的维度是 [batch_size, seq_len, embedding_dim]
  22. #-----------------------------------------------------------------
  23. return enc_outputs, attn_weights # 返回编码器输出和每层编码器注意力权重

2.7 定义编码器类

两部分结构:embedding和编码器

  1. # 定义编码器类
  2. n_layers = 6 # 设置 Encoder 的层数
  3. class Encoder(nn.Module):
  4. def __init__(self, corpus):
  5. super(Encoder, self).__init__()
  6. self.src_emb = nn.Embedding(len(corpus.src_vocab), d_embedding) # 词嵌入层
  7. self.pos_emb = nn.Embedding.from_pretrained( \
  8. get_sin_enc_table(corpus.src_len+1, d_embedding), freeze=True) # 位置嵌入层
  9. self.layers = nn.ModuleList(EncoderLayer() for _ in range(n_layers))# 编码器层数
  10. def forward(self, enc_inputs):
  11. #------------------------- 维度信息 --------------------------------
  12. # enc_inputs 的维度是 [batch_size, source_len]
  13. #-----------------------------------------------------------------
  14. # 创建一个从 1 到 source_len 的位置索引序列
  15. pos_indices = torch.arange(1, enc_inputs.size(1) + 1).unsqueeze(0).to(enc_inputs)
  16. #------------------------- 维度信息 --------------------------------
  17. # pos_indices 的维度是 [1, source_len]
  18. #-----------------------------------------------------------------
  19. # 对输入进行词嵌入和位置嵌入相加 [batch_size, source_len,embedding_dim]
  20. enc_outputs = self.src_emb(enc_inputs) + self.pos_emb(pos_indices)
  21. #------------------------- 维度信息 --------------------------------
  22. # enc_outputs 的维度是 [batch_size, seq_len, embedding_dim]
  23. #-----------------------------------------------------------------
  24. # 生成自注意力掩码
  25. enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
  26. #------------------------- 维度信息 --------------------------------
  27. # enc_self_attn_mask 的维度是 [batch_size, len_q, len_k]
  28. #-----------------------------------------------------------------
  29. enc_self_attn_weights = [] # 初始化 enc_self_attn_weights
  30. # 通过编码器层 [batch_size, seq_len, embedding_dim]
  31. for layer in self.layers:
  32. enc_outputs, enc_self_attn_weight = layer(enc_outputs, enc_self_attn_mask)
  33. enc_self_attn_weights.append(enc_self_attn_weight)
  34. #------------------------- 维度信息 --------------------------------
  35. # enc_outputs 的维度是 [batch_size, seq_len, embedding_dim] 维度与 enc_inputs 相同
  36. # enc_self_attn_weights 是一个列表,每个元素的维度是 [batch_size, n_heads, seq_len, seq_len]
  37. #-----------------------------------------------------------------
  38. return enc_outputs, enc_self_attn_weights # 返回编码器输出和编码器注意力权重

2.7 后续注意力掩码的函数

即sequence mask的实现函数

  1. # 生成后续注意力掩码的函数,用于在多头自注意力计算中忽略未来信息
  2. def get_attn_subsequent_mask(seq):
  3. #------------------------- 维度信息 --------------------------------
  4. # seq 的维度是 [batch_size, seq_len(Q)=seq_len(K)]
  5. #-----------------------------------------------------------------
  6. # 获取输入序列的形状
  7. attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
  8. #------------------------- 维度信息 --------------------------------
  9. # attn_shape 是一个一维张量 [batch_size, seq_len(Q), seq_len(K)]
  10. #-----------------------------------------------------------------
  11. # 使用 numpy 创建一个上三角矩阵(triu = triangle upper)
  12. subsequent_mask = np.triu(np.ones(attn_shape), k=1)
  13. #------------------------- 维度信息 --------------------------------
  14. # subsequent_mask 的维度是 [batch_size, seq_len(Q), seq_len(K)]
  15. #-----------------------------------------------------------------
  16. # 将 numpy 数组转换为 PyTorch 张量,并将数据类型设置为 byte(布尔值)
  17. subsequent_mask = torch.from_numpy(subsequent_mask).byte()
  18. #------------------------- 维度信息 --------------------------------
  19. # 返回的 subsequent_mask 的维度是 [batch_size, seq_len(Q), seq_len(K)]
  20. #-----------------------------------------------------------------
  21. return subsequent_mask # 返回后续位置的注意力掩码

2.8 定义解码器层类 

注意这里定义两个多头注意力机制结构,即解码器的自注意力编码器-解码器的注意力

  1. # 定义解码器层类
  2. class DecoderLayer(nn.Module):
  3. def __init__(self):
  4. super(DecoderLayer, self).__init__()
  5. self.dec_self_attn = MultiHeadAttention() # 多头自注意力层
  6. self.dec_enc_attn = MultiHeadAttention() # 多头自注意力层,连接编码器和解码器
  7. self.pos_ffn = PoswiseFeedForwardNet() # 位置前馈神经网络层
  8. def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
  9. #------------------------- 维度信息 --------------------------------
  10. # dec_inputs 的维度是 [batch_size, target_len, embedding_dim]
  11. # enc_outputs 的维度是 [batch_size, source_len, embedding_dim]
  12. # dec_self_attn_mask 的维度是 [batch_size, target_len, target_len]
  13. # dec_enc_attn_mask 的维度是 [batch_size, target_len, source_len]
  14. #-----------------------------------------------------------------
  15. # 将相同的 Q,K,V 输入多头自注意力层
  16. dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs,
  17. dec_inputs, dec_self_attn_mask)
  18. #------------------------- 维度信息 --------------------------------
  19. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  20. # dec_self_attn 的维度是 [batch_size, n_heads, target_len, target_len]
  21. #-----------------------------------------------------------------
  22. # 将解码器输出和编码器输出输入多头自注意力层
  23. dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs,
  24. enc_outputs, dec_enc_attn_mask)
  25. #------------------------- 维度信息 --------------------------------
  26. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  27. # dec_enc_attn 的维度是 [batch_size, n_heads, target_len, source_len]
  28. #-----------------------------------------------------------------
  29. # 输入位置前馈神经网络层
  30. dec_outputs = self.pos_ffn(dec_outputs)
  31. #------------------------- 维度信息 --------------------------------
  32. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  33. # dec_self_attn 的维度是 [batch_size, n_heads, target_len, target_len]
  34. # dec_enc_attn 的维度是 [batch_size, n_heads, target_len, source_len]
  35. #-----------------------------------------------------------------
  36. # 返回解码器层输出,每层的自注意力和解 - 编码器注意力权重
  37. return dec_outputs, dec_self_attn, dec_enc_attn

2.9 定义解码器类

即:词嵌入+位置嵌入+多层解码器

注意:解码器的输入和编码器的输入内容是不一样的

  1. # 定义解码器类
  2. n_layers = 6 # 设置 Decoder 的层数
  3. class Decoder(nn.Module):
  4. def __init__(self, corpus):
  5. super(Decoder, self).__init__()
  6. self.tgt_emb = nn.Embedding(len(corpus.tgt_vocab), d_embedding) # 词嵌入层
  7. self.pos_emb = nn.Embedding.from_pretrained( \
  8. get_sin_enc_table(corpus.tgt_len+1, d_embedding), freeze=True) # 位置嵌入层
  9. self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)]) # 叠加多层
  10. def forward(self, dec_inputs, enc_inputs, enc_outputs):
  11. #------------------------- 维度信息 --------------------------------
  12. # dec_inputs 的维度是 [batch_size, target_len]
  13. # enc_inputs 的维度是 [batch_size, source_len]
  14. # enc_outputs 的维度是 [batch_size, source_len, embedding_dim]
  15. #-----------------------------------------------------------------
  16. # 创建一个从 1 到 source_len 的位置索引序列
  17. pos_indices = torch.arange(1, dec_inputs.size(1) + 1).unsqueeze(0).to(dec_inputs)
  18. #------------------------- 维度信息 --------------------------------
  19. # pos_indices 的维度是 [1, target_len]
  20. #-----------------------------------------------------------------
  21. # 对输入进行词嵌入和位置嵌入相加
  22. dec_outputs = self.tgt_emb(dec_inputs) + self.pos_emb(pos_indices)
  23. #------------------------- 维度信息 --------------------------------
  24. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  25. #-----------------------------------------------------------------
  26. # 生成解码器自注意力掩码和解码器 - 编码器注意力掩码
  27. dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs) # 填充位掩码
  28. dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs) # 后续位掩码
  29. dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask \
  30. + dec_self_attn_subsequent_mask), 0)
  31. dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs) # 解码器 - 编码器掩码
  32. #------------------------- 维度信息 --------------------------------
  33. # dec_self_attn_pad_mask 的维度是 [batch_size, target_len, target_len]
  34. # dec_self_attn_subsequent_mask 的维度是 [batch_size, target_len, target_len]
  35. # dec_self_attn_mask 的维度是 [batch_size, target_len, target_len]
  36. # dec_enc_attn_mask 的维度是 [batch_size, target_len, source_len]
  37. #-----------------------------------------------------------------
  38. dec_self_attns, dec_enc_attns = [], [] # 初始化 dec_self_attns, dec_enc_attns
  39. # 通过解码器层 [batch_size, seq_len, embedding_dim]
  40. for layer in self.layers:
  41. dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs,
  42. dec_self_attn_mask, dec_enc_attn_mask)
  43. dec_self_attns.append(dec_self_attn)
  44. dec_enc_attns.append(dec_enc_attn)
  45. #------------------------- 维度信息 --------------------------------
  46. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  47. # dec_self_attns 是一个列表,每个元素的维度是 [batch_size, n_heads, target_len, target_len]
  48. # dec_enc_attns 是一个列表,每个元素的维度是 [batch_size, n_heads, target_len, source_len]
  49. #-----------------------------------------------------------------
  50. # 返回解码器输出,解码器自注意力和解码器 - 编码器注意力权重
  51. return dec_outputs, dec_self_attns, dec_enc_attns

2.10 定义 Transformer 模型

注意:在最后定义线性投影层,将解码器输出转换为目标词汇表大小的概率分布

  1. # 定义 Transformer 模型
  2. class Transformer(nn.Module):
  3. def __init__(self, corpus):
  4. super(Transformer, self).__init__()
  5. self.encoder = Encoder(corpus) # 初始化编码器实例
  6. self.decoder = Decoder(corpus) # 初始化解码器实例
  7. # 定义线性投影层,将解码器输出转换为目标词汇表大小的概率分布
  8. self.projection = nn.Linear(d_embedding, len(corpus.tgt_vocab), bias=False)
  9. def forward(self, enc_inputs, dec_inputs):
  10. #------------------------- 维度信息 --------------------------------
  11. # enc_inputs 的维度是 [batch_size, source_seq_len]
  12. # dec_inputs 的维度是 [batch_size, target_seq_len]
  13. #-----------------------------------------------------------------
  14. # 将输入传递给编码器,并获取编码器输出和自注意力权重
  15. enc_outputs, enc_self_attns = self.encoder(enc_inputs)
  16. #------------------------- 维度信息 --------------------------------
  17. # enc_outputs 的维度是 [batch_size, source_len, embedding_dim]
  18. # enc_self_attns 是一个列表,每个元素的维度是 [batch_size, n_heads, src_seq_len, src_seq_len]
  19. #-----------------------------------------------------------------
  20. # 将编码器输出、解码器输入和编码器输入传递给解码器
  21. # 获取解码器输出、解码器自注意力权重和编码器 - 解码器注意力权重
  22. dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)
  23. #------------------------- 维度信息 --------------------------------
  24. # dec_outputs 的维度是 [batch_size, target_len, embedding_dim]
  25. # dec_self_attns 是一个列表,每个元素的维度是 [batch_size, n_heads, tgt_seq_len, src_seq_len]
  26. # dec_enc_attns 是一个列表,每个元素的维度是 [batch_size, n_heads, tgt_seq_len, src_seq_len]
  27. #-----------------------------------------------------------------
  28. # 将解码器输出传递给投影层,生成目标词汇表大小的概率分布
  29. dec_logits = self.projection(dec_outputs)
  30. #------------------------- 维度信息 --------------------------------
  31. # dec_logits 的维度是 [batch_size, tgt_seq_len, tgt_vocab_size]
  32. #-----------------------------------------------------------------
  33. # 返回逻辑值 ( 原始预测结果 ), 编码器自注意力权重,解码器自注意力权重,解 - 编码器注意力权重
  34. return dec_logits, enc_self_attns, dec_self_attns, dec_enc_attns

2.11 准备数据: 机器翻译任务

  1. sentences = [
  2. ['成哥 喜欢 小冰', 'ChengGe likes XiaoBing'],
  3. ['我 爱 学习 人工智能', 'I love studying AI'],
  4. ['深度学习 改变 世界', ' DL changed the world'],
  5. ['自然语言处理 很 强大', 'NLP is powerful'],
  6. ['神经网络 非常 复杂', 'Neural-networks are complex'] ]

 2.12 定义 TranslationCorpus 类

注意:生成式任务的数据处理比较重要

  1. from collections import Counter # 导入 Counter 类
  2. # 定义 TranslationCorpus 类
  3. class TranslationCorpus:
  4. def __init__(self, sentences):
  5. self.sentences = sentences
  6. # 计算源语言和目标语言的最大句子长度,并分别加 1 和 2 以容纳填充符和特殊符号
  7. self.src_len = max(len(sentence[0].split()) for sentence in sentences) + 1
  8. self.tgt_len = max(len(sentence[1].split()) for sentence in sentences) + 2
  9. # 创建源语言和目标语言的词汇表
  10. self.src_vocab, self.tgt_vocab = self.create_vocabularies()
  11. # 创建索引到单词的映射
  12. self.src_idx2word = {v: k for k, v in self.src_vocab.items()}
  13. self.tgt_idx2word = {v: k for k, v in self.tgt_vocab.items()}
  14. # 定义创建词汇表的函数
  15. def create_vocabularies(self):
  16. # 统计源语言和目标语言的单词频率
  17. src_counter = Counter(word for sentence in self.sentences for word in sentence[0].split())
  18. tgt_counter = Counter(word for sentence in self.sentences for word in sentence[1].split())
  19. # 创建源语言和目标语言的词汇表,并为每个单词分配一个唯一的索引
  20. src_vocab = {'<pad>': 0, **{word: i+1 for i, word in enumerate(src_counter)}}
  21. tgt_vocab = {'<pad>': 0, '<sos>': 1, '<eos>': 2,
  22. **{word: i+3 for i, word in enumerate(tgt_counter)}}
  23. return src_vocab, tgt_vocab
  24. # 定义创建批次数据的函数
  25. def make_batch(self, batch_size, test_batch=False):
  26. input_batch, output_batch, target_batch = [], [], []
  27. # 随机选择句子索引
  28. sentence_indices = torch.randperm(len(self.sentences))[:batch_size]
  29. for index in sentence_indices:
  30. src_sentence, tgt_sentence = self.sentences[index]
  31. # 将源语言和目标语言的句子转换为索引序列
  32. src_seq = [self.src_vocab[word] for word in src_sentence.split()]
  33. tgt_seq = [self.tgt_vocab['<sos>']] + [self.tgt_vocab[word] \
  34. for word in tgt_sentence.split()] + [self.tgt_vocab['<eos>']]
  35. # 对源语言和目标语言的序列进行填充
  36. src_seq += [self.src_vocab['<pad>']] * (self.src_len - len(src_seq))
  37. tgt_seq += [self.tgt_vocab['<pad>']] * (self.tgt_len - len(tgt_seq))
  38. # 将处理好的序列添加到批次中
  39. input_batch.append(src_seq)
  40. output_batch.append([self.tgt_vocab['<sos>']] + ([self.tgt_vocab['<pad>']] * \
  41. (self.tgt_len - 2)) if test_batch else tgt_seq[:-1])
  42. target_batch.append(tgt_seq[1:])
  43. # 将批次转换为 LongTensor 类型
  44. input_batch = torch.LongTensor(input_batch)
  45. output_batch = torch.LongTensor(output_batch)
  46. target_batch = torch.LongTensor(target_batch)
  47. return input_batch, output_batch, target_batch
  48. # 创建语料库类实例
  49. corpus = TranslationCorpus(sentences)

 2.13 开始训练和测试

训练模型

  1. import torch # 导入 torch
  2. import torch.optim as optim # 导入优化器
  3. model = Transformer(corpus) # 创建模型实例
  4. criterion = nn.CrossEntropyLoss() # 损失函数
  5. optimizer = optim.Adam(model.parameters(), lr=0.0001) # 优化器
  6. epochs = 5 # 训练轮次
  7. for epoch in range(epochs): # 训练 100 轮
  8. optimizer.zero_grad() # 梯度清零
  9. enc_inputs, dec_inputs, target_batch = corpus.make_batch(batch_size) # 创建训练数据
  10. outputs, _, _, _ = model(enc_inputs, dec_inputs) # 获取模型输出
  11. loss = criterion(outputs.view(-1, len(corpus.tgt_vocab)), target_batch.view(-1)) # 计算损失
  12. if (epoch + 1) % 1 == 0: # 打印损失
  13. print(f"Epoch: {epoch + 1:04d} cost = {loss:.6f}")
  14. loss.backward()# 反向传播
  15. optimizer.step()# 更新参数

 测试模型

  1. # 创建一个大小为 1 的批次,目标语言序列 dec_inputs 在测试阶段,仅包含句子开始符号 <sos>
  2. enc_inputs, dec_inputs, target_batch = corpus.make_batch(batch_size=1,test_batch=True)
  3. print("编码器输入 :", enc_inputs) # 打印编码器输入
  4. print("解码器输入 :", dec_inputs) # 打印解码器输入
  5. print("目标数据 :", target_batch) # 打印目标数据
  6. predict, enc_self_attns, dec_self_attns, dec_enc_attns = model(enc_inputs, dec_inputs) # 用模型进行翻译
  7. predict = predict.view(-1, len(corpus.tgt_vocab)) # 将预测结果维度重塑
  8. predict = predict.data.max(1, keepdim=True)[1] # 找到每个位置概率最大的词汇的索引
  9. # 解码预测的输出,将所预测的目标句子中的索引转换为单词
  10. translated_sentence = [corpus.tgt_idx2word[idx.item()] for idx in predict.squeeze()]
  11. # 将输入的源语言句子中的索引转换为单词
  12. input_sentence = ' '.join([corpus.src_idx2word[idx.item()] for idx in enc_inputs[0]])
  13. print(input_sentence, '->', translated_sentence) # 打印原始句子和翻译后的句子

 2.14 定义贪婪解码器函数

终于进入本文正题 

  1. # 定义贪婪解码器函数
  2. def greedy_decoder(model, enc_input, start_symbol):
  3. # 对输入数据进行编码,并获得编码器输出以及自注意力权重
  4. enc_outputs, enc_self_attns = model.encoder(enc_input)
  5. # 初始化解码器输入为全零张量,大小为 (1, 5),数据类型与 enc_input 一致
  6. dec_input = torch.zeros(1, 5).type_as(enc_input.data)
  7. # 设置下一个要解码的符号为开始符号
  8. next_symbol = start_symbol
  9. # 循环 5 次,为解码器输入中的每一个位置填充一个符号
  10. for i in range(0, 5):
  11. # 将下一个符号放入解码器输入的当前位置
  12. dec_input[0][i] = next_symbol
  13. # 运行解码器,获得解码器输出、解码器自注意力权重和编码器 - 解码器注意力权重
  14. dec_output, _, _ = model.decoder(dec_input, enc_input, enc_outputs)
  15. # 将解码器输出投影到目标词汇空间
  16. projected = model.projection(dec_output)
  17. # 找到具有最高概率的下一个单词
  18. prob = projected.squeeze(0).max(dim=-1, keepdim=False)[1]
  19. next_word = prob.data[i]
  20. # 将找到的下一个单词作为新的符号
  21. next_symbol = next_word.item()
  22. # 返回解码器输入,它包含了生成的符号序列
  23. dec_outputs = dec_input
  24. return dec_outputs

2.15 用贪婪解码器生成翻译文本

  1. # 用贪婪解码器生成翻译文本
  2. enc_inputs, dec_inputs, target_batch = corpus.make_batch(batch_size=1, test_batch=True)
  3. # 使用贪婪解码器生成解码器输入
  4. greedy_dec_input = greedy_decoder(model, enc_inputs, start_symbol=corpus.tgt_vocab['<sos>'])
  5. # 将解码器输入转换为单词序列
  6. greedy_dec_output_words = [corpus.tgt_idx2word[n.item()] for n in greedy_dec_input.squeeze()]
  7. # 打印编码器输入和贪婪解码器生成的文本
  8. enc_inputs_words = [corpus.src_idx2word[code.item()] for code in enc_inputs[0]]
  9. print(enc_inputs_words, '->', greedy_dec_output_words)

 3.结果

        实际测试发现,贪婪解码的效果并不是很好,当然咱们这里只是一个demo,大家还需采取更大的语料进行训练和测试

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/481430
推荐阅读
相关标签
  

闽ICP备14008679号