结构

Transformer的优点

  • 并行计算:相比于RNN,Transformer可以在处理整个序列时并行计算,显著提高了计算效率。
  • 长距离依赖:通过自注意力机制,Transformer能够捕捉序列中远距离的依赖关系,而RNN在处理长序列时会遇到梯度消失或爆炸问题。
  • 扩展性:Transformer非常适合大规模的预训练,例如BERT和GPT系列模型,显示出在多种NLP任务上的优秀表现。

层结构介绍

自注意力机制,多头注意力机制,位置编码,残差链接,层归一化,前馈神经网络。

  1. 自注意力机制(Self-Attention):1、自注意力机制允许模型在处理序列的每个元素时,关注序列中的其他元素,计算它们之间的相关性。每个位置的输出不仅依赖于当前的输入,还能参考其他位置的信息。2、这种机制允许捕捉长距离依赖关系,相比于传统的RNN,它能并行处理整个序列,提高计算效率。

  2. 多头注意力(Multi-Head Attention):ransformer中的“多头注意力”将自注意力机制扩展为多个“头”,每个头独立地学习不同的表示方式。最后,将这些头的输出拼接起来,从而提供更丰富的信息表达。

  3. 位置编码(Positional Encoding):Transformer不使用递归结构,因此无法直接感知序列中元素的顺序。为了弥补这一点,加入了位置编码,它为每个输入位置提供了一个独特的编码,使得模型能够理解序列中元素的相对位置。

  4. 前馈神经网络(Feed-Forward Networks):每个Transformer层中除了注意力机制外,还包含一个前馈神经网络(通常包括两个线性层和一个激活函数)。这些网络对每个位置独立地进行计算。

  5. 层归一化(Layer Normalization):Transformer使用层归一化来帮助稳定训练过程,并加速收敛。层归一化通常在每个子层的输入和输出之间应用。

  6. 残差连接(Residual Connections):在每个子层的输入和输出之间,Transformer结构使用残差连接。这样可以避免深层网络中的梯度消失问题,使得信息可以更容易地流通过网络。

残差链接

残差连接的优势

  1. 缓解梯度消失问题:在深层网络中,梯度会随着反向传播的过程而逐渐消失,而残差连接通过让梯度直接流向较浅的层,缓解了这一问题。
  2. 提高模型的训练效率:通过加速训练过程,模型能够在更深的层次上进行有效学习。
  3. 增强模型的表达能力:残差连接使得每一层不仅学习到新的信息,还可以保留前一层的特征,从而帮助捕捉更复杂的模式。

自注意力机制与多头自注意力机制

原理:

结合数据对qkv进行介绍和说明:

多头自注意力机制:

Transformer模型几乎总是使用多头注意力机制,因为:

  1. 捕捉更多样的关系:多头注意力可以让模型在不同的子空间中计算注意力权重,从而捕捉输入序列中不同的特征或依赖关系(如长距离关系、局部模式等)。单一的自注意力机制可能无法同时关注到多个特性,而多头机制通过多个头并行计算,可以更好地综合这些特性。

  2. 增强模型的表达能力:每个头都有独立的权重矩阵,它们在训练时可以学习到不同的表示,从而提高模型的表示能力。

  3. 提高鲁棒性:通过多个注意力头,模型的决策不会完全依赖于某一个特定的关系,这使得模型对输入数据的变化更加鲁棒。

  4. 分布式计算和效率:多头注意力机制可以并行计算,每个头的注意力权重计算互不影响,因此在硬件上容易实现并行化,计算效率高。

词嵌入编码和位置编码

最终的数据为,词嵌入向量和位置编码向量对应位相加,作为输入数据。相加操作使得词嵌入和位置编码的表示更加紧密融合,便于模型捕捉到更复杂的语义和位置信息。

layerNormal和前向传播

前向传播:线性层+relu+线性层(避免过拟合可以加上dropout)

layerNormal :单个样本所有特征归一化(正态)

编码器和解码器

Transformer由两个主要部分组成:编码器(Encoder)解码器(Decoder)。每个部分都由多个相同的层组成。

编码器(Encoder): 负责将输入序列映射到隐藏表示。每个编码器层包括两个子层:一个是多头自注意力机制,另一个是前馈神经网络。输入经过每个子层时都有残差连接和层归一化。

编码器包括:输入数据(向量),多头注意力机制,前馈神经网络,残差连接与层归一化。

编码后的向量->自注意力机制或者多头注意力机制->(残差连接+层归一化)->前馈神经网络->残差连接+层归一化

解码器(Decoder):负责从编码器的隐藏表示生成输出序列。解码器每个层包含三个子层:一个是多头自注意力机制,另一个是与编码器相关联的多头注意力机制(用于对编码器输出进行关注),最后一个是前馈神经网络。

(自注意力机制+掩码)->(残差连接+层归一化)->(编码器解码器注意力机制)->(残差连接+层归一化)->前馈神经网络->(残差连接+层归一化)

模型训练参数说明:

Transformer 中需要训练的参数主要包括:

  1. 注意力机制: Q,K,V 的权重矩阵和多头注意力拼接后的矩阵进行线性变换的变换矩阵(w)。

  2. 前馈网络: 权重矩阵和偏置。(线性变换矩阵,w,b)

  3. 嵌入层: 输入嵌入矩阵和可选的可训练位置编码。

  4. 层归一化: 缩放和偏置参数。(层归一化公式中如果额外台南佳缩放和平移参数)

  5. 解码器的额外注意力参数: 编码器-解码器注意力的权重。

  6. 输出层: 将模型输出映射到词汇表分布的权重和偏置。

这些参数通过反向传播算法(如 Adam 优化器)进行优化,使模型能够在翻译、文本生成等任务上取得优异的性能。

实现

pytorch 代码示例:

Transformer 架构概述:

  • 编码器(Encoder):负责处理输入序列,通过自注意力机制和前馈网络提取输入数据的上下文信息。
  • 解码器(Decoder):在生成输出时使用编码器的输出,结合自身生成的历史信息,通过自注意力机制和交叉注意力机制(Encoder-Decoder Attention)生成输出序列。

在标准的 Transformer 模型中,解码器层和编码器层的结构相似,唯一不同的是解码器层不仅有自注意力机制,还会有一个跨注意力机制(cross-attention),它允许解码器“关注”编码器的输出。

Dropout:防止过拟合。

代码示例1

import torch
import torch.nn as nn
import math

# 位置编码类
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-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).transpose(0, 1)
        self.register_buffer('pe', pe)

    def forward(self, x):
        x = x + self.pe[:x.size(0), :]
        return x

# Transformer 编码器层
class TransformerLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1):
        super(TransformerLayer, self).__init__()
        self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, src, src_mask=None):
        src2 = self.norm1(src)   #层归一化
        src2 = self.self_attn(src2, src2, src2, attn_mask=src_mask)[0]  #自注意力机制
        src = src + self.dropout1(src2)  #残差+失活
        src2 = self.norm2(src)   #层归一化
        src2 = self.linear2(self.dropout(F.relu(self.linear1(src2))))  
        src = src + self.dropout2(src2)   #残差加失活
        return src

# Transformer 解码器层
class TransformerDecoderLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1):
        super(TransformerDecoderLayer, self).__init__()
        self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)  # Encoder-Decoder attention
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

    def forward(self, tgt, memory, tgt_mask=None, memory_mask=None):  #解码器框架顺序
        # 自注意力
        tgt2 = self.norm1(tgt) #层归一化
        tgt2 = self.self_attn(tgt2, tgt2, tgt2, attn_mask=tgt_mask)[0]  #自注意力机制+掩码
        tgt = tgt + self.dropout1(tgt2)   #残差连接+失活,应该是层归一化加残差连接

        # 编码器-解码器注意力
        tgt2 = self.norm2(tgt)  #层归一化      #多头注意力机制
        tgt2 = self.multihead_attn(tgt2, memory, memory, attn_mask=memory_mask)[0]   
        tgt = tgt + self.dropout2(tgt2)  #残差连接+失活,降低过拟合的风险

        # 前馈网络
        tgt2 = self.norm3(tgt) 
        tgt2 = self.linear2(self.dropout(F.relu(self.linear1(tgt2))))
        tgt = tgt + self.dropout3(tgt2)
        return tgt   #层归一化层,线性层,激活层,失活层,线性层,失活层,残差链接层

# Transformer 模型
class Transformer(nn.Module):
    def __init__(self, ntoken, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward=2048, dropout=0.1):
        super(Transformer, self).__init__()
        self.encoder_layer = TransformerLayer(d_model, nhead, dim_feedforward, dropout)
        self.decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward, dropout)
        self.pos_encoder = PositionalEncoding(d_model)
        self.pos_decoder = PositionalEncoding(d_model)
        self.encoder = nn.ModuleList([self.encoder_layer for _ in range(num_encoder_layers)])
        self.decoder = nn.ModuleList([self.decoder_layer for _ in range(num_decoder_layers)])

    def forward(self, src, tgt, src_mask=None, tgt_mask=None, memory_mask=None):
        src = self.pos_encoder(src)
        for layer in self.encoder:
            src = layer(src, src_mask)

        tgt = self.pos_decoder(tgt)
        for layer in self.decoder:
            tgt = layer(tgt, src, tgt_mask, memory_mask)

        return tgt

# 示例使用
ntoken = 1000  # 词汇表大小
d_model = 512  # 模型维度
nhead = 8  # 多头注意力的头数
num_encoder_layers = 6  # 编码器层数
num_decoder_layers = 6  # 解码器层数

model = Transformer(ntoken, d_model, nhead, num_encoder_layers, num_decoder_layers)
src = torch.randint(0, ntoken, (10, 32))  # (seq_len, batch_size)
tgt = torch.randint(0, ntoken, (20, 32))  # (seq_len, batch_size)
output = model(src, tgt)
print(output.shape)  # 输出形状应为 (seq_len, batch_size, d_model)

使用说明:

  • src:输入序列(来自编码器)。
  • tgt:目标序列(来自解码器,通常是生成的目标)。
  • src_masktgt_mask:可选的掩码,用于避免模型在自回归任务中提前看到未来的内容。
  • memory_mask:跨注意力机制中的掩码,用于指定解码器可以看到哪些来自编码器的部分。

代码示例2

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torchinfo import summary

