☰
CSI自监督异常检测:相对移位检测与p_count机制解析
2026/10/24 3:23:28 网站建设 项目流程

接触CSI这篇论文,得从一个让我头疼了很久的场景说起。工业质检那边经常丢给我一堆“只有正常样本”的数据,比如上千张没有缺陷的电路板照片,却要求训练一个模型,能把后续产线上出现的脏污、划痕、异形件全数检出来。没有异常样本做监督,传统的做法多半是先重构再比对,可重构误差一降,漏检率就上来了。后来读到CSI(Contrasting Shifted Instances)这篇论文,才意识到异常检测的出路不一定要在“异常”这个词上死磕。它的思路是:把正常样本的语义结构学好,任何破坏这种结构的输入自然就成了异常。

这篇工作属于Anomaly Detection异常检测方向里自监督异常见的代表,核心贡献是提出相对移位检测(Relative Shift Detection),用对比移位实例来训练模型。如果你在做无监督异常检测、想知道自监督前置任务到底怎么选,或者只是好奇对比学习除了对比本身还能干点什么,这篇解读应该能给你不少启发。

1. 无监督异常检测的困境:没有异常样本,异常该怎么定义

1.1 传统三条路线的死穴

先聊聊大多数人在没有异常样本时习惯走的几条老路,理解它们为什么不行,你才能明白CSI为什么绕了那么大一圈。

第一条是一分类和密度估计,典型代表有OCSVM和DeepSVDD。思路很简单:把正常样本当成一个簇,在特征空间里圈一个决策边界。问题在于,高维空间里“正常”这个概念根本不是一个紧致的球。DeepSVDD就算设计得再精巧,也躲不过特征坍塌——模型把所有样本都映射到同一个小区域,它学到的是“大家都一样”,而不是“什么才叫正常”。边界一旦收紧,正常样本的微小变化也会被误判;边界一旦放松,异常就全漏进来了。

第二条是重构路线,AutoEncoder和VAE都是这个套路。核心假设是:异常的样本重构误差一定更大。但实测下来你会发现,重构模型的泛化能力非常“友好”,异常区域的纹理也能被编码器解读成某个正常组合,解码器顺手就给补全了。重构误差降下来,缺陷就被抹平了。加上异常类型千奇百怪,你根本没法用一个统一的误差阈值去覆盖所有可能性。

第三条是在预训练特征空间里做距离判断,比如kNN。这条路依赖一个现成的分类模型的特征质量,但分类预训练是从“类间区分”目标出发的,它对正常的类别边界敏感,对类内结构异常(比如一个零件装歪了)往往毫无反应。

1.2 自监督为什么成了突破口

老路走不通,大家开始把目光投向自监督学习:既然没有标签,那我就自己造标签。旋转图片、打乱拼图、遮挡patch,这些操作都能生成明确的伪标签,模型在不依赖人工标注的情况下就能学会理解图像的空间结构和上下文关系。

CSI这篇论文把无监督异常检测重新定义成一个“结构变化探测”问题。模型不需要见过异常,它只需要对正常样本的组成规则足够敏感,破坏规则就报警。这有点像你每天出门前摸一下钥匙串——你不见得能定义什么是“被偷”,但当重量不对、钥匙排列方向不对、拉链卡壳的时候,你立刻就会警觉。CSI想做的,就是把这种“不对劲”的感觉工程化。

1.3 “直接拿分类置信度当异常”为什么行不通

这里有个特别容易踩的坑:前面提到旋转、拼图这类前置任务能训练特征,那是不是训练完一个分类器,直接把置信度拿来判异常就行了?我之前也这么干过,效果非常不稳定。原因在于,分类器只需要区分已知的几种变换类型,一个从未见过的异常输入会被强行归类到某一个已知类里,模型照样给出很高的置信度。这个置信度根本不能代表异常程度,它只代表了“模型有多自信这个样本属于某一个已知变换类型”。

