【Paper Note】ViViT: A Video Vision Transformer

news2025/1/17 3:09:03

ViViT: A Video Vision Transformer

  • Abstract
  • Overview of vision transformer 回顾ViT
  • Embedding video clips 视频编码方式
    • Uniform frame sampling 均匀采样
    • Tubelet embedding 时空管采样
    • 初始化
      • 3D卷积代码介绍
      • 视频编码输入到模型当中
  • Transformer Models for Video
    • Spatio-temporal attention 空间-时间注意力
      • Factorised encoder
      • Factorised self-attention
      • Factorised dot-product attention
  • 消融实验

Abstract

文章主要transformer在包含时序信息维度的视频格式上的问题展开:

  • 视频格式数据生成的token序列数量过多,带来繁重的计算冗余。
  • 训练Transfomer结构模型需要引入大规模的数据集,训练对数据条件十分苛刻。

为了高效处理视频数据中生成的大规模时空tokens
①文章提出并探讨了几种对空间和时间维度进行分解的方法,进而提出了相应的网络结构,从而增加模型对视频数据特征提取的效率和可扩展性。
②其次,规范了模型的训练(主要针对模型的训练策略)。目的在小数据集上也能使得Transformer类模型能有很好的效果

Overview of vision transformer 回顾ViT

在这里插入图片描述
基础的ViT模型主要有三个模块组成

  • Linear Project of Flattened Patches即为Embedding层,对输入的三通道图像数据利用conv卷积层进行分块并完成对应的线性映射,如上式当中的E,而后通过torch.view()进行展平压缩维度。拼接上类别token后采用矩阵相加方式引入位置编码。
  • Transformer Encoder模块,对Embedding层输出的token进行多头注意力计算和多层感知机(中间包含Layer Norm)。其中MSA是整个模型的核心部分。
  • MLP Head层,堆叠的Transformer Block最终的输出经过Head结构提取出类别token所对应的结果信息,文中通过两个线形层叠加中间插入一个tanh激活函数来实现。

Embedding video clips 视频编码方式

一个视频V有4个维度,T * H * W * C。 变成一个序列token就是 Nt * Nh * Nw * d。加上位置编码, 变成transformer的输入 N * d。

区别于常规的二维图像数据,视频数据相当于需在三维空间内进行采样(拓展了一个时间维度)。而文章中所提出的两钟视频嵌入方法目的都是将视频数据   V   ∈ R T × H × W × C \mathrm{~V~}\in\mathbb{R}^{\mathrm{T}\times\mathrm{H}\times\mathrm{W}\times\mathrm{C}}  V RT×H×W×C映射到token当中得到 z ~ ∈ R n t × n h × n w × d \tilde{\mathrm{z}}\in\mathbb{R}^{\mathrm{n_t}\times\mathrm{n_h}\times\mathrm{n}_{\mathrm{w}}\times\mathrm{d}} z~Rnt×nh×nw×d,而后添加位置编码并对token进行reshape得到最终Transformer的输入 z ∈ R N × d \mathrm{z}\in\mathbb{R}^{\mathrm{N}\times\mathrm{d}} zRN×d

Uniform frame sampling 均匀采样

就是先提取帧,然后每一帧按照ViT的方法提取token,然后把不同帧的token拼接起来作为输入
在这里插入图片描述

采用相同的采样帧率,从视频数据当中均匀采样 n t n_t nt 帧,使用相同的embedding方法独立地处理每一个帧当中的patch,而后将得到的所有token拼接concat在一起。具体而言,从每个采样获得的帧当中划分
个不重叠的图像块patch,则共产生 n w × n t n_w\times{n_t} nw×nt个不重叠的图像块patch,则共产生 n t × n w × n t n_t\times{n_w}\times{n_t} nt×nw×nt 个tokens输入Transformer当中。

然而这种切片方法对于长时间序列的数据来说生成的token长度极大,并且不同帧间首位相连的patch在位置编码上与真实情况不一致。

Tubelet embedding 时空管采样