#创建一个掩码,这个掩码在处理包含填充(padding)的序列数据时非常有用。填充通常用于使批次中的所有序列长度一致,但模型在处理这些序列时应忽略填充部分
def create_padding_mask(seq):
    '''
    seq == 0:这个表达式比较输入序列 seq 中的每个元素是否等于0(通常0被用作填充值)。结果是一个布尔张量,形状与 seq 相同,其中填充位置为 True,非填充位置为 False。
    .float():将布尔张量转换为浮点张量,其中 True 变为1.0,False 变为0.0。
    .unsqueeze(1) 和 .unsqueeze(2):这两个操作在张量的中间维度添加两个新的维度,将张量的形状从 (batch_size, seq_len) 改变为 (batch_size, 1, 1, seq_len)。这样做是为了匹配注意力机制中掩码的形状要求。
    '''
    #  函数返回创建的掩码 seq_mask,其形状为 (batch_size, 1, 1, seq_len)。这个掩码可以用于在计算注意力权重时屏蔽填充位置。
    seq_mask = (seq == 0).float().unsqueeze(1).unsqueeze(2)
    return seq_mask

def create_look_ahead_mask(trg):
    '''
    这个函数创建了一个展望掩码,用于在序列生成任务中(如解码器)屏蔽未来信息。
    掩码的形状为 (batch_size, 1, trg_len, trg_len),其中对角线以上的元素为0,以下的元素为1。
    这意味着在计算注意力权重时,每个元素只能看到它自己和它之前的元素,而不能“看到”它之后的元素,从而避免了未来信息的泄露。
    '''

    batch_size, trg_len = trg.shape  # trg:这是一个张量,代表目标序列,其形状为(batch_size, trg_len),其中batch_size是批次大小,trg_len是目标序列的长度。
    '''
    torch.ones((trg_len, trg_len)) 创建一个形状为 (trg_len, trg_len) 的张量,其中所有元素都是1。
    torch.tril(..., diagonal=0) 函数从这个全1的张量中创建一个下三角矩阵,其中对角线以上的元素被设置为0,diagonal=0 参数指定对角线从0开始(即不包括对角线)。
    参数: ... 将下三角矩阵中的1和0转换为0和1,从而得到一个上三角矩阵,其中对角线及以下的元素为1,对角线以上的元素为0。
    .expand(batch_size, 1, trg_len, trg_len) 将这个上三角矩阵扩展到批次大小,即复制这个矩阵 batch_size 次,以适应批次中的每个序列。
    '''
    trg_mask = 1 - torch.tril(torch.ones((trg_len, trg_len)), diagonal=0).expand(
        batch_size, 1, trg_len, trg_len
    )
    #  函数返回创建的掩码 trg_mask,其形状为 (batch_size, 1, trg_len, trg_len)。这个掩码可以用于在解码器中防止未来信息的影响。
    return trg_mask


def create_masks(src, trg):
    '''
        create_masks 的函数,它用于在序列到序列的模型中创建不同类型的掩码,这些掩码用于确保模型在处理数据时忽略填充(padding)标记,并且在解码器中防止未来信息的泄露
        调用 create_padding_mask 函数为源序列创建填充掩码。这个掩码将用于编码器,以确保编码器在处理源序列时忽略填充标记。
        再次调用 create_padding_mask 函数,但这次为解码器创建填充掩码。注意这里使用的是源序列 src 而不是目标序列 trg,这可能是因为在某些模型设计中,解码器的填充掩码是基于源序列的填充情况来确定的
        调用 create_look_ahead_mask 函数为目标序列创建展望掩码,以防止解码器在未来的标记上获取信息
        使用 torch.max 函数结合目标序列的填充掩码和展望掩码。torch.max 会逐元素地比较两个掩码,并返回一个新掩码,
    其中每个位置是两个输入掩码中的最大值。这意味着如果目标序列的某个位置是填充标记或者未来标记,那么在最终的掩码中,这个位置将被设置为1,否则为0。
        函数返回三个掩码:编码器填充掩码 enc_padding_mask,结合了目标序列填充掩码和展望掩码的 combined_mask,以及解码器填充掩码 dec_padding_mask。
    summary: 创建了三种掩码,分别用于编码器和解码器的不同目的。
    编码器填充掩码 enc_padding_mask 确保编码器忽略源序列中的填充标记。
    结合掩码 combined_mask 同时考虑了目标序列的填充标记和未来信息的屏蔽。
    解码器填充掩码 dec_padding_mask 确保解码器忽略目标序列中的填充标记。
    '''
    # encoder padding mask
    enc_padding_mask = create_padding_mask(src)
    # decoder padding mask
    dec_padding_mask = create_padding_mask(src)
    look_ahead_mask = create_look_ahead_mask(trg)
    dec_trg_padding_mask = create_padding_mask(trg)
    combined_mask = torch.max(dec_trg_padding_mask, look_ahead_mask)
    return enc_padding_mask, combined_mask, dec_padding_mask


def SelfAttention(q, k, v, mask):
    # 注意力权重:计算查询向量 q 和键向量 k 的转置之间的点积,得到原始的注意力得分(attention_logits)。k.transpose(-2, -1) 将 k 的最后两个维度交换,以匹配矩阵乘法的要求。
    attention_logits = torch.matmul(q, k.transpose(-2, -1))
    # 编码值的总量:计算缩放因子 scaling,它是键向量 k 的最后一个维度(特征维度)的平方根。这个缩放因子用于防止点积结果过大,从而缓解梯度消失或爆炸的问题。
    scaling = torch.sqrt(torch.tensor(k.size(-1), dtype=torch.float32))
    # 将原始的注意力得分除以缩放因子,得到缩放后的注意力得分。
    scaled_attention_logits = attention_logits / scaling
    # 带掩码的注意力
    if mask is not None:
        #将掩码与一个非常大的负数相乘,然后加到缩放后的注意力得分上。这会使得掩码对应的位置在应用softmax时接近于0,从而在计算注意力权重时忽略这些位置。
        scaled_attention_logits += (mask * -1e9)
    # 激活函数增加模型非线性情况的处理能力softmax
    # 对缩放后的注意力得分应用softmax函数,得到注意力权重。dim=-1 指定在最后一个维度(通常是序列长度)上应用softmax。
    attention_weights = torch.softmax(scaled_attention_logits, dim=-1)
    # 矩阵乘积:注意力权重(attention_weights)和值向量(v)的加权和
    # 计算注意力权重和值向量 v 的矩阵乘积,得到加权和,即自注意力的输出。
    output = torch.matmul(attention_weights, v)

    return output, attention_weights  #函数返回两个值:加权和 output 和注意力权重 attention_weights。


class MultiHeadAttention(nn.Module):
    def __init__(self, embedding_dim, num_heads):
        '''
        self.embedding_dim 和 self.num_heads 存储了输入的维度和头的数量。
        断言确保 embedding_dim 可以被 num_heads 整除,这是实现多头注意力的前提。
        self.head_dim 计算每个头的维度,即 embedding_dim / num_heads。
        三个 nn.Linear 层分别对查询(Q)、键(K)和值(V)进行线性变换,将它们从原始的 embedding_dim 维度映射到相同的维度。
        self.fc_out 是一个线性层,用于在多头注意力合并后将输出映射回原始的 embedding_dim 维度。
        '''
        super(MultiHeadAttention, self).__init__()
        self.embedding_dim = embedding_dim
        self.num_heads = num_heads
        # 确保embedding_dim可以被num_heads整除,以便平均分配到每个头
        assert self.embedding_dim % self.num_heads == 0
        self.head_dim = self.embedding_dim // self.num_heads
        # 为Q, K, V分别创建线性变换层
        self.queries = nn.Linear(embedding_dim, embedding_dim)
        self.keys = nn.Linear(embedding_dim, embedding_dim)
        self.values = nn.Linear(embedding_dim, embedding_dim)
        # 将多头注意力的结果再次变换回原始embedding_dim
        self.fc_out = nn.Linear(embedding_dim, embedding_dim)


    def split_heads(self, x, batch_size):
        '''
        头分割方法将输入张量 x 重塑为 (batch_size, seq_len, num_heads, head_dim) 的形状。
        x.permute(0, 2, 1, 3) 重新排序维度,使得头的维度位于中间,方便后续计算
        '''
        x = x.reshape(batch_size, -1, self.num_heads, self.head_dim)
        return x.permute(0, 2, 1, 3)  # (batch_size , num_heads , seqlen , head_dim)

    def forward(self, q, k, v, mask=None):
        '''
        batch_size 获取输入张量 q 的批次大小。
        q、k 和 v 通过各自的线性层进行变换,以生成查询、键和值的表示。
        self.split_heads 方法将变换后的查询、键和值分割成多个头。
        SelfAttention 函数计算每个头的缩放注意力和注意力权重。这里假设 SelfAttention 函数已经定义,并且接受查询、键、值和掩码作为输入。
        scaled_attention.permute(0, 2, 1, 3) 重新置换维度,以合并头的信息。
        attention_output = scaled_attention.reshape(batch_size, -1, self.embedding_dim) 将多头的输出合并回原始的 embedding_dim 维度。
        out = self.fc_out(attention_output) 通过最后的线性层,将合并后的多头输出映射回原始的 embedding_dim 维度。
        返回最终的输出 out 和注意力权重 attention_weights。
        '''
        batch_size = q.shape[0]
        # (q , k , v) shape: (batch_size , seqlen , embedding_dim)
        # 线性变换Q, K, V
        q = self.queries(q)
        k = self.keys(k)
        v = self.values(v)
        # 分割头
        q = self.split_heads(q, batch_size)  # (batch_size , num_heads , seqlen_q , head_dim)
        k = self.split_heads(k, batch_size)  # (batch_size , num_heads , seqlen_k , head_dim)
        v = self.split_heads(v, batch_size)  # (batch_size , num_heads , seqlen_v , head_dim)

        # scaled_attention shape : (batch_size , num_heads , seqlen_q , head_dim)
        # attention_weights shape : (batch_size , num_heads , seqlen_q , seqlen_k)
        # 计算多头自注意力
        scaled_attention, attention_weights = SelfAttention(q, k, v, mask)
        # 重新组合头
        scaled_attention = scaled_attention.permute(0, 2, 1, 3)  # (batch_size  , seqlen_q , num_heads , head_dim)
        # Concatenation of heads
        attention_output = scaled_attention.reshape(batch_size, -1,
                                                    self.embedding_dim)  # (batch_size, seq_len_q, embedding_dim)
        # 线性连接输出
        out = self.fc_out(attention_output)  # (batch_size , seqlen_q , embedding_dim)
        #输出多头注意力之后的结果和权重
        return out, attention_weights


#FC全连接,激活,全连接
def FC(embedding_dim, fc_dim):
    model = nn.Sequential(
        #语法in_features:输入数据的特征数量。out_features:输出数据的特征数量。bias:布尔值是否添加偏置项(默认为True)
        #权重和偏差模型自己训练
        nn.Linear(embedding_dim, fc_dim),
        nn.ReLU(),
        nn.Linear(fc_dim, embedding_dim)
    )
    return model


