Transformer简单编码实现

一、前言:

Transformer 架构是一种深度学习模型,最初由 Vaswani 等人在 2017 年的论文《Attention Is All You Need》中提出,主要用于处理序列到序列(sequence-to-sequence)的任务,如机器翻译、文本摘要等。它的核心思想是使用注意力机制(Attention Mechanism)来解决传统序列处理模型(如循环神经网络 RNN)在处理长距离依赖问题时的局限性。

Transformer 架构的一些特点:

1:编码器-解码器结构

  • 编码器(Encoder):由多个相同的层(Layer)组成,每个层包含两个主要的子层:自注意力(Self-Attention)机制和前馈神经网络(Feed-Forward Neural Network)。编码器的主要任务是理解输入序列。
  • 解码器(Decoder):同样由多个相同的层组成,每个层包含三个主要的子层:自注意力机制、编码器-解码器注意力(Encoder-Decoder Attention)机制和前馈神经网络。解码器的主要任务是生成输出序列。

2:自注意力机制(Self-Attention)

  • 允许模型在序列的不同位置之间直接捕捉依赖关系,无论这些位置之间的距离有多远。
  • 通过计算序列中每个元素对其他元素的注意力权重,来确定每个元素在序列中的上下文关系。

 3:并行处理能力

  • 由于自注意力机制不依赖于序列的特定顺序,Transformer 可以并行处理整个序列,这与 RNN 的逐元素处理方式相比,大大提高了计算效率。

 4:位置编码(Positional Encoding)

  • 由于 Transformer 不像 RNN 那样具有递归结构,因此需要一种方式来编码序列中元素的位置信息。
  • 位置编码通常是通过添加一个与输入序列长度相关的向量来实现的,这个向量通过正弦和余弦函数生成,以保持序列中元素的顺序信息。

5:残差连接(Residual Connection)

  • 每个子层的输出都会加上该子层的输入,然后进行层归一化(Layer Normalization)。
  • 这种设计有助于避免在深层网络中出现的梯度消失问题。

 6:层归一化(Layer Normalization)

  • 在每个子层的输出上应用归一化,有助于稳定训练过程,加速收敛。

 二、各部分实现:

1、 位置编码:

由于自注意力机制不依赖于序列的特定顺序,Transformer 可以并行处理整个序列,这与 RNN 的逐元素处理方式相比,大大提高了计算效率。但与之对应的,对于Transformer来说:“我爱你”和“你爱我”的效果一样,但实际我们知道,这并不一样,因此,我们需要位置编码来标识个词元的位置关系,这里采用最简单的实现方式:一维绝对位置编码。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)

        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) * -(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)
        self.register_buffer('pe', pe)

    def forward(self, x):
        return self.dropout(x + self.pe[:, :x.size(1)].detach())

2、注意力机制

这部分是transformer架构的核心,有自注意力机制、交叉注意力机制、多头注意力机制等。

注意力机制如图:

输入三个矩阵:Q、K、V,当 Q=K=V时是自注意力机制,当Q!=K=V时,是交叉注意力机制,在trransformer架构中,编码器使用自注意力机制,解码器使用交叉注意力机制,而且都是用多个注意力头,这是为了能多层次的表示特征。

这一部分原理说明请自行探索,这里直接给出实现代码:

class MultiHeadedAttention(nn.Module):
    def __init__(self, h, d_model, dropout):
        super(MultiHeadedAttention, self).__init__()
        assert d_model % h == 0
        self.d_k = d_model // h
        self.h = h
        self.linears = clones(nn.Linear(d_model, d_model), 4)
        self.attn = None
        self.dropout = nn.Dropout(dropout)

    def attention(self, q, k, v, mask=None, dropout=None):
        d_k = q.size(-1)
        scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        p_attn = F.softmax(scores, dim=-1)
        if dropout is not None:
            p_attn = dropout(p_attn)
        return torch.matmul(p_attn, v), p_attn

    def forward(self, query, key, value, mask=None):
        query, key, value = self.linears[0](query), self.linears[1](key), self.linears[2](value)
        if mask is not None:
            mask = mask.unsqueeze(1)
        batch_size = query.size(0)
        query = query.view(batch_size, -1, self.h, self.d_k).transpose(1, 2)
        value = value.view(batch_size, -1, self.h, self.d_k).transpose(1, 2)
        key = key.view(batch_size, -1, self.h, self.d_k).transpose(1, 2)

        x, self.attn = self.attention(query, key, value, mask, dropout=self.dropout)
        x = x.transpose(1, 2).view(batch_size, -1, self.h * self.d_k)
        return self.linears[-1](x)

该部分输入的Q、K、V要先经过映射,之后按照下面的公式计算即可,其中张量形状变化需要注意:输入Q、K、V的形状为[batch_size,seq_length,d_model],需要先将d_model转化为h*d_k(h:注意力头的数量,d_k:每个头应该处理的维度),之后调整位置为[batch_size,h,seq_length,d_k],当完成计算得到注意力得分x和注意力矩阵sttn之后,需要将x由形状[batch_size,h,seq_length,d_k]调整为[batch_size,seq_length,d_model],之后再经过映射返回。

