第十七章 原理篇:Deformable DETR

news2024/11/26 0:42:06

参考教程:

论文:https://arxiv.org/pdf/2010.04159.pdf

源码:https://github.com/fundamentalvision/Deformable-DETR

文章目录

  • Deformable Conv
  • Deformable DETR
    • 计算量
    • Method
      • Deformable Attention Module
      • DeformAttn计算量
      • Multi-scale Deformable Attention Module
    • Deformable Transformer
  • 代码实现
    • DeformableDETR
      • __init__()
      • forward()
    • backbone 和 position encoding
    • transformer
      • MSDeformAttn
        • init()
      • forward()
    • Encoder和Decoder

Deformable Conv

首先来简单的介绍一下可变形卷积。

代码链接: torchvison.DeformConv2d

deformable conv 可变形卷积认为CNN固定的几何结构不能适应图像中复杂的物体。普通的卷积是在固定的、规则的网络点上进行数据采样,感受野的形状是受限的,网络对几何形变的适应力也是受限制的。

所以可变形卷积就给卷积核的每个采样点增加一个可学习的偏移量,让采样点不再受限于规则的网格上。
在这里插入图片描述
普通的卷积网络在处理一张图的不同位置时,感受野大小都是相同的,这不太合适。对编码了位置信息的神经网络来说,不同位置对应的应该是不同的元素或形变的物体。它不应该是固定的一个框。

在上图中举了几个可变形卷积的例子。第一个是我们标准的方方正正的普通卷积。第二个是deformed 卷积,可以看到它的采样点都有着不同方向和距离的偏移。第三个图比较规整,它更像是我们常说的空洞卷积。第四个图是角度的旋转。
在这里插入图片描述
上面这个图是比较直白的deformable convolution的原理图。regular的卷积是某个位置上的值是通过输入的对应位置和卷积核的加权结合得到的。在deformable convolution中,它给每个位置 p n p_n pn增加了一个偏移量 Δ p n \Delta p_n Δpn。而这个偏移量则是由另一个卷积得到的。

也就是说deformable convolution其实是由两个卷积组成的,一个走正常的卷积,一个用于计算偏移量。

Deformable DETR

DETR作为一个省去了很多比如NMS这些处理工作的目标检测方法,不仅可以实现端到端的检测,检测的效果还非常好。

但是它也是有一些问题的。

  1. 首先它的收敛速度非常慢。这个问题之前VIT也有说过,算是基于self-attention训练的常见问题。它相当于自己学习感受野和元素间的关系,更加flexible,所以收敛时间也就更长。
  2. 它检测小物体的效果比较差。因为他是基于backbone得到的feature map做的检测,所以也受到feature map的resolution的限制。假如你想用high-resolution的featuremap来做预测,那么使用DETR的计算复杂度也太高了,是不可接受的。

作者在论文中说,上面这两个问题归根到底都是transformer自身的缺陷。在初始化的时候,这个attention module对feature map上的每一个像素施加了均匀的注意力权重,所以需要长时间的寻来你来关注到稀疏的有意义的位置。

deformable convolution呢,则是一个非常有效的关注稀疏空间位置的方法,它天然地能避免上面这个问题,但是它呢缺乏元素相对关系的建模,而这又是transformer能给它的。所以才会想把两者结合起来。

计算量