2. 相对移位检测的任务设计:旋转、拼图与shift-patch的混合密码

2.1 变换集合的选择逻辑

CSI在训练时用了一组图像变换来生成移位实例,主要包括旋转(90度、180度、270度)、Jigsaw拼图打乱,以及一个很关键的操作:shift-patch,也就是把输入图像切成网格,随机移动其中若干个patch。

选择这些变换是有讲究的。旋转和Jigsaw能提供明确的伪标签,训练样本可以无限生成,不需要人工标注。更重要的是,这两类变换都要求模型理解图像的空间上下文关系。一个正常样本经过旋转后,模型依然能认出其中的语义部件;而一个结构错乱的异常样本,就算不做变换,模型也很难找到正常的部件位置关系。简单说,这些变换是在逼模型去学习“什么状态下,这个结构是合理的”。

2.2 shift-patch:把空间结构破坏显式化

shift-patch是CSI区别于其他自监督方法的杀手锏。拿一张图像,按网格切分,正常状态下各个patch的相对位置是固定的。现在把若干patch移动到别的位置,这种移动就相当于对图像内部的结构做了一次局部破坏。模型需要判断哪些patch被移动了,并预测移动的方向或目标位置。

这个设计特别妙的地方在于:正常数据在训练时已经把这些patch移动规则学得很透,到了推理阶段,只要输入的patch位置关系符合已知规则,模型就认为没问题;一旦输入是异常样本,patch之间的依赖关系根本不在模型掌握的正常分布里,模型对“有没有被移动”的判断就会变得非常不稳定。于是,这个“不确定性”本身就成为了一个异常信号。

2.3 两阶段训练流程:预训练与微调之间差了些什么

CSI的训练分成两个阶段。

第一阶段是特征预训练:用对比学习框架,让同一语义下的不同移位实例在特征空间里尽量靠近,不同语义的实例尽量分开。这一阶段不涉及异常检测任务,目标纯粹是得到一个对正常数据语义结构足够敏感的表示空间。

第二阶段是在固定的encoder之上训练一个投影头,用于预测shift type。这个头收集每个patch对旋转、拼图、shift-patch等变换类型的预测概率,最终汇总成p_count统计量。推理时,再对p_count做绝对偏差判定,偏离正常范围就判为异常。

如果你第一次看论文,可能会疑惑:既然第一阶段已经把所有变换都学了,第二阶段为什么还要单独训练一个投影头?我的理解是,第一阶段学到的特征是好特征,但没有定向服务于“移位检测”这个任务。第二阶段的投影头本质上是在特征之上加了一个很轻量级的决策器,让模型明确针对“空间结构是否被破坏”这件事做输出。

2.4 推理逻辑:为什么p_count能给出最终判决

相对移位检测的推理逻辑可以这样理解:把一张图看成一组patch的联合分布。正常样本中,某个patch出现在某个位置并与相邻patch形成固定的空间关系,模型对“这个patch是否被动过”的判断非常笃定。异常样本则不然,它的空间关系是散乱的,模型在面对“这个patch该属于哪个位置”时,会出现系统性的偏好或混乱,导致预测概率在各类别之间失衡。

把这种失衡量化出来,就是所谓的p_count。正常样本的p_count应该稳定地集中在0附近,因为模型对各个变换类型的判断概率相对均匀;异常样本的p_count会明显偏移,要么偏向某一类变换,要么剧烈震荡。而绝对偏差越大,异常分数越高。

3. 损失函数逐项拆解:CE、Triplet与Soft-Nearest-Neighbor各在算什么

3.1 CE_loss:多分类shift type预测如何建立伪标签

CE_loss负责最基础的监督信号。模型的投影头要对输入的shift type做多分类,类别包括旋转角度、Jigsaw排列方式以及shift-patch的移动模式。这些类别都是自监督生成的伪标签,模型通过最小化交叉熵,学会区分不同的变换类型。

