生成式聊天机器人 -- 基于Transformer实现的SeqToSeq模型 -- 上

生成式聊天机器人 -- 基于Transformer实现的SeqToSeq模型 -- 上

  • 引言
  • 数据预处理
    • 下载并处理数据
    • 数据加载
  • Transformer模型
    • 嵌入层&位置编码层
    • 多头注意力机制
    • EncoderLayer
    • DecoderLayer
    • Point-wise Feed Forward Network
    • Transformer


引言

在此之前,我们已经了解了如何基于RNN加Attention架构去实现一个聊天机器人,本文将基于Transformer架构替换RNN重新实现一个生成式聊天机器人。

生成式聊天机器人 – 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 – 上

生成式聊天机器人 – 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 – 下

中文测试集效果(基于小型数据集训练出来的效果,仅能作为学习使用):

User:以第三人称叙述的方式写一个关于必须做出重要职业决定的主人公的短篇故事。
ChatBot:从前,有一位名叫杰克的人,他的父母住在一个小镇上。他的父母亲是一位勤劳的工作,并且他的职业生涯,并且在公司工作的职业生涯中取得了成功。User:讨论大萧条的原因
ChatBot:大萧条的经济因素是由于经济衰退,由于经济衰退化,经济衰退化,导致经济衰退。这些原因包括经济衰退化User:描述电脑主板的功能
ChatBot:计算机的主板电路板是电脑,通常由两个组成,每个组成,每个组件都包含一个组成,每个组件都包含一个组成,每个组件都包含一个组件。这些组件包括:User:提出数据隐私问题的道德解决方案
ChatBot:数据隐私问题的一个潜在解决方案是实施数据安全和隐私。这些方法可能包括:User:向非技术人员解释冒泡排序算法的概念。
ChatBot:这是一个简单的算法,它将其分成两个球员的球员名字列表,其中每个球员的球员排列表,并且每个球员都会根据其他元素的球员。这意味着它的球员,并且可以在其中找到一个球员的球员。User:给定以下输入,构建一个有创意的故事。一把神奇的弓箭
ChatBot:从前,有一个名叫莉莉莉莉的年轻女孩,她住在一个小村庄里。她住在一个小村庄里,一个名叫莉莉莉莉莉莉莉莉莉莉莉莉莉莉的年轻女孩,在一个小村庄里。她的小村庄里,她住在一个小村庄里,在森林里,在森林里游泳,在森林里游泳。她的一个小村庄里,在森林里,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳。她的一个小村庄里,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳。她的一个小村庄里游泳,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳,在森林里游泳。User:提出一个关于自动驾驶汽车的想法。
ChatBot:自动驾驶汽车的一个潜在想法是使用自动驾驶汽车的自动驾驶汽车。自动驾驶汽车可以通过自动驾驶汽车User:写下您最近观看的电影的评论。寄生虫 (2019)
ChatBot:我最近看了电影《了!我最近看电影《了!我最近看了!我最近看了!我最近看了!我最近看了!我最近看了!我最近看了!我最近的电影《了!我最近的电影《了!我最近的电影《了!我最近的电影《了!我最近的电影《了!我最近的电影《了!我最近的电影星期的电影《了!我最近的电影星期的电影《了!我最近的电影星期的电影《了!我最近的电影星期的电影《了!我最近的电影星期的电影》是什么让我最近的电影《了!我最近的电影《了!我最近的电影《了!我最近的电影星期的电影《了!我最近的电影星期的电影》。我最近的电影《了!我最近的电影《了!我最User:创建 Twitter 帖子来宣传您的新产品。艾丽莎的全天然果酱
ChatBot:我们的新鲜花一切都是享用我们的美味佳选择!

数据预处理

下载并处理数据

本文使用的是github上找到的一个小型的开源中文数据集,下载链接如下:

  • 数据集 + 中文分词

需要下载dataset文件夹到项目根目录下:

在这里插入图片描述

data_zh.json 是一个只有3万条对话的小型数据集,也是本文训练所采用的数据集,仅能作为学习使用 ,而dataset_train.json来自一个开源的中文对话语料库,大约1.4G的对话。

不需要基于大语料库进行训练的同学,可以不下载dataset_train.json文件。

char_base.json 是一个含有常见中文字符的字典文件,我们会利用该文件完成字典的初始化任务。

1. 初始化字典文件 1.初始化字典文件 1.初始化字典文件

