PVT(Pyramid Vision Transformer)学习记录

news2025/1/19 11:21:47

引言与启发

自从ViT之后,关于vision transformer的研究呈井喷式爆发,从思路上分主要沿着两大个方向,一是提升ViT在图像分类的效果;二就是将ViT应用在其它图像任务中,比如分割和检测任务上,这里介绍的PVT(Pyramid Vision Transformer) 就属于后者。PVT相比ViT引入了和CNN类似的金字塔结构,使得PVT像CNN那样作为backbone应用在dense prediction任务(分割和检测等)。

在这里插入图片描述

设计思路

PVT的设计思路是目前在CNN中特征要想获取多尺度特征可以通过FPN(特征金字塔网络),那么Transformer是否也可以这样做呢,由此提出了PVT(Pyramid Vision Transformer)这个模块可以很轻松的与DETR类模型相结合从而实现端到端检测。
其实PVT的思想很简单,就是将Transformer与FPN相结合,通过卷积来将特征图变小,进而减少计算。
在这里插入图片描述
结合上图,我们可以看到其主要创新点为:

  1. 相较于ViT,其使用了细粒度的图像patch,可以学习高分辨率的特征表示
  2. 借鉴于CNN,设计了金字塔结构,可以进行多尺度特征的学习
  3. 引入了SRA模块,主要用于改进多头注意力模块来减少QKV的计算量

模型流程

模型的初始化参数,在后面会对其进行讲解

def pvt_small():
    model = PyramidVisionTransformer(
        patch_size=4, embed_dims=[64, 128, 320, 512], num_heads=[1, 2, 5, 8], mlp_ratios=[8, 8, 4, 4], qkv_bias=True,
        norm_layer=partial(nn.LayerNorm, eps=1e-6), depths=[3, 4, 6, 3], sr_ratios=[8, 4, 2, 1])

首先我们的输入图片为torch.Size([2, 3, 224, 224]),即batch-size=2,channel=3,W=H=224
随后送入stage1:

        x, (H, W) = self.patch_embed1(x)
        pos_embed1 = self._get_pos_embed(self.pos_embed1, self.patch_embed1, H, W)
        x = x + pos_embed1
        x = self.pos_drop1(x)
        for blk in self.block1:
            x = blk(x, H, W)
        x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
        outs.append(x)

Patch操作

首先对输入图片进行patch操作,其完成对图片的切分以及线性映射(维度转换)
stage1时的patchEmbed定义为:

PatchEmbed(
  (proj): Conv2d(3, 64, kernel_size=(4, 4), stride=(4, 4))
  (norm): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
)
  def forward(self, x):
        B, C, H, W = x.shape
        x = self.proj(x).flatten(2).transpose(1, 2)
        x = self.norm(x)
        H, W = H // self.patch_size[0], W // self.patch_size[1]
        return x, (H, W)

可以看到,送入patch_embed模块的向量首先获取其各个参数(B,C,W,H)
随后调用self.proj操作

self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)

其为一个二维卷积:Conv2d(3, 64, kernel_size=(4, 4), stride=(4, 4))

卷积层输出尺寸: o = ⌊(i + 2p - k) / s⌋ + 1

padding默认为0,则输出大小为【2,64,56,56】
随后经过flatten展平操作得到【2,64,56X56】
随后transpose进行维度转换为:【2,56X56,64】即torch.Size([2, 3136, 64])
随后便是归一化操作以及对W,H大小改变并返还,此时W=H=56

位置编码

位置编码方面其使用的是可学习位置编码方式。
pos_embed是由下列方式初始化得到的,此时大小为:torch.Size([1, 3136, 64])

self.pos_embed1 = nn.Parameter(torch.zeros(1, self.patch_embed1.num_patches, embed_dims[0]))

随后进行处理:

pos_embed1 = self._get_pos_embed(self.pos_embed1, self.patch_embed1, H, W)

处理过程为:

def _get_pos_embed(self, pos_embed, patch_embed, H, W):
    if H * W == self.patch_embed1.num_patches:
        return pos_embed
    else:
        return F.interpolate(
            pos_embed.reshape(1, patch_embed.H, patch_embed.W, -1).permute(0, 3, 1, 2),
            size=(H, W), mode="bilinear").reshape(1, -1, H * W).permute(0, 2, 1)

紧接着将处理的语义特征信息与位置编码信息直接相加,注意:
此时的pos_embed1为:torch.Size([1, 3136, 64]) x 为 torch.Size([2, 3136, 64]),也是可以相加的