这里的mask需要着重说明,在transformer架构中编码器和解码器需要用到的mask不同,在编码器中,由于同一个批次输入的序列长度都一致,这其中,有些序列是经过填充得到的,由于计算注意力机制代价比较大,那么这些填充的0就没有计算的必要,那么就要把这些填充的值用一个很小的数来替换,这样的话经过softmax就会把这些值变成0,方便后续计算。那么编码器使用的mask怎么得到的呢:

def create_encoder_mask(src_seq, pad_idx):
    """
    创建编码器的Padding Mask

    Args:
    - src_seq: 源序列 [batch_size, seq_len]
    - pad_idx: 填充标记的索引

    Returns:
    - mask: 布尔掩码 [batch_size, 1, 1, seq_len]
    """
    # 创建掩码,将填充位置设为0,非填充位置设为1
    mask = (src_seq != pad_idx).unsqueeze(1).unsqueeze(2)
    return mask

 解码器主要用于生成任务,在实际情况中,生成任务是有顺序的,只有得到前一个输出才能进行下一步的计算,但是由于Transformer的注意力机制会看到全局的信息,不符合我们的要求,所以我们需要“屏蔽”掉注意力矩阵中当前位置之后的所有元素(其实就是得到一个三角矩阵),确保当前位置无法“看到”未来的信息,从而保持序列生成的顺序性,同时为了不计算那些经过填充的位置,还需要将上述矩阵与类似编码器那样的矩阵做逻辑与运算:

def create_decoder_mask(tgt_seq, pad_idx):
    """
    创建解码器的Padding Mask

    Args:
    - tgt_seq: 目标序列 [batch_size, seq_len]
    - pad_idx: 填充标记的索引

    Returns:
    - padding_mask: 填充掩码 [batch_size, 1, 1, seq_len]
    - subsequent_mask: 后续掩码 [1, seq_len, seq_len]
    """
    # 创建Padding Mask
    padding_mask = (tgt_seq != pad_idx).unsqueeze(1).unsqueeze(2)

    # 创建Subsequent Mask
    seq_len = tgt_seq.size(1)
    subsequent_mask = torch.tril(torch.ones(seq_len, seq_len)).unsqueeze(0)

    # 组合两种Mask
    decoder_mask = padding_mask & subsequent_mask.bool()

    return decoder_mask

3、前馈神经网络:

class PositionwiseFeedForward(nn.Module):
    """Implements FFN equation."""

    def __init__(self, d_model, d_ff, dropout=0.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(F.relu(self.w_1(x))))

4、子层连接:

上面三个图是Transformer架构的三个模块,他们都有一个共同点:一个功能模块后跟残差连接,这样的话我们可以设计实现这种模块,方便复用:

class SublayerConnection(nn.Module):
    def __init__(self, d_model, dropout):
        super(SublayerConnection, self).__init__()
        self.norm = LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer):
        return x + self.dropout(sublayer(self.norm(x)))

5、编码器:

编码器由N个编码器层组成,我们设计实现这个编码器层,一层包括两个子模块:自注意力模块和前馈神经网络模块:

class EncoderLayer(nn.Module):
    def __init__(self, d_model, attn, feed_forward, dropout):
        super(EncoderLayer, self).__init__()
        self.attn = attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(d_model, dropout), 2)
        self.d_model = d_model

    def forward(self, x, mask):
        # mask:标识padding的mask:[batch_size,1,1,src_seq_len]
        # 首先经过自注意力机制,再经过前馈连接网络
        x = self.sublayer[0](x, lambda x: self.attn(x, x, x, mask))
        x = self.sublayer[1](x, self.feed_forward)
        return x

N个编码器层堆叠组成编码器:

class Encoder(nn.Module):
    def __init__(self, layer, N):
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.d_model)

    def forward(self, x, mask):
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)

其实很多模型都是只有编码器而没有解码器,例如如果你做分类、实体识别这种的而非生成任务就可以只用编码器,例如BERT和VIT。

6、解码器:

 解码器由N个解码器层组成,我们设计实现这个解码器层,一层包括三个子模块:掩码自注意力模块、交叉注意力模块和前馈神经网络模块:

class DecoderLayer(nn.Module):
    def __init__(self, d_model, src_attn, self_attn, feed_forword, dropout):
        #src_attn:交叉注意力
        #self._attn:自注意力
        super(DecoderLayer, self).__init__()
        self.self_attn = self_attn
        self.feed_forward = feed_forword
        self.src_attn = src_attn
        self.sublayer = clones(SublayerConnection(d_model, dropout), 3)
        self.d_model = d_model

    def forward(self, x, m, src_mask, target_mask):
        # src_mask:处理编码器的输出,标识padding的mask:[batch_size,1,1,src_seq_len]
        # target_mask:处理解码器输入,是标识padding的mask和下三角矩阵的&:[batch_size,1,target_seq_len,target_seq_len]
        # 先经过解码器输入的自注意力机制,再经过(q=编码器输出、k和v=解码器输入)交叉注意力机制,最后经过前馈连接网络
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, target_mask))
        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
        x = self.sublayer[2](x, self.feed_forward)
        return x