前一种方法是提取2D图像特征,这种方法是提取立方体,假设每个tublet的shape是t, w, h,那就是说没t帧提取一次特征,取每一帧相同位置的w, hpatch组成输入
在这里插入图片描述
从输入volume(体积)当中提取时空上不重叠的“tubes”,这种方法是将vit嵌入到3D的拓展,embedding层就对应的选取三维卷积。则对于维度为 t × h × w t×h×w t×h×w的tube管来说, n t = [ T t ] , n h = [ H h ] , n w = [ W w ] \mathrm{n_{t}}=[\frac{T}{t}],n_{\mathrm{h}}=[\frac{H}{\mathrm{h}}],n_{\mathrm{w}}=[\frac{W}{\mathrm{w}}] nt=[tT],nh=[hH],nw=[wW]这种采样方法直接在采样的过程当中就融合了时空信息。

提取不重叠,空间-时间的tubes(立方体)。这个tublelt的维度就是: t * h * w。token就包含了时间、宽、高

所有的模型都是32帧输入的。

看了下vivit_base_k400的config, 模型名:ViViT- B/16*2。其实16 * 16还是ViT一样的方法。

 config.dataset_configs.num_frames = 32  # 采取32帧
 config.dataset_configs.stride = 2  #2帧为1个
 config.dataset_configs.crop_size = 224 # 大小224

config.model.temporal_encoding_config.method = '3d_conv'
config.model.patches.size = [16, 16, 2]   # H,W是 16* 16的

初始化

模型是以Vit为基础进行训练的,所以初始化需要进行特殊处理

Position emb: 复制t份出来,来适应多帧的处理
Embedding emb:
2d的输入没什么好说的
对于3d的输入,提供了两种不同的方式

在这里插入图片描述
下面的公式可以实现在初始的情况下,等价于只用的1帧的情况,参数由模型自己去学习

在这里插入图片描述

3D卷积代码介绍

首先2D的卷机是一个平面的卷机(H * W), 就是一个H * W的平面 和一个 H * W的卷机核,对应点相乘,输出一个值。

那么3D的卷机就是一个立方体(H * W * D), 就是一个立方体和一个立方体的卷机核相乘,输出一个值。

用pytorch的官方的Conv3D来看, 这个卷机核就是一个3D的立方体 3 * 5 * 2

输入是 (Batch, Channel, Depth, Height, Width) -> (20, 16, 10, 50, 100)

m = nn.Conv3d(16, 33, (3, 5, 2), stride=(2, 1, 1), padding=(4, 2, 0))
input = torch.randn(20, 16, 10, 50, 100)
output = m(input)
output.shape # torch.Size([20, 33, 8, 50, 99]

更改为paper中的输入:

输入是一个batch:16, 3 * 224 * 224的图片, 一共有32帧,

使用kenel,2 * 16 * 16, 理解为2帧变1帧, 图像上 16 * 16的不重叠区域

m = nn.Conv3d(3, 1, (2, 16, 16), stride=(2, 16, 16))
input = torch.randn(16, 3, 32, 224, 224)
output = m(input)
output.shape # torch.Size([16, 1, 16, 14, 14])

视频编码输入到模型当中

输入视频,均匀采样, 知道采样的帧数(n_sampled_frames), 去算间隔, 采样,输出。

def sample_frames_uniformly(x: jnp.ndarray,
                            n_sampled_frames: int) -> jnp.ndarray:
  """Sample frames from the input video."""
  if x.ndim != 5:
    raise ValueError('Input shape should be [bs, t, h, w, c].')
  num_frames = x.shape[1]
  if n_sampled_frames < num_frames:
    t_start_idx = num_frames / (n_sampled_frames + 1)
    t_step = t_start_idx
  else:
    t_start_idx = 0
    t_step = 1
  t_end_idx = num_frames
  temporal_indices = jnp.arange(t_start_idx, t_end_idx, t_step)
  temporal_indices = jnp.round(temporal_indices).astype(jnp.int32)
  temporal_indices = jnp.minimum(temporal_indices, num_frames - 1)
  return x[:, temporal_indices]  # [n, t_s, in_h, in_w, c]

