
本文详解 siamese 网络中嵌入向量的相似度/距离计算方法(如欧氏距离、余弦相似度),澄清 tripletmarginloss 仅用于学习判别性嵌入而非直接分类,并提供可部署的推理示例与关键注意事项。
本文详解 siamese 网络中嵌入向量的相似度/距离计算方法(如欧氏距离、余弦相似度),澄清 tripletmarginloss 仅用于学习判别性嵌入而非直接分类,并提供可部署的推理示例与关键注意事项。
Siamese 网络的核心目标是学习一个映射函数,将输入样本(如图像)编码为低维嵌入向量(embedding),使得同类样本的嵌入在特征空间中彼此靠近,异类样本则相距较远。因此,“相似度”或“距离”的计算发生在前向推理阶段,而非模型结构内部——它独立于训练时使用的损失函数(如 TripletMarginLoss),是下游任务(如检索、验证、聚类)的关键步骤。
✅ 正确的 Siamese 架构设计要点
您提供的代码存在两个关键问题,需立即修正:
-
ResNet 输出维度不匹配:
torchvision.models.resnet18(num_classes=5)会输出 5 维 logits,但 Siamese 网络需要的是固定维度的嵌入向量(如 128-D 或 512-D),而非分类 logits。应移除最后的全连接层(fc),或替换为自定义投影头(projection head):class SiameseNetwork(nn.Module): def __init__(self, embedding_dim=128): super().__init__() self.resnet = torchvision.models.resnet18(pretrained=True) # 移除原始分类头 self.resnet.fc = nn.Sequential( nn.Linear(self.resnet.fc.in_features, 512), nn.ReLU(), nn.Dropout(0.3), nn.Linear(512, embedding_dim) ) def forward_once(self, x): return self.resnet(x) # 返回 embedding 向量 (B, embedding_dim) def forward(self, anchor, positive=None, negative=None): # 支持单样本编码(推理)和三元组编码(训练) if positive is None and negative is None: return self.forward_once(anchor) else: return ( self.forward_once(anchor), self.forward_once(positive), self.forward_once(negative) ) forward()接口设计不合理:原方法强制要求三元组输入,无法用于单样本嵌入提取(如实际部署时对一张图生成 embedding)。应支持灵活调用方式(见上例)。
? 相似度与距离的计算方式(推理阶段)
训练完成后,模型输出的是归一化或未归一化的嵌入向量。常用度量方式如下(均在 CPU/GPU 上高效计算):
-
欧氏距离(Euclidean Distance):对应
TripletMarginLoss(p=2)的隐式度量,适用于学习到的嵌入空间具有几何意义的场景。from torch.nn import functional as F import torch def euclidean_distance(embed1, embed2): return torch.norm(embed1 - embed2, p=2, dim=1) # shape: (B,) # 示例:判断两张图是否同类(阈值需根据验证集调优) emb_a = model(torch.unsqueeze(img_a, 0)) # (1, D) emb_b = model(torch.unsqueeze(img_b, 0)) # (1, D) dist = euclidean_distance(emb_a, emb_b).item() is_same_class = dist -
余弦相似度(Cosine Similarity):更关注方向一致性,对嵌入模长不敏感,常配合 L2 归一化使用:
def cosine_similarity(embed1, embed2): embed1 = F.normalize(embed1, p=2, dim=1) embed2 = F.normalize(embed2, p=2, dim=1) return (embed1 * embed2).sum(dim=1) # shape: (B,), range [-1, 1] sim = cosine_similarity(emb_a, emb_b).item() # > 0.7 可视为相似
⚠️ 注意:
TripletMarginLoss本身不输出分类结果,也不具备类别预测能力。它仅驱动网络学习一个良好的嵌入空间。若需分类(如识别具体类别 ID),必须额外构建分类器(如 KNN、SVM 或在 embedding 上接线性分类头),并用带标签的分类数据集单独训练/微调。
? 实际应用流程总结
-
训练阶段:使用三元组(anchor, positive, negative)+
TripletMarginLoss学习嵌入空间; -
推理阶段:
- 对查询图像和候选图像分别提取 embedding;
- 计算二者间距离/相似度;
- 根据预设阈值(或排序 top-k)判定是否匹配;
-
分类扩展(可选):在 embedding 层后添加
nn.Linear(embed_dim, num_classes),配合CrossEntropyLoss在标注数据上微调,此时才真正实现端到端分类。
最终,Siamese 网络的价值在于无监督/弱监督下的表征学习能力——它让“比较”变得简单、鲁棒且可扩展,而距离计算就是这座桥梁最直观的刻度。











