Transformer复习回顾

本篇博客对Transformer进行了回顾,并动手实践了一下,后续会继续完善遇到的小问题!

需要关注的

需要理解Attention、Self-Attention、Mult-head Attention三部分内容
理论部分参考:李宏毅机器学习2021
博客参考:

  1. 【Transformer 相关理论深入理解】注意力机制、自注意力机制、多头注意力机制、位置编码
  2. Transformer模型详解(图解最完整版)这篇文章的图很清晰,重点看!

其他参考:
LLMs 相关知识及面试题
【研1基本功 (真的很简单)注意力机制】手写多头注意力机制

Attention

  1. 什么是注意力机制?
    它的核心思想是让模型在处理序列数据时,能够聚焦于输入数据中与当前任务最相关的部分。
  2. 如何做注意力?
  3. 需要注意的内容
    • 为什么softmax前要缩放?为什么是除以维度的根号?
      可以参考:self-attention为什么要除以根号d_k
      a. 首先要除以一个数,防止输入softmax的值过大,导致偏导数趋近于0
      b. 选择根号d_k是因为可以使 得q*k的结果满足期望为0,方差为1的分布,类似于归一化。

Self-Attention

Self-Attention 的关键点:Q、K、V是同一个东西,或者三者来源于同一个X,三者同源。
通过X找到X里面的关键点,从而更关注X的关键信息,忽略X的不重要信息。

  1. Attention和Self-Attention的区别
  2. 如何做自注意力
  3. 自注意力的意义
  4. Self-Attention vs LSTM&RNN

Mult-head Attention

  1. 什么是多头?
  2. 为什么多头?有什么作用
    1. 并行处理能力:多头注意力允许模型并行地处理多个注意力头,这可以显著提高计算效率。
    2. 捕捉不同信息:每个注意力头可以学习到序列的不同方面或特征,比如一个头可能关注语法结构,而另一个头可能关注语义信息。这样,模型可以更全面地理解输入数据。
    3. 增强表达能力:通过结合多个注意力头的输出,模型可以构建一个更加丰富和复杂的表示,这有助于捕捉更细微的语言特征。
    4. 减少参数冗余:尽管多头注意力增加了模型的复杂度,但它通过共享底层参数(如查询、键和值的参数)来减少整体参数数量,从而避免参数冗余。
    5. 提高泛化能力:多头注意力机制可以帮助模型更好地泛化到未见过的数据上,因为它可以从不同的角度理解输入,而不是依赖单一的、可能过于简化的表示。
    6. 灵活性和可扩展性:多头注意力机制非常灵活,可以根据任务的需要调整注意力头的数量,以适应不同的复杂性和数据特性。
import math
import torch
print(torch.randn(1,1,1))  # [batch, time ,dimension]

class multi_head_attention(torch.nn.Module):
    def __init__(self, d_model, num_heads)->None:
        super(multi_head_attention, self).__init__()

        self.num_heads = num_heads
        self.d_model = d_model
        self.w_q = torch.nn.Linear(d_model, d_model)
        self.w_k = torch.nn.Linear(d_model, d_model)
        self.w_v = torch.nn.Linear(d_model, d_model)
        self.w_combine = torch.nn.Linear(d_model, d_model)
        self.softmax = torch.nn.Softmax(dim=-1)

    def forward(self, query, key, value):
        batch_size, time, dim = query.shape
        n_d = self.d_model // self.num_heads
        q, k, v = self.w_q(query), self.w_k(key), self.w_v(value)

        q = q.view(batch_size, time, self.num_heads, n_d).permute(0, 2, 1, 3)
        k = k.view(batch_size, time, self.num_heads, n_d).permute(0, 2, 1, 3)
        v = v.view(batch_size, time, self.num_heads, n_d).permute(0, 2, 1, 3)

        score = q @ k.transpose(2,3) / math.sqrt(n_d)
        mask = torch.tril(torch.ones(time,time,dtype=torch.bool))	# 生成下三角矩阵(对角线下方是1),padding_mask的处理方式不同
        score = score.masked_fill(mask==0,float('-inf'))
        score = self.softmax(score) @ v		# @是矩阵乘法运算,同torch.matmul() 

        score = score.permute(0, 2, 1, 3).contiguous(). view(batch_size, time, self.d_model)

        output = self.w_combine(score)
        return output