class EncoderBlock(nn.Module):
    '''
    1、多头注意力(+失活)
    2、残差连接后数据进行层归一化
    3、线性全连接+激活+线性全连接+失活 (前向传播)
    4、和上一层的结果进行残差连接(2+3)后进行层归一化;完成一个编码块的输出
    5、多头注意力,残差链接后层归一化,前向传播,残差连接后层归一化,输出结果
    '''
    def __init__(self, embedding_dim, num_heads, fc_dim, dropout_rate=0.1):
        super(EncoderBlock, self).__init__()

        self.MHA = MultiHeadAttention(embedding_dim, num_heads)

        self.fc = FC(embedding_dim, fc_dim)
        # self.fc = nn.FC(embedding_dim, fc_dim)

        self.norm1 = nn.LayerNorm(embedding_dim, eps=1e-6)
        self.norm2 = nn.LayerNorm(embedding_dim, eps=1e-6)

        self.dropout1 = nn.Dropout(dropout_rate)
        self.dropout2 = nn.Dropout(dropout_rate)

    def forward(self, x, mask):

        attn_out, _ = self.MHA(x, x, x, mask)  # (batch_size , seqlen , embedding_dim)
        attn_out = self.dropout1(attn_out)

        out1 = self.norm1(x + attn_out)  # (batch_size , seqlen , embedding_dim)

        fc_out = self.dropout2(self.fc(out1))  # (batch_size , seqlen , embedding_dim)

        enc_out = self.norm2(out1 + fc_out)  # (batch_size , seqlen , embedding_dim)

        return enc_out


class Encoder(nn.Module):

    def __init__(self,num_layers,embedding_dim,num_heads,fc_dim,src_vocab_size,max_length,dropout_rate=0.1):
        '''
        self.num_layers 存储编码器层的数量。
        self.embedding_dim 存储嵌入的维度。
        self.embedding 是一个嵌入层,将输入的词汇索引映射到嵌入空间。
        self.pos_encoding 是一个位置编码层,为每个位置提供唯一的编码,以保持序列中单词的顺序信息。
        self.enc_layers 是一个包含多个 EncoderBlock 的列表,每个 EncoderBlock 包含一个多头自注意力层和一个前馈网络层。
        self.dropout 是一个dropout层,用于正则化和防止过拟合。
        '''
        super(Encoder, self).__init__()

        self.num_layers = num_layers
        self.embedding_dim = embedding_dim

        # 词嵌入层
        self.embedding = nn.Embedding(src_vocab_size, embedding_dim)
        # 位置编码层
        self.pos_encoding = nn.Embedding(max_length, embedding_dim)
        # 编码器层的列表
        self.enc_layers = [EncoderBlock(embedding_dim, num_heads, fc_dim, dropout_rate)
                           for _ in range(num_layers)]
        # Dropout层
        self.dropout = nn.Dropout(dropout_rate)

    def forward(self, x, mask):
        '''
        batch_size, seqlen = x.shape 获取输入张量 x 的批次大小和序列长度。
        positions = torch.arange(0, seqlen).expand(batch_size, seqlen) 生成位置索引,用于位置编码。
        out = self.dropout((self.embedding(x) + self.pos_encoding(positions))) 将输入的词汇索引通过嵌入层转换为嵌入向量,并加上对应的位置编码,然后应用dropout。
        for i in range(self.num_layers): 循环遍历每一层编码器层,将输出传递给下一层。
        out = self.enc_layers[i](out, mask) 每一层 EncoderBlock 接受当前的输出和掩码,进行处理。
        '''
        batch_size, seqlen = x.shape
        # 生成位置索引
        positions = torch.arange(0, seqlen).expand(batch_size, seqlen)
        # 嵌入输入并加上位置编码
        out = self.dropout((self.embedding(x) + self.pos_encoding(positions)))  # (batch_size , seqlen , embedding_dim)
        # 逐层处理
        for i in range(self.num_layers):
            out = self.enc_layers[i](out, mask)
        return out  # (batch_size , seqlen , embedding_dim)


class DecoderBlock(nn.Module):
    '''
        1、源数据,多头注意力机制后失活后残差链接后层归一化
        2、步骤1后的结果加上编码的数据,进行多头注意力机制后失活后残差连接后层归一化
        3、前向传播(线性连接失活线性连接)后失活
        4、残差链接后层归一化
        解码器块结构
    '''
    def __init__(self, embedding_dim, num_heads, fc_dim, dropout_rate=0.1):
        super(DecoderBlock, self).__init__()

        self.MHA1 = MultiHeadAttention(embedding_dim, num_heads)
        self.MHA2 = MultiHeadAttention(embedding_dim, num_heads)

        self.fc = FC(embedding_dim, fc_dim)

        self.norm1 = nn.LayerNorm(embedding_dim, eps=1e-6)
        self.norm2 = nn.LayerNorm(embedding_dim, eps=1e-6)
        self.norm3 = nn.LayerNorm(embedding_dim, eps=1e-6)

        self.dropout1 = nn.Dropout(dropout_rate)
        self.dropout2 = nn.Dropout(dropout_rate)
        self.dropout3 = nn.Dropout(dropout_rate)

    def forward(self, x, enc_output, look_ahead_mask, padding_mask):
        # enc_output shape : (batch_size , seqlen , embedding_dim)


        attn1, attnW1 = self.MHA1(x, x, x, look_ahead_mask)
        attn1 = self.dropout1(attn1)
        out1 = self.norm1(attn1 + x)  # (batch_size , seqlen , embedding_dim)
        attn2, attnW2 = self.MHA2(out1, enc_output, enc_output, padding_mask)
        attn2 = self.dropout2(attn2)
        out2 = self.norm2(attn2 + out1)  # (batch_size , seqlen , embedding_dim)
        fc_out = self.dropout3(self.fc(out2))
        dec_out = self.norm3(fc_out + out2)  # (batch_size , seqlen , embedding_dim)

        return dec_out, attnW1, attnW2


class Decoder(nn.Module):
    '''
    解码器的主要作用是将编码器的输出和目标序列的输入转换为输出序列。这个过程涉及以下几个关键步骤:
    词嵌入:目标序列中的每个词汇索引通过嵌入层转换为嵌入空间中的向量。
    位置编码:为了保持序列中词汇的顺序信息,每个词汇的嵌入向量加上一个唯一的位置编码。
    多头自注意力:在每个解码器层中,多头自注意力机制允许模型在处理序列时,对序列的不同部分赋予不同的权重,这些权重反映了不同部分之间的相关性。
    编码器-解码器注意力:解码器层还包含一个编码器-解码器注意力机制,它允许解码器关注编码器输出中最相关的部分。
    前馈网络:每个解码器层还包含一个前馈网络,它对自注意力层的输出进行进一步的非线性变换。
    层叠结构:多个解码器层堆叠在一起,使得模型能够捕捉不同层次的序列特征。
    '''
    def __init__(self, num_layers, embedding_dim, num_heads, fc_dim, trg_vocab_size, max_length, dropout_rate=0.1):
        '''
        self.num_layers 存储解码器层的数量。(num_layers)
        self.embedding_dim 存储嵌入的维度。(embedding_dim)
        self.embedding 是一个嵌入层,将目标词汇的索引映射到嵌入空间。
        self.pos_encoding 是一个位置编码层,为每个位置提供唯一的编码,以保持序列中单词的顺序信息。
        self.dec_layers 是一个包含多个 DecoderBlock 的列表,每个 DecoderBlock 包含一个多头自注意力层和一个编码器-解码器注意力层,以及一个前馈网络层。
        self.dropout 是一个dropout层,用于正则化和防止过拟合
        '''
        super(Decoder, self).__init__()
        self.num_layers = num_layers
        self.embedding_dim = embedding_dim
        self.embedding = nn.Embedding(trg_vocab_size, embedding_dim)
        self.pos_encoding = nn.Embedding(max_length, embedding_dim)
        self.dec_layers = [DecoderBlock(embedding_dim, num_heads, fc_dim, dropout_rate)
                           for _ in range(num_layers)]
        self.dropout = nn.Dropout(dropout_rate)

    def forward(self, x, enc_output, look_ahead_mask, padding_mask):
        '''
        batch_size, seqlen = x.shape 获取输入张量 x 的批次大小和序列长度。
        positions = torch.arange(0, seqlen).expand(batch_size, seqlen) 生成位置索引,用于位置编码。
        out = self.dropout((self.embedding(x) + self.pos_encoding(positions))) 将输入的目标词汇索引通过嵌入层转换为嵌入向量,并加上对应的位置编码,然后应用dropout。
        for i in range(self.num_layers): 循环遍历每一层解码器层,将输出传递给下一层。
        out, attnW1, attnW2 = self.dec_layers[i](out, enc_output, look_ahead_mask, padding_mask) 每一层 DecoderBlock 接受当前的输出、编码器的输出、展望掩码和填充掩码,进行处理,并返回新的输出和两个注意力权重。
        attention_weights 字典用于存储每一层的注意力权重,以便于后续分析和可视化
        output: 函数返回最终的输出 out 和一个包含所有注意力权重的字典 attention_weights
        '''
        batch_size, seqlen = x.shape
        attention_weights = {}
        positions = torch.arange(0, seqlen).expand(batch_size, seqlen)
        out = self.dropout((self.embedding(x) + self.pos_encoding(positions)))  # (batch_size , seqlen , embedding_dim)

        for i in range(self.num_layers):
            out, attnW1, attnW2 = self.dec_layers[i](out, enc_output, look_ahead_mask, padding_mask)
        attention_weights['decoder_layer{}_block1'.format(i + 1)] = attnW1
        attention_weights['decoder_layer{}_block2'.format(i + 1)] = attnW2

        return out, attention_weights


class Transformer(nn.Module):

    def __init__(self, num_layers, embedding_dim, num_heads, fc_dim, src_vocab_size, trg_vocab_size, src_max_length,
                 trg_max_length, dropout_rate=0.1):
        '''
        self.encoder 是一个 Encoder 实例,负责处理源序列。
        self.decoder 是一个 Decoder 实例,负责处理目标序列并生成输出。
        self.fc_out 是一个线性层,将解码器的输出映射到目标词汇表的大小,用于最终的词汇预测。
        '''
        super(Transformer, self).__init__()
        self.encoder = Encoder(num_layers, embedding_dim, num_heads, fc_dim, src_vocab_size, src_max_length,
                               dropout_rate)

        self.decoder = Decoder(num_layers, embedding_dim, num_heads, fc_dim, trg_vocab_size, trg_max_length,
                               dropout_rate)
        self.fc_out = nn.Linear(embedding_dim, trg_vocab_size)

    def forward(self, src, trg, enc_padding_mask, look_ahead_mask, dec_padding_mask
                ):
        '''
        src 和 trg 分别是源序列和目标序列的输入数据。
        enc_padding_mask 是编码器的填充掩码,用于告诉模型哪些部分是填充的,应该被忽略。
        look_ahead_mask 是解码器的展望掩码,用于防止解码器在未来的标记上获取信息。
        dec_padding_mask 是解码器的填充掩码,用于告诉解码器哪些部分是填充的,应该被忽略

        enc_output = self.encoder(src, enc_padding_mask):编码器处理源序列 src 并生成编码器的输出 enc_output。
        dec_output, attention_weights = self.decoder(trg, enc_output, look_ahead_mask, dec_padding_mask):
        解码器使用编码器的输出 enc_output、目标序列 trg、展望掩码 look_ahead_mask 和填充掩码 dec_padding_mask 来生成解码器的输出 dec_output 和注意力权重 attention_weights。
        out = self.fc_out(dec_output):解码器的输出 dec_output 通过线性层 self.fc_out 进行变换,生成最终的预测输出 out。
        return out, attention_weights:函数返回最终的预测输出 out 和注意力权重 attention_weights。

        '''
        enc_output = self.encoder(src, enc_padding_mask)
        dec_output, attention_weights = self.decoder(trg, enc_output, look_ahead_mask, dec_padding_mask)
        out = self.fc_out(dec_output)
        return out, attention_weights


