自注意力机制全解析:从原理到计算细节,一文尽览!

简介: 自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。

引言

自注意力机制的历史背景与发展

自注意力机制(Self-Attention)的概念最早可以追溯到20世纪70年代的神经网络研究,但直到近年来才在深度学习领域得到广泛关注和发展。现代意义上的自注意力机制首次出现在2017年的论文《Attention is All You Need》中,该论文由Google Brain团队提出,并引入了Transformer架构。这一创新迅速改变了自然语言处理(NLP)领域的格局。

在此之前,循环神经网络(RNN)及其变体长短期记忆网络(LSTM)和门控循环单元(GRU)是处理序列数据的主要方法。然而,这些模型存在一些固有的局限性,比如难以并行化训练、捕捉长距离依赖关系的能力有限等。此外,随着序列长度增加,RNN类模型的表现往往会下降。

为了解决这些问题,研究人员开始探索基于注意力机制的方法,它最初是为了改善编码器-解码器框架下的机器翻译任务而设计的。传统的注意力机制允许模型在生成输出时集中于输入序列中的某些特定部分,从而提高了性能。但是,这种外部注意力机制仍然依赖于编码器提供的上下文信息。

相比之下,自注意力机制不依赖于任何外部信息源,而是直接关注输入序列内部元素之间的相互作用。这不仅使得模型能够更有效地捕捉序列内部复杂的依赖关系,还极大地促进了模型的并行化训练,因为每个位置上的计算都可以独立进行。因此,自注意力机制成为构建高效且强大的序列建模工具的关键组件之一。

自注意力机制的概念

自注意力机制(Self-Attention),也称为内部注意力机制,是一种将单个序列的不同位置关联起来以计算同一序列的表示的注意力机制。这种机制允许模型在处理序列数据时,动态地调整对每个元素的关注程度,从而捕捉序列内部的复杂依赖关系。

自注意力机制的核心在于,它不依赖于外部信息,而是在序列内部元素之间进行信息的交互和整合。这意味着,对于序列中的每个元素,自注意力机制会计算该元素与序列中所有其他元素的相关性,生成一个加权的表示,其中权重反映了元素间的相互关系。

image.png

自注意力机制的计算过程可以被分解为几个关键步骤。

  1. 输入序列被映射到查询(Query)、键(Key)和值(Value)三个向量。

  2. 通过计算查询向量与所有键向量之间的点积来获得注意力得分。这些得分随后被缩放并经过Softmax函数进行归一化,以获得每个元素的注意力权重。

  3. 这些权重被用来对值向量进行加权求和,生成最终的输出序列。

自注意力机制在现代深度学习模型中的重要性

自注意力机制的重要性在于其灵活性和强大表达能力,特别是在处理长文本或其他类型的序列数据方面表现尤为突出。以下是几个关键点:

  • Transformer架构的核心:自从Transformer被提出以来,它已经在多个NLP基准测试中取得了顶尖的成绩,并成为了当前最先进的预训练语言模型的基础,如BERT、GPT系列等。这些模型都依赖于多层堆叠的自注意力机制来实现卓越的效果。

  • 并行化优势:与传统RNN不同的是,自注意力机制允许对整个序列进行并行处理,而不是按顺序逐个处理时间步。这一特性大大加快了训练速度,尤其是在大规模语料库上训练时显得尤为重要。

  • 捕捉全局依赖性:通过让每个元素都能“看到”整个序列中的所有其他元素,自注意力机制能够在单一层内建立起非常广泛且深入的上下文联系。这对于理解复杂句子结构或文档级别的语义关系至关重要。

  • 跨领域应用:除了NLP之外,自注意力机制也被成功应用于计算机视觉、语音识别等多个领域。例如,在图像分类任务中,它可以用来捕捉图片内的空间依赖关系;而在视频分析中,则有助于理解时间维度上的动态变化。

Q、K、V的生成

在自注意力(Self-Attention)机制中,查询(Query,简称Q)、键(Key,简称K)和值(Value,简称V)是三个核心的概念,它们共同参与计算以生成序列的加权表示。

查询(Query,Q)

查询向量Q代表了当前元素在序列中的作用,它用于“询问”序列中的其他元素以获取相关信息。在自注意力机制中,每个元素都会生成一个对应的查询向量,该向量用于与序列中的所有键向量进行比较,以确定每个元素的重要性或相关性。

键(Key,K)

