wordpress ftp配置,东莞推广seo关键词排名优化,南宁保洁网站建设,网页设计网站期末作业注意力机制#xff1a;当我们看一个图像或者听一段音频时#xff0c;会根据自己的需求#xff0c;集中注意力在关键元素上#xff0c;以获取相关信息。 同样地#xff0c;注意力机制中的模型也会根据输入的不同部分#xff0c;给它们不同的权重#xff0c;并集中注意力在…注意力机制当我们看一个图像或者听一段音频时会根据自己的需求集中注意力在关键元素上以获取相关信息。 同样地注意力机制中的模型也会根据输入的不同部分给它们不同的权重并集中注意力在最具有代表性的元素上以提高模型的性能。
具体来说注意力机制通常包括以下几个步骤 映射输入将输入如文本、图像等映射到一个向量空间中。 计算注意力权重根据注意力模型的不同实现方式计算每个输入元素的权重以表示它在当前任务中的重要性。 加权求和根据计算得到的注意力权重对输入进行加权求和以产生最终的输出。
Self-Attention 是一种“自我关注”机制用于提取输入序列中不同位置之间的相关性。它的基本思想是对于任意一个元素如单词、句子等通过对其它元素的表示进行线性变换得到一个新的表示反映了该元素与其它元素之间的联系。Self-Attention 通常包括以下几个步骤
映射输入将输入序列中每个元素如单词映射到一个高维向量空间中并形成一个矩阵X其中每行代表一个元素的向量表示。 计算注意力权重对于矩阵X中的每个元素都可以利用一个查询向量Q、一个键向量K和一个值向量V计算出它与其他所有元素之间的相似度或者相关度并将这些相似度作为权重加权求和得到输出。 加权求和根据计算得到的注意力权重对值向量V进行加权求和产生最终的输出。 Transformer结构组件和逻辑
重点自注意力、多头注意力、交叉注意力
1、输入嵌入层Input Embedding输入数据的预处理计算每个单词的表示向量X它由单词的Embedding和单词位置的Embedding相加得到。
1单词Embedding计算 将每个单词映射到连续向量空间中 即映射为一个1乘d的向量表示矩阵。 那么n个单词的输入就变成了n*d的矩阵向量。 因为注意力机制需要输入是向量格式两个向量的点乘表示两个向量的相似度或者说相关性
单词Embedding有以下几种方法 使用预训练的词嵌入模型如 Word2Vec、GloVe、FastText 和 BERT 等通过大规模的文本语料库学习单词的向量表示。 训练自定义的词嵌入模型使用诸如 Word2Vec 或 Skip-gram 模型这样的算法在你自己的数据集上训练单词Embedding。 使用深度学习模型中的嵌入层使用深度学习模型如RNN、CNN或 Transformer模型的嵌入层来学习单词 Embedding。
此外有两个词汇表相当于字典对每个字/单词做了索引分别对应源语言和目标语言初始化词汇表得到词元list。
2位置Embedding计算 因为词袋模型的单词向量表示无法提取单词的位置特征而句子的意思跟单词在句子的位置也有关系所以往单词的向量表示中加入位置编码。位置Embedding可以通过训练得到也可以使用某种公式计算得到。在 Transformer 中采用了后者计算公式如下 其中pos 表示单词在句子中的位置d 表示 PE的维度 (与词 Embedding 一样)2i 表示偶数的维度2i1 表示奇数维度 (即 2i≤d, 2i1≤d)。
2、自注意力Self-Attention以及相关计算 自注意力中Q、K、V的物理意义是一样的都表示同一个句子中不同token组成的矩阵。
1计算Q、K、V向量为输入序列的每个单词计算三个向量查询向量Query、键向量Key和值向量Value。 这些向量通常是通过将单词的嵌入向量Embedding Vector输入到一个线性变换层得到的。 Q、K、V使用不同的权重矩阵Wq, WkWv来计算可以理解为是在不同空间上的投影。 正因为有了这种不同空间的投影增加了表达能力。这些权重矩阵正是模型要训练学习的参数。 矩阵中的每一行表示一个token的word embedding向量。假设一个句子单词长度是6embedding维度是512那么Q、K、V都是(6512)的矩阵。
2计算Q、K的点积注意力分数 计算Query向量与Key向量的点积得到一个分数attention score矩阵。这个分数反映了任意两个单词的关联程度或者说任两个token的相似性。
3Softmax函数归一化注意力权重这些分数会经过一个Softmax函数进行归一化得到每个单词的注意力权重一个百分比可以理解为概率。 这些权重表示了在理解当前单词时应该给予序列中其他单词多大的关注。
4注意力权重加权求和加权和向量 这些注意力权重与对应的Value向量进行加权求和得到一个加权和向量。这个加权和向量会被用作当前单词的新表示包含了更丰富的上下文信息。 因为attention score矩阵已经很难表示原来的句子了而V还代表着原来的句子所以用attention score矩阵来对V进行提炼得到的是一个加权后的结果。 单词表示向量矩阵 经过6个Encoder block 后可以得到句子所有单词的编码信息矩阵 C每一个Encoder block输出的矩阵维度与输入完全一致。 3、多头注意力层 Multi-Head Attention Multi-Head Attention就是使用多组WQ 、WK、WV得到多组QueryKeysValues然后每组分别计算得到一个Z矩阵最后将得到的多个Z矩阵进行拼接。 Multi-Head Attention增强学习能力多组不同的线性变换即所谓的多头由多个 Self-Attention 组合形成的。 论文中8个输出矩阵 Z1 到 Z8 之后Multi-Head Attention 将它们拼接在一起 (Concat)然后传入一个Linear层得到 Multi-Head Attention 最终的输出Z。 4、残差连接和层规一化Add Norm addnorm组件是由残差连接和紧随其后的层规一化组成用来提升训练的稳定性。 从示意图看出所有子层模型输出前都经过addnorm组件才用作下一层的输入。
1残差连接 Add指某一层模型的输入x加上该层模型的输出就是xf(x),后续把和作为归一化函数的输入 用意残差连接引入输入直接到输出的通路便于梯度回传缓解在优化过程中由于网络过深引起的梯度消失问题。 为什么这么做添加了残差块后网络的输出就变成h(X)F(X)X 一般的设计模型时不知道最优需要几层网络为了保证设计多出来的几层网络可进行恒等映射F(x)x把网络的输出转变成h(X)F(X)X只要训练让F(X)0就可以了而训练变成0比变成X容易 总之ResNet残差连接解决神经网络退化梯度消失的问题
2层归一化 Norm指 Layer Normalization层归一化 是基于特征维度进行规范化将数据进行标准化乘以缩放系数、加上平移系数保留其非线性能力。就是将每一层神经元的输入都转成均值方差都一样的。 计算公式 LN(x)α(x−μσ)βLN(x)α(x−μσ)β
对输入数据进行Normalize归一化的目的有二 1.能够加快训练的速度可以加快收敛。 2.提高训练的稳定性。
例如对于MultiHeadAttention层 LayerNorm(X MultiHeadAttention(X))
对于FeedForward层 LayerNorm(XFeedForward(X))
Layer NormalizationLN与Batch NormalizationBN的区别 LN是在同一个样本中不同神经元之间进行归一化而BN是在同一个batch中不同样本之间的同一位置的神经元之间进行归一化。
5、位置感知的前馈网络position-wise一个2层全连接网络。 与人类大脑能够并行处理信息的能力类似Transformer模型中的每一个注意力子层都包含一个前馈网络FFN。 FFN层被放置在每个注意力层的后面用来对注意力权重进行非线性转换或对特征进行映射通过使用线性变换和非线性激活函数 使模型对注意力层的输出进行进一步的转换和特征提取从而增强模型的表征能力提供更多的非线性和学习能力。
FFN 就是一个简单的二层全连接网络其中第一层的激活函数为ReLU。对于输入序列中每个位置上的向量x FFN(x)max(0,xW1b1)W2b2 等价 FFN(x)Relu(xW1b1)W2b2FFN(x)Relu(xW1b1)W2b2
这些全连接层是逐位置position-wise的这意味着每个序列位置都会经过相同的线性变换再用非线性激活函数Relu再线性变换。 这里的x就是Multi-Head Attention的输出Z假如Z是(2,64)维的矩阵W1是(64,1024)其中W2与W1维度相反(1024,64)那么按照上面的公式 FFN(Z)(2,64)x(64,1024)x(1024,64)(2,64)我们发现维度没有发生变化这两层网络就是为了将输入的Z映射到更加高维的空间中(2,64)x(64,1024)(2,1024) 然后通过非线性函数ReLU进行筛选筛选完后再变回原来的维度。然后经过AddNormalize输入下一个encoder中经过6个encoder后输入到decoder。 需要注意的是在同一层上这里指的是Encoder或Decoder内的层每个位置上的向量x所对应的参数W1、W2、b1、b2都是相同的但是层与层之间的参数是不同的。
前馈层功能 线性变换1使用可学习的权重矩阵将输入表示投影到更高维度的空间中。 非线性激活第一个线性变换的输出通过非线性激活函数例如ReLU传递。这引入了模型的非线性使其能够捕捉数据中的复杂模式和关系。 线性变换2激活函数的输出然后通过另一个可学习的权重矩阵投影回原始的维度空间中。
6、编码器N个EncoderBlock串联 每个EncoderBlock包含两个子层多头自注意力和基于位置的前馈网络这两个子层都使用了残差连接和层规一化。 1Multi-Head Attention 2Feed Forward 3Add Norm
Encoder block 接收输入矩阵X(n,d)并输出一个矩阵O(n,d)。通过多个 Encoder block 叠加就可以组成 Encoder。 n表示单词数量d表示词嵌入向量的维度如512. 所有编码器都会接收到一个大小为 512 的向量列表即词嵌入向量加入了位置编码其他编码器接收的是上一个编码器的输出。 最后一个 Encoder block 输出的矩阵就是编码信息矩阵 C这一矩阵后续会用到 Decoder 中。 每个查询都会关注所有的键值对并生成一个注意力输出。 由于Q、K和V来自同一组输入故称为Self-Attention。
7、解码器N个DecoderBlock串联 每个 Decoder block 结构 1 掩码多头注意力 每个DecoderBlock的第一个自注意力子层额外增加了注意力掩码,屏蔽未知单词只能看到已经翻译的单词和当前单词。 2 交叉注意力 每一个DecoderBlock比EncoderBlock多一个多头注意力并且使用交叉注意力Cross-attention方法同时接收来自编码器端的输出以及前一个掩码注意力层的输出。 Key和Value是使用编码器的输出进行投影的Query是通过解码器前一层的输出进行投影所得。 3位置感知的前馈网络position-wise
计算逻辑详解 1第一个 Multi-Head Attention 层采用了 Masked 操作。 第一步掩码操作Mask 的作用是只能使用之前的信息。 第二步和之前的 Self-Attention 一样通过输入矩阵X计算得到Q,K,V矩阵。然后计算Q积K。 第三步Q积K之后需要进行 Softmax计算 attention score我们在 Softmax 之前需要使用Mask矩阵遮挡住每一个单词之后的信息。 第四步使用第三步得到的attention score矩阵与矩阵V相乘得到输出 Z。 第五步通过上述步骤就可以得到一个 Mask Self-Attention 的输出矩阵然后和 Encoder 类似通过 Multi-Head Attention 拼接多个输出然后计算得到第一个 Multi-Head Attention 的输出ZZ与输入X维度一样。
2第二个 Multi-Head Attention 层的K, V矩阵使用 Encoder 的编码信息矩阵CEncoder中最后一层的输出做参进行计算而Q使用前一个 Decoder block 的输出计算就是所谓交叉注意力。 主要的区别在于其中 Self-Attention 的 K, V矩阵不是使用 上一个 Decoder block 的输出计算的而是使用 Encoder 的编码信息矩阵 C 作为输入参数。 这样做的好处是在 Decoder 的时候每一位单词都可以利用到 Encoder 所有单词的信息 (这些信息无需 Mask)。
第一个Masked Multi-Head Attention是为了得到之前已经预测输出的信息 第二个Multi-Head Attention是通过当前的输入与经过encoder提取过的特征向量之间的关系来预测输出。
3最后有一个 Softmax层计算下一个翻译单词的概率。 Decoder block 最后的部分是利用 Softmax 预测下一个单词在之前的网络层我们可以得到一个最终的输出 Z 实现代码
输入数据预处理需要安装nltk pip install nltk
需要下载语料库安装nltk_data 下载地址https://gitee.com/iceliooo/nltk_data
import nltk
# 下载reuters语料库这里可能下载不了
nltk.download()requirements.txt
torch2.0.1
torchvision0.15.2
onnx1.14.0
onnxruntime1.15.1
pycocotools2.0.7
PyYAML6.0.1
scipy1.13.0
onnxslim0.1.31
onnxruntime-gpu1.18.0
gradio4.31.5
opencv-python4.9.0.80
psutil5.9.8
py-cpuinfo9.0.0
huggingface-hub0.23.2
safetensors0.4.3
tqdm~4.67.1
ultralytics~8.1.34
numpy~1.26.4
matplotlib~3.9.4
pillow~10.4.0
pandas~2.2.3
scikit-learn~1.6.0
seaborn~0.13.2
thop~0.1.1-2209072238
requests~2.32.3
nltk~3.9.1目录结构
import numpy as np
import torchfrom collections import Counter
from nltk import word_tokenize
from torch.autograd import VariableUNK1
PAD1
BATCH_SIZE128
DEVICEcpu
PAD 0 # 填充元素def subsequent_mask(size):attn_shape (1, size, size)# attn_shape (1, 11, 11)subsequent_mask np.triu(np.ones(attn_shape), k1).astype(uint8)# np.ones(attn_shape) 会生成一个形状为 attn_shape 的全为1的矩阵。# np.triu() 函数将这个矩阵转换为上三角矩阵。# 参数 k 控制了主对角线以上的偏移量。当 k0 时生成的是包含主对角线在内的上三角矩阵当 k1 时生成的是主对角线以上偏移一个单位的上三角矩阵。# 返回一个右上角(不含主对角线)为全False左下角(含主对角线)为全True的subsequent_mask矩阵# torch.from_numpy(subsequent_mask) 将 NumPy 数组 subsequent_mask 转换为 PyTorch 张量。 0 表示对张量中的每个元素进行逐元素比较检查是否等于0。# 最终返回的是一个布尔类型的张量其中每个元素都是与0比较的结果即True或False。即等于0为True等于1为False。return torch.from_numpy(subsequent_mask) 0def seq_padding(X, paddingPAD):按批次batch对数据填充、长度对齐# 计算该批次各条样本语句长度Length [len(x) for x in X]# 获取该批次样本中语句长度最大值MaxLength max(Length)# 遍历该批次样本如果语句长度小于最大长度则用padding填充return np.array([np.concatenate([x, [padding] * (MaxLength - len(x))]) if len(x) MaxLength else x for x in X])class Batch:批次类1. 输入序列源2. 输出序列目标3. 构造掩码def __init__(self, src, trgNone, padPAD):# 将src、trg转为tensor格式将数据放到设备上并规范成整数类型src torch.from_numpy(src).to(DEVICE).long()# src tensor([[ 2, 16, 17, 18, 4, 3, 0],[ 2, 5, 6, 7, 8, 4, 3]])trg torch.from_numpy(trg).to(DEVICE).long()# trg tensor([[ 2, 22, 23, 24, 25, 26, 4, 3, 0, 0, 0], [ 2, 6, 7, 8, 9, 10, 11, 12, 13, 4, 3]])self.src src# self.src tensor([[ 2, 16, 17, 18, 4, 3, 0],[ 2, 5, 6, 7, 8, 4, 3]])# (src ! pad)这部分代码会生成一个与输入序列 src 形状相同的布尔张量其中真实单词的位置为 True填充部分的位置为 False。并在seq length前面增加一维形成维度为 1×seq length 的矩阵self.src_mask (src ! pad).unsqueeze(-2)self.src_masktensor([[[ True, True, True, True, True, True, False]],[[ True, True, True, True, True, True, True]]])# self.src_mask.shape torch.Size([2, 1, 7])# 如果输出目标不为空则需要对解码器使用的目标语句进行掩码if trg is not None:# 解码器使用的目标输入部分self.trg trg[:, : -1] # 去除最后一列# self.trgtensor([[ 2, 22, 23, 24, 25, 26, 4, 3, 0, 0],[ 2, 6, 7, 8, 9, 10, 11, 12, 13, 4]])# 解码器训练时应预测输出的目标结果self.trg_y trg[:, 1:] # 去除第一列的# self.trg_ytensor([[22, 23, 24, 25, 26, 4, 3, 0, 0, 0],[ 6, 7, 8, 9, 10, 11, 12, 13, 4, 3]])# 将目标输入部分进行注意力掩码self.trg_mask self.make_std_mask(self.trg, pad)# 生成一个大小为self.trg.size(-1)的掩码矩阵下三角为True上三角为False# 将应输出的目标结果中真实的词数进行统计self.ntokens (self.trg_y ! pad).data.sum()# self.ntokenstensor(17)# 掩码操作# 这个操作的目的是在训练解码器时确保解码器在预测当前时间步的词时只能依赖之前的词而不能依赖未来的词staticmethoddef make_std_mask(tgt, pad):Create a mask to hide padding and future words.tgt_mask (tgt ! pad).unsqueeze(-2)# 这部分代码会生成一个与目标序列 tgt 形状相同的布尔张量其中真实单词的位置为 True填充部分的位置为 False。并在倒数第二维度上添加一个新维度。# tgt_mask tensor([[[True, True, True, True, True, True, True, True, True, True, True]]])tgt_mask tgt_mask Variable(subsequent_mask(tgt.size(-1)).type_as(tgt_mask.data))# subsequent_mask(tgt.size(-1))生成一个大小为tgt.size(-1)的下三角形的矩阵上三角部分为0下三角部分和对角线为1。# Variable(...).type_as(tgt_mask.data)将生成的下三角形矩阵转换为与目标序列掩码相同的数据类型和设备类型。# (tgt_mask ...): 对上面生成的下三角形矩阵与目标序列掩码进行逻辑与操作得到最终的目标序列掩码。return tgt_maskclass PrepareData:def __init__(self, train_file, dev_file):# 读取数据、分词self.train_en, self.train_cn self.load_data(train_file)self.dev_en, self.dev_cn self.load_data(dev_file)# 构建词表self.en_word_dict, self.en_total_words, self.en_index_dict \self.build_dict(self.train_en)self.cn_word_dict, self.cn_total_words, self.cn_index_dict \self.build_dict(self.train_cn)# 单词映射为索引self.train_en, self.train_cn self.word2id(self.train_en, self.train_cn, self.en_word_dict, self.cn_word_dict)self.dev_en, self.dev_cn self.word2id(self.dev_en, self.dev_cn, self.en_word_dict, self.cn_word_dict)# 划分批次、填充、掩码self.train_data self.split_batch(self.train_en, self.train_cn, BATCH_SIZE)self.dev_data self.split_batch(self.dev_en, self.dev_cn, BATCH_SIZE)def load_data(self, path):读取英文、中文数据对每条样本分词并构建包含起始符和终止符的单词列表形式如en [[BOS, i, love, you, EOS], [BOS, me, too, EOS], ...]cn [[BOS, 我, 爱, 你, EOS], [BOS, 我, 也, 是, EOS], ...]en []cn []with open(path, moder, encodingutf-8) as f:for line in f.readlines():sent_en, sent_cn line.strip().split(\t)sent_en sent_en.lower()# sent_cn cht_to_chs(sent_cn)sent_en [BOS] word_tokenize(sent_en) [EOS]# 中文按字符切分sent_cn [BOS] [char for char in sent_cn] [EOS]en.append(sent_en)cn.append(sent_cn)return en, cndef build_dict(self, sentences, max_words5e4):构造分词后的列表数据构建单词-索引映射key为单词value为id值# 统计数据集中单词词频word_count Counter([word for sent in sentences for word in sent])# 按词频保留前max_words个单词构建词典# 添加UNK和PAD两个单词ls word_count.most_common(int(max_words))total_words len(ls) 2word_dict {w[0]: index 2 for index, w in enumerate(ls)}word_dict[UNK] UNKword_dict[PAD] PAD# 构建id2word映射index_dict {v: k for k, v in word_dict.items()}return word_dict, total_words, index_dictdef word2id(self, en, cn, en_dict, cn_dict, sortTrue):将英文、中文单词列表转为单词索引列表sortTrue表示以英文语句长度排序以便按批次填充时同批次语句填充尽量少length len(en)# 单词映射为索引out_en_ids [[en_dict.get(word, UNK) for word in sent] for sent in en]out_cn_ids [[cn_dict.get(word, UNK) for word in sent] for sent in cn]# 按照语句长度排序def len_argsort(seq):传入一系列语句数据(分好词的列表形式)按照语句长度排序后返回排序后原来各语句在数据中的索引下标return sorted(range(len(seq)), keylambda x: len(seq[x]))# 按相同顺序对中文、英文样本排序if sort:# 以英文语句长度排序sorted_index len_argsort(out_en_ids)out_en_ids [out_en_ids[idx] for idx in sorted_index]out_cn_ids [out_cn_ids[idx] for idx in sorted_index]return out_en_ids, out_cn_idsdef split_batch(self, en, cn, batch_size, shuffleTrue):划分批次shuffleTrue表示对各批次顺序随机打乱# 每隔batch_size取一个索引作为后续batch的起始索引idx_list np.arange(0, len(en), batch_size)# 起始索引随机打乱if shuffle:np.random.shuffle(idx_list)# 存放所有批次的语句索引batch_indexs []for idx in idx_list:形如[array([4, 5, 6, 7]), array([0, 1, 2, 3]), array([8, 9, 10, 11]),...]# 起始索引最大的批次可能发生越界要限定其索引batch_indexs.append(np.arange(idx, min(idx batch_size, len(en))))# 构建批次列表batches []for batch_index in batch_indexs:# 按当前批次的样本索引采样batch_en [en[index] for index in batch_index]batch_cn [cn[index] for index in batch_index]# 对当前批次中所有语句填充、对齐长度# 维度为batch_size * 当前批次中语句的最大长度batch_cn seq_padding(batch_cn)batch_en seq_padding(batch_en)# 将当前批次添加到批次列表# Batch类用于实现注意力掩码batches.append(Batch(batch_en, batch_cn))return batchesif __name__ __main__:DEV_FILE ../data/en-cn/train.txt # 训练集TRAIN_FILE ../data/en-cn/dev.txt # 验证集data PrepareData(TRAIN_FILE, DEV_FILE) # 实例化类类的一些成员变量print(data.en_word_dict) #文件里所有英文组成一个字典。{BOS: 2, EOS: 3, .: 4, i: 5, the: 6, you: 7, to: 8, ......}print(data.cn_word_dict) #文件里所有中文组成一个字典。{BOS: 2, EOS: 3, 。: 4, 我: 5, 的: 6, 了: 7, ......}print(data.cn_total_words)print(data.en_total_words)print(data.en_index_dict) #{2: BOS, 3: EOS, 4: ., 5: i, 6: the, 7: you, 8: to, 9: a, 10: ?,。....}print(data.cn_index_dict)print(data.dev_data)print(data.dev_cn)#[[2, 5, 1273, 7, 4, 3], [2, 5, 93, 7, 924, 34, 1, 4, 3], [2, 5, 61, 153, 694, 479, 4, 3], [2, 15, 47, 51, 48, 4, 3],....]#print(data.train_data) # 里面包含很多个batch每个batch里有数据信息# for index, batch in enumerate(data.train_data):
# print(batch.src) # 转为tensor的batch_en每句话的英文单词在字典的索引构成的列表且填充好
# print(batch.trg) # 转为tensor的batch_cn每句话的中文单词在字典的索引构成的列表且填充好
# print(batch.src_mask) # batch_en的掩码
# print(batch.trg_mask) # batch_cn的掩码
#可能遇到的错误和解决 https://blog.csdn.net/lu_rong_qq/article/details/143409795
模型定义和训练
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy
import torch.nn.functional as Ffrom npl.PrepareData import PrepareData
from npl.parser import args 多头注意力
MultiHeadAttention类封装了在Transformer模型中常用的多头注意力机制。它处理将输入分割成多个注意力头对每个头应用注意力
然后将结果组合。通过这样做模型可以在不同尺度上捕获输入数据中的各种关系提高模型的表达能力。
forward 过程
1 应用线性变换首先使用初始化中定义的权重将查询Q、键K和值V通过线性变换。
2 分割头使用split_heads方法将转换后的Q、K、V分割成多个头。
3 应用缩放点积注意力在分割的头上调用scaled_dot_product_attention方法。注意力分数是通过取查询Q和键K的点积然后通过键的维度d_k的平方根进行缩放来计算的。
4 组合头使用combine_heads方法将每个头的结果组合回单个张量。
5 应用输出变换最后组合的张量通过输出线性变换。class MultiHeadAttention(nn.Module):def __init__(self, d_model, num_heads):super(MultiHeadAttention, self).__init__()# 确保模型维度d_model可以被注意力头数整除assert d_model % num_heads 0, d_model必须能被num_heads整除# 初始化维度self.d_model d_model # 模型的维度self.num_heads num_heads # 注意力头的数量self.d_k d_model // num_heads # 每个头的键、查询和值的维度# 用于转换输入的线性层self.W_q nn.Linear(d_model, d_model) # 查询转换self.W_k nn.Linear(d_model, d_model) # 键转换self.W_v nn.Linear(d_model, d_model) # 值转换self.W_o nn.Linear(d_model, d_model) # 输出转换# 缩放点积注意力:scaled_dot_product_attentiondef scaled_dot_product_attention(self, Q, K, V, maskNone):# 计算注意力分数 乘除法是缩放# 点积 这里注意力分数是通过取查询Q和键K的点积然后通过键的维度d_k的平方根进行缩放来计算的。根据论文公式attn_scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)# 应用掩码如果提供了掩码则将其应用于注意力分数以掩盖特定值如填充的注意力。if mask is not None:attn_scores attn_scores.masked_fill(mask 0, -1e9)# 应用softmax以获得注意力概率计算注意力权重注意力分数通过softmax函数传递以将它们转换为总和为1的概率。attn_probs torch.softmax(attn_scores, dim-1)# 计算输出乘以值以获得最终输出注意力的最终输出是通过将注意力权重乘以值V来计算的。output torch.matmul(attn_probs, V)return outputdef split_heads(self, x):# 重塑输入以进行多头注意力batch_size, seq_length, d_model x.size()return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)def combine_heads(self, x):# 将多个头重新组合成原始形状batch_size, _, seq_length, d_k x.size()return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)def forward(self, Q, K, V, maskNone):# 应用线性变换并分割头Q self.split_heads(self.W_q(Q))K self.split_heads(self.W_k(K))V self.split_heads(self.W_v(V))# 执行缩放点积注意力attn_output self.scaled_dot_product_attention(Q, K, V, mask)# 组合头并应用输出变换output self.W_o(self.combine_heads(attn_output))return outputclass PositionwiseFeedForward(nn.Module):Implements FFN equation.def __init__(self, d_model, d_ff, dropout0.1):super(PositionwiseFeedForward, self).__init__()self.w_1 nn.Linear(d_model, d_ff)self.w_2 nn.Linear(d_ff, d_model)self.dropout nn.Dropout(dropout)def forward(self, x):return self.w_2(self.dropout(self.w_1(x).relu()))
位置感知前馈网络 (由两个带有ReLU激活函数的线性层组成)
PositionWiseFeedForward类定义了一个位置感知的前馈神经网络由两个带有ReLU激活函数的线性层组成。
在Transformer模型的上下文中这个前馈网络分别且相同地应用于每个位置。它有助于转换由注意力机制在Transformer中学习到的特征。class PositionWiseFeedForward(nn.Module):def __init__(self, d_model, d_ff,dropout0.1):参数:param d_model: 模型输入和输出的维度。:param d_ff: 前馈网络中内层的维度。self.fc1和self.fc2: 两个全连接线性层输入和输出维度由d_model和d_ff定义。self.relu: ReLU修正线性单元激活函数在两个线性层之间引入非线性。super(PositionWiseFeedForward, self).__init__()self.fc1 nn.Linear(d_model, d_ff) # 定义两个模型组件fc1、fc2self.fc2 nn.Linear(d_ff, d_model) # 定义两个模型组件fc1、fc2self.relu nn.ReLU() # 定义激活函数self.dropout nn.Dropout(dropout)def forward(self, x):x: 前馈网络的输入。输入首先通过第一个线性层fc1。self.fc1()等价于 self.fc1.forward()self.relu(...): fc1的输出然后通过ReLU激活函数。(ReLU将所有负值替换为零为模型引入非线性。)self.fc2(...): 激活的输出然后通过第二个线性层fc2产生最终输出。return self.fc2(self.dropout(self.relu(self.fc1.forward(x))))
位置编码函数根据论文公式
d_model: 模型输入的维度。
max_seq_length: 预先计算位置编码的最大序列长度。前向方法简单地将位置编码添加到输入x。它使用pe的前x.size(1)个元素以确保位置编码与x的实际序列长度相匹配。最后将pe注册为缓冲区这意味着它将是模块状态的一部分但不会被视为可训练参数。class PositionalEncoding(nn.Module):def __init__(self, d_model, max_seq_length):初始参数:param d_model: 模型输入的维度。:param max_seq_length: 预先计算位置编码的最大序列长度。super(PositionalEncoding, self).__init__()# 用零填充的张量将填充位置编码。pe torch.zeros(max_seq_length, d_model)# 包含序列中每个位置的位置索引的张量。position torch.arange(0, max_seq_length, dtypetorch.float).unsqueeze(1)# 用于以特定方式缩放位置索引的项。div_term torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))# 正弦函数应用于pe的偶数索引余弦函数应用于奇数索引。pe[:, 0::2] torch.sin(position * div_term)pe[:, 1::2] torch.cos(position * div_term)# 最后将pe注册为缓冲区这意味着它将是模块状态的一部分但不会被视为可训练参数。self.register_buffer(pe, pe.unsqueeze(0))def forward(self, x):return x self.pe[:, :x.size(1)]
EncoderLayer类定义了Transformer编码器的单层。它封装了多头自注意力机制然后是位置感知前馈神经网络带有残差连接、层归一化和dropout。forward 处理步骤
自注意力输入x通过多头自注意力机制传递。
加法 归一化自注意力后注意力输出添加到原始输入残差连接然后是dropout和使用norm1的归一化。
前馈网络前一步的输出通过位置感知前馈网络传递。
加法 归一化前馈后与步骤2类似前馈输出添加到该阶段的输入残差连接然后是dropout和使用norm2的归一化。
输出处理后的张量作为编码器层的输出返回。class EncoderLayer(nn.Module):def __init__(self, d_model, num_heads, d_ff, dropout):初始参数:param d_model: 输入的维度。:param num_heads: 多头注意力中的注意力头数。:param d_ff: 位置感知前馈网络中内层的维度。:param dropout: 用于正则化的dropout率。0.1组件self.self_attn: 多头自注意力机制。self.feed_forward: 位置感知前馈神经网络。self.norm1和self.norm2: 层归一化应用于平滑层的输入。self.dropout: Dropout层用于通过在训练期间随机将一些激活设置为零来防止过拟合。super(EncoderLayer, self).__init__()self.self_attn MultiHeadAttention(d_model, num_heads)self.feed_forward PositionWiseFeedForward(d_model, d_ff)self.norm1 nn.LayerNorm(d_model)self.norm2 nn.LayerNorm(d_model)self.dropout nn.Dropout(dropout)def forward(self, x, mask)::param x: 编码器层的输入。:param mask: 可选掩码用于忽略输入的某些部分。:return:下面 完全根据论文写attn_output self.self_attn(x, x, x, mask) # 计算多头注意力x self.norm1(x self.dropout(attn_output)) # 残差连接Add输入输出和层归一化ff_output self.feed_forward(x) # 位置前馈网络x self.norm2(x self.dropout(ff_output)) # 残差连接Add输入输出和层归一化return x
DecoderLayer类定义了Transformer解码器的单层。它由多头自注意力机制、多头交叉注意力机制关注编码器的输出、位置感知前馈神经网络以及相应的残差连接、
层归一化和dropout层组成。这种组合使解码器能够根据编码器的表示生成有意义的输出同时考虑目标序列和源序列。与编码器一样通常会堆叠多个解码器层以形成完整的解码器部分。forward
处理步骤
目标序列上的自注意力输入x通过自注意力机制处理。
加法 归一化自注意力后自注意力的输出添加到原始x然后是dropout和使用norm1的归一化。
编码器输出上的交叉注意力前一步归一化输出通过交叉注意力机制处理该机制关注编码器的输出enc_output。
加法 归一化交叉注意力后交叉注意力的输出添加到该阶段的输入然后是dropout和使用norm2的归一化。
前馈网络前一步的输出通过前馈网络传递。
加法 归一化前馈后前馈输出添加到该阶段的输入然后是dropout和使用norm3的归一化。
输出处理后的张量作为解码器层的输出返回。class DecoderLayer(nn.Module):def __init__(self, d_model, num_heads, d_ff, dropout):参数:param d_model: 输入的维度。:param num_heads: 多头注意力中的注意力头数。:param d_ff: 前馈网络中内层的维度。:param dropout: 用于正则化的dropout率。组件self.self_attn: 目标序列的多头自注意力机制。self.cross_attn: 多头注意力机制用于关注编码器的输出。self.feed_forward: 位置感知前馈神经网络。self.norm1, self.norm2, self.norm3: 层归一化组件。self.dropout: 用于正则化的dropout层。super(DecoderLayer, self).__init__()self.self_attn MultiHeadAttention(d_model, num_heads)self.cross_attn MultiHeadAttention(d_model, num_heads)self.feed_forward PositionWiseFeedForward(d_model, d_ff)self.norm1 nn.LayerNorm(d_model)self.norm2 nn.LayerNorm(d_model)self.norm3 nn.LayerNorm(d_model)self.dropout nn.Dropout(dropout)def forward(self, x, enc_output, src_mask, tgt_mask):输入:param x: 解码器层的输入。:param enc_output: 对应编码器的输出用于交叉注意力步骤。:param src_mask: 源掩码用于忽略编码器输出的某些部分。:param tgt_mask: 目标掩码用于忽略解码器输入的某些部分遮挡后面的单词。:return:attn_output self.self_attn(x, x, x, tgt_mask) # masked 多头注意力参数Q,K,V,MASKx self.norm1(x self.dropout(attn_output)) # 残差连接Add输入输出和层归一化# 同时接收来自编码器端的输出以及前一个掩码注意力层的输出,称为交叉注意力。# Key和Value是使用编码器的输出进行投影的Query是通过解码器前一层的输出进行投影所得。attn_output self.cross_attn(x, enc_output, enc_output, src_mask)x self.norm2(x self.dropout(attn_output)) # 残差连接Add输入输出和层归一化ff_output self.feed_forward(x) # 前馈netx self.norm3(x self.dropout(ff_output)) # 残差连接Add输入输出和层归一化return x
定义参数和组件的模型
Transformer类汇集了Transformer模型的各个组件包括嵌入、位置编码、编码器层和解码器层。它为训练和推理提供了方便的接口封装了多头注意力、前馈网络和层归一化的复杂性。
这个实现遵循标准的Transformer架构适用于机器翻译、文本摘要等序列到序列任务。class Transformer(nn.Module):构造函数采用以下参数src_vocab_size: 源词汇表大小。 5000tgt_vocab_size: 目标词汇表大小。5000d_model: 模型嵌入的维度。 512num_heads: 多头注意力机制中的注意力头数。 8num_layers: 编码器和解码器的层数。 6d_ff: 前馈网络中内层的维度。 2048max_seq_length: 位置编码的最大序列长度。 100dropout: 用于正则化的dropout率。 0.1def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):super(Transformer, self).__init__()self.encoder_embedding nn.Embedding(src_vocab_size, d_model) # 源序列的嵌入层。self.decoder_embedding nn.Embedding(tgt_vocab_size, d_model) # 目标序列的嵌入层。self.positional_encoding PositionalEncoding(d_model, max_seq_length) # 位置编码组件。# 编码器层的列表。self.encoder_layers nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])# 解码器层的列表。self.decoder_layers nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])# 将输出映射到目标词汇表大小的最终全连接线性层。输出函数self.fc nn.Linear(d_model, tgt_vocab_size)self.dropout nn.Dropout(dropout) # Dropout层。def generate_mask(self, src, tgt):src_mask (src ! 0).unsqueeze(1).unsqueeze(2)tgt_mask (tgt ! 0).unsqueeze(1).unsqueeze(3)seq_length tgt.size(1)# triu diagonal1 右移一位的上三角矩阵nopeak_mask (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal1)).bool()tgt_mask tgt_mask nopeak_maskreturn src_mask, tgt_mask# forward# 方法定义了Transformer的前向传递采用源和目标序列并产生输出预测。# 输入嵌入和位置编码首先使用各自的嵌入层嵌入源和目标序列然后添加它们的位置编码。# 编码器层源序列通过编码器层传递最终编码器输出表示处理过的源序列。# 解码器层目标序列和编码器的输出通过解码器层传递得到解码器的输出。# 最终线性层解码器的输出使用全连接线性层映射到目标词汇表大小。# 输出最终输出是一个张量表示模型对目标序列的预测。#def forward(self, src, tgt):src_mask, tgt_mask self.generate_mask(src, tgt)src_embedded self.dropout(self.positional_encoding(self.encoder_embedding(src)))tgt_embedded self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))enc_output src_embeddedfor enc_layer in self.encoder_layers:enc_output enc_layer(enc_output, src_mask)dec_output tgt_embeddedfor dec_layer in self.decoder_layers:dec_output dec_layer(dec_output, enc_output, src_mask, tgt_mask)output self.fc(dec_output)return output
训练模型
超参数这些值定义了Transformer模型的架构和行为
src_vocab_size, tgt_vocab_size: 源和目标序列的词汇表大小均设置为5000。
d_model: 模型嵌入的维度设置为512。
num_heads: 多头注意力机制中的注意力头数设置为8。
num_layers: 编码器和解码器的层数设置为6。
d_ff: 前馈网络中内层的维度设置为2048。
max_seq_length: 位置编码的最大序列长度设置为200。
dropout: 用于正则化的dropout率设置为0.1。
if __name__ __main__:TRAIN_FILE ../data/en-cn/train.txt # 训练集DEV_FILE ../data/en-cn/dev.txt # 验证集save_file ../weights/model.pt # 模型保存路径d_model 512 # 单词向量模型嵌入的维度设置为512num_heads 8 # 多头注意力机制中的注意力头数设置为8num_layers 6 # 编码器和解码器的层数设置为6d_ff 2048 # 前馈网络中内层的维度设置为2048max_seq_length 200 # 位置编码的最大序列长度设置为200dropout 0.1 # 用于正则化的dropout率设置为0.1PAD 0 # padding占位符的索引UNK 1 # 未登录词标识符的索引epochs 5 # 训练轮数device cpudata PrepareData(TRAIN_FILE, DEV_FILE) # 处理训练数据数据路径tgt_vocab len(data.cn_word_dict)src_vocab len(data.en_word_dict)args.src_vocab len(data.en_word_dict)args.tgt_vocab len(data.cn_word_dict)args.save_file save_fileprint(src_vocab %d % args.src_vocab)print(tgt_vocab %d % args.tgt_vocab)print( start train)model Transformer(src_vocab_sizesrc_vocab, tgt_vocab_sizetgt_vocab,d_modeld_model, num_headsnum_heads,num_layersnum_layers, d_ffd_ff, max_seq_lengthmax_seq_length, dropoutdropout)criterion nn.CrossEntropyLoss(ignore_index0)optimizer optim.Adam(model.parameters(), lr0.0001, betas(0.9, 0.98), eps1e-9)for epoch in range(args.epochs):model.train()total_tokens 0.total_loss 0.tokens 0.for i, batch in enumerate(data.train_data): # 获得batch块及其索引optimizer.zero_grad() # 清除上一次迭代的梯度#x model(batch.src, batch.trg)
# x F.log_softmax(nn.Linear(d_model, tgt_vocab).forward(x1), dim-1)print(x)# 损失通过将数据重塑为一维张量并使用交叉熵损失函数来计算。 ---- 计算误差loss criterion(x.contiguous().view(-1, x.size(-1)), batch.trg_y.contiguous().view(-1))total_loss loss # 计算所有batch的总体损失。total_tokens batch.ntokens # batch.ntokens每个batch里有多少个组数据。这里记录所有batch里样本的总数。loss.backward() # 计算损失相对于模型参数的梯度。 ---- 计算梯度optimizer.step() # 使用计算出的梯度更新模型的参数。---- 更新print(fEpoch: {epoch1}, Loss: {loss.item()}) # 打印当前周期号和该周期的损失值。if i % 50 1:tokens 0 # 将50个batch的样本数清零。# model.eval()# print( Evaluate)# with torch.no_grad():# loss criterion(x.contiguous().view(-1, x.size(-1)), batch.trg_y.contiguous().view(-1))# print( Evaluate loss: %f % loss)torch.save(model.state_dict(), args.save_file)命令参数解析辅助类
import argparse
import torch
parser argparse.ArgumentParser()parser.add_argument(--train-file, default../data/train.txt)
parser.add_argument(--dev-file, default../data/dev.txt)parser.add_argument(--UNK, default0, typeint)
parser.add_argument(--PAD, default1, typeint)# TODO 常改动参数
parser.add_argument(--type, defaulttrain) # 默认是训练模式, 若传递 evaluate 则对 dev数据集进行预测输出
parser.add_argument(--gpu, default3, typeint) # gpu 卡号
parser.add_argument(--epochs, default5, typeint) # 训练轮数
parser.add_argument(--layers, default2, typeint) # transformer层数
parser.add_argument(--h-num, default8, typeint) # multihead attention hidden层数
parser.add_argument(--batch-size, default64, typeint)
parser.add_argument(--d-model, default256, typeint)
parser.add_argument(--d-ff, default1024, typeint)
parser.add_argument(--dropout, default0.1, typefloat)
parser.add_argument(--max-length, default60, typeint)
parser.add_argument(--save-file, defaultsave/model.pt) # 模型保存位置args parser.parse_args()device torch.device(fcuda:{args.gpu} if torch.cuda.is_available() else cpu)
args.device device源码和样本数据下载https://github.com/hinesboy/transformer-simple.git 模型定义原理一样写法略有不同。
归一化、掩码mask以及Transformer模型中的多头注意力机制都是深度学习中非常重要的概念。
归一化 在神经网络中归一化通常指的是将输入数据标准化到某个特定范围如0和1之间但更常见的做法是对数据进行标准化standardization即将数据转换为均值为0、方差为1的分布。这有助于提高模型的训练速度和稳定性尤其是在使用梯度下降法时。
掩码Mask 在Transformer模型中掩码的使用非常关键主要有两种类型padding mask 和 sequence mask。
Padding Mask 在处理变长序列时需要对输入序列进行填充使得每个批次的输入具有相同的长度。填充通常用0来表示这些填充的位置并不携带有用信息。 为了确保模型在注意力计算中不关注这些填充位置padding mask会将这些位置的值加上一个非常大的负数如负无穷这样在经过softmax之后这些位置的注意力权重将接近于0确保它们不会影响模型的学习。
Sequence Mask 在解码阶段为了确保模型在生成当前时刻的输出时不能看到未来的信息我们需要使用sequence mask。具体来说对于时间步t解码器的输出只能依赖于t之前的输出而不能依赖于t之后的输出。 实现方法是构建一个上三角矩阵上三角的值全为0表示在计算注意力时只允许关注当前时间步及之前的时间步未来的信息被掩盖。
Transformer中的多头注意力 在Transformer模型中Encoder和Decoder的多头注意力机制略有不同
Encoder的多头注意力 只需要使用padding mask确保填充位置不会影响注意力计算。
Decoder的多头注意力 需要同时使用padding mask和sequence mask。第一个Masked Multi-Head Attention用于获取之前已预测的输出信息确保模型能够利用历史信息进行预测。
第二个Multi-Head Attention则是通过当前的输入信息与Encoder输出的特征向量进行交互以生成下一个时间步的输出。 通过使用掩码Transformer能够有效地处理变长序列并避免信息泄露。归一化则有助于提高训练的效率和效果。
多头注意力机制具有几个优点
并行化通过同时关注输入序列的不同部分多头注意力显著加快了计算速度使其比传统的注意力机制更加高效。
增强表示每个注意力头都关注输入序列的不同方面使模型能够捕捉各种模式和关系。这导致输入的表示更丰富、更强大增强了模型理解和生成文本的能力。
改进泛化性多头注意力使模型能够关注序列内的局部和全局依赖关系从而提高了跨不同任务和领域的泛化性。 软注意力是一种用来衡量数据重要性的机制。在计算机中软注意力通过一些数学函数来计算这个“关注”的程度比如 softmax 或 sigmoid。 这是一种可以预测的方式主要用在以下三种类型的注意力机制中 通道注意力它根据每个特征通道或滤镜来计算得分根据不同的特征图来做判断。 空间注意力这是针对图像的具体区域进行关注而不是通道。它在目标检测、语义分割和人员重新识别等任务中非常有用。 自注意力这个机制会比较输入数据中不同部分之间的关联。自注意力根据输入数据中两个部分的相似度来计算得分。 软注意力的方法通常通过软函数如 softmax 和 sigmoid来加权输入数据的不同部分。这种方式是可预测且可微的这意味着可以通过反向传播进行训练。 通道、空间和时序注意力可以看作是作用于不同域的机制。
硬注意力和软注意力的主要区别在于硬注意力的随机性。硬注意力只选择一个特定的区域来关注而不是像软注意力那样平均分配注意力。硬注意力的主要类型包括
贝叶斯注意力 (Bayesian Attention)这种注意力使用贝叶斯统计模型来决定哪部分应该得到关注。就像你在做实验时用概率来推断某个结果的可能性。经常用于解决视觉问题因为它可以帮助模型在不确定的情况下做出更好的决策。 强化学习注意力 (Reinforced Attention)强化学习是一种基于奖励和惩罚的学习方式。利用这种方式来训练模型帮助它选择最重要的部分。 高斯注意力 (Gaussian Attention)高斯注意力使用一种叫做 2D 高斯核的数学工具来计算注意力得分。
多模态注意力机制是一种用来处理多种不同类型数据的方法比如文本和图像。它的主要特点是能够在不同模式之间生成注意力也就是让模型知道在一堆不同数据中关注哪些部分。 常见的多模态注意力机制包括
交叉注意力 (Cross Attention)这个机制通过比较不同模式之间的相关性来生成注意力得分。 Perceiver 模型这是一个基于 Transformer 的多模态模型专门处理大规模、多模态数据。它可以同时处理不同类型的数据如文本、图像和音频。 多模态注意力机制的好处是它可以让模型从不同角度理解数据帮助模型更全面地做出决策。