ICCV2023人脸识别TransFace论文及代码学习笔记

news2024/11/23 15:22:47

论文链接:https://arxiv.org/pdf/2308.10133.pdf

代码链接:GitHub - DanJun6737/TransFace: Code of TransFace

背景

尽管ViTs在多种视觉任务中展示了强大的表示能力,但作者发现,当应用于具有极大数据集的人脸识别场景时,ViTs的性能却较差。通过深入研究,作者发现现有的数据增强方法和难例挖掘策略与基于ViT的FR模型不兼容,原因在于缺乏对面部结构信息的保留和利用每个局部token信息的专门考虑

创新点

1、由于ViT模型缺乏像卷积那样的归纳偏置,使得ViT模型难以训练并容易过拟合。为了缓解ViTs的过拟合现象,现有工作尝试了几种数据增强策略,如Random Erasing、Mixup、CutMix、RandAugment及其变种,以构建多样化的训练样本。然而,这些实例级数据增强策略并不适用于人脸识别任务,因为它们不可避免地会破坏面部身份的关键结构信息,这可能导致ViTs朝错误的方向优化。此外,最近的研究发现ViTs在训练过程中容易对某些局部区域过拟合,导致模型的泛化性能变差。例如,在人脸识别任务中,ViT的预测可能由少数面部区域(如眼睛和前额)主导。因此,一旦这些关键区域被遮挡(例如,戴墨镜或帽子),模型就倾向于做出错误的决策。这些问题严重影响了基于ViT的人脸识别模型在真实场景中的应用。为了解决上述问题,作者提出Dominant Patch Amplitude Perturbation(DPAP)的Patch级数据增强策略。DPAP不破坏面部的保真度和结构信息,可以有效地扩展样本多样性。具体来说,DPAP使用Squeeze-and-Excitation(SE)模块筛选出K个patches(主导patches),然后随机混合它们的幅度信息,并与原始相位信息结合,生成多样化的样本。与以往的数据增强策略不同,所提出的DPAP巧妙地利用了模型提供的先验知识(即主导patches的位置)来增强数据,这可以更精确地缓解ViTs中的过拟合问题。此外,随着多样化patches的不断生成,DPAP也间接鼓励ViTs利用其他面部区域,特别是深层网络容易忽略的一些区域(如耳朵、嘴巴和鼻子),以做出更优的决策。

2、以前的难例挖掘策略大都是为CNN设计的,它们通常采用样本的实例级指标(如预测概率、预测损失、潜在特征)来挖掘难例。然而,ViT的预测主要由几个patch tokens决定,ViT的全局token可能被几个局部token主导。因此,直接使用这样有偏见的指标来挖掘难例对于ViTs来说是次优的(特别是当一些主导的局部token被忽略时)。为了更好地挖掘难例,作者提出Entropy-guided Hard Sample Mining(EHSM)的新难例挖掘策略。EHSM将ViT视为一个信息处理系统,它根据局部token中包含的总信息量动态调整简单样本和困难样本的重要性权重。EHSM鼓励ViT充分利用每个面部patches中包含的细粒度信息,特别是一些较少关注的面部线索(如嘴唇和下巴),这极大地增强了每个局部token的特征表示能力。这样,即使一些重要的patches被破坏,模型也可以充分利用剩余的面部线索来泛化全局token,从而做出更稳定的预测。

方法论

模型的整体框架图如下,

DPAP

为了解决ViT模型在人脸识别任务中的过拟合问题,论文提出Dominant Patch Amplitude Perturbation(DPAP)的新型patch级数据增强策略。该策略的主要步骤如下:

1、在transformer编码器的输出端插入一个SE模块,并使用SE模块生成的权重(权重反映了局部tokens在预测中的重要性)找出原始图像的K个patches(即K个主导patches),这些patches对最终预测贡献最大

        将图片输入到模型中,以得到权重weight注意,此次前向传播不会产生梯度,该步骤的目的是利用模型生成先验知识

with torch.no_grad():
    local_embeddings, weight, local_patch_entropy = backbone(img)  ## [n, 512], [n, 144], [n, 144]
    loss: torch.Tensor = module_partial_fc(local_embeddings, local_labels, opt, local_patch_entropy) 

        模型网络结构的代码如下,

