DiffusionModel-Transformer知识回顾

论文链接: Attention Is All You Need

CNN vs RNN vs Transformer

  • CNN(卷积神经网络)

    • 特点:
      • 局部连接:每个卷积神经元只与输入数据的局部区域连接,这有助于捕捉局部特征。
      • 权重共享:卷积核的权重在整个输入数据上共享,减少了模型参数的数量,可并行计算。
      • 自动特征提取:无需手动设计特征提取器,网络可以自动学习到有用的特征。
      • 多尺度处理:通过池化层,可以处理不同尺度的特征。
    • 缺点:对相对位置敏感,对绝对位置不敏感
  • RNN(循环神经网络)

    • 特点:
      • 序列处理:能够处理序列数据,如时间序列、文本等。对顺序敏感
      • 记忆能力:通过隐藏状态传递信息,具有记忆过去信息的能力。当前时刻输出必须依赖于上一时刻运算。
      • 参数共享:在序列的每个时间点上,使用相同的权重矩阵。
    • 缺点:
      • 梯度消失/爆炸:在长序列上训练时,梯度可能会消失或爆炸,导致训练困难。
      • 串行计算耗时,每一时刻计算依赖于上一时刻计算,计算复杂度与序列长度线性关系。
      • 长程建模能力弱。
      • 对相对位置敏感,对绝对位置敏感。
  • Transformer

    • 自注意力机制:每个位置的输出都与序列中所有位置有关,这使得模型能够捕捉长距离依赖关系。
    • 没有局部假设,可以进行并行计算,对相对位置不敏感。
    • 没有有序假设,对绝对位置不敏感,需要位置编码来反映位置变化对于特征的影响,
    • 任意两个字符可以进行建模,擅长长短程建模,序列长度的平方级级别。
    • 并行化处理:由于自注意力机制,Transformer可以并行处理序列中的所有元素。
    • 可扩展性:容易扩展到更大的模型和更长的序列。
    • Transformer由于其并行化的特性,在训练效率上通常优于RNN。
  • 区别:

    • 处理数据类型:CNN主要用于图像等具有网格状拓扑结构的数据,RNN和Transformer主要用于序列数据。
    • 特征捕捉能力:CNN擅长捕捉局部特征,RNN擅长捕捉时间序列中的动态特征,而Transformer通过自注意力机制能够捕捉全局特征。
    • 训练效率:Transformer由于其并行化的特性,在训练效率上通常优于RNN。
    • 长序列处理:Transformer通过自注意力机制更好地处理长序列,而RNN可能会遇到梯度消失或爆炸的问题。

Transformer架构

参考:https://blog.csdn.net/weixin_42475060/article/details/121101749

在这里插入图片描述
在这里插入图片描述

  • 输入层:将输入序列转换为模型可以理解的格式。
  • 编码器(Encoder):处理输入序列,提取特征。
    • inputEmbedding和position Encoding作为输入,状态作为输出。由于残差链接的存在,位置信息可以进行充分的传递。
    • encoder由很多个block组成
      • 自注意力层(Self-Attention Layer):允许模型在处理当前词时考虑序列中的所有词。
      • 前馈网络(Feed-Forward Neural Network):一个简单的神经网络,用于进一步处理自注意力层的输出。
      • 在每一部分上,都使用残差+layer normalization来进行处理
  • 解码器(Decoder):根据编码器的输出和之前生成的输出序列生成最终的输出序列。
    • outputEmbedding和position Encoding作为输入,输出预测概率。

    • 掩码多头自注意力层(Masked Multi-Head Self-Attention):与编码器中的自注意力类似,但添加了一个掩码来防止未来位置的信息流入当前位置。

    • 编码器-解码器注意力层(Multi-head Attention):允许解码器层关注编码器的输出。

      • 这一步是解码器与编码器交互的过程。解码器使用编码器的输出作为键(Key)和值(Value),而解码器当前位置的输出作为查询(Query)。
      • 这允许解码器在生成每个输出词时,能够关注编码器处理过的整个输入序列,从而更好地理解输入序列的上下文。
    • 前馈网络FFN:与编码器中的前馈网络相同,用于进一步处理自注意力层的输出。

      • F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 \mathrm{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2
      • FFN 包含两个线性变换,这两个变换是使用不同的权重矩阵和偏置向量进行的
      • 前馈全连接网络也可以被描述为两次卷积操作,其中卷积核的大小为 1。这种描述方式强调了 FFN 在处理序列数据时的局部性,即每个位置的处理只依赖于它自己的输入。

Encoder

  • input word embedding:由稀疏的one-hot进入一个不带bias的FFN(全连接网络)中得到一个稠密的连续向量,可以结存内存,表征更丰富。
  • position encoding
    • 通过sin/cos来固定表征 :每个位置的position encoding是确定性的,对于不同句子,相同位置距离一致,可以推广到更长的句子。
    • pe(pos+k)可以写成pe(pos)的线性组合,从而在测试集中可以推广到更长的句子。‘
    • 通过残差链接使位置信息流入深层
  • multi-head self-attention:
    • 多头使建模能力更强
    • 多组K,Q,V构成,每组单独计算attention向量,把每组的attention向量拼接起来,并进入一个FNN得到最终向量
  • feed-forward network:前馈神经网络只是对每个单独位置进行建模,只考虑每个位置的字符,不同位置参数是共享的。每个embedding各自维度进行融合。 F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 \mathrm{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2

Decoder

  • output word embedding:由稀疏的one-hot进入一个不带bias的FFN(全连接网络)中得到一个稠密的连续向量,可以结存内存,表征更丰富。
  • position encoding
    • 通过sin/cos来固定表征 :每个位置的position encoding是确定性的,对于不同句子,相同位置距离一致,可以推广到更长的句子。
    • pe(pos+k)可以写成pe(pos)的线性组合,从而在测试集中可以推广到更长的句子。‘
    • 通过残差链接使位置信息流入深层
  • masked multi-head self-attention:
    • 多头使建模能力更强
    • 多组K,Q,V构成,每组单独计算attention向量,把每组的attention向量拼接起来,并进入一个FNN得到最终向量
    • 添加了一个掩码来防止未来位置的信息流入当前位置。
  • feed-forward network:前馈神经网络只是对每个单独位置进行建模,只考虑每个位置的字符,不同位置参数是共享的。每个embedding各自维度进行融合。
    • F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 \mathrm{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2
    • 这里的全连接是Position-wise逐位置的,即设前面的attention输出的维度为 B a t c h ∗ L e n g t h ∗ d m o d e l Batch * Length * d_{model} BatchLengthdmodel ,则变换时,实际上是只针对 d m o d e l d_{model} dmodel进行变换,对于每个位置(Length维度)上,都使用同样的变换矩阵,这意味着对于序列中的每个元素,网络都会应用相同的线性变换和激活函数
    • 在论文中,这里的 d m o d e l d_{model} dmodel仍然是512,两层全连接的中间隐层单元数为 d f f = 2048 d_{ff} = 2048 dff=2048。这意味着 FFN 会首先将输入从 512 维扩展到 2048 维,然后通过 ReLU 激活函数,最后再将维度从 2048 维压缩回 512 维。
  • softmax:概率输出

位置编码

它对于每个位置pos进行编码,然后与相应位置的word embedding进行相加,构成当前位置的新word embedding

P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i)}=sin(pos/10000^{2i/d_{\mathrm{model}}})\\PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{\mathrm{model}}}) PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)

  • i:embedding向量中的位置,即 d m o d e l d_{model} dmodel中每一维
  • sin/cos函数好处:可以不用训练,直接编码即可,且不论什么长度都能得到结果;可以表示相对位置 P E P O S + K PE_{POS+K} PEPOS+K可以表示为 P E P O S PE_{POS} PEPOS的线性变换。