编码后,从batch, time,h, w, c -> batch, thw, c

def temporal_encode(x,
                    temporal_encoding_config,
                    patches,
                    hidden_size,
                    return_1d=True,
                    name='embedding'):
  """Encode video for feeding into ViT."""

  n, _, in_h, in_w, c = x.shape

  if temporal_encoding_config.method == 'temporal_sampling':
    n_sampled_frames = temporal_encoding_config.n_sampled_frames
    x = video_utils.sample_frames_uniformly(x, n_sampled_frames)
    t_s = x.shape[1]
    x = jnp.reshape(x, [n, t_s * in_h, in_w, c])

    x = embed_2d_patch(x, patches, hidden_size)
    temporal_dims = t_s
    if return_1d:
      n, th, w, c = x.shape
      x = jnp.reshape(x, [n, th * w, c])
    else:
      n, th, w, c = x.shape
      x = jnp.reshape(x, [n, t_s, -1, w, c])

  elif temporal_encoding_config.method == '3d_conv':
    kernel_init_method = temporal_encoding_config.get('kernel_init_method',
                                                      None)
    x = embed_3d_patch(x, patches, hidden_size, kernel_init_method, name)
    temporal_dims = x.shape[1]
    if return_1d:
      n, t, h, w, c = x.shape
      x = jnp.reshape(x, [n, t * h * w, c])

  else:
    raise AssertionError('Unknown temporal encoding method.')

  assert x.size > 0, ('Found zero tokens after temporal encoding. '
                      'Perhaps one of the patch sizes is such that '
                      'floor(dim_size / patch_size) = 0?')

  return x, temporal_dims

Transformer Models for Video

在这里插入图片描述

Spatio-temporal attention 空间-时间注意力

伴随着采样的输入帧数增加,token的数量也会线性增加。运算量会平方倍的增加,所以需要更加有效的结构。

这种模型简单地将所有的tokens(包括时空)简单地通过Transformer encoder层,导致问题就是引入指数增长的计算量,每个Transformer层对所有时空token均进行成对交互,这种方式极其低效,具体模型构成方式与另一篇文章:Video Transformer Net 所提出的结构类似,如图4所示
在这里插入图片描述

Factorised encoder

使用两个 transformer

  • 第一个是 spatial transformer,输入是某一帧的多个token,输出一个token
  • 第二个是temporal transformer,输入是前一步多帧的token每帧对应一个token),输出结果就通过mlp进行分类

模型是2个单独的transformer encoder组成的:

在这里插入图片描述

  • 空间编码器,通过对同一时间索引的token建模。输出cls_token。
  • 这个帧维度的表征,连接在一起,输入时间编码器中。这个输出就是最后的结果。

Factorised encoder方法:构建两个单独的transformer encoder,分别针对空间和时间处理。首先利用空间编码器(Space Transformer),通过对同一时间索引的token建模。输出cls_token。而后将**输出的类别token和帧维度的表征token拼接输入到时间编码器(Time Transformer)**中得到最终的结果,模型结构如图5所示(相当于两个Transformer模型的叠加),实现代码如下:

class ViViT(nn.Module):
    def __init__(self, image_size, patch_size, num_classes, num_frames, dim = 192, depth = 4, heads = 3, pool = 'cls', in_channels = 3, dim_head = 64, dropout = 0.,
                 emb_dropout = 0., scale_dim = 4, ):
        super().__init__()
        
        assert pool in {'cls', 'mean'}, 'pool type must be either cls (cls token) or mean (mean pooling)
        assert image_size % patch_size == 0, 'Image dimensions must be divisible by the patch size.'
        num_patches = (image_size // patch_size) ** 2
        patch_dim = in_channels * patch_size ** 2
        self.to_patch_embedding = nn.Sequential(
            Rearrange('b t c (h p1) (w p2) -> b t (h w) (p1 p2 c)', p1 = patch_size, p2 = patch_size),
            nn.Linear(patch_dim, dim),
        )

        self.pos_embedding = nn.Parameter(torch.randn(1, num_frames, num_patches + 1, dim))
        self.space_token = nn.Parameter(torch.randn(1, 1, dim))
        self.space_transformer = Transformer(dim, depth, heads, dim_head, dim*scale_dim, dropout)

        self.temporal_token = nn.Parameter(torch.randn(1, 1, dim))
        self.temporal_transformer = Transformer(dim, depth, heads, dim_head, dim*scale_dim, dropout)

        self.dropout = nn.Dropout(emb_dropout)
        self.pool = pool

        self.mlp_head = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, num_classes)
        )

    def forward(self, x):
        x = self.to_patch_embedding(x)
        b, t, n, _ = x.shape

        cls_space_tokens = repeat(self.space_token, '() n d -> b t n d', b = b, t=t)
        x = torch.cat((cls_space_tokens, x), dim=2)
        x += self.pos_embedding[:, :, :(n + 1)]
        x = self.dropout(x)

        x = rearrange(x, 'b t n d -> (b t) n d')
        x = self.space_transformer(x)
        x = rearrange(x[:, 0], '(b t) ... -> b t ...', b=b)

        cls_temporal_tokens = repeat(self.temporal_token, '() n d -> b n d', b=b)
        x = torch.cat((cls_temporal_tokens, x), dim=1)

        x = self.temporal_transformer(x)
        

        x = x.mean(dim = 1) if self.pool == 'mean' else x[:, 0]

        return self.mlp_head(x)

Factorised self-attention

通过 self-attention 层将时空数据分开处理

  • 空间层只在同一帧内不同token间进行attention操作
  • 时间层对不同帧同一位置的token进行attention操作
  • 先计算空间自注意力(token中有相同的时间索引),再计算时间的自注意力(token中有相同的空间索引),其实先后顺序无所谓,只要串行就行
    在这里插入图片描述

相较于Model 1,这个模型包含相同数量的Transformer层。而此模型思路不是在第 l l l 层计算所有成对的token z l z ^l zl 的多头自注意力,而是将自注意力计算在空间和时间上分解,首先只计算空间上的自注意力对于相同时间索引的token),而后再进行时间上的计算对于相同的空间索引)。在降低计算复杂度的同时在每个Transformer层均完成了时空层面的自注意力交互。其结构如图6所示。

自注意力计算:
在这里插入图片描述
先时间后空间,或者先空间后时间没有区别

在这里插入图片描述

def _reshape_to_time_space(x, temporal_dims):
  if x.ndim == 3:
    b, thw, d = x.shape
    assert thw % temporal_dims == 0
    hw = thw // temporal_dims
    x = jnp.reshape(x, [b, temporal_dims, hw, d])
  assert x.ndim == 4
  return x、

reshape_to_2d_factorized, 就是将batch, h * w, channel -> batch, w, h, channel