class VisionTransformer(nn.Module):
    """ 
    Vision Transformer with support for patch or hybrid CNN input stage
    """

    def __init__(self,
                 img_size: int = 112,
                 patch_size: int = 16,
                 in_channels: int = 3,
                 num_classes: int = 1000,
                 embed_dim: int = 768,
                 depth: int = 12,
                 num_heads: int = 12,
                 mlp_ratio: float = 4.,
                 qkv_bias: bool = False,
                 qk_scale: Optional[None] = None,
                 drop_rate: float = 0.,
                 attn_drop_rate: float = 0.,
                 drop_path_rate: float = 0.,
                 hybrid_backbone: Optional[None] = None,
                 norm_layer: str = "ln",
                 mask_ratio = 0.1,
                 using_checkpoint = False,
                 ):
        super().__init__()
        self.num_classes = num_classes  ## 512
        self.num_features = self.embed_dim = embed_dim  ## 512

        if hybrid_backbone is not None:
            raise ValueError
        else:
            self.patch_embed = PatchEmbed(img_size=img_size, patch_size=patch_size, in_channels=in_channels, embed_dim=embed_dim)
        self.mask_ratio = mask_ratio
        self.using_checkpoint = using_checkpoint
        num_patches = self.patch_embed.num_patches  ## 144
        self.num_patches = num_patches  ## 144

        self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim))  ## [1, 144, 512]
        self.pos_drop = nn.Dropout(p=drop_rate)

        # stochastic depth decay rule
        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]  ## drop_path_rate = 0.05, depth = 12
        patch_n = (img_size//patch_size)**2  ## 144
        self.blocks = nn.ModuleList(
            [
                Block(dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
                      drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer,
                      num_patches=num_patches, patch_n=patch_n)
                for i in range(depth)]
        )
        self.extra_gflops = 0.0
        for _block in self.blocks:
            self.extra_gflops += _block.extra_gflops

        if norm_layer == "ln":
            self.norm = nn.LayerNorm(embed_dim)
        elif norm_layer == "bn":
            self.norm = VITBatchNorm(self.num_patches)

        # features head
        self.feature = nn.Sequential(
            nn.Linear(in_features=embed_dim * num_patches, out_features=embed_dim, bias=False),
            nn.BatchNorm1d(num_features=embed_dim, eps=2e-5),
            nn.Linear(in_features=embed_dim, out_features=num_classes, bias=False),
            nn.BatchNorm1d(num_features=num_classes, eps=2e-5)
        )

        self.mask_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
        torch.nn.init.normal_(self.mask_token, std=.02)
        trunc_normal_(self.pos_embed, std=.02)
        self.apply(self._init_weights)

        ## SEModule FC
        self.senet = nn.Sequential(
            nn.Linear(in_features=embed_dim * num_patches, out_features=num_patches, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=num_patches, out_features=num_patches, bias=False),
            nn.Sigmoid()
        )
            

    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)


    @torch.jit.ignore
    def no_weight_decay(self):
        return {'pos_embed', 'cls_token'}


    def get_classifier(self):
        return self.head
    
    
    def random_masking(self, x, mask_ratio=0.1):
        """
        Perform per-sample random masking by per-sample shuffling.
        Per-sample shuffling is done by argsort random noise.
        x: [N, L, D], sequence
        """
        N, L, D = x.size()  # n, 144, 512
        len_keep = int(L * (1 - mask_ratio))

        noise = torch.rand(N, L, device=x.device)  ## [n, 144], noise in [0, 1]

        # sort noise for each sample
        # ascend: small is keep, large is remove
        ids_shuffle = torch.argsort(noise, dim=1)
        ids_restore = torch.argsort(ids_shuffle, dim=1)

        # keep the first subset
        ids_keep = ids_shuffle[:, :len_keep]  ## [n, 129]
        x_masked = torch.gather(
            x, dim=1, index=ids_keep.unsqueeze(-1).repeat(1, 1, D))  ## [n, 129, 512]

        # generate the binary mask: 0 is keep, 1 is remove
        mask = torch.ones([N, L], device=x.device)  ## [n, 144]
        mask[:, :len_keep] = 0  ## [n, 144]
        # unshuffle to get the binary mask
        mask = torch.gather(mask, dim=1, index=ids_restore)

        return x_masked, mask, ids_restore  ## [n, 129, 512], [n, 144], [n, 144]

    
    def forward_features(self, x):
        B = x.shape[0]
        x = self.patch_embed(x)  ## [n, 144, 512]
        x = x + self.pos_embed  ## [n, 144, 512]
        x = self.pos_drop(x)  ## [n, 144, 512]

        if self.training and self.mask_ratio > 0:
            x, _, ids_restore = self.random_masking(x)  ## [n, 129, 512], [n, 144], [n, 144]

        for func in self.blocks:
            if self.using_checkpoint and self.training:
                from torch.utils.checkpoint import checkpoint
                x = checkpoint(func, x)
            else:
                x = func(x)
        x = self.norm(x.float())  ## [n, 129, 512]
        
        if self.training and self.mask_ratio > 0:
            mask_tokens = self.mask_token.repeat(x.shape[0], ids_restore.shape[1] - x.shape[1], 1)  ## [n, 15, 512]
            x_ = torch.cat([x[:, :, :], mask_tokens], dim=1)  ## [n, 144, 512]
            x_ = torch.gather(x_, dim=1, index=ids_restore.unsqueeze(-1).repeat(1, 1, x.shape[2]))  ## [n, 144, 512]
            x = x_  ## [n, 144, 512]

        orginal = x  ## [n, 144, 512]
        out = torch.reshape(x, (B, self.num_patches * self.embed_dim))  ## [n, 144*512]
        out = self.senet(out)  ## [n, 144]
        out_softmax = out.softmax(dim=1)  ## [n, 144]
        out = torch.reshape(out, (B, self.num_patches, 1))  ## [n, 144, 1]
        out = out * orginal  ## [n, 144, 512]
        return torch.reshape(out, (B, self.num_patches * self.embed_dim)), out_softmax  ## [n, 144*512], [n, 144]


    def forward(self, x):
        x, weight = self.forward_features(x)  ## [n, 144*512], [n, 144]
        out_x = torch.reshape(x, (x.shape[0], self.num_patches, self.embed_dim))  ## [n, 144, 512]
        patch_std = torch.std(out_x, dim=2)  ## [n, 144]        
        patch_entropy = torch.log(patch_std) + 0.5 + 0.5*torch.log( torch.tensor(2*math.pi) )  ## Entropy
        # patch_entropy = patch_std  ## [n, 144]
        x = self.feature(x)  ## [n, 512]
        return x, weight, patch_entropy  ## [n, 512], [n, 144], [n, 144]

        其中,输出weight就是由上面所述的SE模块生成的权重。

        随后,找出原始图像的K个patches(即K个主导patches),