这一段的作用相当于给模型划定了一个坐标系:什么样的变换对应什么样的输出模式。模型对已知变换类型的区分能力越强,它对正常样本的“规则记忆”就越牢固。后面用p_count做异常判定时,这个分类能力会转化成“模型对已知规则结构的把握程度”。

3.2 Triplet loss:以shift为锚把特征空间拉开

单纯用CE训练,特征空间可能会偏向于判别决策边界,而不是几何结构。CSI引入Triplet loss,把同一种shift下的不同实例作为正样本对,把不同shift下的实例作为负样本对,强制模型在特征层面做对比。

这么做的好处是,特征空间被拉成了更清晰的结构:同一种移位模式在一个紧凑区域里,不同移位模式被明显分开。异常样本落在哪里?它大概率会落在一个“不属于任何已知shift簇”的中间地带,这正好为后续统计p_count的偏置提供了依据。

3.3 Soft-Nearest-Neighbor loss与Memory Bank的动态存储

SNN loss是Triplet loss的软版本,它的实现依赖一个Memory Bank,也就是一个存了近期所有样本特征表示的动态缓存。模型计算当前样本的特征时,会从Memory Bank里取出最相似的若干个样本,要求当前样本与这些近邻的距离尽可能小。

这个设计的本质是全局对比学习:Triplet loss看的是当前batch内部的关系,SNN loss把视野放宽到整个训练集的历史特征。实验里去掉SNN损失后,特征平滑度明显下降,异常检测的AUROC也会有可见损失。Memory Bank的存储容量不用太大,但更新频率要稳定,否则特征不连续,训练会震荡。

3.4 异常分数p_count的计分机制

最终异常分数的计算逻辑并不复杂。推理时,把输入图像按训练时的网格划分,得到N个patch。对每个patch,投影头输出它在所有shift type上的预测概率。把每个patch最可能的shift type对应的概率汇总起来,就是p_count。

简化表达就是:p_count = (1/N) * 求和_i p_i,c,其中i是patch序号,c是该patch响应最强的shift type。正常样本的p_count会稳定在0附近,因为它没有被哪一种移位类型明显吸引;异常样本则会对某个shift type形成系统性偏好,p_count出现明显偏移。论文里的异常分数取的是p_count与正常p_count期望的绝对偏差,复现时直接用绝对p_count也能达到近似效果。

4. 评测协议与消融实验:CSI到底比谁强、强在哪

4.1 评估协议与基线对比

CSI的评估是在常见公开数据集上进的,采用一类异常检测协议。所谓一类异常检测,就是在训练阶段只使用正常类别样本,测试时把所有样本(包括正常和异常)都丢进去,看模型能否把异常挑出来。评估指标用AUROC,越接近1越好。

对比的方法大致分成三类:传统一分类方法(OCSVM、DeepSVDD)、几何变换方法(GT和Rot)以及CSI本身。结果是CSI在多数协议下能稳定领先,尤其在类别语义比较丰富的数据集上,优势会进一步放大。

直接对比所有技术细节没有必要,但有几个结论值得记住:CSI不是靠堆参数量取胜,而是靠“相对移位检测”这个任务定义取胜。Rot等方法也做几何变换,但缺少shift-patch和p_count这种显式统计机制,所以对局部结构异常的反应不够灵敏。

4.2 消融实验:四个损失分量各自的价值

消融实验是我读这篇论文时收获最大的一部分。逐个去掉不同组件,观察AUROC的下降幅度,可以发现以下几点:

  • 去掉Triplet loss,性能会掉一截。这说明CE只能提供分类信号,无法把特征空间塑造成利于距离判断的结构。
  • 去掉SNN loss和Memory Bank,性能再掉一截。全局特征平滑性对异常检测很重要,局部batch内的对比不足以保证整个正常分布的紧凑性。
  • 去掉shift-patch,模型对局部结构异常的反应灵敏度骤降。旋转和Jigsaw能捕获全局语义破坏,但细粒度的局部移位必须靠shift-patch来补位。

