pytorch-Transformer

Transformer

参考文章

介绍

结构

image-20250930114133235

transformer

论文的原始结构,此处 N=6

transformer是一篇划时代的论文,大模型力大砖飞的典范

NLP问题划时代的解决方案

模块介绍

词嵌入 (Word Embedding)

实现

"""
将输入的离散词索引转换为连续的向量表示
例如,将词汇表中的第5个词映射为一个512维的向量
"""
class Embeddings(nn.Module):
    def __init__(self, d_model, vocab):
        # 初始化方法,传入模型的维度(d_model)和词汇表的大小(vocab)
        super(Embeddings, self).__init__()
        # Embedding层,将词汇表的大小映射为d_model维的向量
        self.lut = nn.Embedding(vocab, d_model)
        # 存储模型的维度 d_model
        self.d_model = d_model

    def forward(self, x):
        # 返回x对应的embedding矩阵(需要乘以math.sqrt(d_model))
        # 这是为了保持词向量的方差,使其适应后续层的训练。
        return self.lut(x) * math.sqrt(self.d_model)

位置编码 (Positional Encoding)

原版Transfomer使用的为: 正余弦位置编码

位置编码公式

目的是将词向量矩阵中加入一些位置信息

$$
\begin{cases}
PE(pos, 2i) = \sin!\left(\frac{pos}{10000^{\frac{2i}{d_{\text{d}}}}}\right)
\
PE(pos, 2i+1) = \cos!\left(\frac{pos}{10000^{\frac{2i}{d_{\text{d}}}}}\right)
\end{cases}
\
\text{d:词嵌入矩阵大小}
\
i=0,1,…,\frac{d}{2}-1 \space\space pos指当前token在序列中的第几个位置
$$

其目的是将上下文增加位置信息,让模型识别到

最终经过这两层后获取的数据为:
$$
encoded\space patches = patch\space embedding + positional\space embedding
$$

实现

"""
为每个输入位置添加一个唯一的位置编码
这个编码会被添加到词向量中,使模型能够理解位置信息
"""
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)

        # 初始化一个size为 max_len(设定的最大长度)×embedding维度 的全零矩阵
        # 来存放所有小于这个长度位置对应的positional embedding
        pe = torch.zeros(max_len, d_model, device=DEVICE)
        # 生成一个位置下标的tensor矩阵(每一行都是一个位置下标)
        position = torch.arange(0., max_len, device=DEVICE).unsqueeze(1)
        # 这里幂运算太多,我们使用exp和log来转换实现公式中pos下面要除以的分母(由于是分母,要注意带负号)
        div_term = torch.exp(torch.arange(0., d_model, 2, device=DEVICE) * -(math.log(10000.0) / d_model))

        # 根据公式,计算各个位置在各embedding维度上的位置纹理值,存放到pe矩阵中
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        # 加1个维度,使得pe维度变为:1×max_len×embedding维度
        # (方便后续与一个batch的句子所有词的embedding批量相加)
        pe = pe.unsqueeze(0)
        # 将pe矩阵以持久的buffer状态存下(不会作为要训练的参数)
        self.register_buffer('pe', pe)

    def forward(self, x):
        # 将一个batch的句子所有词的embedding与已构建好的positional embeding相加
        # (这里按照该批次数据的最大句子长度来取对应需要的那些positional embedding值)
        x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
        return self.dropout(x)

自注意力机制 (Self-Attention)

注意力 Attention

$$
\begin{equation}
\text{Attention}(Q, K, V)
= \mathrm{softmax}!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V
\end{equation}
$$

多头注意力机制 (Multi-Head Attention)


多头注意力机制就是 多个平行的自注意力机制

炮哥详解_多头注意力机制

$$
\begin{equation}
\text{MultiHead}(Q,K,V)
= \mathrm{Concat}(h_1, \ldots, h_h)W^O
\end{equation}
$$

$$
\begin{equation}
h_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)
\end{equation}
$$

concat 操作

掩码注意力机制 (Masked Self-Attention)

交叉注意力机制 (Cross-Attention)

交叉注意力机制

交叉注意力机制-矩阵角度

Q,K,V来自不同的结构,

Q,K来自解码器

V来自编码器

层归一化 (Layer Normalization)

归一化操作分成了

  • 批归一化 ( Batch Normalization, BN )
  • 层归一化 ( Layer Normalization, LN )
项目 批归一化 BN 层归一化 LN
归一化维度 在 batch 维度上做归一化 在特征维度(每个样本)上做归一化
是否依赖 batch size 是(需要较大 batch 才稳定) 否(batch size = 1 也行)
训练 / 推理一致性 训练与推理行为不同(使用 moving mean/var) 训练与推理行为完全一致
主要使用场景 CNN 卷积网络 RNN / Transformer / NLP
对序列模型 效果不好 效果极佳
是否对通道/特征进行逐样本归一化

批归一化

层归一化操作

$$
\begin{equation}
\mathrm{LayerNorm}(x)
= \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta
\end{equation}
$$

自归一化 加残差连接
$$
\begin{equation}
\mathrm{Output} = \mathrm{LayerNorm}\big(x + \mathrm{Sublayer}(x)\big)
\end{equation}
$$

总结

方法 均值 / 方差计算维度 归一化公式核心
BN 跨 batch,对每个特征单独做 $$((x - \mu_\text{batch}) / \sigma_\text{batch})$$
LN 单个样本内部的所有特征 $$((x - \mu_\text{layer}) / \sigma_\text{layer})$$

从矩阵的角度

变体

位置编码的改进

原版使用的是正余弦位置编码

1. 可学习的位置编码(Learned Positional Embedding)

2. 相对位置编码(Relative Positional Encoding)

旋转位置编码(Rotary Positional Embedding, RoPE)

Attention的改进

掩码注意力机制改进

github