## TopK
K = 7
TopK_ALL = torch.argsort(weight, dim=1, descending=True)
TopK_ALL = TopK_ALL.cpu().numpy()
TopK  = TopK_ALL[:, :K]  ## [n, 7]

2、使用线性混合机制随机扰动这些主导patches的幅度信息

probability = 0.2
batch_index = 0
            
for index in TopK:
    if random.random() <= probability:
        for j in range(TopK.shape[1]):
            patch_index_h = int(np.floor(index[j] / 12))  ## 0 < patch_index_h < 12
            patch_index_w = int((index[j] - patch_index_h * 12))
            img_src = img_original[batch_index, 9*patch_index_h:9*(1+patch_index_h), 9*patch_index_w:9*(1+patch_index_w), :]  ## [9, 9, 3]
            random_index = int(np.random.randint(0, img.size()[0], 1))  ## 0 < random_index < n
            random_h = int(np.random.randint(0, 12, 1))  ## 0 < random_h < 12
            random_w = int(np.random.randint(0, 12, 1))  ## 0 < random_w < 12
            img_random = img_original[random_index, 9*random_h:9*(1+random_h), 9*random_w:9*(1+random_w), :]  ## [9, 9, 3]
            img_src_random = amplitude_spectrum_mix(img_src, img_random, alpha=1)
            img_original[batch_index, 9*patch_index_h:9*(1+patch_index_h), 9*patch_index_w:9*(1+patch_index_w), :] = img_src_random
    batch_index = batch_index + 1