x = x + pos_embed1

如下测试,利用广播机制扩充维度进行计算

import torch
data = torch.randn((2, 1, 2, 2))
data1 = torch.randn((1, 1, 2, 2))
print(data)
print(data1)
print(data+data1)

在这里插入图片描述

注意力计算

进入注意力计算模块,每个stage中的注意力层有多个

for blk in self.block1:
    x = blk(x, H, W)

随后开始注意力的计算:
Q构造:经过一个linear进行构造,并进行分头计算(多头注意力)

self.q = nn.Linear(dim, dim, bias=qkv_bias)
 q = self.q(x).reshape(B, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)

得到q为:torch.Size([2, 1, 3136, 64])
随后进入到其创新的模块了,其是对K,V进行下采样,减小其数量:self.sr_ratio是缩小规模
x为torch.Size([2, 3136, 64]),首先经过permute进行维度变换为torch.Size([2, 64,3136]),随后经过reshape为:torch.Size([2, 64, 56, 56])

x_ = x.permute(0, 2, 1).reshape(B, C, H, W)

然后通过卷积操作进行降维:

self.sr = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)
x_ = self.sr(x_).reshape(B, C, -1).permute(0, 2, 1)

self.sr为:Conv2d(64, 64, kernel_size=(8, 8), stride=(8, 8))
通过 卷积层输出尺寸: o = ⌊(i + 2p - k) / s⌋ + 1
可得输出尺寸为:7即49 对应的tensor为:torch.Size([2, 49, 64])

然后便是使用x_来获取kv

self.kv = nn.Linear(dim, dim * 2, bias=qkv_bias)
kv = self.kv(x_).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)

得到kv为torch.Size([2, 2, 1, 49, 64]),k,v皆为torch.Size([2, 1, 49, 64]),但数值上不同
完整代码如下:

  if self.sr_ratio > 1:
        x_ = x.permute(0, 2, 1).reshape(B, C, H, W)
        x_ = self.sr(x_).reshape(B, C, -1).permute(0, 2, 1)
        x_ = self.norm(x_)
        kv = self.kv(x_).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
    else:
        kv = self.kv(x).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)

然后便是进行一系列计算了:

x = (attn @ v).transpose(1, 2).reshape(B, N, C)
@实际为x@y=x.matmul(y)

最终计算得到注意力机制的结果x结果仍为torch.Size([2, 3136, 64])

注意力机制模块完整代码如下:

def forward(self, x, H, W):
    B, N, C = x.shape
    q = self.q(x).reshape(B, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
    if self.sr_ratio > 1:
        x_ = x.permute(0, 2, 1).reshape(B, C, H, W)
        x_ = self.sr(x_).reshape(B, C, -1).permute(0, 2, 1)
        x_ = self.norm(x_)
        kv = self.kv(x_).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
    else:
        kv = self.kv(x).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
    k, v = kv[0], kv[1]
    attn = (q @ k.transpose(-2, -1)) * self.scale
    attn = attn.softmax(dim=-1)
    attn = self.attn_drop(attn)

    x = (attn @ v).transpose(1, 2).reshape(B, N, C)
    x = self.proj(x)
    x = self.proj_drop(x)

    return x

最终得到x经过reshape恢复为2维结果,再次进入下一个stage进行计算

x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()

随后经过卷积缩小特征图大小,在空间上的损失由维度进行补偿,下一个阶段x为:

torch.Size([2, 784, 128])

通过依次该过程,便将其大小进行缩减,通过也将其获取了多尺度信息。值得注意的是,只有stage1上patch=4,在后面的三个stage上patch都为2,这样也就参考卷积,其是一个二倍大小的关系。

总体来看,Pyramid Version Transformer的设计还是较为容易理解的,下面博主从代码入手,来详细讲解PVT的构建过程。

完整代码如下:

import torch
import torch.nn as nn
import torch.nn.functional as F
from functools import partial

from timm.models.layers import DropPath, to_2tuple, trunc_normal_
from timm.models.registry import register_model
from timm.models.vision_transformer import _cfg


class Mlp(nn.Module):
    def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer()
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)

    def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
        return x