之前我们有计算过MSA的计算量,这里来复习一下。

  1. C代表token的维度,假设我们的输入大小是 A h ∗ w , C A^{h*w,C} Ahw,C。在进行Q\K\V计算时唯独没有发生变化,那么进行三次矩阵乘法 A h ∗ w , C ∗ W C , C A^{h*w,C}*W^{C,C} Ahw,CWC,C,每次计算量是 h w C 2 hwC^2 hwC2,计算了三次,总的计算量是 3 h w C 2 3hwC^2 3hwC2
  2. 然后计算 Q h ∗ w , C Q^{h*w,C} Qhw,C K h ∗ w , C K^{h*w,C} Khw,C的内积,计算结果为 X h ∗ w , h ∗ w X^{h*w,h*w} Xhw,hw,内积计算需要点对点进行计算,所以计算量是 h 2 w 2 C h^2w^2C h2w2C
  3. 再下一步计算 X h ∗ w , h ∗ w ∗ V h ∗ w , C X^{h*w,h*w}*V^{h*w,C} Xhw,hwVhw,C,计算量为 h 2 w 2 C h^2w^2C h2w2C
  4. 因为是多头自注意力,所以还需要增加一步 B h ∗ w , C ∗ W C , C B^{h*w,C}*W^{C,C} Bhw,CWC,C,计算量为 h w C 2 hwC^2 hwC2
  5. 加起来的总的计算量是:
    4 h w C 2 + 2 h 2 w 2 C 4hwC^2 + 2h^2w^2C 4hwC2+2h2w2C

假设我们以backbone的最后一层featuremap为输出,送入encoder中进行MSA。假如你的输入图像大小为480*480*3,那么经过backbone后得到的featuremap大小为15*15*2048,2048的通道数还需要经过1*1卷积降维变成C,直接代入数字:
4 h w C 2 + 2 h 2 w 2 C = 4 × 15 × 15 × C 2 + 2 × 1 5 2 × 1 5 2 × C = 900 C 2 + 101250 C \begin{align} &4hwC^2 + 2h^2w^2C \\ = &4\times15\times15\times C^2+ 2\times15^2\times15^2\times C\\ =&900C^2 + 101250C \end{align} ==4hwC2+2h2w2C4×15×15×C2+2×152×152×C900C2+101250C

你想要用多尺度、多层featuremap,比如你使用倒数第二层,那么这时得到的featuremap大小时30*30*1024。也就是h和w都翻倍了,这带来的计算量的提升是很恐怖的。

所以说不太可能使用比较低层的featuremap来作transformer的输入。

Method

在这里插入图片描述

Deformable Attention Module

transformer的一个问题是对于一个输入的feature map,它会看一遍所有的点。所以作者提出了deformable attention module,只关注目标点附近的一小部分点集,而不是整个feature map。

