用户云卷云舒 发表于 2024-6-28 23:12:06

[Algorithm] BEVformer 源码细节学习&&ubuntu20.04下的情况配置&&目标跑起

写在前面

筹划从源码和先跑起来入手,随后分模块逐步学习。期间分享自己的狐疑,风趣高效的语法征象。
之前学习了机器学习和神经网络(RNN) pytorch利用等相关知识,举行了两个demo的实战
如果对安装不感兴趣,欢迎大佬们阅读第二步交换辅导。
学习资源 (不断更新)



[*]深蓝学院
[*]好老哥的解读论文
[*]bilibili小哥论文学习视频,有点乱
[*]事后瞥见的复现老哥早瞥见就抄作业了,22下安装太折腾了
[*]解读1
[*]解读2
第一步目标把开源代码跑起来

情况预备

https://zhuanlan.zhihu.com/p/424817205
可以按照以上内容先安装号显卡驱动、Cuda和配置路径。
显卡驱动安装后 我用smi表现不出来显卡信息。
https://img-blog.csdnimg.cn/fd415bc765dc4189b838e9e891dec7ba.png
我显着在自带的software中心中的driver选择了驱动,但是smi下令找不到显卡信息,末了通过gpt查询,原来显卡驱动不停没有加载。因为我开起来secure boot的签名验证。关闭secure boot 就好了。 https://img-blog.csdnimg.cn/7881b7a4f9d64aceaf90e50524c02546.png
BEV 相关情况预备

首先 conda 和pip换源操纵可以参考:https://blog.csdn.net/h904798869/article/details/131719404
大概单用
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple +包名
阿里云镜像:https://mirrors.aliyun.com/pypi/simple/
清华大学镜像:https://pypi.tuna.tsinghua.edu.cn/simple/
安装建议严格按照文档阐明来,其中mmdection的内容建议按照maptr的来
BEVFormer
MapTR
其中:


[*]Building wheel for mmcv-full (setup.py) … / 这一步很慢,安心等待
[*]mmdection 用maptr的方法安装
[*]https://github.com/zhiqi-li/storage/releases 这里我是用Windows的梯直接去仓库拿的,贼快
[*]包罗预练习参数也可以直接挂梯子作者仓库去拿download
[*]安装cuda,cuda是系统级的东西不会在自己conda假造情况下,安装完成后别忘记添加路径,同时如果驱动安装好了就不必安装cuda自带的驱动,这里都有写明白https://blog.csdn.net/h904798869/article/details/131719404
[*]
记载我安装的几个问题 :

第一个大报错 是mmdetection编译报错,
查询了很久 发现根源是我的系统找不到有用的cuda
这里贴两个帖子,他们在讲解 pytorch cuda 和显卡驱动的 辩证关系
基本就是在排查 GPU驱动 Cuda版本 Pytorch版本之间的问题
https://zhuanlan.zhihu.com/p/658800083
https://zhuanlan.zhihu.com/p/91334380 我以为这个讲的最好
https://blog.csdn.net/qq_41094058/article/details/116207333
各人也可以利用我下面的代码验证以下
驱动安装阐明
用nvcc -v可以看到cuda版本
在conda list中确认其他库的版本
lspci | grep -i nvidia 检察显卡型号
import torch

print('CUDA 可用:', torch.cuda.is_available())
if torch.cuda.is_available():
    print('可用的 CUDA 设备数:', torch.cuda.device_count())
    print('当前 CUDA 设备索引:', torch.cuda.current_device())
    print('当前 CUDA 设备名称:', torch.cuda.get_device_name(torch.cuda.current_device()))
else:
    print('CUDA 不可用')
import torch, torchvision
print(torch.__version__, torch.cuda.is_available())
# Pytorch 实际使用的运行时的 cuda 目录
import torch.utils.cpp_extension
print(torch.utils.cpp_extension.CUDA_HOME)
# 编译该 Pytorch release 版本时使用的 cuda 版本
import torch
print(torch.version.cuda )

如果pytorch成功导入了,但是出现false 则阐明cuda设备不可用,可以去NVidia官网自动查找对应驱动https://www.nvidia.com/Download/index.aspx
以下是cuda的安装地址
https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=22.04&target_type=runfile_local
此外也可以利用ubantu系统自己查到的驱动
https://img-blog.csdnimg.cn/3552bf7bf68247409d921080b63d973f.png大概也可以利用
如此我可以利用上面的代码打印出内容了
https://img-blog.csdnimg.cn/70b9a17ef1544898b83fc5199ffd8e44.png
我的nvidia-smi下令也可以正常输出了
https://img-blog.csdnimg.cn/da68a21420004dd1b19f80f685e18552.png但依然有报错
我cuda toolkit和 nvidia driver是版本是可以匹配的,但cuda版本太高了我去
https://img-blog.csdnimg.cn/697c1c10412d4bff9c891d650e83f963.png然后重新严格安装配情况教程安装了cuda,不寻求自己研究了


[*]第二个大报错 安装 Detectron2
python -m pip install 'git+https://github.com/facebookresearch/detectron2.git'
长时间等待而后报错
git clone git@github.com:facebookresearch/detectron2.git

然后本地安装
pip install .
第一次接触的库的条记:
mmcv-full是一个针对盘算机视觉任务的开源工具库
MMDetection(Masked Object Detection)是一个开源盘算机视觉库,用于目标检测任务。它提供了丰富的目标检测算法和模型,包罗 Faster R-CNN、Mask R-CNN 等
MMsegmentation(Image Segmentation)是一个开源盘算机视觉库,用于图像分割任务。它包罗了多种图像分割算法和模型,如 U-Net、DeepLabV3 等。
Detectron2 是由Facebook AI Research(FAIR)开辟的开源目标检测库。它是原始 Detectron 库的继任者,为构建盘算机视觉模型提供了机动和模块化的框架,特别实用于目标检测和实例分割等任务
数据集下载

