大模型原理:实现带可训练权重的自注意力机制-封装类

实现方式1

实现自注意封装类,计算所有上下文向量,类中的代码与 大模型原理:实现带可训练权重的自注意力机制(以第二个输入元素为计算例子) 几乎相同

计算过程

在自注意力机制中:

  1. 用3个权重矩阵 W_q、W_k 和 W_v 来变换输入矩阵 X 中的输入向量。

  2. 新方法根据所得查询矩阵(Q)和键矩阵(k)计算注意力权重矩阵

  3. 使用注意力权重矩阵和值矩阵(V)计算上下文向量(Z)

自注意力机制包含了可训练的权重矩阵 W_q、W_k 和 W_v。这些矩阵将输入数据转换为查询向量、键向量和值向量,这些组件在注意力机制中至关重要。随着模型在训练中接触更多数据,它会 调整这些可训练的权重

提示:

  • 为了视觉清晰,关注具有 n 个词元的单个输入文本,而不是一批多个输入

  • 图中的数值被截断为 小数点后两位,以减少视觉混乱。每行中的值加起来应为 1.0 或 100%

封装类

SelfAttention_v1 必须继承 nn.Module类,该类为模型层的创建和管理提供了必要的功能。

import torch
import torch.nn as nn


class SelfAttention_v1(nn.Module):
    """
    必须继承nn.Module类,该类为模型层的创建和管理提供了必要的功能
    """
    def __init__(self, d_in, d_out):
        """
        初始化可训练的权重矩阵
        :param d_in: 输入嵌入维度
        :param d_out: 输出嵌入维度
        """
        super().__init__()
        self.W_query = nn.Parameter(torch.rand(d_in, d_out))
        self.W_key = nn.Parameter(torch.rand(d_in, d_out))
        self.W_value = nn.Parameter(torch.rand(d_in, d_out))

    def forward(self, x):
        """
        前向传播
        :param x:
        :return:
        """
        keys = x @ self.W_key
        queries = x @ self.W_query
        values = x @ self.W_value
        attn_scores = queries @ keys.T
        attn_weights = torch.softmax(
            attn_scores / keys.shape[-1] ** 0.5, dim=-1
        )
        context_vec = attn_weights @ values
        return context_vec

使用这个类

# 输入的嵌入向量
inputs = torch.tensor(
    [[0.43, 0.15, 0.89],  # Your     (x^1)
     [0.55, 0.87, 0.66],  # journey  (x^2)
     [0.57, 0.85, 0.64],  # starts   (x^3)
     [0.22, 0.58, 0.33],  # with     (x^4)
     [0.77, 0.25, 0.10],  # one      (x^5)
     [0.05, 0.80, 0.55]]  # step     (x^6)
)

# 输入嵌入维度d_in=3
d_in = inputs.shape[1]
# 输出嵌入维度d_out=2
d_out = 2

torch.manual_seed(123)
sa_v1 = SelfAttention_v1(d_in, d_out)
print("上下文向量:", sa_v1(inputs))

执行结果:

上下文向量: tensor([[0.2996, 0.8053],
        [0.3061, 0.8210],
        [0.3058, 0.8203],
        [0.2948, 0.7939],
        [0.2927, 0.7891],
        [0.2990, 0.8040]], grad_fn=<MmBackward0>)

可以看到第2行 [0.3061, 0.8210] 与 大模型原理:实现带可训练权重的自注意力机制(以第二个输入元素为计算例子) 中的 context_vec_2 相同

实现方式2(改进版)

通过使用PyTorch的 nn.Linear 层来进一步优化,当偏置单元被禁用时,nn.Linear 层可以有效地执行矩阵乘法。
相比手动实现nn.Parameter(torch.rand(...)),使用nn.Linear 的一个重要优势是:提供了 优化的权重初始化方案,从而有助于模型训练的稳定性和有效性

封装类

import torch
import torch.nn as nn


class SelfAttention_v2(nn.Module):
    def __init__(self, d_in, d_out, qkv_bias=False):
        super().__init__()
        self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
        self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)

    def forward(self, x):
        keys = self.W_key(x)
        queries = self.W_query(x)
        values = self.W_value(x)
        attn_scores = queries @ keys.T
        attn_weights = torch.softmax(
            attn_scores / keys.shape[-1]**0.5, dim=-1
        )
        context_vec = attn_weights @ values
        return context_vec

使用该类

# 输入的嵌入向量
inputs = torch.tensor(
    [[0.43, 0.15, 0.89],  # Your     (x^1)
     [0.55, 0.87, 0.66],  # journey  (x^2)
     [0.57, 0.85, 0.64],  # starts   (x^3)
     [0.22, 0.58, 0.33],  # with     (x^4)
     [0.77, 0.25, 0.10],  # one      (x^5)
     [0.05, 0.80, 0.55]]  # step     (x^6)
)

# 输入嵌入维度d_in=3
d_in = inputs.shape[1]
# 输出嵌入维度d_out=2
d_out = 2

torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print("上下文向量:", sa_v2(inputs))

执行结果:

上下文向量: tensor([[-0.5337, -0.1051],
        [-0.5323, -0.1080],
        [-0.5323, -0.1079],
        [-0.5297, -0.1076],
        [-0.5311, -0.1066],
        [-0.5299, -0.1081]], grad_fn=<MmBackward0>)

注意: SelfAttention_v1 和 SelfAttention_v2 因为使用了不同的初始权重矩阵而给出了不同的输出,这是由 nn.Linear 使用了一个更复杂的权值初始化方案所导致的。


原文出处:/show_1GW2cIKgykhk.html