Transformer

Transformer

Transformer总体架构

Transformer是一种基于自注意力机制的深度学习模型,主要用于自然语言处理任务,如机器翻译、文本生成等。它由以下几个主要部分组成:

  1. 输入部分:包括源文本和目标文本的嵌入层和位置编码
  2. 编码器部分:由多个编码器层堆叠而成,每层包含多头自注意力机制和前馈神经网络
  3. 解码器部分:由多个解码器层堆叠而成,每层包含多头自注意力机制、编码器-解码器注意力和前馈神经网络
  4. 输出部分:包括线性层和Softmax层

4

输入部分

输入部分包括源文本嵌入层及其位置编码器和目标文本嵌入层及其位置编码器。

5

文本嵌入层

文本嵌入层的作用是将文本中词汇的数字表示(如索引)转换为向量表示。无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示,希望在这样的高维空间捕捉词汇间的关系。

class Embeddings(nn.Module):
    def __init__(self, vocab_size, embedding_size):
        super().__init__()
        self.vocab_size = vocab_size
        self.embedding_size = embedding_size
        self.embedding = nn.Embedding(vocab_size, embedding_size)

    def forward(self, x):
        # x shape: (batch_size, seq_len) -> [2, 4]
        # embed shape: (batch_size, seq_len, embedding_size) -> [2, 4, 512]
        embed = self.embedding(x)
        # 1.符合标准正态分布  2.增强embedding的影响
        return embed * math.sqrt(self.embedding_size)

位置编码器

因为在Transformer的编码器结构中,并没有针对词汇位置信息的处理,因此需要在嵌入层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中,以弥补位置信息的缺失。

位置编码使用正弦和余弦函数生成:

\[PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

\[PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

其中:

  • \(pos\) 是位置索引
  • \(i\) 是维度索引
  • \(d_{\text{model}}\) 是模型维度(词嵌入维度)
# 加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
class PositionalEncoding(nn.Module):
    def __init__(self, embedding_size, dropout_rate, max_len=5000):
        super().__init__()
        self.embedding_size = embedding_size
        self.dropout = nn.Dropout(dropout_rate)

        # 位置编码
        pe = torch.zeros(max_len, embedding_size)
        # 位置列矩阵 eg:[max_len, 1]
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        # 变化矩阵 eg: [1, 256]
        div_term = torch.exp(torch.arange(0, embedding_size, 2).float() * (-math.log(10000.0) / embedding_size))

        # [max_len, 1] @ [1, 256] = [max_len, 256]
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        pe = pe.unsqueeze(0) # [max_len, 256] -> [1, max_len, 256]

        # 把pe位置编码矩阵 注册成模型的持久缓冲区buffer; 模型保存再加载时,可以根模型参数一样,一同被加载
        # 什么是buffer: 对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不参与模型训练
        self.register_buffer('pe', pe)

    def forward(self, x):
        # x shape: [batch_size, seq_len, embedding_size] -> [2, 4, 512]
        # pe shape: [1, max_len, embedding_size] -> [1, 5000, 512]
        x = x + self.pe[:, :x.size(1)] # [2, 4, 512] + [1, 4, 512] = [2, 4, 512]
        return self.dropout(x)

将位置编码与词嵌入相加得到最终的输入表示:

\[\text{Input} = \text{Embedding}(x) + PE(pos) \]

编码器部分

编码器部分由N个编码器层堆叠而成,每个编码器层由两个子层连接结构组成:

  1. 第一个子层连接结构:包括一个多头自注意力子层和规范化层以及一个残差连接
  2. 第二个子层连接结构:包括一个前馈全连接子层和规范化层以及一个残差连接

7

注意力机制

Transformer中使用的注意力计算规则是缩放点积注意力:

\[\text{Attention}(Q, K, V) = \text{Softmax}\left(\frac{Q \cdot K^T}{\sqrt{d_k}}\right) \cdot V \]

其中:

  • \(Q\) 是查询矩阵
  • \(K\) 是键矩阵
  • \(V\) 是值矩阵
  • \(d_k\) 是键向量的维度(用于缩放点积,防止梯度消失)
# todo 生成下三角矩阵
def sub_mask(size):
    attn_shape = torch.ones((1, size, size), dtype=torch.long)
    mask = torch.triu(attn_shape, diagonal=1).type(torch.uint8)
    return mask == 0

# todo 注意力
def attention(query, key, value, mask=None, dropout=None):
    # 自注意力机制 query, key, value 输入的维度都是 [batch_size, seq_len, embedding_size]]->[2, 4, 512]
    # 编码器端padding_mask 解码器端sentence_mask

    d_k = query.size()[-1] # 词嵌入维度:512
    # attention公式:score = query * key.T / sqrt(d_k)
    score = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

    if mask is not None: # mask = 0 -> score = -1e9
        score = score.masked_fill(mask == 0, -1e9)

    attention_weights = F.softmax(score, dim=-1)

    if dropout is not None:
        attention_weights = dropout(attention_weights)

    # 返回 attention(Q,K,V)输出和注意力权重
    return torch.matmul(attention_weights, value), attention_weights

多头注意力机制

多头注意力机制通过多个注意力头并行计算注意力,然后将结果拼接起来,再通过一个线性变换得到最终输出。

13

多头注意力机制的作用

这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以提升模型效果。

'''
    要求:module 必须是一个“干净的、未参与计算图的 Module”
    clones() 只能复制「未 forward 的 layer」
'''
def clones(module, n):
    return nn.ModuleList([copy.deepcopy(module) for _ in range(n)])

# todo 多头注意力
class MultiHeadAttention(nn.Module):
    def __init__(self, head, embedding_size, dropout=0.1):
        super(MultiHeadAttention, self).__init__()

        assert embedding_size % head == 0 # 确保词嵌入维度能被头数整除

        self.head = head # 头数
        self.embedding_size = embedding_size
        self.d_k = embedding_size // head # 词嵌入维度
        self.linear_layers = clones(nn.Linear(embedding_size, embedding_size), n = 4) # 4 个线性层

        self.attention_weights = None # 注意力权重
        self.dropout = nn.Dropout(dropout)

    def forward(self, query, key, value, mask=None):
        # query, key, value = [batch_size, seq_len, embedding_size]]->[2, 4, 512]
        if mask is not None:
            mask = mask.unsqueeze(0) # [8, 4, 4] -> [1, 8, 4, 4]

        batch_size = query.size(0)

        # [batch_size, seq_len, embedding_size]]->[2, 4, 512]->[2, 4, 8, 64]->[2, 8, 4, 64]
        # 让句子长度4和句子特征64靠在一起 更有利捕捉句子特征
        # (Linear1, query), (Linear2, key), (Linear3, value)
        query, key, value = [l(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2)
                             for l, x in zip(self.linear_layers, (query, key, value))]

        # myoutptlist_data = []
        # for model, x in zip(self.linears, (query, key, value)):
        #     print('x--->', x.shape) # [2,4,512]
        #     myoutput = model(x)
        #     print('myoutput--->',  myoutput.shape)  # [2,4,512]
        #     # [2,4,512] --> [2,4,8,64] --> [2,8,4,64]
        #     tmpmyoutput = myoutput.view(batch_size, -1,  self.head, self.d_k).transpose(1, 2)
        #     myoutptlist_data.append( tmpmyoutput )
        # mylen = len(myoutptlist_data)   # mylen:3
        # query = myoutptlist_data[0]     # [2,8,4,64]
        # key = myoutptlist_data[1]       # [2,8,4,64]
        # value = myoutptlist_data[2]     # [2,8,4,64]

        # attention_weights -> [batch_size, head, seq_len, seq_len] -> [2, 8, 4, 4]
        output, self.attention_weights = attention(query, key, value, mask, self.dropout)

        # [batch_size, head, seq_len, seq_len] -> [2, 4, 8, 64] -> [2, 4, 512]
        output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.embedding_size)

        output = self.linear_layers[-1](output)
        return output

前馈全连接层

在Transformer中,前馈全连接层是具有两层线性层的全连接网络,中间使用ReLU激活函数。

# todo 前馈全连接层
# 作用: * 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
class FeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1): # d_model: 词嵌入维度,d_ff: 前馈全连接层内部特征维度
        super(FeedForward, self).__init__()
        self.linear_1 = nn.Linear(d_model, d_ff)
        self.linear_2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)
        self.relu = nn.ReLU()

    def forward(self, x):
        # x 来自于 编码器的多头注意力层
        return self.linear_2(self.dropout(self.relu(self.linear_1(x))))