def amplitude_spectrum_mix(img1, img2, alpha, ratio=1.0):   ## img_src, img_random, alpha=1, ratio=1.0
    """Input image size: ndarray of [H, W, C], ps: [9, 9, 3]"""
    lam = np.random.uniform(0, alpha)  ## 0 < lam < 1
    assert img1.shape == img2.shape
    h, w, c = img1.shape  ## 9, 9, 3
    h_crop = int(h * sqrt(ratio))  ## 1
    w_crop = int(w * sqrt(ratio))  ## 1
    h_start = h // 2 - h_crop // 2  ## 4
    w_start = w // 2 - w_crop // 2  ## 4

    img1_fft = np.fft.fft2(img1, axes=(0, 1))  ## 计算二维的傅里叶变换
    img2_fft = np.fft.fft2(img2, axes=(0, 1))
    img1_abs, img1_pha = np.abs(img1_fft), np.angle(img1_fft)
    img2_abs, img2_pha = np.abs(img2_fft), np.angle(img2_fft)

    img1_abs = np.fft.fftshift(img1_abs, axes=(0, 1))  ## 将FFT输出中的直流分量移动到频谱中央
    img2_abs = np.fft.fftshift(img2_abs, axes=(0, 1))
    
    img1_abs_ = np.copy(img1_abs)
    img2_abs_ = np.copy(img2_abs)

    img1_abs[h_start:h_start + h_crop, w_start:w_start + w_crop] = \
        lam * img2_abs_[h_start:h_start + h_crop, w_start:w_start + w_crop] + (1 - lam) * img1_abs_[h_start:h_start + h_crop, w_start:w_start + w_crop]

    img1_abs = np.fft.ifftshift(img1_abs, axes=(0, 1))
    img2_abs = np.fft.ifftshift(img2_abs, axes=(0, 1))

    img_src_random = img1_abs * (np.e ** (1j * img1_pha))
    img_src_random = np.real(np.fft.ifft2(img_src_random, axes=(0, 1)))
    img_src_random = np.uint8(np.clip(img_src_random, 0, 255))
    
    return img_src_random

3、将重建的图像输入TransFace模型进行监督训练(该步骤会正常产生梯度,优化参数)

img_fft = torch.tensor(img_original).cuda()
img_fft = img_fft.permute(0, 3, 1, 2)    ## [n, 3, 112, 112]
img_fft = ((img_fft / 255) - 0.5) / (0.5)
            
local_embeddings, weight, local_patch_entropy = backbone(img_fft)  ## [n, 512], [n, 144], [n, 144]
loss: torch.Tensor = module_partial_fc(local_embeddings, local_labels, opt, local_patch_entropy)

EHSM

为了更精确地挖掘难例,论文提出新的难例挖掘策略Entropy-guided Hard Sample Mining (EHSM)。EHSM通过信息论的启发,将ViT视为一个信息处理系统,根据局部tokens中包含的总信息量动态调整简单样本和困难样本的重要性权重。

具体来说,

