手把手教你复现Transformer模型
Transformer模型是由Google在2017年发表的论文《AttentionIsAllYouNeed》中提出的一种基于注意力机制的深度学习模型架构。起初用于解决机器翻译任务中的序列到序列问题,后由于其出色的性能被广泛应用于各种自然语言处理(NLP)任务及计算机视觉(CV)等其他领域中。Transformer架构引入了。
本文介绍 Transformer 模型,它起初被研发用于机器翻译,后应用于计算机识别、多模态任务等多领域。模型引入自注意力机制和位置编码,架构含输入、输出、编解码器。多头注意力为核心,因并行、捕捉长距依赖和强表达能力推动 AI 发展 。
一
Transformer模型简介
Transformer 模型是由 Google 在 2017 年发表的论文《Attention Is All You Need》中提出的一种基于注意力机制的深度学习模型架构。起初用于解决机器翻译任务中的序列到序列问题,后由于其出色的性能被广泛应用于各种自然语言处理(NLP)任务及计算机视觉(CV)等其他领域中。
Transformer架构引入了自注意力机制(self-attention mechanism),这是一个关键的创新,使其在处理序列数据时表现出色。摒弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),采用自注意力机制来对输入序列进行建模。这种机制可以让模型在处理每个位置的信息时,同时关注输入序列中的其他位置,从而更好地捕捉长序列中的依赖关系。
由于 Transformer 本身不具备对序列中位置信息的感知能力,因此还引入了位置编码。它通过给每个位置的输入添加一个固定的编码向量,将位置信息融入到模型中,使得模型能够感知到输入序列中各个元素的顺序关系。
二
模型结构
Transformer模型(如图1所示)主要由**输入、输出、编码器(Encoder)和解码器(Decoder)**构成。其中,编码器(Encoder)和解码器(Decoder)这两部分都包含多个相同的层。编码器负责将输入序列编码成一个固定长度的隐状态表示,而解码器则利用这个隐状态表示生成目标序列。

图1 Transformer模型架构
输入:首先将输入的语句经过词向量层的处理转换为词向量,在经过位置编码为模型提供单词在序列中的位置信息。位置编码通过一组正弦和余弦函数来实现(如图2所示),这些函数的频率和相位随着位置的不同而变化。位置编码的维度与嵌入向量的维度相同,因此将它们直接相加到词向量上,得到一个包含位置信息的输入向量,作为输入数据进入模型。

