Transformer模型详解:从零开始,手把手带你构建NLP的“万能钥匙”(附PyTorch代码)


摘要: 告别RNN的序列依赖,拥抱并行化的注意力机制。本文将通过图解和PyTorch代码,带你一步步拆解Transformer的核心结构,让你彻底搞懂让BERT和GPT如此强大的“幕后功臣”。


大家好,我是[你的博客名]。

如果你关注人工智能,你一定听过GPT-4、BERT这些如雷贯耳的名字。它们在自然语言处理(NLP)领域掀起了革命,而这一切的基石,都源于一个在2017年横空出世的模型——Transformer

很多朋友可能对RNN或LSTM有所了解,但一提到Transformer就觉得它复杂、难懂。别担心!这篇文章的使命,就是带你拨开云雾,从最核心的**自注意力机制(Self-Attention)**开始,结合图解和代码,让你不仅能看懂,更能亲手实现一个Transformer。

准备好了吗?让我们开始这场探索之旅!

一、为什么需要Transformer?RNN的“天花板”

在Transformer出现之前,RNN(循环神经网络)及其变体(如LSTM、GRU)是处理序列数据的王者。它们通过一个循环结构,依次处理序列中的每个元素,并将信息从前一步传递到后一步。
在这里插入图片描述

(图注:RNN/LSTM通过循环传递信息,擅长处理序列数据)

但这种结构有两个致命的“天花板”:

  1. 无法并行计算: 当前时间步的计算依赖于上一步的结果,这使得模型在长序列上训练得非常慢。
  2. 长距离依赖问题: 理论上LSTM可以捕捉长距离依赖,但实际上,信息在经过多个时间步的传递后,会逐渐丢失或“稀释”,导致模型难以关联距离很远的词语。

为了打破这些限制,Google的研究者们在论文《Attention Is All You Need》中提出了一个全新的架构——Transformer,它完全抛弃了循环结构,完全依赖于一种叫做“注意力”的机制。

二、Transformer架构概览:一个“积木城堡”

首先,我们来看一下Transformer的宏观结构。它本质上是一个**Encoder-Decoder(编码器-解码器)**架构。

在这里插入图片描述

(图注:Transformer由编码器(左侧)和解码器(右侧)堆叠而成)

  • 编码器(Encoder): 负责“理解”输入序列。它将输入的单词序列(比如一个英文句子)转换成一系列富含上下文信息的数字表示(向量)。
  • 解码器(Decoder): 负责“生成”输出序列。它利用编码器的输出和已经生成的部分结果,来预测下一个单词(比如生成法文翻译)。

编码器和解码器都不是单一的模块,而是由N个相同的层堆叠而成(原论文中N=6)。接下来,我们将把这些“积木”一块块拆开看。

三、核心构件1:输入处理与位置编码

计算机不认识单词,只认识数字。所以第一步,我们需要将单词转换成向量。

1. 词嵌入(Word Embedding)
这一步很简单,我们通过一个嵌入层(Embedding Layer)将每个单词映射到一个固定维度的向量。

2. 位置编码(Positional Encoding)
Transformer没有RNN的循环结构,它一次性“看到”所有单词。这就带来一个问题:它无法感知单词的顺序。例如,“我打你”和“你打我”在它看来可能是一样的。

为了解决这个问题,研究者引入了位置编码(Positional Encoding)。这是一种特殊设计的向量,它包含了单词在句子中的位置信息。这个位置编码会直接加到词嵌入向量上。

在这里插入图片描述

(图注:将单词的嵌入向量与它的位置编码向量相加)

它使用不同频率的正弦和余弦函数来生成,公式如下:
P E ( p o s , 2 i ) = sin ⁡ ( p o s / 1000 0 2 i / d model ) PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{\text{model}}}) PE(pos,2i)=sin(pos/100002i/dmodel)
P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s / 1000 0 2 i / d model ) PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{\text{model}}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)
其中 pos 是单词的位置,i 是向量的维度索引。这种设计的好处是,模型可以学习到单词之间的相对位置关系。

PyTorch代码实现:

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))
        
        # 使用sin和cos函数填充
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        
        # 增加一个batch维度,并注册为buffer(不参与梯度更新)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer('pe', pe)

    def forward(self, x):
        # x的维度是 [seq_len, batch_size, d_model]
        # 将位置编码加到输入向量上
        x = x + self.pe[:x.size(0), :]
        return x

四、灵魂所在:自注意力机制(Self-Attention)

这是Transformer最核心、最天才的部分!

直观理解: 当我们阅读“The animal didn’t cross the street because it was too tired”这句话时,我们的大脑会自动将“it”和“The animal”关联起来。自注意力机制就是模拟这个过程,让模型在处理一个单词时,能够“关注”到句子中其他所有单词,并计算出每个单词对于当前单词的重要性(即“注意力分数”)。

Q, K, V 的故事:
自注意力机制为每个输入向量创建了三个新的向量:Query (查询), Key (键), 和 Value (值)。这三个向量是通过将输入向量分别乘以三个独立的权重矩阵( W Q , W K , W V W_Q, W_K, W_V WQ,WK,WV)得到的。

  • Query (Q): 代表当前单词,可以理解为“我要查询谁和我关系最密切”。
  • Key (K): 代表句子中所有单词的“标签”,用来和Query进行匹配。
  • Value (V): 代表句子中所有单词的“内容”。

在这里插入图片描述

