当前位置: 首页 > news >正文

人工智能入门课【手写自注意力机制】

原理

自注意力(Self-Attention)是一种强大的机制,广泛应用于自然语言处理、计算机视觉等领域,尤其是在Transformer架构中发挥了关键作用。它的核心思想是让模型能够动态地关注输入序列中不同位置之间的关系,从而更好地捕捉全局信息和长距离依赖。

在传统的序列处理模型如循环神经网络(RNN)中,信息是按时间步逐个传递的,模型在处理当前时刻的信息时,只能依赖于之前时刻的信息,这使得它在处理长序列时容易出现梯度消失或梯度爆炸的问题,难以捕捉长距离依赖关系。而自注意力机制通过计算序列中每个位置与其他所有位置的关联程度,直接在全局范围内进行信息交互,解决了这一问题。

具体来说,自注意力机制的实现过程可以分为以下几个步骤:

  1. 首先,输入序列会被转换为三个矩阵,分别是查询(Query)、键(Key)和值(Value)。这三个矩阵是通过输入序列与三个不同的可学习权重矩阵(W^q,W^k,W^v)相乘得到的。
    1. 具体公式:\left\{\begin{matrix} query = W^q\cdot x\\ key = W^k\cdot x\\ value = W^v\cdot x \end{matrix}\right.
    2. 查询矩阵用于表示当前需要关注的内容,键矩阵用于表示序列中各个位置的特征,值矩阵则包含了序列中各个位置的实际信息。这三个矩阵的维度通常是相同的,且可以根据具体任务进行调整。
  2. 接下来,计算注意力分数。公式为attn\_score = \frac{QK^T}{\sqrt{d_k}}对于输入序列中的每个位置,都会将其查询向量与序列中所有位置的键向量进行点积运算,得到一个分数矩阵。
    1. 为了使这些分数具有可比性,通常会对它们进行缩放,即将每个分数除以键向量维度的平方根。这样做的目的是防止点积结果过大而导致梯度消失或梯度爆炸。
  3. 然后,将分数矩阵通过softmax函数进行归一化,公式为attn\_weight = Softmax(attn\_score)得到注意力权重矩阵。softmax函数的作用是将分数矩阵中的每个元素转换为一个概率值,使得所有权重的和为1。这样,每个位置的权重就表示了它在全局范围内的重要性,权重越大,说明该位置与其他位置的关联越强。
  4. 最后,将注意力权重矩阵与值矩阵相乘,公式为output = attn\_weight\cdot V,得到加权的值矩阵。这个加权的值矩阵就是自注意力机制的输出,它包含了输入序列中各个位置经过加权后的信息。通过这种方式,模型能够根据注意力权重动态地组合序列中的信息,从而更好地捕捉序列中的全局依赖关系。
  5. 总公式为:output = Softmax(\frac{QK^T}{\sqrt{d_k}})\cdot V

自注意力机制的一个重要特性是并行化计算。由于它不需要像RNN那样按顺序逐个处理序列中的元素,因此可以同时计算所有位置之间的注意力关系,大大提高了计算效率。这使得自注意力机制在处理大规模数据时具有显著的优势。

此外,自注意力机制还可以通过多头注意力(Multi-Head Attention)的方式进一步增强模型的表现能力。多头注意力的核心思想是将输入序列分成多个不同的“头”,每个头都独立地进行自注意力计算,然后将所有头的输出拼接在一起,再通过一个线性变换进行整合。这样做的好处是能够让模型从不同的角度捕捉序列中的信息,从而更好地理解序列的结构和语义。

-

-

单头自注意力

实现方法1

这段代码实现了一个简化版本的单头自注意力机制(Self-Attention),并展示了如何使用它处理输入数据。

在forward方法中

  1. 首先分别对输入数据 x 应用三个线性变换,得到查询(query)、键(key)和值(value)。它们的形状仍然是 [batch_size, seq_len, hidden_dim]
  2. 计算query和key的点积
    1. key.transpose(1, 2)key 的形状从 [batch_size, seq_len, hidden_dim] 转置为 [batch_size, hidden_dim, seq_len]

    2. torch.matmul(query, key.transpose(1, 2)) 计算 querykey 的点积,得到形状为 [batch_size, seq_len, seq_len] 的注意力分数矩阵。

    3. / math.sqrt(self.hidden_dim) 是一个缩放操作,用于防止点积结果过大导致的梯度消失或梯度爆炸问题。缩放因子是 hidden_dim 的平方根。

  3. 对注意力分数矩阵应用 Softmax 函数,将每个位置的分数转换为概率值,使得每一行的和为 1。这一步确保了注意力权重的合理性。
  4. 使用注意力权重矩阵 attn_weights 对值矩阵 value 进行加权求和,得到最终的自注意力输出。输出的形状为 [batch_size, seq_len, hidden_dim]
import math
import torch
import torch.nn as nnclass SelfAttn1(nn.Module):     # 简化版本,单头def __init__(self, hidden_dim=728):super().__init__()self.hidden_dim = hidden_dim        # 隐藏层维度self.query_proj = nn.Linear(hidden_dim, hidden_dim)     # q的线性映射,映射到hidden_dim维self.key_proj = nn.Linear(hidden_dim, hidden_dim)       # k的线性映射,映射到hidden_dim维self.value_proj = nn.Linear(hidden_dim, hidden_dim)     # v的线性映射,映射到hidden_dim维def forward(self, x):# x: [batch_size, seq_len, hidden_dim]query = self.query_proj(x)    # 分别对输入数据 x 应用三个线性变换key = self.key_proj(x)value = self.value_proj(x)attn_weights = torch.matmul(query, key.transpose(1, 2)) / math.sqrt(self.hidden_dim)    # 计算注意力权重 [batch_size, seq_len, seq_len]attn_weights = torch.softmax(attn_weights, dim=-1)          # 对注意力分数矩阵应用 Softmax 函数,将每个位置的分数转换为概率值,使得每一行的和为 1。  [batch_size, seq_len, seq_len]attn_output = torch.matmul(attn_weights, value)         # 使用注意力权重矩阵 attn_weights 对值矩阵 value 进行加权求和,得到最终的自注意力输出。  [batch_size, seq_len, hidden_dim]  也可以使用@return attn_outputx = torch.randn(4, 10, 728)
self_attn = SelfAttn1()
attn_output = self_attn(x)
print(attn_output.shape)

-

实现方法2

这段代码实现了一个优化版本的单头自注意力机制(SelfAttn2),与之前的 SelfAttn1 代码相比,主要的区别在于对查询(Query)、键(Key)和值(Value)的计算方式进行了优化。主要区别如下:

  1. SelfAttn2 中,使用了一个单一的线性变换层 self.proj,将输入数据映射到一个维度为 hidden_dim * 3 的空间(即query、key、value的三个映射矩阵被合并成一个)。这意味着输出张量的最后一个维度是输入维度的三倍。随后的forward()中,这个输出张量会被分割成三个部分,分别对应查询(Query)、键(Key)和值(Value)。
  2. forward()
    1. 首先,通过 self.proj(x) 将输入数据 x 映射到一个维度为 [batch_size, seq_len, hidden_dim * 3] 的张量 proj_output

    2. 然后,使用 torch.splitproj_output 按最后一个维度分割成三个部分,每部分的维度为 [batch_size, seq_len, hidden_dim],分别对应查询(query)、键(key)和值(value)。这种分割方式确保了每个部分的维度与原始输入维度一致。

    3. 接着同SelfAttn1()

import math
import torch
import torch.nn as nnclass SelfAttn2(nn.Module):     # 效率优化,qkv大矩阵运算def __init__(self, hidden_dim=728):super().__init__()self.hidden_dim = hidden_dimself.proj = nn.Linear(hidden_dim, hidden_dim * 3)       # qkv的线性映射,映射到hidden_dim * 3维def forward(self, x):# x: [batch_size, seq_len, hidden_dim]proj_output = self.proj(x)    # 将输入数据 x 映射到一个维度为 [batch_size, seq_len, hidden_dim * 3] 的张量 proj_output。query, key, value = torch.split(proj_output, self.hidden_dim, dim=-1)       # 使用 torch.split 将 proj_output 按最后一个维度分割成三个部分,分别对应查询(query)、键(key)和值(value)。[batch_size, seq_len, hidden_dim]attn_weights = torch.matmul(query, key.transpose(1, 2)) / math.sqrt(self.hidden_dim)attn_weights = torch.softmax(attn_weights, dim=-1)attn_output = torch.matmul(attn_weights, value)return attn_outputx = torch.randn(4, 10, 728)
self_attn = SelfAttn2()
attn_output = self_attn(x)
print(attn_output.shape)

SelfAttn1 中,分别使用了三个独立的线性变换层(self.query_projself.key_projself.value_proj)来计算查询、键和值。这种方式虽然直观,但计算效率较低,因为每次都需要对输入数据进行三次独立的线性变换。

但在 SelfAttn2 中,通过一个线性变换层将输入数据映射到一个更大的空间,然后一次性分割成查询、键和值。这种方式减少了线性变换的次数,从而提高了计算效率。具体来说,SelfAttn2 只需要一次矩阵乘法操作,而 SelfAttn1 需要三次矩阵乘法操作。SelfAttn2 的实现更加简洁,因为它将查询、键和值的计算合并到了一个线性变换层中,减少了代码的冗余性。

-

 实现方法3

这段代码实现了一个更完整的单头自注意力机制(SelfAttn3),在之前的版本基础上增加了以下功能:

  1. 注意力掩码(attn_mask:用于遮蔽某些位置的注意力权重,例如处理序列中的填充部分或防止解码器中的未来信息泄露

  2. Dropout:在注意力权重上应用 Dropout,以防止模型过拟合。

  3. 输出映射(output_proj:对自注意力的输出进行额外的线性变换,以调整输出的特征空间。

import math
import torch
import torch.nn as nnclass SelfAttn3(nn.Module):     # 加入dropout和attn_mask、以及output映射def __init__(self, hidden_dim=728, dropout_rate=0.1, *args, **kwargs):super().__init__()self.hidden_dim = hidden_dimself.proj = nn.Linear(hidden_dim, hidden_dim * 3)       # qkv的线性映射,映射到hidden_dim * 3维self.attention_dropout = nn.Dropout(dropout_rate)        # dropoutself.output_proj = nn.Linear(hidden_dim, hidden_dim)def forward(self, x, attn_mask=None):# x: [batch_size, seq_len, hidden_dim]proj_output = self.proj(x)query, key, value = torch.split(proj_output, self.hidden_dim, dim=-1)       # [batch_size, seq_len, hidden_dim]attn_weights = torch.matmul(query, key.transpose(1, 2)) / math.sqrt(self.hidden_dim)if attn_mask is not None:    # 如果提供了注意力掩码 attn_mask,则将掩码中为 0 的位置对应的注意力权重设置为一个非常小的值(-1e20)。这样,在应用 Softmax 时,这些位置的权重会趋近于 0,从而实现遮蔽效果。attn_weights = attn_weights.masked_fill(attn_weights==0, float('-1e20'))attn_weights = torch.softmax(attn_weights, dim=-1)attn_weights = self.attention_dropout(attn_weights)attn_output = torch.matmul(attn_weights, value)attn_output = self.output_proj(attn_output)    # 对自注意力的输出进行额外的线性变换return attn_outputx = torch.randn(4, 10, 728)
mask = torch.randint(0, 2, (4, 10, 728))
self_attn = SelfAttn3()
attn_output = self_attn(x, mask)
print(attn_output.shape)

其中应用掩码部分:

如果提供了注意力掩码 attn_mask,则将掩码中为 0 的位置对应的注意力权重设置为一个非常小的值(-1e20)。这样,在应用 Softmax 时,这些位置的权重会趋近于 0,从而实现遮蔽效果。

if attn_mask is not None:attn_weights = attn_weights.masked_fill(attn_mask == 0, float('-1e20'))

输出映射:对自注意力的输出进行额外的线性变换,调整输出的特征空间。这一步可以进一步优化模型的表达能力。

attn_output = self.output_proj(attn_output)

-

-

多头自注意力

多头自注意力(Multi-Head Attention)是自注意力机制的一个扩展,它通过将输入数据分割成多个不同的“头”(heads),分别计算每个头的自注意力,然后将这些头的输出拼接起来,从而能够从多个不同的角度捕捉输入数据的特征。这种机制在Transformer架构中被广泛应用,极大地提升了模型对复杂数据结构的建模能力。

多头自注意力的原理

在单头自注意力中,输入数据首先被映射到查询(Query)、键(Key)和值(Value)三个矩阵,然后通过计算查询和键的点积得到注意力分数,再通过Softmax函数将这些分数转换为注意力权重,最后用这些权重对值进行加权求和,得到最终的输出。然而,单头自注意力只能从一个固定的视角来捕捉输入数据的特征,这在处理复杂的语言或图像数据时可能会限制模型的表现能力。

多头自注意力的核心思想是将输入数据分割成多个不同的“头”,每个头都独立地计算自注意力,从而能够从多个不同的角度捕捉输入数据的特征。具体来说,输入数据首先被分割成多个子空间,每个子空间对应一个“头”。在每个头中,分别计算查询、键和值,然后计算自注意力。最后,将所有头的输出拼接起来,再通过一个线性变换层进行整合,得到最终的输出。

多头自注意力的优势

多头自注意力的主要优势在于它能够从多个不同的角度捕捉输入数据的特征。在自然语言处理中,这意味着模型能够同时捕捉到句子中的局部信息和全局信息,从而更好地理解句子的语义。例如,一个头可能专注于捕捉句子中的主谓宾结构,而另一个头可能专注于捕捉句子中的修饰词和被修饰词之间的关系。通过将这些不同视角的信息融合在一起,模型能够得到一个更加丰富和全面的特征表示。

此外,多头自注意力还具有并行计算的优势。由于每个头的计算是独立的,因此可以并行进行,从而提高了计算效率。这使得多头自注意力机制在处理大规模数据时具有显著的优势。

-

这段代码实现了一个多头自注意力机制(MultiHeadSelfAttn),它是Transformer架构中的核心组件之一。代码中详细展示了如何将输入数据分割成多个“头”,分别计算每个头的自注意力,然后将这些头的输出拼接起来并进行整合。

import math
import torch
import torch.nn as nnclass MultiHeadSelfAttn(nn.Module):def __init__(self, hidden_dim=728, head_num=8, dropout_rate=0.1):super().__init__()self.hidden_dim = hidden_dimself.head_num = head_numself.head_dim = hidden_dim // head_numself.query_proj = nn.Linear(hidden_dim, hidden_dim)  # q的线性映射,映射到hidden_dim维self.key_proj = nn.Linear(hidden_dim, hidden_dim)  # k的线性映射,映射到hidden_dim维self.value_proj = nn.Linear(hidden_dim, hidden_dim)  # v的线性映射,映射到hidden_dim维self.attention_dropout = nn.Dropout(dropout_rate)self.output_proj= nn.Linear(hidden_dim, hidden_dim)def forward(self, x, attn_mask=None):# x: [batch_size, seq_len, hidden_dim]batch_size, seq_len, _ = x.size()query = self.query_proj(x)key = self.key_proj(x)value = self.value_proj(x)# [batch_size, seq_len, hidden_dim] -> [batch_size, seq_len, head_num, head_dim]query = query.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)       # [batch_size, head_num, seq_len, head_dim]key = key.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)value = value.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)attn_score = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(self.head_dim)      # [batch_size, head_num, seq_len, seq_len]if attn_mask is not None:attn_score = attn_score.masked_fill(attn_mask==0, float('-inf'))attn_score = torch.softmax(attn_score, dim=-1)attn_score = self.attention_dropout(attn_score)attn_output = torch.matmul(attn_score, value)attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)attn_output = self.output_proj(attn_output)return attn_outputx = torch.randn(4, 10, 728)
mask = torch.randint(0, 2, (4, 8, 10, 10))
self_attn = MultiHeadSelfAttn()
attn_output = self_attn(x, mask)
print(attn_output.shape)

-

核心部分代码讲解

首先分割多个头

  • 将查询、键和值的形状从 [batch_size, seq_len, hidden_dim] 转换为 [batch_size, seq_len, head_num, head_dim]

  • 然后通过 transpose(1, 2) 将形状调整为 [batch_size, head_num, seq_len, head_dim],以便每个头可以独立计算自注意力。

query = query.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)       # [batch_size, head_num, seq_len, head_dim]
key = key.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)
value = value.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)