图2 位置编码的计算公式
其中:
-
pos是序列中元素的位置。
-
i是位置编码向量的维度索引。
-
dmodel是模型的维度,即嵌入向量的维度。
编码器:Nx = 6,编码器由6个编码器层堆叠而成,每个编码器层包含两个子层:多头注意力(Multi - Head Attention)子层和前馈网络(Feed - Forward Network)子层。多头注意力机制允许模型在不同的表示子空间中并行地计算注意力,从而能够捕捉到输入序列中不同位置之间的复杂关系。具体来说,它通过线性变换将输入的查询(Query)、键(Key)和值(Value)投影到多个低维空间中,然后在每个子空间中计算注意力权重,最后将这些子空间的结果拼接起来并经过一个线性变换得到最终输出。而前馈网络是一个全连接神经网络,它对多头注意力子层的输出进行非线性变换,同时对特征进行了变换和融合。通常包含两个线性层和一个ReLU激活函数。
编码器的代码实现方法如下:
class EncoderLayer(nn.Module): def __init__(self): super(EncoderLayer, self).__init__() self.enc_self_attn = MultiHeadAttention() self.pos_ffn = PoswiseFeedForwardNet() def forward(self, enc_inputs, enc_self_attn_mask): enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_inputs to same Q,K,V(未线性变换前) enc_outputs = self.pos_ffn(enc_outputs) return enc_outputs, attnclass Encoder(nn.Module): def __init__(self): super(Encoder, self).__init__() self.src_emb = nn.Embedding(src_vocab_size, d_model) # token Embedding self.pos_emb = PositionalEncoding(d_model) # Transformer中位置编码时固定的,不需要学习 self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) def forward(self, enc_inputs): enc_outputs = self.src_emb(enc_inputs) # [batch_size, src_len, d_model] enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(0, 1) # [batch_size, src_len, d_model] # Encoder输入序列的pad mask矩阵 enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs) # [batch_size, src_len, src_len] enc_self_attns = [] # 在计算中不需要用到,它主要用来保存你接下来返回的attention的值(这个主要是为了你画热力图等,用来看各个词之间的关系 for layer in self.layers: # for循环访问nn.ModuleList对象 # 上一个block的输出enc_outputs作为当前block的输入 # enc_outputs: [batch_size, src_len, d_model], enc_self_attn: [batch_size, n_heads, src_len, src_len] enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask) # 传入的enc_outputs其实是input,传入mask矩阵是因为你要做self attention enc_self_attns.append(enc_self_attn) # 这个只是为了可视化 return enc_outputs, enc_self_attns
解码器: 解码器的结构与编码器类似,也是由6个相同层的堆叠构成。与编码器不同,解码器在每个层中增加了第三个子层,这个子层专门对编码器的输出进行多头自注意力处理。和编码器一样,我们在解码器的每个子层之后也采用了残差连接和层归一化。此外,我们对解码器的自注意力子层进行了特殊设计,以防止在预测时关注到当前及之后的位置。通过这种掩码处理,结合输出嵌入的偏移机制,我们确保了在预测序列中任意位置i的符号时,只能依赖于位置i之前已经生成的输出。这样的设计保证了解码过程的自回归特性。
解码器的代码实现方法如下:
class DecoderLayer(nn.Module): def __init__(self): super(DecoderLayer, self).__init__() self.dec_self_attn = MultiHeadAttention() self.dec_enc_attn = MultiHeadAttention() self.pos_ffn = PoswiseFeedForwardNet()
def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask): # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len] dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask) # 这里的Q,K,V全是Decoder自己的输入 # dec_outputs: [batch_size, tgt_len, d_model], dec_enc_attn: [batch_size, h_heads, tgt_len, src_len] dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask) # Attention层的Q(来自decoder) 和 K,V(来自encoder) dec_outputs = self.pos_ffn(dec_outputs) # [batch_size, tgt_len, d_model] return dec_outputs, dec_self_attn, dec_enc_attn # dec_self_attn, dec_enc_attn这两个是为了可视化的class Decoder(nn.Module): def __init__(self): super(Decoder, self).__init__() self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model) # Decoder输入的embed词表 self.pos_emb = PositionalEncoding(d_model) self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)]) # Decoder的blocks def forward(self, dec_inputs, enc_inputs, enc_outputs): dec_outputs = self.tgt_emb(dec_inputs) # [batch_size, tgt_len, d_model] dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1).to(device) # [batch_size, tgt_len, d_model] # Decoder输入序列的pad mask矩阵(这个例子中decoder是没有加pad的,实际应用中都是有pad填充的) dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs).to(device) # [batch_size, tgt_len, tgt_len] # Masked Self_Attention:当前时刻是看不到未来的信息的 dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs).to(device) # [batch_size, tgt_len, tgt_len] # Decoder中把两种mask矩阵相加(既屏蔽了pad的信息,也屏蔽了未来时刻的信息) dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequence_mask), 0).to(device) # [batch_size, tgt_len, tgt_len]; torch.gt比较两个矩阵的元素,大于则返回1,否则返回0 # 这个mask主要用于encoder-decoder attention层 # get_attn_pad_mask主要是enc_inputs的pad mask矩阵(因为enc是处理K,V的,求Attention时是用v1,v2,..vm去加权的,要把pad对应的v_i的相关系数设为0,这样注意力就不会关注pad向量) # dec_inputs只是提供expand的size的 dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs) # [batc_size, tgt_len, src_len] dec_self_attns, dec_enc_attns = [], [] for layer in self.layers: # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len], dec_enc_attn: [batch_size, h_heads, tgt_len, src_len] # Decoder的Block是上一个Block的输出dec_outputs(变化)和Encoder网络的输出enc_outputs(固定) dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask) dec_self_attns.append(dec_self_attn) dec_enc_attns.append(dec_enc_attn) # dec_outputs: [batch_size, tgt_len, d_model] return dec_outputs, dec_self_attns, dec_enc_attns
输出:解码器的最后一个输出经过一个线性层和一个 Softmax 函数,得到目标序列中每个单词的概率分布,从而完成序列生成任务。

上图为训练模型部分的基本流程。

上图为训练完,测试模型的基本流程。
示例:
训练集

测试结果

三
多头注意力机制

