本文章记录最近看的一个孪生网络实现人脸面部相似度的代码实例,关于孪生网络的定义,可以点击这里,该项目所使用的的网络架构为标准的卷积神经网络架构,在每个卷积层之后使用批量归一化(batch normolization),然后进行dropout。
孪生网络架构的代码片段:
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.cnn1 = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(1, 4, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(4),
#nn.BatchNorm2d(4)中参数4为通道数
nn.ReflectionPad2d(1),
nn.Conv2d(4, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
nn.ReflectionPad2d(1),
nn.Conv2d(8, 8, kernel_size=3),
nn.ReLU(inplace=True),
nn.BatchNorm2d(8),
)
self.fc1 = nn.Sequential(
nn.Linear(8*100*100, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 500),
nn.ReLU(inplace=True),
nn.Linear(500, 5))
def forward_once(self, x):
output = self.cnn1(x)
output = output.view(output.size(0), -1)
output = self.fc1(output)
return output
def forward(self, input1, input2):
output1 = self.forward_once(input1)
output2 = self.forward_once(input2)
return output1, output2
在这个结构中,实际上就只有一个网络。因为孪生网络中两个网络的权重是相同的,所以我们使用一个模型并连续输入两张图像,并使用两个图像来计算损失值,然后反向传播,更新参数。
在使用pairwise_distance计算完两张图片的欧式距离后,使用对比损失作为目标损失函数
class ContrastiveLoss(torch.nn.Module):
def __init__(self, margin=2.0):
super(ContrastiveLoss, self).__init__()
self.margin = margin
def forward(self, output1, output2, label):
euclidean_distance = F.pairwise_distance(output1, output2)
loss_contrastive = torch.mean((1-label) * torch.pow(euclidean_distance, 2) +
(label) * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))
return loss_contrastive
代码中对比损失用数学公式表示为
其中的DW是两张图片的欧式距离。观察上述的contrastive loss表达式可以发现,这种损失函数可以很好的表达成对样本的匹配程度。当y=0(即样本相似)时,损失函数只剩下,此项越小越好。即原本相似的样本,如果在特征空间的欧式距离较大,则说明当前的模型不好。当y=1(即样本不相似)时,损失函数剩下,此项越小越好,也就是说m-Dw越小越好,Dw越大越好。即当样本不相似时,两张图片在特征空间的的欧氏距离反而小的话,则要加大损失。
数据加载
class SiameseNetworkDataset(Dataset):
def __init__(self,imageFolderDataset,transform=None,should_invert=True):
self.imageFolderDataset = imageFolderDataset
self.transform = transform
self.should_invert = should_invert
def __getitem__(self,index):
img0_tuple = random.choice(self.imageFolderDataset.imgs)
#we need to make sure approx 50% of images are in the same class
should_get_same_class = random.randint(0,1)
if should_get_same_class:
while True:
#keep looping till the same class image is found
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1]==img1_tuple[1]:
break
else:
while True:
#keep looping till a different class image is found
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1] !=img1_tuple[1]:
break
img0 = Image.open(img0_tuple[0])
img1 = Image.open(img1_tuple[0])
img0 = img0.convert("L")
img1 = img1.convert("L")
if self.should_invert:
img0 = PIL.ImageOps.invert(img0)
img1 = PIL.ImageOps.invert(img1)
if self.transform is not None:
img0 = self.transform(img0)
img1 = self.transform(img1)
return img0, img1 , torch.from_numpy(np.array([int(img1_tuple[1]!=img0_tuple[1])],dtype=np.float32))
def __len__(self):
return len(self.imageFolderDataset.imgs)
网络架构需要输入一对图片以及标签(相似/不相似)。该实例创建了自定义的数据加载器来达到这个目的。在SiameseNetworkDataset这个类中生成一对图像和相似度标签。如果图像来自同一个类,标签为0,否则为1.
网络训练过程
1.向网络传送第一张图片
2.向网络传送第二张图片
3.利用1.2中的输出特征值计算损失
4.反向传播计算梯度
5.使用Adam优化器来更新权重
net = SiameseNetwork()
criterion = ContrastiveLoss()
optimizer = torch.optim.Adam(net.parameters(),0.001,betas=(0.9, 0.99))
counter = []
loss_history = []
iteration_number= 0
for epoch in range(0,Config.train_number_epochs):
for i, data in enumerate(train_dataloader,0):
img0, img1 , label = data
#img0, img1 , label = img0.cpu, img1.cpu, label.cpu
optimizer.zero_grad()
output1,output2 = net(img0,img1)
loss_contrastive = criterion(output1,output2,label)
loss_contrastive.backward()
optimizer.step()
if i %10 == 0 :
print("Epoch number {}\n Current loss {}\n".format(epoch,loss_contrastive.item()))
iteration_number +=10
counter.append(iteration_number)
loss_history.append(loss_contrastive.item())
show_plot(counter,loss_history)
torch.save(net.state_dict(),'net_params.pkl')
最后用测试集判断相似度
以上就是本人对这个项目的理解,希望对你有帮助,如有不对,恳请指出。
(数据集和代码github可下:https://github.com/marsmarcin/Siamese-Nets-for-Face-Reco)