# SiameseNetwork
# 1.孪生神经网络
在深度学习领域,神经网络取得了成功。但普通的神经网络模型的训练需要大量的数据,对于一些数据有限的场景,如人脸验证,签字验证,必须考虑其他方法。
Siamese
古语表示瞿罗
,即现在的泰国,如Siamese cat
,之所以Siamese
表示孪生,是因为19世纪瞿罗
出了一对连体双胞胎,在美国玲玲马戏团
做演出比较出名,因此提起Siamese
即表示孪生的意思。1 (opens new window)
孪生神经网络Siamese Network
,如其名字孪生Siamese
的意思即存在连体,连体即彼此共享一部分。孪生神经网络的结构也包括两个子网络,两个子网络之间共享权重。
图片来自于1
如上图,两个网络是同一个并共享权重,当两个子网络不共享权重时,通常定义为伪孪生神经网络
。
图片来自于1
从上面的图中可以看出来,孪生神经网络有两个输入,input1
和input2
,因此孪生神经网络常用来通过比较两个输入特征向量的距离来衡量两个输入的相似度。早在1993
年的NIPS
上Yann Lecun
就发表了使用孪生神经网络做签名验证的论文。现在的人脸识别应用也有基于孪生神经来做的。
孪生神经网络的优点,对于类别不平衡问题更鲁棒,更易于做集成学习(Ensemble Learning),可以从语义相似性上学习来估测两个输入的距离。孪生神经网络的缺点,由于有两个输入,两个子网,其训练相对于常规网络运算量更大,需要的时间更长。输出的结果不是概率,孪生神经网络时成对的输入,其输出是两个类间的距离而不是概率。
# 2.孪生神经网络的损失函数
由与孪生神经网络是计算的两个输入的相似度,距离,而不是对输入做分类,因此交叉商损失函数
不适用于此种场景,孪生神经网络的
常用的损失函数有Triplet Loss
和Contrastive Loss
。
# 2.1 Triplet Loss
Triplet Loss
三元组损失函数,其应用见谷歌2015
年发表在CVPR
上的做人脸验证的论文facenet
。该损失函数定义一个三元组作为输入,分别是Anchor
,再随机选取一个和Anchor
属于同一类的样本作为正样本Triplet Loss
的目的是通过训练,使得同种类别的距离更近,不通类别的距离更大,即拉近anchor与positive推远anchor和negative,如下图:
通过这种相似度比较式的学习,模型不仅与同类别更像,还学会了与不同类别增大区分度的信息。通常定义一个Anchor
距离Negative
的距离比距离Positive
大
定义为:
# 2.2 Contrastive Loss
衡量相似度的另一常用函数是Yann Lecun
在2005
年的一篇论文Dimensionality Reduction by Learning an Invariant Mapping (opens new window)中使用的Contrastive Loss
。
Contrastive Loss
的输入是一对样本,基于相似的一对对象特征距离应该更小,不相似的一对对象特征距离应该较大来计算。从数据中选一对样本Contrastive Loss
可表示为:
Y
表示 是否匹配,匹配为1
不匹配为0
m
是设置的安全距离
,当 的距离小于 时,Contrasive Loss
将变成0
,这使得 与 相似而不是相同,能保证算法的泛化能力
# 3.动手实现一个孪生网络
# 3.1 网络结构
这里使用Contrasive Loss
定义一个孪生神经网络,网络结构如图:
这里上下两个网络使用同一个网络来实现,对于两个输入,每一步推理使用相同的权重forward
两次,然后计算损失函数更新权重,这里并没有定义两个网络。为了简化训练,自定义了比较小的网络
class SiameseNetwork(nn.Module):
"""Custom Siamese Network
"""
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn = nn.Sequential(
nn.Conv2d(1, 128, kernel_size=5, stride=3, padding=2), # 10
nn.ReLU(inplace=True),
nn.LocalResponseNorm(5, alpha=0.001, beta=.75, k=2), # TODO
nn.MaxPool2d(4, stride=2), # 4
nn.Dropout2d(p=.5),
) # 12544
self.fc = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(inplace=True),
nn.Dropout2d(p=0.5),
nn.Linear(512, 128),
nn.ReLU(inplace=True),
nn.Linear(128, 2)
)
def forward_once(self, x):
y = self.cnn(x)
y = y.view(y.size()[0], -1)
y = self.fc(y)
return y
def forward(self, x1, x2):
y1 = self.forward_once(x1)
y2 = self.forward_once(x2)
return y1, y2
# 3.2 损失函数
损失函数使用的是前述的Contrastive Loss
,其定义为:
class ContrastiveLoss(torch.nn.Module):
def __init__(self, margin):
super(ContrastiveLoss, self).__init__()
self.margin = margin
def forward(self, x1, x2, y):
dist = F.pairwise_distance(x1, x2)
total_loss = (1-y) * torch.pow(dist, 2) + \
y * torch.pow(torch.clamp_min_(self.margin - dist, 0), 2)
loss = torch.mean(total_loss)
return loss
# 3.3 数据
这里使用的是基于MNIST
数据集随机选取的1000
张图像然后生成了8000
对作为输入来训练的,测试时输入两张手写字图片输出其相似度。
# 3.4 训练结果
训练了20
个epoch
,损失函数值的变化趋势如下图:
由于使用的batch_size
较小,迭代的次数较少,可以看到损失函数没有很好的收敛。且打开训练数据看了下自己生成的train.csv
中的图像对,绝大部分label
都是0
,存在严重的数据不平衡问题,需要改进。在测试数据上的输出,对于有些输入可以比较好的衡量其相似度。
Predicted Distance: 0.0020178589038550854
Actual Label: Different Signature
Predicted Distance: 0.0002805054828058928
Actual Label: Same Signature
Predicted Distance: 0.003011130029335618
Actual Label: Different Signature
Predicted Distance: 0.0018709745490923524
Actual Label: Different Signature
完整代码见gitee仓库 (opens new window)
# 4.SiameseNetWork的应用
1.签名验证Signature Verification using a "Siamese"
Time Delay Neural Network (opens new window)
2.三胞胎网络Deep metric learning using Triplet network (opens new window)
3.One-ShotLearning, Siamese Neural Networks for One-shot Image Recognition (opens new window)