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

论文链接: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  ## 512self.num_features = self.embed_dim = embed_dim  ## 512if hybrid_backbone is not None:raise ValueErrorelse:self.patch_embed = PatchEmbed(img_size=img_size, patch_size=patch_size, in_channels=in_channels, embed_dim=embed_dim)self.mask_ratio = mask_ratioself.using_checkpoint = using_checkpointnum_patches = self.patch_embed.num_patches  ## 144self.num_patches = num_patches  ## 144self.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 ruledpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]  ## drop_path_rate = 0.05, depth = 12patch_n = (img_size//patch_size)**2  ## 144self.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.0for _block in self.blocks:self.extra_gflops += _block.extra_gflopsif norm_layer == "ln":self.norm = nn.LayerNorm(embed_dim)elif norm_layer == "bn":self.norm = VITBatchNorm(self.num_patches)# features headself.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 FCself.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.ignoredef no_weight_decay(self):return {'pos_embed', 'cls_token'}def get_classifier(self):return self.headdef 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, 512len_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 removeids_shuffle = torch.argsort(noise, dim=1)ids_restore = torch.argsort(ids_shuffle, dim=1)# keep the first subsetids_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 removemask = torch.ones([N, L], device=x.device)  ## [n, 144]mask[:, :len_keep] = 0  ## [n, 144]# unshuffle to get the binary maskmask = 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 checkpointx = 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 = 0for 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 < 12patch_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 < nrandom_h = int(np.random.randint(0, 12, 1))  ## 0 < random_h < 12random_w = int(np.random.randint(0, 12, 1))  ## 0 < random_w < 12img_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_randombatch_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 < 1assert img1.shape == img2.shapeh, w, c = img1.shape  ## 9, 9, 3h_crop = int(h * sqrt(ratio))  ## 1w_crop = int(w * sqrt(ratio))  ## 1h_start = h // 2 - h_crop // 2  ## 4w_start = w // 2 - w_crop // 2  ## 4img1_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.mzph.cn/news/828419.shtml

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

相关文章

SpringMVC 源码剖析

SpringMVC 源码剖析 0 从源码角度分析SpringMVC执行流程 // 前端控制器&#xff0c;SpringMVC最核心的类 public class DispatcherServlet extends FrameworkServlet {// 前端控制器最核心的方法&#xff0c;这个方法是负责处理请求的&#xff0c;一次请求&#xff0c;调用一次…

C语言趣味代码(三)

这一篇主要围绕写一个程序---寻找数字 来写&#xff0c;在这篇我会详细和大家介绍基本实现以及它的改良版&#xff0c;还有相关知识的拓展&#xff0c;干货绝对满满。 1. 寻找数字 在这一主题下&#xff0c;我们会编写一些代码&#xff0c;来锻炼玩家的反应力&#xff0c;同时…

Vue2学习笔记(尚硅谷天禹老师)

目录 一、入门案例 二、模板语法 三、数据绑定 四、el和data的两种写法 五、MVVM模型 六、Object.defineproperty方法 七、Vue中响应式原理 八、数据代理 九、methods配置项 十、Vue中的事件处理 十一、Vue中的键盘事件 十二、计算属性 十三、监视属性watch 十四、绑定Class样式…

玩转微服务-SonarQube

这里写目录标题 第一节 SonarQube1.1 简介1.2 四个组成部分1.2.1 SonarQube服务器1.2.2 SonarQube数据库1.2.3 插件1.2.4 Scanner 1.3 工作流程 第二节 SonarQube的安装2.1 安装2.2 插件 第三节 P3C规范3.1 简介3.2 SonarQube 配置 P3C规范3.3 IDEA配置 P3C规范 第四节 Maven项…

Mybatis-动态SQL

黑马程序员JavaWeb开发教程 文章目录 一、Mybatis-XML映射文件1、XML映射文件&#xff08;1&#xff09;规范&#xff08;2&#xff09;MybatisX 二、Mybatis-动态SQL-if1、动态SQL2、 标签<if><where>3、示例 三、Mybatis-动态SQL-foreach根据 id 批量删除员工1、…

iOS - 多线程-atomic

文章目录 iOS - 多线程-atomic1. 源码分析1.1 get方法1.2 set方法 2. 一般不使用atomic的原因 iOS - 多线程-atomic atomic用于保证属性setter、getter的原子性操作&#xff0c;相当于在getter和setter内部加了线程同步的锁可以参考源码objc4的objc-accessors.mm它并不能保证使…

刚刚,ChatGPT重大更新!GPT-4更加聪明,已成毕业论文撰写润色修改最佳利器!