键向量K包含了序列中每个元素的特征信息,这些信息将用于与查询向量进行匹配。键向量的主要作用是提供一种机制,使得模型能够识别和比较序列中不同元素之间的关系。在自注意力中,每个元素都会有一个对应的键向量,它与查询向量一起决定了注意力分数。

值(Value,V)

值向量V包含了序列中每个元素的实际信息或特征,这些信息将根据注意力分数被加权求和,以生成最终的输出。值向量代表了序列中每个元素的具体内容,它们是模型最终用于生成输出的原始数据。

image.png

在自注意力机制中,输入序列的每个元素首先被映射到三个向量:查询(Q)、键(K)和值(V)。这一过程通常通过与三个权重矩阵的线性变换实现。具体来说,输入序列X与权重矩阵W^Q、W^K和W^V相乘,得到Q、K和V:

image.png

其中,X是输入序列,W^Q、W^K和W^V是可学习的权重矩阵。这些矩阵的维度通常是(序列长度,特征维度)乘以(特征维度,Q/K/V维度)。Q、K和V的维度是(序列长度,Q/K/V维度)。

这三个变换可以看作是对原始输入的一种重新编码,目的是从不同角度提取信息,以便后续计算注意力分数时能够更有效地捕捉到元素间的相关性。

缩放点积计算注意力得分

自注意力机制中,查询向量Q与所有键向量K之间的点积被用来计算注意力得分。为了避免点积结果过大导致梯度问题,引入了一个缩放因子1/√dk,其中dk是键向量的维度。缩放后的注意力得分计算如下:

image.png
image.png

这个操作生成了一个注意力得分矩阵,其中每个元素代表对应元素对之间的相似度。

Softmax 归一化

为了将注意力得分转换为权重,应用Softmax函数进行归一化。Softmax确保所有输出权重的和为1,从而使得模型可以学习到每个元素对的重要性:

image.png

Softmax函数定义为:

image.png
image.png

其中,xi是注意力得分矩阵中的元素。这意味着对于每个位置 i,在计算完 softmax 后,我们会获得该位置与其他所有位置的相关性的“概率”。

权求和生成输出

最后,归一化的注意力权重被用来对值向量V进行加权求和,生成最终的输出序列。输出序列的每个元素是所有值向量的一个加权和,权重由对应的注意力权重决定:

image.png

这一步骤有效地整合了序列内部的信息,使得每个元素的输出表示包含了整个序列的上下文信息。

完整的计算流程

f3a43e21-ac19-4ce8-9085-9b03de505c51.gif

接下来,会一步步拆解,让你更清楚地理解这个过程。

首先,我们看输入部分:

image.png

然后,我们会用初始化的权重来计算key、value和query:

image.png

接下来,我们会为第一个输入计算attention score:

image.png

然后,对attention score执行softmax操作:

image.png

这一步之后,我们会将每个输入的softmaxed score和对应的value相乘,得到3个weighted value:

image.png

最后,把上一步的weighted value加起来(对应元素相加),就得到了输出。同样的步骤,我们也会对input#2和input#3执行,得到另外两个输出。

image.png

最后,这段代码展示了如何定义一个SelfAttention类,并在其中实现自注意力机制的核心步骤。

import torch
import torch.nn as nn
import torch.nn.functional as F
from math import sqrt

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

        # Ensure the embedding size is divisible by the number of heads
        assert (self.head_dim * heads == embed_size), "Embedding size must be divisible by heads"

        # Define linear transformations for queries, keys, and values
        self.values = nn.Linear(embed_size, embed_size, bias=False)
        self.keys = nn.Linear(embed_size, embed_size, bias=False)
        self.queries = nn.Linear(embed_size, embed_size, bias=False)
        self.fc_out = nn.Linear(embed_size, embed_size)

    def forward(self, x):
        N = x.shape[0]  # Batch size
        length = x.shape[1]  # Sequence length

        # Pass inputs through linear layers for queries, keys, and values
        values = self.values(x)
        keys = self.keys(x)
        queries = self.queries(x)

        # Split into multiple heads
        values = values.view(N, length, self.heads, self.head_dim)
        keys = keys.view(N, length, self.heads, self.head_dim)
        queries = queries.view(N, length, self.heads, self.head_dim)

        # Permute dimensions to put heads second
        values = values.permute(0, 2, 1, 3)  # (N, heads, length, head_dim)
        keys = keys.permute(0, 2, 1, 3)
        queries = queries.permute(0, 2, 1, 3)

        # Compute scaled dot-product attention
        energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])  # (N, heads, query_len, key_len)
        attention = F.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)  # Softmax on the last dimension

        # Weighted sum
        out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
            N, length, self.heads * self.head_dim
        )  # (N, length, embed_size)

        return self.fc_out(out)