注意力机制

自注意力机制

在这里插入图片描述

  • 一般的attention机制,可以抽象为输入一个查询(query),去查询键值对(key-value pair)中的key,然后得到一个概率分布,再据此对value进行加权相加,获取当前query下的注意力表征。而我们的query,往往是Decoder中某一个step的输出,key-value pair往往是encoder的输出。在self-attention中其query、key、value都是由encoder的输出经过不同的变换而来,也即self-attention,所有的东西都是自己。他们定义了一种叫“Scaled Dot-Product Attention”的计算方式,用于计算给定query、key和value下的注意力表征
    在这里插入图片描述
  • 这里的Q、K和V分别表示query、key和value矩阵,它们的维度分别为 L q ∗ d k 、 L k ∗ d k 、 L k ∗ d v L_q*d_k、L_k*d_k、L_k*d_v LqdkLkdkLkdv
  • 缩放因子 d k d_k dk :这里为何要进行缩放呢?论文中给出了解释:在 d k d_k dk 比较小的时候,不加缩放的效果和加性attention的效果差不多,但当 d k d_k dk 比较大的时候,不加缩放的效果就明显比加性attention的效果要差,怀疑是当 d k d_k dk 增长的时候,内积的量级也会增长,导致softmax函数会被推向梯度较小的区域,为了缓解这个问题,加上了这个缩放项进行量级缩小。

多头注意力机制

在这里插入图片描述

将注意力的计算分散到不同的子空间进行,以期望能从多方面进行注意力的学习,并行地将Q,K,V通过不同的映射矩阵映射到不同的空间(每个空间是一个头),再分别在这些空间中对应着进行单个“Scaled Dot-Product Attention”的学习,最后将得到的多头注意力表征进行拼接,经过一个额外映射层映射到原来的空间。
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d h ) W O w h e r e h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) \boxed{\mathrm{MultiHead}(Q,K,V)=\mathrm{Concat}(\mathrm{head}_{1},...,\mathrm{head}_{\mathrm{h}})W^{O}}\\\mathrm{where~head_{i}}=\mathrm{Attention}(QW_{i}^{Q},KW_{i}^{K},VW_{i}^{V}) MultiHead(Q,K,V)=Concat(head1,...,headh)WOwhere headi=Attention(QWiQ,KWiK,VWiV)

  • d m o d e l d_{model} dmodel是原始维度,而 d k d_k dk d v d_v dv 是投影后的键和值的维度。h 是头的数目,即要进行的注意力计算的次数。 d k = d v = d m o d e l / h d_k=d_v=d_{model}/h dk=dv=dmodel/h
  • W i Q ∈ R d m o d e l ∗ d k , W i K ∈ R d m o d e l ∗ d k , W i V ∈ R d m o d e l ∗ d v , W O ∈ R h d v ∗ d m o d e l W_i^Q\in R^{d_{model}*d_k},W_i^K\in R^{d_{model}*d_k},W_i^V\in R^{d_{model}*d_v},W^O\in R^{hd_v*d_{model}} WiQRdmodeldk,WiKRdmodeldk,WiVRdmodeldv,WORhdvdmodel
  • h e a d i head_i headi:表示第 i i i个头的变换矩阵, h h h表示头的个数,这是第 i 个头的自注意力输出。每个头都独立地执行自注意力操作。
  • 在论文里面, h = 8 h=8 h=8,并且 d k = d v = d m o d e l / h = 64 d_k=d_v=d_{model}/h=64 dk=dv=dmodel/h=64可见这里虽然分了很多头去计算,但整体的维度还是不变的,因此计算量还是一样的。