if __name__ == '__main__':
    # set hyperparameters
    EMBEDDING_DIM = 256
    FC_DIM = 512
    NUM_LAYERS = 4
    NUM_HEADS = 8
    DROPOUT_RATE = 0.1
    SRC_VOCAB_SIZE = 64896
    TRG_VOCAB_SIZE = 52351
    SRC_MAXLEN = 100
    TRG_MAXLEN = 100

    model = Transformer(
        NUM_LAYERS,
        EMBEDDING_DIM,
        NUM_HEADS,
        FC_DIM,
        SRC_VOCAB_SIZE,
        TRG_VOCAB_SIZE,
        SRC_MAXLEN,
        TRG_MAXLEN,
        DROPOUT_RATE
    )

    temp_src = torch.randint(low=0, high=200, size=(64, 38), dtype=torch.int64)
    temp_trg = torch.randint(low=0, high=200, size=(64, 36), dtype=torch.int64)

    enc_padding_mask, look_ahead_mask, dec_padding_mask = create_masks(temp_src, temp_trg)

    temp_trg_out, temp_attention_weights = model(temp_src, temp_trg,
                                                 enc_padding_mask,
                                                 look_ahead_mask,
                                                 dec_padding_mask)
    temp_trg_out.shape, temp_attention_weights['decoder_layer4_block2'].shape

    summary(model, input_data=[temp_src, temp_trg, enc_padding_mask, look_ahead_mask, dec_padding_mask])


#---------------datat --utils
import torch
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pad_sequence

# ------------------ 1. Tokenizer -----------------分词器-
class Tokenizer:
    def __init__(self, special_tokens=None):
        self.special_tokens = special_tokens or {"<pad>", "<unk>", "<sos>", "<eos>"}

    def tokenize(self, text):
        """
        基于空格的简单分词
        """
        return text.strip().split()

    def detokenize(self, tokens):
        """
        将分词列表还原为句子
        """
        return " ".join(tokens)

# ------------------ 2. Vocabulary --------------词汇表----
class Vocab:
    def __init__(self, tokens, special_tokens=None):
        self.special_tokens = special_tokens or {"<pad>", "<unk>", "<sos>", "<eos>"}
        self.token_to_id = {token: idx for idx, token in enumerate(self.special_tokens)}
        self.id_to_token = {idx: token for token, idx in self.token_to_id.items()}
        self.build_vocab(tokens)

    def build_vocab(self, tokens):
        """
        构建词汇表
        """
        for token in tokens:
            if token not in self.token_to_id:
                idx = len(self.token_to_id)
                self.token_to_id[token] = idx
                self.id_to_token[idx] = token

    def __len__(self):
        return len(self.token_to_id)

    def get(self, token, default="<unk>"):
        return self.token_to_id.get(token, self.token_to_id[default])

    def reverse(self, idx):
        return self.id_to_token.get(idx, "<unk>")

# ------------------ 3. Dataset ------------------
class TranslationDataset(Dataset):
    def __init__(self, src_sentences, trg_sentences, src_vocab, trg_vocab):
        """
        初始化数据集
        :param src_sentences: 源语言句子列表
        :param trg_sentences: 目标语言句子列表
        :param src_vocab: 源语言词汇表
        :param trg_vocab: 目标语言词汇表
        """
        self.src_sentences = src_sentences
        self.trg_sentences = trg_sentences
        self.src_vocab = src_vocab
        self.trg_vocab = trg_vocab

    def __len__(self):
        return len(self.src_sentences)

    def __getitem__(self, idx):
        src_sentence = self.src_sentences[idx]
        trg_sentence = self.trg_sentences[idx]
        src_tokens = [self.src_vocab.get(token) for token in src_sentence.split()]
        trg_tokens = [self.trg_vocab.get(token) for token in trg_sentence.split()]
        return torch.tensor(src_tokens), torch.tensor(trg_tokens)

# ------------------ 4. Padding and Mask ------------------
def collate_fn(batch):
    """
    批处理函数,用于对序列进行填充
    """
    src_batch, trg_batch = zip(*batch)
    src_batch = pad_sequence(src_batch, batch_first=True, padding_value=0)  # 假设 0 是 <pad>
    trg_batch = pad_sequence(trg_batch, batch_first=True, padding_value=0)
    src_len = torch.tensor([len(seq) for seq in src_batch])
    trg_len = torch.tensor([len(seq) for seq in trg_batch])
    return (src_batch, src_len), (trg_batch, trg_len)

def create_padding_mask(seq, pad_token=0):
    """
    创建填充掩码
    """
    return (seq == pad_token).unsqueeze(1).unsqueeze(2)  # [batch_size, 1, 1, seq_len]

# ------------------ 5. Main Pipeline ------------------
def load_translation_data(src_sentences, trg_sentences, src_vocab, trg_vocab, batch_size=2):
    """
    加载翻译数据,生成 DataLoader
    """
    dataset = TranslationDataset(src_sentences, trg_sentences, src_vocab, trg_vocab)
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
    return dataloader

# ------------------ 6. Example ------------------
if __name__ == "__main__":
    # 示例数据
    src_sentences = ["hello world", "machine learning is fun"]
    trg_sentences = ["你好 世界", "机器 学习 很有趣"]

    # 初始化分词器和词汇表
    src_vocab = Vocab({"hello", "world", "machine", "learning", "is", "fun"})
    trg_vocab = Vocab({"你好", "世界", "机器", "学习", "很有趣"})

    # 加载数据
    dataloader = load_translation_data(src_sentences, trg_sentences, src_vocab, trg_vocab, batch_size=2)

    # 测试
    for batch in dataloader:
        (src, src_len), (trg, trg_len) = batch
        print("Source:", src)
        print("Source Lengths:", src_len)
        print("Target:", trg)
        print("Target Lengths:", trg_len)

论文

《Attention Is All You Need》**提出了一种新的神经网络架构——Transformer,该模型完全基于注意力机制,摒弃了传统的循环和卷积层。

研究背景

  1. 研究问题: 这篇文章要解决的问题是如何通过一种新的网络架构来提高序列转换任务的性能,特别是机器翻译任务。现有的序列转换模型通常基于复杂的循环神经网络(RNN)或卷积神经网络(CNN),这些模型虽然有效,但在训练过程中存在并行化困难且计算效率低下的问题。
  2. 研究难点: 该问题的研究难点包括:如何在保持高精度的同时提高模型的并行化能力,减少训练时间;如何在不使用循环层的情况下有效地捕捉长距离依赖关系。
  3. 相关工作: 相关工作包括基于RNN和CNN的序列转换模型,如RNN、LSTM、GRU、ConvS2S等。这些模型在处理序列数据时表现出色,但存在计算复杂度高、并行化困难等问题。此外,注意力机制已经在一些任务中显示出其有效性,但通常是与RNN结合使用。

研究方法

这篇论文提出了Transformer模型。具体来说:

  • 自注意力机制: Transformer模型完全依赖于自注意力机制来捕捉输入和输出之间的全局依赖关系。自注意力机制允许模型在计算每个位置的表示时考虑整个输入序列的信息,从而有效地处理长距离依赖关系。
  • 多头注意力: 为了进一步提高模型的表达能力,Transformer使用了多头注意力机制。多头注意力机制通过线性投影将查询、键和值映射到不同的子空间,并在这些子空间上并行地执行注意力计算,然后将结果拼接并再次投影,得到最终的输出。
  • 位置编码: 由于Transformer模型没有循环和卷积层,无法利用序列的顺序信息。因此,论文引入了位置编码,通过在输入嵌入中添加正弦和余弦函数来注入位置信息。
  • 前馈神经网络: 每个Transformer层还包含一个位置无关的前馈神经网络,该网络对每个位置的输入独立地进行非线性变换。

实验设计

  • 数据集: 论文使用了标准的WMT 2014英语-德语和英语-法语数据集进行实验。英语-德语数据集包含约450万句对,英语-法语数据集包含约3600万句对。
  • 硬件配置: 实验在一个包含8个NVIDIA P100 GPU的机器上进行。基础模型的训练时间为12小时,而大型模型的训练时间为3.5天。
  • 超参数配置: 使用Adam优化器,初始学习率根据公式动态调整,warmup步数为4000步。此外,还采用了残差连接和层归一化技术。

问题1:Transformer模型在处理长序列时如何避免计算效率低和难以并行化的问题?

Transformer模型通过完全依赖自注意力机制来避免传统递归神经网络(RNN)和卷积神经网络(CNN)在处理长序列时的计算效率低和难以并行化的问题。自注意力机制允许模型在输入序列的不同位置之间建立全局依赖关系,而不需要按顺序逐个处理序列元素。这使得Transformer可以在多个GPU上并行计算,显著提高了训练速度和效率。此外,Transformer模型还采用了多头自注意力机制,进一步增强了模型对不同表示子空间信息的捕捉能力,从而在保持计算效率的同时提高了模型性能。

问题2:Transformer模型中的位置编码是如何设计的?其作用是什么?

Transformer模型中的位置编码用于在输入嵌入的底部添加位置信息,以利用序列的顺序信息。由于模型中没有递归和卷积,位置编码的设计变得尤为重要。位置编码使用正弦和余弦函数,其波长形成一个几何级数。具体来说,位置编码的计算公式为:

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)PE(pos,2i)​=sin(pos/100002i/dmodel​)PE(pos,2i+1)​=cos(pos/100002i/dmodel​)

其中,pos是位置,i是维度。这种设计使得模型可以轻松学习到基于相对位置的关注机制,因为对于任何固定的偏移k,PEpos+k可以表示为PEpos的线性函数。位置编码的使用确保了模型在处理长序列时能够捕捉到序列元素的顺序信息。