import json# 常见的中文字符和英文标点符号
common_characters = ",。!?;:“”‘’()[]&#;``【】``;《》1234567890"
common_english_punctuation = ".,!?;:\"'()[]{}<>qwertyuiopasdfghjklzxcvbnm"# 打开JSON文件以读取汉字映射数据
with open('../dataset/dataset/char_base.json', 'r', encoding='utf-8') as json_file:char_data = json.load(json_file)# 创建汉字映射字典
word_map = {}
# 遍历char_data列表并将汉字及其对应的索引添加到字典中
for item in char_data:char = item.get("char", "")if char not in word_map:word_map[char] = len(word_map) + 1# 遍历常见字符并将它们添加到字典中
for char in common_characters:if char not in word_map:word_map[char] = len(word_map) + 1# 遍历常见英文标点符号并将它们添加到字典中
for char in common_english_punctuation:if char not in word_map:word_map[char] = len(word_map) + 1# 添加特殊标记
word_map['<unk>'] = len(word_map) + 1
word_map['<start>'] = len(word_map) + 1
word_map['<end>'] = len(word_map) + 1
word_map['<pad>'] = 0if __name__ == '__main__':# 保存汉字映射字典到文件 --- word_map保存每个字符对应的IDwith open('../dataset/dataset/WORDMAP_corpus.json', 'w', encoding='utf-8') as map_file:json.dump(word_map, map_file, ensure_ascii=False)

执行上述代码后,处理效果如下:

在这里插入图片描述

2. 初始化句对 2. 初始化句对 2.初始化句对

import jsonmax_len = 256# 打开WORDMAP_corpus_.json文件以读取汉字映射数据
with open('../dataset/dataset/WORDMAP_corpus.json', 'r', encoding='utf-8') as word_map_file:word_map = json.load(word_map_file)def encode_question(words, word_map):enc_c = [word_map.get(word, word_map['<unk>']) for word in words] + [word_map['<pad>']] * (max_len - len(words))return enc_cdef encode_reply(words, word_map):enc_c = [word_map['<start>']] + [word_map.get(word, word_map['<unk>']) for word in words] + [word_map['<end>']] + [word_map['<pad>']] * (max_len - len(words))return enc_cpairs_encoded = []def generate_from_small_dataset():# 只有3万条对话的小型数据集,数据量连大模型的微调都不够。全参训练就图看个乐,能回就成功了,误当真。input_file = "../dataset/dataset/data_zh.json"with open(input_file, "r", encoding="utf-8") as file:  # 指定编码格式为utf-8data_zh = json.load(file)new_data = []for idx, item in enumerate(data_zh):query_history = []instruction = item["instruction"]output = item["output"]instruction = instruction.replace('\n\n', '\n')output = output[0].replace('\n\n', '\n')output = output[:max_len - 1].ljust(max_len - 1)qus = encode_question(instruction[:max_len-1], word_map)ans = encode_reply(output, word_map)pairs_encoded.append([qus, ans])generate_from_small_dataset()
if __name__ == '__main__':print('开始写pairs_encoded')with open('../pairs_encoded.json', 'w') as p:json.dump(pairs_encoded, p)

执行上述代码后,处理效果如下:
在这里插入图片描述


数据加载

下面我们使用DataSet类来封装对数据的读取和加载逻辑:

# 定义数据集类
class Dataset(Dataset):def __init__(self):# 加载已编码的问答对self.pairs = json.load(open('pairs_encoded.json'))self.dataset_size = len(self.pairs)def __getitem__(self, i):# 获取一组问答对question = torch.LongTensor(self.pairs[i][0])reply = torch.LongTensor(self.pairs[i][1])return question, replydef __len__(self):# 返回数据集大小return self.dataset_size

使用pytorch提供的DataLoader类来封装DataSet,完成数据的批量加载:

train_loader = data.DataLoader(Dataset(),batch_size=512,shuffle=True,pin_memory=True)

Transformer模型

关于Transformer和SeqToSeq模型理论部分大家可以阅读下文学习,本文就不再进行理论知识的讲解了,重点看代码逻辑的实现:

  • 演进历史: Seq2Seq 到 Transformer

嵌入层&位置编码层