原本的MSA公式如下:
M u l t i H e a d A t t n ( z q , x ) = ∑ m = 1 M W m [ ∑ k ∈ Ω k A m q k ∗ W m ′ x k ] MultiHeadAttn(z_q,x) = \sum^M_{m=1}W_m[\sum_{k\in\Omega_k}A_{mqk}*W'_mx_k] MultiHeadAttn(zq,x)=m=1MWm[kΩkAmqkWmxk]
其中m表示第m个head。head的个数是M个。
x k x_k xk是输入特征,也就是通俗的讲的token。一共有N个输入。
W m ′ x k W'_mx_k Wmxk可以理解成从输入的token转变成MSA中的Value的过程。
A m q k A{mqk} Amqk是求得的attention matrix。表示第m个head上的第q个Query和第k个Key之间的attention。

而作者提出的deformable attention的公式如下:
D e f o r m A t t n ( z q , p q , x ) = ∑ m = 1 M W m [ ∑ k = 1 K A m q k ∗ W m ′ x ( p q + Δ p m q k ] DeformAttn(z_q,p_q,x) = \sum^M_{m=1}W_m[\sum^K_{k=1}A{mqk}*W'_mx(p_q+\Delta p_{mqk}] DeformAttn(zq,pq,x)=m=1MWm[k=1KAmqkWmx(pq+Δpmqk]
两者相比有明显的差别:

  1. 在MSA中k是从全局取的,而在DeformAttn中k是有限制的。K是取样的总数。每个Query只和K个Key进行注意力的计算。
  2. 在DeformAttn中使用了一个 Δ p m q k \Delta p_{mqk} Δpmqk来代表采样的偏移量。

此外,通过前面的module流程图,也能看到在deformAttn中,attention matrix不再是Query和Key进行内积得到的,而是通过linear的线性变化得到的。
在这里插入图片描述
在实际实验中,feature z q z_q zq会通过一个线性映射来得到通道数为3MK的结果,其中前2MK是用来代表偏移量 Δ p m q k \Delta p_{mqk} Δpmqk的,最后一个MK会送入softmax操作来获得我们的attention matrix。

DeformAttn计算量

理一下这个过程,并算一下计算量。

假设输入大小为 H ∗ W , C H*W,C HW,C,M代表head的个数,K代表选取的样本点的个数。

以下指的是encoder的过程,在decoder中没有HW的概念,直接使用的是object query的数量。

  1. 计算attention matrix需要 H ∗ W ∗ C ∗ M K H*W*C*MK HWCMK
  2. 计算偏移量需要 2 ∗ H ∗ W ∗ C ∗ M K 2*H*W*C*MK 2HWCMK
  3. 进行Value值的计算有两种方法:
    1. 一种是采样前进行计算,需要 H ∗ W ∗ C 2 H*W*C^2 HWC2
    2. 一种是采样后计算,需要 H ∗ W ∗ K ∗ C 2 H*W*K*C^2 HWKC2
  4. 计算输出的时候需要 H ∗ W ∗ C ∗ K H*W*C*K HWCK
  5. 用的是多头,还需要增加一步 B H ∗ W , C ∗ W C , C B^{H*W,C}*W^{C,C} BHW,CWC,C,计算量为 H W C 2 HWC^2 HWC2
  6. 在偏移量计算的时候,要用到插值的方式,带来了比较多的计算量,计为 4 ∗ H ∗ W ∗ C ∗ K 4*H*W*C*K 4HWCK

所以总的计算量是:
3 H W C M K + 5 H W C K + H W C 2 + m i n H W C 2 , H W K C 2 3HWCMK + 5HWCK + HWC^2 + min{HWC^2, HWKC^2} 3HWCMK+5HWCK+HWC2+minHWC2,HWKC2

在decoder中因为输入的token个数不是HW,所以要进行一些替换。N代表object query的数量,在decoder中的计算量如下。
3 N C M K + 5 N C K + N C 2 + m i n H W C 2 , N K C 2 3NCMK + 5NCK + NC^2 + min{HWC^2, NKC^2} 3NCMK+5NCK+NC2+minHWC2,NKC2

Multi-scale Deformable Attention Module

为了便于在多尺度多层次的feature map上应用,作者又提出了DeformAttn的扩展:Multi-scale deformable attention。

M S D e f o r m A t t n ( z q , p ^ q , { x l } l = 1 L ) = ∑ m = 1 M W m [ ∑ l = 1 L ∑ k = 1 K A m l q k ∗ W m ′ x l ( ϕ ( p ^ q ) + Δ p m l q k ) ] MSDeformAttn(z_q,\hat{p}_q,\{x^l\}^L_{l=1}) = \sum^M_{m=1}W_m[\sum^L_{l=1}\sum^K_{k=1}A{mlqk}*W'_mx^l(\phi(\hat{p}_q)+\Delta p_{mlqk})] MSDeformAttn(zq,p^q,{xl}l=1L)=m=1MWm[l=1Lk=1KAmlqkWmxl(ϕ(p^q)+Δpmlqk)]
m还代表第几个head,l代表第几个feature level。
Δ p m l q k \Delta p_{mlqk} Δpmlqk A m l q k A_{mlqk} Amlqk代表了第m个head上第l个feature level下第k个采样点的偏移量和attention。并且满足不同层不同head的权重和为1。

对于多尺度的feature map,要求是将它们的通道数映射成一致。如下图:所有feature map的通道数都被映射为256。

在这里插入图片描述
假如你想要的feature map数量比backbone给的要多怎么办呢,就要使用stride = 2的3*3卷积来获得更小的feature map,同时通道数还要保持一致。

Deformable Transformer

在这里插入图片描述
看一下整体结构。

在encoder部分,所有的attention模块都用的是multi-scale deformable attention。并且用的也是多尺度的输入和输出。

在decoder中,它的cross-attention的部分被换成了multi-scale deformable attention,self-attention的部分用的还是普通的MSA。

代码实现

源码链接:
https://github.com/fundamentalvision/Deformable-DETR

我们用和DETR一样的顺序来看Deformable DETR的代码。

Deformable DETR的代码和DETR代码整体上其实大差不大,没有很大的区别。

DeformableDETR

我们首先来看DeformableDETR这个类。

init()

class DeformableDETR(nn.Module):
    """ This is the Deformable DETR module that performs object detection """
    def __init__(self, backbone, transformer, num_classes, num_queries, num_feature_levels,
                 aux_loss=True, with_box_refine=False, two_stage=False):

它的传入参数和DETR的也很一致。

  1. backbone:你打算使用的backbone
  2. transformer:构造好的transformer
  3. num_classes:数据集中物体种类的数量。
  4. num_feature_levels 你想要使用的feature层数
  5. num_queries:object queries的数量,也就代表了每张图中能预测的物体的最大数量。
  6. aux_loss:是否要使用aux_loss。

在此基础上增加了with_box_refine和two-stage,这是deformable DETR做的扩展内容,我们先忽略。

它的构造函数部分,我们忽略掉扩展内容后,看看剩下的代码。

self.num_queries = num_queries # object query的数量
self.transformer = transformer # 你的transformer
hidden_dim = transformer.d_model
self.class_embed = nn.Linear(hidden_dim, num_classes) # 分类预测头
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3) # bbox预测头
self.backbone = backbone # 你的backbone
self.aux_loss = aux_loss # 是否使用aux_loss
self.num_feature_levels = num_feature_levels

if not two_stage:
	self.query_embed = nn.Embedding(num_queries, hidden_dim*2)

self.class_embed = nn.ModuleList([self.class_embed for _ in range(num_pred)])
self.bbox_embed = nn.ModuleList([self.bbox_embed for _ in range(num_pred)])

前几行和DETR中一样。多了个num_feature_levels来控制是否使用多尺度。

假如说你打算使用多尺度,因为不同尺度下的feature map的通道数不一样,所以要将它们映射到一样的通道数。

假如你想要的feature map数量比num_backbone_outs要多,那么就需要用stride=2的3*3卷积自己再做出来一点。

下面的代码就是实现了这个功能。

假如num_feature_levels = 1,就直接将最后一层映射过去就行。
假如num_feature_levels>1,对于num_backbone_outs范围内的部分,使用1*1卷积进行降维,超过的部分,就用3*3卷积创造新的feature map。

if num_feature_levels > 1:
    num_backbone_outs = len(backbone.strides)
    input_proj_list = []
    for _ in range(num_backbone_outs):
        in_channels = backbone.num_channels[_]
        input_proj_list.append(nn.Sequential(
            nn.Conv2d(in_channels, hidden_dim, kernel_size=1),
            nn.GroupNorm(32, hidden_dim),
        ))
    for _ in range(num_feature_levels - num_backbone_outs):
        input_proj_list.append(nn.Sequential(
            nn.Conv2d(in_channels, hidden_dim, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(32, hidden_dim),
        ))
        in_channels = hidden_dim
    self.input_proj = nn.ModuleList(input_proj_list)
else:
    self.input_proj = nn.ModuleList([
        nn.Sequential(
            nn.Conv2d(backbone.num_channels[0], hidden_dim, kernel_size=1),
            nn.GroupNorm(32, hidden_dim),
        )])

这一部分也就是对应着DETR中的。

nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size=1)

forward()

在DETR中这部分代码也是比较简单的,在DeformableDETR中变长了不少。

因为这里是考虑了multi-scale feature map的。在DETR的backbone中拿出的features,直接经过projection后就送进transformer了。在这里还需要按顺序每个都projection一下。

features, pos = self.backbone(samples)
srcs = []
masks = []
for l, feat in enumerate(features):
    src, mask = feat.decompose()
    srcs.append(self.input_proj[l](src))
    masks.append(mask)
    assert mask is not None

假如你想要的featuremap比backbone给的多,这时候还要多处理一下。多出来的第一个featuremap还是在backbone的输出上进行卷积,后面的都要在前一个的基础上进行卷积。
并且也要专门生成position embedding。

if self.num_feature_levels > len(srcs):
     _len_srcs = len(srcs)
     for l in range(_len_srcs, self.num_feature_levels):
         if l == _len_srcs:
             src = self.input_proj[l](features[-1].tensors)
         else:
             src = self.input_proj[l](srcs[-1])
         m = samples.mask
         mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0]
         pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype)
         srcs.append(src)
         masks.append(mask)
         pos.append(pos_l)

然后这些东西,才一股脑地放进transformer里面去。

 hs, init_reference, inter_references, enc_outputs_class, enc_outputs_coord_unact = self.transformer(srcs, masks, pos, query_embeds)

之前DETR中的transformer的输入是tensor,这里变成了list,说明transformer中肯定也有比较大的改动。

backbone 和 position encoding

和DETR中没有什么区别,仍然是使用了IntermediateLayerGetter来获取不同层的输出。

在backbone的部分增加了一些attributes。主要是因为在DeformDETR的forward()中会用到。

 if return_interm_layers:
      # return_layers = {"layer1": "0", "layer2": "1", "layer3": "2", "layer4": "3"}
      return_layers = {"layer2": "0", "layer3": "1", "layer4": "2"}
      self.strides = [8, 16, 32]
      self.num_channels = [512, 1024, 2048]
  else:
      return_layers = {'layer4': "0"}
      self.strides = [32]
      self.num_channels = [2048]

backbone里面的len(self.strides)也就代表了你输出的featuremap的个数。
在刚刚DeformDETR的forward()中,当num_feature_level = 1的时候,它的projection用的是nn.Conv2d(backbone.num_channels[0], ,我解释说这代表输入是最后一层的通道数,其实就是我们的backbone的num_channels数量只有1,所以[0]也是最后一层。

transformer

这一部分变复杂了好多啊。

DeformableTransformer的整体结构仍是由n个encoderlayer组成的一个Encoder和n个decoderlayer组成的一个Decoder拼成的。

一个区别是这里还增加了一个level embedding 还有一个reference_points。

self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model))

