自学内容网 自学内容网

Transformer

摘要

  本周,我学习了Transformer模型,了解了它的编码器和解码器结构。

Abstract

  This week, I studied the Transformer model and learned about its encoder and decoder structure.

1. 自注意力

  自注意力是通过对序列中的每个位置与所有位置进行对比计算来获得特征表示。在自注意力机制中,输入的查询( Q Q Q)、键( K K K)和值( V V V)都来自同一个输入序列。
  优点:自注意力可以捕捉序列中所有位置之间的关系,使每个位置的信息与其他所有位置相连,适合长距离依赖的场景;自注意力不需要依赖前后顺序进行计算,因此可以在GPU上高度并行化,尤其适用于大规模训练;它根据输入内容动态生成权重,对不同内容灵活建模,因此在NLP和计算机视觉任务中效果显著。
  缺点:计算和内存需求大:自注意力需要计算序列中每对位置的关系,对长序列尤其不友好;由于自注意力的全局特性,位置信息在其中没有天然保留,因此需要额外的机制(如位置编码)来保留位置信息;自注意力对所有位置一视同仁,可能对局部特征较弱的场景(例如传统的卷积处理)不如卷积操作有效。

2. 位置编码

  自注意力对序列并行计算,没有考虑序列的前后顺序,因此需要位置编码来保留位置信息。位置编码可以是手动设计的或者可学习的。
  输入 X X X的形状为 s e q _ l e n × e m b e d d i n g _ d i m seq\_len\times embedding\_dim seq_len×embedding_dim i i i是行索引, 2 j 2j 2j 2 j + 1 2j+1 2j+1是列索引,Transformer中的位置编码公式为
p i , 2 j = s i n ( i 1000 0 2 j / d ) p i , 2 j + 1 = c o s ( i 1000 0 2 j / d ) \begin{aligned} \displaystyle \Large p_{i,2j}&=\Large sin(\frac{i}{10000^{2j/d}})\\ \displaystyle \Large p_{i,2j+1}&=\Large cos(\frac{i}{10000^{2j/d}})\\ \end{aligned} pi,2jpi,2j+1=sin(100002j/di)=cos(100002j/di)
  Transformer中位置编码层的PyTorch实现如下。

import torch
import torch.nn as nn