class Attention(nn.Module):
    def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0., sr_ratio=1):
        super().__init__()
        assert dim % num_heads == 0, f"dim {dim} should be divided by num_heads {num_heads}."

        self.dim = dim
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk_scale or head_dim ** -0.5

        self.q = nn.Linear(dim, dim, bias=qkv_bias)
        self.kv = nn.Linear(dim, dim * 2, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop)

        self.sr_ratio = sr_ratio
        if sr_ratio > 1:
            self.sr = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)
            self.norm = nn.LayerNorm(dim)

    def forward(self, x, H, W):
        B, N, C = x.shape
        q = self.q(x).reshape(B, N, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)

        if self.sr_ratio > 1:
            x_ = x.permute(0, 2, 1).reshape(B, C, H, W)
            x_ = self.sr(x_).reshape(B, C, -1).permute(0, 2, 1)
            x_ = self.norm(x_)
            kv = self.kv(x_).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        else:
            kv = self.kv(x).reshape(B, -1, 2, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        k, v = kv[0], kv[1]

        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        x = (attn @ v).transpose(1, 2).reshape(B, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)

        return x


class Block(nn.Module):

    def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
                 drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm, sr_ratio=1):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = Attention(
            dim,
            num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
            attn_drop=attn_drop, proj_drop=drop, sr_ratio=sr_ratio)
        # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)

    def forward(self, x, H, W):
        x = x + self.drop_path(self.attn(self.norm1(x), H, W))
        x = x + self.drop_path(self.mlp(self.norm2(x)))

        return x


class PatchEmbed(nn.Module):
    """ Image to Patch Embedding
    """

    def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
        super().__init__()
        img_size = to_2tuple(img_size)
        patch_size = to_2tuple(patch_size)

        self.img_size = img_size
        self.patch_size = patch_size
        assert img_size[0] % patch_size[0] == 0 and img_size[1] % patch_size[1] == 0, \
            f"img_size {img_size} should be divided by patch_size {patch_size}."
        self.H, self.W = img_size[0] // patch_size[0], img_size[1] // patch_size[1]
        self.num_patches = self.H * self.W
        self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
        self.norm = nn.LayerNorm(embed_dim)

    def forward(self, x):
        B, C, H, W = x.shape

        x = self.proj(x).flatten(2).transpose(1, 2)
        x = self.norm(x)
        H, W = H // self.patch_size[0], W // self.patch_size[1]

        return x, (H, W)