self.reference_points = nn.Linear(d_model, 2)

然后我们来看一下forward()的部分。

bs, c, h, w = src.shape
src = src.flatten(2).permute(2, 0, 1)
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)
mask = mask.flatten(1)

这里的src在flatten()后使用permute(2,0,1)是因为nn.MultiheadAttention中默认的batch_size不是在最前面的。

在DETR中只有一层feature map,所以都是直接算的。但是在这里有多个,所以要遍历list后每一个都处理一下。

for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
     bs, c, h, w = src.shape
     spatial_shape = (h, w)
     spatial_shapes.append(spatial_shape)
     src = src.flatten(2).transpose(1, 2)
     mask = mask.flatten(1)
     pos_embed = pos_embed.flatten(2).transpose(1, 2)
     lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
     lvl_pos_embed_flatten.append(lvl_pos_embed)
     src_flatten.append(src)
     mask_flatten.append(mask)

单个比较来看,src, mask的处理和之前没有区别,pos_embedding上额外加上了level_embed,进行了level位置的区分。

然后这些东西就被一股脑地送进encoder中去。

memory = self.encoder(src_flatten, spatial_shapes, level_start_index, valid_ratios, lvl_pos_embed_flatten, mask_flatten)

假如说你用的不是two-stage的扩展的话。

