大模型原理:因果注意力(掩码注意力)-封装类

介绍

把 因果注意力 和 dropout 修改应用 大模型原理:实现带可训练权重的自注意力机制-封装类计算所有上下文向量 中开发的 SelfAttention Python类中。

这个类将成为开发多头注意力的基础,而多头注意力是我们最终实现的注意力类。

实现

导包

import torch
import torch.nn as nn

封装类

class CausalAttention(nn.Module):
    def __init__(self, d_in, d_out, context_length, dropout, qkv_bias=False):
        """

        :param d_in: 输入嵌入维度
        :param d_out: 输出嵌入维度
        :param context_length: 上下文长度,用于设置掩码矩阵
        :param dropout: dropout忽略概率
        :param qkv_bias:
        """
        super().__init__()
        self.d_out = d_out
        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)
        # 增加 Dropout 层
        self.dropout = nn.Dropout(dropout)
        self.register_buffer(
            'mask',
            torch.triu(torch.ones(context_length, context_length), diagonal=1)
        )

    def forward(self, x):
        b, num_tokens, d_in = x.shape
        keys = self.W_key(x)
        queries = self.W_query(x)
        values = self.W_value(x)

        # 将维度1和2转置,将批维度保持在第一个位置(0)
        attn_scores = queries @ keys.transpose(1, 2)

        # 直接修改传入的矩阵,而不是返回副本,减少内存
        attn_scores.masked_fill_(
            self.mask.bool()[:num_tokens, :num_tokens], -torch.inf)

        attn_weights = torch.softmax(
            attn_scores / keys.shape[-1]**0.5, dim=-1
        )
        # 对注意力权重矩阵进行dropout操作
        attn_weights = self.dropout(attn_weights)

        # 计算上下文向量
        context_vec = attn_weights @ values
        return context_vec

关键代码说明:

在 __init__ 方法中增加了 self.register_buffer(),生成固定掩码并注册为缓冲区

作用:用于注册无需梯度(不需要训练)、但需随模型保存/迁移设备的张量,是模型 “非训练参数”的标准管理方式;

好处:

  • 只需生成一次,后续重复使用
  • 掩码无需梯度,随模型保存

准备数据

# 输入的嵌入向量
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

模拟批量输入

模拟批量输入,因为真正执行时,是通过数据加载器,批量读取的

# 模拟批量输入,因为真正执行时,是通过数据加载器,批量读取的
batch = torch.stack((inputs, inputs), dim=0)
print("批量数据:\n", batch)
print("批量数据的shape:", batch.shape)

执行结果:

批量数据:
 tensor([[[0.4300, 0.1500, 0.8900],
         [0.5500, 0.8700, 0.6600],
         [0.5700, 0.8500, 0.6400],
         [0.2200, 0.5800, 0.3300],
         [0.7700, 0.2500, 0.1000],
         [0.0500, 0.8000, 0.5500]],

        [[0.4300, 0.1500, 0.8900],
         [0.5500, 0.8700, 0.6600],
         [0.5700, 0.8500, 0.6400],
         [0.2200, 0.5800, 0.3300],
         [0.7700, 0.2500, 0.1000],
         [0.0500, 0.8000, 0.5500]]])
批量数据的shape: torch.Size([2, 6, 3])

计算上下文向量

# 计算上下文向量
print("\n\b计算上下文向量--------")
# 为了复现,设置随机种子
torch.manual_seed(123)
# 词元的数量,用于设置掩码矩阵
context_length = batch.shape[1]
print("context_length:", context_length)
# 传入参数:实例化
ca = CausalAttention(d_in, d_out, context_length, 0.0)
# 计算上下文向量
context_vecs = ca(batch)

print("上下文向量:", context_vecs)
print("上下文向量.shape:", context_vecs.shape)

执行结果:

计算上下文向量--------
context_length: 6
上下文向量: tensor([[[-0.4519,  0.2216],
         [-0.5874,  0.0058],
         [-0.6300, -0.0632],
         [-0.5675, -0.0843],
         [-0.5526, -0.0981],
         [-0.5299, -0.1081]],

        [[-0.4519,  0.2216],
         [-0.5874,  0.0058],
         [-0.6300, -0.0632],
         [-0.5675, -0.0843],
         [-0.5526, -0.0981],
         [-0.5299, -0.1081]]], grad_fn=<UnsafeViewBackward0>)
上下文向量.shape: torch.Size([2, 6, 2])

最终生成的上下文向量是一个三维张量:torch.Size([2, 6, 2]),其中每个词元现在用二维嵌入来表示。


原文出处:/show_1GW2d1wFDXzj.html