前馈全连接层的作用

考虑注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来增强模型的能力。

规范化层

规范化层是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常慢。因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内。

Transformer中使用的是层规范化(Layer Normalization):

\[\text{LayerNorm}(x) = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]

其中:

  • \(\mu\) 是均值
  • \(\sigma\) 是标准差
  • \(\gamma\)\(\beta\) 是可学习的参数
  • \(\epsilon\) 是防止除零的小常数
# todo 规范化层
class LayerNorm(nn.Module): # 针对同一样本,不同特征进行规范化. 每个句子长度不一
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        # y = a_2 * (x - mean) / (std + eps) + b_2
        # 相当于 y = k * x + b 中的 k , b
        self.a_2 = nn.Parameter(torch.ones(features)) # Parameter 可学习参数
        self.b_2 = nn.Parameter(torch.zeros(features))
        self.eps = eps # 避免除零

    def forward(self, x):
        mean = x.mean(-1, keepdim=True) # 平均值
        std = x.std(-1, keepdim=True) # 标准差
        return self.a_2 * (x - mean) / (std + self.eps) + self.b_2 # * 是位置相乘,不是矩阵相乘

子层连接结构

在每个编码器层中,输入到每个子层以及规范化层的过程中,还使用了残差连接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构)。在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构。

子层连接结构的数学表示:

\[\text{SublayerOutput} = \text{LayerNorm}(x + \text{Sublayer}(x)) \]

其中:

  • \(x\) 是输入
  • \(\text{Sublayer}(x)\) 是子层的输出
# todo 子层连接结构
class SubLayerConnection(nn.Module):
    def __init__(self, size, dropout):
        super(SubLayerConnection, self).__init__()
        self.norm = LayerNorm(features=size)
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x, sublayer):
        # x shape: [batch_size, seq_len, embedding_size] -> [2, 4, 512]
        # sublayer(x) shape: [batch_size, seq_len, embedding_size] -> [2, 4, 512]
        return x + self.dropout(sublayer(self.norm(x)))

编码器层

作为编码器的组成单元,每个编码器层完成一次对输入的特征提取过程,即编码过程。

17

编码器层的计算过程:

  1. 多头自注意力子层:\(\text{MultiHeadAttention}(x, x, x)\)
  2. 残差连接和层规范化:\(\text{LayerNorm}(x + \text{MultiHeadAttention}(x, x, x))\)
  3. 前馈全连接层:\(\text{FFN}(\text{LayerNorm}(x + \text{MultiHeadAttention}(x, x, x)))\)
  4. 残差连接和层规范化:\(\text{LayerNorm}(\text{FFN}(\text{LayerNorm}(x + \text{MultiHeadAttention}(x, x, x))) + \text{FFN}(\text{LayerNorm}(x + \text{MultiHeadAttention}(x, x, x))))\)
# todo 编码器层
class EncoderLayer(nn.Module):
    def __init__(self, size, self_attn, feed_forward, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = self_attn # 多头注意力层
        self.feed_forward = feed_forward # 前馈全连接层
        self.sublayer = clones(SubLayerConnection(size, dropout), 2) # 创建两个子层连接层
        self.size = size
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask):
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        x = self.sublayer[1](x, self.feed_forward)
        return self.dropout(x)

编码器

编码器用于对输入进行指定的特征提取过程,也称为编码,由N个编码器层堆叠而成。