问题3:在英语句法分析任务中,Transformer模型的表现如何?与其他模型相比有何优势?

在英语句法分析任务中,Transformer模型表现出色。具体来说,在一个包含40K训练句子的WSJ数据集上,Transformer模型取得了91.3的F1分数。在半监督设置下,使用更大的高置信度和BerkeleyParser语料库,模型取得了92.7的F1分数。与其他模型相比,Transformer模型在仅有40K训练句子的WSJ数据集上的表现就优于所有先前报告的模型,仅次于递归神经网络文法。此外,Transformer模型在半监督设置下的表现也优于许多先进的模型,显示出其强大的泛化能力。

深入分析模型架构

1、框架

        大多数有竞争力的神经序列转换模型具有编码器-解码器结构[5, 2, 35]。在这里,编码器将符号表示的输入序列(x1,...,xn)映射到一个连续表示序列z=(z1,,zn)。给定z,解码器随后生成一个符号输出序列(y1,...,ym),一个元素接一个元素。在每一步中,模型都是自回归的[10],在生成下一个时消费之前生成的符号作为额外的输入。

        Transformer遵循这一整体架构,使用堆叠的自注意力以及点式的、全连接的层作为编码器和解码器,分别显示在图1的左右两部分。

 2、编码器和解码器堆栈

编码器:编码器由N=6个相同的层组成。每层有两个子层。第一个是多头自注意力机制,第二个是一个简单的、逐位置的全连接前馈网络。我们在每个子层周围采用残差连接[11],然后是层归一化[1]。也就是说,每个子层的输出是LayerNorm(x+Sublayer(x)),其中Sublayer(x)是由子层本身实现的函数。为了促进这些残差连接,模型中的所有子层,以及嵌入层,都产生维度为dmodel=512的输出。

解码器:解码器也由N=6个相同的层组成。除了每个编码器层中的两个子层外,解码器还插入了一个第三个子层,它对编码器堆栈的输出进行多头注意力。与编码器类似,我们在每个子层周围采用残差连接,然后是层归一化。我们还修改了解码器堆栈中的自注意力子层,以防止位置关注后续位置。缩放点积注意力。

(左)缩放的点积注意力。(右)多头注意力由多个并行运行的注意力层组成。对于这些值,分配给每个值的权重是通过查询与相应键的兼容性函数计算的。

   

2.1 缩放的点积注意力

我们称我们的特定注意力为“缩放的点积注意力”(图2)。输入包括维度为dkdk​的查询和键,以及维度为dvdv​的值。我们计算查询与所有键的点积,除以dkdk​​,并应用softmax函数来获得值上的权重。

在实践中,我们在一组查询上同时计算注意力函数,打包成一个矩阵Q。键和值也打包成一个矩阵K和V。我们计算输出矩阵如下:

两种最常用的注意力函数是加性注意力[2]和点积(乘法)注意力。点积注意力与我们算法相同,除了缩放因子为1dkdk​​1​。加性注意力使用一个带有单个隐藏层的前馈网络来计算兼容性函数。虽然这两种方法在理论复杂性上相似,但实际上点积注意力更快且更节省空间,因为它可以使用高度优化的矩阵乘法代码实现。

对于小的dkdk​值,这两种机制表现相似,但加性注意力在dkdk​较大时性能优于点积注意力,且无需缩放[3]。我们怀疑对于较大的dkdk​值,点积的幅度会变大,将softmax函数推向梯度极小的区域44。为了抵消这种效应,我们将点积按1dkdk​​1​进行缩放。

2.2 多头注意力

我们发现,与其使用dmodeldmodel​维的键、值和查询执行单一注意力函数,不如将查询、键和值线性投影到dk,dkdk​,dk​和dvdv​维度,分别用不同的学习线性投影。在这些投影版本的查询、键和值上,我们并行执行注意力函数,产生dd维的输出值。这些值被连接起来,再次投影,得到最终值,如图2所示。

2.3 注意力在我们的模型中的应用

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

  • 在“编码器-解码器注意力”层中,查询来自前一个解码器层,记忆键和值来自编码器的输出。这允许解码器中的每个位置都能关注输入序列中的所有位置。这模仿了典型的编码器-解码器注意力机制,例如在序列到序列模型中[38,2,9]。

  • 编码器包含自注意力层。在一个自注意力层中,所有的键、值和查询都来自同一个地方,在这种情况下,是编码器前一层输出。编码器中的每个位置都可以关注编码器前一层中的所有位置。

  • 类似地,解码器中的自注意力层允许解码器中的每个位置都能关注解码器中的所有位置,直到并包括该位置。我们需要防止解码器中的左向信息流,以保持自回归属性。我们通过在softmax输入中设置遮罩(设置为−∞−∞),从而在缩放的点积注意力内部实现这一点,这些遮罩对应于非法连接。见图2。

3 逐位置前馈网络

除了注意力子层外,我们的编码器和解码器中的每一层都包含一个全连接前馈网络,该网络分别且恒定地应用于每个位置。这包括两个线性变换以及它们之间的ReLU激活函数。

FFN(x)=max(0,xW1​+b1​)W2​+b2​

虽然线性变换在不同位置上是相同的,但它们从一层到另一层使用不同的参数。另一种描述方法是作为两个核大小为1的卷积。输入和输出的数据维度是dmodel=512dmodel​=512,内层的维度是dff=2048dff​=2048。

4 嵌入和Softmax

与其它序列转换模型类似,我们使用学习到的嵌入将输入标记和输出标记转换为维度为dmodeldmodel​的向量。我们还使用通常学习的线性变换和softmax函数将解码器输出转换为预测的下一个标记概率。在我们的模型中,我们在两个嵌入层和预softmax线性变换之间共享相同的权重矩阵,类似于[30]。在嵌入层中,我们将这些权重乘以根号下d(model)

表1:不同层类型最大路径长度、每层复杂性和最小顺序操作次数。n是序列长度,d是表示维度,k是卷积的核大小,r是受限自注意力中的邻域大小。

Layer Type Complexity per Layer Sequential Operations Maximum Path Length
Self-Attention O(n2·d) O(1) O(1)
Recurrent O(n·d2) O(n) O(n)
Convolutional O(k·n·d2) O(1) O(logk(n))
Self-Attention(restricted) O(r⋅n⋅d)O(r⋅n⋅d) O(1)O(1) O(n/r)

自注意力

在本节中,我们将自注意力层的各个方面与常用的循环层和卷积层进行比较,后者用于将一个变量长度的符号表示序列(x1,...,xn)(x1​,...,xn​)映射到另一个等长度的序列(z1,...,zn)(z1​,...,zn​),其中xi,zi∈Rdxi​,zi​∈Rd,例如在典型的序列转换编码器或解码器中的隐藏层。激励我们对自注意力使用,我们考虑了三个期望。

一个是每层的总计算复杂度。另一个是可以并行化的计算量,通过所需的最少顺序操作数来衡量。

第三个是网络中长距离依赖关系之间的路径长度。学习长距离依赖关系是许多序列转换任务中的一个关键挑战。影响学习这种依赖性能力的一个关键因素是前向和后向信号在网络中必须遍历的路径长度。输入序列和输出序列中任意位置组合之间的路径越短,学习长距离依赖性就越容易[12]。因此,我们还将比较由不同层类型组成的网络中任意两个输入和输出位置之间的最大路径长度。

如表1所示,自注意力层连接所有位置,并且执行顺序操作的数量是恒定的,而循环层需要O(n)个顺序操作。就计算复杂性而言,当序列长度n小于表示维度d时,自注意力层比循环层更快,这在机器翻译中最先进的模型使用的句子表示(如word-piece[38]和byte-pair[31]表示)中最为常见。为了提高涉及非常长序列的任务的计算性能,可以将自注意力限制为只考虑以各自输出位置为中心的输入序列中大小为r的邻域。这将使最大路径长度增加到O(n/r)O(n/r)。我们计划在未来的工作中进一步研究这种方法。

具有核宽度k<nk<n的单个卷积层并不连接所有输入和输出位置对。在连续核的情况下,这需要一堆O(n/k)卷积层,或者在扩张卷积的情况下是O(log_k(n))[18],增加了网络中任意两个位置之间最长路径的长度。卷积层通常比循环层贵一个k倍。然而,可分离卷积[6]显著降低了复杂性,降至O(k·n·d+n·d^2)[6]。即使k=n,可分离卷积的复杂度也等于自注意力层和逐点前馈层的组合,这是我们模型中采用的方法。

作为附带好处,自注意力可以产生更具解释性的模型。我们在附录中检查了来自模型的注意力分布,并展示了示例。不仅单个注意力头明显学会了执行不同的任务,许多似乎表现出与句子的语法和语义结构相关的行为。

训练

本节描述了我们模型的训练制度。

6.1 训练数据和批量

我们在标准WMT 2014英文-德语数据集上进行了训练,该数据集包含大约450万个句子对。句子使用字节对编码[3]进行编码,它有一个共享的源-目标词汇表,大约有37000个标记。对于英语-法语,我们使用了更大规模的WMT 2014英文-法语数据集,包含3600万句子,并将标记分割成32000词块词汇表[38]。句子对通过大致的序列长度被批量在一起。每个训练批次包含一组句子对,大约包含25000个源标记和25000个目标标记。

6.2 硬件和计划

我们在一台配备8个NVIDIA P100 GPU的机器上训练了我们的模型。对于使用本文中描述超参数的基础模型,每个训练步骤大约需要0.4秒。我们总共训练了100,000步或12小时的基础模型。对于我们的大型模型(如表3底部所描述),步时间为1.0秒。大型模型训练了300,000步(3.5天)。

6.3 梯度器

我们使用了Adam优化器[20],其中β1=0.9,β2=0.98β1​=0.9,β2​=0.98,以及ϵ=10−9ϵ=10−9。我们根据公式在训练过程中改变学习率。

lrate=dmodel−0.5⋅min⁡(step_num−0.5,step_num⋅warmup_steps−1.5)(3)lrate=dmodel−0.5​⋅min(step_num−0.5,step_num⋅warmup_steps−1.5)(3)

这对应于在第一个热身步骤训练步骤中线性增加学习率,在之后按步数平方根的比例减少。我们使用了warmupsteps=4000.warmups​teps=4000.

6.4 正则化

我们在训练期间采用三种类型的正则化:

表2:Transformer在英语到德语和英语到法语的新颖测试2014上,比之前的最先进模型实现了更好的BLEU分数,且训练成本只是前者的几分之一。