class PyramidVisionTransformer(nn.Module):
    def __init__(self, img_size=224, patch_size=16, in_chans=3, num_classes=1000, embed_dims=[64, 128, 256, 512],
                 num_heads=[1, 2, 4, 8], mlp_ratios=[4, 4, 4, 4], qkv_bias=False, qk_scale=None, drop_rate=0.,
                 attn_drop_rate=0., drop_path_rate=0., norm_layer=nn.LayerNorm,
                 depths=[3, 4, 6, 3], sr_ratios=[8, 4, 2, 1], F4=False):
        super().__init__()
        self.num_classes = num_classes
        self.depths = depths
        self.F4 = F4

        # patch_embed
        self.patch_embed1 = PatchEmbed(img_size=img_size, patch_size=patch_size, in_chans=in_chans,
                                       embed_dim=embed_dims[0])
        self.patch_embed2 = PatchEmbed(img_size=img_size // 4, patch_size=2, in_chans=embed_dims[0],
                                       embed_dim=embed_dims[1])
        self.patch_embed3 = PatchEmbed(img_size=img_size // 8, patch_size=2, in_chans=embed_dims[1],
                                       embed_dim=embed_dims[2])
        self.patch_embed4 = PatchEmbed(img_size=img_size // 16, patch_size=2, in_chans=embed_dims[2],
                                       embed_dim=embed_dims[3])

        # pos_embed
        self.pos_embed1 = nn.Parameter(torch.zeros(1, self.patch_embed1.num_patches, embed_dims[0]))
        self.pos_drop1 = nn.Dropout(p=drop_rate)
        self.pos_embed2 = nn.Parameter(torch.zeros(1, self.patch_embed2.num_patches, embed_dims[1]))
        self.pos_drop2 = nn.Dropout(p=drop_rate)
        self.pos_embed3 = nn.Parameter(torch.zeros(1, self.patch_embed3.num_patches, embed_dims[2]))
        self.pos_drop3 = nn.Dropout(p=drop_rate)
        self.pos_embed4 = nn.Parameter(torch.zeros(1, self.patch_embed4.num_patches + 1, embed_dims[3]))
        self.pos_drop4 = nn.Dropout(p=drop_rate)

        # transformer encoder
        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]  # stochastic depth decay rule
        cur = 0
        self.block1 = nn.ModuleList([Block(
            dim=embed_dims[0], num_heads=num_heads[0], mlp_ratio=mlp_ratios[0], qkv_bias=qkv_bias, qk_scale=qk_scale,
            drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[cur + i], norm_layer=norm_layer,
            sr_ratio=sr_ratios[0])
            for i in range(depths[0])])

        cur += depths[0]
        self.block2 = nn.ModuleList([Block(
            dim=embed_dims[1], num_heads=num_heads[1], mlp_ratio=mlp_ratios[1], qkv_bias=qkv_bias, qk_scale=qk_scale,
            drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[cur + i], norm_layer=norm_layer,
            sr_ratio=sr_ratios[1])
            for i in range(depths[1])])

        cur += depths[1]
        self.block3 = nn.ModuleList([Block(
            dim=embed_dims[2], num_heads=num_heads[2], mlp_ratio=mlp_ratios[2], qkv_bias=qkv_bias, qk_scale=qk_scale,
            drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[cur + i], norm_layer=norm_layer,
            sr_ratio=sr_ratios[2])
            for i in range(depths[2])])

        cur += depths[2]
        self.block4 = nn.ModuleList([Block(
            dim=embed_dims[3], num_heads=num_heads[3], mlp_ratio=mlp_ratios[3], qkv_bias=qkv_bias, qk_scale=qk_scale,
            drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[cur + i], norm_layer=norm_layer,
            sr_ratio=sr_ratios[3])
            for i in range(depths[3])])

        # init weights
        trunc_normal_(self.pos_embed1, std=.02)
        trunc_normal_(self.pos_embed2, std=.02)
        trunc_normal_(self.pos_embed3, std=.02)
        trunc_normal_(self.pos_embed4, std=.02)
        self.apply(self._init_weights)

    def init_weights(self, pretrained=True):
        import torch

        # if isinstance(pretrained, str):
        #     logger = get_root_logger()
        #     load_checkpoint(self, pretrained, map_location='cpu', strict=False, logger=logger)

    def reset_drop_path(self, drop_path_rate):
        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(self.depths))]
        cur = 0
        for i in range(self.depths[0]):
            self.block1[i].drop_path.drop_prob = dpr[cur + i]

        cur += self.depths[0]
        for i in range(self.depths[1]):
            self.block2[i].drop_path.drop_prob = dpr[cur + i]

        cur += self.depths[1]
        for i in range(self.depths[2]):
            self.block3[i].drop_path.drop_prob = dpr[cur + i]

        cur += self.depths[2]
        for i in range(self.depths[3]):
            self.block4[i].drop_path.drop_prob = dpr[cur + i]

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            trunc_normal_(m.weight, std=.02)
            if isinstance(m, nn.Linear) and m.bias is not None:
                nn.init.constant_(m.bias, 0)
        elif isinstance(m, nn.LayerNorm):
            nn.init.constant_(m.bias, 0)
            nn.init.constant_(m.weight, 1.0)

    def _get_pos_embed(self, pos_embed, patch_embed, H, W):
        if H * W == self.patch_embed1.num_patches:
            return pos_embed
        else:
            return F.interpolate(
                pos_embed.reshape(1, patch_embed.H, patch_embed.W, -1).permute(0, 3, 1, 2),
                size=(H, W), mode="bilinear").reshape(1, -1, H * W).permute(0, 2, 1)

    def forward_features(self, x):
        outs = []

        B = x.shape[0]

        # stage 1
        x, (H, W) = self.patch_embed1(x)
        pos_embed1 = self._get_pos_embed(self.pos_embed1, self.patch_embed1, H, W)
        x = x + pos_embed1
        x = self.pos_drop1(x)
        for blk in self.block1:
            x = blk(x, H, W)
        x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
        outs.append(x)

        # stage 2
        x, (H, W) = self.patch_embed2(x)
        pos_embed2 = self._get_pos_embed(self.pos_embed2, self.patch_embed2, H, W)
        x = x + pos_embed2
        x = self.pos_drop2(x)
        for blk in self.block2:
            x = blk(x, H, W)
        x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
        outs.append(x)

        # stage 3
        x, (H, W) = self.patch_embed3(x)
        pos_embed3 = self._get_pos_embed(self.pos_embed3, self.patch_embed3, H, W)
        x = x + pos_embed3
        x = self.pos_drop3(x)
        for blk in self.block3:
            x = blk(x, H, W)
        x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
        outs.append(x)

        # stage 4
        x, (H, W) = self.patch_embed4(x)
        pos_embed4 = self._get_pos_embed(self.pos_embed4[:, 1:], self.patch_embed4, H, W)
        x = x + pos_embed4
        x = self.pos_drop4(x)
        for blk in self.block4:
            x = blk(x, H, W)
        x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()
        outs.append(x)

        return outs

    def forward(self, x):
        x = self.forward_features(x)

        if self.F4:
            x = x[3:4]

        return x


