transformer.py
ultralytics\nn\modules\transformer.py
目录
2.class TransformerEncoderLayer(nn.Module):
3.class AIFI(TransformerEncoderLayer):
4.class TransformerLayer(nn.Module):
5.class TransformerBlock(nn.Module):
8.class LayerNorm2d(nn.Module):
9.class MSDeformAttn(nn.Module):
10.class DeformableTransformerDecoderLayer(nn.Module):
11.class DeformableTransformerDecoder(nn.Module):
1.所需的库和模块
# Ultralytics YOLO 🚀, AGPL-3.0 license
"""Transformer modules."""
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import constant_, xavier_uniform_
from .conv import Conv
from .utils import _get_clones, inverse_sigmoid, multi_scale_deformable_attn_pytorch
__all__ = (
"TransformerEncoderLayer",
"TransformerLayer",
"TransformerBlock",
"MLPBlock",
"LayerNorm2d",
"AIFI",
"DeformableTransformerDecoder",
"DeformableTransformerDecoderLayer",
"MSDeformAttn",
"MLP",
)
2.class TransformerEncoderLayer(nn.Module):
# 这段代码定义了一个名为 TransformerEncoderLayer 的类,它是 PyTorch 中的一个模块( nn.Module ),用于构建 Transformer 编码器的一个层。
# 定义了一个名为 TransformerEncoderLayer 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。这个类用于定义 Transformer 编码器中的一个单独的层。
class TransformerEncoderLayer(nn.Module):
# 定义 transformer 编码器的单层。
"""Defines a single layer of the transformer encoder."""
# 这是类的构造函数,用于初始化 TransformerEncoderLayer 实例。
# 1.c1 : 输入和输出的特征维度。
# 2.cm : 内部 Feedforward 网络的第一层的维度,默认为 2048。
# 3.num_heads : 在 MultiheadAttention 中使用的头的数量,默认为 8。
# 4.dropout : Dropout 比率,默认为 0.0,表示不使用 Dropout。
# 5.act : 激活函数,默认使用 GELU。
# 6.normalize_before : 一个布尔值,指示是否在每个子层(MultiheadAttention 和 Feedforward)之前进行归一化。
def __init__(self, c1, cm=2048, num_heads=8, dropout=0.0, act=nn.GELU(), normalize_before=False):
# 使用指定的参数初始化 TransformerEncoderLayer 。
"""Initialize the TransformerEncoderLayer with specified parameters."""
# 调用父类( nn.Module )的构造函数,这是 Python 中类的初始化标准做法。
super().__init__()
# 从项目的某个 utils 模块中导入 TORCH_1_9 变量,这个变量可能用于检查 PyTorch 的版本是否满足特定的要求。
from ...utils.torch_utils import TORCH_1_9
# 如果 TORCH_1_9 为 False (即 PyTorch 版本低于 1.9),则抛出 ModuleNotFoundError 异常,因为 nn.MultiheadAttention 的 batch_first=True 参数需要 PyTorch 1.9 或更高版本。
if not TORCH_1_9:
raise ModuleNotFoundError(
"TransformerEncoderLayer() requires torch>=1.9 to use nn.MultiheadAttention(batch_first=True)."
)
# torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None, batch_first=False)
# torch.nn.MultiheadAttention 是 PyTorch 中的一个模块,用于实现多头自注意力机制。这种机制允许模型在不同的表示子空间中并行地学习信息。
# 参数说明 :
# embed_dim : 嵌入的维度,即输入的特征维度。
# num_heads : 多头注意力机制中的头数。
# dropout : Dropout 概率,默认为 0.0,表示不使用 Dropout。
# bias : 一个布尔值,指示是否在线性层中添加偏置项,默认为 True。
# add_bias_kv : 一个布尔值,指示是否为键和值的计算添加偏置,默认为 False。
# add_zero_attn : 一个布尔值,指示是否在自注意力中添加一个额外的零矩阵以学习绝对位置信息,默认为 False。
# kdim : 键的特征维度,默认为 None,此时与 embed_dim 相同。
# vdim : 值的特征维度,默认为 None,此时与 embed_dim 相同。
# batch_first : 一个布尔值,指示输入和输出张量的第一个维度是否为批量大小,默认为 False,即第一个维度为序列长度。
# MultiheadAttention 模块的前向传播函数(forward method)接受以下参数:
# forward(query, key, value, attn_mask=None, key_padding_mask=None)
# 参数说明 :
# query : 查询张量。
# key : 键张量。
# value : 值张量。
# attn_mask : 一个可选的掩码张量,用于屏蔽自注意力中的某些位置。
# key_padding_mask : 一个可选的键填充掩码张量,用于在自注意力中忽略某些键。
# 返回值 :
# MultiheadAttention 的前向传播函数返回一个元组,包含 :
# 1. 多头自注意力的输出。
# 2. 多头自注意力的权重矩阵(如果 need_weights 参数设置为 True,则返回)。
# 初始化一个多头注意力( MultiheadAttention )模块,它接受输入的特征维度 c1 ,头的数量 num_heads ,以及 Dropout 比率。
self.ma = nn.MultiheadAttention(c1, num_heads, dropout=dropout, batch_first=True)
# Implementation of Feedforward model 前馈模型的实现。
# 初始化两个全连接层( Feedforward network ), fc1 将输入从 c1 维度转换到 cm 维度, fc2 将输出从 cm 维度转换回 c1 维度。
self.fc1 = nn.Linear(c1, cm)
self.fc2 = nn.Linear(cm, c1)
# 初始化两个层归一化( LayerNorm )模块,用于对多头注意力和 Feedforward 网络的输出进行归一化。
self.norm1 = nn.LayerNorm(c1)
self.norm2 = nn.LayerNorm(c1)
# torch.nn.Dropout(p=0.5, inplace=False)
# torch.nn.Dropout 是 PyTorch 中的一个模块,用于在神经网络训练过程中随机丢弃(即将输出设置为零)输入单元的一部分,这是一种正则化技术,旨在防止模型过拟合。
# 参数说明 :
# p : 一个浮点数,表示每次训练迭代中被丢弃的输入单元的比例。例如, p=0.5 表示有 50% 的概率将输入单元的输出设置为零。默认值是 0.5。
# inplace : 一个布尔值,指示是否在原地修改输入数据。如果设置为 True ,则输入数据会被直接修改,这可以减少内存消耗,但可能会导致梯度计算的问题。默认值是 False 。
# 在训练模式下, Dropout 层会按照指定的比例随机丢弃输入单元;在评估模式下(即模型推理时), Dropout 层不会丢弃任何单元,即表现得像一个恒等映射。这种模式切换是通过调用模型的 .train() 和 .eval() 方法来实现的。
# 初始化三个 Dropout 模块,用于在多头注意力和 Feedforward 网络的输出上应用 Dropout。
self.dropout = nn.Dropout(dropout)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
# 将激活函数和归一化顺序的参数保存为类的属性。
self.act = act
self.normalize_before = normalize_before
# 这个类定义了 Transformer 编码器的一个层,包括多头注意力、Feedforward 网络、归一化和 Dropout。这些组件共同工作,以实现序列到序列的转换。
# 这段代码是一个 Python 类中的静态方法 with_pos_embed 的定义。静态方法不需要访问类的实例或类属性,它们可以独立于类使用。这个方法的作用是将位置嵌入(position embeddings)添加到给定的张量(tensor)中,如果提供了位置嵌入的话。
# 这是一个装饰器,用于声明 with_pos_embed 是一个静态方法。静态方法不接收类或实例的第一个隐式参数(通常是 self 或 cls ),因此它们不能访问类的属性或方法。
@staticmethod
# 定义了一个名为 with_pos_embed 的静态方法,它接受两个参数 : tensor 和 pos 。
# 1.tensor : 要添加位置嵌入的输入张量。
# 2.pos : 可选的位置嵌入张量。默认值为 None 。
def with_pos_embed(tensor, pos=None):
# 如果提供,则将位置嵌入添加到张量。
"""Add position embeddings to the tensor if provided."""
# 这是方法的返回语句,它执行以下逻辑。 如果 pos 参数为 None ,则直接返回原始的 tensor 。 如果 pos 不为 None ,则将 pos 张量加到 tensor 上,并返回结果。
return tensor if pos is None else tensor + pos
# 这个方法通常用于深度学习模型中,特别是在处理序列数据(如自然语言处理或时间序列数据)时,位置嵌入被用来给模型提供关于序列中元素位置的信息。位置嵌入有助于模型理解单词或时间点的顺序。
# 这段代码定义了一个名为 forward_post 的方法,它是 Transformer 编码器层的前向传播函数,使用了“后归一化”( post-normalization )策略。这意味着在多头自注意力( MultiheadAttention )和前馈网络( Feedforward network )之后应用归一化。
# 定义了一个名为 forward_post 的方法,它是类的实例方法,因为第一个参数是 self 。
# 1.src : 输入的张量,通常是编码器层的输入。
# 2.src_mask : 可选的掩码张量,用于在自注意力计算中屏蔽某些位置。
# 3.src_key_padding_mask : 可选的键填充掩码张量,用于在自注意力计算中忽略某些键(例如,用于处理不等长的序列)。
# 4.pos : 可选的位置嵌入张量。
def forward_post(self, src, src_mask=None, src_key_padding_mask=None, pos=None):
# 通过后归一化执行前向传递。
"""Performs forward pass with post-normalization."""
# 使用 with_pos_embed 静态方法将位置嵌入添加到输入张量 src 中,如果提供了位置嵌入 pos 。结果赋值给 q (查询)和 k (键)。
# def with_pos_embed(tensor, pos=None):
# -> 将位置嵌入(position embeddings)添加到给定的张量(tensor)中,如果提供了位置嵌入的话。如果 pos 参数为 None ,则直接返回原始的 tensor 。 如果 pos 不为 None ,则将 pos 张量加到 tensor 上,并返回结果。
# -> return tensor if pos is None else tensor + pos
q = k = self.with_pos_embed(src, pos)
# 计算多头自注意力。 self.ma 是多头自注意力模块, q 和 k 是查询和键, value 是值, attn_mask 是注意力掩码, key_padding_mask 是键填充掩码。返回的结果是一个元组,其中第一个元素是注意力输出,赋值给 src2 。
src2 = self.ma(q, k, value=src, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0]
# 将自注意力的输出 src2 通过 Dropout 层 self.dropout1 后,加到原始输入 src 上。
src = src + self.dropout1(src2)
# 对结果进行归一化,使用层归一化模块 self.norm1 。
src = self.norm1(src)
# 计算前馈网络的输出。首先,将归一化后的 src 输入到第一个全连接层 self.fc1 ,然后应用激活函数 self.act ,接着通过 Dropout 层 self.dropout ,最后通过第二个全连接层 self.fc2 。结果赋值给 src2 。
src2 = self.fc2(self.dropout(self.act(self.fc1(src))))
# 将前馈网络的输出 src2 通过 Dropout 层 self.dropout2 后,加到归一化后的结果 src 上。
src = src + self.dropout2(src2)
# 对最终的结果进行归一化,使用层归一化模块 self.norm2 ,然后返回这个归一化后的结果。
return self.norm2(src)
# 这个方法展示了 Transformer 编码器层的典型结构,包括多头自注意力和前馈网络,以及在这两个子层之后应用的层归一化和残差连接。这种结构有助于模型捕捉序列数据中的长距离依赖关系,并提高模型的训练稳定性。
# 这段代码定义了一个名为 forward_pre 的方法,它是 Transformer 编码器层的前向传播函数,使用了“前归一化”( pre-normalization )策略。这意味着在多头自注意力( MultiheadAttention )和前馈网络( Feedforward network )之前应用归一化。
# 定义了一个名为 forward_pre 的方法,它是类的实例方法,因为第一个参数是 self 。
# 1.src : 输入的张量,通常是编码器层的输入。
# 2.src_mask : 可选的掩码张量,用于在自注意力计算中屏蔽某些位置。
# 3.src_key_padding_mask : 可选的键填充掩码张量,用于在自注意力计算中忽略某些键(例如,用于处理不等长的序列)。
# 4.pos : 可选的位置嵌入张量。
def forward_pre(self, src, src_mask=None, src_key_padding_mask=None, pos=None):
# 执行带有预规范化的前向传递。
"""Performs forward pass with pre-normalization."""
# 对输入张量 src 进行归一化,使用层归一化模块 self.norm1 ,并将结果赋值给 src2 。
src2 = self.norm1(src)
# 使用 with_pos_embed 静态方法将位置嵌入添加到归一化后的张量 src2 中,如果提供了位置嵌入 pos 。结果赋值给 q (查询)和 k (键)。
q = k = self.with_pos_embed(src2, pos)
# 计算多头自注意力。 self.ma 是多头自注意力模块, q 和 k 是查询和键, value 是值, attn_mask 是注意力掩码, key_padding_mask 是键填充掩码。返回的结果是一个元组,其中第一个元素是注意力输出,赋值给 src2 。
src2 = self.ma(q, k, value=src2, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0]
# 将自注意力的输出 src2 通过 Dropout 层 self.dropout1 后,加到原始输入 src 上。
src = src + self.dropout1(src2)
# 对残差连接的结果进行归一化,使用层归一化模块 self.norm2 ,并将结果赋值给 src2 。
src2 = self.norm2(src)
# 计算前馈网络的输出。首先,将归一化后的 src2 输入到第一个全连接层 self.fc1 ,然后应用激活函数 self.act ,接着通过 Dropout 层 self.dropout ,最后通过第二个全连接层 self.fc2 。结果赋值给 src2 。
src2 = self.fc2(self.dropout(self.act(self.fc1(src2))))
# 将前馈网络的输出 src2 通过 Dropout 层 self.dropout2 后,加到归一化后的结果 src 上,并返回这个结果。
return src + self.dropout2(src2)
# 这段代码定义了一个名为 forward 的方法,它是 Transformer 编码器模块的前向传播函数。这个方法根据类实例的 normalize_before 属性决定是使用前归一化(pre-normalization)还是后归一化(post-normalization)策略来执行前向传播。
# 定义了一个名为 forward 的方法,它是类的实例方法,因为第一个参数是 self 。
# 1.src : 输入的张量,通常是编码器层的输入。
# 2.src_mask : 可选的掩码张量,用于在自注意力计算中屏蔽某些位置。
# 3.src_key_padding_mask : 可选的键填充掩码张量,用于在自注意力计算中忽略某些键(例如,用于处理不等长的序列)。
# 4.pos : 可选的位置嵌入张量。
def forward(self, src, src_mask=None, src_key_padding_mask=None, pos=None):
# 通过编码器模块前向传播输入。
"""Forward propagates the input through the encoder module."""
if self.normalize_before:
# 如果实例的 normalize_before 属性为 True ,则调用 forward_pre 方法执行前向传播,该方法使用前归一化策略。
# def forward_pre(self, src, src_mask=None, src_key_padding_mask=None, pos=None): -> 这意味着在多头自注意力( MultiheadAttention )和前馈网络( Feedforward network )之前应用归一化。
return self.forward_pre(src, src_mask, src_key_padding_mask, pos)
# 如果 normalize_before 属性为 False ,则调用 forward_post 方法执行前向传播,该方法使用后归一化策略。
# def forward_post(self, src, src_mask=None, src_key_padding_mask=None, pos=None): -> 这意味着在多头自注意力( MultiheadAttention )和前馈网络( Feedforward network )之后应用归一化。
return self.forward_post(src, src_mask, src_key_padding_mask, pos)
# 这个方法提供了一个简单的分支,根据 normalize_before 的值选择不同的前向传播路径。 forward_pre 和 forward_post 方法分别在之前的解释中已经详细说明,它们实现了 Transformer 编码器层的两种不同归一化策略。
# 这种设计使得同一个编码器模块可以灵活地根据需要选择不同的归一化顺序,以适应不同的训练策略或实验设置。
3.class AIFI(TransformerEncoderLayer):
# 这段代码定义了一个名为 AIFI 的新类,它继承自 TransformerEncoderLayer 类。 AIFI 类代表了一个特定的 Transformer 编码器层,它可能是为某个特定的应用或模型架构定制的。
# 定义了一个名为 AIFI 的类,它继承自 TransformerEncoderLayer 类。这意味着 AIFI 类将继承 TransformerEncoderLayer 类的所有方法和属性,并可以添加或覆盖特定的功能。
class AIFI(TransformerEncoderLayer):
# 定义 AIFI transformer 层。
"""Defines the AIFI transformer layer."""
# 这是 AIFI 类的构造函数,用于初始化类的实例。
# 1.c1 : 输入和输出的特征维度。
# 2.cm : 内部 Feedforward 网络的第一层的维度,默认为 2048。
# 3.num_heads : 在 MultiheadAttention 中使用的头的数量,默认为 8。
# 4.dropout : Dropout 比率,默认为 0,表示不使用 Dropout。
# 5.act : 激活函数,默认使用 GELU。
# 6.normalize_before : 一个布尔值,指示是否在每个子层(MultiheadAttention 和 Feedforward)之前进行归一化。
def __init__(self, c1, cm=2048, num_heads=8, dropout=0, act=nn.GELU(), normalize_before=False):
# 使用指定的参数初始化 AIFI 实例。
"""Initialize the AIFI instance with specified parameters."""
# 这行代码调用了父类 TransformerEncoderLayer 的构造函数,并传递了所有传入 AIFI 构造函数的参数。这样做是为了确保父类被正确初始化,并且所有必要的属性都被设置。
super().__init__(c1, cm, num_heads, dropout, act, normalize_before)
# 通过继承 TransformerEncoderLayer , AIFI 类可以复用父类的所有功能,同时可以添加或修改特定的行为。这种继承机制使得代码更加模块化,易于维护和扩展。如果 AIFI 类需要特定的定制,可以在不改变父类代码的情况下进行。
# 例如,可以在 AIFI 类中添加新的方法或属性,或者覆盖父类的某些方法以改变其行为。
# 这段代码定义了 AIFI 类的 forward 方法,它是 Transformer 层的前向传播函数。这个方法将输入张量 x 通过 AIFI 层进行处理,并返回处理后的结果。
# 定义了一个名为 forward 的方法,它是类的实例方法,因为第一个参数是 self 。
# 1.x : 输入的张量,形状为 [B, C, H, W] ,其中 B 是批次大小, C 是通道数, H 是高度, W 是宽度。
def forward(self, x):
# AIFI transformer 层的前向传递。
"""Forward pass for the AIFI transformer layer."""
# 从输入张量 x 中提取通道数 c ,高度 h 和宽度 w 。
c, h, w = x.shape[1:]
# 调用 build_2d_sincos_position_embedding 方法生成二维正弦余弦位置嵌入 pos_embed ,其维度与输入张量 x 的通道数 c 相匹配。
# def build_2d_sincos_position_embedding(w, h, embed_dim=256, temperature=10000.0):
# -> 用于构建二维的正弦余弦位置嵌入(position embedding)。返回的位置嵌入张量增加了一个批次维度 [None] ,使其形状为 (1, w*h, embed_dim) 。
# -> return torch.cat([torch.sin(out_w), torch.cos(out_w), torch.sin(out_h), torch.cos(out_h)], 1)[None]
pos_embed = self.build_2d_sincos_position_embedding(w, h, c)
# 将输入张量 x 从 [B, C, H, W] 形状展平并置换为 [B, HxW, C] ,以适应 Transformer 层的输入要求。
# Flatten [B, C, H, W] to [B, HxW, C]
# 调用父类 TransformerEncoderLayer 的 forward 方法,将展平并置换后的张量和位置嵌入 pos_embed 传递给它。位置嵌入通过 .to(device=x.device, dtype=x.dtype) 确保与输入张量 x 在同一设备上(如 CPU 或 GPU),并且数据类型相同。
x = super().forward(x.flatten(2).permute(0, 2, 1), pos=pos_embed.to(device=x.device, dtype=x.dtype))
# torch.Tensor.contiguous()
# 在 PyTorch 中, contiguous() 方法是一个用于确保张量(Tensor)在内存中连续存储的方法。如果张量不是连续的,该方法会返回一个新的连续张量副本。如果张量已经是连续的,它将返回原始张量本身。
# 当你对一个张量进行某些操作,比如索引(indexing)、切片(slicing)或者使用某些视图操作(view operations)时,PyTorch 可能会返回一个与原始张量共享内存但内存布局不连续的张量。
# 这可能会导致后续操作出现问题,因为某些 PyTorch 函数和几乎所有的 CUDA 操作都要求输入张量在内存中是连续的。
# 返回值 :
# 一个新的连续张量,如果原始张量已经是连续的,则返回原始张量本身。
# 将 Transformer 层的输出置换回 [B, C, HxW] 的形状。
# 使用 view([-1, c, h, w]) 将输出张量重塑为原始的四维形状 [B, C, H, W] 。
# .contiguous() 确保张量在内存中是连续存储的,这对于某些 PyTorch 操作是必要的。
return x.permute(0, 2, 1).view([-1, c, h, w]).contiguous()
# 总的来说, forward 方法处理输入张量 x ,通过 AIFI 层进行前向传播,并返回与输入相同形状的处理后的结果。这个方法结合了位置嵌入和 Transformer 层的功能,使得模型能够捕捉到输入数据中的空间位置信息。
# 这段代码定义了一个名为 build_2d_sincos_position_embedding 的静态方法,它用于构建二维的正弦余弦位置嵌入(position embedding)。这种位置嵌入通常用于图像处理或任何需要位置信息的二维数据。
@staticmethod
# 这是一个静态方法,不需要类的实例就可以调用。
# 1.w : 网格的宽度。
# 2.h : 网格的高度。
# 3.embed_dim : 嵌入的维度,默认为 256。
# 4.temperature : 用于调整正弦余弦波频率的温度参数,默认为 10000.0。
def build_2d_sincos_position_embedding(w, h, embed_dim=256, temperature=10000.0):
# 构建二维正弦余弦位置嵌入。
"""Builds 2D sine-cosine position embedding."""
# 确保嵌入维度 embed_dim 可以被 4 整除,因为每个位置需要 4 个值(两个正弦和两个余弦)。
assert embed_dim % 4 == 0, "Embed dimension must be divisible by 4 for 2D sin-cos position embedding" # 对于二维正余弦位置嵌入,嵌入维度必须能被 4 整除。
# 创建两个张量 grid_w 和 grid_h ,分别代表宽度和高度的索引。
grid_w = torch.arange(w, dtype=torch.float32)
grid_h = torch.arange(h, dtype=torch.float32)
# 使用 torch.meshgrid 生成二维网格, indexing="ij" 表示网格的索引顺序是先列后行。
grid_w, grid_h = torch.meshgrid(grid_w, grid_h, indexing="ij")
# 计算位置维度 pos_dim ,即 embed_dim 除以 4。
pos_dim = embed_dim // 4
# 创建一个张量 omega ,它将用于调整正弦余弦函数的频率。
omega = torch.arange(pos_dim, dtype=torch.float32) / pos_dim
# 通过温度参数 temperature 调整 omega 的值,以控制正弦余弦波的频率。
omega = 1.0 / (temperature**omega)
# 将 grid_w 和 grid_h 展平,并与 omega 相乘,以生成不同频率的正弦余弦波。
# 这行代码涉及到矩阵乘法,用于计算二维正弦余弦位置嵌入的一部分。
# grid_w.flatten() : 将 grid_w 张量展平(flatten),这意味着将多维数组转换为一维数组。这样,每个位置的宽度索引都会被拉平成一个长序列。
# [..., None] : 这是一个高级索引技巧,用于在指定位置增加一个新的维度。在这里,它被用来在 grid_w 的展平结果上增加一个新的维度,使其从一维张量 (w*h,) 变成二维张量 (w*h, 1) 。
# omega[None] : 这里 omega 是一个一维张量, [None] 将其变形为 (1, pos_dim) 的二维张量。
# @ : 这是 PyTorch 中的矩阵乘法运算符。在这里,它用于执行 grid_w 的展平结果(形状为 (w*h, 1) )和 omega (形状为 (1, pos_dim) )之间的矩阵乘法。
# 结果 out_w 是一个形状为 (w*h, pos_dim) 的二维张量,其中包含了每个位置的宽度索引与 omega 相乘的结果。这个结果将用于后续的正弦和余弦函数计算,以生成位置嵌入。
# 简而言之,这行代码通过矩阵乘法将每个位置的宽度索引与不同的频率相乘,为生成正弦余弦波形做准备。这样做的目的是为了在位置嵌入中引入位置信息,使得模型能够捕捉到序列中不同位置之间的关系。
out_w = grid_w.flatten()[..., None] @ omega[None]
out_h = grid_h.flatten()[..., None] @ omega[None]
# 对 out_w 和 out_h 分别计算正弦和余弦值。
# 使用 torch.cat 将这些值连接起来,形成最终的位置嵌入张量。
# 返回的位置嵌入张量增加了一个批次维度 [None] ,使其形状为 (1, w*h, embed_dim) 。
return torch.cat([torch.sin(out_w), torch.cos(out_w), torch.sin(out_h), torch.cos(out_h)], 1)[None]
# 这个方法生成了一个二维位置嵌入张量,可以用于 Transformer 模型或其他需要位置信息的模型中。通过这种方式,模型可以学习到输入数据中不同位置之间的关系。
4.class TransformerLayer(nn.Module):
# 这段代码定义了一个名为 TransformerLayer 的PyTorch模块,它实现了一个Transformer层,具体来说是一个包含自注意力机制和前馈网络的模块。这个模块是基于论文"An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale"中描述的Transformer架构。
# 定义了一个名为 TransformerLayer 的类,它继承自PyTorch的 nn.Module ,是一个神经网络模块。
class TransformerLayer(nn.Module):
# Transformer 层 https://arxiv.org/abs/2010.11929(为获得更好的性能,删除了 LayerNorm 层)。
"""Transformer layer https://arxiv.org/abs/2010.11929 (LayerNorm layers removed for better performance)."""
# 是 TransformerLayer 类的构造函数,它初始化Transformer层。
# 1.c :是嵌入的维度。
# 2.num_heads :是多头自注意力机制中的头数。
def __init__(self, c, num_heads):
# 使用线性变换和多头注意力初始化自注意力机制。
"""Initializes a self-attention mechanism using linear transformations and multi-head attention."""
super().__init__()
# torch.nn.Linear(in_features, out_features, bias=True)
# 在PyTorch中, torch.nn.Linear 是一个实现全连接线性层(也称为稠密层)的模块。这个层对输入数据执行线性变换,即 y = xA^T + b ,其中 x 是输入, A 是权重矩阵, b 是偏置向量, y是输出。
# 参数说明 :
# in_features :输入特征的维度。
# out_features :输出特征的维度。
# bias :一个布尔值,指示是否添加偏置项,默认为True。
# Linear 模块在初始化时会创建两个参数 :
# 一个是权重矩阵 weight ,其形状为 (out_features, in_features) ;另一个是偏置向量 bias ,其形状为 (out_features) 。如果 bias 参数设置为False,则偏置项不会被创建。
# 定义了一个线性层 q ,用于计算查询(query)向量。这里 c 是输入和输出的维度, bias=False 表示不使用偏置项。
self.q = nn.Linear(c, c, bias=False)
# 定义了一个线性层 k ,用于计算键(key)向量。
self.k = nn.Linear(c, c, bias=False)
# 定义了一个线性层 v ,用于计算值(value)向量。
self.v = nn.Linear(c, c, bias=False)
# torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None, batch_first=False)
# 在PyTorch中, torch.nn.MultiheadAttention 是一个实现多头自注意力机制的模块。这个模块允许模型在处理序列数据时,同时关注序列的不同子空间表示。
# 参数说明 :
# embed_dim :输入的特征维度,即每个输入向量的维度。
# num_heads :多头注意力机制中的头数,即将 embed_dim 分割成多少份。
# dropout :注意力权重的dropout概率,默认为0.0,即不使用dropout。
# bias :是否在线性层中添加偏置项,默认为True。
# add_bias_kv :是否为键(key)和值(value)的计算添加偏置项,默认为False。
# add_zero_attn :是否在注意力权重中添加一个额外的零矩阵,用于一些特定的训练技巧,默认为False。
# kdim :键(key)的特征维度,默认为None,此时会使用 embed_dim 。
# vdim :值(value)的特征维度,默认为None,此时会使用 embed_dim 。
# batch_first :输入和输出张量的格式,默认为False,即 (batch, seq, feature) ;如果设置为True,则为 (seq, batch, feature) 。
# MultiheadAttention 模块的主要功能是 :
# 计算输入序列的自注意力,它接受三个主要的输入:查询(query)、键(key)和值(value)。在多头自注意力中,这些输入会被分割成多个头,每个头计算一部分注意力,然后将结果合并。
# 定义了一个多头自注意力机制 ma ,其中 embed_dim 是嵌入的维度, num_heads 是头数。
self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads)
# self.fc1 = nn.Linear(c, c, bias=False) 和 self.fc2 = nn.Linear(c, c, bias=False) 分别定义了两个线性层,用于实现前馈网络(feed-forward network),这两个层都是无偏置的。
self.fc1 = nn.Linear(c, c, bias=False)
self.fc2 = nn.Linear(c, c, bias=False)
# 定义了 TransformerLayer 类的前向传播函数 forward ,它接收输入 x 。
def forward(self, x):
# 将 transformer 块应用于输入 x 并返回输出。
"""Apply a transformer block to the input x and return the output."""
# 应用多头自注意力机制到输入 x 上,并将结果与原始输入 x 相加,实现残差连接。
x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x
# 应用前馈网络到输入 x 上,并将结果与原始输入 x 相加,再次实现残差连接。
return self.fc2(self.fc1(x)) + x
# 这个 TransformerLayer 模块结合了自注意力机制和前馈网络,通过残差连接和层归一化(在这个实现中被移除)来构建Transformer层。
# 这种结构能够有效地处理序列数据,如自然语言或图像块的序列。在图像识别任务中,输入图像被分割成固定大小的块,然后这些块被视为序列中的元素,通过Transformer层进行处理。
5.class TransformerBlock(nn.Module):
# 这段代码定义了一个名为 TransformerBlock 的PyTorch模块,它实现了一个Vision Transformer(ViT)中的Transformer块。这个模块包括一个可选的卷积层、一个线性层(用于学习位置嵌入),以及一个包含指定数量层的Transformer层序列。
# 定义了一个名为 TransformerBlock 的类,它继承自PyTorch的 nn.Module ,是一个神经网络模块。
class TransformerBlock(nn.Module):
# 视觉Transformer https://arxiv.org/abs/2010.11929。
"""Vision Transformer https://arxiv.org/abs/2010.11929."""
# 是 TransformerBlock 类的构造函数,它初始化Transformer模块。
# 1.c1 和 2.c2 :分别代表输入和输出通道的数量。
# 3.num_heads :是多头自注意力机制中的头数。
# 4.num_layers :是Transformer层的数量。
def __init__(self, c1, c2, num_heads, num_layers):
# 使用位置嵌入和指定数量的头和层来初始化 Transformer 模块。
"""Initialize a Transformer module with position embedding and specified number of heads and layers."""
super().__init__()
# 初始化一个卷积层变量,默认为 None 。
self.conv = None
# 检查输入和输出通道数是否不同。
if c1 != c2:
# 如果输入和输出通道数不同,则创建一个卷积层来调整通道数。
self.conv = Conv(c1, c2)
# 创建一个线性层,用于学习位置嵌入。这里的 c2 是Transformer层的维度。
self.linear = nn.Linear(c2, c2) # learnable position embedding
# 创建一个包含 num_layers 个 TransformerLayer 的序列模块。
self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers)))
# 保存输出通道数。
self.c2 = c2
# 定义了 TransformerBlock 类的前向传播函数 forward ,它接收输入 x 。
def forward(self, x):
# 前向传播通过瓶颈模块的输入。
"""Forward propagates the input through the bottleneck module."""
# 检查是否有卷积层。
if self.conv is not None:
# 如果有卷积层,则对输入 x 进行卷积操作。
x = self.conv(x)
# 获取输入 x 的 批大小 b 、 通道数、 宽度 w 和 高度 h 。
b, _, w, h = x.shape
# 将输入 x 展平并置换维度,以适应Transformer层的输入要求。
# 这行代码是PyTorch中对张量(tensor)进行操作的两个步骤,目的是将输入张量 x 转换成适合Transformer模型处理的格式。下面是详细的解释 :
# x.flatten(2) : 这个操作将输入张量 x 在第2维(通常是特征或通道维度)进行展平。在PyTorch中, flatten() 函数可以将张量展平成一个一维张量。
# 这里的参数 2 指定了除了保留前两个维度(通常是批次大小 batch 和序列长度 sequence_length )之外,将所有后续维度展平成一个维度。
# 这意味着,如果 x 是一个四维张量,比如 (batch_size, channels, height, width) ,那么 x.flatten(2) 之后,它将变成 (batch_size, channels, height, width) 。
# .permute(2, 0, 1) : 这个操作是重新排列张量的维度。 permute() 函数接受一系列维度索引作为参数,并按照这些索引重新排序张量的维度。 在这个例子中, permute(2, 0, 1) 意味着 :
# 2 表示将原始张量的第2维(经过 flatten 操作后,是展平后的特征维度)移动到第0维的位置。
# 0 表示将原始张量的第0维(批次大小 batch )移动到第1维的位置。
# 1 表示将原始张量的第1维(经过 flatten 操作后,是展平后的特征维度)移动到第2维的位置。
# 综合来看, p = x.flatten(2).permute(2, 0, 1) 这行代码的作用是将输入张量 x 从 (batch_size, channels, height, width) 转换为 (batch_size, seq_len, embed_dim) 的形式。
# 其中 seq_len 是展平后的特征维度, embed_dim 是每个序列元素的维度(在这个上下文中, embed_dim 等于 channels * height * width )。
# 这种转换是为了将图像张量转换成序列张量,使其能够被Transformer模型处理。在Vision Transformer中,这种转换是将图像分割成多个patch,并将这些patch视为序列中的元素。
p = x.flatten(2).permute(2, 0, 1)
# 将位置嵌入加到展平的输入上,通过Transformer层序列,然后置换和重塑维度,以恢复到原始的批大小和空间维度。
# 这行代码是PyTorch中对张量进行操作的一系列步骤,用于处理Transformer模块的输出,并将结果转换回原始的四维张量形状。下面是详细的解释 :
# self.tr(p + self.linear(p)) : 这部分代码首先通过一个线性层 self.linear(p) 为输入张量 p 添加位置嵌入,然后将位置嵌入加到 p 上。这个结果被传递到 self.tr ,即Transformer层序列,进行自注意力和前馈网络的处理。
# .permute(1, 2, 0) : 这个操作重新排列处理后的张量维度。 permute(1, 2, 0) 意味着 :
# 将第1维(序列长度 seq_len )移动到第0维的位置。
# 将第2维(嵌入维度 embed_dim )移动到第1维的位置。
# 将第0维(批次大小 batch )移动到第2维的位置。
# 这样,张量的形状从 (batch_size, seq_len, embed_dim) 变回 (seq_len, batch_size, embed_dim) 。
# .reshape(b, self.c2, w, h) : 最后, reshape 操作将张量的形状从 (seq_len, batch_size, embed_dim) 转换回四维张量的形状 (batch_size, channels, height, width) 。
# 这里的 b 是批次大小, self.c2 是通道数(即 embed_dim ), w 和 h 分别是原始输入张量的高度和宽度。
# 综合来看,这行代码的作用是将Transformer处理后的序列张量转换回原始的四维张量形状,以便进行后续的处理或输出。这种转换在Vision Transformer模型中非常重要,因为它允许模型处理图像数据,同时利用Transformer的强大能力捕捉全局依赖关系。
return self.tr(p + self.linear(p)).permute(1, 2, 0).reshape(b, self.c2, w, h)
# 这个 TransformerBlock 模块首先通过可选的卷积层调整通道数,然后将输入展平并加上学习的位置嵌入,最后通过Transformer层序列进行处理。这个模块可以用于构建Vision Transformer模型,它能够处理图像块的序列,并利用自注意力机制捕捉全局依赖关系。
6.class MLPBlock(nn.Module):
# 这段代码定义了一个名为 MLPBlock 的类,它是一个多层感知机(Multi-Layer Perceptron)的单个块,通常用于深度学习模型中的前馈网络(Feedforward Network)。
# 定义了一个名为 MLPBlock 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class MLPBlock(nn.Module):
# 实现多层感知器的单个块。
"""Implements a single block of a multi-layer perceptron."""
# 这是 MLPBlock 类的构造函数,用于初始化类的实例。
# 1.embedding_dim : 输入和输出的特征维度。
# 2.mlp_dim : MLP中间层的维度。
# 3.act : 激活函数,默认为 nn.GELU 。
def __init__(self, embedding_dim, mlp_dim, act=nn.GELU):
# 使用指定的嵌入维度、MLP 维度和激活函数初始化 MLPBlock。
"""Initialize the MLPBlock with specified embedding dimension, MLP dimension, and activation function."""
# 调用父类 nn.Module 的构造函数,这是 Python 中类的初始化标准做法。
super().__init__()
# 初始化两个全连接层(Linear layers)。
# 第一个全连接层,将输入从 embedding_dim 维度转换到 mlp_dim 维度。
self.lin1 = nn.Linear(embedding_dim, mlp_dim)
# 第二个全连接层,将输出从 mlp_dim 维度转换回 embedding_dim 维度。
self.lin2 = nn.Linear(mlp_dim, embedding_dim)
# 初始化激活函数。这里 act 是一个函数(如 nn.GELU ),调用它创建一个激活函数的实例。
self.act = act()
# 定义了 forward 方法,它是类的实例方法,用于定义数据通过 MLPBlock 的前向传播。
# x : 输入的张量。
def forward(self, x: torch.Tensor) -> torch.Tensor:
# MLPBlock 的前向传递。
"""Forward pass for the MLPBlock."""
# 方法首先将输入 x 通过第一个全连接层 self.lin1 ,然后应用激活函数 self.act ,最后通过第二个全连接层 self.lin2 。
# 返回最终的输出张量。
return self.lin2(self.act(self.lin1(x)))
# 这个 MLPBlock 类实现了一个简单的两层 MLP 结构,常用于 Transformer 架构中的前馈网络部分。通过这种方式,模型可以学习输入数据的非线性变换。
7.class MLP(nn.Module):
# 这段代码定义了一个名为 MLP 的类,它实现了一个简单的多层感知机(Multi-Layer Perceptron,简称 MLP),也称为前馈神经网络(Feedforward Neural Network,简称 FFN)。
# 定义了一个名为 MLP 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class MLP(nn.Module):
# 实现一个简单的多层感知器(也称为 FFN)。
"""Implements a simple multi-layer perceptron (also called FFN)."""
# 这是 MLP 类的构造函数,用于初始化类的实例。
# 1.input_dim : 输入层的维度。
# 2.hidden_dim : 隐藏层的维度。
# 3.output_dim : 输出层的维度。
# 4.num_layers : 网络中的层数,包括输入层、所有隐藏层和输出层。
# 5.act : 激活函数,默认为 nn.ReLU 。
# 6.sigmoid : 一个布尔值,指示是否在输出层使用 Sigmoid 激活函数,默认为 False 。
def __init__(self, input_dim, hidden_dim, output_dim, num_layers, act=nn.ReLU, sigmoid=False):
# 使用指定的输入、隐藏、输出维度和层数初始化 MLP。
"""Initialize the MLP with specified input, hidden, output dimensions and number of layers."""
# 调用父类 nn.Module 的构造函数,这是 Python 中类的初始化标准做法。
super().__init__()
# 将层数 num_layers 保存为类的属性。
self.num_layers = num_layers
# 创建一个列表 h ,包含 num_layers - 1 个 hidden_dim 值,用于定义除了输入层和输出层之外的所有隐藏层的维度。
h = [hidden_dim] * (num_layers - 1)
# 初始化一个 nn.ModuleList ,其中包含 num_layers 个全连接层( nn.Linear )。
# 这些层的输入和输出维度由 zip([input_dim] + h, h + [output_dim]) 确定,即第一个层的输入维度是 input_dim ,输出维度是 hidden_dim ;最后一个层的输入维度是 hidden_dim ,输出维度是 output_dim 。
self.layers = nn.ModuleList(nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]))
# 将 sigmoid 参数保存为类的属性,用于控制是否在输出层使用 Sigmoid 激活函数。
self.sigmoid = sigmoid
# 初始化激活函数。这里 act 是一个函数(如 nn.ReLU ),调用它创建一个激活函数的实例,并将其保存为类的属性。
self.act = act()
# 这个 MLP 类实现了一个标准的多层感知机结构,可以根据需要自定义层数、每层的维度和激活函数。这种结构在深度学习中非常常见,可以用于各种任务,如分类、回归等。
# 这段代码定义了 MLP 类的 forward 方法,它是多层感知机(MLP)的前向传播函数。这个方法将输入 x 通过 MLP 的所有层进行处理,并返回处理后的结果。
# 定义了一个名为 forward 的方法,它是类的实例方法,用于定义数据通过 MLP 的前向传播。
def forward(self, x):
# 整个 MLP 的前向传递。
"""Forward pass for the entire MLP."""
# 使用 enumerate 函数遍历 self.layers 中的每一层, i 是层的索引, layer 是层的实例。
for i, layer in enumerate(self.layers):
# 对于除了最后一层之外的每一层,应用激活函数 self.act (默认为 nn.ReLU )到该层的输出上。这是通过 getattr(self, "act", nn.ReLU()) 实现的,它尝试获取 self 的 act 属性,如果不存在则使用 nn.ReLU 作为默认值。 对于最后一层,不应用激活函数,直接传递输出。
x = getattr(self, "act", nn.ReLU())(layer(x)) if i < self.num_layers - 1 else layer(x)
# 在所有层的计算完成后,检查 self.sigmoid 属性是否为 True 。如果是,对最终输出应用 sigmoid 函数;否则,直接返回最终输出 x 。
return x.sigmoid() if getattr(self, "sigmoid", False) else x
# 这个方法实现了 MLP 的前向传播过程,其中每一层的输出都通过激活函数(除了最后一层),并且在最后根据 sigmoid 属性决定是否应用 sigmoid 函数。这种设计使得 MLP 类可以灵活地应用于不同的场景,例如在二分类问题中可能需要在输出层使用 sigmoid 激活函数。
8.class LayerNorm2d(nn.Module):
# 这段代码定义了一个名为 LayerNorm2d 的类,它是一个二维层归一化(Layer Normalization)模块,灵感来源于 Detectron2 和 ConvNeXt 的实现。层归一化是一种归一化技术,用于稳定深度网络的训练过程,特别是在处理图像数据时。
# 定义了一个名为 LayerNorm2d 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class LayerNorm2d(nn.Module):
# 2D 层规范化模块灵感来自 Detectron2 和 ConvNeXt 实现。
"""
2D Layer Normalization module inspired by Detectron2 and ConvNeXt implementations.
Original implementations in
https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py
and
https://github.com/facebookresearch/ConvNeXt/blob/main/models/convnext.py.
"""
# 这是 LayerNorm2d 类的构造函数,用于初始化类的实例。
# 1.num_channels : 输入张量的通道数。
# 2.eps : 用于数值稳定性的小常数,防止除以零。
def __init__(self, num_channels, eps=1e-6):
# 使用给定的参数初始化 LayerNorm2d。
"""Initialize LayerNorm2d with the given parameters."""
# 调用父类 nn.Module 的构造函数,这是 Python 中类的初始化标准做法。
super().__init__()
# 初始化两个参数 : weight 和 bias 。这两个参数是可学习的,并且在训练过程中会被优化。
# 一个形状为 ( num_channels, ) 的参数,初始值为 1。
self.weight = nn.Parameter(torch.ones(num_channels))
# 一个形状为 ( num_channels, ) 的参数,初始值为 0。
self.bias = nn.Parameter(torch.zeros(num_channels))
# 将 eps 值保存为类的属性。
self.eps = eps
# 定义了 forward 方法,它是类的实例方法,用于定义数据通过 LayerNorm2d 的前向传播。
def forward(self, x):
# 执行前向传递以进行二维层归一化。
"""Perform forward pass for 2D layer normalization."""
# 计算输入张量 x 的均值 u 和方差 s ,其中 1 表示沿着通道维度计算, keepdim=True 保持维度以便进行广播操作。
u = x.mean(1, keepdim=True)
s = (x - u).pow(2).mean(1, keepdim=True)
# 执行归一化操作,将输入张量 x 中的每个值减去均值 u ,然后除以标准差(方差 s 的平方根加上 eps )。
x = (x - u) / torch.sqrt(s + self.eps)
# 将归一化后的张量 x 乘以 weight 参数,并加上 bias 参数,然后返回结果。 [:, None, None] 用于增加维度,以便 weight 和 bias 可以与 x 进行广播操作。
return self.weight[:, None, None] * x + self.bias[:, None, None]
# 这个 LayerNorm2d 类实现了二维层归一化,通常用于卷积神经网络中,以归一化每个样本的特征图的通道。这种归一化有助于加速训练过程,提高模型的泛化能力。
9.class MSDeformAttn(nn.Module):
# 这段代码定义了一个名为 MSDeformAttn 的类,它是一个多尺度可变形注意力(Multiscale Deformable Attention)模块,灵感来源于 Deformable-DETR 和 PaddleDetection 的实现。这种注意力机制允许模型在不同尺度上动态地关注输入特征图的不同区域。
# 定义了一个名为 MSDeformAttn 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class MSDeformAttn(nn.Module):
# 基于可变形 DETR 和 PaddleDetection 实现的多尺度可变形注意力模块。
"""
Multiscale Deformable Attention Module based on Deformable-DETR and PaddleDetection implementations.
https://github.com/fundamentalvision/Deformable-DETR/blob/main/models/ops/modules/ms_deform_attn.py
"""
# 这是 MSDeformAttn 类的构造函数,用于初始化类的实例。
# 1.d_model : 输入的特征维度。
# 2.n_levels : 多尺度数量。
# 3.n_heads : 多头注意力中的头数。
# 4.n_points : 每个头的采样点数。
def __init__(self, d_model=256, n_levels=4, n_heads=8, n_points=4):
# 使用给定的参数初始化 MSDeformAttn。
"""Initialize MSDeformAttn with the given parameters."""
# 调用父类 nn.Module 的构造函数,这是 Python 中类的初始化标准做法。
super().__init__()
# 检查 d_model 是否可以被 n_heads 整除,如果不能,则抛出 ValueError 异常。
if d_model % n_heads != 0:
raise ValueError(f"d_model must be divisible by n_heads, but got {d_model} and {n_heads}") # d_model 必须能被 n_heads 整除,但得到 {d_model} 和 {n_heads}。
# 计算每个头的特征维度 _d_per_head 。
_d_per_head = d_model // n_heads
# Better to set _d_per_head to a power of 2 which is more efficient in a CUDA implementation
# 确保 _d_per_head 乘以 n_heads 等于 d_model ,这是多头注意力机制的要求。
assert _d_per_head * n_heads == d_model, "`d_model` must be divisible by `n_heads`" # `d_model` 必须能被 `n_heads` 整除。
# 设置 im2col_step 属性,它用于图像到列的转换步骤,通常与 CUDA 实现中的内存对齐有关。
self.im2col_step = 64
# 将传入的参数保存为类的属性。
self.d_model = d_model
self.n_levels = n_levels
self.n_heads = n_heads
self.n_points = n_points
# 初始化几个线性层。
# sampling_offsets : 用于计算采样偏移。
self.sampling_offsets = nn.Linear(d_model, n_heads * n_levels * n_points * 2)
# attention_weights : 用于计算注意力权重。
self.attention_weights = nn.Linear(d_model, n_heads * n_levels * n_points)
# # value_proj : 用于投影值(value)。
self.value_proj = nn.Linear(d_model, d_model)
# output_proj : 用于投影输出。
self.output_proj = nn.Linear(d_model, d_model)
# 调用一个方法来重置模块的参数,这通常是通过初始化权重和偏置为特定的值来完成的。
self._reset_parameters()
# 这个 MSDeformAttn 类实现了多尺度可变形注意力机制,它允许模型在不同的尺度上关注输入特征图的不同区域,这在处理具有不同尺度特征的目标时特别有用。这种注意力机制在目标检测和图像分割等任务中表现出了优越的性能。
# 这段代码定义了 MSDeformAttn 类的 _reset_parameters 方法,用于初始化或重置模块的参数。这个方法使用了几种不同的初始化策略来设置网络参数的初始值,这对于模型的训练和性能至关重要。
# 定义了一个名为 _reset_parameters 的方法,它是类的实例方法,用于重置模块的参数。
def _reset_parameters(self):
# 重置模块参数。
"""Reset module parameters."""
# torch.nn.functional.constant_(input, value)
# constant_ 是 PyTorch 中的一个函数,用于将一个张量(Tensor)的值设置为一个常数值。这个函数通常用于参数初始化,即将模型中的权重或偏置设置为特定的值。
# 参数说明 :
# input : 要修改的张量。
# value : 要设置的常数值。
# constant_ 函数会直接在原始张量上进行修改,不创建新的张量副本。这意味着它是一个就地(in-place)操作。
# 使用 constant_ 函数将 sampling_offsets 层的权重初始化为 0。
constant_(self.sampling_offsets.weight.data, 0.0)
# 创建一个张量 thetas ,包含从 0 到 n_heads - 1 的整数,每个整数乘以 2.0 * math.pi / n_heads ,用于计算每个头的角度。
thetas = torch.arange(self.n_heads, dtype=torch.float32) * (2.0 * math.pi / self.n_heads)
# 计算 thetas 的余弦和正弦值,并将它们堆叠起来形成 grid_init 。
grid_init = torch.stack([thetas.cos(), thetas.sin()], -1)
# 将 grid_init 归一化,使其最大值为 1。 调整 grid_init 的形状,并重复以匹配 n_levels 和 n_points 。
# 这段代码涉及到对张量 grid_init 的一系列操作,目的是将其转换为适合作为偏置项的格式,用于可变形卷积或注意力机制中的偏移量。
# 归一化。 grid_init / grid_init.abs().max(-1, keepdim=True)[0]
# grid_init.abs() : 计算 grid_init 中每个元素的绝对值。
# .max(-1, keepdim=True) : 沿着最后一个维度(索引为 -1)找到最大值,并保持维度,以便可以进行广播操作。
# [0] : 从结果中提取最大值张量。
# 将 grid_init 除以最大绝对值,对张量进行归一化,使得最大值为 1。
# 调整形状。 .view(self.n_heads, 1, 1, 2)
# 将归一化后的 grid_init 调整为形状 (self.n_heads, 1, 1, 2) 。这种形状调整是为了匹配多头注意力机制中头的数量以及偏移量需要的两个维度(通常是 x 和 y 方向)。
# 重复。 .repeat(1, self.n_levels, self.n_points, 1)
# 沿着不同的维度重复 grid_init : 1 : 不改变头的数量维度。 self.n_levels : 重复每个头的级别数。 self.n_points : 重复每个级别的采样点数。 1 : 不改变最后一个维度(偏移量的两个维度)。
# 这些操作的结果使 grid_init 被转换为一个形状为 (self.n_heads, self.n_levels, self.n_points, 2) 的张量,其中每个采样点的偏移量都被归一化并重复以匹配多头注意力的配置。这个张量随后被用作可变形卷积或注意力机制中的偏置项,以指导模型关注输入特征图的特定区域。
grid_init = (
(grid_init / grid_init.abs().max(-1, keepdim=True)[0])
.view(self.n_heads, 1, 1, 2)
.repeat(1, self.n_levels, self.n_points, 1)
)
# 根据采样点的索引调整 grid_init 的值,使得不同采样点的偏移量不同。
# 这段代码涉及到对张量 grid_init 的逐元素操作,目的是根据采样点的索引调整偏移量。
# for i in range(self.n_points) : 遍历从 0 到 n_points - 1 的索引 i ,其中 n_points 是每个头的采样点数。
# grid_init[:, :, i, :] : 选择 grid_init 中第 i 个采样点的所有值。由于 grid_init 的形状是 (self.n_heads, self.n_levels, self.n_points, 2) ,这个操作选择了第 i 个采样点的所有头和级别的值。
# *= i + 1 : 将所选值乘以 i + 1 。这意味着第一个采样点的值保持不变(乘以 1),第二个采样点的值乘以 2,依此类推,直到最后一个采样点的值乘以 n_points 。
# 这种调整确保了随着采样点索引的增加,偏移量逐渐增加。这在可变形卷积或注意力机制中很有用,因为它允许模型为不同采样点学习不同尺度的偏移量,从而更灵活地关注输入特征图的不同区域。
# 最终结果是 grid_init 中的每个采样点都有一个根据其索引调整的偏移量,这些偏移量随后被用作可变形卷积或注意力机制中的偏置项。
for i in range(self.n_points):
grid_init[:, :, i, :] *= i + 1
# 在不计算梯度的情况下,将 grid_init 视为一个新的偏置项,并赋值给 sampling_offsets 层的偏置。
with torch.no_grad():
self.sampling_offsets.bias = nn.Parameter(grid_init.view(-1))
# 将 attention_weights 层的权重和偏置初始化为 0。
constant_(self.attention_weights.weight.data, 0.0)
constant_(self.attention_weights.bias.data, 0.0)
# torch.nn.init.xavier_uniform_(tensor, gain=1)
# torch.nn.init.xavier_uniform_() 是 PyTorch 中的一个函数,用于对神经网络的权重进行 Xavier 均匀分布初始化。这种初始化方法旨在保持激活函数的方差在前向传播和反向传播过程中大致相同,以避免梯度消失或梯度爆炸的问题。
# 参数说明 :
# 1.tensor : 要初始化的张量,通常是模型中的权重。
# 2.gain : 一个可选的缩放因子,默认值为 1。
# 这种初始化方法有助于在深度学习模型训练的早期阶段保持激活值和梯度的方差,从而促进模型的收敛。
# 使用 Xavier 均匀分布初始化 value_proj 层的权重。
xavier_uniform_(self.value_proj.weight.data)
# 将 value_proj 层的偏置初始化为 0。
constant_(self.value_proj.bias.data, 0.0)
# 使用 Xavier 均匀分布初始化 output_proj 层的权重。
xavier_uniform_(self.output_proj.weight.data)
# 将 output_proj 层的偏置初始化为 0。
constant_(self.output_proj.bias.data, 0.0)
# 这个方法使用了不同的初始化策略,包括零初始化、Xavier 均匀分布初始化和基于角度的初始化,以确保模型在训练开始时具有合理的初始参数。这些初始化策略有助于模型在训练过程中的稳定性和收敛性。
# 这段代码是 MSDeformAttn 类的 forward 方法的完整定义,它实现了多尺度可变形注意力(Multiscale Deformable Attention)的前向传播逻辑。
# 定义了 forward 方法,它接收以下参数 :
# query : 查询张量,通常代表输入特征。
# refer_bbox : 参考边界框张量,用于确定采样位置。
# value : 值张量,即注意力机制中的值(V)。
# value_shapes : 一个包含不同尺度值张量形状的列表。
# value_mask : 可选的值掩码张量,用于屏蔽某些值。
def forward(self, query, refer_bbox, value, value_shapes, value_mask=None):
# 执行前向传递以实现多尺度可变形注意力。
"""
Perform forward pass for multiscale deformable attention.
https://github.com/PaddlePaddle/PaddleDetection/blob/develop/ppdet/modeling/transformers/deformable_transformer.py
Args:
query (torch.Tensor): [bs, query_length, C]
refer_bbox (torch.Tensor): [bs, query_length, n_levels, 2], range in [0, 1], top-left (0,0),
bottom-right (1, 1), including padding area
value (torch.Tensor): [bs, value_length, C]
value_shapes (List): [n_levels, 2], [(H_0, W_0), (H_1, W_1), ..., (H_{L-1}, W_{L-1})]
value_mask (Tensor): [bs, value_length], True for non-padding elements, False for padding elements
Returns:
output (Tensor): [bs, Length_{query}, C]
"""
# 从 query 张量中提取 批次大小 bs 和 查询序列长度 len_q 。
bs, len_q = query.shape[:2]
# 从 value 张量中提取值序列长度 len_v 。
len_v = value.shape[1]
# 确保所有值张量的形状之和等于 len_v 。
assert sum(s[0] * s[1] for s in value_shapes) == len_v
# 将 value 张量通过 value_proj 线性层进行投影。
value = self.value_proj(value)
if value_mask is not None:
# torch.Tensor.masked_fill(mask, value)
# torch.Tensor.masked_fill() 是 PyTorch 中的一个函数,它用于根据掩码张量(mask tensor)将输入张量(input tensor)中的特定元素填充为指定的值。这个函数有两个版本 :就地操作的 masked_fill_() 和非就地操作的 masked_fill() 。
# 参数说明 :
# mask : 一个布尔类型的张量(BoolTensor),其形状必须与输入张量的形状一致或者可以广播到输入张量的形状。在 mask 中为 True 的位置将被填充。
# value : 一个标量值,用于指定填充的值。
# 功能描述 :
# masked_fill() 函数将 mask 中为 True 的位置对应的输入张量 self 的元素设置为 value ,并返回一个新的张量。原始的输入张量 self 不会被修改。
# 返回值 :
# 返回一个新的张量,其中 mask 为 True 的位置被填充了 value 。
# 注意事项 :
# mask 必须是一个布尔张量,其元素只能是 0 (False)或 1 (True),或者是直接的布尔值。
# mask 的形状必须与输入张量 self 的形状一致,或者两者是可广播的(broadcastable)。
# value 可以是一个标量,也可以是一个与输入张量形状相同的张量,但这种情况下使用较少见。
# 如果提供了 value_mask ,则使用它来将 value 中的某些值置为 0。
value = value.masked_fill(value_mask[..., None], float(0))
# 调整 value 张量的形状以适应多头注意力机制。
# 这行代码使用 PyTorch 的 .view() 方法来调整 value 张量的形状,以适应多头注意力机制的需求。
# 理解 value 张量 : value 是一个包含值(V)的张量,通常用于注意力机制中,其原始形状为 [bs, len_v, d_model] ,其中 : bs 是批次大小(batch size)。 len_v 是值序列的长度。 d_model 是模型的维度,即每个值向量的维度。
# 调整形状 : .view(bs, len_v, self.n_heads, self.d_model // self.n_heads) 是将 value 张量重新塑形为 [bs, len_v, self.n_heads, self.d_model // self.n_heads] 。
# 这种形状调整是为了将值张量分割成多个头( self.n_heads ),每个头处理一部分维度。这样做的目的是实现多头注意力机制,其中每个头可以独立地学习输入数据的不同表示。
# 维度计算 : self.d_model // self.n_heads 计算每个头处理的维度。这是通过将总维度 d_model 除以头数 n_heads 来实现的。确保 d_model 可以被 n_heads 整除是很重要的,这样可以保证每个头得到的维度是一个整数。
# 实际应用 : 在多头注意力中,每个头将处理分割后的值张量的一个子集,这有助于模型在不同的表示子空间中捕捉信息。
# 这种形状调整是多头注意力机制中的一个关键步骤,它允许模型并行处理多个子空间的信息,从而提高模型的表达能力。通过这种方式,模型可以更灵活地捕捉输入数据的不同特征。
value = value.view(bs, len_v, self.n_heads, self.d_model // self.n_heads)
# 计算采样偏移量 sampling_offsets 并调整其形状。
# 这行代码执行了两个主要操作:首先通过一个线性层( self.sampling_offsets )计算采样偏移量,然后将结果张量重塑为特定的形状。
# 计算采样偏移量 : sampling_offsets = self.sampling_offsets(query)
# self.sampling_offsets 是一个 nn.Linear 层,它的作用是将输入的查询( query )张量映射到采样偏移量的张量。
# query 是一个形状为 [bs, len_q, d_model] 的张量,其中 bs 是批次大小, len_q 是查询序列的长度, d_model 是查询的维度。
# self.sampling_offsets 输出的张量形状为 [bs, len_q, n_heads * n_levels * n_points * 2] ,其中 n_heads 是多头注意力中的头数, n_levels 是不同尺度的数量, n_points 是每个头的采样点数, 2 表示每个采样点有两个偏移值(通常对应于 x 和 y 坐标)。
# 重塑采样偏移量张量 : .view(bs, len_q, self.n_heads, self.n_levels, self.n_points, 2)
# .view() 方法用于改变张量的形状而不改变其数据。
# 这里将 sampling_offsets 张量的形状从 [bs, len_q, n_heads * n_levels * n_points * 2] 重塑为 [bs, len_q, self.n_heads, self.n_levels, self.n_points, 2] 。 这种重塑是为了将采样偏移量组织成多个头、多个尺度和多个采样点的结构,每个采样点有两个偏移值。
# 通过这行代码,采样偏移量被计算并重塑为适合后续多尺度可变形注意力计算的形状。这种形状的张量可以直接用于计算采样位置,其中每个头在每个尺度上的每个采样点都有两个偏移值。这种结构允许模型在不同尺度和位置上动态地调整其注意力,以更好地捕捉输入特征图中的关键信息。
sampling_offsets = self.sampling_offsets(query).view(bs, len_q, self.n_heads, self.n_levels, self.n_points, 2)
# 计算注意力权重 attention_weights ,进行 softmax 归一化,并调整其形状。
# 这行代码涉及计算注意力权重并对其进行操作以适应多尺度可变形注意力机制。
# 计算注意力权重 :attention_weights = self.attention_weights(query)
# self.attention_weights 是一个 nn.Linear 层,它将输入的查询( query )张量映射到注意力权重的张量。
# query 是一个形状为 [bs, len_q, d_model] 的张量,其中 bs 是批次大小, len_q 是查询序列的长度, d_model 是查询的维度。
# self.attention_weights 输出的张量形状为 [bs, len_q, n_heads * n_levels * n_points] ,其中 n_heads 是多头注意力中的头数, n_levels 是不同尺度的数量, n_points 是每个头的采样点数。
# 重塑注意力权重张量 : .view(bs, len_q, self.n_heads, self.n_levels * self.n_points)
# .view() 方法用于改变张量的形状而不改变其数据。
# 这里将 attention_weights 张量的形状从 [bs, len_q, n_heads * n_levels * n_points] 重塑为 [bs, len_q, self.n_heads, self.n_levels * self.n_points] 。 这种重塑是为了将注意力权重组织成多个头的结构,每个头有多个尺度和多个采样点。
attention_weights = self.attention_weights(query).view(bs, len_q, self.n_heads, self.n_levels * self.n_points)
# 应用 softmax 函数 : F.softmax(attention_weights, -1)
# F.softmax 是 PyTorch 中的 softmax 函数,它沿着指定的维度(这里是最后一个维度,索引为 -1)对张量进行 softmax 归一化。 Softmax 函数将每个头内每个尺度和每个采样点的权重转换为概率分布,使得每个头内所有权重的和为 1。
# 进一步重塑注意力权重张量 : .view(bs, len_q, self.n_heads, self.n_levels, self.n_points)
# 再次使用 .view() 方法将 attention_weights 张量的形状从 [bs, len_q, self.n_heads, self.n_levels * self.n_points] 重塑为 [bs, len_q, self.n_heads, self.n_levels, self.n_points] 。
# 这种进一步的重塑是为了将注意力权重组织成多个头、多个尺度和多个采样点的结构,每个采样点有一个权重。
# 通过这行代码,注意力权重被计算、归一化并重塑为适合后续多尺度可变形注意力计算的形状。这种形状的张量可以直接用于计算每个头在每个尺度上的每个采样点的加权和,从而实现对输入特征图的动态关注。
attention_weights = F.softmax(attention_weights, -1).view(bs, len_q, self.n_heads, self.n_levels, self.n_points)
# N, Len_q, n_heads, n_levels, n_points, 2
# 根据 refer_bbox 的形状确定采样点的数量 num_points 。
num_points = refer_bbox.shape[-1]
# 如果 num_points 为 2,计算 2D 采样位置。
# 这段代码处理当参考边界框 refer_bbox 中的采样点数 num_points 为 2 时的情况。这通常意味着每个参考框有 x 和 y 两个坐标。
if num_points == 2:
# torch.as_tensor(data, dtype=None, device=None)
# torch.as_tensor 是 PyTorch 中的一个函数,它用于将输入的数据转换为一个新的 PyTorch 张量(Tensor)。如果输入已经是张量, torch.as_tensor 会直接返回该张量,否则会尝试将其转换为张量。
# 这个函数通常用于确保数据以张量的形式存在,同时允许在转换过程中指定数据类型和设备。
# 参数说明 :
# data : 要转换为张量的数据,可以是列表、元组、NumPy 数组、Pandas DataFrame/Series 或其他张量。
# dtype : 指定张量的数据类型(例如, torch.float32 、 torch.int64 等)。如果未指定,将根据输入数据自动推断数据类型。
# device : 指定张量所在的设备(例如, torch.device("cpu") 或 torch.device("cuda:0") )。如果未指定,张量将被创建在当前默认设备上。
# torch.as_tensor 与 torch.tensor 类似,但 torch.tensor 总是复制数据,而 torch.as_tensor 在可能的情况下会共享原始数据的内存,这可以提高内存效率。如果输入数据已经是一个张量, torch.as_tensor 会直接返回该张量,而不会创建新的副本。
# 创建偏移归一化器。
# 将 value_shapes 转换为张量,并确保其数据类型和设备与 query 张量相同。
# .flip(-1) 将张量在最后一个维度上翻转,这通常是为了调整形状以匹配期望的格式。
offset_normalizer = torch.as_tensor(value_shapes, dtype=query.dtype, device=query.device).flip(-1)
# 计算归一化的采样偏移量。
# 使用广播机制将 sampling_offsets 除以 offset_normalizer 。
# None 用于增加维度以匹配 sampling_offsets 的形状 [bs, len_q, self.n_heads, self.n_levels, self.n_points, 2] 和 offset_normalizer 的形状 [n_levels, 2] 。
add = sampling_offsets / offset_normalizer[None, None, None, :, None, :]
# 计算采样位置。
# 使用广播机制将 refer_bbox 与 add 相加。
# None 用于增加维度以匹配 refer_bbox 的形状 [bs, len_q, self.n_heads, self.n_levels, 4] 和 add 的形状 [bs, len_q, self.n_heads, self.n_levels, self.n_points, 2] 。
# 结果 sampling_locations 包含了每个采样点的 x 和 y 坐标,这些坐标将用于从特征图中采样。
sampling_locations = refer_bbox[:, :, None, :, None, :] + add
# 通过这行代码,采样位置被计算为参考边界框加上归一化的采样偏移量。这种计算允许模型在特征图上动态地采样,以更好地捕捉输入特征图中的关键信息。
# 如果 num_points 为 4,计算 4D 采样位置(通常用于表示 2D 坐标的两个偏移量)。
# 这段代码处理当参考边界框 refer_bbox 中的采样点数 num_points 为 4 时的情况。这通常意味着每个参考框有四个坐标,其中两个表示中心点位置,另外两个表示宽度和高度。
elif num_points == 4:
# 计算归一化的采样偏移量。
# 将 sampling_offsets 除以 self.n_points ,这用于调整偏移量的大小。
# 将结果乘以 refer_bbox 的宽度和高度(由 refer_bbox[:, :, None, :, None, 2:] 提取)。
# 将整个表达式乘以 0.5,这可能是为了进一步缩放偏移量。
add = sampling_offsets / self.n_points * refer_bbox[:, :, None, :, None, 2:] * 0.5
# 计算采样位置。
# 使用广播机制将 refer_bbox 的中心点坐标(由 refer_bbox[:, :, None, :, None, :2] 提取)与 add 相加。
# 结果 sampling_locations 包含了调整后的采样点坐标,这些坐标将用于从特征图中采样。
# 通过这行代码,采样位置被计算为中心点加上根据宽度和高度调整的归一化采样偏移量。这种计算允许模型在特征图上动态地采样,以更好地捕捉输入特征图中的关键信息。
sampling_locations = refer_bbox[:, :, None, :, None, :2] + add
# 如果 num_points 既不是 2 也不是 4,则抛出异常。
else:
raise ValueError(f"Last dim of reference_points must be 2 or 4, but got {num_points}.")
# 调用 multi_scale_deformable_attn_pytorch 函数,它实现了多尺度可变形注意力的计算,并返回输出。
# def multi_scale_deformable_attn_pytorch(value: torch.Tensor, value_spatial_shapes: torch.Tensor, sampling_locations: torch.Tensor, attention_weights: torch.Tensor, ) -> torch.Tensor:
# -> 它实现了多尺度可变形注意力(Multiscale Deformable Attention)的计算。调整输出形状并返回。调整输出张量的形状,使其维度顺序为 [bs, num_queries, num_heads * embed_dims] ,并确保张量在内存中是连续的。
# -> return output.transpose(1, 2).contiguous()
output = multi_scale_deformable_attn_pytorch(value, value_shapes, sampling_locations, attention_weights)
# 将输出通过 output_proj 线性层进行投影,并返回最终结果。
return self.output_proj(output)
# 这个方法实现了多尺度可变形注意力机制的核心计算,它允许模型在不同尺度上动态地关注输入特征图的不同区域,这在处理具有不同尺度特征的目标时特别有用。
10.class DeformableTransformerDecoderLayer(nn.Module):
# 这段代码定义了一个名为 DeformableTransformerDecoderLayer 的类,它是一个可变形 Transformer 解码器层,灵感来源于 PaddleDetection 和 Deformable-DETR 的实现。这个类继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
# 类定义。定义了一个名为 DeformableTransformerDecoderLayer 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class DeformableTransformerDecoderLayer(nn.Module):
# 可变形 Transformer 解码器层受到 PaddleDetection 和 Deformable-DETR 实现的启发。
"""
Deformable Transformer Decoder Layer inspired by PaddleDetection and Deformable-DETR implementations.
https://github.com/PaddlePaddle/PaddleDetection/blob/develop/ppdet/modeling/transformers/deformable_transformer.py
https://github.com/fundamentalvision/Deformable-DETR/blob/main/models/deformable_transformer.py
"""
# 构造函数。这是 DeformableTransformerDecoderLayer 类的构造函数,用于初始化类的实例。
# 1.d_model : 输入的特征维度。
# 2.n_heads : 多头注意力中的头数。
# 3.d_ffn : 前馈网络(Feedforward Network)中间层的维度。
# 4.dropout : Dropout 比率。
# 5.act : 激活函数,默认为 nn.ReLU 。
# 6.n_levels : 多尺度数量。
# 7.n_points : 每个头的采样点数。
def __init__(self, d_model=256, n_heads=8, d_ffn=1024, dropout=0.0, act=nn.ReLU(), n_levels=4, n_points=4):
# 使用给定的参数初始化DeformableTransformerDecoderLayer。
"""Initialize the DeformableTransformerDecoderLayer with the given parameters."""
super().__init__()
# Self attention
# 自注意力模块。
# 自注意力模块,用于处理解码器内部的序列。
self.self_attn = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)
# 自注意力后的 Dropout 层。
self.dropout1 = nn.Dropout(dropout)
# 自注意力后的层归一化(Layer Normalization)层。
self.norm1 = nn.LayerNorm(d_model)
# Cross attention
# 交叉注意力模块。
# 交叉注意力模块,即多尺度可变形注意力( MSDeformAttn )模块,用于处理编码器和解码器之间的交互。
self.cross_attn = MSDeformAttn(d_model, n_levels, n_heads, n_points)
# 交叉注意力后的 Dropout 层。
self.dropout2 = nn.Dropout(dropout)
# 交叉注意力后的层归一化(Layer Normalization)层。
self.norm2 = nn.LayerNorm(d_model)
# FFN
# 前馈网络模块。
# self.linear1 和 self.linear2 : 前馈网络的两个线性层。
self.linear1 = nn.Linear(d_model, d_ffn)
# 激活函数,用于前馈网络中间。
self.act = act
# self.dropout3 和 self.dropout4 : 前馈网络中的 Dropout 层。
self.dropout3 = nn.Dropout(dropout)
self.linear2 = nn.Linear(d_ffn, d_model)
self.dropout4 = nn.Dropout(dropout)
# 前馈网络后的层归一化(Layer Normalization)层。
self.norm3 = nn.LayerNorm(d_model)
# 这个 DeformableTransformerDecoderLayer 类实现了 Transformer 解码器层的一个变体,它结合了自注意力、交叉注意力和前馈网络,特别适用于需要处理空间信息的任务,如目标检测和图像分割。通过引入可变形注意力机制,模型能够更灵活地关注输入特征图的不同区域。
# 这段代码定义了一个名为 with_pos_embed 的静态方法,它用于将位置嵌入(positional embeddings)添加到输入张量中,如果提供了位置嵌入的话。
# 静态方法定义。 @staticmethod : 这是一个装饰器,表示该方法是一个静态方法。静态方法不需要类的实例或类本身即可调用,它们不访问类的属性或方法。
@staticmethod
# with_pos_embed 静态方法的名称。
# 1.tensor : 输入的张量,通常是特征嵌入。
# 2.pos : 可选的位置嵌入张量。
def with_pos_embed(tensor, pos):
# 如果提供,则将位置嵌入添加到输入张量。
"""Add positional embeddings to the input tensor, if provided."""
# 这个方法检查 pos 参数是否为 None 。
# 如果 pos 为 None ,则方法直接返回原始的输入张量 tensor ,不做任何修改。
# 如果 pos 不为 None ,则方法将位置嵌入 pos 添加到输入张量 tensor 上,并返回结果。
return tensor if pos is None else tensor + pos
# 位置嵌入通常用于序列模型中,如 Transformer,以提供序列中每个元素的位置信息。这对于模型理解序列中元素的顺序非常重要。
# 在某些情况下,如果输入数据已经包含了位置信息(例如,通过某种预处理步骤),则可能不需要额外的位置嵌入。这个方法提供了一种灵活的方式来选择是否添加位置嵌入。
# 这段代码定义了 DeformableTransformerDecoderLayer 类中的 forward_ffn 方法,它实现了前馈网络(Feed-Forward Network,FFN)部分的前向传播。
# 方法定义。 forward_ffn 是一个实例方法,它接收一个参数。
# 1.tgt :代表目标张量,即当前解码器层的输入。
def forward_ffn(self, tgt):
# 通过该层的前馈网络部分执行前向传递。
"""Perform forward pass through the Feed-Forward Network part of the layer."""
# 前馈网络的前向传播。
# self.linear1(tgt) : 目标张量 tgt 首先通过第一个线性层 self.linear1 ,将维度从 d_model 转换为 d_ffn 。
# self.act(...) : 然后通过激活函数 self.act (通常是 nn.ReLU ),对第一个线性层的输出进行非线性变换。
# self.dropout3(...) : 接着通过 Dropout 层 self.dropout3 ,用于防止过拟合。
# self.linear2(...) : 最后通过第二个线性层 self.linear2 ,将维度从 d_ffn 转换回 d_model 。
tgt2 = self.linear2(self.dropout3(self.act(self.linear1(tgt))))
# 残差连接和层归一化。
# self.dropout4(tgt2) : 对 tgt2 应用 Dropout 层 self.dropout4 。
# tgt + ... : 将 Dropout 后的 tgt2 加到原始输入 tgt 上,实现残差连接。
tgt = tgt + self.dropout4(tgt2)
# 最后,对残差连接的结果应用层归一化(Layer Normalization) self.norm3 ,然后返回归一化后的结果。
return self.norm3(tgt)
# forward_ffn 方法实现了 Transformer 模型中 FFN 的标准结构:两个线性层之间有一个激活函数,整个结构前后都有 Dropout,并且包含残差连接和层归一化。这种结构有助于模型学习输入数据的复杂变换,同时残差连接和层归一化有助于避免训练过程中的梯度消失或爆炸问题。
# 这段代码定义了 DeformableTransformerDecoderLayer 类的 forward 方法,它实现了整个解码器层的前向传播。这个方法结合了自注意力、交叉注意力和前馈网络(FFN)的计算步骤。
# 方法定义。
# embed : 解码器层的输入嵌入。
# refer_bbox : 参考边界框,用于交叉注意力。
# feats : 从编码器层传入的特征。
# shapes : 特征图的空间形状。
# padding_mask : 用于交叉注意力的填充掩码。
# attn_mask : 用于自注意力的注意力掩码。
# query_pos : 查询的位置嵌入。
def forward(self, embed, refer_bbox, feats, shapes, padding_mask=None, attn_mask=None, query_pos=None):
# 对整个解码器层进行前向传递。
"""Perform the forward pass through the entire decoder layer."""
# Self attention
# 自注意力。
# 使用 with_pos_embed 方法将 位置 嵌入添加到输入嵌入 embed 中。
q = k = self.with_pos_embed(embed, query_pos)
# 将查询 q 和键 k 传递给自注意力模块 self_attn ,并接收输出。
# 这行代码是 DeformableTransformerDecoderLayer 类的 forward 方法中的一部分,它负责执行自注意力(self-attention)操作。
# 转置查询和键 : q.transpose(0, 1), k.transpose(0, 1)
# q 和 k 分别是自注意力中的查询(query)和键(key),它们通常是从解码器的嵌入张量 embed 中提取的。
# .transpose(0, 1) 方法将张量的第一个维度(通常是批次大小)和第二个维度(通常是序列长度)交换,以匹配 nn.MultiheadAttention 所需的输入形状。
# 传递值 : embed.transpose(0, 1)
# embed 是自注意力中的值(value),它与查询和键相同。
# nn.MultiheadAttention 模块期望输入的形状为 (seq_len, batch_size, feature_dim) 。
# 因此,如果你的输入张量 embed 的形状为 (batch_size, seq_len, feature_dim) ,你需要通过 transpose(0, 1) 将其转置为 (seq_len, batch_size, feature_dim) ,以满足 nn.MultiheadAttention 的输入要求。
# 执行自注意力 : self.self_attn(...)
# self.self_attn 是 nn.MultiheadAttention 模块,它计算查询和键之间的注意力权重,并将这些权重应用到值上。
# 应用注意力掩码 : attn_mask=attn_mask
# attn_mask 是一个可选的掩码,用于在自注意力计算中屏蔽某些位置,例如在处理不等长序列时。
# 接收输出并转置 : [0].transpose(0, 1)
# self.self_attn 返回的是一个元组,其中第一个元素是注意力输出,第二个元素是注意力权重(如果需要的话)。
# [0] 选择元组中的第一个元素,即注意力输出。
# .transpose(0, 1) 将输出张量转置回原始形状,以便与原始输入 embed 的形状对齐。
# 赋值给 tgt :tgt = ...
# 转置后的注意力输出被赋值给 tgt ,这个输出将被用于后续的残差连接和层归一化操作。
# 通过这行代码,模型能够在解码器内部捕捉序列元素之间的关系,这对于理解序列数据的结构和上下文信息非常重要。自注意力机制允许模型在不同的序列位置之间动态地分配不同的注意力权重。
tgt = self.self_attn(q.transpose(0, 1), k.transpose(0, 1), embed.transpose(0, 1), attn_mask=attn_mask)[
0
].transpose(0, 1)
# 应用残差连接和层归一化。
embed = embed + self.dropout1(tgt)
embed = self.norm1(embed)
# Cross attention
# 交叉注意力。
# 再次使用 with_pos_embed 方法将位置嵌入添加到经过自注意力处理的嵌入 embed 中。
# 将处理后的嵌入、参考边界框、特征和形状传递给交叉注意力模块 cross_attn ,并接收输出。
# 这行代码是 DeformableTransformerDecoderLayer 类的 forward 方法中的一部分,它负责执行交叉注意力(cross attention)操作。
# 添加位置嵌入 : self.with_pos_embed(embed, query_pos)
# 这个方法调用 with_pos_embed 静态方法,将位置嵌入 query_pos 添加到嵌入张量 embed 中,如果 query_pos 被提供的话。这有助于模型理解查询序列中各个元素的位置信息。
# 调整参考边界框的形状 : refer_bbox.unsqueeze(2)
# refer_bbox 是一个包含参考边界框信息的张量, unsqueeze(2) 方法在第二个维度(索引为 2)上增加一个新的轴,为后续的广播操作做准备。
# 执行交叉注意力 : self.cross_attn(...)
# elf.cross_attn 是多尺度可变形注意力(MSDeformAttn)模块,它结合了嵌入张量、调整形状后的参考边界框、特征张量 feats 、特征图的空间形状 shapes ,以及可选的填充掩码 padding_mask 。
# 这个模块计算编码器特征和解码器查询之间的注意力权重,并将这些权重应用到编码器特征上,生成加权的编码器特征表示。
# 接收输出 :
# 交叉注意力模块的输出被赋值给 tgt ,这个输出将用于后续的残差连接和层归一化操作。
# 通过这行代码,模型能够在不同尺度上关注编码器特征图中的不同区域,这对于理解全局上下文信息和处理空间变化非常有帮助。这种机制特别适用于需要精确空间定位的任务,如目标检测和图像分割。
tgt = self.cross_attn(
self.with_pos_embed(embed, query_pos), refer_bbox.unsqueeze(2), feats, shapes, padding_mask
)
# 应用残差连接和层归一化。
embed = embed + self.dropout2(tgt)
embed = self.norm2(embed)
# FFN
# 前馈网络。调用 forward_ffn 方法,将处理后的嵌入传递给前馈网络,并返回输出。
return self.forward_ffn(embed)
# forward 方法实现了 Transformer 解码器层的一个完整前向传播过程,包括自注意力、交叉注意力和前馈网络。
# 自注意力允许模型在解码器内部捕捉序列之间的关系,交叉注意力允许模型将编码器的输出与解码器的输入相结合,前馈网络则提供了非线性变换的能力。这种方法使得模型能够处理复杂的序列数据,并在各种序列到序列的任务中表现出色。
11.class DeformableTransformerDecoder(nn.Module):
# 这段代码定义了一个名为 DeformableTransformerDecoder 的类,它实现了基于 PaddleDetection 的可变形 Transformer 解码器。这个类继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
# 类定义。定义了一个名为 DeformableTransformerDecoder 的类,它继承自 PyTorch 的 nn.Module 类,表示这是一个神经网络模块。
class DeformableTransformerDecoder(nn.Module):
# 基于 PaddleDetection 的可变形 Transformer 解码器的实现。
"""
Implementation of Deformable Transformer Decoder based on PaddleDetection.
https://github.com/PaddlePaddle/PaddleDetection/blob/develop/ppdet/modeling/transformers/deformable_transformer.py
"""
# 构造函数。 这是 DeformableTransformerDecoder 类的构造函数,用于初始化类的实例。
# 1.hidden_dim : 隐藏层的维度。
# 2.decoder_layer : 解码器层的类或模块,它将被克隆多次以构成多层解码器。
# 3.num_layers : 解码器中的层数。
# 4.eval_idx : 用于评估的特定层的索引,如果设置为 -1 ,则使用最后一层。
def __init__(self, hidden_dim, decoder_layer, num_layers, eval_idx=-1):
# 使用给定的参数初始化DeformableTransformerDecoder。
"""Initialize the DeformableTransformerDecoder with the given parameters."""
super().__init__()
# 初始化解码器层。创建一个解码器层的列表,其中每个层都是 decoder_layer 的克隆。这个列表将包含 num_layers 个层。
# def _get_clones(module, n): -> 是从一个给定的模块( module )创建多个克隆(副本),并返回这些克隆组成的列表。返回一个 nn.ModuleList 实例,其中包含了 n 个克隆的模块。 -> return nn.ModuleList([copy.deepcopy(module) for _ in range(n)])
self.layers = _get_clones(decoder_layer, num_layers)
# 保存解码器中的层数。
self.num_layers = num_layers
# 保存隐藏层的维度。
self.hidden_dim = hidden_dim
# 保存用于评估的层的索引。如果 eval_idx 是负数,它将被转换为对应的正数索引,以适应从 0 开始的索引。
self.eval_idx = eval_idx if eval_idx >= 0 else num_layers + eval_idx
# 这个 DeformableTransformerDecoder 类实现了一个多层解码器,其中每一层都是可变形 Transformer 解码器层的实例。这种结构允许模型在处理序列数据时捕捉长距离依赖关系,并在不同的层之间传递信息。通过克隆单个解码器层多次,可以简化代码并提高效率。
# 这段代码定义了 DeformableTransformerDecoder 类的 forward 方法,它实现了整个解码器的前向传播过程。这个方法处理解码器嵌入、参考边界框、图像特征、特征形状,并与边界框头部和得分头部交互以产生输出。
# 方法定义。
# 1.embed : 解码器的输入嵌入。
# 2.refer_bbox : 参考边界框(锚点)。
# 3.feats : 图像特征。
# 4.shapes : 特征的形状。
# 5.bbox_head : 边界框头部网络,用于预测边界框。
# 6.score_head : 得分头部网络,用于预测得分。
# 7.pos_mlp : 位置多层感知机,用于生成位置嵌入。
# 8.attn_mask : 注意力掩码。
# 9.padding_mask : 填充掩码。
def forward(
self,
embed, # decoder embeddings
refer_bbox, # anchor
feats, # image features
shapes, # feature shapes
bbox_head,
score_head,
pos_mlp,
attn_mask=None,
padding_mask=None,
):
# 对整个解码器进行前向传递。
"""Perform the forward pass through the entire decoder."""
# 前向传播逻辑。
# 初始化输出为输入嵌入。
output = embed
# 初始化 存储边界框 和 类别得分 的列表。
dec_bboxes = []
dec_cls = []
# 初始化最后一个细化的边界框为 None 。
last_refined_bbox = None
# 对参考边界框应用 Sigmoid 函数。
refer_bbox = refer_bbox.sigmoid()
# 这段代码是 DeformableTransformerDecoder 类的 forward 方法的一部分,它描述了如何遍历解码器层,处理输出,并根据训练模式或评估模式进行不同的操作。
# 遍历解码器层。 enumerate(self.layers) 遍历解码器层列表 self.layers , i 是层的索引, layer 是解码器层的实例。
for i, layer in enumerate(self.layers):
# 层的前向传播。
# 对每个解码器层,使用当前的 output 、 参考边界框 refer_bbox 、 特征 feats 、 形状 shapes 、 填充掩码 padding_mask 、 注意力掩码 attn_mask 和 位置嵌入 pos_mlp(refer_bbox) 进行前向传播。
# 层的输出更新 output 用于下一个层的输入。
output = layer(output, refer_bbox, feats, shapes, padding_mask, attn_mask, pos_mlp(refer_bbox))
# 边界框预测和细化。
# 使用边界框头部网络 bbox_head[i] 预测边界框。
bbox = bbox_head[i](output)
# 通过将预测的边界框 bbox 与参考边界框 refer_bbox 结合,使用 inverse_sigmoid 函数和 Sigmoid 函数来细化边界框。
# def inverse_sigmoid(x, eps=1e-5): -> 它用于计算输入张量 x 的逆 Sigmoid 函数值。逆 Sigmoid 函数通常用于将 Sigmoid 函数的输出范围(0 到 1)映射回原始的实数范围。返回输入张量 x 的逆 Sigmoid 函数值。 -> return torch.log(x1 / x2)
refined_bbox = torch.sigmoid(bbox + inverse_sigmoid(refer_bbox))
# 这段代码是 DeformableTransformerDecoder 类的 forward 方法中的一部分,它处理模型的训练模式和评估模式下的不同逻辑。
# 在训练模式下,记录每个层的类别得分 dec_cls ,并根据是否是第一层或后续层更新边界框列表 dec_bboxes 。
if self.training:
# 在训练模式下,使用得分头部网络 score_head[i] 对当前层的输出 output 进行评分,并追加到类别得分列表 dec_cls 中。
dec_cls.append(score_head[i](output))
if i == 0:
# 如果是第一层( i == 0 ),则将细化的边界框 refined_bbox 追加到边界框列表 dec_bboxes 中。
dec_bboxes.append(refined_bbox)
else:
# 如果不是第一层,使用当前层的预测边界框 bbox 和上一层的细化边界框 last_refined_bbox 的逆 Sigmoid 值相结合,再次应用 Sigmoid 函数,并将结果追加到边界框列表 dec_bboxes 中。
dec_bboxes.append(torch.sigmoid(bbox + inverse_sigmoid(last_refined_bbox)))
# 在评估模式下,仅在指定的评估索引 self.eval_idx 处记录类别得分和边界框,并退出循环。
# 如果模型不在训练模式,检查当前层的索引 i 是否等于评估索引 self.eval_idx 。
elif i == self.eval_idx:
# 在评估模式下,同样使用得分头部网络 score_head[i] 对当前层的输出 output 进行评分,并追加到类别得分列表 dec_cls 中。
dec_cls.append(score_head[i](output))
# 将当前层的细化边界框 refined_bbox 追加到边界框列表 dec_bboxes 中。
dec_bboxes.append(refined_bbox)
# 一旦到达评估索引 self.eval_idx ,退出循环,因为只需要该层的输出。
break
# 这段代码确保在训练模式下,模型会处理所有层并收集每层的类别得分和边界框。在评估模式下,模型只会处理到指定的评估索引层,并收集该层的类别得分和边界框,然后停止进一步的处理。这样的设计允许模型在训练时学习所有层的信息,在评估时则专注于特定的层,这在实际应用中可以提高效率。
# 更新参考边界框和最后细化的边界框。
# 更新 last_refined_bbox 为当前层的 refined_bbox 。
last_refined_bbox = refined_bbox
# 更新 refer_bbox 为当前层的 refined_bbox ,如果是训练模式,则使用 detach() 避免梯度回传。
refer_bbox = refined_bbox.detach() if self.training else refined_bbox
# 返回结果。返回 所有层的边界框 和 类别得分 的堆叠列表。
return torch.stack(dec_bboxes), torch.stack(dec_cls)
# 这段代码展示了如何在 Transformer 解码器中处理序列数据,通过多层解码器层逐步细化边界框预测,并在每个层生成类别得分。这种方法允许模型在处理复杂的序列到序列任务时,如目标检测,能够逐步改进预测结果。
# 这个 forward 方法实现了 Transformer 解码器的完整前向传播过程,包括多层解码器层的处理、边界框和类别得分的预测。通过这种方式,模型能够逐步细化边界框预测,并在每个层生成类别得分。