计算多头版的查询和键的点积

  • 计算每个头的查询和键的点积,得到注意力分数矩阵。key.transpose(-1, -2) 将键的形状从 [batch_size, head_num, seq_len, head_dim] 转置为 [batch_size, head_num, head_dim, seq_len]

  • 点积结果的形状为 [batch_size, head_num, seq_len, seq_len]

  • 通过除以 math.sqrt(self.head_dim) 进行缩放,防止点积结果过大导致的梯度消失或梯度爆炸问题。

attn_score = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(self.head_dim)      # [batch_size, head_num, seq_len, seq_len]

计算注意力分数(这部分和之前的没区别)

  • 对注意力分数应用 Softmax 函数,将分数转换为概率值。

  • 对注意力权重应用 Dropout,随机丢弃一部分权重,以防止过拟合。

  • 使用注意力权重对值进行加权求和,得到每个头的输出。输出的形状为 [batch_size, head_num, seq_len, head_dim]

attn_score = torch.softmax(attn_score, dim=-1)
attn_score = self.attention_dropout(attn_score)
attn_output = torch.matmul(attn_score, value)

恢复原来的形状 

  • 将每个头的输出拼接起来,恢复到原始的形状 [batch_size, seq_len, hidden_dim]

  • 通过一个线性变换层 output_proj 对拼接后的输出进行整合,得到最终的多头自注意力输出。

attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)
attn_output = self.output_proj(attn_output)

-

-

Decoder

这段代码实现了一个基于Transformer架构的解码器(Decoder),它由多个解码器层(DecoderLayer)组成。每个解码器层包含多头自注意力机制和前馈神经网络(FFN)。代码还展示了如何使用这个解码器处理输入数据,并生成输出。

import math
import torch
import torch.nn as nnclass DecoderLayer(nn.Module):def __init__(self, hidden_dim, head_num, dropout_rate=0.1):super(DecoderLayer, self).__init__()self.hidden_dim = hidden_dimself.head_num = head_numself.head_dim = hidden_dim // head_num# multi-head self-attentionself.query_proj = nn.Linear(hidden_dim, hidden_dim)  # q的线性映射,映射到hidden_dim维self.key_proj = nn.Linear(hidden_dim, hidden_dim)  # k的线性映射,映射到hidden_dim维self.value_proj = nn.Linear(hidden_dim, hidden_dim)  # v的线性映射,映射到hidden_dim维self.attention_dropout = nn.Dropout(dropout_rate)self.output_proj = nn.Linear(hidden_dim, hidden_dim)self.norm1 = nn.LayerNorm(hidden_dim, eps=1e-6)# ffnself.up_proj = nn.Linear(hidden_dim, hidden_dim * 4)    # 升维,gpt升维为4倍self.down_proj = nn.Linear(hidden_dim * 4, hidden_dim)self.act_fn = nn.GELU()self.drop_ffn = nn.Dropout(dropout_rate)self.norm2 = nn.LayerNorm(hidden_dim, eps=1e-6)def multi_head_self_attn(self, x, attn_mask=None):# x: [batch_size, seq_len, hidden_dim]batch_size, seq_len, _ = x.size()query = self.query_proj(x)key = self.key_proj(x)value = self.value_proj(x)# [batch_size, seq_len, hidden_dim] -> [batch_size, seq_len, head_num, head_dim]query = query.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)  # [batch_size, head_num, seq_len, head_dim]key = key.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)value = value.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)attn_score = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(self.head_dim)  # [batch_size, head_num, seq_len, seq_len]if attn_mask is not None:attn_mask = attn_mask.tril()attn_score = attn_score.masked_fill(attn_mask == 0, float('-inf'))else:attn_mask = torch.ones_like(attn_score).tril()attn_score = attn_score.masked_fill(attn_mask == 0, float('-inf'))attn_score = torch.softmax(attn_score, dim=-1)attn_score = self.attention_dropout(attn_score)attn_output = torch.matmul(attn_score, value)attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)attn_output = self.output_proj(attn_output)return attn_outputdef ffn(self, x):x = self.up_proj(x)x = self.act_fn(x)x = self.down_proj(x)x = self.drop_ffn(x)return xdef forward(self, x, attn_mask=None):x = x + self.multi_head_self_attn(x, attn_mask)x = self.norm1(x)x = x + self.ffn(x)x = self.norm2(x)return xclass Decoder(nn.Module):def __init__(self, vocab_size, hidden_dim, head_num, dropout_rate=0.1):super(Decoder, self).__init__()self.layer_list = nn.ModuleList([DecoderLayer(hidden_dim, head_num, dropout_rate) for _ in range(6)])self.emb = nn.Embedding(vocab_size, hidden_dim)self.out = nn.Linear(hidden_dim, vocab_size)def forward(self, x, attn_mask=None):x = self.emb(x)for layer in self.layer_list:x = layer(x, attn_mask)x = self.out(x)return torch.softmax(x, dim=-1)x = torch.randint(low=0, high=12, size=(3, 4))
mask = (torch.tensor([[1, 1, 1, 1], [1, 1, 0, 0], [1, 1, 1, 0]]).unsqueeze(1).unsqueeze(2).repeat(1, 8, 4, 1))      # [3,4]->[3,8,4,4
print(mask.shape)
decoder = Decoder(vocab_size=12, hidden_dim=64, head_num=8)
output = decoder(x, mask)
print(output.shape)