下载车身can数据、车辆位姿数据、地图场景数据、Camera Lidar传感器数据
https://img-blog.csdnimg.cn/51625a90d0ae448cad44efb2f3651fb4.png把map解压后的三个文件放入到mini的maps文件夹中
https://img-blog.csdnimg.cn/9f892278b65243759ba5745812ceb896.png展示结构如下
https://img-blog.csdnimg.cn/d9eb4e2453c847b69b6aef33daf49975.pnghttps://img-blog.csdnimg.cn/55d7e3983b634d98939ccd84ab457949.pnghttps://img-blog.csdnimg.cn/339dd89c6d394bbb9de0a01a33e30ddc.png
将data放入beformer下,然后
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes --version v1.0-mini --canbus ./data
在其后按照提示和教程缺啥补啥
https://img-blog.csdnimg.cn/40fb0d6f8edf46daa714e146e3955e36.png
报错就把这里将所有的data.converter 前面的tools去掉
练习 测试 画图

BEV复现教程 结果如下,看起来还不太正确。
情况和数据整理完毕后,按照教程举行,我的显卡可以跑small 和Tiny,把base更换成一位上即可。
https://img-blog.csdnimg.cn/ce451eeb2c324a2b8be5d32f4a0584c0.png
第二步开始论文具体阅读和代码解读
第二步论文阅读

论文初步阅读

https://img-blog.csdnimg.cn/2872069541ad4a6cadfcee68acb5a811.png
两个留意力机制为代码和文章重点解读部分
https://img-blog.csdnimg.cn/9f3e17f1e1f9426ea569aa4033743f04.pnghttps://img-blog.csdnimg.cn/59ee72c82f0546529439934a22f2b2d0.pnghttps://img-blog.csdnimg.cn/70c9d824cf244dbfb29b9b278c119c44.pnghttps://img-blog.csdnimg.cn/44b7fab2dcdb48c3b426261fcfce4091.png
论文细节阅读

代码结构

https://img-blog.csdnimg.cn/a17bd1b0bbc9465c95d2b86bb49ee141.png模型结构利用config管理参数的方式,在bevformer_XXX.py中是参数,具体的模型搭建其着实bevformer_head.py中 组件在modules中可以找到
https://img-blog.csdnimg.cn/0ee8d8155e684ebbb49d47828a50c33c.png
再次整理细节重点学习 (全代码阅读 TSA SCA MSD,后面两节按照实行顺序总结的代码整理较乱,日后缓慢更新 )

重点是Encoder中的 BEVFormerLayer,有作者提出的Temporalsellfattention SpatialCrossAttention 和可变形留意力
由于代码利用了注册器,不太好直接探求跳转链路,可以利用断点的方式按照顺序阅读。这里不做顺序阅读,而是将multi_scale_deformable_attn_function.py、spatial_cross_attention.py、temporal_self_attention.py的内容,有代价的每一句做注释解读,贴在下面。拓展语法和概念等则写于本章末了面。
temporal_self_attention.py 的解读

# ---------------------------------------------
# Copyright (c) OpenMMLab. All rights reserved.
# ---------------------------------------------
#Modified by Zhiqi Li
# ---------------------------------------------

from projects.mmdet3d_plugin.models.utils.bricks import run_time
from .multi_scale_deformable_attn_function import MultiScaleDeformableAttnFunction_fp32
from mmcv.ops.multi_scale_deform_attn import multi_scale_deformable_attn_pytorch
import warnings
import torch
import torch.nn as nn
from mmcv.cnn import xavier_init, constant_init
from mmcv.cnn.bricks.registry import ATTENTION
import math
from mmcv.runner.base_module import BaseModule, ModuleList, Sequential
from mmcv.utils import (ConfigDict, build_from_cfg, deprecated_api_warning,
                        to_2tuple)