# todo 编码器
class Encoder(nn.Module):
    def __init__(self, layer, N):
        # layer: 1 个 编码器层
        # N: 编码器层数
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(features=layer.size) # 规范化层

    def forward(self, x, mask):
        # 数据经过 N 个编码器层
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x) # 返回规范化层后的结果

解码器部分

解码器部分由N个解码器层堆叠而成,每个解码器层由三个子层连接结构组成:

  1. 第一个子层连接结构:包括一个多头自注意力子层和规范化层以及一个残差连接
  2. 第二个子层连接结构:包括一个多头注意力子层和规范化层以及一个残差连接
  3. 第三个子层连接结构:包括一个前馈全连接子层和规范化层以及一个残差连接

8

解码器层中的各个部分,如多头注意力机制、规范化层、前馈全连接网络、子层连接结构都与编码器中的实现相同。

解码器层

作为解码器的组成单元,每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程。

解码器层的计算过程:

  1. 带掩码的多头自注意力子层:\(\text{MaskedMultiHeadAttention}(x, x, x)\)
  2. 残差连接和层规范化:\(\text{LayerNorm}(x + \text{MaskedMultiHeadAttention}(x, x, x))\)
  3. 多头注意力子层(编码器-解码器注意力):\(\text{MultiHeadAttention}(\text{上一层的输出}, \text{编码器的输出}, \text{编码器的输出})\)
  4. 残差连接和层规范化:\(\text{LayerNorm}(\text{上一层的输出} + \text{MultiHeadAttention}(\text{上一层的输出}, \text{编码器的输出}, \text{编码器的输出}))\)
  5. 前馈全连接层:\(\text{FFN}(\text{上一层的输出})\)
  6. 残差连接和层规范化:\(\text{LayerNorm}(\text{上一层的输出} + \text{FFN}(\text{上一层的输出}))\)
# todo 解码器层
class DecoderLayer(nn.Module):
    def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
        super(DecoderLayer, self).__init__()
        self.size = size # 词嵌入维度
        self.self_attn = self_attn # 自注意力机制 Q = K = V
        self.src_attn = src_attn # 一般注意力机制 Q != K == V
        self.feed_forward = feed_forward # 前馈全连接层
        self.sublayer = clones(SubLayerConnection(size, dropout), 3)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, memory, source_mask, target_mask):
        m = memory # 源句子
        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, source_mask))
        x = self.sublayer[2](x, self.feed_forward)
        return self.dropout(x)

解码器

根据编码器的结果以及上一次预测的结果,对下一次可能出现的"值"进行特征表示。

# todo 解码器
class Decoder(nn.Module):
    def __init__(self, layer, N):
        super(Decoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(features=layer.size)

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

输出部分

输出部分包括线性层和Softmax层。

6

线性层

线性层的作用是通过对上一步的线性变换得到指定维度的输出,也就是转换维度的作用。

数学公式:

\[\text{Linear}(x) = xW + b \]

其中:

  • \(W\) 是权重矩阵
  • \(b\) 是偏置项

Softmax层

Softmax层的作用是使最后一维的向量中的数字缩放到0-1的概率值域内,并满足它们的和为1。

数学公式:

\[\text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{n} e^{x_j}} \]

其中:

  • \(x_i\) 是输入向量的第\(i\)个元素
  • \(n\) 是词汇表大小
class Generator(nn.Module):
    def __init__(self, d_model, vocab):
        """
        d_model: 词嵌入维度
        vocab: 词表大小
        """
        super(Generator, self).__init__()
        self.proj = nn.Linear(d_model, vocab)

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

模型构建

class EncoderDecoder(nn.Module):
    def __init__(self, encoder, decoder, source_embed, target_embed, generator):
        super(EncoderDecoder, self).__init__()
        self.encoder = encoder # 编码器
        self.decoder = decoder # 解码器
        self.source_embed = source_embed # 源句子嵌入
        self.target_embed = target_embed # 目标句子嵌入
        self.generator = generator # 生成器

    def forward(self, src, tgt, src_mask, tgt_mask):
        return self.generator(self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask))

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

    def decode(self, memory, src_mask, tgt, tgt_mask):
        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)