-

DecoderLayer解释

参数解析

  • hidden_dim:隐藏层维度,表示输入数据的特征维度。

  • head_num:头的数量,表示将输入数据分割成多少个子空间。

  • head_dim:每个头的维度,计算公式为 hidden_dim // head_num

  • query_projkey_projvalue_proj:三个线性变换层,分别用于将输入数据映射到查询(Query)、键(Key)和值(Value)空间。

  • attention_dropout:Dropout层,用于在注意力权重上应用 Dropout,防止过拟合。

  • output_proj:一个额外的线性变换层,用于对多头自注意力的输出进行整合。

  • norm1norm2:两个LayerNorm层,用于在自注意力和前馈网络之后进行归一化。

  • up_projdown_proj:前馈网络的升维和降维线性变换层。

  • act_fn:激活函数,这里使用了GELU。

  • drop_ffn:Dropout层,用于在前馈网络中防止过拟合。

这个方法实现了多头自注意力机制。

def multi_head_self_attn(self, x, attn_mask=None):# x: [batch_size, seq_len, hidden_dim]    x 的形状为 [batch_size, seq_len, hidden_dim]batch_size, seq_len, _ = x.size()query = self.query_proj(x)        # 使用三个线性变换层分别计算查询(query)、键(key)和值(value)。key = self.key_proj(x)value = self.value_proj(x)# [batch_size, seq_len, hidden_dim] -> [batch_size, seq_len, head_num, head_dim]    将查询、键和值的形状从 [batch_size, seq_len, hidden_dim] 转换为 [batch_size, head_num, seq_len, head_dim]。query = query.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)key = key.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)value = value.view(batch_size, seq_len, self.head_num, self.head_dim).transpose(1, 2)attn_score = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(self.head_dim)    # 计算每个头的查询和键的点积,得到注意力分数矩阵。if attn_mask is not None:    # 如果提供了注意力掩码 attn_mask,则将掩码中为 0 的位置对应的注意力分数设置为负无穷(-inf)。attn_mask = attn_mask.tril()attn_score = attn_score.masked_fill(attn_mask == 0, float('-inf'))else:    # 如果没有提供掩码,则生成一个下三角矩阵作为掩码,以防止解码器中的未来信息泄露。attn_mask = torch.ones_like(attn_score).tril()attn_score = attn_score.masked_fill(attn_mask == 0, float('-inf'))attn_score = torch.softmax(attn_score, dim=-1)    # 对注意力分数应用 Softmax 函数,将分数转换为概率值。attn_score = self.attention_dropout(attn_score)    # 对注意力权重应用 Dropout。attn_output = torch.matmul(attn_score, value)    # 使用注意力权重对值进行加权求和,得到每个头的输出。attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)    # 将所有头的输出拼接起来,恢复到原始的形状 [batch_size, seq_len, hidden_dim]。attn_output = self.output_proj(attn_output)    # 通过一个线性变换层 output_proj 对拼接后的输出进行整合。return attn_output

