资讯详情

孪生网络与One-shot图像识别:从度量学习到少样本分类实践

📅 2026/10/16 12:49:00 | 华诺云谱 👁 阅读
孪生网络与One-shot图像识别:从度量学习到少样本分类实践
《Siamese Neural Networks for One-shot Image Recognition》这篇论文是我今年读过最常拿出来回味的一篇。第一次听到“One-shot Learning”时我第一反应是“一张图就学会一个新类这怕不是玄学”。但读完孪生网络的方案后发现它的思路极其朴素与其让模型记住一万个类别的绝对标签不如教会它比较两张图片“像不像”。这个视角转换直接改变了我后续做图像小样本任务时的建模方式。这篇文章也适合那些刚接触少样本学习、或者想了解度量学习为什么能解决“类别数不固定”问题的同学读完后你不仅能看懂论文的公式和结构还能大致知道如何从头复现一个能跑的孪生网络。1. 先搞清楚One-shot 图像识别到底难在哪1.1 传统分类模型的死穴类别一变模型就得重训我们平时见到的图像分类模型本质上是学一个从图像到固定标签集合的映射假设训练集有1000个类别模型最后一层就是1000个输出用softmax算概率训练目标是让正确类别的概率最大。这种做法在类别数量固定、每类样本充足时效果不错但如果第二天新来了一个类只有一张标注图传统模型根本没法直接用你总不能把1000维的输出改成1001维然后只拿一张图去训练整个网络大概率会过拟合到连“旋转个角度”都认不出来。One-shot Learning单样本学习想解决的就是“每个新类只给你一个参考样本也能把属于这个类的图片认出来”这个极端但现实的问题。我在实际项目里遇到过类似的场景某个工业视觉检测任务新产线的零件只有少数几张良品图不可能收集几千张再训练分类器。这时候“比较相似度”的思路就比“训练分类器”思路靠谱得多。1.2 论文为什么选“图像识别”作为切入点图像识别是度量学习最自然的试验场原因有三个一是图像天然可以用卷积网络提取特征不需要额外设计特征工程二是成对比较在图像上有直观意义比如人脸验证、签名验证、物品匹配核心都是“这两张图是不是同一个东西”三是公开数据集较多方便在不同任务之间迁移验证。论文选择的手写字符数据集恰好具备“类多、每类样本少、跨字母表差异大”的特点相比自然图像它可控性强又不会太简单——同样是“a”这个字符不同书写风格、不同语言体系下的笔画差异非常大机器需要真正学到“结构相似性”而不是像素级相似。1.3 为什么不直接微调一个预训练模型你可能想问现在有那么多预训练模型拿一张新类图片去微调最后的分类层不就行了问题是预训练模型通常也是在固定类别上训练的它的高层特征天然偏向训练集的类别区分新类和原类别差异较大时特征不一定能迁移好。而且只有一两张样本时微调全连接层很容易学到“只认这一张图”的捷径比如背景、光照、边框等无关信息。孪生网络绕开了这个困境它不直接输出“这张图属于谁”而是输出“两张图有多像”。训练时喂的是“同类/非同类”的成对样本这样模型学到的是一个普适的相似度函数而不是某个具体类别的分类边界。上线时遇到新类别不需要重训模型只需要把新类的参考图和待测图分别过网络拿距离做判断即可。2. 孪生网络的核心设计思路、结构与损失函数2.1 名字的由来两个分支共享同一套权重Siamese在英文里是“暹罗双胞胎”的意思网络结构也像一对连体婴儿左右两个分支结构完全一样并且共享全部权重。输入分别是一对图片比如 x1 和 x2各自经过同样的卷积和全连接层最后得到两个特征向量 h(x1) 和 h(x2)。这两个向量的距离就代表两张输入图的“语义相似度”。为什么共享权重而不是各用一套网络核心原因有两点第一如果左右各一套那么同样的物体在左边和右边会被映射到不同的空间距离比较就没有一致基准了第二共享权重直接让参数量减半也强制模型对同一个类别的不同视角提取相同特征这本身就起到了正则化作用。我实现的时候如果脑子一热把权重复制成两份再训练效果经常变差原因就是梯度会分叉特征空间容易不一致。2.2 从“距离”到“概率”逻辑回归式损失孪生网络训练时构造的训练样本是一对一对的正样本对表示两张图来自同一类别标签 y1负样本对表示两张图来自不同类别标签 y0。特征向量之间的距离可以用多种方式衡量论文采用的是带权重的L1距离也就是对两个特征向量的逐元素差取绝对值再经过一个全连接层映射成一个标量。为了让输出表示“两张图属于同一类别的概率”标量会被喂进一个sigmoid函数得到 p ∈ (0,1)然后用二元交叉熵作为损失函数。损失表达式简化下来就是L - y * log(p) - (1 - y) * log(1 - p)这个形式和普通二分类问题一模一样只是输入换成了图片对。你可能会发现它没有显式地拉近正样本对之间的距离或推远负样本对之间的距离但交叉熵的梯度会自动起到这个作用p 要接近1模型就得让同类的特征距离尽量小p 要接近0就得让不同类的特征距离尽量大。相比单纯用欧氏距离加margin的对比损失论文这种“先映射成概率再分类”的写法在工程上更直观也更容易在框架里套用现成的损失函数。2.3 为什么这个结构能“学会新类别”关键在于训练过程积累的不是某个类别的模板而是“什么样的特征差异算同类”的度量规则。例如在字符数据集上训练时模型看到大量不同人手写的“A”“B”“C”的正负样本对慢慢会知道笔画拐角、端点结构、内部空洞这些高阶属性才是区分字符的核心而粗细、倾斜、轻微位移之类的不重要。到了测试阶段新类别可能是一个从未见过的字符我们只需提供1张参考图然后把参考图和新输入图一起喂给网络。因为网络输出的是相似度概率不是新类别的分类置信度所以它天然可以处理任意数量的新类别。这也是后来一大批少样本学习工作的地基思想与其给模型“看的东西”不如给模型“比较的方法”。3. 论文里的网络结构与训练细节拆解3.1 一个经典的卷积特征提取器论文用的子网络不算深对比今天动不动几十层的ResNet它就像一个“小作坊模型”。具体结构大致可以拆成两块卷积特征提取部分和全连接映射部分。卷积部分依次是第一层64个10x10的卷积核步长1激活函数ReLU接2x2最大化池化第二层128个7x7卷积核ReLU接2x2最大化池化第三层128个4x4卷积核ReLU接2x2最大化池化第四层256个4x4卷积核ReLU接2x2最大化池化。卷积输出经过展平后接两层4096维的全连接层最后输出一个特征向量。这个向量再经过距离层和sigmoid得到最终相似度。这个结构放在今天看没什么惊艳之处但有一个容易被忽略的设计卷积核从大到小、通道数从少到多这是当时处理小尺寸图像的常见做法。大感受野先捕捉整体轮廓小感受野再细化局部特征。如果你复现时想把输入图片改成96x96的自然图像这个结构需要整体调整不能直接照抄。3.2 输入预处理随机扰动相当于深度学习“免费午餐”论文特别强调了对输入图像的预处理随机位移、缩放、旋转有时候还会加一点图像畸变。这些操作不是为了展示数据增强技巧而是因为孪生网络很容易钻进一个死胡同——只靠像素位置对齐来判断相似度。想象一下两张相同字符的图片如果一张比另一张整体偏移了3个像素网络如果只在数值上比较原始像素距离会变大从而误判为不同类。加入随机扰动后模型被迫学习“无论图形出现在画面哪个位置特征都该一致”从而对平移、缩放、旋转具有一定不变性。我复现时验证过如果不做这些扰动训练集准确率能很快到90%以上但测试集的一对一判断惨不忍睹这就是典型的“记住了位置没记住形状”。后来把随机旋转和缩放加上测试准确率直接上涨十多个点。数据增强在度量学习里不是可选项是必选项。3.3 训练样本对的构造与均衡训练孪生网络最核心的工程环节是生成样本对。每次训练时随机取两个类别然后从这两个类别里各取若干图片组成正负对。论文里的做法是固定比例生成正负样本对通常让一轮中正负样本数量大致相等避免模型把所有样本都预测成负类也能得到很低的损失。如果你自己造数据我建议不要简单地从全量数据里随机配对因为随机配对会导致绝大多数样本对是负样本对类别多的时候负样本对可能占比超过99%模型会严重偏向输出“不同类”正样本对的召回率会很低。一个简单的平衡技巧是每次迭代先随机挑B个类别再从每个类别里挑K张图然后用这些图组内生成正对、跨类别生成负对把正负比例控制在1:1左右。3.4 优化策略小学习率与多阶段衰减论文使用的优化器是带动量的SGD学习率初始值在1e-3量级训练过程中逐步衰减。这个经验在复现时同样管用孪生网络相比普通分类网络同一个batch里包含两个输入梯度更新的方差更大学习率太大会导致损失震荡。我自己的经验是先用较大的学习率跑20个epoch观察损失有没有持续下降如果损失波动很剧烈就把学习率除以10如果训练集损失降到很低但验证集损失不降就加大数据增强强度而不是直接堆epoch。孪生网络很容易在训练集上做到“完美对比”但真正考验的是对新类别对的泛化能力所以设置独立的验证集很关键。4. 实验设置与结果解读Omniglot和跨域迁移4.1 Omniglot数据集为少样本而生的“字符动物园”论文的主实验是在Omniglot数据集上做的。这个数据集包含来自50种不同书写体系的1623类手写字符每个类别只有20个样本非常符合“每类样本极少”的设定。它经常被看作是图像领域的小型ImageNet原因就在这里类别多、类间差异大、类内又有很大的书写风格变化。如果只看数据规模Omniglot比MNIST小得多但它的难度并不低有些字符写起来极其相似只是朝向或一个小笔画不同。模型要区分它们必须捕捉到结构层级的差异而不是简单的像素差异。4.2 训练和测试类别的划分测试类必须“整个没见过”论文在划分数据时有一个很重要的设计训练时用一部分字母表字符体系的类别作为训练集测试时用另一部分字母表里的类别作为测试集。这意味着测试阶段面对的是完全新的字符形状模型在训练时连一个该体系的字符都没有见过。这比“同一批类别里抽一部分图做测试”要难得多也更符合实际应用里“新类涌现”的场景。这种划分方式直接影响了后来少样本学习社区的标准做法meta-training阶段和meta-testing阶段的类别集合必须不相交。我见过不少复现出错的人图省事把所有类别混在一起随机划分部分图片做训练和测试最后准确率高得离谱却完全没验证到模型对新类别的泛化能力。严格来说这属于测试集污染。4.3 One-shot和Few-shot的评测结果论文报告的one-shot任务准确率大约在90%左右Omniglot的20-way分类few-shot比如5-shot会更高一些。数字本身放到今天不算惊艳但关键信息是在完全没见过的新字符类别上仅凭1张参考图就能达到这个水平已经证明了孪生网络学到的相似度度量具备跨类别的迁移能力。后续很多工作把Omniglot的one-shot准确率做到了99%以上但那是建立在更复杂的原型网络、匹配网络或者更大规模训练上。作为奠基性的工作论文的价值不在于刷高一个数据集而在于提供了一个即便用简单卷积网络也能跑通的baseline让大家有共同的起点去改进。4.4 迁移到MNIST证明学到的是“比较能力”不是“字符识别能力”论文还做了一个跨数据域实验用Omniglot训练好的孪生网络直接去测试MNIST手写数字的one-shot分类。这里的迁移跨度在于Omniglot是字符体系不一的符号MNIST是阿拉伯数字两者笔画分布差异很大而且MNIST的图像风格偏像素边缘。如果网络学到的是“识别字符种类”而不是“比较形状相似性”跨域效果会崩掉。实际结果表明迁移效果虽然不如在Omniglot上那么高但仍然显著优于随机猜测这说明网络内部提取到的特征具有一定通用性至少能够把数字形状间的相似关系刻画出来。这种跨域验证的思路也提醒我度量模型的泛化能力不能只看同分布测试集必须加一个分布外测试哪怕效果没那么好也能知道模型的底线在哪。5. 孪生网络的影响与潜在局限5.1 从学术paper到工业应用孪生思想无处不在孪生网络后来的影响力远远超过论文本身。人脸验证系统里最常见的做法就是训练一个孪生网络或者它的变体比较两张人脸是否为同一人目标跟踪里常把当前帧的目标模板和候选区域做相似度匹配甚至推荐系统里也可以用孪生结构比较用户行为序列和商品描述的语义相似度。可以说凡是“判断两个东西是否相似/匹配”的任务都可能用上孪生网络。它在少样本学习领域更是直接催生了一条明确的技术路线学习一个嵌入函数使得同类样本在嵌入空间中聚集、异类样本分散。后来的连体变体、原型网络、关系网络本质上都是在这个思路上换不同的相似度计算方式和训练策略。5.2 局限一特征空间的表达能力有限孪生网络用整张图像的特征向量来代表输入这意味着所有信息被压成一个固定长度的向量如果类别非常复杂比如每个类别有多个不同视角、不同形态一个全局向量很难容纳所有可能的变化。举个例子一个类别如果是“各种不同形状的杯子”有的带把、有的不带把有的高、有的矮靠一个512维特征向量去聚类就会很吃力。论文在字符数据上能奏效是因为字符类别内部的形状变化相对受控多数情况下同一个字符的写法再变骨架结构仍然类似。5.3 局限二样本对的选择直接影响效果训练孪生网络时正样本对和负样本对的构成对最终性能影响极大。如果负样本对选的都是“难度极高”的相似类模型会被迫学得非常精细但可能在小扰动下变得脆弱如果全是“难度极低”的类别模型学到的是粗粒度区分能力面对新类的相似判对时很容易翻车。实践中的做法是引入“难样本挖掘”每轮训练动态寻找那些距离较近但标签不同的负样本对或者距离较远但标签相同的正样本对让模型持续挑战分布边缘。这是论文没有展开但后续很多工作重点研究的内容你在复现时要特别注意。5.4 局限三类别规模变大时的匹配效率孪生网络本身只是“单次比较”模块要想在大量类别中搜索最相似类别需要把所有候选类的参考图片逐对和待测图片计算距离。如果候选类有1万个那么预测一张图片的类别需要做1万次网络前向可以预先缓存参考特征但每次仍要计算待测特征并逐一比较。这个计算量在离线场景可以接受但在实时场景需要配合近似最近邻搜索或索引加速。所以在工程落地上更常见的做法是先用孪生网络提取所有已知类的原型特征把它们存成特征向量库再对待测图片做一次特征提取然后用余弦距离或欧氏距离做向量检索。这样一来“比较”只发生在向量层面速度就会快很多。6. 从头复现的实操要点与踩坑记录6.1 数据准备从公开数据集构造样本对复现论文最简单的方式是使用公开的Omniglot数据集你可以直接下载预处理好像素值的版本。如果没有也可以用MNIST代替但效果会略低因为数字类别只有10类且形态相近少样本的挑战性不够。构造样本对的代码逻辑不复杂加载全部图像按类别分组每个epoch开始前随机选择一半类别作为正样本来源另一半作为负样本来源。正样本对是从同一个类别的20张图片中随机取两张负样本对是从两个不同类别中各随机取一张。每次构造的batch里正负各占一半。这里有一个容易踩的坑把验证集和测试集的类别也放进了训练时的负样本池。严格来说训练过程中模型只能接触训练类别的图片不能接触测试类别的图片否则会出现信息泄露测试准确率会虚高。6.2 网络搭建的伪代码示例下面是我习惯用的一个简化版孪生网络PyTorch风格伪代码保留了论文的核心结构只在输入尺寸上做了微调import torch import torch.nn as nn import torch.nn.functional as F class BaseNet(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(1, 64, kernel_size10) self.conv2 nn.Conv2d(64, 128, kernel_size7) self.conv3 nn.Conv2d(128, 128, kernel_size4) self.conv4 nn.Conv2d(128, 256, kernel_size4) self.fc1 nn.Linear(256 * 4 * 4, 4096) self.fc2 nn.Linear(4096, 4096) def forward(self, x): x F.relu(self.conv1(x)) x F.max_pool2d(x, 2) x F.relu(self.conv2(x)) x F.max_pool2d(x, 2) x F.relu(self.conv3(x)) x F.max_pool2d(x, 2) x F.relu(self.conv4(x)) x F.max_pool2d(x, 2) x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return x class SiameseNet(nn.Module): def __init__(self): super().__init__() self.base_net BaseNet() self.fc_distance nn.Linear(4096, 1) def forward(self, x1, x2): h1 self.base_net(x1) h2 self.base_net(x2) # 带权重的L1距离 diff torch.abs(h1 - h2) out self.fc_distance(diff) return torch.sigmoid(out)这里有个细节全连接层输入维度要根据你输入图片尺寸计算。论文原始输入是105x105像素经过4次2倍池化后尺寸变成6x6乘以256个通道我这里简化为4x4实际跑的时候要打印一下shape。如果你想加快速度可以适当降低全连接层宽度比如4096改成512效果不会差太多但训练和省内存会友好很多。6.3 训练时最容易遇到的四个问题第一个问题是不收敛。我遇到过损失一直停在0.69左右的情况sigmoid输出接近0.5说明模型什么都没学到。原因大多是学习率太大或者正负样本对构造混乱。把学习率调到0.0001并打印batch内部正负标签统计确保不是全正或全负问题就能解决。第二个问题是严重过拟合。训练集损失很低但验证集one-shot准确率不到50%。解决办法不是加dropout而是加数据增强。孪生网络需要平移、缩放、旋转这些扰动来逼着模型学会形状单纯随机翻转在字符数据上帮助有限因为翻转会把某些字符变成完全不同的符号。第三个问题是测试阶段度量方式没对齐。训练时我们用带权重的L1距离也就是两个特征向量逐元素相减再全连接映射最后sigmoid。测试时如果直接使用普通的欧氏距离两个分数分布不在一起会导致人为设定的阈值失效。建议测试时也走同样的网络前向直接拿sigmoid概率做判断。第四个问题是样本对构造太慢。很多人用多重循环生成样本对数据集一大就卡到不行。更好的做法是把所有图像转成一个Nx1xHxW的Tensor然后预先建立类别到索引的映射表通过随机整数索引快速构造对组全程用Tensor索引完成不解Python循环。这个优化能让训练速度快两倍以上。6.4 如何评估一个one-shot模型One-shot评估最常见的指标是N-way accuracy。具体做法是从测试类别里随机抽取N个类别每个类别随机选1张参考图再随机抽1张查询图查询图属于这N类中的某一类让模型计算查询图和N张参考图的相似度最终取相似度最高的参考图类别作为预测。重复几百或上千次统计准确率。N通常取5、10或20。N越小任务越简单论文里的核心结果常报20-way也就是相当于20选1概率基线为5%。评估时要注意每次抽到的类别集可能不同这会带来方差因此要重复足够多次并用固定随机种子。如果要求更公平的对比建议把N-way评估的类别组合固定几组比如随机生成100个不同的20-way任务所有模型都在同一组任务上测试避免某次抽到简单类别导致虚高。这个细节很多复现笔记都没提但我实际测试后发现不同随机种子导致的准确率波动能达到3%左右。7. 对初学者的几个建议和扩展思路如果你是第一次读这篇论文建议不要一上来就啃公式先动手跑一个最小例子。用MNIST或者Omniglot的子集把网络缩小到几层训练100个batch先感受一下正负样本对的训练过程。等模型能稳定输出相似度后再慢慢加数据增强、换更复杂的结构、调整损失函数。这一步能帮你把抽象的“度量学习”变成具体的“两个数字之间的距离”。读完之后如果还想深入可以顺着三个方向扩展第一把固定相似度函数换成可学习的比较网络比如关系网络第二把特征提取器换成现代卷积网络如ResNet通常是直接用预训练权重做骨干网络再微调距离层第三把交叉熵换成ArcFace、CosFace这类角度损失它们在类别内紧凑性上往往比普通二分类效果好很多。最后分享一个我踩坑后养成的习惯论文里的结构图和公式描述的是“理想实验条件”复现时不一定要100%照搬。比如原始网络参数量很大在个人显卡上跑得很慢完全可以用32个卷积核代替64个卷积核做验证只要保证核心训练方式不变结果依然能验证思路是否正确。真正重要的不是复现出一模一样的数字而是理解这个网络为什么能泛化到没见过的类别上。深刻理解这一点之后你再去看如今各种少样本学习、对比学习的方法都会觉得它们像是这棵老树上长出的新枝。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