Model BLEU BLEU Training Cost(FLOPs) Training Cost(FLOPs)
Model EN-DE EN-FR EN-DE EN-FR
ByteNet[18] 23.75
Deep-Att+ PosUnk[39] 39.2 1.0.1020
GNMT+RL[38] 24.6 39.92 2.3⋅10192.3⋅1019 1.4.1020
ConvS2S[9] 25.16 40.46 9.6⋅10189.6⋅1018 1.5.1020
MoE[32] 26.03 40.56 2.0⋅10192.0⋅1019 1.2.1020
Deep-Att+ PosUnk Ensemble[39] 40.4 8.0.1020
GNMT+RL Ensemble[38] 26.30 41.16 1.8·1020 1.1.1021
ConvS2S Ensemble[9] 26.36 41.29 7.7⋅10197.7⋅1019 1.2.1021
Transformer(base model) 27.3 38.1 3.3⋅10183.3⋅1018
Transformer(big) 28.4 41.8 2.3·1019

残差丢弃我们应用dropout[33]到每个子层的输出,然后将其添加到子层输入并标准化。此外,我们在编码器和解码器堆栈中对嵌入和位置编码的和也应用了dropout。对于基础模型,我们使用的丢弃率为Pdrop=0.1。Pdrop​=0.1。

在训练期间,我们采用了标签平滑值ϵls=0.1ϵls​=0.1 [36]。这会损害困惑度,因为模型学会变得更加不确定,但可以提高准确性和BLEU分数。

网络组成

深度神经网络(DNN)是一种人工神经网络,由多层神经元(节点)组成,用于学习和提取数据中的复杂特征。以下是深度神经网络的一些基本结构和知识点介绍:网络结构,激活函数,损失函数,优化算法,正则化,归一化,dropout,权重初始化,跳跃连接,Attention机制,卷积操作,循环网络,残差块,数据增强,生成对抗网络,迁移学习,对比学习,位置编码,自监督学习,混合精度训练,注意力蒸馏,模型剪枝,自动机器学习

1. 网络结构

输入层,隐藏层,输出层,卷积层,池化层

2. 激活函数

激活函数决定了节点的输出。包括:Sigmoid,ReLU,Softmax,Tanh(双曲正切函数),Dying ReLU,Leaky ReLU,Parametric ReLU(PReLU),ELU(Exponential Linear Unit),Swish,GELU(Gaussian Error Linear Unit)

总结:1、Sigmoid 和 Tanh 曾经是早期神经网络的常用激活函数,但由于梯度消失问题,较少用于深层网络。2、ReLU 是现代神经网络中最广泛使用的激活函数,因其计算简单且能够解决梯度消失问题。3、Leaky ReLU 和 PReLU 是对ReLU的改进,解决了ReLU中可能出现的神经元死亡问题。4、Swish 和 GELU 是近几年提出的激活函数,在某些任务上表现优异。Softmax 常用于多分类任务的输出层,用于产生概率分布。5、平滑的激活函数,能够产生比ReLU更好的结果,特别是在某些深度网络中。应用:在一些高端模型(如Google提出的EfficientNet中)表现出色。

3. 损失函数

损失函数用于评估模型的预测与真实值之间的差距。常见的损失函数包括:均方误差(MSE):用于回归问题,计算预测值与真实值的平方差。交叉熵损失:用于分类问题,评估模型的预测概率与真实标签之间的差异。

4. 优化算法

优化算法用于调整网络的权重和偏置,以最小化损失函数。常见的优化算法包括:随机梯度下降(SGD):通过随机抽样训练数据来更新参数。Adam:结合了动量和自适应学习率,能有效加速训练过程。

5. 正则化

正则化技术用于防止模型过拟合,常见的方法包括:L1和L2正则化:在损失函数中添加惩罚项,以限制权重的大小。Dropout:在训练过程中随机忽略部分神经元,以减少对特定神经元的依赖。

6. 迁移学习

迁移学习是一种利用预训练模型的技术,可以在少量数据的情况下获得较好的性能。通过将一个任务中学到的知识迁移到另一个任务中,通常可以加速训练并提高模型的表现。

7. 权重初始化

权重初始化是指在训练神经网络之前,随机设定网络的权重。这一过程对模型的训练收敛速度和稳定性有重要影响。常用的权重初始化方法包括:Xavier初始化:适用于Sigmoid和tanh激活函数,确保输入和输出的方差相等。He初始化:适用于ReLU激活函数,能够应对深层网络中的梯度消失问题。

8. 跳跃连接(Skip Connections)

跳跃连接是深度神经网络(如ResNet)中的一种结构,它允许前面层的输出绕过中间层直接连接到后面的层,帮助解决深层网络中的梯度消失问题,并加速训练。

9. Attention机制

Attention机制广泛应用于自然语言处理(NLP)和计算机视觉任务中,能够根据不同部分的重要性动态调整网络对输入数据的“关注”。自注意力机制,多头注意力机制。Self-Attention:尤其是在Transformer架构中,允许模型聚焦于输入序列的不同部分,提升了模型在序列任务(如翻译、摘要等)中的表现。Multi-Head Attention:进一步改进了Self-Attention,通过多个并行的Attention头来捕捉不同的上下文关系。

10. 卷积操作(Convolution)

在计算机视觉任务中,卷积神经网络(CNN) 是最重要的架构之一。卷积操作通过局部感知输入的特征(如边缘、纹理等),让网络能够捕捉图像中的空间特征。关键组成部分包括:

卷积层:进行局部特征提取。池化层:降低特征图的维度,减小计算量。全连接层:用于最终的分类或回归任务。

11. 循环网络(Recurrent Networks, RNN)

循环神经网络(RNN)及其变种(如LSTM、GRU)在处理序列数据(如时间序列、文本)方面尤为重要。它们通过保留输入的历史信息,能够有效处理具有时序相关性的任务。

12. 残差块(Residual Block)

在深度网络中,通过引入残差块,可以避免梯度消失或梯度爆炸问题。残差块在输入与输出之间引入了一条直接的快捷连接路径,有效加深了网络的深度而不损失训练效果。

13. Dropout

Dropout是一种用于防止过拟合的正则化技术。它通过在每次训练过程中随机“丢弃”一部分神经元,使得模型不会过度依赖某些特定的神经元,增强了模型的泛化能力。

14. 数据增强(Data Augmentation)

数据增强通过对训练数据进行随机变换(如旋转、缩放、平移等),人为增加训练样本的多样性,帮助模型更好地泛化。常用于图像数据的处理。

15. 生成对抗网络(GAN)

GAN是一类非常有影响力的生成模型,它由两个网络组成——生成器和判别器,生成器试图生成逼真的数据样本,判别器则试图区分生成的数据和真实数据。它们被广泛用于图像生成、超分辨率和其他任务。

16. 对比学习(Contrastive Learning)

对比学习是一种无监督学习方法,旨在通过使相似样本彼此接近、不同样本远离的方式学习有效的特征表示。它在表示学习和迁移学习中表现出色,如SimCLR、MoCo等。

17. 位置编码(Positional Encoding)

在处理序列数据(特别是Transformer模型中)时,位置编码帮助模型理解输入序列中每个元素的位置信息,因为Transformer模型本身是无序的。

18. 自监督学习(Self-Supervised Learning)

自监督学习通过使用数据中的隐含信息(如旋转角度、颜色等)进行预训练,再用于下游任务。它能够在没有大量标签数据的情况下学习有用的特征表示。

19. 混合精度训练(Mixed Precision Training)

混合精度训练结合了16位和32位浮点运算,可以在加速深度学习模型训练的同时减少显存使用。它在大规模训练中非常有用,特别是在GPU和TPU的高效利用上。

20. 知识蒸馏(Knowledge Distillation)

蒸馏是一种将大型模型中的知识迁移到较小模型中的方法,从而保持模型性能的同时降低模型复杂度。在实际应用中,通过“教师模型”和“学生模型”的训练,可以提高小模型的推理速度。

21. 模型剪枝(Model Pruning)

剪枝是一种减少模型大小和复杂度的技术,通过去除不重要的权重或神经元来提高计算效率和推理速度。

22. 自动机器学习(AutoML)

AutoML通过自动选择模型、超参数调优和特征工程,减少了人工调参的工作量。它旨在使模型开发更加高效且易于应用。

损失函数不收敛

在深度神经网络训练过程中,损失函数不收敛是一个常见的问题,可能由多种原因导致。以下是一些常见原因及其对应的解决方法:

1. 学习率设置不当

过高的学习率:可能导致损失函数在最优值附近震荡而不收敛。

过低的学习率:可能导致收敛速度过慢,训练过程显得不稳定。

解决方法:调整学习率,可以使用学习率调度器(如余弦退火、学习率衰减)来动态调整学习率。使用自适应学习率算法(如Adam、RMSprop),这些算法能根据梯度变化自动调整学习率。

2. 初始化权重不当

权重初始化不当可能导致神经元在训练初期无法有效激活,尤其在使用Sigmoid或Tanh等激活函数时,可能导致梯度消失。

解决方法:使用合适的权重初始化方法,如He初始化(用于ReLU激活函数)或Xavier初始化(用于Sigmoid和Tanh激活函数)。

3. 激活函数选择不当

不同的激活函数在不同情况下表现不同,某些激活函数可能会导致梯度消失或饱和。

解决方法:尝试不同的激活函数,如ReLU、Leaky ReLU或ELU等,这些函数在深层网络中通常表现更好。

4. 数据预处理不当

输入数据的分布和范围可能影响训练效果。未标准化或未归一化的数据可能导致训练过程不稳定。解决方法:

对输入数据进行标准化(均值为0,标准差为1)或归一化(将数据缩放到特定范围)处理。

5. 模型复杂度

模型过于复杂(层数或参数过多)可能导致过拟合,而模型过于简单可能无法学习到足够的特征。

解决方法:调整网络结构,适当增加或减少隐藏层的数量和每层的神经元数。

使用交叉验证选择最佳模型架构。

6. 正则化技术

未使用正则化技术可能导致模型在训练集上过拟合,从而在验证集上表现不佳。

解决方法:添加L1或L2正则化,或使用Dropout等技术。

7. 训练数据不足或不平衡

如果训练数据量不足或类别不平衡,可能导致模型无法学习到有效特征。

解决方法:增加训练数据量,或使用数据增强技术。采用平衡类别的方法,如过采样、欠采样等。

8. 训练时间不足

有时训练时间不够,模型还未完全收敛。

解决方法:增加训练的epoch数,并观察训练和验证损失的变化。

结论:解决深度神经网络训练时损失函数不收敛的问题通常需要综合考虑多个因素。通过适当调整学习率、权重初始化、激活函数选择、数据预处理、模型结构和正则化等方面,可以有效改善训练效果。调试和优化过程可能需要反复实验,直到找到合适的配置

梯度弥散和梯度爆炸

在深度学习中,梯度爆炸和梯度弥散是训练神经网络时常见的问题,特别是在深层网络中。以下是这两种情况的概念介绍和原因分析。