1、EHSM首先估计每个局部token的局部信息熵(即下面代码中的patch_entropy

x, weight = self.forward_features(x)  ## [n, 144*512], [n, 144]
out_x = torch.reshape(x, (x.shape[0], self.num_patches, self.embed_dim))  ## [n, 144, 512]
patch_std = torch.std(out_x, dim=2)  ## [n, 144]        
patch_entropy = torch.log(patch_std) + 0.5 + 0.5*torch.log( torch.tensor(2*math.pi) )  ## Entropy
# patch_entropy = patch_std  ## [n, 144]
x = self.feature(x)  ## [n, 512]
return x, weight, patch_entropy  ## [n, 512], [n, 144], [n, 144]

信息熵的计算公式如下,

2、然后,将所有局部信息熵聚合为样本的全局信息熵 

gamma = 1.0
K_ = 144
entropy_topK, _ = torch.topk(patch_entropy_, k = K_, dim=1)
entropy = gamma * torch.mean(entropy_topK, dim=1)

3、最后,EHSM使用熵感知权重机制来适应性地为每个样本分配重要性权重

sample_weight = 1 + torch.exp(-entropy)
G_weight = sample_weight

通过这种方式,EHSM明确鼓励模型关注信息量较少的难样本。

为了最小化目标Loss,模型在训练过程中必须同时优化权重和基本分类损失,这将带来两个好处:(1) 最小化基本分类损失可以鼓励模型从多样化的训练样本中学习更好的面部特征;(2) 最小化权重(即最大化总信息)将促进模型充分挖掘每个面部patches中包含的特征信息,特别是一些较少关注的面部线索(如鼻子、嘴唇和下巴),这显著增强了每个局部token的特征表示能力。

实验

数据集

使用MS1MV2和Glint360K数据集训练模型。使用LFW、AgeDB-30、CFP-FP和IJB-C评估模型。

训练设置

使用Pytorch在8个NVIDIA Tesla V100 GPU上训练。采用ArcFace作为基本分类损失,并将所有输入图像裁剪到112×112大小。使用AdamW优化器进行优化。对于MS1MV2,基础学习率设置为1e-3;对于Glint360K,学习率设置为1e-4。

与SOTA方法的结果对比

在LFW、CFP-FP和AgeDB-30上评估TransFace并与其它方法比较,发现TransFace的性能已经接近饱和状态。TransFace-L在三个数据集上的性能分别比ViT-L高出0.03%、0.22%和0.15%

在MS1MV2和Glint360K上训练TransFace,并与IJB-C基准上的SOTA比较。TransFace在MS1MV2数据集上训练的模型在“TAR@FAR=1E-4”上大幅超越其他基于ResNet的模型。例如,与CurricularFace相比,TransFace-B在“TAR@FAR=1E-4”上提高了0.45%。此外,TransFace-S在“TAR@FAR=1E-4”上比ViT-S高出0.56%。在Glint360K上训练的模型,TransFace显著优于其他竞争对手。特别是,TransFace-L在“TAR@FAR=1E-4”和“TAR@FAR=1E-5”上分别比ViT-L高出0.48%和0.51%

消融实验

结论

作者提出TransFace,引入DPAP的patch级数据增强策略和EHSM的难例挖掘策略。其中,DPAP采用线性混合机制来扰动主导patches的幅度信息,以缓解ViTs中的过拟合问题。EHSM充分利用多个局部tokens中的信息熵来衡量样本难度,极大地增强了局部tokens的特征表示能力。TransFace除了添加SE模块外,没有引入任何重大的架构变化。

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

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

相关文章

Prometheus+Grafana多方位监控

PrometheusGrafana多方位监控 契机 ⚙ 最近发现火山引擎有托管的Prometheus,可是当前是邀测阶段。并且发现火山云的ECS是自带开机自启的exporter的。刚好需要搭建一套服务器监控&#xff0c;所以研究了一套Prometheus监控&#xff0c;包含linux主机监控nginx监控es监控rabbitM…

ITMS-90426: Invalid Swift Support

原文 Please correct the following issues and upload a new binary to App Store Connect. ITMS-90426: Invalid Swift Support - The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it. 解决方式 ITMS-…

Keepalived+LVS实现Nginx集群配置

Nginx1和Nginx2组成集群&#xff0c;为了实现负载均衡&#xff0c;在集群的前端配置了LVS服务&#xff0c;但是一台LVS容器产生单点故障&#xff0c;因此需要过Keepalived实现LVS的高可用集群 192.168.136.55node1keepalived192.168.136.56node2keeplived192.168.136.57 node3n…

SoundStream: 下一代的神经网络音频编解码器,实时压缩不牺牲音质

音频编解码技术的目标是&#xff0c;通过减少音频文件的大小来节省存储空间或减轻网络传输的负担。理想的情况下&#xff0c;即使音频被压缩&#xff0c;我们听到的声音与原版也应该没有任何区别。 过去&#xff0c;已经有不少编解码技术被开发出来&#xff0c;满足了这些需求…

Linux基础-socket详解、TCP/UDP

文章目录 一、Socket 介绍二、Socket 通信模型三、Socket 常用函数1 创建套接字2 绑定套接字3、监听连接4、接受连接5、接收和发送数据接收数据发送数据 6、关闭套接字 四、Socket编程试验1、源码server.cclient.c 2、编译&#xff1a;3、执行结果 五、补充TCP和UDP协议的Socke…

无人机+三维建模:倾斜摄影技术详解

无人机倾斜摄影测量技术是一项高新技术&#xff0c;近年来在国际摄影测量领域得到了快速发展。这种技术通过从一个垂直和四个倾斜的五个不同视角同步采集影像&#xff0c;从而获取到丰富的建筑物顶面及侧视的高分辨率纹理。这种技术不仅能够真实地反映地物情况&#xff0c;还能…

3.CQL使用-创建、查询、返回

Neo4j的Cypher语言为处理图形数据而构建的。 CQL代表Cypher查询语言。 https://neo4j.com/docs/cypher-manual/3.5/clauses/load-csv/ CQL语句使用文档地址。 小技巧&#xff1a;在neo4j的CQL语句输入框里&#xff0c;shift enter换行&#xff0c;直接enter键会执行CQL语…

2024智能科学与软件工程国际学术会议(ICISSE 2024)

2024智能科学与软件工程国际学术会议&#xff08;ICISSE 2024) 会议简介 2024智能科学与软件工程国际学术会议&#xff08;ICISSE 2024&#xff09;将在北京隆重举行。本次会议汇集了全球智能科学和软件工程领域的专家学者&#xff0c;共同探讨该领域的最新研究成果和发展趋…

跟TED演讲学英文:AI isn‘t as smart as you think -- but it could be by Jeff Dean

AI isn’t as smart as you think – but it could be Link: https://www.ted.com/talks/jeff_dean_ai_isn_t_as_smart_as_you_think_but_it_could_be Speaker: Jeff Dean Jeffrey Adgate “Jeff” Dean (born July 23, 1968) is an American computer scientist and software…

Spirng 当中 Bean的作用域

Spirng 当中 Bean的作用域 文章目录 Spirng 当中 Bean的作用域每博一文案1. Spring6 当中的 Bean的作用域1.2 singleton 默认1.3 prototype1.4 Spring 中的 bean 标签当中scope 属性其他的值说明1.5 自定义作用域&#xff0c;一个线程一个 Bean 2. 总结:3. 最后&#xff1a; 每…

JAVA基础---Stream流

Stream流出现背景 背景 在Java8之前&#xff0c;通常用 fori、for each 或者 Iterator 迭代来重排序合并数据&#xff0c;或者通过重新定义 Collections.sorts的 Comparator 方法来实现&#xff0c;这两种方式对 大数量系统来说&#xff0c;效率不理想。 Java8 中添加了一个…

一款可视化正则表达式工具

regex-vis是一款在线免费且可视化的正则表达式工具 界面图&#xff1a; 只能输入由26个英文字母组成的字符串 ^[A-Za-z]$ 只能输入数字 ^[0-9]*$测试错误 测试正确 快来感受一下叭 官方网址&#xff1a; Regex VisRegex visualizer & editor, make the regular expr…

西门子:HMI小游戏-灰太狼与喜羊羊

DB块&#xff1a; HMI界面&#xff1a; 实际视频&#xff1a; 抓羊小游戏

docker各目录含义

目录含义builder构建docker镜像的工具或过程buildkit用于构建和打包容器镜像&#xff0c;官方构建引擎&#xff0c;支持多阶段构建、缓存管理、并行化构建和多平台构建等功能containerd负责容器生命周期管理&#xff0c;能起、停、重启&#xff0c;确保容器运行。负责镜管理&am…

电脑技巧:推荐一款非常好用的媒体播放器PotPlayer

目录 一、 软件简介 二、功能介绍 2.1 格式兼容性强 2.2 高清播放与硬件加速 2.3 自定义皮肤与界面布局 2.4 多音轨切换与音效增强 2.5 字幕支持与编辑 2.6 视频截图与录像 2.7 网络流媒体播放 三、软件特色 四、使用技巧 五、总结 一、 软件简介 PotPlayer播放器 …

【面试经典 150 | 回溯】括号生成

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;暴力法方法二&#xff1a;回溯 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到…

compose调用系统分享功能分享图片文件

compose调用系统分享功能图片文件 简介UI界面提供给外部程序的文件访问权限创建FileProvider设置共享文件夹 通用分享工具虚拟机验证结果参考 本系列用于新人安卓基础入门学习笔记&#xff0c;有任何不同的见解欢迎留言 运行环境 jdk17 andriod 34 compose material3 简介 本案…

JAVA面试专题-Redis

你在最近的项目中哪些场景使用了Redis 缓存 缓存穿透 缓存穿透&#xff1a;查询一个不存在的数据&#xff0c;mysql查询不到数据也不好直接写入缓存&#xff0c;导致每次请求都查数据库。 解决方案一&#xff1a;缓存空数据&#xff0c;即使查询返回的数据为空&#xff0c;也把…

xss漏洞学习

1.xss漏洞简介 跨站脚本&#xff08;Cross-Site Scripting&#xff09;&#xff0c;本应该缩写为CSS&#xff0c;但是该缩写已被层叠样式脚本Cascading Style Sheets所用&#xff0c;所以改简称为XSS。也称跨站脚本或跨站脚本攻击。 原理&#xff1a;跨站脚本攻击XSS通过将恶…

SQLite如何处理CSV 虚拟表(三十七)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite的DBSTAT 虚拟表&#xff08;三十六&#xff09; 下一篇:SQLite的扩展函数Carray()表值函数(三十八) ​ RFC4180格式是一种文本文件格式&#xff0c;被用于表格数据间的交互&#xff0c;也可将表格数据转化…