def reshape_to_2d_factorized(x: jnp.ndarray, axis: int,
                             two_d_shape: Tuple[int, int, int, int]):
  """Converts 1d inputs back to 2d after axial attention."""
  assert x.ndim == 3, ('The input dimention should be '
                       '[batch_size, height*width, channel]')
  batch_size, height, width, channel = two_d_shape
  if axis == 1:
    assert x.shape[0] == batch_size * width
    return x.reshape((batch_size, width, height, channel)).transpose(
        (0, 2, 1, 3))
  elif axis == 2:
    assert x.shape[0] == batch_size * height
    return x.reshape(two_d_shape

def reshape_to_2d_factorized(x: jnp.ndarray, axis: int,
                             two_d_shape: Tuple[int, int, int, int]):
  """Converts 1d inputs back to 2d after axial attention."""
  assert x.ndim == 3, ('The input dimention should be '
                       '[batch_size, height*width, channel]')
  batch_size, height, width, channel = two_d_shape
  if axis == 1:
    assert x.shape[0] == batch_size * width
    return x.reshape((batch_size, width, height, channel)).transpose(
        (0, 2, 1, 3))
  elif axis == 2:
    assert x.shape[0] == batch_size * height
    return x.reshape(two_d_shape)

在不同的维度上做注意力,来实现时间和空间。

其实也是一样的,LN + atttion + 残差连

def _run_attention_on_axis(inputs, axis, two_d_shape):
      """Reshapes the input and run attention on the given axis."""
      inputs = model_utils.reshape_to_1d_factorized(inputs, axis=axis)
      x = nn.LayerNorm(
          dtype=self.dtype, name='LayerNorm_{}'.format(_AXIS_TO_NAME[axis]))(
              inputs)
      x = self_attention(
          name='MultiHeadDotProductAttention_{}'.format(_AXIS_TO_NAME[axis]))(
              x, deterministic=deterministic)
      x = nn.Dropout(rate=self.dropout_rate)(x, deterministic)
      x = x + inputs
      return model_utils.reshape_to_2d_factorized(
          x, axis=axis, two_d_shape=two_d_shape)

整个这个因式分解的注意力模块,就是在不同的轴上,做自注意力。

可以使用先时间后空间,attention_axes = (1, 2)。或者先空间后时间,attention_axes= (2, 1)。

所以整个就是: 时间attn + 空间attn + LN + MLP

Factorised dot-product attention

时间、空间heads是并行的,而不是串行的。
spatial还是同一帧内不同token,temporal是不同帧同一位置的token

在这里插入图片描述

第四种模型的思想则是通过利用dot-product点积注意力操作来取代上述的因式分解factorisation操作,通过注意力计算的方式来代替简单的张量reshape。思想是对于空间注意力和时间注意力分别构建对应的键、值。具体思路如图所示。

在这里插入图片描述

class FDATransformerEncoder(nn.Module):
    """Factorized Dot-product Attention Transformer Encoder"""

    def __init__(self, dim, depth, heads, dim_head, mlp_dim, nt, nh, nw, dropout=0.):
        super().__init__()
        self.layers = nn.ModuleList([])
        self.nt = nt
        self.nh = nh
        self.nw = nw

        for _ in range(depth):
            self.layers.append(
                PreNorm(dim, FDAttention(dim, nt, nh, nw, heads=heads, dim_head=dim_head, dropout=dropout)))

    def forward(self, x):
        for attn in self.layers:
            x = attn(x) + x

        return x


class FDAttention(nn.Module):
    """Factorized Dot-product Attention"""

    def __init__(self, dim, nt, nh, nw, heads=8, dim_head=64, dropout=0.):
        super().__init__()
        inner_dim = dim_head * heads
        project_out = not (heads == 1 and dim_head == dim)

        self.nt = nt
        self.nh = nh
        self.nw = nw

        self.heads = heads
        self.scale = dim_head ** -0.5

        self.attend = nn.Softmax(dim=-1)
        self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)

        self.to_out = nn.Sequential(
            nn.Linear(inner_dim, dim),
            nn.Dropout(dropout)
        ) if project_out else nn.Identity()

    def forward(self, x):
        b, n, d, h = *x.shape, self.heads

        qkv = self.to_qkv(x).chunk(3, dim=-1)
        q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h=h), qkv)
        qs, qt = q.chunk(2, dim=1)
        ks, kt = k.chunk(2, dim=1)
        vs, vt = v.chunk(2, dim=1)

        # Attention over spatial dimension
        qs = qs.view(b, h // 2, self.nt, self.nh * self.nw, -1)
        ks, vs = ks.view(b, h // 2, self.nt, self.nh * self.nw, -1), vs.view(b, h // 2, self.nt, self.nh * self.nw, -1)
        spatial_dots = einsum('b h t i d, b h t j d -> b h t i j', qs, ks) * self.scale
        sp_attn = self.attend(spatial_dots)
        spatial_out = einsum('b h t i j, b h t j d -> b h t i d', sp_attn, vs)

        # Attention over temporal dimension
        qt = qt.view(b, h // 2, self.nh * self.nw, self.nt, -1)
        kt, vt = kt.view(b, h // 2, self.nh * self.nw, self.nt, -1), vt.view(b, h // 2, self.nh * self.nw, self.nt, -1)
        temporal_dots = einsum('b h s i d, b h s j d -> b h s i j', qt, kt) * self.scale
        temporal_attn = self.attend(temporal_dots)
        temporal_out = einsum('b h s i j, b h s j d -> b h s i d', temporal_attn, vt)

消融实验

比较不同的token获取方式
比较了不同变种的transformer
比较了多种数据增强方式
比较了不同输入数据尺寸
比较了几类变种
比较了不同的输入帧数

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

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

相关文章

安全测试(linux基线排查)看这一篇就够了

前言部分&#xff1a; 作为一个安全测试人员&#xff0c;在确保WEB应用程序没有漏洞外&#xff0c;应该也需要关注一下主机环境的安全&#xff0c;因为应用程序部署在主机环境提供运行环境&#xff0c;也应当关注一下主机环境的安全。于此&#xff0c;通过学习本次对linux安全加…

香橙派pi5下,debian,docker19.03.9版本runc容器逃逸

在香橙派pi5下,debian,docker19.03.9版本下,安装系统后,启动docker,显示一切正常。 当加入k8s集群以后,可以正常连接到集群,node状态显示为ready。看起来一切正常。不过过一会之后,香橙派节点内存飙升,然后挂掉。重连失败,需要重启后才能重连。且swapoff -a命令执行…

C++之深入解析C++20协程的原理和应用

一、无栈协程成为 C20 协程标准 协程分为无栈协程和有栈协程两种&#xff0c;无栈指可挂起/恢复的函数&#xff0c;有栈协程则相当于用户态线程。有栈协程切换的成本是用户态线程切换的成本&#xff0c;而无栈协程切换的成本则相当于函数调用的成本&#xff1b;无栈协程和线程…

个人写校园点评项目的笔记

目录 ​编辑 1.解决短信登陆--2023.4.14 redis 数据类型 阿里云短信服务 存入redis的key和value 流程 dto的意义 给token设置有效期 拦截器的类没有交给Spring Constants 2.商户查询缓存&#xff08;不采用SpringCache&#xff0c;而是尝试原理实现&#xff09; 20…

Spring Cloud Alibab --Seata

事务特性 A&#xff08;Atomic&#xff09;&#xff1a;原子性&#xff0c;构成事务的所有操作&#xff0c;要么都执行完成&#xff0c;要么全部不执行&#xff0c;不可能出现部分成功部分失败的情况。 C&#xff08;Consistency&#xff09;&#xff1a;一致性&#xff0c;在事…

Tarjan算法求割点和桥

先进行一些定义&#xff0c;假设目前有一个无向连通图 割点&#xff1a;某点及其边去掉后&#xff0c;图不再连通 桥&#xff1a;某条边去掉后&#xff0c;图不再联通 tarjan算法求割点 不考虑子结点到父结点的情况 dfn(x) x实际杯访问的时间点 low(x) x通过图可回溯到的最…

22从零开始学Java之你知道return、break与continue的区别吗?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家介绍了while、do-while两种循环结构&#xff0c;并且给大家总结…

KubeSphere 社区双周报 | OpenFunction 支持 Dapr 状态管理 | 2023.03.31-04.13

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2023.03.31-2023.…

测试工具之JMH详解

文章目录 1 JMH1.1 引言1.2 简介1.3 DEMO演示1.3.1 测试项目构建1.3.2 编写性能测试1.3.3 执行测试1.3.4 报告结果 1.4 注解介绍1.4.1 BenchmarkMode1.4.2 Warmup1.4.3 Measurement1.4.4 Threads1.4.5 Fork1.4.6 OutputTimeUnit1.4.7 Benchmark1.4.8 Param1.4.9 Setup1.4.10 Te…

大数据实战 --- 美团外卖平台

目录 开发环境 数据描述 功能需求 数据准备 数据分析 RDD操作 Spark SQL操作 创建Hbase数据表 创建外部表 统计查询 开发环境 HadoopHiveSparkHBase 启动Hadoop&#xff1a;start-all.sh 启动zookeeper&#xff1a;zkServer.sh start 启动Hive&#xff1a; nohup …

Netty中的HttpServerCodec和HttpObjectAggregator

首先使用Netty搭建一个HttpServer&#xff0c;代码如下&#xff1a; public class App {public static boolean useEpoll false;static {String os System.getProperty("os.name");if (Objects.nonNull(os) && os.equalsIgnoreCase("linux") &a…

Git分支篇git branch和git checkout

分支作用 在开发过程中&#xff0c;项目往往由多人协同开发&#xff0c;那么将多人编写的代码汇总到一起就成了一个困难且复杂的工作&#xff0c;另外项目也需要备份和版本迭代&#xff0c;因此不能只有一个版本。因此分支就成为了优秀的解决方案。 分支相互独立&#xff0c;…

C++STL详解(九)--使用红黑树封装实现set和map

文章目录 控制底层红黑树模板参数模板参数中的仿函数map,set中的正向迭代器map,set中的反向迭代器[]下标访问运算符重载map的模拟实现代码map的模拟实现适用map,set容器的底层红黑树代码(修改版本) 控制底层红黑树模板参数 如果我们用一棵KV模型的红黑树同时实现map和set,我们…

【数据结构】八大排序算法(梭哈)

目录 1.直接插入排序2. * 希尔排序关于希尔排序的时间复杂度 3.选择排序4. * 堆排序5.冒泡排序6. * 快速排序6.1递归快排6.1.1 hoare版6.1.2 挖坑法6.1.3 前后指针法6.1.4 关于每个区间操作的结束位置总是小于key6.1.5 关于有序原数据的效率优化 6.2 非递归快排 7. * 归并排序7…

计算机网络考试复习——第五章

本章考察范围为5.1 5.3 5.4这三部分。 该层传输的单位是报文段 5.1 运输层协议概述&#xff1a; 5.1.1 进程之间的通信&#xff1a; 运输层是向它上面的应用层提供通信服务。它属于面向通信部分的最高层&#xff0c;同时也是用户功能的最低层。 屏蔽作用&#xff1a;运输层…

flutter系列之:如何自定义动画路由

文章目录 简介自定义跳转使用flutter动画基础实现一个自定义的route总结 简介 flutter中有默认的Route组件&#xff0c;叫做MaterialPageRoute&#xff0c;一般情况下我们在flutter中进行跳转的话&#xff0c;只需要向Navigator中传入一个MaterialPageRoute就可以了。 但是Ma…

让你的three.js动起来

让你的three.js动起来 简介 本节主要是给实例添加动画效果&#xff0c;以及加了一些小插件用以实现帧率检测、gui可视化配置、动态监听屏幕大小变化刷新和鼠标操控功能。 引入的插件js&#xff1a; three.jsdat.gui.jsStats.jsTrackballControls.js 实际效果&#xff1a; …

Java 线程状态有哪些?

文章目录 Java 线程状态有哪些&#xff1f;初始状态&#xff08;NEW&#xff09;可运行状态&#xff08;RUNNABLE&#xff09;就绪状态运行状态 阻塞状态&#xff08;BLOCKED&#xff09;等待状态&#xff08;WAITING&#xff09;超时等待&#xff08;TIMED_WAITING&#xff09…

一次打靶场记录

题目提示 1、目录爆破 在对靶场进行信息收集、目录扫描之后发现结果存在www.zip,data.zp 两个备份文件 下载回来解压之后发现www.zip是网站备份文件&#xff0c;data.zip是数据库文件&#xff0c;存在一个maccms的数据库 苹果cms的数据库&#xff0c;导入本地数据库。 admin…

【并发编程Python】为什么Python这么慢

Python速度慢的所有原因 Python作为一门非常优秀的计算机语言&#xff0c;其速度慢给Python减了很多分&#xff0c;也是其一直被诟病的主要原因之一&#xff0c;通常情况下&#xff0c;Python比Java/C慢约5-10倍&#xff0c;在一些特殊的情况下Python甚至比C慢100~200倍&#x…