今天ChatGPT Plus版本做了升级。GPT-4增强了记忆功能&#xff0c;能够通过你的多次对话了解你的意图&#xff0c;并提供合适的回应&#xff0c;总结一句话就是&#xff1a;更加懂你&#xff01; ChatGPT到底能干什么&#xff1f;我简单总结一下&#xff1a; 翻译&#xff1a;中…

电商技术揭秘三十一:智能风控与反欺诈技术

相关系列文章 电商技术揭秘相关系列文章合集&#xff08;1&#xff09; 电商技术揭秘相关系列文章合集&#xff08;2&#xff09; 电商技术揭秘二十八&#xff1a;安全与合规性保障 电商技术揭秘二十九&#xff1a;电商法律合规浅析 电商技术揭秘三十&#xff1a;知识产权保…

C++必修:从C到C++的过渡(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. 缺省参数 1.1. 缺省参数的使用 缺省参数是声明或定义函数时为函数的参数指定…

Java基础之JVM对象内存分配机制简介

一 对象内存分配 1.1 运行时数据区域 1.2 常见java应用启动JVM参数&#xff1a; -Xss&#xff1a;每个线程的栈大小(单位kb)-Xms&#xff1a;堆的初始大小&#xff0c;默认物理内存的1/64,示例&#xff1a;-Xms:4g -Xms:10m-Xmx&#xff1a;堆的最大可用大小&#xff0c;默认物…

(Java)队列

一.概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾&#xff08;Tail/Rear&#xff09; 出队列&#xff1a;进行删…

数据的正态性检验

正态性检验 判断 pd.Series 数据是否符合正态分布&#xff0c;可以采用以下几种方法。 计算偏度 偏度&#xff08;Skewness&#xff09;是衡量数据分布对称性的统计量。如果偏度大于 0&#xff0c;则数据分布是右偏的&#xff08;正偏斜&#xff09;&#xff1b;如果偏度小于…

时尚新选择,小塔RFID技术重塑样衣管理

在时尚领域&#xff0c;样衣是创意与工艺的完美结合&#xff0c;每一件都承载着设计师的心血与期待。然而&#xff0c;当这些珍贵的样版在传统的管理体系下流转时&#xff0c;样版管理成为一个令人头疼的问题。手动记录、盘点和样板追溯成为常态&#xff0c;但这种方式容易出错…

Web前端开发之HTML_1

第一个前端程序VS Code安装VS Code 快捷键 1. 第一个前端程序 使用记事本&#xff0c;新建一个文本文档&#xff0c;重命名为Welcome.html&#xff0c;如下图&#xff1a; 用记事本打开文档&#xff0c;内容输入如下&#xff1a; <html> <head> <t…

深度学习| 注意力机制

注意力机制 为什么需要注意力机制Seq2Seq问题Transfomer Attention注意力机制分类软硬注意力注意力域 为什么需要注意力机制 这个可以从NLP的Seq2Seq问题来慢慢理解。 Seq2Seq问题 Seq2Seq&#xff08;Sequence to Sequence&#xff09;&#xff1a;早期很多模型中&#xff…

数据赋能(67)——概念:数据变现

数据变现是指通过某种方式将数据转化为实际的收益或绩效。数据变现的方式多种多样&#xff0c;可以根据不同的应用场景和业务需求进行选择和组合。 数据变现的主要方式如下&#xff1a; 数据销售与租赁 组织直接出售原始数据或经过处理、整合后的数据给需要的组织或个人。组织…

Redis分布式锁 - 基于Jedis和LUA的分布式锁

先基于单机模式&#xff0c;基于Jedis手工造轮子实现自己的分布式锁。 首先看两个命令&#xff1a; Redis 分布式锁机制&#xff0c;主要借助 setnx 和 expire 两个命令完成。 setnx命令: setnx 是 set if not exists 的简写。将 key 的值设为 value &#xff0c;当且仅当…

uniapp问题归类

最近使用uniapp中&#xff0c;遇到了一些问题&#xff0c;这边mark下。 1. 启动页变形 设置启动页的时候发现在部分android手机上启动页被拉伸了&#xff0c;最后看了下官方建议使用9.png图 生成9.png地址&#xff0c;推荐图片大小为1080x2340 uniapp推荐官方地址传送门 我…

【Linux驱动层】iTOP-RK3568学习之路(四):杂项设备驱动框架

一、杂项设备驱动简介 在 Linux 中&#xff0c;把无法归类的五花八门的设备定义成杂项设备。相较于字符设备&#xff0c;杂项设备有以下两个优点: (1)节省主设备号:杂项设备的主设备号固定为 10&#xff0c;而字符设备不管是动态分配还是静态分配设备号&#xff0c;都会消耗一…

【leetcode面试经典150题】71. 对称二叉树(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…