class PositionEmbedding(nn.Module):
    def __init__(self, seq_len, num_hidden, dropout):
        super().__init__()
        self.dropout = nn.Dropout(dropout)
        self.p = torch.zeros(1, seq_len, num_hidden)
        e = torch.arange(seq_len).reshape(-1, 1) / torch.pow(10000, torch.arange(num_hidden) // 2 * 2 / num_hidden)
        self.p[0, :, 0::2] = torch.sin(e[:, 0::2])
        self.p[0, :, 1::2] = torch.cos(e[:, 0::2])
    
    def forward(self, x):
        return self.dropout(x + self.p.to(x.device))

3. 交叉注意力

  交叉注意力用于在不同输入序列之间进行信息交互。与自注意力不同,交叉注意力的查询( Q Q Q)来自一个序列,而键 K K K和值 V V V来自另一个序列。这种机制允许模型在生成输出时关注到来自另一序列的特征。
  优点:交叉注意力用于不同输入的交互,可以灵活地在不同输入之间建立关联;交叉注意力在解码过程中能灵活地获取编码器输出,适用于序列生成、语言翻译和图像描述生成等任务。
  缺点:交叉注意力通常在解码过程中使用,结构较复杂,相比自注意力更难调试和优化;交叉注意力依赖不同输入的对齐质量,若编码器输出信息不足或偏差较大,解码阶段的生成质量也会受影响。尤其在多层次交互时,计算量可能较高,需在编码器和解码器间反复计算多层交叉注意力。

4. 前馈网络层(FFN)

  FFN的公式为 F F N ( X ) = m a x ( 0 , X W 1 + b 1 ) W 2 + b 2 FFN(X)=max(0, XW_1+b_1)W_2+b_2 FFN(X)=max(0,XW1+b1)W2+b2。自注意力机制擅长捕获序列中各个位置之间的全局依赖关系,而 FFN 则通过独立处理每个位置的表示,捕获更细粒度的局部特征。
  Transformer中FFN的PyTorch实现如下。

class FFN(nn.Module):
    def __init__(self, ffn_num_hidden, ffn_num_output):
        super().__init__()
        self.s = nn.Sequential(
            nn.LazyLinear(ffn_num_hidden),
            nn.ReLU(),
            nn.LazyLinear(ffn_num_output)
        )
        
    def forward(self, x):
        return self.s(x)

5. 残差连接和层归一化(Add&Norm)

  Add&Norm的计算过程为 Y = L a y N o r m ( X + S u b L a y e r ( X ) ) Y=LayNorm(X+SubLayer(X)) Y=LayNorm(X+SubLayer(X))。残差链接能帮助信息直接跳过子层,缓解梯度消失和网络退化问题,提升模型训练的稳定性。层归一化能标准化子层输出,提高训练稳定性和收敛速度。
在这里插入图片描述
  Transformer中Add&Norm的PyTorch实现如下。

class AddNorm(nn.Module):
    def __init__(self, norm_shape, dropout):
        super().__init__()
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(norm_shape)
        
    def forward(self, x, y):
        return self.ln(self.dropout(y) + x)

4. 编码器

  编码器层的结构如下。编码器由嵌入层、位置编码层和 n n n个编码器块构成。编码器将输入序列(例如单词或字符序列)转换为嵌入向量;将位置编码与嵌入向量相加,结合位置信息,形成输入序列的初始表示;对输入序列进行自注意力计算,捕获序列中每个标记与其他标记之间的依赖关系;进一步处理自注意力计算得到的表示向量,进行非线性变换;编码器层输出上下文丰富的表示向量序列,作为下一层编码器层或解码器层的输入。
在这里插入图片描述
  Transformer中编码器层的PyTorch实现如下。

class TransformerEncoderBlock(nn.Module):
    def __init__(self,num_hidden, num_head, ffn_num_hidden, dropout):
        super().__init__()
        self.attention = MultiHeadAttention(num_hidden, num_hidden, num_hidden, \
                                            num_hidden, num_head, dropout)
        self.addnorm1 = AddNorm(num_hidden, dropout)
        self.ffn = FFN(ffn_num_hidden, num_hidden)
        self.addnorm2 = AddNorm(num_hidden, dropout)
        
    def forward(self, x, mask):
        y = self.addnorm1(x, self.attention(x, x, x, mask))
        return self.addnorm2(y, self.ffn(y))

  Transformer中编码器的PyTorch实现如下。

import math

class TransformerEncoder(nn.Module):
    def __init__(self, vocab_size, num_hidden, ffn_num_hidden, num_head, num_block, dropout):
        super().__init__()
        self.num_hidden = num_hidden
        self.embedding = nn.Embedding(vocab_size, num_hidden)
        self.positionembedding = PositionEmbedding(num_hidden, dropout)
        self.blocks = nn.Sequential()
        for i in range(num_block):
            self.blocks.add_module("block_" + str(i), \
                                   TransformerEncoderBlock(num_hidden, num_head, ffn_num_hidden, dropout))
            
    def forward(self, x, mask):
        x = self.embedding(x) * math.sqrt(self.num_hidden)
        x = self.positionembedding(x)
        for _, block in enumerate(self.blocks):
            x = block(x, mask)
        return x

5. 解码器

  解码器层的结构如下。解码器由嵌入层、位置编码层、 n n n个解码器层、线性层和Softmax层构成。目标序列的输入首先通过嵌入层转换为向量表示,并添加位置编码;对输入序列进行带因果掩码的自注意力计算,避免信息泄露;结合编码器的上下文信息,将源序列和目标序列的依赖关系通过交叉注意力计算融入表示向量中;解码器对表示向量进行非线性变换,增强模型的特征表达能力,输出的表示向量序列作为下一层解码器层的输入。最终经过 n n n个解码器层的输出经过线性层和 softmax 层,生成目标词汇表的概率分布,选择概率最高的词汇作为输出。
在这里插入图片描述
  Transformer中解码器层的PyTorch实现如下。

class TransformerDecoderBlock(nn.Module):
    def __init__(self, num_hidden, num_head, ffn_num_hidden, dropout, i):
        super().__init__()
        self.i = i
        self.attention1 = MultiHeadAttention(num_hidden, num_hidden, num_hidden, \
                                             num_hidden, num_head, dropout)
        self.addnorm1 = AddNorm(num_hidden, dropout)
        self.attention2 = MultiHeadAttention(num_hidden, num_hidden, num_hidden, \
                                             num_hidden, num_head, dropout)
        self.addnorm2 = AddNorm(num_hidden, dropout)
        self.ffn = FFN(ffn_num_hidden, num_hidden)
        self.addnorm3 = AddNorm(num_hidden, dropout)
        
    def forward(self, x, state):
        # state:[encoderblock_output, encoder_mask, [None]*num_decoderblock]
        encoderblock_output, encoder_mask = state[0], state[1]
        if state[2][self.i] is None:
            key_value = x
        else:
            key_value = torch.cat((state[2][self.i], x), dim=1)
        state[2][self.i] = key_value
        if self.training:
            decoder_mask = torch.triu(torch.ones(1, x.shape[1], x.shape[1]), diagonal=1).bool()
        else:
            decoder_mask = None
        y = self.addnorm1(x, self.attention1(x, key_value, key_value, decoder_mask))
        y = self.addnorm2(y, self.attention2(y, encoderblock_output, encoderblock_output, encoder_mask))
        y = self.addnorm3(y, self.ffn(y))
        return y, state

  Transformer中解码器的PyTorch实现如下。

class TransformerDecoder(nn.Module):
    def __init__(self, vocab_size, num_hidden, ffn_num_hidden, num_head, num_block, dropout):
        super().__init__()
        self.num_hidden = num_hidden
        self.embedding = nn.Embedding(vocab_size, num_hidden)
        self.positionembedding = PositionEmbedding(num_hidden, dropout)
        self.blocks = nn.Sequential()
        for i in range(num_block):
            self.blocks.add_module("block_" + str(i), TransformerDecoderBlock(num_hidden, num_head, ffn_num_hidden, dropout, i))
        self.f = nn.LazyLinear(vocab_size)
        
    def forward(self, x, state):
        y = self.positionembedding(self.embedding(x) * math.sqrt(self.num_hidden))
        for _, block in enumerate(self.blocks):
            y, state = block(y, state)
        return self.f(y), state

参考

Dive into deep learning 11.7 The Transformer Architecture
Ashish Vaswani, Noam Shazeer, Niki Parmar and et.al. Attention Is All You Need.

总结

  本周,我学习了Transformer模型。
  下周,我将学习神经网络中的正则化。


原文地址:https://blog.csdn.net/qq_51180928/article/details/143455488

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!