# Example usage
embed_size = 256  # Embedding dimension
heads = 8  # Number of attention heads
x = torch.rand(64, 10, embed_size)  # Simulate input data

self_attention = SelfAttention(embed_size, heads)
output = self_attention(x)

print(output.shape)  # Should print: torch.Size([64, 10, 256])

自注意力机制的扩展应用场景

自然语言处理(NLP)中的机器翻译

案例描述:使用Transformer架构进行英德双语翻译。Transformer通过多层堆叠的自注意力机制,能够捕捉源语言句子中各个单词之间的复杂关系,从而生成更加准确的目标语言句子。

import torch
import torch.nn as nn
from transformers import BertTokenizer, BertModel

# 初始化BERT模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
model = BertModel.from_pretrained('bert-base-multilingual-cased')

def translate_sentence(sentence, model, tokenizer):
    # 对输入句子进行编码
    inputs = tokenizer(sentence, return_tensors="pt")

    # 获取BERT模型的输出
    outputs = model(**inputs)
    last_hidden_states = outputs.last_hidden_state

    # 这里可以加入额外的解码逻辑,例如使用一个预训练好的翻译模型
    # 简单起见,我们直接返回最后一层隐藏状态作为示例
    return last_hidden_states

# 示例翻译
sentence = "Hello, how are you?"
translated = translate_sentence(sentence, model, tokenizer)
print(translated.shape)  # 输出形状应为 (batch_size, sequence_length, hidden_dim)

推荐系统中的个性化推荐

在推荐系统中,个性化推荐的目标是为每个用户提供最符合其兴趣和需求的内容或商品。为了实现这一点,模型需要能够捕捉用户行为模式,并根据这些模式进行预测。自注意力机制(Self-Attention)因其强大的建模能力,在这一领域得到了广泛应用。下面我们将进一步细化基于自注意力机制的个性化推荐系统的构建过程,并提供更加详细的代码示例。

模型选择:SASRec (Self-Attentive Sequential Recommendation)

SASRec 是一种专门为序列化推荐设计的深度学习模型,它利用了自注意力机制来捕捉用户历史行为之间的复杂依赖关系。该模型可以有效地处理长序列数据,并且能够在不牺牲计算效率的情况下提高推荐质量。

数据准备

在构建个性化推荐系统之前,我们需要准备好用户的历史行为数据。通常情况下,这些数据包括用户ID、物品ID以及交互时间戳等信息。对于SASRec模型来说,还需要对物品进行编码,以便输入到模型中。

import pandas as pd
from sklearn.preprocessing import LabelEncoder

# 假设我们有一个包含用户行为的数据框 df
df = pd.read_csv('user_behavior.csv')

# 对用户和物品ID进行编码
user_encoder = LabelEncoder()
item_encoder = LabelEncoder()

df['user_id'] = user_encoder.fit_transform(df['original_user_id'])
df['item_id'] = item_encoder.fit_transform(df['original_item_id'])

# 将数据按用户分组,并按照时间排序
user_sequences = df.groupby('user_id')['item_id'].apply(list).reset_index(name='sequence')

构建SASRec模型

接下来,我们将定义SASRec模型结构。这里使用 TensorFlow 和 Keras API 来实现。需要注意的是,实际应用中可能需要安装额外的库,如 tfrs 或者其他特定于推荐系统的框架。

import tensorflow as tf
from tensorflow.keras.layers import Embedding, LayerNormalization, Dense
from tensorflow.keras.models import Model
import numpy as np