代码

https://blog.csdn.net/Magical_Bubble/article/details/89083225
https://nlp.seas.harvard.edu/2018/04/03/attention.html

Model Architecture

编码器解码器(通用架构)

  • 编码器的功能:编码器的作用是将输入的符号序列 ( x 1 , … , x n ) (x_1, \ldots, x_n) (x1,,xn)映射到一系列连续的表示 z = ( z 1 , … , z n ) \mathbf{z} = (z_1, \ldots, z_n) z=(z1,,zn)。这里的符号可以是单词、字符或其他任何形式的标记。

  • 连续表示:编码器输出的 z \mathbf{z} z是连续的向量表示,它们捕捉了输入序列的语义信息和结构信息。

  • 解码器的功能:给定编码器的输出 z \mathbf{z} z,解码器生成一个符号序列 ( ( y 1 , … , y m ) ((y_1, \ldots, y_m) ((y1,,ym),这个过程是逐步进行的,一次生成一个符号。

  • 自回归特性:在生成输出序列的过程中,模型是自回归的(auto-regressive)。这意味着在生成下一个符号时,模型会考虑之前已经生成的所有符号。这种特性使得模型能够生成连贯和语法正确的输出。

class EncoderDecoder(nn.Module):"""A standard Encoder-Decoder architecture. Base for this and many other models."""# 定义了一个名为 EncoderDecoder 的类,它继承自 PyTorch 的 nn.Module,# 表示这是一个神经网络模块,EncoderDecoder 可以作为许多其他模型的基础结构。    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):super(EncoderDecoder, self).__init__()self.encoder = encoderself.decoder = decoderself.src_embed = src_embedself.tgt_embed = tgt_embedself.generator = generator# 它们分别代表编码器、解码器、源语言嵌入层、目标语言嵌入层和输出生成器。# super() 调用确保了父类 nn.Module 的初始化过程被正确执行。    def forward(self, src, tgt, src_mask, tgt_mask):"Take in and process masked src and target sequences."return self.decode(self.encode(src, src_mask), src_mask,tgt, tgt_mask)# src 是源序列,tgt 是目标序列,src_mask 和 tgt_mask 是对应的掩码,# 用于在处理序列时忽略 padding 部分。# 方法首先调用 encode 处理源序列,然后使用 decode 方法进行解码。    #注意要传入maskdef encode(self, src, src_mask):return self.encoder(self.src_embed(src), src_mask)# encode 方法定义了编码过程,它接收源序列 src 和对应的掩码 src_mask。# 使用 src_embed 将源序列转换为嵌入表示,然后传递给编码器。    def decode(self, memory, src_mask, tgt, tgt_mask):return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)# decode 方法定义了解码过程,它接收经过编码器处理后的记忆(memory),# 源掩码 src_mask,目标序列 tgt 和目标掩码 tgt_mask。# 使用 tgt_embed 将目标序列转换为嵌入表示,然后传递给解码器。

ENCODER

class Encoder(nn.Module):"Core encoder is a stack of N layers"# 定义一个Encoder类,继承自PyTorch的nn.Module,作为模型的核心编码器,由N层堆叠而成。def __init__(self, layer, N):super(Encoder, self).__init__()# 初始化Encoder,调用父类nn.Module的构造函数。self.layers = clones(layer, N)# 创建N个layer的副本,堆叠成编码器的层。self.norm = LayerNorm(layer.size)# 添加一个层归一化(Layer Normalization)模块。def forward(self, x, mask):# 定义前向传播函数,接收输入x和掩码mask。"Pass the input (and mask) through each layer in turn."# 将输入和掩码逐层传递。for layer in self.layers:x = layer(x, mask)# 对于每一层,使用前一层的输出作为当前层的输入。return self.norm(x)# 经过所有层之后,使用归一化层处理最终的输出。class LayerNorm(nn.Module):
#LayerNorm(x+sublayer(x))"Construct a layernorm module (See citation for details)."# 定义一个LayerNorm类,用于实现层归一化。def __init__(self, features, eps=1e-6):super(LayerNorm, self).__init__()# 初始化LayerNorm模块。self.a_2 = nn.Parameter(torch.ones(features))# 创建一个可学习的参数a_2,用于归一化后的缩放。self.b_2 = nn.Parameter(torch.zeros(features))# 创建一个可学习的参数b_2,用于归一化后的偏移。self.eps = eps# 小的常数eps,用于数值稳定性。def forward(self, x):mean = x.mean(-1, keepdim=True)# 计算x在最后一个维度上的平均值。std = x.std(-1, keepdim=True)# 计算x在最后一个维度上的标准差。return self.a_2 * (x - mean) / (std + self.eps) + self.b_2# 应用归一化公式,并使用参数a_2和b_2进行缩放和偏移。class SublayerConnection(nn.Module):"""A residual connection followed by a layer norm.Note for code simplicity the norm is first as opposed to last."""# 定义一个SublayerConnection类,实现残差连接后跟一个层归一化。def __init__(self, size, dropout):super(SublayerConnection, self).__init__()# 初始化SublayerConnection。self.norm = LayerNorm(size)# 添加一个与输入尺寸相同的层归一化模块。self.dropout = nn.Dropout(dropout)# 添加一个dropout层,用于正则化。def forward(self, x, sublayer):# 定义前向传播函数,接收输入x和一个子层函数sublayer。"Apply residual connection to any sublayer with the same size."# 将残差连接应用于具有相同尺寸的任何子层。return x + self.dropout(sublayer(self.norm(x)))# 将归一化后的输入x与经过dropout处理的子层输出相加,实现残差连接。

class EncoderLayer(nn.Module):"Encoder is made up of self-attn and feed forward (defined below)"# 定义一个EncoderLayer类,编码器层由自注意力机制和前馈网络组成。def __init__(self, size, self_attn, feed_forward, dropout):super(EncoderLayer, self).__init__()# 初始化EncoderLayer。self.self_attn = self_attn# 添加自注意力机制模块。self.feed_forward = feed_forward# 添加前馈网络模块。self.sublayer = clones(SublayerConnection(size, dropout), 2)''' def clones(module, N):return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])'''# 创建两个SublayerConnection的副本,用于实现两个残差连接。self.size = size# 保存层的尺寸。def forward(self, x, mask):# 定义前向传播函数。"Follow Figure 1 (left) for connections."# 按照文献中的图1(左)来连接各个组件。x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))# 应用第一个残差连接和自注意力机制。return self.sublayer[1](x, self.feed_forward)# 应用第二个残差连接和前馈网络。

DECODER

class Decoder(nn.Module):"Generic N layer decoder with masking."# 定义一个Decoder类,继承自PyTorch的nn.Module,是一个具有N层的通用解码器,支持掩码操作。def __init__(self, layer, N):super(Decoder, self).__init__()self.layers = clones(layer, N)# 创建N个layer的副本,构成解码器的层堆栈。self.norm = LayerNorm(layer.size)# 添加一个层归一化模块。def forward(self, x, memory, src_mask, tgt_mask):# 前向传播函数,接收目标序列的输入x,源序列的编码器输出memory,以及源序列和目标序列的掩码。for layer in self.layers:x = layer(x, memory, src_mask, tgt_mask)# 对每个解码器层应用前向传播,并将结果传递给下一层。return self.norm(x)# 经过所有层后,使用层归一化处理最终输出。def subsequent_mask(size):"Mask out subsequent positions."# 定义一个函数,用于生成一个掩码,以屏蔽目标序列中后续的位置。attn_shape = (1, size, size)# 定义注意力矩阵的形状。subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')# 使用NumPy生成一个上三角矩阵,并将数据类型转换为uint8。return torch.from_numpy(subsequent_mask) == 0# 将NumPy数组转换为PyTorch张量,并返回一个布尔张量,上三角部分为False,其余为True。
class DecoderLayer(nn.Module):"Decoder is made of self-attn, src-attn, and feed forward (defined below)"# 定义一个DecoderLayer类,继承自PyTorch的nn.Module,解码器层由自注意力机制、源注意力机制和前馈网络组成。def __init__(self, size, self_attn, src_attn, feed_forward, dropout):super(DecoderLayer, self).__init__()# 调用父类构造函数,初始化DecoderLayer。self.size = size# 保存解码器层的尺寸。self.self_attn = self_attn# 保存自注意力机制的实例。self.src_attn = src_attn# 保存源注意力机制的实例。self.feed_forward = feed_forward# 保存前馈网络的实例。self.sublayer = clones(SublayerConnection(size, dropout), 3)# 创建3个SublayerConnection的副本,用于解码器层中的3个残差连接。def forward(self, x, memory, src_mask, tgt_mask):# 前向传播函数,接收目标序列的输入x,编码器的输出memory,以及源序列和目标序列的掩码。"Follow Figure 1 (right) for connections."# 根据文献中的图1(右)来连接解码器层的组件。m = memory# 将编码器的输出赋值给变量m,以简化代码。x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))# 应用自注意力机制,并通过第一个残差连接和层归一化。x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))# 应用源注意力机制,并通过第二个残差连接和层归一化。return self.sublayer[2](x, self.feed_forward)# 应用前馈网络,并通过第三个残差连接和层归一化,返回最终输出。

Attention(多头注意力)

Transformer 以三种不同的方式使用多头注意力:

  • 1)在“编码器-解码器注意”层中,查询Q来自前面的解码器层,memory键key和值value来自输出的编码器。这允许解码器中的每个位置都参加所有 输入序列中的位置。这模拟了典型的编码器-解码器 序列到序列模型中的注意力机制
  • 2)编码器encoder包含自注意力层。在自我关注层(self-attention)中,所有 键K、值V和查询Q来自同一个地方,在本例中为输出编码器中的上一层。编码器中的每个位置都可以参加到编码器前一层的所有位置。
  • 3)类似地,解码器中的自注意力层允许每个位置在 解码器处理解码器中的所有位置,包括该 位置。我们需要防止解码器中的信息向左流动 保留自动回归属性。我们在缩放点内实现了这一点。 通过mask来关注产品(设置为−∞) 输入中的所有值 对应于非法连接的 Softmax。