query_embed, tgt = torch.split(query_embed, c, dim=1)
            query_embed = query_embed.unsqueeze(0).expand(bs, -1, -1)
            tgt = tgt.unsqueeze(0).expand(bs, -1, -1)
            reference_points = self.reference_points(query_embed).sigmoid()
            init_reference_out = reference_points

获取我们的decoder的输入还有reference_points。然后将这些东西和encoder的输出一起,一股脑送进decoder里。

hs, inter_references = self.decoder(tgt, reference_points, memory,                                          spatial_shapes, level_start_index, valid_ratios, query_embed, mask_flatten)

MSDeformAttn

forward部分比较复杂,大致看一下流程。细节不管了。

init()

相比于一般的attention的输入d_model, nhead,这里增加了一个n_levels和n_points。
n_levels就是我们之前公式里的L,你想要在多少个feature level上进行attention。
n_points就是我们之前公式里的K,采样点的数量。

class MSDeformAttn(nn.Module):
    def __init__(self, d_model=256, n_levels=4, n_heads=8, n_points=4):
       super().__init__()
       self.im2col_step = 64
       self.d_model = d_model
       self.n_levels = n_levels
       self.n_heads = n_heads
       self.n_points = n_points

       self.sampling_offsets = nn.Linear(d_model, n_heads * n_levels * n_points * 2) 
       self.attention_weights = nn.Linear(d_model, n_heads * n_levels * n_points)
       self.value_proj = nn.Linear(d_model, d_model)
       self.output_proj = nn.Linear(d_model, d_model)

       self._reset_parameters()