from mmcv.utils import ext_loader
ext_module = ext_loader.load_ext(
    '_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])


@ATTENTION.register_module()
class TemporalSelfAttention(BaseModule):
    """An attention module used in BEVFormer based on Deformable-Detr.

    `Deformable DETR: Deformable Transformers for End-to-End Object Detection.
    <https://arxiv.org/pdf/2010.04159.pdf>`_.

    Args:
      embed_dims (int): The embedding dimension of Attention.
            Default: 256.
      num_heads (int): Parallel attention heads. Default: 64.
      num_levels (int): The number of feature map used in
            Attention. Default: 4.
      num_points (int): The number of sampling points for
            each query in each head. Default: 4.
      im2col_step (int): The step used in image_to_column.
            Default: 64.
      dropout (float): A Dropout layer on `inp_identity`.
            Default: 0.1.
      batch_first (bool): Key, Query and Value are shape of
            (batch, n, embed_dim)
            or (n, batch, embed_dim). Default to True.
      norm_cfg (dict): Config dict for normalization layer.
            Default: None.
      init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
            Default: None.
      num_bev_queue (int): In this version, we only use one history BEV and one currenct BEV.
         the length of BEV queue is 2.
    """
# embed_dims (int): 注意力机制的嵌入维度。
# num_heads (int): 注意力机制中并行的注意头数。
# num_levels (int): 使用的特征图的数量。
# num_points (int): 每个注意头中每个查询点的采样点数。
# im2col_step (int): 在图像到列矩阵转换中使用的步长。
# dropout (float): 应用于 inp_identity 的 Dropout 层的丢弃率。
# batch_first (bool): Key、Query 和 Value 的形状是否为 (batch, n, embed_dim) 或 (n, batch, embed_dim)。
# norm_cfg (dict): 用于规范化层的配置字典。
# init_cfg (obj: mmcv.ConfigDict): 用于初始化的配置对象。
# num_bev_queue (int): 在这个版本中,我们只使用一个历史 Bird's Eye View(BEV)和一个当前 BEV。BEV 队列的长度为 2。

    def __init__(self,
               embed_dims=256,
               num_heads=8,
               num_levels=4,
               num_points=4,
               num_bev_queue=2,
               im2col_step=64,
               dropout=0.1,
               batch_first=True,
               norm_cfg=None,
               init_cfg=None):

      super().__init__(init_cfg)
      if embed_dims % num_heads != 0:#检查 embed_dims 特征维度是否可以被 num_heads 多头数量 整除,否则引发错误。
            raise ValueError(f'embed_dims must be divisible by num_heads, '
                           f'but got {embed_dims} and {num_heads}')
      dim_per_head = embed_dims // num_heads # 多头注意力量 划分特征
      self.norm_cfg = norm_cfg
      self.dropout = nn.Dropout(dropout)
      self.batch_first = batch_first
      self.fp16_enabled = False

      # you'd better set dim_per_head to a power of 2
      # which is more efficient in the CUDA implementation
      def _is_power_of_2(n):
            if (not isinstance(n, int)) or (n < 0):
                raise ValueError(
                  'invalid input for _is_power_of_2: {} (type: {})'.format(
                        n, type(n)))
            return (n & (n - 1) == 0) and n != 0

      if not _is_power_of_2(dim_per_head):
            warnings.warn(
                "You'd better set embed_dims in "
                'MultiScaleDeformAttention to make '
                'the dimension of each attention head a power of 2 '
                'which is more efficient in our CUDA implementation.')

      self.im2col_step = im2col_step
      self.embed_dims = embed_dims
      self.num_levels = num_levels
      self.num_heads = num_heads
      self.num_points = num_points
      self.num_bev_queue = num_bev_queue
      # 用于生成采样偏移的线性层。
      self.sampling_offsets = nn.Linear(
            embed_dims*self.num_bev_queue, num_bev_queue*num_heads * num_levels * num_points * 2)
      # 用于生成注意力权重的线性层
      self.attention_weights = nn.Linear(embed_dims*self.num_bev_queue,                                 
         num_bev_queue*num_heads * num_levels * num_points)
               #: 用于投影值的线性层。
      self.value_proj = nn.Linear(embed_dims, embed_dims)
      #用于输出投影的线性层。
      self.output_proj = nn.Linear(embed_dims, embed_dims)
      self.init_weights()

    def init_weights(self):
      """Default initialization for Parameters of Module."""
      constant_init(self.sampling_offsets, 0.)
      thetas = torch.arange(
            self.num_heads,
            dtype=torch.float32) * (2.0 * math.pi / self.num_heads)
      grid_init = torch.stack(, -1)
      grid_init = (grid_init /
                     grid_init.abs().max(-1, keepdim=True)).view(
            self.num_heads, 1, 1,
            2).repeat(1, self.num_levels*self.num_bev_queue, self.num_points, 1)

      for i in range(self.num_points):
            grid_init[:, :, i, :] *= i + 1

      self.sampling_offsets.bias.data = grid_init.view(-1)
      #用于将参数初始化为常量值。
      constant_init(self.attention_weights, val=0., bias=0.)
      #用于使用 Xavier 初始化参数
      xavier_init(self.value_proj, distribution='uniform', bias=0.)
      xavier_init(self.output_proj, distribution='uniform', bias=0.)
      self._is_init = True

    def forward(self,
                query,
                key=None,
                value=None,
                identity=None,
                query_pos=None,
                key_padding_mask=None,
                reference_points=None,
                spatial_shapes=None,
                level_start_index=None,
                flag='decoder',

                **kwargs):
      """Forward Function of MultiScaleDeformAttention.

      Args:
            query (Tensor): Query of Transformer with shape
                (num_query, bs, embed_dims).
            key (Tensor): The key tensor with shape
                `(num_key, bs, embed_dims)`.
            value (Tensor): The value tensor with shape
                `(num_key, bs, embed_dims)`.
            identity (Tensor): The tensor used for addition, with the
                same shape as `query`. Default None. If None,
                `query` will be used.
            query_pos (Tensor): The positional encoding for `query`.
                Default: None.
            key_pos (Tensor): The positional encoding for `key`. Default
                None.
            reference_points (Tensor):The normalized reference
                points with shape (bs, num_query, num_levels, 2),
                all elements is range in , top-left (0,0),
                bottom-right (1, 1), including padding area.
                or (N, Length_{query}, num_levels, 4), add
                additional two dimensions is (w, h) to
                form reference boxes.
            key_padding_mask (Tensor): ByteTensor for `query`, with
                shape .
            spatial_shapes (Tensor): Spatial shape of features in
                different levels. With shape (num_levels, 2),
                last dimension represents (h, w).
            level_start_index (Tensor): The start index of each level.
                A tensor has shape ``(num_levels, )`` and can be represented
                as .

      Returns:
             Tensor: forwarded results with shape .
      """

# 输入参数:
# query (Tensor): Transformer 的查询张量,形状为 (num_query, bs, embed_dims)。
# key (Tensor): 键张量,形状为 (num_key, bs, embed_dims)。
# value (Tensor): 值张量,形状为 (num_key, bs, embed_dims)。
# identity (Tensor): 用于加法的张量,与 query 形状相同。如果为None,将使用 query。
# query_pos (Tensor): 用于 query 的位置编码。
# key_padding_mask (Tensor): 用于 query 的 ByteTensor,形状为 。
# reference_points (Tensor): 归一化的参考点,形状为 (bs, num_query, num_levels, 2),或 (N, Length_{query}, num_levels, 4)。这用于变形注意力。
# spatial_shapes (Tensor): 不同层级中特征的空间形状,形状为 (num_levels, 2),其中最后一个维度表示 (h, w)。
# level_start_index (Tensor): 每个层级的起始索引,形状为 (num_levels,)。
# 输出:
# 返回值: 形状为 的张量,表示前向传播的结果。

# flag: 一个字符串参数,可能用于指定这个操作是在编码器(encoder)还是解码器(decoder)中。

      if value is None:
            assert self.batch_first
            bs, len_bev, c = query.shape # (num_query, bs, embed_dims)
            value = torch.stack(, 1).reshape(bs*2, len_bev, c)
      #获取 query 张量的形状信息,并利用 torch.stack 和 reshape 函数将其复制为 value 张量
            # value = torch.cat(, 0)

      if identity is None:
            identity = query
      if query_pos is not None:
            query = query + query_pos
            # 将位置编码加入到q中
      if not self.batch_first:
            # change to (bs, num_query ,embed_dims)
            query = query.permute(1, 0, 2)
            value = value.permute(1, 0, 2)
            #按照惯例整理顺序

      bs,num_query, embed_dims = query.shape
      _, num_value, _ = value.shape# (num_key, bs, embed_dims)
      assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value
      # (num_levels, 2),其中最后一个维度表示 (h, w) ???没看懂
      assert self.num_bev_queue == 2

      query = torch.cat(, query], -1)
      value = self.value_proj(value)