#这个函数实际上就是“Scaled Dot Product Attention”这个模块的计算
def SDPattention(query, key, value, mask=None, dropout=None):"Compute 'Scaled Dot Product Attention'"d_k = query.size(-1)# 获取query的最后一个维度的大小,即d_k,它代表键(key)和值(value)的维度。scores = torch.matmul(query, key.transpose(-2, -1)) \/ math.sqrt(d_k)# 使用torch.matmul计算query和key的转置(-2, -1)的点积,得到注意力分数的原始值。# 然后,通过sqrt(d_k)进行缩放,以防止梯度消失或爆炸问题。if mask is not None:scores = scores.masked_fill(mask == 0, -1e9)# 如果提供了掩码,使用.masked_fill将掩码为0的位置替换为一个非常大的负数(-1e9),这在softmax操作中相当于将这些位置的概率设置为0。p_attn = F.softmax(scores, dim = -1)# 应用softmax函数对缩放后的分数进行归一化,得到每个位置的注意力权重。dim=-1表示在最后一个维度上应用softmax。if dropout is not None:p_attn = dropout(p_attn)# 如果提供了dropout,将其应用于注意力权重,以进行正则化。return torch.matmul(p_attn, value), p_attn# 最后,使用归一化的注意力权重和value计算加权和,得到最终的注意力输出。同时返回注意力权重用于可能的后续分析。
class MultiHeadedAttention(nn.Module):def __init__(self, h, d_model, dropout=0.1):"Take in model size and number of heads."# 初始化MultiHeadedAttention类,继承自PyTorch的nn.Module。super(MultiHeadedAttention, self).__init__()# 确保模型的维度(隐层单元数)d_model可以被头数h整除。assert d_model % h == 0# 假设值向量d_v的维度等于键向量d_k的维度。self.d_k = d_model // h# 保存每个头的键和值的维度。self.h = h# 保存头的数量。self.linears = clones(nn.Linear(d_model, d_model), 4)#3+1# 创建4个线性变换层的副本,用于query, key, value的线性变换和最终的输出变换。#其中的3个分别是Q、K和V的变换矩阵,最后一个是用于最后将多头concat之后进行变换的矩阵。self.attn = None# 保存注意力分数,初始化为None。self.dropout = nn.Dropout(p=dropout)# 创建一个dropout层实例。def forward(self, query, key, value, mask=None):"Implements Figure 2"# 前向传播函数,实现多头注意力机制。if mask is not None:# 如果提供了掩码,则将其扩展到适合多头注意力的维度。mask = mask.unsqueeze(1)# 扩展掩码的维度,以应用于所有头。nbatches = query.size(0)# 获取输入query的批量大小。# 1) Do all the linear projections in batch from d_model => h x d_k ,进行线性变换query, key, value = \[l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)for l, x in zip(self.linears, (query, key, value))]# 对linears中的每个linear中的query, key, value分别应用线性变换,然后重塑和转置以准备多头注意力。# 2) Apply attention on all the projected vectors in batch. x, self.attn = SDPattention(query, key, value, mask=mask, dropout=self.dropout)# 应用多头注意力机制,计算加权的value,同时保存注意力分数和应用dropout。# 3) "Concat" using a view and apply a final linear. x = x.transpose(1, 2).contiguous() \.view(nbatches, -1, self.h * self.d_k)# 将注意力加权的value转置和重塑,准备进行最终的线性变换。return self.linears[-1](x)# 应用最后一个线性变换层,得到最终的输出。