def _conv_filter(state_dict, patch_size=16):
    """ convert patch embedding weight from manual patchify + linear proj to conv"""
    out_dict = {}
    for k, v in state_dict.items():
        if 'patch_embed.proj.weight' in k:
            v = v.reshape((v.shape[0], 3, patch_size, patch_size))
        out_dict[k] = v

    return out_dict

def pvt_small():
    model = PyramidVisionTransformer(
        patch_size=4, embed_dims=[64, 128, 320, 512], num_heads=[1, 2, 5, 8], mlp_ratios=[8, 8, 4, 4], qkv_bias=True,
        norm_layer=partial(nn.LayerNorm, eps=1e-6), depths=[3, 4, 6, 3], sr_ratios=[8, 4, 2, 1])

    return model

model = pvt_small()
data = torch.randn((2, 3, 224, 224))
feature = model(data)
print(model)
for out in feature:
    print(out.shape)

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

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

相关文章

【云原生】k8s 如何运行 Container?

文章目录 引语1、什么是 Pod1.1 简介1.2 Pod 怎样管理多个容器?1.3 如何使用 Pod? 2、Pod 基本操作2.1 查看 Pod2.2 创建 Pod2.3 删除 pod2.4 进入 Pod 中容器2.5 查看 Pod 日志2.6 查看 Pod 的描述信息 3、Pod 运行多个容器3.1 创建 Pod3.2 查看指定容器…

浅谈编译器对构造函数和拷贝构造的优化

一、前言 ①我们先看一段程序&#xff0c;传值传参和传引用传参 #include <iostream> using namespace std;class D { public:D(int a0)//构造:_a(a){cout << "D(int a0)" << endl;}D(const D& d)//拷贝{_a d._a;cout << "D(con…

域名到期时间查询网站-免费版的翼龙老域名挖掘

批量域名过期查询软件 随着互联网的不断发展&#xff0c;域名越来越重要&#xff0c;特别是对于那些热爱SEO优化的网站管理员来说&#xff0c;域名的重要性无法忽视。而对于那些热衷于SEO优化的用户来说&#xff0c;域名过期查询可能是一个必不可少的工具&#xff0c;它可以用…

数据结构初阶--链表OJⅡ

目录 前言相交链表思路分析代码实现 环形链表思路分析代码实现 环形链表Ⅱ思路分析代码实现 复制带随机指针的链表思路分析代码实现 前言 本篇文章承接上篇博客&#xff0c;继续对部分经典链表OJ题进行讲解 相交链表 先来看题目描述 思路分析 这道题我们还是首先来判断一…

蓝牙耳机怎么挑选?小编分享2023畅销蓝牙耳机排行榜

蓝牙耳机怎么挑选&#xff1f;蓝牙、音质、续航、佩戴是蓝牙耳机选购时最重要的四大维度&#xff0c;这几年随着技术的成熟体验有了很大改善&#xff0c;但挑选的时候仍然要仔细对比&#xff0c;不然容易踩雷。小编根据销量整理了蓝牙耳机排行榜&#xff0c;一起看看最受消费者…

水务行业数智化招标采购系统建设解决方案

水务行业数智化采购解决方案 国家“十四五”规划和2035年远景目标纲要&#xff1a;提升产业链供应链现代化水平。加快数字化发展&#xff0c;推动产业数字化&#xff0c;数字产业化&#xff0c;以数字化转型整体驱动生产方式、生活方式和治理方式变革。利用数字技术重构价值链…

kafka-Producer Sender 源码分析