def test_transformer():
    """
    完整的 Transformer 模型测试函数
    """
    # 1. 创建编码器
    from encoder_demo import Encoder, EncoderLayer, MultiHeadAttention, FeedForward, LayerNorm
    from input_demo import Embeddings, PositionalEncoding

    # 创建多头注意力和前馈网络
    multi_head_attn = MultiHeadAttention(head=8, embedding_size=512)
    feed_forward = FeedForward(d_model=512, d_ff=2048)

    # 创建编码器层
    encoder_layer = EncoderLayer(
        size=512,
        self_attn=multi_head_attn,
        feed_forward=feed_forward,
        dropout=0.1
    )

    # 创建编码器(6层)
    encoder = Encoder(layer=encoder_layer, N=6)

    # 2. 创建解码器
    from decoder_demo import Decoder, DecoderLayer  # 假设有这些模块

    # 创建解码器层(需要自注意力、源注意力和前馈网络)
    self_attn = MultiHeadAttention(head=8, embedding_size=512)
    src_attn = MultiHeadAttention(head=8, embedding_size=512)
    feed_forward_dec = FeedForward(d_model=512, d_ff=2048)

    decoder_layer = DecoderLayer(
        size=512,
        self_attn=self_attn,
        src_attn=src_attn,
        feed_forward=feed_forward_dec,
        dropout=0.1
    )

    decoder = Decoder(layer=decoder_layer, N=6)

    # 3. 创建嵌入层
    src_embed = nn.Sequential(
        Embeddings(vocab_size=1000, embedding_size=512),
        PositionalEncoding(embedding_size=512, dropout_rate=0.1)
    )

    tgt_embed = nn.Sequential(
        Embeddings(vocab_size=1000, embedding_size=512),
        PositionalEncoding(embedding_size=512, dropout_rate=0.1)
    )

    # 4. 创建生成器
    generator = Generator(d_model=512, vocab=1000)

    # 5. 创建完整的 EncoderDecoder 模型
    model = EncoderDecoder(
        encoder=encoder,
        decoder=decoder,
        source_embed=src_embed,
        target_embed=tgt_embed,
        generator=generator
    )

    # 6. 准备测试数据
    batch_size = 2
    src_seq_len = 10
    tgt_seq_len = 8

    # 源序列和目标序列(词汇表索引)
    src = torch.randint(low=1, high=1000, size=(batch_size, src_seq_len))
    tgt = torch.randint(low=1, high=1000, size=(batch_size, tgt_seq_len))

    # 创建掩码
    src_mask = torch.ones(batch_size, 1, src_seq_len).type(torch.uint8)  # 源序列掩码
    tgt_mask = sub_mask(tgt_seq_len)  # 目标序列后续掩码

    # 7. 执行前向传播
    print("开始测试 Transformer 模型...")
    print(f"输入源序列形状: {src.shape}")
    print(f"输入目标序列形状: {tgt.shape}")
    print(f"源掩码形状: {src_mask.shape}")
    print(f"目标掩码形状: {tgt_mask.shape}")

    try:
        output = model(src, tgt, src_mask, tgt_mask)
        print(f"输出形状: {output.shape}")
        print("Transformer 模型测试成功!")
        return model, output
    except Exception as e:
        print(f"测试过程中出现错误: {e}")
        return None, None

Transformer的特点和优势

  1. 并行计算:与RNN不同,Transformer可以并行处理整个序列,大大提高了训练速度。
  2. 长距离依赖:自注意力机制能够捕捉序列中任意两个位置之间的依赖关系,不受距离限制。
  3. 可解释性:注意力权重可以可视化,帮助我们理解模型在做出决策时关注了输入的哪些部分。
  4. 扩展性:Transformer架构可以很容易地扩展到更大的模型和数据集。
posted @ 2026-01-30 00:11  xggx  阅读(4)  评论(0)    收藏  举报