attention = multi_head_attention(d_model=512, num_heads=8)
X = torch.randn(128,64,512)
output = attention(X,X,X)

print(output,output.size())

位置编码:Positional Encoding

  1. 为什么需要位置编码
  2. 怎么做位置编码
  3. 位置编码的生成

mask细节

在 Transformer 模型中,mask 用于控制注意力机制的计算,确保模型能够处理不同类型的序列数据。
两个 mask 的实现:make_pad_maskmake_casual_mask。它们分别用于处理不同的任务。

1. make_pad_mask

import torch

# 示例数据
"""
src = torch.tensor([[1, 2, 0, 0], [3, 4, 5, 0]]) 表示的是一个批次(batch)的两个句子,每个句子有 4 个词(或位置),每个位置的值表示词的索引。

具体来说:
src 的形状是 (2, 4),表示有 2 个句子(或者说 2 个样本),每个句子有 4 个位置。
1, 2, 0, 0 和 3, 4, 5, 0 分别是两个句子的词索引序列。
在这个上下文中:

1, 2, 3, 4, 5 是词的索引。
0 是填充符,用来补齐不同长度的句子。
"""

src = torch.tensor([[1, 2, 0, 0], 
					[3, 4, 5, 0]])
pad_idx = 0

def make_pad_mask(q, k, pad_idx_q, pad_idx_k):
    len_q, len_k = q.size(1), k.size(1)

    q_mask = q.ne(pad_idx_q).unsqueeze(1).unsqueeze(3)
    q_mask = q_mask.repeat(1, 1, 1, len_k)

    k_mask = k.ne(pad_idx_k).unsqueeze(1).unsqueeze(2)
    k_mask = k_mask.repeat(1, 1, len_q, 1)

    mask = q_mask & k_mask
    return mask

# 生成填充掩码
src_mask = make_pad_mask(src, src, pad_idx, pad_idx)
print(src_mask)

# 输出
tensor([[[[ True,  True, False, False],
          [ True,  True, False, False],
          [False, False, False, False],
          [False, False, False, False]]],
        [[[ True,  True,  True, False],
          [ True,  True,  True, False],
          [ True,  True,  True, False],
          [False, False, False, False]]]])

make_pad_mask 函数用于生成填充(padding)掩码。这个掩码确保模型在计算注意力时忽略填充的部分,避免对这些填充部分进行无用的计算。

实现细节:

  • q.ne(pad_idx_q).unsqueeze(1).unsqueeze(3):首先,将查询张量 q 中的填充位置转换为布尔值(填充的位置为 False,其他为 True),然后扩展维度以便后续的广播操作。
  • q.repeat(1, 1, 1, len_k):重复 q 的掩码,以匹配 k 的维度。
  • k.ne(pad_idx_k).unsqueeze(1).unsqueeze(2):将键张量 k 中的填充位置转换为布尔值,并扩展维度以便后续的广播操作。
  • k.repeat(1, 1, len_q, 1):重复 k 的掩码,以匹配 q 的维度。
  • q & k:对查询和键的掩码进行按位与操作,得到最终的填充掩码。

用途:确保模型在计算注意力时,不关注填充的部分,只关注有效的序列数据。
使用位置:填充掩码通常应用于Encoder 和 Decoder 中的自注意力(Self-Attention)机制,以及 Decoder 中的Encoder-Decoder Attention 机制。

填充掩码的应用:

2. make_casual_mask

import torch

# 示例数据
trg = torch.tensor([[6, 7, 8], [9, 10, 11]])

def make_casual_mask(q, k):
    len_q, len_k = q.size(1), k.size(1)
    mask = torch.tril(torch.ones(len_q, len_k, dtype=torch.bool)).to(q.device)
    return mask

