1.绪论介绍
1.1 传统的RNN网络
传统的RNN(递归神经网络)主要存在以下几个问题:
-
梯度消失和梯度爆炸:这是RNN最主要的问题。由于序列的长距离依赖,当错误通过层传播时,梯度可以变得非常小(消失)或非常大(爆炸),这使得网络难以学习。
-
计算效率低:RNN由于其递归性质,必须按序列顺序执行计算,这限制了计算的并行性。对于长序列,这会导致训练过程非常慢。
-
难以捕捉长距离依赖:尽管理论上RNN能够处理任何长度的序列,但在实践中,它们往往难以学习到输入序列中的长距离依赖关系。
为了解决这些问题,研究者们开发了一些改进的RNN结构,如长短时记忆网络(LSTM)和门控循环单元(GRU),这些结构通过引入门控机制来调节信息的流动,有效地缓解了梯度消失的问题,提高了模型对长距离依赖的捕捉能力。但是依然无法解决并行计算问题;
1.2 Transformer优势
- Self-Attention机制来进行并行计算,在输入和输出都相同
- Self-Attention 允许模型在处理数据时同时考虑序列中的所有元素,而不需要顺序处理每个时间步。这意味着模型可以同时处理序列中的多个位置,大幅提高了计算效率。因此,Self-Attention 特别适合于需要处理大规模数据集的任务,能够有效利用现代多核处理器的并行计算能力。现在基本已经取代RNN了
1.3传统word2vec
Word2Vec 是一种广泛使用的词嵌入方法,主要由两种模型构成:连续词袋(CBOW)和跳字模型(Skip-gram)。
- CBOW 模型通过上下文的词来预测目标词
-
- 跳字模型则是通过一个目标词来预测它周围的上下文词
- 跳字模型则是通过一个目标词来预测它周围的上下文词
- 这两种模型都利用大量文本数据,通过学习词与词之间的关系来生成词向量。
- CBOW模型通过上下文预测目标词,适合大型数据集且训练速度较快。相比之下,Skip-gram模型通过目标词预测周围上下文,适合小型数据集,能够捕捉稀有词的细节,虽然训练速度较慢但表现更佳。
这种方法存在几个问题:
- 词义多样性: Word2Vec 生成的词向量无法有效表达多义词的不同含义,每个词只有一个向量表示。
- 依赖于大量文本数据: 要获得有意义的词向量,Word2Vec 需要大量的文本数据进行训练。
- 缺乏语境感知: 生成的词向量不考虑上下文环境,因此在理解依赖上下文的语言特性时可能效果不佳。
尽管存在这些问题,Word2Vec 仍然是自然语言处理领域的基础工具之一,为后续的模型如BERT、GPT等提供了基础架构上的启示。
2. Transformer
思考以下问题:
- 输入如何编码?
- 输出结果是什么?
- Attention的目的?
- 怎样组合在一起?
2.1 注意力
- 对于于输入的数据,你的关注点是什么?
- 注意力机制(Attention Mechanism)是一种用于增强神经网络特定部分的重要性的技术,它最初是在视觉图像处理领域被提出,并在自然语言处理(NLP)中获得了广泛的应用和发展,尤其是在机器翻译任务中表现显著。
2.1.1 原理
注意力机制的核心思想是在处理信息时不是平等地对待所有的数据,而是让模型学会自动地将“注意力”集中在更重要的信息上。在NLP中,这通常意味着模型将更多地关注与当前任务最相关的单词或短语。例如,在机器翻译中,当模型试图翻译一个词时,它会考虑输入句子中哪些词与当前翻译的词最相关,并相应地调整其处理重点。
2.1.2 由来
注意力机制最早是由Bahdanau等人在2014年提出,用于改进序列到序列(Seq2Seq)的模型,特别是在机器翻译任务中。他们的模型通过对输入数据的不同部分赋予不同的权重,动态地选择性地关注输入序列的特定部分,从而改进了模型对长句子的处理能力。这种动态权重分配机制允许模型在翻译时更加灵活和准确,特别是在对齐和选择翻译时最为关键的词汇方面。
2.1.3 发展
随后,注意力机制在许多深度学习应用中被广泛采用,包括但不限于文本摘要、语音识别和计算机视觉等领域。2017年,Google的研究者提出了“Transformer”模型,该模型完全基于注意力机制,不使用任何卷积或递归层。Transformer模型凭借其高效的并行处理能力和出色的性能,迅速成为了许多NLP任务的基础架构,包括后来的BERT、GPT等影响深远的模型。
2.1.4 实现
注意力机制的一个常见形式是所谓的“缩放点积注意力”,它的数学公式可以表示为:
Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dkQKT)V
这里,(Q)、(K)、和 (V) 分别代表查询(query)、键(key)、和值(value)矩阵,(d_k) 是键向量的维度。该公式通过查询和键的点积来计算一个权重,然后这个权重通过 softmax 函数进行归一化,最终这个归一化的权重用来加权值矩阵 (V),从而得到加权后的输出,重点关注与查询最相关的信息。
下面是使用 PyTorch 实现简单的缩放点积注意力机制的代码示例:
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V):
d_k = K.size(-1) # 获取键向量的维度
scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(d_k)
weights = F.softmax(scores, dim=-1) # 沿最后一个维度应用 Softmax
output = torch.matmul(weights, V) # 加权值矩阵
return output
# 假设 d_model = 512 且 batch_size = 1, seq_length = 10
d_model = 512
batch_size = 1
seq_length = 10
# 随机生成 Q, K, V 矩阵
Q = torch.rand(batch_size, seq_length, d_model)
K = torch.rand(batch_size, seq_length, d_model)
V = torch.rand(batch_size, seq_length, d_model)
# 调用函数并打印输出
output = scaled_dot_product_attention(Q, K, V)
print(output)
这段代码定义了一个函数 scaled_dot_product_attention
,它接受查询 (Q)、键 (K)、值 (V) 三个矩阵作为输入,并返回注意力后的输出矩阵。
2.2 自注意力
自注意力(Self-Attention),也称为内部注意力,是一种注意力机制,它能使模型在处理一个序列的时候,能够考虑序列中的所有元素的相关性。在自注意力机制中,查询(Q)、键(K)和值(V)都来自同一个输入。这使得模型能够在输入序列的每个位置,评估其他位置的重要性,并据此更新自身。
自注意力的优势在于它的并行计算能力和对输入序列内部动态关系的捕捉能力。这种机制广泛应用于自然语言处理(NLP)领域,尤其是在Transformer架构中,它通过自注意力层来捕获单词之间的复杂依赖关系,从而改善了翻译质量、文本生成和其他语言理解任务的性能。
自注意力的计算过程通常包括以下步骤:
- 线性变换:输入序列经过线性变换生成查询(Q)、键(K)和值(V)。
- 计算注意力分数:通过计算查询和所有键的点积,得到注意力分数。
- 应用Softmax:对注意力分数应用Softmax函数,使得分数转换为概率形式,表征每个元素对其他元素的影响程度。
- 加权和:利用Softmax输出的权重对值(V)进行加权求和,得到最终的输出。
自注意力机制能够在模型中引入更多的上下文信息,增强模型对信息的整合能力,从而在处理复杂的序列数据时提供更丰富的表征。
如何计算
输入经过编码后得到向量想得到当前词语上下文的关系,可以当作是是加权构建三个矩阵分别来查询当前词跟其他词的关系,以及特征向量的表达。
三个需要训练的矩阵
Q:query,要去查询的
K:key,等着被查的
V:value,实际的特征信息
q与k的内积表示有多匹配输入两个向量得到一个分值K:key,等着被查的
V:value,实际的特征A信息
最终的得分值经过softmax就是最终上下文结果ScaledDot-ProductAttention
不能让分值随着向量维度的增大而增加
每个词的Q会跟整个序列中每一个K计算得分,然后基于得分再分配特征
每个词的Q会跟每一个K计算得分
Softmax后就得到整个加权结果
此时每个词看的不只是它前面的序列而是整个输入序列
同一时间计算出所有词的表示结果
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, embed_size, heads):
super(SelfAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads
assert self.head_dim * heads == embed_size, "Embed size needs to be divisible by heads"
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
def forward(self, values, keys, queries, mask=None):
N = queries.shape[0]
value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1]
# Split the embedding into self.heads different pieces
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = queries.reshape(N, query_len, self.heads, self.head_dim)
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# Dot product between queries and keys for each batch and head
energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
attention = torch.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)
out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(N, query_len, self.heads * self.head_dim)
out = self.fc_out(out)
return out
# Example usage
embed_size = 256
heads = 8
attention = SelfAttention(embed_size, heads)
input_tensor = torch.rand((1, 10, embed_size)) # (batch_size, sequence_length, embed_size)
output = attention(input_tensor, input_tensor, input_tensor)
print(output.shape) # Expected shape: (1, 10, 256)
2.3 multi-headed机制
一组q,k,v得到了一组当前词的特征表达类似卷积神经网络中的filter
能不能提取多种特征呢?
多头注意力机制(Multi-head Attention)是一种在自然语言处理和相关领域中广泛使用的技术,它可以帮助模型更好地捕捉信息中的不同方面(如不同位置的语义关系)。这种机制最著名的应用是在Transformer模型中,它通过并行地处理数据以捕捉复杂的依赖关系,提高了模型的表达能力和性能。
2.3.1 基本概念
多头注意力机制的核心思想是将注意力操作分割成多个“头”,每个头独立地学习输入数据的不同表示。这样做的好处是可以让模型在不同的子空间中捕获信息,从而更全面地理解数据。
2.3.2 组件和操作
- 线性变换:输入序列(Queries, Keys, Values)首先通过线性层被映射到多个不同的空间(对应于不同的头)。
- 点积注意力:每个头计算点积注意力,这是通过对Queries和Keys的点积得到的分数进行softmax运算,然后这些分数用于加权Values。
- 拼接与最终线性变换:所有头的输出被拼接起来,然后通过另一个线性变换产生最终的输出。
2.3.3 公式表达
设有h个头,输入的维度为 d m o d e l d_{model} dmodel,每个头处理的维度为 d k = d m o d e l / h d_k = d_{model} / h dk=dmodel/h。对于每个头 i i i:
-
对于Queries, Keys, Values执行线性变换:
Q i = Q W i Q , K i = K W i K , V i = V W i V Q_i = QW_i^Q, \quad K_i = KW_i^K, \quad V_i = VW_i^V Qi=QWiQ,Ki=KWiK,Vi=VWiV
其中 W i Q , W i K , W i V W_i^Q, W_i^K, W_i^V WiQ,WiK,WiV是线性变换的权重矩阵。 -
计算每个头的输出:
head i = Attention ( Q i , K i , V i ) = softmax ( Q i K i T d k ) V i \text{head}_i = \text{Attention}(Q_i, K_i, V_i) = \text{softmax}\left(\frac{Q_iK_i^T}{\sqrt{d_k}}\right)V_i headi=Attention(Qi,Ki,Vi)=softmax(dkQiKiT)Vi -
拼接所有头的输出,并进行最终的线性变换:
MultiHead ( Q , K , V ) = Concat ( head 1 , . . . , head h ) W O \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O MultiHead(Q,K,V)=Concat(head1,...,headh)WO
其中 W O W^O WO是最终线性层的权重矩阵。
2.3.4 优势
- 多角度学习:通过多个头观察数据的多个方面,模型可以在不同的表示子空间中捕获更丰富的信息。
- 灵活性增强:多头注意力允许模型在各种任务中更灵活地调整其注意力焦点。
- 并行处理:每个头的操作可以并行进行,这在实现上可以显著提高效率。
多头注意力机制提供了一种有效的方式来增强模型的表达力和性能,特别是在处理复杂的序列数据时,如机器翻译、文本摘要等任务中。
卷积中的特征图:
不同的注意力结果
得到的特征向量表达也不相同
2.3.5 堆叠多层
2.4 位置信息表达
在Transformer模型中,位置编码(Positional Encoding)是用来给模型提供关于单词在序列中位置的信息的一种机制。由于Transformer中的self-attention机制本身不考虑序列的顺序,仅通过内容来计算关联度,所以位置编码成为确保模型能够理解词汇顺序的关键组件。
2.4.1 工作原理
位置编码将每个位置的索引映射到一个高维空间,以捕捉位置间的相对或绝对关系。在原始的Transformer模型中,位置编码通过正弦和余弦函数的组合来实现,公式如下:
P
E
(
p
o
s
,
2
i
)
=
sin
(
p
o
s
1000
0
2
i
/
d
m
o
d
e
l
)
PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)
PE(pos,2i)=sin(100002i/dmodelpos)
P
E
(
p
o
s
,
2
i
+
1
)
=
cos
(
p
o
s
1000
0
2
i
/
d
m
o
d
e
l
)
PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)
PE(pos,2i+1)=cos(100002i/dmodelpos)
其中( pos )是词汇在序列中的位置,( i )是维度索引,( d_{model} )是模型的维度。通过这种方式,每个位置生成一个唯一的编码,可以加到对应词汇的嵌入向量中。
2.4.2 优点与作用
-
捕捉序列信息:通过位置编码,Transformer能够理解单词在文本中的顺序,这对于理解语言结构非常关键,如句法依赖和语序相关性。
-
泛化能力:使用正弦和余弦函数允许模型将位置信息泛化到未见过的位置长度,即模型能处理比训练时更长的序列。
-
无需额外参数:位置编码是固定的,不需要通过训练学习,这减少了模型的复杂度和过拟合的风险。
2.4.3 应用
位置编码的应用使得Transformer模型在处理需要顺序信息的任务(如机器翻译、文本生成等)时表现出色。同时,位置编码的设计也影响了后续模型的演变,例如各种变种尝试使用可学习的位置编码以适应不同的任务需求。
在self-attention中每个词都会考虑整个序列的加权,所以其出现位置并不会对结果产生什么影响,相当于放哪都无所谓,但是这跟实际就有些不符合了,我们希望模型能对位置有额外的认识。
- 初始化:PositionalEncoding 类初始化时计算整个编码矩阵,它只需要计算一次。我们创建一个零矩阵,然后填充正弦和余弦值。
- 正弦和余弦计算:使用广播机制来填充正弦和余弦值到编码矩阵中。通过指数衰减的方式调整频率,使得模型可以在较大范围内识别位置信息。
- 前向传播:在前向传播方法中,我们将位置编码加到输入的嵌入向量上。这里使用 .detach() 来避免在位置编码上进行梯度计算,因为位置编码是固定的,不需要更新。
import torch
import math
class PositionalEncoding(torch.nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEncoding, self).__init__()
self.encoding = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
self.encoding[:, 0::2] = torch.sin(position * div_term)
self.encoding[:, 1::2] = torch.cos(position * div_term)
self.encoding = self.encoding.unsqueeze(0)
def forward(self, x):
return x + self.encoding[:, :x.size(1)].detach()
# Example usage
d_model = 512 # Dimension of the model
max_len = 100 # Maximum length of the input sequences
pos_encoder = PositionalEncoding(d_model, max_len)
# Assume x is your batch of token embeddings with shape [batch_size, sequence_length, d_model]
x = torch.randn(10, 50, d_model) # Example tensor
x = pos_encoder(x)
2.5 Add与Normalize
在Transformer模型中,“Add” 和 “Normalize” 是每个编码器和解码器层中的重要部分,具体体现在残差连接(Residual Connection)和层归一化(Layer Normalization)中。
2.5.1 Add (残差连接)
在Transformer的每个子层(如自注意力层和前馈神经网络)后面,都有一个残差连接,其操作是将子层的输入加到其输出上。这种结构有助于缓解深层网络中的梯度消失问题,从而支持模型学习更深层次的表示。具体来说,对于任意一个输入 (x) 和一个子层函数 (F(x)),残差连接的输出是 x + F ( x ) x + F(x) x+F(x),这里的 (+) 就是 Add 操作。
2.5.2Normalize (层归一化)
Add 操作之后,通常会有一个层归一化步骤。层归一化是在特征的最后一维上(即对每个样本单独归一化,而不是整个批次)进行归一化,使得输出均值为0,方差为1。归一化通常是在添加残差之后进行,其目的是帮助模型在训练过程中保持稳定,加速收敛,并减少了不同初始化带来的影响。在数学上,层归一化可以表示为:
LayerNorm
(
x
+
F
(
x
)
)
\text{LayerNorm}(x + F(x))
LayerNorm(x+F(x))
这种Add和Normalize的组合方式,在每个编码器和解码器层中重复使用,有助于保持不同层的学习在一个稳定的数值范围内,从而改善模型的训练效率和最终的学习效果。
2.6 Decoder
Transformer模型中的解码器(Decoder)部分是负责将编码器输出的信息转换成最终输出序列的组件。解码器在结构上与编码器相似,但包括一些额外的机制来处理序列生成任务。具体来说,解码器由多个相同的层组成,每层都包含三个主要的子模块:
-
掩蔽自注意力机制(Masked Self-Attention):
- 解码器的第一个子模块是掩蔽自注意力层,这里的“掩蔽”指的是防止位置关注到未来的位置信息。这是通过在自注意力计算中应用一个三角形掩蔽(mask),确保位置i只能关注到位置i之前(包括i)的输出。
- 这种掩蔽保证解码器在生成当前词时只依赖于已生成的词,从而有助于顺序生成输出序列。
-
编码器-解码器注意力机制(Encoder-Decoder Attention):
- 第二个子模块是编码器-解码器注意力层,其中解码器利用自己的输出作为查询,而将编码器的输出作为键和值。
- 这允许每个位置的解码器访问整个输入序列的信息,从而更好地理解和翻译输入的上下文。
-
前馈神经网络(Feed-Forward Networks):
- 每个解码器层还包括一个前馈神经网络,这是一个独立于序列位置的同样操作。该网络包含两个线性变换和一个激活函数。
- 该层主要作用是对每个位置的表示进行进一步的处理。
每个子模块后面都会跟一个残差连接和层归一化步骤,这与编码器中的操作相同。这些层的堆叠使得解码器不仅能够处理当前位置的信息,还能够利用整个输入序列的信息,有效地生成序列的每个部分。
整体上,解码器的设计使其能够在接收前面生成的输出的同时,考虑整个输入序列的上下文,从而有效地进行序列到序列的学习和生成任务。
Attention计算不同加入了MASK机制
2.6.1 Masked Self-Attention
掩蔽自注意力机制(Masked Self-Attention)是Transformer模型中的一种技术,主要用于解码器部分以防止未来信息的提前泄露。其实现方法如下:
-
生成掩蔽矩阵:首先,生成一个上三角形的掩蔽矩阵,其中上三角部分(包括对角线上方的元素)都设置为负无穷大(或非常大的负数),其余部分设置为0。这种掩蔽确保了在计算当前词元时,只能利用该词元以及它之前的词元的信息。
-
计算注意力分数:在自注意力机制中,首先计算查询(Query)、键(Key)和值(Value)矩阵。注意力分数是通过查询矩阵与键矩阵的点积得到的,然后将掩蔽矩阵应用于这些分数上。
-
应用Softmax:将掩蔽后的注意力分数通过Softmax函数进行归一化处理,这确保了只有未被掩蔽的部分(即当前及之前的词元)对最终输出有贡献。
-
计算输出:最后,将Softmax输出的结果与值(Value)矩阵相乘,得到掩蔽自注意力的输出。
这种机制是Transformer解码器正确处理序列生成任务的关键,例如在机器翻译或文本生成中,能够确保模型在预测下一个词时只依赖于前面的词。
import torch
import torch.nn as nn
import torch.nn.functional as F
class MaskedSelfAttention(nn.Module):
def __init__(self, embed_size):
super(MaskedSelfAttention, self).__init__()
self.query = nn.Linear(embed_size, embed_size)
self.key = nn.Linear(embed_size, embed_size)
self.value = nn.Linear(embed_size, embed_size)
def forward(self, x, mask):
Q = self.query(x)
K = self.key(x)
V = self.value(x)
# 计算Q和K的点积,并除以维度的平方根,以稳定梯度
attention_scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(x.size(-1), dtype=torch.float32))
# 应用掩蔽,将未来位置的得分设置为负无穷
attention_scores = attention_scores.masked_fill(mask == 0, float('-inf'))
# 应用Softmax得到注意力权重
attention_weights = F.softmax(attention_scores, dim=-1)
# 计算输出
output = torch.matmul(attention_weights, V)
return output
# 假设embed_size为64,序列长度为10
embed_size = 64
seq_length = 10
# 创建模型实例
model = MaskedSelfAttention(embed_size)
# 创建输入数据,假设batch_size为1
x = torch.rand(1, seq_length, embed_size)
# 创建掩蔽矩阵,确保只能看到当前及之前的位置
mask = torch.tril(torch.ones(seq_length, seq_length)).unsqueeze(0)
# 调用模型
output = model(x, mask)
print(output)
2.6.2 编码器和解码器注意力机制
seqseq :基于LSTM的编码器和解码器,并通过注意力机制将编码器的输出与解码器的每一步输出关联起来。这允许解码器在生成输出时重点关注输入序列的相关部分。
import torch
import torch.nn as nn
import torch.nn.functional as F
class Encoder(nn.Module):
def __init__(self, input_dim, hidden_dim):
super(Encoder, self).__init__()
self.hidden_dim = hidden_dim
self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)
def forward(self, src):
outputs, (hidden, cell) = self.lstm(src)
return outputs, hidden, cell
class Decoder(nn.Module):
def __init__(self, output_dim, hidden_dim):
super(Decoder, self).__init__()
self.output_dim = output_dim
self.hidden_dim = hidden_dim
self.lstm = nn.LSTM(output_dim, hidden_dim, batch_first=True)
self.fc_out = nn.Linear(hidden_dim, output_dim)
self.attention = nn.Linear(self.hidden_dim * 2, 1)
def forward(self, input, hidden, cell, encoder_outputs):
# 重复最后的隐藏状态来匹配编码器输出的维度
hidden_repeated = hidden.repeat(encoder_outputs.size(1), 1, 1).transpose(0, 1)
# 计算注意力权重
energy = torch.tanh(self.attention(torch.cat((hidden_repeated, encoder_outputs), dim=2)))
attention_weights = F.softmax(energy.squeeze(2), dim=1).unsqueeze(1)
# 应用注意力权重到编码器输出
weighted = torch.bmm(attention_weights, encoder_outputs)
lstm_input = torch.cat((input.unsqueeze(1), weighted), dim=2)
# 通过LSTM传递
output, (hidden, cell) = self.lstm(lstm_input, (hidden, cell))
prediction = self.fc_out(output.squeeze(1))
return prediction, hidden, cell
class Seq2Seq(nn.Module):
def __init__(self, encoder, decoder):
super(Seq2Seq, self).__init__()
self.encoder = encoder
self.decoder = decoder
def forward(self, src, trg):
batch_size = trg.shape[0]
trg_len = trg.shape[1]
trg_vocab_size = self.decoder.output_dim
# 存储输出
outputs = torch.zeros(batch_size, trg_len, trg_vocab_size).to(trg.device)
# 编码器的最后隐藏状态用作解码器的初始隐藏状态
encoder_outputs, hidden, cell = self.encoder(src)
# 解码器的第一输入初始化为<sos>标记
input = trg[:,0]
for t in range(1, trg_len):
output, hidden, cell = self.decoder(input, hidden, cell, encoder_outputs)
outputs[:, t] = output
input = output.argmax(1) # 贪婪解码
return outputs
# 参数设置
INPUT_DIM = 10
OUTPUT_DIM = 10
HIDDEN_DIM = 128
# 创建模型实例
encoder = Encoder(INPUT_DIM, HIDDEN_DIM)
decoder = Decoder(OUTPUT_DIM, HIDDEN_DIM)
model = Seq2Seq(encoder, decoder)
# 假设源和目标语言的维度都是10
src = torch.rand(1, 5, INPUT_DIM) # (batch_size, src_len, input_dim)
trg = torch.rand(1, 5, OUTPUT_DIM) # (batch_size, trg_len, output_dim)
# 调用模型
output = model(src, trg)
print(output)
Transformer模型是一种基于注意力机制,特别是自注意力(self-attention)和互注意力(cross-attention)机制来处理序列数据的架构。它在很多自然语言处理任务中表现出色,如机器翻译、文本摘要等。Transformer的编码器和解码器都是由多个相同的层堆叠而成,每一层包括多头注意力机制和全连接的前馈网络;
'''
Transformer模型是一种基于注意力机制,特别是自注意力(self-attention)和互注意力(cross-attention)机制来处理序列数据的架构。它在很多自然语言处理任务中表现出色,如机器翻译、文本摘要等。Transformer的编码器和解码器都是由多个相同的层堆叠而成,每一层包括多头注意力机制和全连接的前馈网络。
'''
### 定义模型
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, heads, d_model, dropout=0.1):
super().__init__()
self.d_model = d_model
self.d_k = d_model // heads
self.h = heads
self.q_linear = nn.Linear(d_model, d_model)
self.v_linear = nn.Linear(d_model, d_model)
self.k_linear = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
self.out = nn.Linear(d_model, d_model)
def forward(self, q, k, v, mask=None):
bs = q.size(0)
# perform linear operation and split into h heads
k = self.k_linear(k).view(bs, -1, self.h, self.d_k)
q = self.q_linear(q).view(bs, -1, self.h, self.d_k)
v = self.v_linear(v).view(bs, -1, self.h, self.d_k)
# transpose to get dimensions bs * h * sl * d_model
k = k.transpose(1,2)
q = q.transpose(1,2)
v = v.transpose(1,2)
# calculate attention using function we will define next
scores = attention(q, k, v, self.d_k, mask, self.dropout)
# concatenate heads and put through final linear layer
concat = scores.transpose(1,2).contiguous().view(bs, -1, self.d_model)
output = self.out(concat)
return output
def attention(q, k, v, d_k, mask=None, dropout=None):
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
scores = F.softmax(scores, dim=-1)
if dropout is not None:
scores = dropout(scores)
output = torch.matmul(scores, v)
return output
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff=2048, dropout=0.1):
super().__init__()
# We set d_ff as a default to 2048
self.linear_1 = nn.Linear(d_model, d_ff)
self.dropout = nn.Dropout(dropout)
self.linear_2 = nn.Linear(d_ff, d_model)
def forward(self, x):
x = F.relu(self.linear_1(x))
x = self.dropout(x)
x = self.linear_2(x)
return x
class EncoderLayer(nn.Module):
def __init__(self, d_model, heads, dropout=0.1):
super().__init__()
self.norm_1 = nn.LayerNorm(d_model)
self.norm_2 = nn.LayerNorm(d_model)
self.attn = MultiHeadAttention(heads, d_model, dropout=dropout)
self.ff = FeedForward(d_model, dropout=dropout)
def forward(self, x, mask):
x2 = self.norm_1(x)
x = x + self.attn(x2, x2, x2, mask)
x2 = self.norm_2(x)
x = x + self.ff(x2)
return x
class DecoderLayer(nn.Module):
def __init__(self, d_model, heads, dropout=0.1):
super().__init__()
self.norm_1 = nn.LayerNorm(d_model)
self.norm_2 = nn.LayerNorm(d_model)
self.norm_3 = nn.LayerNorm(d_model)
self.attn_1 = MultiHeadAttention(heads, d_model, dropout=dropout)
self.attn_2 = MultiHeadAttention(heads, d_model, dropout=dropout)
self.ff = FeedForward(d_model, dropout=dropout)
def forward(self, x, e_outputs, src_mask, trg_mask):
x2 = self.norm_1(x
得出最终预测结果
损失函数cross-entropy即可
总结
Self-Attention
Multi-Head
多层堆叠,位置编码并行加速训练