class SASRec(Model):
    def __init__(self, num_users, num_items, embedding_dim, max_len, num_heads=1, num_blocks=2):
        super(SASRec, self).__init__()
        self.user_embedding = Embedding(input_dim=num_users + 1, output_dim=embedding_dim)
        self.item_embedding = Embedding(input_dim=num_items + 1, output_dim=embedding_dim)
        self.positional_encoding = self.get_positional_encoding(max_len, embedding_dim)
        self.attention_layers = [tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=embedding_dim) for _ in range(num_blocks)]
        self.layer_norms = [LayerNormalization() for _ in range(num_blocks)]
        self.dense = Dense(num_items, activation='softmax')

    def get_positional_encoding(self, seq_len, d_model):
        # 创建位置编码矩阵
        position = np.arange(seq_len)[:, np.newaxis]
        div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
        pe = np.zeros((seq_len, d_model))
        pe[:, 0::2] = np.sin(position * div_term)
        pe[:, 1::2] = np.cos(position * div_term)
        return pe[np.newaxis, ...]

    def call(self, inputs):
        user_ids, sequences = inputs
        seq_emb = self.item_embedding(sequences) + self.positional_encoding[:, :tf.shape(sequences)[1], :]
        mask = tf.cast(tf.not_equal(sequences, 0), dtype=tf.float32)

        for i in range(len(self.attention_layers)):
            seq_emb = self.layer_norms[i](seq_emb + self.attention_layers[i](seq_emb, seq_emb, attention_mask=mask))

        logits = self.dense(seq_emb)
        return logits

# 初始化SASRec模型
num_users = len(user_encoder.classes_)
num_items = len(item_encoder.classes_)
embedding_dim = 128
max_len = 50  # 用户行为序列的最大长度

model = SASRec(num_users, num_items, embedding_dim, max_len)

训练模型

为了训练模型,我们需要定义损失函数和优化器,并编写一个适合推荐任务的数据生成器。这里简化了训练过程,实际应用中应该根据具体情况进行调整。

from tensorflow.keras.optimizers import Adam

# 编写简单的负采样函数以生成负样本
def negative_sampling(pos_seq, num_items, num_neg_samples=5):
    neg_samples = []
    for pos_item in pos_seq:
        neg_items = np.random.choice(num_items, size=num_neg_samples, replace=False)
        neg_samples.append(neg_items)
    return np.array(neg_samples)

# 定义损失函数(交叉熵)
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# 选择优化器
optimizer = Adam(learning_rate=0.001)

# 训练模型
for epoch in range(epochs):
    for batch in data_generator():
        with tf.GradientTape() as tape:
            user_ids, sequences, labels = batch
            predictions = model([user_ids, sequences])
            loss = loss_fn(labels, predictions)

        gradients = tape.gradient(loss, model.trainable_variables)
        optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    print(f'Epoch {epoch + 1}/{epochs}, Loss: {loss.numpy()}')

推荐预测

一旦模型训练完成,就可以用来为新用户提供推荐服务。通过给定用户的最新行为序列,模型可以预测下一个最有可能点击的商品。

def recommend_items(model, user_id, user_history, top_k=10):
    # 对用户行为序列进行填充以匹配最大长度
    sequence = np.pad(user_history[-max_len:], (max_len - len(user_history[-max_len:]), 0), 'constant')

    # 获取预测结果
    predictions = model.predict([[user_id], [sequence]])
    sorted_indices = np.argsort(predictions[0])[-top_k:]

    # 返回解码后的商品ID
    recommended_items = item_encoder.inverse_transform(sorted_indices)
    return recommended_items.tolist()

# 示例推荐
user_id = 1234
user_history = [234, 567, 890]  # 用户的历史行为记录
recommendations = recommend_items(model, user_id, user_history)
print(recommendations)  # 输出推荐商品ID

这种类型的模型特别适用于那些需要理解用户长期偏好变化的应用场景,例如电子商务平台上的商品推荐、社交媒体中的内容推荐等。

总结

通过上述详细探讨,我们不仅深入了解了自注意力机制的基本原理及其在自然语言处理、计算机视觉、语音处理以及推荐系统等多个领域的广泛应用,还结合具体案例和代码示例展示了如何将这一强大工具付诸实践。自注意力机制凭借其捕捉复杂依赖关系的能力、并行化处理的优势以及对长序列数据的有效建模,已经成为现代深度学习不可或缺的一部分。


参考文献:

Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., ... & Polosukhin, I. (2017). Attention is all you need. In Advances in neural information processing systems (pp. 5998-6008).

Devlin, J., Chang, M.-W., Lee, K., & Toutanova, K. (2018). BERT: Pre-training of deep bidirectional transformers for language understanding. arXiv preprint arXiv:1810.04805.

Dosovitskiy, A., Beyer, L., Kolesnikov, A., Weissenborn, D., Zhai, X., Unterthiner, T., ... & Houlsby, N. (2020). An image is worth 16x16 words: Transformers for image recognition at scale. arXiv preprint arXiv:2010.11929.