这个方法实现了前馈神经网络(FFN)。

  1. 首先通过 up_proj 将输入数据升维到 hidden_dim * 4

  2. 应用激活函数 GELU。

  3. 通过 down_proj 将数据降维回 hidden_dim

  4. 应用 Dropout 防止过拟合。

    def ffn(self, x):x = self.up_proj(x)    # 首先通过 up_proj 将输入数据升维到 hidden_dim * 4。x = self.act_fn(x)    # 应用激活函数 GELU。x = self.down_proj(x)    # 通过 down_proj 将数据降维回 hidden_dim。x = self.drop_ffn(x)return x

    这是解码器层的前向传播方法。

      def forward(self, x, attn_mask=None):x = x + self.multi_head_self_attn(x, attn_mask)    # 将输入数据 x 与多头自注意力的输出相加,然后通过 LayerNorm 层 norm1 进行归一化。x = self.norm1(x)    # 通过 LayerNorm 层 norm1 进行归一化。x = x + self.ffn(x)    # 将归一化后的数据与前馈神经网络的输出相加,然后通过 LayerNorm 层 norm2 进行归一化。x = self.norm2(x)return x

      Decoder解释

      • vocab_size:词汇表大小,表示输入数据的词汇量。

      • hidden_dim:隐藏层维度。

      • head_num:头的数量。

      • dropout_rate:Dropout比率。

      • layer_list:一个包含 6 个解码器层的 ModuleList

      • emb:一个嵌入层,用于将输入的词汇索引映射到隐藏层维度的向量。

      • out:一个线性变换层,用于将解码器的输出映射到词汇表大小的维度

      forward

      def forward(self, x, attn_mask=None):x = self.emb(x)    # 使用嵌入层 emb 将输入的词汇索引映射到隐藏层维度的向量。for layer in self.layer_list:    # 依次将数据通过 6 个解码器层。x = layer(x, attn_mask)x = self.out(x)    # 最后,通过线性变换层 out 将解码器的输出映射到词汇表大小的维度。return torch.softmax(x, dim=-1)    # 应用 Softmax 函数,将输出转换为概率分布。

      -

      -

      总结

      总的来说,自注意力机制是一种强大的神经网络架构组件,用于动态地衡量输入序列中不同位置之间的关联程度。它通过计算查询(Query)、键(Key)和值(Value)之间的点积,生成注意力权重,再利用这些权重对值进行加权求和,从而实现对输入数据的全局信息捕捉。这种机制允许模型在处理每个元素时,同时考虑整个序列的信息,有效解决了传统序列模型难以捕捉长距离依赖的问题。自注意力机制的核心优势在于其并行计算能力和对全局信息的高效利用,使其在自然语言处理和计算机视觉等领域得到了广泛应用。

      如果你喜欢我的内容,别忘了点赞、关注和收藏哦!你的每一个支持都是我不断进步的动力,也让我更有信心继续创作更多有价值的内容。感谢你的陪伴,让我们一起在知识的海洋里探索更多!❤️✨

      相关文章:

      人工智能入门课【手写自注意力机制】

      原理 自注意力(Self-Attention)是一种强大的机制,广泛应用于自然语言处理、计算机视觉等领域,尤其是在Transformer架构中发挥了关键作用。它的核心思想是让模型能够动态地关注输入序列中不同位置之间的关系,从而更好地…...

      记7(激活函数+多层神经网络+梯度下降法及其优化

      目录 1、激活函数1.1、sigmoid函数:2端饱和,下面2个函数都要幂运算,运算速度会比较慢1.2、ReLU函数(Rectified Linear Unit,修正线性单元)1.3、PReLU函数(Parameteric Rectified Linear Unit&am…...

      Qt u盘自动升级软件

      Qt u盘自动升级软件 Chapter1 Qt u盘自动升级软件u盘自动升级软件思路:step1. 获取U盘 判断U盘名字是否正确, 升级文件是否存在。step2. 升级step3. 升级界面 Chapter2 Qt 嵌入式设备应用程序,通过U盘升级的一种思路Chapter3 在开发板上运行的…...

      关于低代码技术架构的思考

      我们经常会看到很多低代码系统的技术架构图,而且经常看不懂。是因为技术架构图没有画好,还是因为技术不够先进,有时候往往都不是。 比如下图: 一个开发者,看到的视角往往都是技术层面,你给用户讲React18、M…...

      如何使用 ChatBox AI 简化本地模型对话操作

      部署模型请看上一篇帖子:本地部署DeepSeek教程(Mac版本)-CSDN博客 使用 ChatBox AI 简化本地模型对话操作: 打开 ChatBox AI 官网:Chatbox AI官网:办公学习的AI好助手,全平台AI客户端&#xf…...

      缩位求和——蓝桥杯

      1.题目描述 在电子计算机普及以前,人们经常用一个粗略的方法来验算四则运算是否正确。 比如:248153720248153720 把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是 1 位数,得 24814>145 156 56 而…...

      hexo部署到github page时,hexo d后page里面绑定的个人域名消失的问题

      Hexo 部署博客到 GitHub page 后,可以在 setting 中的 page 中绑定自己的域名,但是我发现更新博客后绑定的域名消失,恢复原始的 githubio 的域名。 后面搜索发现需要在 repo 里面添加 CNAME 文件,内容为 page 里面绑定的域名&…...

      neo4j入门

      文章目录 neo4j版本说明部署安装Mac部署docker部署 neo4j web工具使用数据结构图数据库VS关系数据库 neo4j neo4j官网Neo4j是用ava实现的开源NoSQL图数据库。Neo4作为图数据库中的代表产品,已经在众多的行业项目中进行了应用,如:网络管理&am…...

      代码随想录——回溯

      文章目录 组合组合总数电话号码的字母组合组合总数组合总数Ⅱ分割回文串复原IP地址子集子集Ⅱ非递减子序列去重的实现方法方法 1:**排序 跳过重复元素**方法 2:**使用哈希表或数组记录已使用的数字** 去重的完整示例总结本题代码 全排列全排列Ⅱ重新安排…...

      独立游戏RPG回顾:高成本

      刚看了某纪录片, 内容是rpg项目的回顾。也是这个以钱为核心话题的系列的最后一集。 对这期特别有代入感,因为主角是曾经的同事,曾经在某天晚上听过其项目组的争论。 对其这些年的起伏特别的能体会。 主角是制作人,在访谈中透露这…...

      SQLModel入门

      目录 概述快速开始官方教程简单使用样例 概述 SQLModel 是一个 ORM 框架,其基于 SQLAlchemy 和 Pydantic,其中 SQLALchemy 提供底层 ORM 能力,Pydantic 提供类型校验能力,SQLModel 中,一个 SQLModel model 既是一个 S…...

      关于MySQL InnoDB存储引擎的一些认识

      文章目录 一、存储引擎1.MySQL中执行一条SQL语句的过程是怎样的?1.1 MySQL的存储引擎有哪些?1.2 MyIsam和InnoDB有什么区别? 2.MySQL表的结构是什么?2.1 行结构是什么样呢?2.1.1 NULL列表?2.1.2 char和varc…...

      【学习笔记】深度学习网络-正则化方法

      作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程,深度学习领域研究生必读教材),开始深度学习领域学习,深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…...

      NVIDIA (英伟达)的 GPU 产品应用领域

      游戏娱乐领域 PC 游戏:NVIDIA 的 GeForce 系列 GPU 是 PC 游戏玩家的首选之一。能实现实时光线追踪、高分辨率渲染等,使游戏画面更加逼真,如《赛博朋克 2077》等支持光线追踪的游戏,在 NVIDIA GPU 的加持下,可呈现出真…...

      Docker快速部署高效照片管理系统LibrePhotos搭建私有云相册

      文章目录 前言1.关于LibrePhotos2.本地部署LibrePhotos3.LibrePhotos简单使用4. 安装内网穿透5.配置LibrePhotos公网地址6. 配置固定公网地址 前言 想象一下这样的场景:你有一大堆珍贵的回忆照片,但又不想使用各种网盘来管理。怎么办?别担心…...

      goframe 多语言国际化解决方案

      项目背景 本项目采用基于JSON配置的多语言国际化(i18n)解决方案,支持多种语言的无缝切换和本地化。 目录结构 manifest/ └── i18n/├── zh.json # 简体中文├── zh-tw.json # 繁体中文├── en.json # 英语├…...

      mysql如何修改密码

      在MySQL中修改密码可以通过多种方式完成,具体取决于你的MySQL版本和你是否有足够的权限。以下是一些常用的方法来修改MySQL用户的密码: 方法1: 使用ALTER USER命令 这是最常用的方法,适用于MySQL 5.7及以上版本。 ALTER USER usernameloca…...

      17.2 图形绘制8

      版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 17.2.10 重绘 先看以下例子: 【例 17.28】【项目:code17-028】绘制填充矩形。 private void button1_Clic…...

      Java基础知识总结(三十八)--读取数据

      使用Reader体系,读取一个文本文件中的数据。返回 -1 ,标志读到结尾。 import java.io.*; class { public static void main(String[] args) throws IOException { /* 创建可以读取文本文件的流对象,让创建好的流对象和指定的文件相关联。…...

      【并查集】

      并查集(Disjoint Set Union,DSU)是一种用于处理不相交集合的数据结构,主要支持两种操作:查找(Find)和合并(Union)。它在解决连通性问题、图论问题以及动态连通性等问题时…...

      SQL NOW() 函数详解

      SQL NOW() 函数详解 引言 在SQL数据库中,NOW() 函数是一个常用的日期和时间函数,用于获取当前的时间戳。本文将详细介绍 NOW() 函数的用法、参数、返回值以及在实际应用中的注意事项。 函数概述 NOW() 函数返回当前的日期和时间,格式为 Y…...

      [EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率

      Paper Card 论文标题:FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者:Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...

      Rust 条件语句

      Rust 条件语句 在编程语言中,条件语句是进行决策和实现分支逻辑的关键。Rust 语言作为一门系统编程语言,其条件语句的使用同样至关重要。本文将详细介绍 Rust 中的条件语句,包括其基本用法、常见场景以及如何避免常见错误。 基本用法 Rust…...

      Windows 上安装 PostgreSQL

      Windows 上安装 PostgreSQL PostgreSQL 是一款功能强大的开源对象-关系型数据库系统,它具有出色的扩展性和稳定性。本文将详细介绍在 Windows 操作系统上安装 PostgreSQL 的步骤和注意事项。 1. 准备工作 在开始安装 PostgreSQL 之前,请确保您的计算机满足以下要求: 操作…...

      UE 5.3 C++ 对垃圾回收的初步认识

      一.UObject的创建 UObject 不支持构造参数。 所有的C UObject都会在引擎启动的时候初始化,然后引擎会调用其默认构造器。如果没有默认的构造器,那么 UObject 将不会编译。 有修改父类参数的需求,就使用指定带参构造 // Sets default value…...

      解码,蓝桥杯2020G

      a2b 解码后&#xff1a;aab ​ #include<iostream> using namespace std; typedef struct Node {char data;int size;Node* next; }Node,*Linklist; char* scan(char str[],int size) {int i 0;Linklist head new Node;Linklist rear head;while (i<size-1) {Lin…...

      【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(一)

      ✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;贪心算法篇–CSDN博客 文章目录 一.贪心算法1.什么是贪心算法2.贪心算法的特点 二.例题1.柠…...

      Python3 + Qt5:实现AJAX异步更新UI

      使用 Python 和 Qt5 开发时异步加载数据的方法 在开发使用 Python 和 Qt5 的应用程序时&#xff0c;为了避免在加载数据时界面卡顿&#xff0c;可以采用异步加载的方式。以下是几种实现异步加载的方法&#xff1a; 1. 使用多线程&#xff08;QThread&#xff09; 通过将数据…...

      Windows系统中Docker可视化工具对比分析,Docker Desktop,Portainer,Rancher

      Docker可视化工具对比分析&#xff0c;Docker Desktop&#xff0c;Portainer&#xff0c;Rancher Windows系统中Docker可视化工具对比分析1. 工具概览2. Docker Desktop官网链接&#xff1a;主要优点&#xff1a;主要缺点&#xff1a;版本更新频率&#xff1a; 3. Portainer官网…...

      从ai产品推荐到利用cursor快速掌握一个开源项目再到langchain手搓一个Text2Sql agent

      目录 0. 经验分享&#xff1a;产品推荐 1. 经验分享&#xff1a;提示词优化 2. 经验分享&#xff1a;使用cursor 阅读一篇文章 3. 经验分享&#xff1a;使用cursor 阅读一个完全陌生的开源项目 4. 经验分享&#xff1a;手搓一个text2sql agent &#xff08;使用langchain l…...

      curope python安装

      目录 curope安装 测试: 报错:libc10.so: cannot open shared object file: No such file or directory 解决方法: curope安装 git clone : GitHub - Junyi42/croco at bd6f4e07d5c4f13ae5388efc052dadf142aff754 cd models/curope/ python setup.py build_ext --inplac…...

      低代码产品插件功能一览

      下图是统计的目前市面上流行的低代码、零代码产品的插件功能。 产品名称 产品类型 官方插件数量 支持拓展 官方插件功能 宜搭 零代码 3 暂不支持 云打印、CAD看图、打印表单详情 微搭 低代码 1 暂不支持 小程序 明道云 低代码 2 支持 视图、工作流节点 简道…...

      流浪 Linux: 外置 USB SSD 安装 ArchLinux

      注: ArchLinux 系统为滚动更新, 变化很快, 所以本文中的安装方法可能很快就过时了, 仅供参考. 实际安装时建议去阅读官方文档. 最近, 突然 (也没有那么突然) 有了一大堆 PC: 4 个笔记本, 2 个台式主机 (M-ATX 主板), 1 个小主机 (迷你主机). 嗯, 多到用不过来. 但是, 窝又不能…...

      开启 AI 学习之旅:从入门到精通

      最近 AI 真的超火,不管是工作还是生活里,到处都能看到它的身影。好多小伙伴都跑来问我,到底该怎么学 AI 呢?今天我就把自己学习 AI 的经验和心得分享出来,希望能帮到想踏入 AI 领域的朋友们! 一、学习内容有哪些 (一)编程语言 Python 绝对是首选!它在 AI 领域的生态…...

      笔记:使用ST-LINK烧录STM32程序怎么样最方便?

      一般板子在插件上&#xff0c; 8脚 3.3V;9脚 CLK;10脚 DIO;4脚GND ST_Link 19脚 3.3V;9脚 CLK;7脚 DIO;20脚 GND 烧录软件&#xff1a;ST-LINK Utility&#xff0c;Keil_5; ST_Link 接口针脚定义&#xff1a; 按定义连接ST_Link与电路板&#xff1b; 打开STM32 ST-LINK Uti…...

      开发环境搭建-4:WSL 配置 docker 运行环境

      在 WSL 环境中构建&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 基本概念说明 容器技术 利用 Linux 系统的 文件系统&#xff08;UnionFS&#xff09;、命名空间&#xff08;namespace&#xff09;、权限管理&#xff08;cgroup&#xff09;&#xff0c;虚拟出一…...

      925.长按键入

      目录 一、题目二、思路三、解法四、收获 一、题目 你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&am…...

      【数据采集】案例01:基于Scrapy采集豆瓣电影Top250的详细数据

      基于Scrapy采集豆瓣电影Top250的详细数据 Scrapy 官方文档:https://docs.scrapy.org/en/latest/豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:基于Scrapy框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat Python…...

      doris:主键模型的导入更新

      这篇文档主要介绍 Doris 主键模型基于导入的更新。 整行更新​ 使用 Doris 支持的 Stream Load、Broker Load、Routine Load、Insert Into 等导入方式&#xff0c;向主键模型&#xff08;Unique 模型&#xff09;导入数据时&#xff0c;如果没有相应主键的数据行&#xff0c;…...

      日志2025.2.1

      日志2025.2.1 1.做了敌人状态机 public class EnermyStateMachine { public EnermyState currentState { get; private set; } public void InitializeState(EnermyState startState) { currentState startState; currentState.Enter(); } public void Change…...

      RK3568使用QT操作LED灯

      文章目录 一、QT中操作硬件设备思路Linux 中的设备文件操作硬件设备的思路1. 打开设备文件2. 写入数据到设备3. 从设备读取数据4. 设备控制5. 异常处理在 Qt 中操作设备的典型步骤实际应用中的例子:控制 LED总结二、QT实战操作LED灯设备1. `mainwindow.h` 头文件2. `mainwindo…...

      Rank-analysis-1.2——一款基于LCU API的排位分析工具,大四学生独立开发

      LOL Rank Record Analysis&#xff1a;一款基于LCU API的排位分析工具&#xff0c;大四学生独立开发&#xff01; 大家好&#xff01;我是河南科技学院的大四学生&#xff0c;今天给大家分享一个我自己开发的软件——LOL Rank Record Analysis。这是一个基于 Riot 提供的 LCU …...

      关于系统重构实践的一些思考与总结

      文章目录 一、前言二、系统重构的范式1.明确目标和背景2.兼容屏蔽对上层的影响3.设计灰度迁移方案3.1 灰度策略3.2 灰度过程设计3.2.1 case1 业务逻辑变更3.2.2 case2 底层数据变更&#xff08;数据平滑迁移&#xff09;3.2.3 case3 在途新旧流程兼容3.2.4 case4 接口变更3.2.5…...

      代码随想录-训练营-day17

      235. 二叉搜索树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/class S…...

      C++,STL,【目录篇】

      文章目录 一、简介二、内容提纲第一部分&#xff1a;STL 概述第二部分&#xff1a;STL 容器第三部分&#xff1a;STL 迭代器第四部分&#xff1a;STL 算法第五部分&#xff1a;STL 函数对象第六部分&#xff1a;STL 高级主题第七部分&#xff1a;STL 实战应用 三、写作风格四、…...

      《数据可视化新高度:Graphy的AI协作变革》

      在数据洪流奔涌的时代&#xff0c;企业面临的挑战不再仅仅是数据的收集&#xff0c;更在于如何高效地将数据转化为洞察&#xff0c;助力决策。Graphy作为一款前沿的数据可视化工具&#xff0c;凭借AI赋能的团队协作功能&#xff0c;为企业打开了数据协作新局面&#xff0c;重新…...

      abc 390 D(暴搜 复杂度用 bell数 证明 n 的集合的划分方法的数目)

      D题意&#xff1a; 将 长度为 N 的数组 划分为集合 有多少种不同的 异或和 这道题做出来和bell 数没什么关系&#xff0c;如果要证明复杂度那么就需要bell 数 #include <bits/stdc.h> using namespace std; typedef pair<int, int> PII; #define int long long i…...

      AJAX综合案例——图书管理

      黑马程序员视频地址&#xff1a; AJAX-Day02-10.案例_图书管理AJAX-Day02-10.案例_图书管理_总结_V1.0是黑马程序员前端AJAX入门到实战全套教程&#xff0c;包含学前端框架必会的&#xff08;ajaxnode.jswebpackgit&#xff09;&#xff0c;一套全覆盖的第25集视频&#xff0c…...

      计算机网络 笔记 网络层 3

      IPv6 IPv6 是互联网协议第 6 版&#xff08;Internet Protocol Version 6&#xff09;的缩写&#xff0c;它是下一代互联网协议&#xff0c;旨在解决 IPv4 面临的一些问题&#xff0c;以下是关于 IPv6 的详细介绍&#xff1a; 产生背景&#xff1a; 随着互联网的迅速发展&…...

      Web服务器启动难题:Spring Boot框架下的异常处理解析

      摘要 在尝试启动Web服务器时遇到了无法启动的问题&#xff0c;具体错误为org.springframework.boot.web.server.WebServerException。这一异常表明Spring Boot框架在初始化Web服务器过程中出现了故障。通常此类问题源于配置文件错误、端口冲突或依赖项缺失等。排查时应首先检查…...