1. 梯度爆炸

        概念:梯度爆炸是指在反向传播过程中,网络的梯度值变得非常大,导致参数更新时出现剧烈的变化。这会使得模型的损失函数不稳定,可能导致模型训练失败,甚至出现NaN(不是一个数字)值。

        原因:深层网络:在深层神经网络中,随着层数的增加,梯度在反向传播过程中不断累积,可能导致梯度值指数级增长。激活函数选择:某些激活函数(如ReLU)在特定条件下可能导致输出值增大,从而使梯度爆炸。权重初始化不当:不合适的权重初始化(如初始化过大)可能导致在训练初期就引发梯度爆炸。

        解决方法:梯度裁剪(Gradient Clipping):在梯度更新前,将梯度限制在一个预定义的范围内,以防止过大的更新。合适的权重初始化:使用适当的权重初始化方法,如Xavier或He初始化,以保持激活值和梯度的稳定性。调整学习率:减小学习率可以帮助减缓梯度的变化。

2. 梯度弥散

        概念:梯度弥散是指在反向传播过程中,网络的梯度值变得非常小,导致参数更新幅度过小,训练速度变慢,甚至可能导致模型无法学习。

        原因:深层网络:在深层神经网络中,随着层数的增加,梯度在反向传播过程中可能会逐层减小,尤其是在使用Sigmoid或Tanh等激活函数时,容易出现饱和现象,从而导致梯度消失。激活函数选择:某些激活函数(如Sigmoid和Tanh)在输入值远离0时,梯度值会迅速接近于0。权重初始化不当:初始化过小的权重可能导致激活值和梯度都非常小,从而造成梯度弥散。

        解决方法:使用合适的激活函数:使用ReLU或其变种(如Leaky ReLU、ELU),这些激活函数在输入为正时不会导致梯度消失。批量归一化(Batch Normalization):在每一层后添加批量归一化,有助于缓解梯度弥散的问题,并加速训练。残差网络(ResNet):采用残差连接,使得梯度能够通过跳跃连接直接传递,缓解梯度消失问题。

        总结:梯度爆炸和梯度弥散都是深度神经网络训练中的常见问题,可能导致模型无法有效学习。了解它们的原因和影响因素,有助于采取适当的解决措施,保证模型训练的稳定性和效率。通过合理的网络设计、激活函数选择和参数调整,可以有效减少这些问题的发生。

神经网络过拟合与欠拟合

1. 过拟合 (Overfitting)

过拟合是指神经网络在训练数据上的表现非常好,但在测试数据或实际应用中的泛化能力很差。这表明模型学到了训练数据中的噪声或无关特征,而没有捕捉到数据的本质规律

常见原因:

  • 模型过于复杂(例如网络层数太深、参数过多)。
  • 训练数据不足,导致模型学习不到足够的多样性。
  • 数据中存在噪声,而模型将其视为有意义的模式。

解决过拟合的方案

增加数据量:使用增强数据或者增加更多的真实数据,主要目的是增加数据的多样性。增强数据的方式有旋转缩放剪裁等。

正则化:L1正则化和L2正则化,权重衰减,限制模型参数的大小。Dropout,在训练过程中随机丢弃一部分神经元,减少模型对特定神经元的依赖

简化模型:减少网络的层数或者每层的神经元数量

提前停止 (Early Stopping):在验证集损失开始增加时停止训练,避免模型过度拟合训练数据。

使用预训练模型:使用已经在大数据集上训练好的模型,并在目标任务上进行微调。

2. 欠拟合 (Underfitting)

欠拟合是指神经网络无法在训练数据上获得足够好的表现,甚至连训练集都无法拟合。这表明模型过于简单,不能捕捉数据的复杂规律。

常见原因:

  • 模型过于简单(例如网络层数不够、参数不足)。
  • 训练时间不足,模型未能充分学习。
  • 特征表达不足,输入数据未进行有效的预处理。

解决欠拟合的方案

增加模型复杂度:增加网络的层数或每层的神经元数量。使用更复杂的模型架构,如深度残差网络(ResNet)或变换器(Transformer)。

延长训练时间:增加训练的迭代次数或轮数(epochs)。

优化数据预处理:归一化或标准化数据,提高模型的输入质量。选择更有代表性和重要的特征。

更换激活函数:尝试更合适的激活函数,如ReLU、Leaky ReLU、Swish等。

调整超参数:增大学习率或改变优化器(如SGD、Adam)。调整batch size,平衡模型的训练效率和收敛速度。

引入额外信息:使用特征工程方法增强输入特征。合并多个数据来源,提供更多上下文信息

transformer

模型模块组成介绍

核心模块:自注意力机制,多头注意力,位置编码,前馈神经网络,残差连接和归一化。

自注意力机制:计算输入序列中各元素之间的相关性,为每个元素分配权重,关注重要部分

多头注意力(Multi-Head Attention):通过多个注意力头同时关注不同的特征空间。

位置编码(Positional Encoding):在序列输入中加入位置信息,弥补模型对序列顺序的敏感性。

前馈神经网络(Feedforward Network):为每个位置独立学习更复杂的特征。

残差连接(Residual Connection)和归一化(Layer Normalization):提高梯度流动,防止梯度消失并稳定训练。

模型架构---

attention----

transformer适用场景

自然语言处理:机器翻译(如 Google Translate 使用的 Transformer 模型),文本生成(如 GPT 系列),文本分类、情感分析、问答系统。

计算机视觉:图像分类(如 Vision Transformer, ViT),图像生成(如 DALL-E),目标检测和语义分割。

语音处理:语音识别,语音合成(如 SpeechTransformer)

时间序列数据:金融数据预测,传感器异常检测