前馈网络

前馈网络实际上就是两层全连接

class PositionwiseFeedForward(nn.Module):"Implements FFN equation."# 定义一个位置感知前馈网络类,继承自PyTorch的nn.Module。def __init__(self, d_model, d_ff, dropout=0.1):super(PositionwiseFeedForward, self).__init__()# 初始化FFN。self.w_1 = nn.Linear(d_model, d_ff)# 第一个线性层,将输入从d_model维度变换到d_ff维度。self.w_2 = nn.Linear(d_ff, d_model)# 第二个线性层,将输入从d_ff维度变换回d_model维度。self.dropout = nn.Dropout(dropout)# Dropout层,用于正则化。def forward(self, x):# 前向传播函数,实现FFN的计算。return self.w_2(self.dropout(F.relu(self.w_1(x))))# 应用第一个线性层,然后是ReLU激活函数,接着是dropout,最后是第二个线性层。

位置编码

class PositionalEncoding(nn.Module):"Implement the PE function."# 定义一个位置编码类,继承自PyTorch的nn.Module。def __init__(self, d_model, dropout, max_len=5000):super(PositionalEncoding, self).__init__()# 初始化位置编码。self.dropout = nn.Dropout(p=dropout)# Dropout层,用于正则化。# Compute the positional encodings once in log space.pe = torch.zeros(max_len, d_model)# 初始化位置编码矩阵,大小为max_len(最大序列长度)乘以d_model(模型维度)。position = torch.arange(0, max_len).unsqueeze(1)# 生成0到max_len-1的序列,然后增加一个维度。div_term = torch.exp(torch.arange(0, d_model, 2) *-(math.log(10000.0) / d_model))# 计算除数项,用于正弦和余弦函数的频率调整。pe[:, 0::2] = torch.sin(position * div_term)# 将正弦函数应用于偶数索引的位置编码。pe[:, 1::2] = torch.cos(position * div_term)# 将余弦函数应用于奇数索引的位置编码。pe = pe.unsqueeze(0)# 增加一个维度,以匹配batch的维度。self.register_buffer('pe', pe)# 将位置编码注册为一个不需要梯度的buffer。def forward(self, x):# 前向传播函数,将位置编码添加到输入x。x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)# 将位置编码添加到输入x,Variable确保pe不会进行梯度计算。return self.dropout(x)# 应用dropout后返回结果。