偏移量和attention matrix都通过线性映射得到。

偏移量的维度是 2 ∗ M ∗ K ∗ L 2*M*K*L 2MKL, attention matrix的维度是 M ∗ K ∗ L M*K*L MKL

forward()

forward()部分的输入有点多。

配合着transformer的输入看了一下之后,大致了解了这部分都是什么。

def forward(self, query, reference_points, input_flatten, input_spatial_shapes, input_level_start_index, input_padding_mask=None):
  1. query: 我们输入的带位置编码的query。
  2. input_flatten:在encoder的输入部分其实就是不带位置编码的query,原始的flatten的feature。在decoder的cross_attn部分就是encoder的输出。
  3. 剩下的先不管,记住这两个就可以。

我们的value是对input_flatten进行projection得到的,这个在整体流程图中也是可以看到的。

value = self.value_proj(input_flatten)

而我们的偏移量和attention weight都是基于query计算的。

sampling_offsets = self.sampling_offsets(query).view(N, Len_q, self.n_heads, self.n_levels, self.n_points, 2)
attention_weights = self.attention_weights(query).view(N, Len_q, self.n_heads, self.n_levels * self.n_points)
attention_weights = F.softmax(attention_weights, -1).view(N, Len_q, self.n_heads, self.n_levels, self.n_points)

这里的attention weights要保证和为1,所以也要做softmax。

然后这些会经过一个MSDeformAttnFunction的处理,这里面也包括了前向和后向的计算。源码位置:ms_deform_attn_func.py

最后的输出还要经过projection:

output = self.output_proj(output)

Encoder和Decoder

如果忽略MSA到DeformAttn的变化,encoder和decoder中的改动其实挺小的。

我们直接来看一下encoder layer的forward():

def forward(self, src, pos, reference_points, spatial_shapes, level_start_index, padding_mask=None):
        # self attention
        src2 = self.self_attn(self.with_pos_embed(src, pos), reference_points, src, spatial_shapes, level_start_index, padding_mask)
        src = src + self.dropout1(src2)
        src = self.norm1(src)

        # ffn
        src = self.forward_ffn(src)

        return src

简洁明了:

  1. 加pos_embedding
  2. self-attention得到src2
  3. src2和src残差和 然后norm
  4. 经过MLP(这里面已经包括了残差和+norm)

再来看一下decoder的forward()

首先进行decoder的自注意力计算,这里用的还是普通的MSA,因为这一部分没有涉及到feature。

self.self_attn = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)

这里的q,k,tgt进行transpose也是因为默认batch_first=False的原因。

q = k = self.with_pos_embed(tgt, query_pos)
tgt2 = self.self_attn(q.transpose(0, 1), k.transpose(0, 1), tgt.transpose(0, 1))[0].transpose(0, 1)
tgt = tgt + self.dropout2(tgt2)
tgt = self.norm2(tgt)