# 生成自回归掩码
trg_mask = make_casual_mask(trg, trg)
print(trg_mask)

# 输出
tensor([[ True, False, False],
        [ True,  True, False],
        [ True,  True,  True]])
trg_mask[i][j]True 时,位置 i 的元素可以关注到位置 j 的元素

make_casual_mask 函数用于生成自回归(causal)掩码。这个掩码确保模型在生成序列时不会看到未来的信息,从而保持生成的自回归特性。

实现细节:

  • torch.tril(torch.ones(len_q, len_k)).type(torch.BoolTensor).to(self.device):创建一个下三角矩阵,确保当前位置只关注当前位置及之前的位置,未来的位置被掩盖。这是通过生成一个全为 1 的矩阵,然后使用 tril 函数将矩阵转换为下三角矩阵。
  • type(torch.BoolTensor).to(self.device):将矩阵转换为布尔类型并移动到指定设备(通常是 GPU)。

用途: 确保模型在生成序列时,不会利用当前时间步之后的词,这对于训练自回归模型非常重要。
使用位置:自回归掩码主要应用于 Decoder 的自注意力(Self-Attention)机制中。

总结

  • make_pad_mask 用于处理填充位置,确保模型在计算注意力时忽略填充部分。
  • make_casual_mask 用于自回归掩码,确保模型在生成序列时只关注当前位置及之前的位置。

这两个掩码在 Transformer 模型中发挥了不同的作用,分别处理填充和自回归的问题。

训练Transformer的流程(以文本翻译为例)

在训练 Transformer 模型时,每一步处理的数据维度可能会有所不同。以下是每一步及其对应的一般数据维度的概述:

  1. 原始文本

    • 维度:N/A(文本数据)
  2. 分词

    • 输入:句子列表(例如,[“Hello world”])
    • 输出:单词列表(例如,[“Hello”, “world”])
    • 维度:N/A(列表数据)
  3. 索引化

    • 输入:单词列表
    • 输出:单词索引列表(例如,[5, 10])
    • 维度:[句子数量, 序列长度]
  4. 填充或截断

    • 输入:索引列表
    • 输出:填充或截断后的索引矩阵(例如,[[5, 10, 0, …, 0]])
    • 维度:[batch_size, seq_len]
  5. 词嵌入

    • 输入:索引矩阵
    • 输出:词嵌入矩阵(每个索引转换为一个嵌入向量)
    • 维度:[batch_size, seq_len, embedding_dim]
  6. 位置编码

    • 输入:词嵌入矩阵
    • 输出:位置编码矩阵(与词嵌入矩阵相加)
    • 维度:[batch_size, seq_len, embedding_dim]
  7. 编码器处理

    • 输入:位置编码矩阵
    • 输出:编码器输出矩阵(经过多头自注意力和前馈网络处理)
    • 维度:[batch_size, seq_len, model_dim]
  8. 解码器处理(对于生成任务,如翻译):

    • 输入:编码器输出矩阵
    • 输出:解码器输出序列(逐个生成的单词的嵌入表示)
    • 维度:[batch_size, seq_len, model_dim]
  9. 损失函数

    • 输入:解码器输出序列与目标序列
    • 输出:损失值(例如,交叉熵损失)
    • 维度:N/A(标量值)
  10. 反向传播

    • 根据损失函数计算梯度,并更新模型参数。
  11. 优化

    • 使用优化算法调整模型参数。

注意,embedding_dim 是词嵌入的维度,model_dim 是模型内部表示的维度,通常与 embedding_dim 相同或略大。batch_size 是一次处理的样本数量,seq_len 是序列的最大长度,实际长度可能因句子而异,但通过填充或截断操作统一为 seq_len

在实际应用中,这些维度可能会根据具体的模型配置和任务需求进行调整。例如,对于翻译任务,目标序列的处理方式与源序列类似,但目标序列的解码器输出将用于计算损失函数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值