这里需要注意两个不同的注意力模块的输入。同理,N个解码器层堆叠组成解码器:

class Decoder(nn.Module):
    def __init__(self, layer, N):
        super(Decoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.d_model)

    def forward(self, x, m, src_mask, target_mask):
        for layer in self.layers:
            x = layer(x, m, src_mask, target_mask)
        return self.norm(x)

7、生成器:

生成器很简单,就是一个映射层加一个softmax层:

class Generator(nn.Module):
    def __init__(self, d_model, vocab):
        super(Generator, self).__init__()
        self.proj = nn.Linear(d_model, vocab)

    def forward(self, x):
        
        return F.softmax(self.proj(x), dim=-1)

三、整体实现:

对于两个嵌入输入:src_embed和tgt_embed,首先src_embed经过编码器,编码器结果与tgt_embed一起传入解码器,解码器结果输入生成器得到最后的生成结果:

class transformer(nn.Module):
    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
        super(transformer, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = src_embed
        self.tgt_embed = tgt_embed
        self.generator = generator

    def encode(self, src, src_mask):
        return self.encoder(self.src_embed(src), src_mask)

    def decode(self, m, target, src_mask, tgt_mask):
        return self.decoder(self.tgt_embed(target), m, src_mask, tgt_mask)

    def forward(self, src, target, src_mask, target_mask):
        m = self.encode(src, src_mask)
        x = self.decode(m, target, src_mask, target_mask)
        return self.generator(x)

构造模型:

这里给定参数:

src_vocab:源输入的文本词汇量大小

tgt_vocab:生成目标的文本词汇量大小(最后的输出是目标文本词汇索引)

n:编码器和解码器的层数

d_model:词向量嵌入的维度

d_ff:中间层的维度(前馈神经网络部分使用d_model*4)

h:注意力模块头的数量

dropout:用于模型泛化,丢弃参数比例

max_len:模型最长输入序列长度

def make_model(src_vocab, tgt_vocab, n=12, d_model=768, d_ff=3072, h=8, dropout=0.1, max_len=5000):
    c = copy.deepcopy
    attn = MultiHeadedAttention(h, d_model, dropout)
    ff = PositionwiseFeedForward(d_model, d_ff, dropout)
    position = PositionalEncoding(d_model, dropout, max_len)
    model = transformer(
        Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), n),
        Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), n),
        nn.Sequential(Embeddings(d_model, src_vocab), c(position)),
        nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),
        Generator(d_model, tgt_vocab)
    )
    for p in model.parameters():
        if p.dim() > 1:
            nn.init.xavier_uniform_(p)
    return model

### Transformer 编码器实现 在构建Transformer编码器时,通常会涉及到多个组件的组合。下面展示了一个完整的`TransformerModel`类定义,该类实现了带有自注意力机制的编码器结构[^1]。 ```python import torch.nn as nn class TransformerModel(nn.Module): def __init__(self, input_dim, model_dim=64, n_heads=4, num_encoder_layers=2): super(TransformerModel, self).__init__() # 输入维度转换到模型内部使用的维度大小 self.model_dim = model_dim # 将输入数据映射至model_dim维空间 self.fc_in = nn.Linear(input_dim, model_dim) # 构建多层Transformer编码器堆叠 encoder_layer = nn.TransformerEncoderLayer(d_model=model_dim, nhead=n_heads) self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_encoder_layers) # 输出线性变换层 self.fc_out = nn.Linear(model_dim, 1) def forward(self, src): # 对输入进行初步处理并送入编码器 src = self.fc_in(src) * math.sqrt(self.model_dim) # 应用位置编码 (假设已实现PositionalEncoding函数) # src = positional_encoding(src) # 经过编码器处理后的输出 output = self.transformer_encoder(src) # 返回最终结果前再做一次线性变换 return self.fc_out(output).squeeze(-1) ``` 此代码片段展示了如何利用PyTorch库创建一个简单Transformer架构实例[^2]。值得注意的是,在实际应用中还需要考虑加入位置编码(position encoding),因为原始论文指出绝对位置信息对于序列任务非常重要[^5]。 为了使上述代码更加完善,可以引入位置编码功能: ```python def get_positional_encoding(max_len, d_model): pe = torch.zeros(max_len, d_model) position = torch.arange(0., max_len).unsqueeze(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) return pe.unsqueeze(0) class PositionalEncoding(nn.Module): def __init__(self, d_model, dropout=0.1, max_len=5000): super(PositionalEncoding, self).__init__() self.dropout = nn.Dropout(p=dropout) self.pe = get_positional_encoding(max_len, d_model) def forward(self, x): x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False) return self.dropout(x) ``` 通过这种方式,可以在保持原有逻辑不变的情况下增强模型的表现力和泛化能力[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值