然后再用自注意力的结果和encoder的输出做cross_attn,这里用的就是DeformAttn了。

tgt2 = self.cross_attn(self.with_pos_embed(tgt, query_pos),
                               reference_points,
                               src, src_spatial_shapes, level_start_index, src_padding_mask)
tgt = tgt + self.dropout1(tgt2)
tgt = self.norm1(tgt)

# ffn
tgt = self.forward_ffn(tgt)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/746622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM学习笔记(二)

学习黑马视频:01_什么是jvm_哔哩哔哩_bilibili 一、JVM内存结构 程序计数器 虚拟机栈 本地方法栈 堆 方法区 程序计数器、栈、本地方法栈,都是线程私有的。堆、方法区是线程共享的区域。 1. 虚拟机栈(JVM Stacks) 1&#xff09…

avue 自定义按钮修改后触发表单自带的校验方法;avue表单提交

代码&#xff1a; <avue-form :option"option" v-model"publishForm" ref"publishForm" submit"handleSubmit"><template slot-scope"{size}" slot"menuForm"><el-button :size"size" …

LayUi之选项卡的详解(附源码讲解)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于LayUi的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.选项卡是什么 二.选项卡在什么时候使用…

最新软件工程毕业设计选题推荐100例

文章目录 0 简介1 如何选题2 最新软件工程毕设选题3 最后 0 简介 学长搜集分享最新的软件工程业专业毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准&#xff1a; 相对容易工作量达标题目新颖 1 如何选题 最近非常多的…

IDEA debug 断点调试技巧

1、首先看下IDEA中Debug模式下的界面&#xff1a; ① 以Debug模式启动服务&#xff0c;左边的一个按钮则是以Run模式启动。在开发中&#xff0c;我一般会直接启动Debug模式&#xff0c;方便随时调试代码。 ② 断点&#xff1a;在左边行号栏单击左键&#xff0c;或者快捷键Ctrl…

Git教程-廖雪峰-个人归纳更新总结

文章目录 前言Git简介&#xff1a;Git的诞生&#xff1a;集中式和分布式 安装Git&#xff1a;创建版本库时光穿梭机(*)版本回退撤销修改删除文件 远程仓库&#xff1a;添加远程仓库&#xff1a;删除远程库与本地库的链接&#xff1a; 从远程库克隆(*) 使用GitHub 前言 日常需要…

微服务系列文章之 Redisson实现分布式锁

一、高效分布式锁 当我们在设计分布式锁的时候&#xff0c;我们应该考虑分布式锁至少要满足的一些条件&#xff0c;同时考虑如何高效的设计分布式锁&#xff0c;这里我认为以下几点是必须要考虑的。 1、互斥 在分布式高并发的条件下&#xff0c;我们最需要保证&#xff0c;同…

浅谈Python+requests+pytest接口自动化测试框架的搭建

框架的设计思路 首先要明确进行接口自动化需要的步骤&#xff0c;如下图所示&#xff1a; ​然后逐步拆解需要完成的工作&#xff1a; 1&#xff09;了解分析需求&#xff1a;了解接口要实现的功能 2&#xff09;数据准备&#xff1a;根据开发文档确定接口的基本情况&#x…

SuperMap iClient3D for Cesium最短路径分析

作者&#xff1a;Mei 目录 前言实现思路实现步骤1、构建二维网络数据集1.1拓扑检查1.2线拓扑数据集处理1.3构建二维网络数据集 2、发布网络分析服务3、实现代码 前言 在交通、消防业务场景中&#xff0c;如果某地发生火灾或者交通事故&#xff0c;需要快速规划出最短抢救路线&a…

河南元宇宙创造者大赛成果展示空间“元豫宙”斩获TopDigital创新营销奖金奖

6月29日&#xff0c;在上海举行的TopDigital营销盛典上&#xff0c;河南文旅元宇宙空间“元豫宙”&#xff0c;凭借其匠心独具的创新性场景设计、美轮美奂的超写实场景呈现、新潮炫酷的沉浸式虚拟体验&#xff0c;斩获TopDigital创新营销奖虚拟场景设计组金奖。元豫宙&#xff…