#将 query 连接到 value 的前部分,并对 value 应用 self.value_proj
# 我的理解,由于value是上一时刻和上上bev的信息,如此增加模型在进行自注意力计算时对上下文的理解,而线性变换
#将输入数据映射到一个更高维度的空间,以便提高模型的表示能力
#gpt说如果一个查询需要依赖较远的位置的信息,通过将值信息添加到查询前面,
# 可以使得模型更容易捕捉到这些长距离的依赖关系,提高了模型对整个序列的建模能力。
      if key_padding_mask is not None:
            value = value.masked_fill(key_padding_mask[..., None], 0.0)
#如果存在 key_padding_mask,则使用 masked_fill 将 value 进行填充
      value = value.reshape(bs*self.num_bev_queue,
                              num_value, self.num_heads, -1)

      sampling_offsets = self.sampling_offsets(query)
      sampling_offsets = sampling_offsets.view(
            bs, num_query, self.num_heads,self.num_bev_queue, self.num_levels, self.num_points, 2)
      attention_weights = self.attention_weights(query).view(
            bs, num_query,self.num_heads, self.num_bev_queue, self.num_levels * self.num_points)
      attention_weights = attention_weights.softmax(-1)
# 利用线性层计算采样offset和权重,并且把权重归一化
      attention_weights = attention_weights.view(bs, num_query,
                                                   self.num_heads,
                                                   self.num_bev_queue,
                                                   self.num_levels,
                                                   self.num_points)

      attention_weights = attention_weights.permute(0, 3, 1, 2, 4, 5)\
            .reshape(bs*self.num_bev_queue, num_query, self.num_heads, self.num_levels, self.num_points).contiguous()
      sampling_offsets = sampling_offsets.permute(0, 3, 1, 2, 4, 5, 6)\
            .reshape(bs*self.num_bev_queue, num_query, self.num_heads, self.num_levels, self.num_points, 2)
#根据 reference_points 的形状不同(2 或 4),计算 sampling_locations
      if reference_points.shape[-1] == 2:
            offset_normalizer = torch.stack(
                , spatial_shapes[..., 0]], -1)
            sampling_locations = reference_points[:, :, None, :, None, :] \
                + sampling_offsets \
                / offset_normalizer
            #NONE插入维度
#将采样偏移 sampling_offsets 转化为相对于输入空间的实际位置。
      elif reference_points.shape[-1] == 4:
            sampling_locations = reference_points[:, :, None, :, None, :2] \
                + sampling_offsets / self.num_points \
                * reference_points[:, :, None, :, None, 2:] \
                * 0.5
      else:
            raise ValueError(
                f'Last dim of reference_points must be'
                f' 2 or 4, but get {reference_points.shape[-1]} instead.')
      if torch.cuda.is_available() and value.is_cuda:

            # using fp16 deformable attention is unstable because it performs many sum operations
            if value.dtype == torch.float16:
                MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32
            else:
                MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32
            output = MultiScaleDeformableAttnFunction.apply(
                value, spatial_shapes, level_start_index, sampling_locations,
                attention_weights, self.im2col_step)
      else:

            output = multi_scale_deformable_attn_pytorch(
                value, spatial_shapes, sampling_locations, attention_weights)

      # output shape (bs*num_bev_queue, num_query, embed_dims)
      # (bs*num_bev_queue, num_query, embed_dims)-> (num_query, embed_dims, bs*num_bev_queue)
      output = output.permute(1, 2, 0)

      # fuse history value and current value
      # (num_query, embed_dims, bs*num_bev_queue)-> (num_query, embed_dims, bs, num_bev_queue)
      output = output.view(num_query, embed_dims, bs, self.num_bev_queue)
      output = output.mean(-1)
#计算 output 张量中每个元素在最后一个维度上的均值
      # (num_query, embed_dims, bs)-> (bs, num_query, embed_dims)
      output = output.permute(2, 0, 1)

      output = self.output_proj(output)
      # out再整一次变换

      if not self.batch_first:
            output = output.permute(1, 0, 2)

      return self.dropout(output) + identity
    # 加一次dropout防止过拟合,同时引入残差连接或者跳跃连接,从而帮助梯度传播以及加速模型的训练
SCA


# ---------------------------------------------
# Copyright (c) OpenMMLab. All rights reserved.
# ---------------------------------------------
#Modified by Zhiqi Li
# ---------------------------------------------

from mmcv.ops.multi_scale_deform_attn import multi_scale_deformable_attn_pytorch
import warnings
import torch
import torch.nn as nn
import torch.nn.functional as F
from mmcv.cnn import xavier_init, constant_init
from mmcv.cnn.bricks.registry import (ATTENTION,
                                    TRANSFORMER_LAYER,
                                    TRANSFORMER_LAYER_SEQUENCE)
from mmcv.cnn.bricks.transformer import build_attention
import math
from mmcv.runner import force_fp32, auto_fp16

from mmcv.runner.base_module import BaseModule, ModuleList, Sequential

from mmcv.utils import ext_loader
from .multi_scale_deformable_attn_function import MultiScaleDeformableAttnFunction_fp32, \
    MultiScaleDeformableAttnFunction_fp16