可见,这里首先是按照最大长度max_len生成一个位置,而后根据公式计算出所有的向量,在forward函数中根据长度取用即可,非常方便。注意要设置requires_grad=False,因其不参与训练。

Embedding and Softmax

class Embeddings(nn.Module):def __init__(self, d_model, vocab):super(Embeddings, self).__init__()self.lut = nn.Embedding(vocab, d_model)self.d_model = d_modeldef forward(self, x):return self.lut(x) * math.sqrt(self.d_model)

Full Model

def make_model(src_vocab, tgt_vocab, N=6, d_model=512, d_ff=2048, h=8, dropout=0.1):"Helper: Construct a model from hyperparameters."# 定义一个辅助函数,根据超参数构建模型。c = copy.deepcopy# 使用deepcopy函数,以便在后续创建模块副本时保留原始模块的引用。attn = MultiHeadedAttention(h, d_model)# 创建多头注意力机制模块。ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 创建位置感知前馈网络模块。position = PositionalEncoding(d_model, dropout)# 创建位置编码模块。model = EncoderDecoder(Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),# 创建编码器,包含N个编码器层。使用 deepcopy 创建 attn 和 ff 的副本,确保每个 EncoderLayer 使用独立的注意力和前馈网络模块,而不是所有层共享同一个实例Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N),# 创建解码器,包含N个解码器层,src_vocab 表示源语言词汇大小nn.Sequential(Embeddings(d_model, src_vocab), c(position)),# 创建源序列的嵌入层和位置编码层,tgt_vocab表示目标语言词汇大小nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),# 创建目标序列的嵌入层和位置编码层。Generator(d_model, tgt_vocab)# 创建输出层,用于生成最终的输出序列。)# Initialize parameters with Glorot / fan_avg.# 使用Glorot初始化(也称为Xavier均匀初始化)初始化模型参数。for p in model.parameters():if p.dim() > 1:# 如果参数张量的维度大于1,则应用Xavier均匀初始化。nn.init.xavier_uniform_(p)return model# 返回构建好的模型。
  • 在这个函数中,src_vocab 和 tgt_vocab 分别代表源语言和目标语言的词汇表大小。N 是编码器和解码器层的数量,d_model 是模型的维度,d_ff 是前馈网络的维度,h 是多头注意力中头的数量,dropout 是dropout率。

  • Encoder 和 Decoder 是构建编码器和解码器的组件,它们分别由多个 EncoderLayer 和 DecoderLayer 组成。EncoderLayer 和 DecoderLayer 层内部使用 MultiHeadedAttention 和 PositionwiseFeedForward 模块。

  • Embeddings 是嵌入层,用于将输入序列的单词索引转换为连续的向量表示。PositionalEncoding 添加了位置信息到嵌入的向量中。

  • Generator 是输出层,通常是一个线性层后接一个softmax函数,用于生成最终的输出序列的概率分布。

Training

Batches and Masking

class Batch:"Object for holding a batch of data with mask during training."# 定义一个Batch类,用于在训练时存储一批数据及其掩码。def __init__(self, src, trg=None, pad=0):self.src = src # 保存源语言序列数据。self.src_mask = (src != pad).unsqueeze(-2)# 为源语言序列创建掩码,`unsqueeze(-2)` 在序列倒数第二个维度上增加一个维度,以匹配注意力机制的维度需求。if trg is None:# 如果没有提供目标语言序列,则不执行任何操作。returnself.trg = trg[:, :-1]# 保存目标语言序列数据,但去掉最后一个时间步,因为解码器在生成第t个词的时候只能看到前t-1个词。self.trg_y = trg[:, 1:]# 保存目标语言序列的下一个时间步,用于训练时的监督信号(用于计算损失)self.trg_mask = self.make_std_mask(self.trg, pad)# 为目标语言序列创建掩码,使用静态方法 `make_std_mask`。self.ntokens = (self.trg_y != pad).data.sum()# 计算目标序列中非填充词的数量,用于跟踪模型训练时的词数。@staticmethoddef make_std_mask(tgt, pad):"Create a mask to hide padding and future words."# 静态方法,用于创建掩码以隐藏填充词和未来词。tgt_mask = (tgt != pad).unsqueeze(-2)# 为目标语言序列创建掩码,类似于源序列掩码的创建。tgt_mask = tgt_mask & Variable(subsequent_mask(tgt.size(-1)).type_as(tgt_mask.data))# 结合上述掩码和通过 `subsequent_mask` 创建的三角掩码,以确保解码器在生成第t个词时不会看到未来的词。return tgt_mask# 返回最终的掩码。
  • src 代表源序列数据,trg 代表目标序列数据,pad 是填充标记的值
  • self.src_mask 是源序列的掩码,用于在处理注意力机制时忽略填充词pad。
  • 如果提供了 trg,则 self.trg 是目标序列,去掉了序列的最后一个词,因为解码器在生成序列时是自回归的,即在生成第 t 个词时只能看到前 t-1 个词,不能发生数据泄露。
  • self.trg_y 是目标序列的下一个时间步,用于计算损失。
  • self.trg_mask 是目标序列的掩码,它结合了填充词掩码和未来词掩码。