2023年31个最适合博主的WordPress主题

自从我最初开始写博客以来&#xff0c;在近十年的经验中&#xff0c;我已经出于各种目的在多个博客中测试和使用了数十种不同的 WordPress 主题。 以下是我挑选的绝对最佳WordPress主题&#xff0c;专门针对不想编写一行代码的博主。 无论您是想创建个人理财博客、撰写时尚、…

项目范围管理中8个常见错误,千万别大意!

管理项目范围就像驾驶汽车&#xff0c;如果不注视前方&#xff0c;汽车就会偏离道路。同样&#xff0c;如果不控制项目范围&#xff0c;项目就会偏离正轨。管理者在尝试管理项目范围时可能会遇到很多问题&#xff0c;下面来了解一下范围管理的常见错误&#xff0c;看看如何通过…

服务器数据库中了360后缀勒索病毒怎么办,如何预防勒索病毒攻击?

随着网络技术的不断发展&#xff0c;企业的计算机服务器也受到了网络安全威胁&#xff0c;近日&#xff0c;很多企业的服务器被360后缀勒索病毒攻击&#xff0c;导致企业的数据库中的许多重要数据被加密&#xff0c;无法正常读取打开。360后缀勒索病毒数据BeijingCrypt勒索病毒…

JVM 中的垃圾回收策略

文章目录 JVM 中的垃圾回收策略死亡对象的判断算法引用计数可达性分析 垃圾回收算法标记-清除算法复制算法标记-整理算法分代算法 JVM 中的垃圾回收策略 C 语言中&#xff0c;malloc 的内存必须 手动 free&#xff0c;否则容易出现内存泄漏&#xff08;光申请内存&#xff0c;…

Prometheus监控Tongweb容器

&#x1f3c5;概述 JMX Exporter主要是利用Java的JMX机制来读取JVM运行时的一些数据&#xff0c;然后转化为Prometheus可读取的metrics格式的数据。 JMX Exporter有两种用法&#xff1a; 启动独立进程。通过RMI读取JVM数据&#xff0c;但是单独进程监控也存在问题。JVM进程内启…

WSL 更新NVIDIA 驱动 安装CUDA

WSL 一定要使用WSL2&#xff0c;我选择的linux系统是ubuntu22.04&#xff0c;在微软应用商店安装的。 安装完成之后可以通过 wsl -l -v查看 NVIDIA 驱动 WSL 中不要直接安装linux版的显卡驱动&#xff0c;而是需要在windows中安装驱动。可以参考这篇文章NVIDIA官网文章 得保…

Spring Cache (基础知识+瑞吉外卖项目)

Spring Cache 基本介绍 Spring Cache是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能。 Spring Cache提供了一层抽象&#xff0c;底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓…

机器学习实践(2.1)LightGBM分类任务

前言 LightGBM也属于Boosting集成学习模型(还有前面文章的XGBoost)&#xff0c;LightGBM和XGBoost同为机器学习的集大成者。相比越来越流行的深度神经网络&#xff0c;LightGBM和XGBoost能更好的处理表格数据&#xff0c;并具有更强的可解释性&#xff0c;还具有易于调参、输入…

Java 多线程编程在 JMeter 中应用

目录 前言&#xff1a; 首先新建一个简单的线程组和一个简单的请求&#xff1a; 添加 JSR223 预处理程序&#xff08;后置处理程序需要下一次次请求&#xff09; 可以通过复制私有变量来控制所有线程获取公共变量时的线程安全问题。 日志输出&#xff1a; 前言&#xff1a…

Excel VLOOKUP使用详解

VLOOKUP语法格式&#xff1a; VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) VLOOKUP&#xff08;要查找的值&#xff0c;查找区域&#xff0c;要返回的结果在查找区域的第几列&#xff0c;精确匹配或近似匹配&#xff09; 一、精确查找 根据姓名查找对应…