多头注意力机制是整个框架的核心部分,它由多个缩放点积注意力(Scaled Dot-Product Attention)组成。
三个线性层(Linear):分别对输入的 Query(Q)、Key(K)、Value(V)进行特征变换,这是多头注意力的前置步骤,通过线性变换将输入映射到不同的表示空间,以便更好地捕捉特征和关系。
详细流程
1、特征变换
输入的词向量x,首先通过三个权值矩阵WQ、WK、WV进行特征变换,转变成为计算Attention值所需的Q,K,V向量
2、缩放点积注意力(Scaled Dot-Product Attention)
(1)计算注意力分数:将变换后的 Q 和 K 进行点积运算,然后除以一个缩放因子(通常是,其中是 K 的维度),得到未归一化的注意力分数。这一步的目的是为了防止点积结果过大,导致后续的 SoftMax 函数梯度过于平缓。
(2)应用掩码(Mask,可选):在某些情况下,例如在处理序列数据时,为了避免未来信息的泄露,会使用掩码操作。掩码可以将某些位置的注意力分数设置为负无穷或一个非常小的值,使得在 SoftMax 计算后这些位置的权重接近 0。
(3)SoftMax 归一化:对经过缩放和掩码(如果有)后的注意力分数进行 SoftMax 操作,得到归一化的注意力权重,这些权重表示了每个位置的重要性程度。
(4)加权求和:将归一化的注意力权重与 V 进行加权求和,得到最终的注意力输出。
3、Concat 与 Linear
多个缩放点积注意力的输出进行拼接(Concat),然后再经过一个线性层(Linear),得到多头注意力的最终输出。

值得一提的是,多头注意力机制相较于自注意力机制的不同之处在于,自注意力机制对于输入矩阵x,只使用了一组WQ,WK,WV来进行线性变换。而多头注意力机制使用了多组WQ,WK,WV,然后每组分别计算得到一个矩阵最后将得到的多个矩阵进行拼接。
多头注意力的代码实现方法如下:
class MultiHeadAttention(nn.Module): """这个Attention类可以实现: Encoder的Self-Attention Decoder的Masked Self-Attention Encoder-Decoder的Attention 输入:seq_len x d_model 输出:seq_len x d_model """ def __init__(self): super(MultiHeadAttention, self).__init__() self.W_Q = nn.Linear(d_model, d_k * n_heads, bias=False) # q,k必须维度相同,不然无法做点积 self.W_K = nn.Linear(d_model, d_k * n_heads, bias=False) self.W_V = nn.Linear(d_model, d_v * n_heads, bias=False) # 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)
def forward(self, input_Q, input_K, input_V, attn_mask): """ input_Q: [batch_size, len_q, d_model] input_K: [batch_size, len_k, d_model] input_V: [batch_size, len_v(=len_k), d_model] attn_mask: [batch_size, seq_len, seq_len] """ residual, batch_size = input_Q, input_Q.size(0) # 下面的多头的参数矩阵是放在一起做线性变换的,然后再拆成多个头,这是工程实现的技巧 # B: batch_size, S:seq_len, D: dim # (B, S, D) -proj-> (B, S, D_new) -split-> (B, S, Head, W) -trans-> (B, Head, S, W) # 线性变换 拆成多头 # Q: [batch_size, n_heads, len_q, d_k] Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # K: [batch_size, n_heads, len_k, d_k] # K和V的长度一定相同,维度可以不同 K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # V: [batch_size, n_heads, len_v(=len_k), d_v] V = self.W_V(input_V).view(batch_size, -1, n_heads, d_v).transpose(1, 2)
# 因为是多头,所以mask矩阵要扩充成4维的 # attn_mask: [batch_size, seq_len, seq_len] -> [batch_size, n_heads, seq_len, seq_len] attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)
# context: [batch_size, n_heads, len_q, d_v], attn: [batch_size, n_heads, len_q, len_k] context, attn = ScaledDotProductAttention()(Q, K, V, attn_mask) # 下面将不同头的输出向量拼接在一起 # context: [batch_size, n_heads, len_q, d_v] -> [batch_size, len_q, n_heads * d_v] context = context.transpose(1, 2).reshape(batch_size, -1, n_heads * d_v)
# 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model output = self.fc(context) # [batch_size, len_q, d_model] return nn.LayerNorm(d_model).to(device)(output + residual), attn
四
总结
因其优秀的并行计算能力,长距离依赖捕捉能力和模型表达能力,transformer模型被广泛的应用于自然语言处理(NLP)、计算机视觉(CV)和多模态任务等领域。Transformer 模型的出现为许多领域带来了新的思路和方法,推动了人工智能技术的发展和应用。
读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用
对于0基础小白入门:
如果你是零基础小白,想快速入门大模型是可以考虑的。
一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。
包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)