Training Loop

def run_epoch(data_iter, model, loss_compute):"Standard Training and Logging Function"# 定义一个函数,用于执行模型训练的标准步骤和记录日志。start = time.time()# 记录训练开始的时间。total_tokens = 0# 初始化用于累加的token计数器。total_loss = 0# 初始化用于累加的损失值。tokens = 0# 初始化token计数器,用于计算一段时间内的平均性能。for i, batch in enumerate(data_iter):# 遍历数据迭代器产生的批次数据。out = model.forward(batch.src, batch.trg, batch.src_mask, batch.trg_mask)# 将当前批次数据传递给模型的前向传播函数,获取模型的输出。loss = loss_compute(out, batch.trg_y, batch.ntokens)# 使用损失计算函数,根据模型输出、目标序列的下一个词以及批次中的token数计算损失。total_loss += loss# 累加损失值。total_tokens += batch.ntokens# 累加批次中的token数。tokens += batch.ntokens# 增加当前时间段内的token计数。if i % 50 == 1:# 每50个批次记录一次训练进度。elapsed = time.time() - start# 计算从上一次记录开始到现在经过的时间。print("Epoch Step: %d Loss: %f Tokens per Sec: %f" %(i, loss / batch.ntokens, tokens / elapsed))# 打印当前周期步数、平均损失以及每秒处理的token数。start = time.time()# 重置计时器,以便计算下一个时间段的性能。tokens = 0# 重置token计数器。return total_loss / total_tokens# 在整个周期结束时,返回平均损失。

Training Data and Batching

  • 数据集特征
    • WMT 2014 英德数据集:标准的英德机器翻译数据集,包含约450万句子对。
    • 字节对编码(BPE):一种词汇编码技术,用于减少词汇表的大小,同时保留句子的语义信息。
    • 共享的源-目标词汇表:英德数据集使用一个共享的词汇表,大小约为37000个令牌。
    • WMT 2014 英语-法语数据集:更大的数据集,包含3600万句子对,词汇表大小为32000个单词。
  • 批处理策略
    • 按序列长度分组:句子对根据近似的序列长度批量组合,这有助于提高计算效率,因为相似长度的序列可以更均匀地分配计算资源。
    • 每批次令牌数量:每个训练批次的目标是包含约25000个源令牌和25000个目标令牌,这有助于保持批次的计算负荷相对稳定。
  • 使用 torchtext 进行批处理:使用 torchtext 的 batch_size_fn 函数动态确定每个批次的大小,这允许模型根据当前批次的实际数据量调整批次大小。
global max_src_in_batch, max_tgt_in_batch
def batch_size_fn(new, count, sofar):
#new(当前处理的批次),count(当前批次是第几个批次),sofar(到目前为止处理的总元素数量)"Keep augmenting batch and calculate total number of tokens + padding."global max_src_in_batch, max_tgt_in_batch#如果是处理新批次的第一个批次(count == 1),则重置最长源序列和目标序列的长度计数器。if count == 1:max_src_in_batch = 0max_tgt_in_batch = 0max_src_in_batch = max(max_src_in_batch,  len(new.src))#更新 max_src_in_batch 为当前批次中的最长源序列长度和之前记录的最长长度中的较大值。max_tgt_in_batch = max(max_tgt_in_batch,  len(new.trg) + 2)#更新 max_tgt_in_batch 为当前批次中最长的目标序列长度加2(可能为了包括开始和结束标记)和之前记录的最长长度中的较大值。src_elements = count * max_src_in_batch #计算算当前批次中源序列的总元素数,包括填充的元素。tgt_elements = count * max_tgt_in_batchreturn max(src_elements, tgt_elements)#返回源序列和目标序列总元素数中的较大值,这个值决定了批次的实际大小,确保即使在不同长度的序列中也能有效地进行填充。

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

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

相关文章

运行在Linux上的程序越来越慢的排查思路

1、通过free -h 排查内存使用情况,是否内存满了 2、通过df -h 排查磁盘的使用情况,磁盘是否没有空间了 3、检查系统资源配置情况,比如使用ulimit -a检查当前会话的资源限制,如最大文件数、打开文件描述符数等,看是否…

清华计算几何-ConvexHull(凸包)-求极点InTriangle/ToLeft Test

ConvexHull(凸包) 凸包是什么 凸包是计算几何一个非常基础核心的概念。我理解的凸包就是给定一个点集合, 最外围的点的包围体就是凸包。如下所示: 极点(ExtremityPoint) 给定的点集合中, 如果一个点存在一条直线, 让其他所有点都在于该直线的同一侧, 则该点为极点。 非极点 …

JavaScript进阶(7) ----构造函数和原型对象

目录 构造函数 prototype 定义: 使用场景: constructor 使用场景: 原型proto 原型链 定义 特点 instanceof 运算符 原型继承的基本概念 在JavaScript中,构造函数和原型是面向对象编程的核心概念,它们共同构…

海康工业相机驱动

1.新建基于对话框的MFC程序,界面布局如下 2.修改控件ID,为控件绑定变量 3.创建全局变量,构造函数中初始化变量,初始化对话框界面,补齐各控件按钮响应函数 全文程序如下: // MFC_GrabimageDlg.h : 头文件 /…