多模态学习:同时处理图像、文本、语音等多种模态数据(如 CLIP、Flaming

transformer主要变体

Transformer 的灵活性使其衍生出多种变体,以适应不同任务:

编码器-解码器结构:例如 BERT(仅编码器)、GPT(仅解码器)、T5(完整编码器-解码器结构)。

轻量化模型:TinyBERT、MobileBERT:优化参数量,适用于移动设备。DistilBERT:通过蒸馏方法减少模型大小。

视觉相关变体:Vision Transformer (ViT):直接将图像分块后输入,SwinTransformer:使用滑动窗口机制优化图像处理。

长序列处理:Longformer、BigBird:通过稀疏注意力机制高效处理超长序列。

高效变种:Performer、Reformer:使用低秩分解或局部注意力机制降低计算复杂度。

跨模态模型:DALL-E、CLIP:同时处理文本和图像,实现生成与理解。

图像数据上的transformer

对于图片数据,包括图像分类、目标检测、图像生成和分割等。以下是关于图片数据中使用 Transformer 的详细介绍,

图像分类:ViT、DeiT。

目标检测:DETR、DINO。

图像分割:Segformer、Swin Transformer。

图像生成:DALL-E、VQ-VAE-Transformer。

超分辨率与复原:SwinIR、ESRT。

图像数据的编码分类1

适用于图像数据的 Transformer 模型的数据编码方式主要是为了将图像数据转换为适合 Transformer 模型处理的序列化数据形式。以几种常见的方法和流程:

1. 图像分块编码 (Patch Encoding)

Transformer 的标准输入是序列形式,因此需要将图像切分为小块处理:

  • 切分图像

将图像划分为固定大小的非重叠小块(例如,16×16 像素的 patch)。

如果输入图像是 H×W×C(高、宽、通道),那么切分后得到小块,每个小块大小为 P×P×C

  • 线性投影 (Linear Projection)

使用一个全连接层将每个 P×P×C小块展开为固定长度的向量。

转换后每个 patch 的表示大小为 D(Transformer 的嵌入维度)。

  • 位置编码 (Positional Encoding)

因为 Transformer 模型本身不具备位置感知能力,需要为每个 patch 添加位置编码。

可以是固定的位置编码(如正余弦函数生成)或可学习的位置编码。

2. 图像序列化 (Flattening)

直接将整个图像展开成一个序列,然后通过嵌入层映射到高维:

  • 序列化方法:将 H×W×C 图像直接展平为一个 (H×W)×C 的序列。将每个像素点或像素组通过嵌入层(全连接层)映射到 D维。

  • 缺点:对高分辨率图像来说,序列长度会非常长,导致计算和内存需求增加。

3. 混合编码方式 (Hybrid Models)

将卷积神经网络 (CNN) 与 Transformer 结合:

  • 卷积特征提取:先用 CNN 提取特征图(例如,通过 ResNet 或 MobileNet 提取)。

提取的特征图大小通常为提取的特征图大小通常H′×W′×C′。

  • Flatten 或 Patch 处理:将特征图展平或切分成小块,作为 Transformer 的输入。

这种方法结合了 CNN 的局部感受野能力与 Transformer 的全局建模能力。

4. 嵌入层结合通道维度 (Channel-wise Embedding)

直接对图像的每个通道进行处理:

  • 嵌入层结合通道维度方法:将每个通道的像素序列化,输入嵌入层。对通道维度进行拼接或融合。
  • 使用场景:在多光谱或多模态图像中,通道间可能有较强的语义相关性。

5. 特殊编码方式 (如 Swin Transformer)

Swin Transformer 是一种专门为图像设计的 Transformer,它在编码方式上有所改进:

  • 分层分块:使用滑动窗口切分图像为局部区域。对每个窗口内的 patch 进行 Transformer 操作。
  • 多尺度处理:模型通过逐步减少特征图分辨率(类似于 CNN 的池化)来实现多尺度建模。


6. 时间序列数据与视频处理(扩展)

对于视频数据,可以先对每帧图像进行上述编码,然后将每帧的表示作为序列输入 Transformer。

总结

  • 主流方法:Vision Transformer (ViT) 主要采用图像分块编码;Swin Transformer 通过滑动窗口增强局部感受能力。
  • 适用场景
    • 图像分块适用于高分辨率图像。
    • 混合编码适用于希望结合 CNN 和 Transformer 优势的场景。
    • 时间序列扩展适用于视频和连续数据处理。

==============================================

图像数据的编码分类2

        在计算机视觉中,图像数据的编码方式指的是将图像转换成模型可以理解的格式。常见的编码方式包括 像素级编码 和 高级特征编码

1. 传统像素级编码(Pixel-level Encoding)

这是最基础的编码方式,它将图像的每个像素点的颜色值转换成特征向量。

RGB 编码:图像的每个像素由红色、绿色、蓝色三个通道的值表示。每个通道的值通常在 0 到 255 之间(8 位)。例如,对于一张 256x256 的 RGB 图像,图像的编码为一个 (256, 256, 3) 的矩阵。这种编码方式通常用于传统的图像分类、物体识别等任务。

灰度图像编码:将图像的每个像素表示为一个灰度值(单通道),常用于一些低复杂度的图像处理任务。例如,对于一个 256x256 的灰度图像,编码是一个 (256, 256) 的矩阵。

应用场景:适用于传统的机器学习模型,如 SVM、KNN、CNN 等。常用于图像预处理,特别是在视觉任务中对图像进行标准化或归一化。

2. 基于卷积神经网络(CNN)的高级特征编码

随着深度学习的进展,特别是卷积神经网络(CNN)的广泛应用,图像数据的编码不仅仅局限于像素层面,更多的是在更高的特征层面进行编码。

卷积特征图:在 CNN 中,通过卷积层提取图像的特征,输出的特征图可以视为图像的高级表示。每一层的卷积操作会对图像进行不同层次的特征提取(例如边缘、纹理、形状等)。

对于深层网络,最终的特征图是一个多维张量,包含了图像的高层次特征,而不是原始的像素数据。

局部特征编码:使用卷积神经网络(CNN)对图像进行处理,获取局部区域的特征表示。这些特征可以用于目标检测、图像分割等任务。每个卷积层通常提取不同层次的图像特征,逐层编码图像的内容。

应用场景:用于图像分类、目标检测、语义分割等任务。在转移学习中,通过利用预训练网络(如 VGG、ResNet、Inception 等)来获取图像的特征表示。

3. Transformer-based 图像编码(如 Vision Transformer, ViT)

Transformer 最初是为自然语言处理(NLP)任务设计的,但在计算机视觉中,Vision Transformer(ViT)将图像分割成固定大小的块,然后将每个块作为 Transformer 的输入进行处理。

Patch Embedding:将图像分成若干个小块(patch),每个块的像素经过线性变换得到固定维度的嵌入(embedding),然后将这些块作为“tokens”输入到 Transformer 中。例如,对于 224x224 大小的图像,如果将其分成 16x16 的小块,那么图像就会被分成 196 个块,每个块通过线性变换转化为嵌入向量。

这样,每个图像就可以表示为多个小块的特征序列,Transformer 利用自注意力机制来建模这些块之间的关系。

应用场景:用于图像分类、目标检测、图像生成等任务,尤其适用于大规模数据集。在 ViT 中,Transformer 通过处理各个图像块的全局依赖性来获取丰富的上下文信息,适用于长范围的图像理解任务。

4. 图像特征压缩编码(如 JPEG、PNG)

这类编码方法通常用于图像压缩,以减少存储空间和传输带宽。图像压缩通常依赖于数据冗余的去除和重要特征的保留。

JPEG 压缩:一种常见的有损图像压缩方法,它利用离散余弦变换(DCT)将图像变换到频域,去除高频信息,从而减小图像的文件大小。

适合于自然图像,压缩比高,图像质量在压缩较大时会有所损失。

PNG 压缩:一种无损压缩方法,适用于需要保留每个像素信息的场景。常用于图像质量要求高的应用。

应用场景用于图像存储、传输、加载等任务,特别是在需要减小图像文件大小时。

5. 图像编码与嵌入(Embedding)

在一些先进的计算机视觉任务中,图像的编码不仅仅是获取图像的低级或高级特征表示,还涉及将这些特征嵌入到低维空间中,便于后续处理和分析。

VGG、ResNet、EfficientNet 特征提取:这些模型通常用作预训练模型,通过提取图像的高维特征并进行降维(例如使用全连接层将特征压缩为更低维度的向量),这些向量可以作为图像的嵌入表示。

CLIP(Contrastive Language-Image Pretraining):通过联合训练图像和文本描述,CLIP 学习到图像和文本的共同嵌入空间。图像编码通过其深度网络生成图像嵌入,这些嵌入可以与文本嵌入进行对比和匹配。

应用场景:用于图像检索、图像与文本匹配、多模态任务(例如图像和文本的联合学习)。

6. 图像处理中的变换(如 Fourier Transform, Wavelet Transform)

这些编码方法通常用于图像的频域分析,适用于某些特定任务,比如图像滤波、去噪等。

傅里叶变换(Fourier Transform):将图像从空间域转换到频率域,主要用于分析图像的频率成分。这种方法常用于图像滤波和压缩。

小波变换(Wavelet Transform):通过多尺度分析来提取图像的局部特征。与傅里叶变换不同,小波变换可以提供时间和频率的同时局部化信息,常用于图像去噪和压缩。

应用场景:用于图像的频率分析、滤波、压缩和去噪。

总结:在图像数据的编码过程中,选择何种编码方式取决于具体的任务需求。例如,对于分类任务,基于 CNN 提取的高级特征或者 Vision Transformer 的图像块编码都能提供有效的图像表示;对于图像存储或传输,压缩编码(如 JPEG、PNG)可以减少文件大小;而对于多模态任务,图像的嵌入和特征表示通常能够为模型提供更具判别力的输入

transformer与图像处理

1. 图像分类 Vision Transformer (ViT)

核心思想:将图片分割为固定大小的图像块(patches),类似 NLP 中的词嵌入处理方式,然后将这些块作为输入嵌入 Transformer 模型。

处理流程:

        图像被划分成尺寸为 16×16或 32×32 像素的小块

        将每个块展平成向量,作为输入特征向量。

        加入位置编码(Positional Encoding)以保留位置信息。

        使用标准的 Transformer 编码器进行特征提取。

        最后通过分类头(通常是 MLP 层)预测类别。

优点:在大型数据集(如 ImageNet)上表现优异。

代表模型:ViT、DeiT(Data-efficient ViT)。

2. 目标检测 DEtection TRansformer (DETR)

核心思想:使用 Transformer 模型直接回归目标的位置和类别,无需传统的区域提取或 anchor boxes。

处理流程:

        将图像特征通过 CNN 提取后嵌入到 Transformer 模型。

        引入一组 learnable query embeddings 表示不同的检测对象。

        使用自注意力机制来建模对象间的关系。

        输出目标的类别和边界框信息。

优点:简化目标检测流水线,无需非极大值抑制(NMS)。

代表模型:DETR、DINO。

3. 图像生成 DALL-E 和 Image Transformer

DALL-E:通过跨模态 Transformer,将文本描述转化为图像。

Image Transformer:用于生成图像,通过自回归方式逐像素或逐块生成。

处理流程:

        将图像分块,嵌入到 Transformer 模型中。

        使用注意力机制学习图像块间的相关性。

        逐步生成高质量图像。

4. 语义分割 Segformer

核心思想:结合轻量级 Transformer 和语义分割任务需求,通过多尺度特征提取实现高效分割。

优点:同时具备全局上下文感知能力和高分辨率细节提取能力。

Swin Transformer for Segmentation

特点:基于滑动窗口机制,支持更高分辨率的输入,增强局部特征提取能力。

5. 视频分析 TimeSformer

将视频数据分解为时间帧(Temporal Frame),并应用 Transformer 分别建模时间和空间信息。

应用场景:动作识别,视频分类

6. 图像超分辨率 SwinIR

结合 Swin Transformer 和图像复原技术,用于超分辨率重建、去噪等任务。

优点:有效处理复杂图像结构和高频细节。

主要问题

高计算复杂度:Transformer 的注意力机制对大图像分辨率会导致内存需求爆炸。

解决方案:稀疏注意力(Sparse Attention)、滑动窗口(Sliding Window)。

数据需求大:需要大量标注数据才能充分训练 Transformer。

解决方案:结合预训练与迁移学习(如 ViT 在 ImageNet 上的预训练)。

transformer中不同数据源的编码

嵌入方式指将数据如何抽象为向量

编码方式指机器以什么方式理解和处理向量

1. 文本数据

词嵌入(Word Embedding)

目标:将单词或子词映射为固定维度的向量表示。

常见方法:

        预训练词向量:如 Word2Vec、GloVe。这些方法基于统计学习。

        学习嵌入:直接训练模型(如 Transformer)时优化的词向量。

        子词级嵌入:如 Byte Pair Encoding (BPE)、WordPiece,适用于处理词汇外(OOV)词的问题。

        位置编码(Positional Encoding)Transformer 中无法感知序列顺序,因此需要显式加入位置信息。

        固定位置编码:使用三角函数(cos,sin)生成各维度的位置嵌入。

        可学习位置编码:将位置嵌入作为可训练参数。

        相对位置编码:表示元素之间的相对位置信息,常用于改进长序列任务(如 Transformer-XL、T5)。

2. 图像数据

Patch Embedding(块嵌入)

将图像划分为固定大小的小块(patches),然后展平并映射为向量。例如,在 Vision Transformer (ViT) 中,每个 16*16的图像块展平为一个向量,再通过线性变换映射到所需的维度。

        位置编码类似文本中的位置编码,图像也需要编码位置信息:

        绝对位置编码:为每个图像块分配固定的位置信息。

        相对位置编码:捕捉图像块之间的相对位置信息。

        二维位置编码:分别为行和列提供位置信息。

3. 视频数据

        时空块嵌入(Spatiotemporal Embedding)对视频数据分解为时间帧,每帧应用类似图像的 Patch Embedding,同时结合时间维度的编码。例如,TimeSformer 使用空间编码与时间编码相结合。

        时间位置编码:为每个帧添加时间位置信息,帮助模型捕捉时序特征。

4. 音频数据

        频谱嵌入(Spectrogram Embedding)将音频转换为频谱图(如梅尔频谱图),然后类似图像任务进行 Patch Embedding。

        时间-频率位置编码:同时编码时间和频率维度的位置。

        序列嵌入:对原始音频波形进行分帧处理,直接作为输入(如 Wav2Vec 系列)。

5. 多模态数据

        模态嵌入(Modality Embedding)为不同模态(如文本、图像、音频)分配独特的模态标识。例如,在 CLIP 模型中,文本和图像的嵌入通过统一的 Transformer 空间对齐。

        对齐编码(Alignment Encoding):使用统一的编码方式对多模态数据进行对齐,例如通过共享注意力机制学习模态之间的关系。

6. 特殊任务相关编码

        任务嵌入(Task Embedding):为不同任务加入任务标识符。例如:在 T5 中,输入中明确标注任务类型(如 "translate English to French:")。

        段落嵌入(Segment Embedding):用于区分文本的不同部分(如 BERT 的句子 A 和句子 B)。

        指针或目标编码:用于序列生成任务的指针网络(Pointer Networks),帮助模型生成更结构化的输出。

编码方式的选择原则

        数据类型:根据输入数据(文本、图像、音频等)选择合适的编码方式。

        任务目标:如需要捕捉时序信息,则选择时间位置编码。

        计算效率:如高维图像数据可选择稀疏注意力编码方式以降低复杂度。

Logo

尧米是由西云算力与CSDN联合运营的AI算力和模型开源社区品牌,为基于DaModel智算平台的AI应用企业和泛AI开发者提供技术交流与成果转化平台。

更多推荐