(图注:每个输入向量都生成Q, K, V三个向量)

计算过程(四步走):

  1. 计算分数(Score): 将你的Query向量与所有单词的Key向量进行点积。这个分数代表了Query和每个Key的相似度或相关性。
    Score = Q · K^T

  2. 缩放(Scale): 将分数除以一个缩放因子(通常是Key向量维度的平方根 d k \sqrt{d_k} dk )。这可以防止在维度过高时点积结果过大,导致梯度消失。
    Scaled_Score = Score / sqrt(d_k)

  3. Softmax: 对缩放后的分数进行Softmax操作,将其转换为0到1之间的概率分布。这些就是“注意力权重”,表示每个单词对当前单词的贡献度。

  4. 加权求和(Weighted Sum): 将注意力权重与每个单词的Value向量相乘,然后求和。这样,与当前单词关系越密切的单词(注意力权重越高),其Value向量在最终结果中的占比就越大。

在这里插入图片描述

(图注:自注意力的完整计算流程,也是Transformer的核心公式)

五、强力升级:多头注意力(Multi-Head Attention)

一次自注意力只让模型从一个“角度”去看待单词间的关系。但实际上,单词间的关系是多样的(比如语法关系、语义关系等)。

**多头注意力(Multi-Head Attention)**就是让模型拥有多个“头”(独立的Q, K, V权重矩阵),从不同角度并行地进行自注意力计算。
在这里插入图片描述

(图注:将Q, K, V拆分成多个头,并行计算,最后拼接)

它的过程如下:

  1. 将原始的Q, K, V矩阵在特征维度上分割成h个小块(h是头的数量)。
  2. 每个“头”独立地执行上面描述的自注意力计算,得到各自的输出。
  3. 将所有头的输出拼接(Concatenate)起来。
  4. 将拼接后的结果通过一个最终的线性层( W O W_O WO)进行变换,得到最终的输出。

这样做的好处是,它允许模型在不同的表示子空间中共同学习信息,极大地增强了模型的表达能力。

PyTorch代码实现 (Multi-Head Attention):

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0
        
        self.d_k = d_model // num_heads
        self.num_heads = num_heads
        
        self.linears = nn.ModuleList([nn.Linear(d_model, d_model) for _ in range(4)]) # W_q, W_k, W_v, W_o
        self.attn = None
        
    def forward(self, query, key, value, mask=None):
        # query, key, value: [batch_size, seq_len, d_model]
        if mask is not None:
            mask = mask.unsqueeze(1) # For broadcasting across heads
            
        batch_size = query.size(0)
        
        # 1. Linear projections & split into heads
        query, key, value = [l(x).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
                             for l, x in zip(self.linears, (query, key, value))]
        
        # 2. Apply attention on all the projected vectors in batch.
        # q, k, v are now [batch_size, num_heads, seq_len, d_k]
        scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(self.d_k)
        
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9) # Apply mask
            
        p_attn = F.softmax(scores, dim=-1)
        
        # 3. Apply attention to V and concatenate heads
        x = torch.matmul(p_attn, value)
        x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
        
        # 4. Final linear layer
        return self.linears[-1](x)

六、组装编码器和解码器

现在我们有了核心部件,可以组装一个完整的**编码器层(Encoder Layer)解码器层(Decoder Layer)**了。

编码器层有两个子层:

  1. 多头注意力层
  2. 一个简单的全连接前馈网络(Feed-Forward Network)

在每个子层之后,都跟着一个残差连接(Residual Connection)层归一化(Layer Normalization)。这被称为“Add & Norm”,是训练深度模型的关键技巧,能有效防止梯度消失和爆炸。
在这里插入图片描述

解码器层有三个子层:

  1. 带掩码的多头注意力(Masked Multi-Head Attention): 这是解码器特有的。在生成翻译时,模型只能看到已经生成的词,不能“偷看”未来的词。这个掩码(Mask)就是用来实现这一点的。
  2. 编码器-解码器注意力(Encoder-Decoder Attention): 它的Query来自上一个解码器子层,但Key和Value来自编码器的最终输出。这是连接编码器和解码器的桥梁,让解码器能够“关注”输入句子的所有部分。
  3. 全连接前馈网络。

同样,每个子层后都有“Add & Norm”。
在这里插入图片描述

七、最后的冲刺:输出层

当解码器栈输出了最终的向量序列后,我们只需要再加一个线性层和一个Softmax层,就能将其转换为一个在整个词汇表上的概率分布。概率最高的那个词,就是我们预测的下一个词!

总结

恭喜你!我们已经把Transformer这个庞大的“城堡”完整地游览了一遍。让我们回顾一下它的精髓:

  1. 完全基于注意力: 抛弃了RNN的循环结构,实现了高效的并行计算。
  2. 自注意力机制: 通过Q, K, V计算单词间的依赖关系,有效捕捉长距离依赖。
  3. 多头注意力: 从不同角度捕捉信息,增强模型表达能力。
  4. 位置编码: 解决了无序性问题,将位置信息融入模型。
  5. Encoder-Decoder架构: 经典的积木式搭建,清晰且强大。

正是这些创新,使得Transformer成为了现代NLP的基石。无论是用于理解的BERT(主要使用Encoder),还是用于生成的GPT(主要使用Decoder),它们的核心思想都源于此。

希望这篇图文并茂、代码详实的文章能帮你真正理解Transformer。如果你有任何问题,欢迎在评论区留言讨论!

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值