说明 本文基于 kafka 2.7 编写。author blog.jellyfishmix.com / JellyfishMIX - githubLICENSE GPL-2.0 Sender 类属性 public class Sender implements Runnable {private final Logger log;/*** Sender 具体用的是 KafkaClient 接口的实现类 NetworkClient, 为 Sender 提…

【算法】经典背包问题

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录 引入Dp1.01背包2.完全背包3.多重背包4.分组背包 acwing 背包问题——学习笔记 01背包、完全背包、多重背包、分组背包 引入Dp Dp问题&#…

[SUCTF 2018]GetShell

有个文件上传&#xff0c;给了部分源码 if($contentsfile_get_contents($_FILES["file"]["tmp_name"])){$datasubstr($contents,5);foreach ($black_char as $b) {if (stripos($data, $b) ! false){die("illegal char");}} } 可以知道有…

黑白照片如何变彩色?黑白照变彩色的秘诀分享。​

黑白照片如何变彩色&#xff1f;将黑白照片变成彩色可以给照片增添生动的视觉效果和真实感&#xff0c;使得人物、场景更加具体形象&#xff0c;让人们更容易与之产生共鸣和情感联系&#xff0c;此外&#xff0c;通过给黑白老照片添加颜色&#xff0c;还可以打破时间和空间的限…

社区分享|JumpServer引领我走向开源天地

编者注&#xff1a;以下内容基于山东青岛的JumpServer社区用户JonnyJ的社区分享整理而成。 “接触到JumpServer之后&#xff0c;我从一个开源受益者逐渐成长为开源的贡献者。其实我们每个人都可以成为开源贡献者&#xff0c;不局限于软件产品&#xff0c;哪怕只是你的一段共享…

K8S集群+kubeadm+flannel+docker+harbor实例

目录 第一章.环境准备 1.1.部署架构图 1.2.节点要求 1.3.部署软件 1.4.修改主机名 1.5.所有节点修改hosts文件 1.6.关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换 1.7.调整内核参数 第二章.部署K8S集群 2.1.所有节点安装docker 2.2.所有节点安装ku…

KVM管理-快照

KVM管理-快照 创建快照 为虚拟机vm1创建一个快照 [rootmyserver ~]# virsh snapshot-create-as vm1 vm1.snap Domain snapshot vm1.snap created快照只能使用qcow2创建&#xff0c;raw格式一般无法创建快照 查看磁盘镜像信息 [rootmyserver ~]# qemu-img info /var/lib/lib…

方案设计——食物测温仪方案

食物测温仪&#xff0c;在食物烹饪时&#xff0c;温度和时间至关重要&#xff0c;所以食物测温仪孕育而生&#xff0c;当用户使用时只需将食物测温仪的探头插入食物中&#xff0c;即刻能得到当前食物温度数据&#xff0c;不必用经验判断。做为一款食物测温仪&#xff0c;运用场…

Spring Boot :统一功能处理

在用户登陆验证的业务中&#xff0c;如果只是使用Spring AOP的话&#xff0c;session无法获取的&#xff0c;还有各种参数&#xff08;request等&#xff09;很难获取&#xff0c;这时候Spring拦截器就发挥了重大的作用了。 1.Spring 拦截器 创建拦截器分俩步&#xff1a;1.创…

项目集效益管理

项目集效益管理是定义、创建、最大化和交付项目集所提供的效益的绩效领域。 本章内容包括&#xff1a; 1 效益识别 2 效益分析和规划 3 效益交付 4 效益移交 5 效益维持 项目集效益管理包括一系列对项目集的成功极为重要的要素。项目集效益管理包括阐明项目集的 计划效益和预期…

AMBER分子动力学模拟之结果分析(最低能量结果)-- HIV蛋白酶-抑制剂复合物(3)

AMBER分子动力学模拟之结果分析(最低能量结果)-- HIV蛋白酶-抑制剂复合物(3) 在analysis目录下 解析.out文件 下载process_mdout.perl 脚本 perl process_mdout.perl ../md/md0.out ../md/md1.out ../md/md2.out # 可以不使用md0.out # 或者 $AMBERHOME/bin/process_md…

ShardingSphere 5.3 系列ShardingSphere-Proxy保姆级教程 | Spring Cloud 50

一、前言 通过以下系列章节&#xff1a; Spring Boot集成ShardingSphere实现数据分片&#xff08;一&#xff09; | Spring Cloud 40 Spring Boot集成ShardingSphere实现数据分片&#xff08;二&#xff09; | Spring Cloud 41 Spring Boot集成ShardingSphere实现数据分片&…

Linux:centos:组账户管理 》》添加组,用户加入组(设置组密码),删除组,查询账户信息,查询登录用户信息

/etc/group # 组信息文件 /etc/gshadow # 组密码文件&#xff08;不常用&#xff09; groupadd &#xff08;属性&#xff09; 组名 # 新建组 groupdel &#xff08;属性&#xff09; 组名 # 删除组 gpasswd # 可以…