"Attention Is All You Need"是一篇于2017年发表的开创性论文,首次介绍了Transformer模型。
这篇论文彻底改变了自然语言处理(NLP)领域的研究方向,为后续的众多NLP模型和应用奠定了基础。我们熟知的ChatGPT也是基于今天介绍的Transformer.
Transformer模型的核心设计理念可以概括为以下几点:
接下来,理解自注意力三个核心向量 Q K V:
针对句子“The cat sat on the mat”中的“sat”进行计算。为了简化,我们假设经过某种嵌入方法后,每个词的嵌入向量为:
注意:为了演示目的,我们这里假设Query(Q)、Key(K)和Value(V)向量等同于词嵌入本身,而在实际的Transformer模型中,Q、K、V是通过词嵌入乘以不同的权重矩阵得到的。
假设得分经过Softmax函数归一化后(为简化计算,这里不展示Softmax的计算过程),得到的权重(假定值)为:
根据上述得到的权重,我们现在计算加权的Value向量和(在本例中,Value向量和Key向量相同):
进行计算:
加权和向量[0.66, 0.33]代表了“sat”这个词在考虑其它词的“关注”后的新表示。这个向量捕捉了句子中与“sat”相关性最高的信息。在这个简化的示例中,“sat”本身获得了最高的权重,这是有意义的,因为在自注意力机制中,当前处理的词往往对自身的表示贡献最大。
请注意,这个示例非常简化,实际上在Transformer模型中,词嵌入的维度会更大(例如,512维),并且Q、K、V向量是通过词嵌入与不同的权重矩阵相乘得到的。此外,还会应用多头注意力机制,进一步增强模型的能力。
在Transformer模型中,经过自注意力机制计算得到的加权和向量,如我们示例中的[0.66, 0.33],会作为下一层或下一个处理步骤的输入。具体来说,这个向量会经过以下几个步骤:
因此,在下一次迭代(即下一层处理)时,加权和向量[0.66, 0.33]会经过残差连接、层归一化和前馈网络等一系列变换,然后可能成为下一层自注意力机制的输入。这是Transformer架构的核心设计之一,通过这种方式,模型能够捕获和整合序列中的信息,并在深层次上理解和处理文本。
在PyTorch中实现注意力机制可以有多种方式,这里提供一个基本的自注意力(self-attention)实现示例。自注意力是Transformer网络中使用的一种注意力形式,它允许模型在序列的不同位置间加权聚合信息。
以下是一个简单的自注意力类的实现:
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
), "Embedding 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, query, mask):
N = query.shape[0]
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.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 = query.reshape(N, query_len, self.heads, self.head_dim)
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# Einsum does matrix multiplication for query*keys for each training example
# with each head
attention = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
if mask is not None:
attention = attention.masked_fill(mask == 0, float("-1e20"))
attention = F.softmax(attention / (self.embed_size ** (1/2)), dim=3)
out = torch.einsum("nhqk,nvhd->nqhd", [attention, values]).reshape(
N, query_len, self.heads * self.head_dim
)
out = self.fc_out(out)
return out
这段代码实现了一个简单的多头自注意力机制,它包括以下步骤:
einsum
进行矩阵乘法,以计算查询和键之间的注意力分数。einsum
将注意力权重应用于值,获得加权的值。要使用上面的自注意力机制,你需要将其整合到你的神经网络模型中。以下是一个如何在一个简单的序列处理任务中使用自注意力模块的示例:
import torch
import torch.nn as nn
# 假设我们有一个特定大小的嵌入层和自注意力层
embed_size = 256
heads = 8
sequence_length = 100 # 输入序列的长度
batch_size = 32
vocab_size = 10000 # 假设的词汇大小
class TransformerBlock(nn.Module):
def __init__(self, embed_size, heads):
super(TransformerBlock, self).__init__()
self.attention = SelfAttention(embed_size, heads)
self.norm1 = nn.LayerNorm(embed_size)
self.norm2 = nn.LayerNorm(embed_size)
self.feed_forward = nn.Sequential(
nn.Linear(embed_size, 2 * embed_size),
nn.ReLU(),
nn.Linear(2 * embed_size, embed_size)
)
def forward(self, value, key, query, mask):
attention = self.attention(value, key, query, mask)
x = self.norm1(attention + query)
forward = self.feed_forward(x)
out = self.norm2(forward + x)
return out
class Transformer(nn.Module):
def __init__(self, embed_size, heads, vocab_size, sequence_length):
super(Transformer, self).__init__()
self.word_embedding = nn.Embedding(vocab_size, embed_size)
self.position_embedding = nn.Embedding(sequence_length, embed_size)
self.layers = nn.ModuleList([TransformerBlock(embed_size, heads) for _ in range(6)])
self.fc_out = nn.Linear(embed_size, vocab_size)
def forward(self, x, mask):
N, seq_length = x.shape
positions = torch.arange(0, seq_length).expand(N, seq_length).to(x.device)
out = self.word_embedding(x) + self.position_embedding(positions)
for layer in self.layers:
out = layer(out, out, out, mask)
out = self.fc_out(out)
return out
# 创建一个模型实例
model = Transformer(embed_size, heads, vocab_size, sequence_length)
# 创建一个随机输入序列
input_seq = torch.randint(0, vocab_size, (batch_size, sequence_length))
# 创建一个mask(这个例子中为None)
mask = None
# 前向传播
output = model(input_seq, mask)
TransformerBlock类实现下面图:
这里,Transformer
类实现了一个包含6个TransformerBlock
层的简单Transformer模型,TransformerBlock
包含了自注意力层(SelfAttention中的多头自注意力中 多头此处等于heads 为 8)和前馈神经网络。模型的输入是一个整数序列,这些整数代表词汇表中的索引,然后模型输出一个相同长度的序列,其中的每个元素是对应的词汇表大小的向量,表示概率分布。
mask
参数是可选的,它可以用来掩盖序列中的某些部分,例如在处理变长输入或者防止模型在解码时看到未来的信息时非常有用。
在实际应用中,需要根据具体任务调整模型结构和超参数,比如词嵌入的大小、注意力头的数量、序列的长度等,并且可能需要添加额外的层和功能,比如词嵌入层、位置嵌入层和最终的输出层。此外,还需要准备训练数据,定义损失函数和优化器,并执行训练循环。
最终输出形状(32, 100, 10000),下面解释意义:
假设我们正在使用Transformer模型进行文本生成任务。模型的任务是基于给定的上文,生成故事的续写。我们一次性处理32个故事片段(即批量大小为32),每个片段目标生成长度为100个单词,模型可以从一个包含10000个单词的词汇表中选择每个位置的单词。
输出形状(32, 100, 10000)
的含义
如何使用输出
现在模型需要为下一个单词位置生成一个概率分布。
假设的概率分布
当模型考虑下一个单词时,假设它为以下几个选项生成了概率分布:
在这个假设的概率分布中,“prince”获得了最高的概率(0.6),表明根据模型的预测和当前的上下文,“prince”是继“... there lived a”之后最可能的单词。
基于概率分布,模型会选择“prince”作为下一个单词,因为它具有最高的概率值(0.6)。这表示模型认为,在给定的上下文中,“prince”是最合适的词汇来继续这个故事。
因此,故事片段更新为:“Once upon a time, in a faraway kingdom, there lived a prince ...”。
假设批量中的第一个故事片段目前的文本是“Once upon a time, in a faraway kingdom, there lived a ...”,模型需要决定下一个最佳单词。模型为当前位置输出的概率分布可能强烈倾向于单词“prince”,因此选择“prince”作为下一个单词。这个过程会对每个位置和批量中的每个故事片段重复进行,直到生成完整的故事片段。
最终输出形状(32, 100, 10000)
精确地体现了模型在文本生成任务中的能力,即并行处理多个文本片段,为每个片段的每个位置生成单词的概率分布,并据此选择单词以构建连贯的文本。这种方法的核心在于Transformer模型通过自注意力机制能够有效捕获长距离依赖关系,并在给定上下文的基础上进行准确的文本生成。
本文分享自 程序员郭震zhenguo 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!