Transformer模型:自然语言处理
正是这些突破性的设计,使得训练更大、更深、更复杂的语言模型成为可能,直接催生了BERT、GPT、T5等一系列强大的预训练模型,它们在文本分类、情感分析、机器翻译、文本生成等几乎所有NLP任务中都取得了显著的成果,并持续推动着人工智能技术的发展。线性层(Linear Layer): 一个全连接层,其权重与词嵌入层共享(这是一个常用的技巧,可以减少模型参数),将解码器输出的d_model维向量投影到词
自2017年由Google研究人员在论文《Attention Is All You Need》中提出以来,Transformer模型已经彻底改变了自然语言处理(NLP)的格局。 它摒弃了以往NLP任务中广泛使用的循环神经网络(RNN)和卷积神经网络(CNN)结构,完全基于自注意力(Self-Attention)机制来捕捉输入和输出之间的全局依赖关系。 这种创新的架构不仅在机器翻译等任务上取得了当时最先进的性能,更重要的是,其固有的并行计算能力极大地提升了训练效率,为后续BERT、GPT等大规模预训练语言模型的诞生奠定了基础。
本文将对Transformer模型进行全面而深入的解析,从其核心架构、关键组件到具体实现,并提供详细的代码示例以供参考,旨在为读者构建一个对Transformer模型完整而清晰的认知。
一、 Transformer的整体架构:编码器-解码器的演进
与许多序列到序列(Sequence-to-Sequence)模型类似,Transformer在宏观上依然沿用了编码器-解码器(Encoder-Decoder)的架构。
编码器(Encoder): 其主要职责是将输入的符号序列(例如,一个句子)映射到一个连续的表示序列中。这个表示序列捕捉了输入文本的深层语义信息。
解码器(Decoder): 接收编码器的输出以及之前已生成的部分输出序列,然后逐个生成下一个输出符号,最终构成完整的输出序列。
在原始论文中,编码器和解码器都不是单一的组件,而是由N个(论文中N=6)相同的层堆叠而成。 这种堆叠结构使得模型能够学习从低阶到高阶、从简单到复杂的特征表示。
编码器的输出会作为每一个解码器层的输入之一,这使得解码器在生成输出时能够利用到完整的输入序列信息。 接下来,我们将深入剖析构成这些编码器和解码器的核心组件。
二、 核心机制:深入理解Transformer的构建模块
Transformer的强大能力源于其内部精巧的组件设计。我们将逐一解析这些模块,并附上相应的代码实现。
2.1 词嵌入(Word Embedding)与位置编码(Positional Encoding)
词嵌入(Word Embedding)
和大多数NLP模型一样,Transformer的第一步是将输入的离散词元(tokens)转换为连续的向量表示,即词嵌入。这通常通过一个可学习的嵌入层(Embedding Layer)来实现。每个词元会被映射到一个固定维度的向量(在Transformer论文中,这个维度d_model被设为512)。
位置编码(Positional Encoding)
Transformer架构本身不包含任何循环或卷积操作,这意味着它无法直接捕捉序列中词元的顺序信息。 如果不引入位置信息,模型看到的就是一堆无序的词,这对于理解语言是致命的。为了解决这个问题,研究者们引入了“位置编码”的概念。
位置编码是一个与词嵌入维度相同的向量,它包含了词元在序列中的绝对或相对位置信息。这个位置编码向量会与对应的词嵌入向量相加,作为模型编码器和解码器最底层的输入。 这样,模型就能间接地利用到序列的顺序信息。
原始论文中使用了一种基于正弦和余弦函数的固定位置编码方法,其公式如下:
PE(pos,2i)=sin(pos/100002i/dm.qcmcp.pro/odel) PE_{(pos, 2i)} = \sin(pos / 10000^{2i / d_{\text{model}}})
PE
(pos,2i)
=sin(pos/10000
2i/d
model
)
PE(pos,2i+1)=cos(pos/100002i/dm.qcmcp.pro/odel) PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i / d_{\text{model}}})
PE
(pos,2i+1)
=cos(pos/10000
2i/d
model
)
其中,pos是词元在序列中的位置,i是编码向量中的维度索引。 这种设计使得每个位置都有一个独特的编码,并且模型可以学习到词元间的相对位置关系,因为对于任意固定的偏移量k,PE_{pos+k}都可以被PE_{pos}线性表示。
PyTorch 代码实现:
import to.qcmcp.pro/rch
import tor.qcmcp.pro/ch.nn as nn
import m.qcmcp.pro/ath
class PositionalEnc.qcmcp.pro/oding(nn.Module):
def __init__(self, d_model, dro.qcmcp.pro/pout=0.1, max_len=5000):
super(PositionalEnco.qcmcp.pro/ding, self).__init__()
self.dropout = nn.Dro.qcmcp.pro/pout(p=dropout)
# 初始化一个足够长的位置编码矩阵
pe = torch.ze.qcmcp.pro/ros(max_len, d_model)
# 创建一个代表位置的张量 [max_len, 1]
position = torch.ar.qcmcp.pro/ange(0, max_len, dtype=torch.float).unsqueeze(1)
# 计算除数项,用于不同频率的正弦/余弦函数
div_term = torch.exp(torch.ara.qcmcp.pro/nge(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
# 为偶数维度应用sin函数
pe[:, 0::2] = torch.sin(po.qcmcp.pro/sition * div_term)
# 为奇数维度应用cos函数
pe[:, 1::2] = torch.cos(pos.qcmcp.pro/ition * div_term)
# 增加一个batch维度,使其能够与输入批次相加
pe = pe.unsqu.ikrnm.pro/eeze(0)
# 将pe注册为模型的buffer,这样它就不会被视为模型参数,但会随模型移动(例如.to(device))
self.register_bu.ikrnm.pro/ffer('pe', pe)
def forward(self, x):
"""
x: [batch_size, seq_len, d_m.ikrnm.pro/odel]
"""
# 将位置编码加到输入嵌入上
# self.pe[:, :x.size(1)] 确保位置编码的长度与输入序列的长度匹配
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)
# 示例
d_model = 512
vocab_size = 1000
seq_len = 100
batch_size = 32
embedding = nn.Embe.ikrnm.pro/dding(vocab_size, d_model)
pos_encoder = PositionalEnc.ikrnm.pro/oding(d_model)
# 模拟输入
input_tokens = torch.ran.ikrnm.pro/dint(0, vocab_size, (batch_size, seq_len))
input_embedding = embe.ikrnm.pro/dding(input_tokens)
# 添加位置编码
final_input = pos_encoder(input_emb.ikrnm.pro/edding)
print(final_input.shape) # to.ikrnm.pro/rch.Size([32, 100, 512])
2.2 自注意力机制(Self-Attention Mechanism)
自注意力机制是Transformer模型的核心。 它允许模型在处理一个序列中的某个词元时,能够权衡序列中所有其他词元的重要性,并动态地调整当前词元的表示。
从数学上讲,注意力机制可以描述为一个将查询(Query)和一组键值对(Key-Value pairs)映射到输出的函数。 输出是值的加权和,其中分配给每个值的权重是通过查询与相应键的兼容性函数计算得出的。
在自注意力中,Query、Key和Value都来自同一个输入序列。 对于输入序列中的每一个词元的嵌入向量,我们都会通过乘以三个独立的可学习权重矩阵(WQ、WK、WV)来生成对应的Query向量(q)、Key向量(k)和Value向量(v)。
计算过程可以分解为以下步骤:
计算注意力分数(Attention Score): 对于一个给定的q,我们计算它与序列中所有k的点积。这个分数决定了在编码当前词元时,我们应该对其他词元投入多少关注。
缩放(Scaling): 将计算出的分数除以d_k(Key向量的维度)的平方根。 这一步是为了防止在d_k较大时点积结果过大,导致softmax函数进入梯度极小的区域,从而使得训练过程更加稳定。
Softmax: 对缩放后的分数应用softmax函数,将其转换为概率分布。这些概率值(权重)的和为1。
加权求和: 将每个v向量乘以其对应的softmax权重,然后将所有加权后的向量相加,得到该位置的自注意力输出。
整个过程可以用一个公式概括,即“缩放点积注意力”(Scaled Dot-Product Attention):
Attention(Q,K,V)=softmax(QKTdk)V \text{Atten.ikrnm.pro/tion}(Q, K, V) = \text{soft.ikrnm.pro/max}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
Attention(Q,K,V)=so.ikrnm.pro/ftmax(
d
k
QK
T
)V
PyTorch 代码实现 (缩放点积注意力):
import torch.nn.funct.ikrnm.pro/ional as F
def scaled_dot_product_atten.ikrnm.pro/tion(q, k, v, mask=None):
"""
计算缩放点积注意力
q: [batch_size, n_hea.rulvb.pro/ds, seq_len, d_k]
k: [batch_size, n_hea.rulvb.pro/ds, seq_len, d_k]
v: [batch_size, n_hea.rulvb.pro/ds, seq_len, d_v]
mask: [batch_size, 1, 1, seq_len] or [batch_size, 1, seq_len, seq_len]
"""
d_k = q.size(-1)
# 1. 计算注意力分数: (Q * K^T) / sqrt(d_k)
scores = torch.matmul(q, k.tran.rulvb.pro/spose(-2, -1)) / math.sqrt(d_k)
# 2. 应用掩码 (如果提供)
if mask is not None:
# 将掩码中为0的位置填充为一个非常小的负数,这样在softmax后会趋近于0
scores = scores.mas.rulvb.pro/ked_fill(mask == 0, -1e9)
# 3. 计算softmax得到注意力权重
p_attn = F.soft.rulvb.pro/max(scores, dim=-1)
# 4. 加权求和
return torch.mat.rulvb.pro/mul(p_attn, v), p_attn
2.3 多头注意力机制(Multi-Head Attention)
与其只进行一次注意力计算,Transformer的作者发现将模型维度d_model拆分成h个“头”(heads),并为每个头独立地学习Q、K、V的投影(线性变换),然后并行地执行注意力计算会更有益。 这就是多头注意力机制。
多头注意力机制允许模型在不同的表示子空间中共同关注来自不同位置的信息。 每个头可以学习到不同方面的语义关联。例如,一个头可能关注语法依赖,而另一个头可能关注语义上的相似性。
计算过程:
将输入的Q, K, V(初始时,它们都是相同的输入嵌入)分别通过h个独立的线性层,投影成h组低维度的q_i, k_i, v_i。
对这h组q_i, k_i, v_i并行地执行缩放点积注意力计算,得到h个输出head_i。
将这h个输出head_i拼接(Concatenate)起来。
将拼接后的结果再通过一个最终的线性层进行一次投影,得到多头注意力的最终输出。
这个过程可以表示为:
MultiHead(Q,K,V)=Concat(head1,…,hea.rulvb.pro/dh)WO \text{MultiHead}(Q, K, V) = \text{Concat}(\text{he.rulvb.pro/ad}_1, \dots, \text{head}_h)W^O
MultiHead(Q,K,V)=Co.rulvb.pro/ncat(head
1
,…,head
h
)W
O
where headi=Atten.rulvb.pro/tion(QWiQ,KWiK,VWiV) \text{where head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)
where head
i
=Attention(QW
i
Q
,KW
i
K
,VW
i
V
)
在论文中,h被设为8,每个头的d_k和d_v被设为d_model / h = 512 / 8 = 64。
PyTorch 代码实现:
class MultiHeadAttention(nn.Mod.mpbis.pro/ule):
def __init__(self, d_model, n_he.mpbis.pro/ads):
super(MultiHeadAtte.mpbis.pro/ntion, self).__init__()
assert d_model % n_he.mpbis.pro/ads == 0
self.d_model = d_mo.mpbis.pro/del
self.n_heads = n_he.mpbis.pro/ads
self.d_k = d_model // n_he.mpbis.pro/ads # d_k = d_q
self.d_v = d_model // n_he.mpbis.pro/ads
# 定义Q, K, V和输出的线性层
self.W_q = nn.Li.mpbis.pro/near(d_model, d_model)
self.W_k = nn.Lin.mpbis.pro/ear(d_model, d_model)
self.W_v = nn.Lin.mpbis.pro/ear(d_model, d_model)
self.W_o = nn.Lin.mpbis.pro/ear(d_model, d_model)
def forward(self, q, k, v, ma.sbnhp.pro/sk=None):
"""
q, k, v: [batch_size, seq_len, d_m.sbnhp.pro/odel]
mask: 掩码
"""
batch_size = q.size(0)
# 1. 线性投影并切分成多个头
# [batch_size, seq_len, d_m.sbnhp.pro/odel] -> [batch_size, n_heads, seq_len, d_k]
q_s = self.W_q(q).view(bat.sbnhp.pro/ch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
k_s = self.W_k(k).view(bat.sbnhp.pro/ch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
v_s = self.W_v(v).view(bat.sbnhp.pro/ch_size, -1, self.n_heads, self.d_v).transpose(1, 2)
# 2. 对每个头应用缩放点积注意力
# context: [batch_size, n_hea.sbnhp.pro/ds, seq_len, d_v], attn: [batch_size, n_heads, seq_len, seq_len]
context, attn = scaled_dot_pr.sbnhp.pro/oduct_attention(q_s, k_s, v_s, mask=mask)
# 3. 拼接多头输出并进行最终的线性变换
# [batch_size, n_he.sbnhp.pro/ads, seq_len, d_v] -> [batch_size, seq_len, d_model]
context = context.transpose(1, 2).contig.sbnhp.pro/uous().view(batch_size, -1, self.d_model)
output = self.W_o(context)
return output, attn
2.4 前馈神经网络(Position-wise Feed-Forward Networks)
在每个编码器和解码器层中,多头注意力子层的输出会经过一个逐位置的前馈神经网络(FFN)。 这个FFN由两个线性变换和一个ReLU激活函数组成。
FFN(x)=max(0,xW1+b1)W2+b2 \te.sbnhp.pro/xt{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2
FFN(x)=max(0,xW
1
+b
1
)W
2
+b
2
这个操作是“逐位置”的,意味着它独立地应用于序列中的每个位置(每个词元的表示向量),但层与层之间不共享参数。 它的作用是对注意力子层提取的特征进行非线性变换,增加模型的表示能力。在论文中,输入和输出的维度d_model是512,而中间层的维度d_ff是2048。
PyTorch 代码实现:
class PositionwiseFeedForw.sbnhp.pro/ard(nn.Module):
def __init__(self, d_m.qihjy.pro/odel, d_ff):
super(PositionwiseFeedForw.qihjy.pro/ard, self).__init__()
self.fc1 = nn.Lin.qihjy.pro/ear(d_model, d_ff)
self.fc2 = nn.Lin.qihjy.pro/ear(d_ff, d_model)
self.relu = nn.ReLU()
def forward(self, x):
"""
x: [batch_size, seq_len, d_model]
"""
return self.fc2(self.relu(self.fc1(x)))
2.5 残差连接(Residual Connections)与层归一化(Layer Normalization)
为了构建更深层次的网络并有效训练,Transformer在每个子层(多头注意力和FFN)的周围都使用了残差连接,然后进行层归一化(Add & Norm)。
残差连接: 源自ResNet,它将子层的输入直接加到子层的输出上。 这样做可以有效地缓解深度神经网络中的梯度消失问题,使得信息和梯度能够更容易地在网络中传播。 其操作为 x + Sublayer(x)。
层归一化(Layer Normalization): 与批量归一化(Batch Normalization)不同,层归一化是对每个样本在特征维度上进行归一化,而不是对一个批次在样本维度上进行。 这使得它独立于批次大小,在NLP任务中表现更稳定。其作用是稳定训练过程,加速模型收敛。
PyTorch 代码实现 (Add & Norm):
class LayerNorm(nn.M.qihjy.pro/odule):
def __init__(self, feat.qihjy.pro/ures, eps=1e-6):
super(LayerNo.qihjy.pro/rm, self).__init__()
# gamma 和 beta 是可学习的仿射变换参数
self.gamma = nn.Param.qihjy.pro/eter(torch.ones(features))
self.beta = nn.Param.qihjy.pro/eter(torch.zeros(features))
self.eps = eps
def forward(self, x):
"""
x: [batch_size, seq_len, d_m.yxbxs.pro/odel]
"""
# 在最后一个维度(特征维度)上计算均值和方差
mean = x.mean(-1, kee.yxbxs.pro/pdim=True)
std = x.std(-1, kee.yxbxs.pro/pdim=True)
# 归一化并应用仿射变换
return self.gam.yxbxs.pro/ma * (x - mean) / (std + self.eps) + self.beta
这个组件通常会包装在子层外部,例如:norm(x + sublayer(x))。
三、 编码器(Encoder)的完整结构
通过组合上述模块,我们可以构建一个完整的编码器层。每个编码器层包含两个主要的子层:
多头自注意力(Multi-Head Self-Attention)
逐位置前馈神经网络(Position-wise Feed-Forward Network)
每个子层都包裹在“Add & Norm”组件中。
编码器则是由N个这样的编码器层堆叠而成。 底层编码器的输入是词嵌入和位置编码的和,而其他编码器的输入则是其前一层编码器的输出。
PyTorch 代码实现 (编码器层与编码器):
class Encoder.yxbxs.pro/Layer(nn.Module):
def __init__(self, d_model, n_heads, d_ff):
super(EncoderL.yxbxs.pro/ayer, self).__init__()
self.enc_self_attn = MultiHeadAtten.yxbxs.pro/tion(d_model, n_heads)
self.pos_ffn = PositionwiseFeedFor.yxbxs.pro/ward(d_model, d_ff)
self.layernorm1 = LayerN.yxbxs.pro/orm(d_model)
self.layernorm2 = LayerNo.yxbxs.pro/rm(d_model)
self.dropout = nn.Dro.yxbxs.pro/pout(0.1)
def forward(self, enc_inp.yxbxs.pro/uts, enc_self_attn_mask):
"""
enc_inputs: [batch_siz.yxbxs.pro/e, src_len, d_model]
enc_self_attn_mask: [bat.yxbxs.pro/ch_size, 1, src_len, src_len]
"""
# 1. 多头自注意力 + Add & Norm
# 在自注意力中,q, k, v 都来自 enc_inputs
attn_output, _ = self.enc_self_attn(enc_inp.kszeo.pro/uts, enc_inputs, enc_inputs, enc_self_attn_mask)
# 残差连接和层归一化
enc_inputs = self.layernorm1(enc_inputs + self.dro.kszeo.pro/pout(attn_output))
# 2. 前馈神经网络 + Add & Norm
ffn_output = self.pos_ffn(enc_inp.kszeo.pro/uts)
# 残差连接和层归一化
enc_outputs = self.layern.kszeo.pro/orm2(enc_inputs + self.dropout(ffn_output))
return enc_outputs
class Encoder(nn.Mo.kszeo.pro/dule):
def __init__(self, vocab_size, d_m.kszeo.pro/odel, n_layers, n_heads, d_ff):
super(Encoder, self).__init__()
self.src_emb = nn.Embed.kszeo.pro/ding(vocab_size, d_model)
self.pos_emb = PositionalEnc.kszeo.pro/oding(d_model)
self.layers = nn.ModuleList([Enco.kszeo.pro/derLayer(d_model, n_heads, d_ff) for _ in range(n_layers)])
def forward(self, enc_in.nnbhg.pro/puts):
"""
enc_inputs: [bat.nnbhg.pro/ch_size, src_len]
"""
# 1. 输入嵌入和位置编码
enc_outputs = self.nnbhg.pro/src_emb(enc_inputs)
enc_outputs = self.nnbhg.pro/pos_emb(enc_outputs)
# 2. 创建Padding Mask
# 这个掩码用于在自注意力中忽略padding部分的词元
enc_self_attn_mask = (enc_inputs != 0).unsqu.nnbhg.pro/eeze(1).unsqueeze(2) # 假设0是PAD token的索引
# 3. 堆叠编码器层
for layer in self.nnbhg.pro/layers:
enc_outputs = layer(enc_out.nnbhg.pro/puts, enc_self_attn_mask)
return enc_outputs
四、 解码器(Decoder)的完整结构
解码器的目标是生成输出序列。它的结构与编码器层相似,但有两点关键不同,这体现在它包含了三个子层。
带掩码的多头自注意力(Masked Multi-Head Self-Attention): 这是解码器的第一个子层。由于在生成第i个词时,模型只能看到第i个词之前(包括第i个词)的输出,而不能看到未来的信息,因此需要一个“掩码”来阻止向左的信息流。 这个掩码会屏蔽掉注意力分数矩阵上三角部分(对应未来的位置)。
编码器-解码器注意力(Encoder-Decoder Attention): 这是解码器的第二个子层,有时也称为“交叉注意力”(Cross-Attention)。 在这一层中,Query(Q)来自前一个解码器子层(带掩码的自注意力层)的输出,而Key(K)和Value(V)则来自编码器最终的输出。 这使得解码器在生成每个词元时,都能关注到输入序列的所有部分,从而决定当前最相关的信息是什么。
逐位置前馈神经网络(Position-wise Feed-Forward Network): 与编码器中的FFN完全相同。
同样,每个子层也都包裹在“Add & Norm”组件中。
PyTorch 代码实现 (解码器层与解码器):
def get_attn_subsequence_mask(seq):
"""
创建一个上三角矩阵形式的掩码,用于解码器的自注意力。
seq: [batch_size, tgt_len]
"""
attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
# 创建一个对角线及以下为1,以上为0的矩阵
subsequence_mask = torch.etytb.pro/triu(torch.ones(attn_shape), diagonal=1).byte()
return subsequ.etytb.pro/ence_mask == 0 # 反转,使有效部分为True
class Decod.etytb.pro/erLayer(nn.Module):
def __init__(self, d_mo.etytb.pro/del, n_heads, d_ff):
super(Decod.etytb.pro/erLayer, self).__init__()
self.dec_self_attn = MultiHe.etytb.pro/adAttention(d_model, n_heads)
self.dec_enc_attn = MultiHe.etytb.pro/adAttention(d_model, n_heads)
self.pos_ffn = PositionwiseFeedForw.etytb.pro/ard(d_model, d_ff)
self.layernorm1 = Lay.etytb.pro/erNorm(d_model)
self.layernorm2 = Lay.etytb.pro/erNorm(d_model)
self.layernorm3 = Lay.etytb.pro/erNorm(d_model)
self.dropout = nn.Dro.etytb.pro/pout(0.1)
def forward(self, dec_inputs, enc_outp.pexuy.pro/uts, dec_self_attn_mask, dec_enc_attn_mask):
"""
dec_inputs: [batch_size, tgt_len, d_mo.pexuy.pro/del]
enc_outputs: [batch_size, src_len, d_mo.pexuy.pro/del]
dec_self_attn_mask: [bat.pexuy.pro/ch_size, 1, tgt_len, tgt_len] (结合了padding mask和subsequence mask)
dec_enc_attn_mask: [ba.pexuy.pro/tch_size, 1, tgt_len, src_len] (padding mask for encoder output)
"""
# 1. 带掩码的多头自注意力 + Add & Norm
self_attn_ou.pexuy.pro/tput, _ = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
dec_inputs = self.layer.pexuy.pro/norm1(dec_inputs + self.dropout(self_attn_output))
# 2. 编码器-解码器注意力 + Add & Norm
# Q来自解码器,K, V来自编码器
enc_attn_output, _ = self.pexuy.pro/dec_enc_attn(dec_inputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
dec_inputs = self.layer.pexuy.pro/norm2(dec_inputs + self.dropout(enc_attn_output))
# 3. 前馈神经网络 + Add & Norm
ffn_output = self.pexuy.pro/pos_ffn(dec_inputs)
dec_outputs = self.pexuy.pro/layernorm3(dec_inputs + self.dropout(ffn_output))
return dec_outputs
class Decoder(nn.Module):
def __init__(self, voc.pexuy.pro/ab_size, d_model, n_layers, n_heads, d_ff):
super(Decoder, self).__init__()
self.tgt_emb = nn.Embed.dntz.pro/ding(vocab_size, d_model)
self.pos_emb = Positi.dntz.pro/onalEncoding(d_model)
self.layers = nn.Modu.dntz.pro/leList([DecoderLayer(d_model, n_heads, d_ff) for _ in range(n_layers)])
def forward(self, dec_inp.dntz.pro/uts, enc_inputs, enc_outputs):
"""
dec_inputs: [bat.dntz.pro/ch_size, tgt_len]
enc_inputs: [bat.dntz.pro/ch_size, src_len] (用于创建dec_enc_attn_mask)
enc_outputs: [ba.dntz.pro/tch_size, src_len, d_model]
"""
# 1. 目标序列嵌入和位置编码
dec_outputs = self.dntz.pro/tgt_emb(dec_inputs)
dec_outputs = self.dntz.pro/pos_emb(dec_outputs)
# 2. 创建掩码
# Padding mask for dec.dntz.pro/oder self-attention
dec_self_attn_pad_ma.dntz.pro/sk = (dec_inputs != 0).unsqueeze(1).unsqueeze(2)
# Subsequence ma.dntz.pro/sk for decoder self-attention
dec_self_attn_subse.dntz.pro/quence_mask = get_attn_subsequence_mask(dec_inputs)
# 结合两种掩码
dec_self_attn_mask = dec_self_a.pcku.pro/ttn_pad_mask & dec_self_attn_subsequence_mask
# Padding mask for enc.pcku.pro/oder-decoder attention
dec_enc_attn_ma.pcku.pro/sk = (enc_inputs != 0).unsqueeze(1).unsqueeze(3)
# 3. 堆叠解码器层
for layer in self.la.pcku.pro/yers:
dec_outputs = la.pcku.pro/yer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
return dec_outputs
五、 最终输出与模型整合
解码器堆栈的输出是一个浮点数向量序列。为了将其转换为最终的输出词元概率,需要经过最后两个步骤:
线性层(Linear Layer): 一个全连接层,其权重与词嵌入层共享(这是一个常用的技巧,可以减少模型参数),将解码器输出的d_model维向量投影到词汇表大小(vocab_size)的维度。这个输出通常被称为“logits”。
Softmax层: 对logits应用softmax函数,将其转换为每个词元的概率分布。 概率最高的词元就是该时间步的预测输出。
现在,我们可以将编码器、解码器和最后的线性层整合在一起,构建完整的Transformer模型。
PyTorch 代码实现 (完整Transformer):
class Transfo.pcku.pro/rmer(nn.Module):
def __init__(self, src_vo.pcku.pro/cab_size, tgt_vocab_size, d_model, n_layers, n_heads, d_ff):
super(Transfo.pcku.pro/rmer, self).__init__()
self.encoder = Enco.pcku.pro/der(src_vocab_size, d_model, n_layers, n_heads, d_ff)
self.decoder = Deco.pcku.pro/der(tgt_vocab_size, d_model, n_layers, n_heads, d_ff)
self.projection = nn.Line.pcku.pro/ar(d_model, tgt_vocab_size)
def forward(self, enc_inp.pcku.pro/uts, dec_inputs):
"""
enc_inputs: [ba.pcku.pro/tch_size, src_len]
dec_inputs: [b.pcku.pro/atch_size, tgt_len]
"""
# 编码器处理输入
enc_outputs = self.enc.pcku.pro/oder(enc_inputs)
# 解码器处理目标序列和编码器输出
dec_outputs = self.dec.pcku.pro/oder(dec_inputs, enc_inputs, enc_outputs)
# 最终线性投影
dec_logits = self.project.pcku.pro/ion(dec_outputs) # [batch_size, tgt_len, tgt_vocab_size]
# 返回每个位置的logits,通常在计算损失时会内部应用softmax
return dec_log.pcku.pro/its.view(-1, dec_logits.size(-1))
六、 总结与展望
Transformer模型的提出是自然语言处理领域的一个里程碑。它通过创新的自注意力机制,成功地解决了RNN在处理长距离依赖和并行计算方面的瓶颈。 其核心思想——通过注意力机制动态地构建序列中任意两个位置之间的关联——被证明是极其有效的。
Transformer的关键创新点可以总结为:
完全基于注意力: 摒弃了循环和卷积结构,完全依赖自注意力机制处理序列数据。
多头注意力: 从不同子空间捕捉信息,增强了模型的表达能力。
位置编码: 解决了非顺序架构中如何引入序列顺序信息的问题。
高效的并行计算: 相比RNN的顺序计算模式,Transformer的计算可以高度并行化,极大地缩短了训练时间。
正是这些突破性的设计,使得训练更大、更深、更复杂的语言模型成为可能,直接催生了BERT、GPT、T5等一系列强大的预训练模型,它们在文本分类、情感分析、机器翻译、文本生成等几乎所有NLP任务中都取得了显著的成果,并持续推动着人工智能技术的发展。 Transformer架构的成功也启发了其在计算机视觉、语音识别等其他领域的应用,展现了其作为一种通用特征提取器的巨大潜力。
更多推荐
所有评论(0)