首先我们来看Transformer中的嵌入层与位置编码层的代码实现:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")class Embeddings(nn.Module):"""实现词的嵌入并添加位置编码。"""def __init__(self, vocab_size, d_model, max_len):super(Embeddings, self).__init__()# 嵌入向量的维度self.d_model = d_model# 定义一个 Dropout 层,丢弃率为 0.1,用于防止过拟合self.dropout = nn.Dropout(0.1)# 定义一个嵌入层,将词的索引映射到 d_model 维的稠密向量空间# vocab_size 是词汇表的大小,d_model 是嵌入向量的维度self.embed = nn.Embedding(vocab_size, d_model)# 调用 create_positinal_encoding 方法创建位置编码self.pe = self.create_positinal_encoding(max_len, self.d_model)def create_positinal_encoding(self, max_len, d_model):pe = torch.zeros(max_len, d_model).to(device)# 遍历每个词的位置for pos in range(max_len):# 遍历每个位置的每个偶数维度for i in range(0, d_model, 2):# 根据正弦公式计算偶数维度的位置编码值pe[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model)))# 根据余弦公式计算奇数维度的位置编码值pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model)))# 在第 0 维添加一个维度,将 pe 的形状变为 (1, max_len, d_model),以便后续扩展批次大小pe = pe.unsqueeze(0)return pedef forward(self, encoded_words):# 通过嵌入层将编码后的词转换为嵌入向量# 乘以 math.sqrt(self.d_model) 是为了缩放嵌入向量,防止梯度消失或爆炸embedding = self.embed(encoded_words) * math.sqrt(self.d_model)# 将位置编码添加到嵌入向量上# self.pe[:, :embedding.size(1)] 截取位置编码的前 embedding.size(1) 个位置# 这里位置编码会自动扩展为与 encoded_words 相同的批次大小 -- 广播embedding += self.pe[:, :embedding.size(1)]# 对添加了位置编码的嵌入向量应用 Dropout 操作embedding = self.dropout(embedding)return embedding

下面通过一个实例来演示该层的运行流程:

在这里插入图片描述
关于Transformer采用的正弦位置编码理论部分这里就不过多讲解了,大家可自行查询相关资料进行学习。


多头注意力机制

本节我们来重点看一下Transformer中的多头注意力机制模块代码是如何实现的,以及通过图解的方式完整的看一遍它的处理流程。

import torch.nn.functional as Fclass MultiHeadAttention(nn.Module):"""多头注意力机制。多头注意力机制允许模型在不同的表示子空间中并行地关注输入序列的不同部分,从而捕捉到更丰富的语义信息。"""def __init__(self, heads, d_model):"""初始化多头注意力机制。:param heads: 注意力头的数量。:param d_model: 模型的维度,即输入和输出向量的维度。"""super(MultiHeadAttention, self).__init__()# 确保 d_model 能被 heads 整除,因为要将 d_model 均分到每个头中assert d_model % heads == 0# 每个头的维度self.d_k = d_model // heads# 注意力头的数量self.heads = heads# 定义 Dropout 层,丢弃率为 0.1,用于防止过拟合self.dropout = nn.Dropout(0.1)# 定义WQ,WK,WV矩阵,用于将输入的q,k,v向量投影到对应的query空间,key空间和value空间中self.query = nn.Linear(d_model, d_model)self.key = nn.Linear(d_model, d_model)self.value = nn.Linear(d_model, d_model)# 经过多头注意力计算后,每个头的输出是相互独立的。# 将这些输出拼接起来只是简单地将它们组合在一起,并没有充分融合各个头所提取的信息。# 通过一个线性变换层(全连接层),可以对拼接后的向量进行线性组合和变换,使得不同头的信息能够相互作用和融合,从而得到一个更加综合和丰富的表示。self.concat = nn.Linear(d_model, d_model)def forward(self, query, key, value, mask):"""前向传播方法,定义了多头注意力机制的具体计算流程。:param query: 查询向量,形状通常为 (batch_size, seq_len, d_model):param key: 键向量,形状通常为 (batch_size, seq_len, d_model):param value: 值向量,形状通常为 (batch_size, seq_len, d_model):param mask: 掩码矩阵,用于在注意力计算中屏蔽某些位置的信息,形状通常为 (batch_size, 1, seq_len, seq_len):return: 经过多头注意力机制计算后的交互向量,形状为 (batch_size, seq_len, d_model)"""# 通过线性变换将输入的 query、key 和 value 向量分别投影到对应的query,key,value空间中去query = self.query(query)key = self.key(key)value = self.value(value)# 将 query、key 和 value 向量的维度进行重塑和调整,以适应多头注意力的计算# 先将最后一个维度 d_model 拆分为 (heads, d_k),表示每个头分别关注输入维度的不同部分# 然后调整维度顺序为 (batch_size, heads, seq_len, d_k)query = query.view(query.shape[0], -1, self.heads, self.d_k).permute(0, 2, 1, 3)key = key.view(key.shape[0], -1, self.heads, self.d_k).permute(0, 2, 1, 3)value = value.view(value.shape[0], -1, self.heads, self.d_k).permute(0, 2, 1, 3)# 计算注意力分数# 通过矩阵乘法计算 query 和 key 的转置的乘积# 除以 math.sqrt(query.size(-1)) 是为了缩放注意力分数,防止梯度消失或爆炸scores = torch.matmul(query, key.permute(0, 1, 3, 2)) / math.sqrt(query.size(-1))# 应用掩码# 将掩码矩阵中值为 0 的位置对应的注意力分数设置为负无穷大# 这样在后续的 softmax 计算中,这些位置的权重将趋近于 0scores = scores.masked_fill(mask == 0, -1e9)# 计算注意力权重# 对注意力分数应用 softmax 函数,将其转换为概率分布weights = F.softmax(scores, dim=-1)# 应用 Dropout 进行正则化weights = self.dropout(weights)# 计算上下文向量# 通过矩阵乘法将注意力权重和 value 向量相乘,得到每个头的上下文向量context = torch.matmul(weights, value)# 将多头注意力的输出进行拼接# 先调整维度顺序为 (batch_size, seq_len, heads, d_k)# 然后将最后两个维度拼接成一个维度,即 (batch_size, seq_len, d_model)context = context.permute(0, 2, 1, 3).contiguous().view(context.shape[0], -1, self.heads * self.d_k)# 通过一个线性变换层(全连接层),可以对拼接后的向量进行线性组合和变换,使得不同头的信息能够相互作用和融合,从而得到一个更加综合和丰富的表示interacted = self.concat(context)return interacted

下面通过一个自注意力机制的运行实例来演示该层的运算流程:

# 参数信息
heads = 2
d_model = 8
batch_size = 1
seq_len = 6

在这里插入图片描述

上图流程中只演示了因果掩码的应用,使用padding掩码的过程也是一样的,将padding mask矩阵与还未进行SoftMax归一化的Attention Score矩阵相加即可,后面进行SoftMax运算时,会将Attention Score中值为负无穷的位置处的权重值设置为0。


EncoderLayer

本节我们来看一下Transformer中Encoder块是如何实现的:

class EncoderLayer(nn.Module):"""编码器层。编码器层是 Transformer 模型编码器部分的核心组件,主要由多头自注意力机制和前馈神经网络构成,并且在每个子层后都使用了残差连接和层归一化操作,有助于缓解梯度消失问题,提升模型的训练稳定性和性能。"""def __init__(self, d_model, heads):"""初始化编码器层。:param d_model: 模型的维度,即输入和输出向量的维度大小。:param heads: 多头注意力机制中的头数,决定了模型可以并行关注不同特征子空间的能力。"""super(EncoderLayer, self).__init__()# 定义层归一化层,对输入张量的最后一个维度进行归一化操作,# 使输入数据的均值为 0,方差为 1,有助于加速模型收敛和提高稳定性self.layernorm = nn.LayerNorm(d_model)# 实例化多头自注意力机制模块,用于对输入序列进行自注意力计算,# 可以捕捉序列中不同位置之间的依赖关系self.self_multihead = MultiHeadAttention(heads, d_model)# 实例化前馈神经网络模块,用于对多头自注意力机制的输出进行非线性变换,# 增加模型的表达能力self.feed_forward = FeedForward(d_model)# 定义 Dropout 层,丢弃率为 0.1,用于在训练过程中随机将部分输入元素置为 0,# 防止模型过拟合self.dropout = nn.Dropout(0.1)def forward(self, embeddings, mask):"""前向传播方法,定义了编码器层的具体计算流程。:param embeddings: 输入的嵌入向量,形状通常为 (batch_size, seq_len, d_model),其中 batch_size 是批次大小,seq_len 是序列长度,d_model 是模型维度。:param mask: 掩码矩阵,用于在多头注意力计算中屏蔽某些位置的信息,形状通常为 (batch_size, 1, seq_len, seq_len)。:return: 编码后的向量,形状与输入的 embeddings 相同,为 (batch_size, seq_len, d_model)。"""# 第一步:多头自注意力机制# 将输入的 embeddings 同时作为 query、key 和 value 输入到多头自注意力模块中,# 计算得到交互后的表示# 对多头自注意力模块的输出应用 Dropout 操作,防止过拟合interacted = self.dropout(self.self_multihead(embeddings, embeddings, embeddings, mask))# 第二步:第一个残差连接和层归一化# 将多头自注意力机制的输出与输入的 embeddings 相加,形成残差连接,# 有助于缓解梯度消失问题,使模型更容易训练# 对残差连接的结果应用层归一化操作,稳定训练过程interacted = self.layernorm(interacted + embeddings)# 第三步:前馈神经网络# 将经过多头自注意力机制和层归一化处理后的结果输入到前馈神经网络中,# 进行非线性变换# 对前馈神经网络的输出应用 Dropout 操作,防止过拟合feed_forward_out = self.dropout(self.feed_forward(interacted))# 第四步:第二个残差连接和层归一化# 将前馈神经网络的输出与经过多头自注意力机制和层归一化处理后的结果相加,# 形成残差连接# 对残差连接的结果应用层归一化操作,得到最终的编码结果encoded = self.layernorm(feed_forward_out + interacted)return encoded

再理解了多头注意力机制的代码实现后,EncoderLayer的代码就比较好理解了,但是这里需要注意一点,EncoderLayer类的前向传播方法传入的mask为padding_mask,用于将传入序列中为pad的词,在计算注意力分数时给排除掉,避免将注意力过多分配给这些无意义的词。


DecoderLayer

本节我们来看一下Transformer中Decoder块是如何实现的:

class DecoderLayer(nn.Module):"""解码器层。解码器层是 Transformer 模型解码器部分的核心组件,主要包含两个多头注意力机制(自注意力和编码器 - 解码器注意力)以及一个前馈神经网络,并且在每个子层后都使用了残差连接和层归一化操作,有助于提升模型性能和训练稳定性。"""def __init__(self, d_model, heads):"""初始化解码器层。:param d_model: 模型的维度,即输入和输出向量的维度大小。:param heads: 多头注意力机制中的头数,决定了模型可以并行关注不同特征子空间的能力。"""super(DecoderLayer, self).__init__()# 定义层归一化层,对输入张量的最后一个维度进行归一化操作,# 使输入数据的均值为 0,方差为 1,有助于加速模型收敛和提高稳定性self.layernorm = nn.LayerNorm(d_model)# 实例化第一个多头自注意力机制模块,用于对目标序列进行自注意力计算,# 可以捕捉目标序列中不同位置之间的依赖关系self.self_multihead = MultiHeadAttention(heads, d_model)# 实例化第二个多头注意力机制模块,用于进行编码器 - 解码器注意力计算,# 可以让解码器关注编码器的输出信息self.src_multihead = MultiHeadAttention(heads, d_model)# 实例化前馈神经网络模块,用于对多头注意力机制的输出进行非线性变换,# 增加模型的表达能力self.feed_forward = FeedForward(d_model)# 定义 Dropout 层,丢弃率为 0.1,用于在训练过程中随机将部分输入元素置为 0,# 防止模型过拟合self.dropout = nn.Dropout(0.1)def forward(self, embeddings, encoded, src_mask, target_mask):"""前向传播方法,定义了解码器层的具体计算流程。:param embeddings: 目标序列的嵌入向量,形状通常为 (batch_size, target_seq_len, d_model),其中 batch_size 是批次大小,target_seq_len 是目标序列长度,d_model 是模型维度。:param encoded: 编码器的输出,形状通常为 (batch_size, src_seq_len, d_model),其中 src_seq_len 是源序列长度。:param src_mask: 源序列的掩码矩阵,用于在编码器 - 解码器注意力计算中屏蔽某些位置的信息,形状通常为 (batch_size, 1, src_seq_len, src_seq_len)。:param target_mask: 目标序列的掩码矩阵,用于在自注意力计算中屏蔽某些位置的信息,通常是一个上三角矩阵,用于防止解码器看到未来的信息,形状为 (batch_size, 1, target_seq_len, target_seq_len)。:return: 解码后的向量,形状与输入的 embeddings 相同,为 (batch_size, target_seq_len, d_model)。"""# 第一步:目标序列的自注意力机制# 将输入的目标序列嵌入向量同时作为 query、key 和 value 输入到自注意力模块中,# 使用目标序列掩码 target_mask 进行计算,得到交互后的表示# 对自注意力模块的输出应用 Dropout 操作,防止过拟合query = self.dropout(self.self_multihead(embeddings, embeddings, embeddings, target_mask))# 第一个残差连接和层归一化# 将自注意力机制的输出与输入的目标序列嵌入向量相加,形成残差连接,# 有助于缓解梯度消失问题,使模型更容易训练# 对残差连接的结果应用层归一化操作,稳定训练过程query = self.layernorm(query + embeddings)# 第二步:编码器 - 解码器注意力机制# 将经过自注意力和层归一化处理后的结果作为 query,# 编码器的输出 encoded 同时作为 key 和 value,# 使用源序列掩码 src_mask 输入到多头注意力模块中,进行编码器 - 解码器注意力计算,# 让解码器关注编码器的输出信息# 对多头注意力模块的输出应用 Dropout 操作,防止过拟合interacted = self.dropout(self.src_multihead(query, encoded, encoded, src_mask))# 第二个残差连接和层归一化# 将编码器 - 解码器注意力机制的输出与经过自注意力和层归一化处理后的结果相加,# 形成残差连接# 对残差连接的结果应用层归一化操作interacted = self.layernorm(interacted + query)# 第三步:前馈神经网络# 将经过编码器 - 解码器注意力机制和层归一化处理后的结果输入到前馈神经网络中,# 进行非线性变换# 对前馈神经网络的输出应用 Dropout 操作,防止过拟合feed_forward_out = self.dropout(self.feed_forward(interacted))# 第三个残差连接和层归一化# 将前馈神经网络的输出与经过编码器 - 解码器注意力机制和层归一化处理后的结果相加,# 形成残差连接# 对残差连接的结果应用层归一化操作,得到最终的解码结果decoded = self.layernorm(feed_forward_out + interacted)return decoded

Decoder 在其所流经的第一个多头注意力机制模块中传入的掩码为因果掩码,用于确保在生成某个位置的输出时,解码器只能关注到该位置及其之前的输入信息,而不能看到未来的信息。

Decoder 所流经的第二个多头注意力机制模块的q来源于解码器自身第一个注意力层的输出,k和v来自编码器的输出,由于q,k,v来源不同,该层也被成为交叉自注意力层。同时该层计算时,会传入原序列对应的padding掩码矩阵,用于屏蔽源序列中的填充位置,防止模型在自注意力计算时关注到这些无意义的位置。


Point-wise Feed Forward Network

在 Transformer 架构里,FeedForward 层也被称作逐点前馈网络(Point-wise Feed Forward Network),主要是因为以下两点原因:

  1. 独立处理每个位置:逐点前馈网络对输入序列中的每个位置(时间步)进行独立的计算,各个位置之间不存在交互。也就是说,在该网络的计算过程中,对于输入序列里的每一个元素,都是按照相同的方式进行处理的,彼此之间没有依赖关系。例如,在一个句子的嵌入表示中,每个词的嵌入向量都会独立地经过前馈网络的计算,不会受到其他词的影响。
  2. 共享参数:逐点前馈网络在整个序列上共享相同的参数。具体而言,FeedForward 层中的两个线性层 self.fc1 和 self.fc2 的权重参数在处理输入序列的所有位置时都是一样的。这种参数共享的方式大大减少了模型的参数数量,提升了计算效率。
class FeedForward(nn.Module):"""前馈神经网络。"""def __init__(self, d_model, middle_dim=2048):super(FeedForward, self).__init__()self.fc1 = nn.Linear(d_model, middle_dim)self.fc2 = nn.Linear(middle_dim, d_model)self.dropout = nn.Dropout(0.1)def forward(self, x):out = F.relu(self.fc1(x))out = self.fc2(self.dropout(out))return out

下面通过一个实例来演示该过程:

# 初始化参数值
batch_size = 1
embedd_size = 8
seq_len  = 6
middle_dim = 16

在这里插入图片描述

自注意力机制负责将同一个序列中所有词向量按照注意力权重进行信息融合,经过自注意力机制的处理后,Transformer再将数据通过前馈层独立的完成对单个词向量中信息的融合。


Transformer

我们上面已经讲解完了Transformer中涉及到的所有组件,下面看看如何把这些组件组装成一个完整的transformer模型。

import torch.nn.functional as Fclass Transformer(nn.Module):"""Transformer模型。Transformer是一种基于注意力机制的深度学习模型,广泛应用于自然语言处理任务,由编码器(Encoder)和解码器(Decoder)组成,能够处理序列到序列的任务。"""def __init__(self, d_model, heads, num_layers, word_map, max_len=260):"""初始化Transformer模型。:param d_model: 模型的维度,即嵌入向量和隐藏层的维度大小。:param heads: 多头注意力机制中的头数。:param num_layers: 编码器和解码器的层数。:param word_map: 词汇表,用于将单词映射为索引。:param max_len: 输入序列的最大长度,默认为260。"""super(Transformer, self).__init__()# 保存模型的维度self.d_model = d_model# 计算词汇表的大小self.vocab_size = len(word_map)# 初始化嵌入层,用于将输入的单词索引转换为嵌入向量self.embed = Embeddings(self.vocab_size, d_model, max_len=max_len)# 初始化编码器层列表,包含num_layers个EncoderLayer实例self.encoder = nn.ModuleList([EncoderLayer(d_model, heads) for _ in range(num_layers)])# 初始化解码器层列表,包含num_layers个DecoderLayer实例self.decoder = nn.ModuleList([DecoderLayer(d_model, heads) for _ in range(num_layers)])# 初始化线性层,用于将解码器的输出映射到词汇表大小的维度,以便进行单词预测self.logit = nn.Linear(d_model, self.vocab_size)def encode(self, src_words, src_mask):"""编码器前向传播,对源序列进行编码。:param src_words: 源序列的单词索引,形状通常为 (batch_size, src_seq_len):param src_mask: 源序列的掩码,用于屏蔽填充位置,形状通常为 (batch_size, 1, src_seq_len, src_seq_len):return: 编码后的源序列嵌入向量,形状为 (batch_size, src_seq_len, d_model)"""# 将源序列的单词索引通过嵌入层转换为嵌入向量src_embeddings = self.embed(src_words)# 依次通过编码器的每一层进行处理for layer in self.encoder:src_embeddings = layer(src_embeddings, src_mask)return src_embeddingsdef decode(self, target_words, target_mask, src_embeddings, src_mask):"""解码器前向传播,根据编码后的源序列和目标序列进行解码。:param target_words: 目标序列的单词索引,形状通常为 (batch_size, tgt_seq_len):param target_mask: 目标序列的掩码,包括填充掩码和因果掩码,形状通常为 (batch_size, 1, tgt_seq_len, tgt_seq_len):param src_embeddings: 编码后的源序列嵌入向量,形状为 (batch_size, src_seq_len, d_model):param src_mask: 源序列的掩码,用于屏蔽填充位置,形状通常为 (batch_size, 1, src_seq_len, src_seq_len):return: 解码后的目标序列嵌入向量,形状为 (batch_size, tgt_seq_len, d_model)"""# 将目标序列的单词索引通过嵌入层转换为嵌入向量tgt_embeddings = self.embed(target_words)# 依次通过解码器的每一层进行处理for layer in self.decoder:tgt_embeddings = layer(tgt_embeddings, src_embeddings, src_mask, target_mask)return tgt_embeddingsdef forward(self, src_words, src_mask, target_words, target_mask):"""Transformer模型的前向传播。:param src_words: 源序列的单词索引,形状通常为 (batch_size, src_seq_len):param src_mask: 源序列的掩码,用于屏蔽填充位置,形状通常为 (batch_size, 1, src_seq_len, src_seq_len):param target_words: 目标序列的单词索引,形状通常为 (batch_size, tgt_seq_len):param target_mask: 目标序列的掩码,包括填充掩码和因果掩码,形状通常为 (batch_size, 1, tgt_seq_len, tgt_seq_len):return: 经过Transformer模型处理后的输出,形状为 (batch_size, tgt_seq_len, vocab_size),表示每个位置预测每个单词的概率对数"""# 对源序列进行编码encoded = self.encode(src_words, src_mask)# 根据编码后的源序列和目标序列进行解码decoded = self.decode(target_words, target_mask, encoded, src_mask)# 将解码后的结果通过线性层映射到词汇表大小的维度# 并使用log_softmax函数将其转换为概率对数out = F.log_softmax(self.logit(decoded), dim=2)return out

Transformer 类实现了一个完整的 Transformer 模型,包含嵌入层、编码器、解码器和输出层。通过 encode 方法对源序列进行编码,decode 方法根据编码后的源序列和目标序列进行解码,最后在 forward 方法中组合编码和解码过程,并输出预测结果的概率对数。

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/17666.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP文件传输

文件传输 工作原理 本质:客户端通过标准IO或者文件IO&#xff0c;读取文件中的信息 然后将读取到的信息&#xff0c;通过套接字发送给服务器 服务器接收到这些数据之后&#xff0c;立刻通过标准IO或者文件IO写到文件里面去 这个过程里面&#xff0c;服务器需要知道2件事情 1&…

欧拉函数杂记

定义 φ ( n ) \varphi (n) φ(n)表示 [ 1 , n ] [1,n] [1,n]中与 n n n互质的数的个数。 性质 φ ( p ) p − 1 , p ∈ P \varphi (p)p-1,\ p\in \mathbb {P} φ(p)p−1, p∈P φ ( n ) n ∏ i 1 m p i − 1 p i \varphi (n)n\prod_{i1}^{m} \frac{p_i-1}{p_i} φ(n)ni1∏…

在 CentOS 上更改 SSH 默认端口以提升服务器安全性

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; GitCode︱ Gitee ︱ Github &#x1f496; 欢迎点赞 &#x1f44d; 收藏 ⭐评论 …

Spring Boot(8)深入理解 @Autowired 注解:使用场景与实战示例

搞个引言 在 Spring 框架的开发中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是它的一个核心特性&#xff0c;它能够让代码更加模块化、可测试&#xff0c;并且易于维护。而 Autowired 注解作为 Spring 实现依赖注入的关键工具&…

搜狗拼音输入法自定义短语设置

点击搜狗拼音输入法 选择设置 选择高级->自定义短语->自定义短语设置 选择添加新的短语 填入想设置的短语&#xff0c;点击确定 效果展示

反射概率以及一些基本API的使用

请问&#xff0c;获取对象有几种方式&#xff1f; 1、通过构造函数来new一个对象&#xff1b; 2、通过clone来克隆一个对象&#xff1b; 3、通过序列化反序列化来构建一个对象&#xff1b; 4、通过反射来创建对象&#xff1b;a、通过Class类来创建&#xff1b;b、通过Const…

从零搭建:Canal实时数据管道打通MySQL与Elasticsearch

Canal实时同步Mysql Binlog至 Elasticsearch 文章目录 Canal实时同步Mysql **Binlog**至**Elasticsearch** 一. 环境准备1.环境检查检查Mysql是否开启BinLog开启Mysql BinlogJava环境检查 2.新建测试库和表3.新建Es索引 二.**部署 Canal Server****2.1 解压安装包****2.2 配置 …

五、k8s:容忍 存储卷

容忍&#xff1a; 即使节点上有污点&#xff0c;依然可以部署pod。 tolerations: operator: "Exists" 不指定key&#xff0c;表示容忍所有的污点 cordon和drain cordon: 直接标记节点为不可用&#xff0c;pod不能部署到该节点。新建的pod不会再部署到该节点&#…

Springboot_实战

项目开发 lombok使用 自动为实体类提供get、set、toString方法 引入依赖 实体类上添加注解 统一响应结果 注意要写get、set方法&#xff1b;下面是错误的&#xff0c;因此要加上Data注解 一个注册的接口的示例 Controller层 Service层 Mapper层 参数校验 但是同样存在一…

稀土抑烟剂——为纺织品安全加持,保护您的每一寸触感

一、稀土抑烟剂的基本概念 稀土抑烟剂是基于稀土元素&#xff08;如稀土氧化物和稀土金属化合物&#xff09;研发的一类新型阻燃材料。它能够有效提高纺织品的阻燃性&#xff0c;抑制火灾发生时产生的烟雾和有害气体&#xff0c;减少火灾对人体的危害。稀土抑烟剂具有更强的稳…

本地部署SafeLine详细指南:抵御网络攻击构建更安全的网站环境

文章目录 前言1.关于SafeLine2.安装Docker3.本地部署SafeLine4.使用SafeLine5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址前言 各位建站小能手们,无论是想搭建个人博客、企业官网还是各种应用平台来推广自己的内容或产品,在这个数字时代都已经…

Chrome 浏览器可以读写本地文件了,虽说是实验api,但是基本86+已经支持了

目前该API只能在https域名上使用&#xff01;&#xff01;&#xff01; 实现逻辑&#xff1a; 1.唤醒浏览器选择文件夹、文件权限 document.getElementById(button).addEventListener(click, async () > {getFile()});async function getFile () {// 打开文件选择器 记…

华宇TAS应用中间件与因朵科技多款产品完成兼容互认证

在数字化浪潮澎湃向前的当下&#xff0c;信息技术的深度融合与协同发展成为推动各行业创新变革的关键力量。近日&#xff0c;华宇TAS应用中间件携手河北因朵科技有限公司&#xff0c;完成了多项核心产品的兼容互认证。 此次兼容性测试的良好表现&#xff0c;为双方的进一步深入…

数字IC秋招知识点—1

数字IC秋招准备知识点—1 时序逻辑与组合逻辑 1. 定义与核心原理 组合逻辑&#xff1a; 输入决定输出&#xff1a;当前的输出仅由输入决定&#xff0c;无记忆功能。无反馈回路示例&#xff1a;基本逻辑单元&#xff0c;加法器&#xff0c;多路选择器MUX&#xff0c;译码器&am…

【webview Android】视频获取首帧为封面

文章目录 需求分析获得首帧其他方法 需求分析 客户端中h5上传视频&#xff0c;视频封面默认首帧。 遇到问题&#xff1a;原生的video现象如下 IOS会在加载好后显示首帧&#xff08;没加载好显示黑屏&#xff0c;符合预期&#xff09;Android加载好后默认封面为一个奇怪的占位…

大脑网络与智力:基于图神经网络的静息态fMRI数据分析方法|文献速递-医学影像人工智能进展

Title 题目 Brain networks and intelligence: A graph neural network based approach toresting state fMRI data 大脑网络与智力&#xff1a;基于图神经网络的静息态fMRI数据分析方法 01 文献速递介绍 智力是一个复杂的构念&#xff0c;包含了多种认知过程。研究人员通…

DeepSeek如何重塑我的编程学习:计算机新生的AI实践

目录 &#x1f680;前言&#x1f31f;邂逅DeepSeek&#xff1a;从困惑到惊喜&#x1f4af;初学编程的困境&#x1f4af;DeepSeek的优势 &#x1f58a;️DeepSeek在编程学习中的运用&#x1f4af;注释&#x1f4af;算法逐步分析&#x1f4af;调试帮助&#x1f4af;跨语言迁移学习…

信息收集-Web应用JS架构URL提取数据匹配Fuzz接口WebPack分析自动化

知识点&#xff1a; 1、信息收集-Web应用-JS提取分析-人工&插件&项目 2、信息收集-Web应用-JS提取分析-URL&配置&逻辑 FUZZ测试 ffuf https://github.com/ffuf/ffuf 匹配插件 Hae https://github.com/gh0stkey/HaE JS提取 JSFinder https://github.com/Threez…

安科瑞光储充一体化微电网系统的设计与优化研究-安科瑞 蒋静

摘要&#xff1a;双碳能源技术是一种绿色、可持续的能源发展方向&#xff0c;光储充一体系统作为其中的重要组成部分&#xff0c;具有将光能转化为电能并进行储存和供电的功能。文章对光储充一体系统的设计与性能进行分析&#xff0c;以期为双碳能源技术的推广和应用提供技术支…

【BUG】Ubuntu|有nvcc,没有nvidia-smi指令,找不到nvidia-driver安装包

很奇怪&#xff0c;本来能使用的&#xff0c;放个假回来就用不了了。 排查了以下所有步骤最终解决。 我的Ubuntu版本&#xff1a;Ubuntu22 nvcc -v&#xff1a;有。如果没有的话你需要安装“sudo apt-get install nvidia-cuda-toolkit”&#xff0c;其他问题请去别的博客查。…