from projects.mmdet3d_plugin.models.utils.bricks import run_time
ext_module = ext_loader.load_ext(
    '_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])


@ATTENTION.register_module()
class SpatialCrossAttention(BaseModule):
    """An attention module used in BEVFormer.
    Args:
      embed_dims (int): The embedding dimension of Attention.
            Default: 256. 是bev线性变换后注意里特征数量
      num_cams (int): The number of cameras 摄像头的数量
      dropout (float): A Dropout layer on `inp_residual`.
            Default: 0.. 为了防止过拟合dropout层参数
      init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
            Default: None. 初始化参数
      deformable_attention: (dict): The config for the deformable attention used in SCA. SCA的可变性注意力参数
    """
   

    def __init__(self,
               embed_dims=256,
               num_cams=6,
               pc_range=None,
               dropout=0.1,
               init_cfg=None,
               batch_first=False,
               deformable_attention=dict(
                     type='MSDeformableAttention3D',
                     embed_dims=256,
                     num_levels=4),
               **kwargs
               ):
      super(SpatialCrossAttention, self).__init__(init_cfg)

      self.init_cfg = init_cfg
      self.dropout = nn.Dropout(dropout)
      self.pc_range = pc_range
      self.fp16_enabled = False
      self.deformable_attention = build_attention(deformable_attention)
      self.embed_dims = embed_dims
      self.num_cams = num_cams
      self.output_proj = nn.Linear(embed_dims, embed_dims)
      self.batch_first = batch_first
      self.init_weight()

    def init_weight(self):
      """Default initialization for Parameters of Module."""
      xavier_init(self.output_proj, distribution='uniform', bias=0.)

      #以上初始化和TSA基本一致,没有笔记内容
   
    @force_fp32(apply_to=('query', 'key', 'value', 'query_pos', 'reference_points_cam'))
    def forward(self,
                query,
                key,
                value,
                residual=None,
                query_pos=None,
                key_padding_mask=None,
                reference_points=None,
                spatial_shapes=None,
                reference_points_cam=None,
                bev_mask=None,
                level_start_index=None,
                flag='encoder',
                **kwargs):
      """Forward Function of Detr3DCrossAtten.
      Args:
            query (Tensor): Query of Transformer with shape
                (num_query, bs, embed_dims).
                #Q
                #网上解释num_query类似于 DETR里面的 object_queries,也就是最多预测多少个目标
            key (Tensor): The key tensor with shape
                `(num_key, bs, embed_dims)`.
                # k
            value (Tensor): The value tensor with shape
                `(num_key, bs, embed_dims)`. (B, N, C, H, W)
            residual (Tensor): The tensor used for addition, with the
                same shape as `x`. Default None. If None, `x` will be used.
         #残差
             query_pos (Tensor): The positional encoding for `query`.
                Default: None.
            key_pos (Tensor): The positional encoding for`key`. Default
                None.
                # q 和 k的位置编码
            reference_points (Tensor):The normalized reference
                points with shape (bs, num_query, 4),
                all elements is range in , top-left (0,0),
                bottom-right (1, 1), including padding area.
                or (N, Length_{query}, num_levels, 4), add
                additional two dimensions is (w, h) to
                form reference boxes.
                # 参考点归一化
                数据标准化( Standardization )是将数据转换为均值为0,方差为1的数据,也就是将数据按比例缩放,
                使得其分布具有标准正态分布。 数据归一化( Normalization )
                是将数据转换为满足0≤x≤1的数据,也就是将数据缩放到 区间。
                #num_levels:The number of feature map used in
            Attention 被用于注意力的特征地图的数量
            key_padding_mask (Tensor): ByteTensor for `query`, with
                shape .
                #k 注意力掩码
            spatial_shapes (Tensor): Spatial shape of features in
                different level. With shape(num_levels, 2),
                last dimension represent (h, w).
                #空间形状,在不同level的特征的空间形状,最后一个维度2是(h,w)
            level_start_index (Tensor): The start index of each level.
                A tensor has shape (num_levels) and can be represented
                as .
                # 开始遍历的index
      Returns:
             Tensor: forwarded results with shape .
             # 返回查询数量 批次 特征数量
      """

      if key is None:
            key = query
      if value is None:
            value = key

      if residual is None:
            inp_residual = query
            # 残差链接网络传输值被初始化为query
            slots = torch.zeros_like(query)
            # 以 query的shape初始化 slot
      if query_pos is not None:
            query = query + query_pos
            # 同样地把线性层学习到的query位置编码和query叠加到一起

      bs, num_query, _ = query.size()#(num_query, bs, embed_dims)
      #这里是不是错了? 在input地方的备注维度顺序不同?


      D = reference_points_cam.size(3)
      indexes = []
      for i, mask_per_img in enumerate(bev_mask):
            index_query_per_img = mask_per_img.sum(-1).nonzero().squeeze(-1)
            indexes.append(index_query_per_img)
      max_len = max()
      # 每个特征点对应一个mask点,特征点的值为false,就可以将其在注意力中抛弃
      # 举例子说明:如果mask_per_img =m torch.tensor([,])      
      # sum_per_img = mask_per_img.sum(-1) 得到tensor
      # nonzero_indices = sum_per_img.nonzero() 得到tensor [,]
      # index_query_per_img = nonzero_indices.squeeze(-1)去除上一步操作后多出来的维度
      # 得到
      # 最后用indexes 储存计算好的indices
      # each camera only interacts with its corresponding BEV queries. This step cangreatly save GPU memory.
      queries_rebatch = query.new_zeros(
            )
      reference_points_rebatch = reference_points_cam.new_zeros(
            )
      
      for j in range(bs):
            for i, reference_points_per_img in enumerate(reference_points_cam):   
                index_query_per_img = indexes
                queries_rebatch = query
                reference_points_rebatch = reference_points_per_img
      #重新计算q和reference point 根据上一步计算的index
      num_cams, l, bs, embed_dims = key.shape

      key = key.permute(2, 0, 1, 3).reshape(
            bs * self.num_cams, l, self.embed_dims)
      value = value.permute(2, 0, 1, 3).reshape(
            bs * self.num_cams, l, self.embed_dims)

      queries = self.deformable_attention(query=queries_rebatch.view(bs*self.num_cams, max_len, self.embed_dims), key=key, value=value,
                                          reference_points=reference_points_rebatch.view(bs*self.num_cams, max_len, D, 2), spatial_shapes=spatial_shapes,
                                          level_start_index=level_start_index).view(bs, self.num_cams, max_len, self.embed_dims)
      # 使用可变形注意力
      for j in range(bs):
            for i, index_query_per_img in enumerate(indexes):
                slots += queries
# 用计算好的 queries和indexed更新slots
      count = bev_mask.sum(-1) > 0
# 将bev_mask 按照最后一个维度相加 判断是否大于0 结果储存在count中
      count = count.permute(1, 2, 0).sum(-1)
      count = torch.clamp(count, min=1.0) # 将count的元素的 最小值设为1
      slots = slots / count[..., None]
      slots = self.output_proj(slots)

      return self.dropout(slots) + inp_residual # .


@ATTENTION.register_module()
class MSDeformableAttention3D(BaseModule):
    """An attention module used in BEVFormer based on Deformable-Detr.
    `Deformable DETR: Deformable Transformers for End-to-End Object Detection.
    <https://arxiv.org/pdf/2010.04159.pdf>`_.
    Args:
      embed_dims (int): The embedding dimension of Attention.
            Default: 256.
      num_heads (int): Parallel attention heads. Default: 64.
      num_levels (int): The number of feature map used in
            Attention. Default: 4.
      num_points (int): The number of sampling points for
            each query in each head. Default: 4.
      im2col_step (int): The step used in image_to_column.
            Default: 64.
      dropout (float): A Dropout layer on `inp_identity`.
            Default: 0.1.
      batch_first (bool): Key, Query and Value are shape of
            (batch, n, embed_dim)
            or (n, batch, embed_dim). Default to False.
      norm_cfg (dict): Config dict for normalization layer.
            Default: None.
      init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
            Default: None.
    """
# embed_dims(嵌入维度):注意力机制中的嵌入维度。默认为256,影响了注意力机制中的向量表示维度。

# num_heads(注意力头数):并行的注意力头数。默认为64,控制了注意力机制中多头注意力的并行数量。

# num_levels(特征图数量):注意力中使用的特征图数量。默认为4,影响了注意力机制中特征图的层级数。

# num_points(采样点数):每个注意力头中每个查询点的采样点数。默认为4,决定了每个头部的注意力机制对查询点进行采样的数量。

# im2col_step(image_to_column 步长):在 image_to_column 操作中使用的步长。

# dropout(丢弃率):应用于 inp_identity 的 Dropout 层的丢弃率。默认为0.1,用于在训练中随机丢弃输入张量中的一部分元素,以防止过拟合。

# batch_first(批次优先):用于指定输入张量的维度顺序。如果为 True,表示输入张量的形状是(batch, n, embed_dim),否则为 (n, batch, embed_dim)。默认为 False。

# norm_cfg(归一化层配置):用于归一化层的配置字典。默认为 None,

# init_cfg(初始化配置):初始化配置的配置对象。
    def __init__(self,
               embed_dims=256,
               num_heads=8,
               num_levels=4,
               num_points=8,
               im2col_step=64,
               dropout=0.1,
               batch_first=True,
               norm_cfg=None,
               init_cfg=None):
      super().__init__(init_cfg)
      if embed_dims % num_heads != 0:
            raise ValueError(f'embed_dims must be divisible by num_heads, '
                           f'but got {embed_dims} and {num_heads}')
      dim_per_head = embed_dims // num_heads # 每一个头的特征数量
      self.norm_cfg = norm_cfg
      self.batch_first = batch_first
      self.output_proj = None
      self.fp16_enabled = False

      # you'd better set dim_per_head to a power of 2
      # which is more efficient in the CUDA implementation
      def _is_power_of_2(n):
            if (not isinstance(n, int)) or (n < 0):
                raise ValueError(
                  'invalid input for _is_power_of_2: {} (type: {})'.format(
                        n, type(n)))
            return (n & (n - 1) == 0) and n != 0

      if not _is_power_of_2(dim_per_head):
            warnings.warn(
                "You'd better set embed_dims in "
                'MultiScaleDeformAttention to make '
                'the dimension of each attention head a power of 2 '
                'which is more efficient in our CUDA implementation.')

      self.im2col_step = im2col_step
      self.embed_dims = embed_dims
      self.num_levels = num_levels
      self.num_heads = num_heads
      self.num_points = num_points
      self.sampling_offsets = nn.Linear(
            embed_dims, num_heads * num_levels * num_points * 2)
      self.attention_weights = nn.Linear(embed_dims,
                                           num_heads * num_levels * num_points)
      self.value_proj = nn.Linear(embed_dims, embed_dims)
# 同 TSA的注意力
      self.init_weights()

    def init_weights(self):
      """Default initialization for Parameters of Module."""
      constant_init(self.sampling_offsets, 0.)
      #极坐标网格构建
      # 创建一个0到2pi 等分为8分的tensor
      thetas = torch.arange(
            self.num_heads,
            dtype=torch.float32) * (2.0 * math.pi / self.num_heads)
      # 初始化grid
      #利用三角函数计算每个角度对应的余弦和正弦值,然后通过torch.stack在最后一个维度
      #将这两个值堆叠在一起形成一个形状为(num_heads, 2)的张量。
      # 这个张量的每一行表示一个角度对应的极坐标中的(x, y)坐标,
      # 使用grid_init.abs().max(-1, keepdim=True)计算每个行向量的绝对值中的最大值,
      # 并在最后一个维度上保持维度。然后,将grid_init除以这个最大值,实现归一化。
      # 最后,通过view函数将结果变形成形状为(num_heads, 1, 1, 2)的张量

      #最终的输出是一个形状为(num_heads, 1, 1, 2)的张量,
      # 表示了num_heads个头部的极坐标网格。每个头部的网格用一个(x, y)坐标表示,
      # 这个坐标在单位圆上,且在整个num_heads中均匀分布

      grid_init = torch.stack(, -1)
      grid_init = (grid_init /
                     grid_init.abs().max(-1, keepdim=True)).view(
            self.num_heads, 1, 1,
            2).repeat(1, self.num_levels, self.num_points, 1)
## 遍历第二个维度上,通过这种方式记录是第几个采样点的极坐标      
      for i in range(self.num_points):
            grid_init[:, :, i, :] *= i + 1

#grid_init.view(-1) 将 grid_init 张量展平为一个一维张量
      self.sampling_offsets.bias.data = grid_init.view(-1)
      constant_init(self.attention_weights, val=0., bias=0.)
      xavier_init(self.value_proj, distribution='uniform', bias=0.)
      xavier_init(self.output_proj, distribution='uniform', bias=0.)
      self._is_init = True

    def forward(self,
                query,
                key=None,
                value=None,
                identity=None,
                query_pos=None,
                key_padding_mask=None,
                reference_points=None,
                spatial_shapes=None,
                level_start_index=None,
                **kwargs):
      """Forward Function of MultiScaleDeformAttention.
      Args:
            query (Tensor): Query of Transformer with shape
                ( bs, num_query, embed_dims).
            key (Tensor): The key tensor with shape
                `(bs, num_key,embed_dims)`.
            value (Tensor): The value tensor with shape
                `(bs, num_key,embed_dims)`.
            identity (Tensor): The tensor used for addition, with the
                same shape as `query`. Default None. If None,
                `query` will be used.
            query_pos (Tensor): The positional encoding for `query`.
                Default: None.
            key_pos (Tensor): The positional encoding for `key`. Default
                None.
            reference_points (Tensor):The normalized reference
                points with shape (bs, num_query, num_levels, 2),
                all elements is range in , top-left (0,0),
                bottom-right (1, 1), including padding area.
                or (N, Length_{query}, num_levels, 4), add
                additional two dimensions is (w, h) to
                form reference boxes.
            key_padding_mask (Tensor): ByteTensor for `query`, with
                shape .
            spatial_shapes (Tensor): Spatial shape of features in
                different levels. With shape (num_levels, 2),
                last dimension represents (h, w).
            level_start_index (Tensor): The start index of each level.
                A tensor has shape ``(num_levels, )`` and can be represented
                as .
      Returns:
             Tensor: forwarded results with shape .
      """

      if value is None:
            value = query
      if identity is None:
            identity = query
      if query_pos is not None:
            query = query + query_pos

      if not self.batch_first:
            # change to (bs, num_query ,embed_dims)
            query = query.permute(1, 0, 2)
            value = value.permute(1, 0, 2)

      bs, num_query, _ = query.shape
      bs, num_value, _ = value.shape
      assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value

      value = self.value_proj(value)
      if key_padding_mask is not None:
            value = value.masked_fill(key_padding_mask[..., None], 0.0)
      value = value.view(bs, num_value, self.num_heads, -1)
      sampling_offsets = self.sampling_offsets(query).view(
            bs, num_query, self.num_heads, self.num_levels, self.num_points, 2)
      attention_weights = self.attention_weights(query).view(
            bs, num_query, self.num_heads, self.num_levels * self.num_points)

      attention_weights = attention_weights.softmax(-1)

      attention_weights = attention_weights.view(bs, num_query,
                                                   self.num_heads,
                                                   self.num_levels,
                                                   self.num_points)

      if reference_points.shape[-1] == 2:
            """
            For each BEV query, it owns `num_Z_anchors` in 3D space that having different heights.
            After proejcting, each BEV query has `num_Z_anchors` reference points in each 2D image.
            For each referent point, we sample `num_points` sampling points.
            For `num_Z_anchors` reference points,it has overall `num_points * num_Z_anchors` sampling points.
            """
            offset_normalizer = torch.stack(
                , spatial_shapes[..., 0]], -1)

            bs, num_query, num_Z_anchors, xy = reference_points.shape
            reference_points = reference_points[:, :, None, None, None, :, :]
            sampling_offsets = sampling_offsets / \
                offset_normalizer
            bs, num_query, num_heads, num_levels, num_all_points, xy = sampling_offsets.shape
            sampling_offsets = sampling_offsets.view(
                bs, num_query, num_heads, num_levels, num_all_points // num_Z_anchors, num_Z_anchors, xy)
            sampling_locations = reference_points + sampling_offsets
            bs, num_query, num_heads, num_levels, num_points, num_Z_anchors, xy = sampling_locations.shape
            assert num_all_points == num_points * num_Z_anchors

            sampling_locations = sampling_locations.view(
                bs, num_query, num_heads, num_levels, num_all_points, xy)

      elif reference_points.shape[-1] == 4:
            assert False
      else:
            raise ValueError(
                f'Last dim of reference_points must be'
                f' 2 or 4, but get {reference_points.shape[-1]} instead.')

      #sampling_locations.shape: bs, num_query, num_heads, num_levels, num_all_points, 2
      #attention_weights.shape: bs, num_query, num_heads, num_levels, num_all_points
      #准备步骤基本可TSA相同

      if torch.cuda.is_available() and value.is_cuda:
            if value.dtype == torch.float16:
                MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32
            else:
                MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32
            output = MultiScaleDeformableAttnFunction.apply(
                value, spatial_shapes, level_start_index, sampling_locations,
                attention_weights, self.im2col_step)
      else:
            output = multi_scale_deformable_attn_pytorch(
                value, spatial_shapes, sampling_locations, attention_weights)
      if not self.batch_first:
            output = output.permute(1, 0, 2)

      return output

拓展

关于embedding

embedding层的作用
我的理解,在之前NLP的学习中,一个词语放入到语义中可以利用一个多维度的向量在超空间里分解他的寄义,然后这个向量(坐标亦或位置),表现出来这个字和差异维度轴的相关程度,从而阐明了这个词语的寄义。
gpt表明:
Embedding 层在深度学习中主要用于将高维的离散数据映射到低维的一连空间
Embedding 层将输入的离散数据,比如单词、类别标签等,映射到一个固定维度的实数向量。这使得模型可以大概更好地理解和处理这些数据,因为一连向量包罗了更多的信息
Embedding 层会根据模型的练习数据学习出适合任务的特征表示。这意味着相似的类别或单词在嵌入空间中会有相似的表示,这有助于进步模型的泛化本领
Embedding 层可以将高维的离散数据映射到低维的一连空间。这有助于减少模型的参数数量,进步练习和推理效率
与独热编码等稀疏表示相比,Embedding 层提供了密集的表示,其中每个维度都包罗信息。这可以减少存储需求,并更有用地传达模型学到的知识
其中:
• 一连的向量: 意味着向量的每个元素都可以是任意实数,而不仅仅是整数。在嵌入层中,这通常是为了获得更机动、更具表达力的表示。
• 低维向量: 意味着向量的维度相对较低。在嵌入的上下文中,这有助于减少模型参数的数量,同时保留重要的特征。低维度的表示通常更容易被模型学习和泛化。
gpt举例:
一个emmeding的简朴利用
import torch
import torch.nn as nn

# 假设我们有一个词汇表的大小为10,每个词的嵌入维度为3
vocab_size = 10
embedding_dim = 3

# 创建一个 Embedding 层
embedding_layer = nn.Embedding(vocab_size, embedding_dim)

# 定义一个输入,包含三个词的索引
input_indices = torch.tensor(, dtype=torch.long)

# 将输入传递给嵌入层,得到嵌入向量
embedded_vector = embedding_layer(input_indices)

# 输出嵌入向量
print(embedded_vector)
tensor([[-2.8465,0.1365, -0.4851],
      [ 0.4402, -0.3163, -0.8770],
      [-0.4027, -0.1626,0.3808]], grad_fn=<EmbeddingBackward0>)
“每个词的嵌入维度为3” 意味着在嵌入层中为每个词分配的嵌入向量的维度是3。嵌入向量是一个实数向量,用于表示模型学习到的词汇表中每个词的语义信息。
在这个特定的例子中,每个词都被映射到一个包罗3个实数值的向量。这三个值构成了嵌入向量的三个维度。这种表示是通过练习神经网络从数据中学习得到的,网络通过反向传播算法来调解嵌入向量的权重,以最好地适应练习数据。
通常,嵌入维度的选择是一个超参数,可以根据具体任务和数据的性质举行调解。较大的嵌入维度可以提供更丰富的语义信息,但也需要更多的盘算资源。一样寻常而言,在实际应用中,嵌入维度的选择可能会在试验和验证中举行调解,以找到适合特定任务的最佳值。
关于xavier

对于网络的每一层初始化权重时,使得输入和输出的方差相等。这有助于制止在网络的前向传播和反向传播中引入梯度爆炸或梯度消失的问题。
权重初始化表明聚集
摘读如下
   Xavier初始化,也称为Glorot初始化,是一种常用的参数初始化方法,旨在有用地初始化神经网络中的权重。它的筹划思想是根据前一层和后一层的神经元数量来设置权重的初始值,以保持信号在前向传播过程中的适当范围;其焦点思想是,保持输入信号和梯度的方差在差异层之间大致相等,制止在深层网络中产生梯度消失或梯度爆炸的问题。这种初始化方法有助于提供符合的梯度范围,促进网络的稳固练习和收敛。。具体而言,对于具有线性激活函数(如sigmoid和tanh)的网络层,Xavier初始化将权重初始化为匀称分布或高斯分布,其方差取决于前一层神经元数量n和后一层神经元数量m。
对于匀称分布的Xavier初始化(匀称版):
从匀称分布中随机初始化权重矩阵W,范围为[-a, a],其中a = sqrt(6 / (n + m))。
对于高斯分布的Xavier初始化(高斯版):
从高斯分布中随机初始化权重矩阵W,均值为0,方差为variance,其中variance = 2 / (n + m)。 焦点筹划思想表明
当我们练习深度神经网络时,梯度的传播黑白常关键的。如果梯度在每一层传播时渐渐消失,即梯度靠近于零,那么底层的参数将很难更新,导致网络难以学习有用的表示。相反,如果梯度在每一层传播时渐渐增大,即梯度爆炸,那么参数更新的幅度会非常大,导致练习不稳固甚至无法收敛。因此,要保持输入信号和梯度的方差在差异层之间大致相等,以确保在前向传播和反向传播过程中,信号和梯度可以大概保持符合的范围,从而促进网络的稳固练习和收敛。
怎样保证输入信号和梯度的方差在差异层之间大致相等呢?在Xavier初始化中,存在递归的思想,其盘算方式是递归的,即权重的初始范围是根据前一层和后一层的神经元数量举行盘算的。
好的,让我们来看一个具有5层的神经网络的例子,以表明Xavier初始化是怎样工作的。假设我们有一个具有以下结构的神经网络:输入层(100个神经元)


[*]隐藏层1(80个神经元) - 隐藏层2(60个神经元) - 隐藏层3(40个神经元) - 输出层(10个神经元)。现在,我们将利用Xavier初始化来初始化每一层的权重。
对于隐藏层1,我们需要盘算它的权重初始范围。根据Xavier初始化的公式,我们需要知道前一层和后一层的神经元数量。在这种情况下,前一层是输入层,有100个神经元,后一层是隐藏层1自己,有80个神经元。根据公式,我们可以盘算权重初始范围a:a
= sqrt(6 / (100 + 80)) ≈ 0.136。现在,我们可以从匀称分布[-0.136, 0.136]中随机初始化隐藏层1的权重矩阵。
接下来,我们继续盘算隐藏层2的权重初始范围。前一层是隐藏层1,有80个神经元,后一层是隐藏层2自己,有60个神经元。我们利用相同的公式来盘算权重初始范围a:a
= sqrt(6 / (80 + 60)) ≈ 0.153。然后,我们从匀称分布[-0.153, 0.153]中随机初始化隐藏层2的权重矩阵。
类似地,我们可以盘算隐藏层3和输出层的权重初始范围,并举行相应的初始化。
通过如许的递归盘算和初始化过程,Xavier初始化确保了每一层的权重都与前一层和后一层的神经元数量相关联。这有助于平衡信号和梯度的传播,制止梯度消失或梯度爆炸问题,从而进步神经网络的练习稳固性和收敛性能。
关于stack 和 reshape和permute和cat和view和NONE的用法

tmp_prev_bev = prev_bev[:, i].reshape(
                        bev_h, bev_w, -1).permute(2, 0, 1)
有我不熟悉的语法 gpt查询如下:
A:
prev_bev[:, i]:表示选择 prev_bev 张量中的所有行 (
页: [1]
查看完整版本: [Algorithm] BEVformer 源码细节学习&&ubuntu20.04下的情况配置&&目标跑起