【动态规划Ⅰ】斐波那契、爬楼梯、杨辉三角

动态规划—斐波那契系列 什么是动态规划斐波那契数组相关题目509. 斐波那契数 Easy1137. 第 N 个泰波那契数 Easy 杨辉三角118. 杨辉三角 Easy 爬楼梯相关题目70. 爬楼梯 Easy746. 使用最小花费爬楼梯 Easy 什么是动态规划 动态规划是一种通过将原问题分解为相对简单的子问题来…

近期几首小诗汇总-生活~卷

生活 为生活飘零,风雨都不阻 路见盲人艰,为她心点灯 贺中科大家长论坛成立十五周年 科学家园有喜贺 园外丑汉翘望中 曾一学子入我科 正育科二盼长大 憧憬也能入此家 与科学家论短长 园外翘首听高论 发现有隙入此坛 竟然也能注册成 入园浏览惶然立 此贴…

JAVA中的回溯算法解空间树,八皇后问题以及骑士游历问题超详解

1.回溯算法的概念 回溯算法顾名思义就是有回溯的算法 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法&#xff…

[线性RNN系列] Mamba: S4史诗级升级

前言 iclr24终于可以在openreview上看预印本了 这篇(可能是颠覆之作)文风一眼c re组出品;效果实在太惊艳了,实验相当完善,忍不住写一篇解读分享分享。 TL;DR (overview) Structured State-Sp…

xshell公钥免密登录

设备:一台linux系统机器,一台windows系统机器 软件:xshell 要求:公钥免密登录 一、生成公钥、私钥 1、打开shell ; 点击工具 ; 新建用户生成密钥向导 2、生成密钥参数 密钥类型:RS…

element ui ts table重置排序

#日常# 今天带的实习生&#xff0c;在遇到开发过程中&#xff0c;遇到了element ui table 每次查询的时候都需要重置排序方式&#xff0c;而且多个排序是由前端排序。 <el-table :data"tableData" ref"restTable"> </<el-table> <script…

bi项目笔记

1.bi是什么 bi项目就是商业智能系统&#xff0c;也就是数据可视画、报表可视化系统&#xff0c;如下图的就是bi项目了 2.技术栈

Linux rsync文件同步工具

scp的不足 1. 性能问题 单线程传输 SCP只使用单线程进行传输&#xff0c;这意味着在传输大文件或大量小文件时&#xff0c;其传输速度和效率可能不如其他多线程工具。 无法压缩数据传输 SCP不支持内置的压缩机制&#xff0c;这在传输大文件时会导致带宽使用效率较低。 2.…

我花了5年时间训练自己这种能力,希望你也能成功

人生最重要的能力是日拱一卒&#xff0c;即每天做一点点对自己有利的事并持续足够长的时间。作者之前急于求成&#xff0c;减肥失败。同事通过每月改进一件小事成功减肥且知识储备丰富。作者受启发后&#xff0c;通过走楼梯、换代糖等小改变&#xff0c;用 4 年减了 40 斤&…

从头开始搭建一套Elasticsearch集群

前言 刚开始使用ES接触的就是rpm或者是云上提供的ES服务&#xff0c;基本上开箱即用。特别是云上的ES服务&#xff0c;开局就是集群版本&#xff0c;提供的是优化后的参数配置、开箱即匹配访问鉴权及常用插件&#xff0c;如无特殊需要基本上屏蔽了所有细节&#xff0c;直接可投…

深入了解 MySQL 的 EXPLAIN 命令

一、什么是 EXPLAIN 命令&#xff1f; EXPLAIN 命令用于显示 MySQL 如何执行某个 SQL 语句&#xff0c;尤其是 SELECT 语句。通过 EXPLAIN 命令&#xff0c;可以看到查询在实际执行前的执行计划&#xff0c;这对于优化查询性能至关重要。 二、EXPLAIN 的基本用法 要使用 EXP…

如何禁用键盘上的特定键或快捷方式?这里有详细步骤

要禁用特定的键盘键或快捷键吗&#xff1f;微软官方应用程序Microsoft PowerToys使这项任务变得非常简单。以下是使用Microsoft PowerToys中的键盘管理器禁用特定键或快捷方式的快速指南。 如果你还没有安装Microsoft PowerToys 如果你的设备上没有安装Microsoft PowerToys&a…

springboot上传图片

前端的name的值必须要和后端的MultipartFile 形参名一致 存储本地

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【匿名密钥证明(C/C++)】

匿名密钥证明(C/C) 在使用本功能时&#xff0c;需确保网络通畅。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 确定密钥别名keyAlias&#xff0c;密钥别名最大长度为64字节&#xff1b;初始化参数集&#xff1a;通过[OH_Huk…

css3 transform的旋转和位移制作太阳花

css3 transform 实例展示知识点rotate 旋转translate 位移transform: translate(300px,200px) rotate(90deg) 实例代码 实例展示 知识点 transform的两个属性 rotate 旋转 translate 位移 transform: translate(300px,200px) rotate(90deg) 实例代码 <!DOCTYPE html&g…

flask 定时任务(APScheduler)使用current_app app_context()上下文

前言: 描述&#xff1a;flask定时任务调用的方法中使用了current_app.logger.info()记录日志报错 报错代码 raise RuntimeError(unbound_message) from None RuntimeError: Working outside of application context.This typically means that you attempted to use functiona…