这个消融结果给我的启发是,如果你想在自己的场景里做减法,第一个能砍的是Memory Bank的容量,而不是shift-patch。后者是整个方法对“局部异常”敏感的关键。

4.3 两个让我意外的反直觉结论

第一个是,CE分类头训练的模型,最终并不直接用分类置信度做异常评分,而是改用p_count的绝对偏差。我一开始不太理解,后来复现时才发现,分类置信度对异常输入存在严重的校准问题,异常样本也可能被高置信度地归到某一类。p_count统计的是一种偏移方向的一致性,只有系统性偏移才能被捕捉到,这比单点置信度稳定得多。

第二个是,自监督前置任务本身并不直接解决异常检测问题。你在ImageNet上预训练一个分类模型,特征也很好,但拿去做异常检测,效果通常一般。CSI真正值钱的地方在于它重新设定了“什么信号可以当作异常信号”——不是特征有多好,而是特征对结构破坏有多敏感。

5. 复现要点与落地心得:从论文到自己的数据

5.1 训练配置与超参数选择

复现CSI不需要特别大的算力。backbone用ResNet-18或更轻的变体在中小数据集上就能跑出不错的结果。优化器一般用Adam或LARS,batch size建议保持在256左右,太小会影响对比学习的效果。Memory Bank的大小可以设为训练集样本数的若干倍,论文里常见设置是数千到上万个特征向量。

在动手之前,建议先把评估协议确定下来:训练集只保留正常类别,测试集混入异常类别,统一用AUROC做度量。别小看这一步,很多人复现结果不稳定,问题就出在异常样本混进了训练集,或者评估指标用了准确率这种对类别不平衡极不友好的度量。

5.2 三个让我真正踩过的坑

第一个坑是Memory Bank的更新顺序。如果每个epoch结束之后再统一更新缓存,特征分布会有明显跳变,训练震荡非常严重。我后来改成沿用论文里的在线更新方式,每个batch计算完特征后立即对缓存做增量替换,稳定性好了很多。

第二个坑是shift-patch的网格尺寸。在32x32的小图数据集上,4x4网格比较合理;分辨率更高的图像如果还傻乎乎用4x4,patch尺寸太大,局部细微异常会被完全稀释。我自己在262x262左右的工业图像上做过测试,单一网格效果非常有限,需要做多尺度网格组合才能捕获小缺陷。这也解释了为什么后来的自监督异常检测工作会沿多尺度窗口方向继续演进。

第三个坑是p_count的统计细节。论文实现里用的是softmax概率而不是logits,我一开始为了让数值更好看,换成logits做统计,结果异常分数分布变得很极端,阈值极难选。改回softmax之后,p_count的分布才变得平滑可调。另外,输入图片做推理时要保持和训练一致的前处理流程,尤其是标准化参数和裁剪尺寸,一不一致对结果影响远大于模型本身。

5.3 从CSI往后看:异常检测的重心为什么移向了patch与多尺度

CSI最大的贡献,是证明了“结构破坏检测”这条路的可行性。但它也留下了一个明显的边界:它对全局语义类别异常很有效,对于工业数据里常见的局部小缺陷,单尺度网格的shift-patch还是太粗糙。后来的工作大多围绕这个边界展开,有的做多尺度窗口,有的在block级别上做特征距离,有的结合分区特征密度估计。你在落地时如果遇到小缺陷漏检,不要盲目调阈值,先把输入分辨率提上来,再做多尺度网格组合,大概率比硬调模型结构更划算。

我自己复现完CSI之后的实际体会是,这篇论文不是那种“秀肌肉”的SOTA机器,它更像一个思路转换器——把异常检测从“建一个正常的封闭边界”拉回到“学习正常数据的结构与规则”上。如果你也想在自己的数据上跑一圈,我建议你先记住一个点:异常检测模型的评价,不看你训练loss降得多快,而看你的特征对结构破坏有多敏感。把这一点想透了,读CSI的时间就花得值了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询