Wu, Y., Al-Shedivat, M., Ghasemipour, S., Wang, Y., Abbeel, P., Schwing, A. G., & Finn, C. (2020). Pay less attention with lightweight and dynamic convolutions. International Conference on Learning Representations.

相关文章
|
5天前
|
供应链 监控 安全
|
8天前
|
供应链 监控 安全
对话|企业如何构建更完善的容器供应链安全防护体系
随着云计算和DevOps的兴起,容器技术和自动化在软件开发中扮演着愈发重要的角色,但也带来了新的安全挑战。阿里云针对这些挑战,组织了一场关于云上安全的深度访谈,邀请了内部专家穆寰、匡大虎和黄竹刚,深入探讨了容器安全与软件供应链安全的关系,分析了当前的安全隐患及应对策略,并介绍了阿里云提供的安全解决方案,包括容器镜像服务ACR、容器服务ACK、网格服务ASM等,旨在帮助企业构建涵盖整个软件开发生命周期的安全防护体系。通过加强基础设施安全性、技术创新以及倡导协同安全理念,阿里云致力于与客户共同建设更加安全可靠的软件供应链环境。
150233 10
|
16天前
|
弹性计算 人工智能 安全
对话 | ECS如何构筑企业上云的第一道安全防线
随着中小企业加速上云,数据泄露、网络攻击等安全威胁日益严重。阿里云推出深度访谈栏目,汇聚产品技术专家,探讨云上安全问题及应对策略。首期节目聚焦ECS安全性,提出三道防线:数据安全、网络安全和身份认证与权限管理,确保用户在云端的数据主权和业务稳定。此外,阿里云还推出了“ECS 99套餐”,以高性价比提供全面的安全保障,帮助中小企业安全上云。
201928 14
对话 | ECS如何构筑企业上云的第一道安全防线
|
7天前
|
SQL 安全 前端开发
预编译为什么能防止SQL注入?
SQL注入是Web应用中常见的安全威胁,攻击者通过构造恶意输入执行未授权的SQL命令。预编译语句(Prepared Statements)是一种有效防御手段,它将SQL代码与数据分离,确保用户输入不会被解释为SQL代码的一部分。本文详细介绍了SQL注入的危害、预编译语句的工作机制,并结合实际案例和多语言代码示例,展示了如何使用预编译语句防止SQL注入,强调了其在提升安全性和性能方面的重要性。
|
4天前
|
人工智能 自然语言处理 API
阿里云百炼xWaytoAGI共学课DAY1 - 必须了解的企业级AI应用开发知识点
本课程旨在介绍阿里云百炼大模型平台的核心功能和应用场景,帮助开发者和技术小白快速上手,体验AI的强大能力,并探索企业级AI应用开发的可能性。
|
11天前
|
搜索推荐 物联网 PyTorch
Qwen2.5-7B-Instruct Lora 微调
本教程介绍如何基于Transformers和PEFT框架对Qwen2.5-7B-Instruct模型进行LoRA微调。
425 34
Qwen2.5-7B-Instruct Lora 微调
|
1月前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
9984 29
|
4天前
|
人工智能 算法 搜索推荐
阿里云百炼xWaytoAGI共学课开课:手把手学AI,大咖带你从零搭建AI应用
阿里云百炼xWaytoAGI共学课开课啦。大咖带你从零搭建AI应用,玩转阿里云百炼大模型平台。3天课程,涵盖企业级文本知识库案例、多模态交互应用实操等,适合有开发经验的企业或独立开发者。直播时间:2025年1月7日-9日 20:00,地点:阿里云/WaytoAGI微信视频号。参与课程可赢取定制保温杯、雨伞及磁吸充电宝等奖品。欢迎加入钉钉共学群(群号:101765012406),与百万开发者共学、共享、共实践!
|
4天前
|
SQL 存储 Apache
基于 Flink 进行增量批计算的探索与实践
本文整理自阿里云高级技术专家、Apache Flink PMC朱翥老师在Flink Forward Asia 2024的分享,内容分为三部分:背景介绍、工作介绍和总结展望。首先介绍了增量计算的定义及其与批计算、流计算的区别,阐述了增量计算的优势及典型需求场景,并解释了为何选择Flink进行增量计算。其次,详细描述了当前的工作进展,包括增量计算流程、执行计划生成、控制消费数据量级及执行进度记录恢复等关键技术点。最后,展示了增量计算的简单示例、性能测评结果,并对未来工作进行了规划。
267 5
基于 Flink 进行增量批计算的探索与实践

热门文章

最新文章