深入浅出孪生神经网络,高效训练模型

server/2025/1/16 0:19:38/

大家好,在深度学习领域,神经网络几乎能处理各种任务,但通常需要依赖于海量数据来达到最佳效果。然而,对于像面部识别和签名验证这类任务,我们不可能总是有大量的数据可用。由此产生了一种新型的神经网络架构,称为孪生网络。

孪生神经网络能够基于少量数据实现精准预测,本文将介绍孪生神经网络的基本概念,以及如何用PyTorch来搭建一个签名验证系统。

1.孪生神经网络概述

孪生神经网络由两个或若干个结构和参数完全相同的子网络构成,这些子网络在更新参数时同步进行。

这种网络通过分析输入样本的特征向量来评估它们的相似度,这一特性让其在众多应用领域中发挥着重要作用。

传统神经网络一般被训练用于识别多个不同的类别。当数据集中需要添加或移除类别时,这些网络通常需要经过调整和重新训练,这个过程不仅耗时,而且通常需要大量的数据支持。

而孪生网络专注于学习样本之间的相似性,能够轻松识别图像是否相似。这种能力使孪生网络在处理新类别数据时,无需重新训练即可进行有效的分类,提供了一种灵活且高效的解决方案。

1.1 优劣势

优势:

  • 对类别失衡的适应能力更强:通过一次性学习,孪生网络仅需少量样本即可有效识别图像类别,具有良好的鲁棒性和适应性。

  • 与顶级分类器的协同效应:由于其独特的学习机制,与传统分类方法相比,孪生网络与最佳分类器(如GBM和随机森林)的结合能够带来更佳的性能表现。

  • 学习语义相似性:孪生网络专注于深层特征的嵌入学习,将相同类别或概念的样本聚集在一起,从而能够捕捉和学习它们之间的语义相似性。

劣势:

  • 训练耗时较长:孪生网络采用成对学习机制,需要综合考虑所有可用信息,因此其训练过程比逐点学习的分类网络更为耗时。

  • 缺乏概率输出:孪生网络的训练过程专注于成对比较,它不提供预测结果的概率值,而是直接输出各类别之间的相对距离。

1.2 孪生网络中使用的损失函数

图片

在孪生网络的训练过程中,由于采用的是成对学习方式,传统的交叉熵损失函数不再适用。目前,主要有两种损失函数用于训练这类网络:

1)三元组损失

三元组是一种特殊的损失函数,它通过比较基线(锚点)输入与正样本(真实输入)和负样本(虚假输入)之间的差异来工作。其目标是最小化锚点与正样本之间的距离,同时最大化锚点与负样本之间的距离,从而确保网络能够准确区分不同类别的样本。

图片

在上述方程中,α是控制间隔的参数,用于调整相似样本对与不相似样本对之间的距离差异;f(A)、f(P)、f(N)分别代表锚点、正样本和负样本的特征嵌入。

训练时,将由锚点图像、负样本图像和正样本图像组成的三元组作为单一样本输入到模型中。这样做的目的是确保锚点与正样本图像之间的距离小于锚点与负样本图像之间的距离。

2)对比损失

对比损失是当前非常流行的损失函数,它基于距离而非传统的错误预测来计算损失。该损失函数主要应用于特征嵌入的学习,目标是优化样本点在欧几里得空间(Euclidean space)的分布:对于相似的样本点,我们希望它们之间的距离尽可能地小,以便能够准确捕捉它们之间的相似性;对于不相似的样本点,则希望它们之间的距离足够大,以便于区分差异。

图片

定义Dw是欧几里得距离(Euclidean distance):

图片

Gw是网络对一张图像的输出结果。

2.孪生网络在签名验证领域的应用

孪生网络具有优秀的相似性比较能力,常被应用于需要验证身份的系统中,例如面部识别和签名验证。

接下来,将在PyTorch框架下构建一个基于孪生神经网络的签名验证系统,实现高精度的身份验证功能。

图片

2.1 数据集和数据集预处理

使用ICDAR 2011数据集进行签名验证,该数据集收录了荷兰用户的签名样本,包括真实和伪造的签名。数据集分为训练和测试部分,每个部分都有用户的真实与伪造签名文件夹。数据集的标签信息以CSV文件的形式提供。

图片

ICDAR 数据集中的签名

在把这些原始数据输入神经网络之前,需要将图像转换为张量,并整合CSV中的标签。这可以通过PyTorch的自定义数据集类实现,以下是代码示例。

#数据预处理和加载
class SiameseDataset():def __init__(self,training_csv=None,training_dir=None,transform=None):# used to prepare the labels and images pathself.train_df=pd.read_csv(training_csv)self.train_df.columns =["image1","image2","label"]self.train_dir = training_dir    self.transform = transformdef __getitem__(self,index):# 获取图像路径image1_path=os.path.join(self.train_dir,self.train_df.iat[index,0])image2_path=os.path.join(self.train_dir,self.train_df.iat[index,1])# Loading the imageimg0 = Image.open(image1_path)img1 = Image.open(image2_path)img0 = img0.convert("L")img1 = img1.convert("L")# 应用图像变换if self.transform is not None:img0 = self.transform(img0)img1 = self.transform(img1)return img0, img1 , th.from_numpy(np.array([int(self.train_df.iat[index,2])],dtype=np.float32))def __len__(self):return len(self.train_df)

预处理数据集后,需要在PyTorch框架中使用Dataloader类来加载这些数据。为了适应计算需求,通过transforms函数对图像进行尺寸调整,将其高度和宽度统一缩减至105像素。

# 从原始图像文件夹加载数据集
siamese_dataset = SiameseDataset(training_csv,training_dir,transform=transforms.Compose([transforms.Resize((105,105)),transforms.ToTensor()]))

2.2 神经网络架构

在PyTorch中创建一个神经网络

#创建孪生网络
class SiameseNetwork(nn.Module):def __init__(self):super(SiameseNetwork, self).__init__()# 设置CNN层序列self.cnn1 = nn.Sequential(nn.Conv2d(1, 96, kernel_size=11,stride=1),nn.ReLU(inplace=True),nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2),nn.MaxPool2d(3, stride=2),nn.Conv2d(96, 256, kernel_size=5,stride=1,padding=2),nn.ReLU(inplace=True),nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2),nn.MaxPool2d(3, stride=2),nn.Dropout2d(p=0.3),nn.Conv2d(256,384 , kernel_size=3,stride=1,padding=1),nn.ReLU(inplace=True),nn.Conv2d(384,256 , kernel_size=3,stride=1,padding=1),nn.ReLU(inplace=True),nn.MaxPool2d(3, stride=2),nn.Dropout2d(p=0.3),)# 定义全连接层self.fc1 = nn.Sequential(nn.Linear(30976, 1024),nn.ReLU(inplace=True),nn.Dropout2d(p=0.5),nn.Linear(1024, 128),nn.ReLU(inplace=True),nn.Linear(128,2))def forward_once(self, x):# 前向传播output = self.cnn1(x)output = output.view(output.size()[0], -1)output = self.fc1(output)return outputdef forward(self, input1, input2):# 输入1的前向传播output1 = self.forward_once(input1)# 输入2的前向传播output2 = self.forward_once(input2)return output1, output2

在代码实现中,构建了这样的神经网络结构:首层卷积层配备了96个11x11大小的卷积核,步长为1像素,用于处理105x105像素的输入签名图像。紧接着的第二层卷积层,以第一层的输出(经过响应归一化和池化处理)为输入,使用256个5x5大小的卷积核进行特征提取。

第三层和第四层卷积层之间不涉及任何池化或归一化操作。第三层使用384个3x3大小的卷积核,直接连接到第二层卷积层的输出(该输出已经经过归一化、池化和dropout处理)。第四层卷积层则包含256个3x3大小的卷积核。这样的设计使网络能够学习到较少的低级特征,以适应更小的感受野,同时捕捉到更多的高级或抽象特征。

网络中的第一层全连接层包含1024个神经元,而第二层全连接层则有128个神经元。

通过限制两个网络共享相同的权重,实现了使用单一模型连续处理两张图像的策略。这种方法不仅节省了内存空间,还提升了计算效率。具体操作中,对这两张图像计算损失值,并执行反向传播来更新权重。

2.3 损失函数

对于这项任务,使用对比损失函数来学习特征嵌入,其目标是让相似的样本点在欧几里得空间中的距离尽可能小,而不相似的样本点则保持较大的距离。以下是在PyTorch框架中实现对比损失函数的方法。

class ContrastiveLoss(torch.nn.Module):"""Contrastive loss function.Based on:"""def __init__(self, margin=1.0):super(ContrastiveLoss, self).__init__()self.margin = margindef forward(self, x0, x1, y):# 欧几里得距离diff = x0 - x1dist_sq = torch.sum(torch.pow(diff, 2), 1)dist = torch.sqrt(dist_sq)mdist = self.margin - distdist = torch.clamp(mdist, min=0.0)loss = y * dist_sq + (1 - y) * torch.pow(dist, 2)loss = torch.sum(loss) / 2.0 / x0.size()[0]return loss

2.4 训练网络

孪生网络的训练过程如下:

  • 初始化网络、损失函数和优化器,通过网络传递图像对的第一张图像,通过网络传递图像对的第二张图像。

  • 使用第一张和第二张图像的输出计算损失,将损失反向传播以计算模型的梯度。

  • 使用优化器更新权重,保存模型。

# 声明孪生网络
net = SiameseNetwork().cuda()
# 声明损失函数
criterion = ContrastiveLoss()
# 声明优化器
optimizer = th.optim.Adam(net.parameters(), lr=1e-3, weight_decay=0.0005)
#训练模型
def train():loss=[] counter=[]iteration_number = 0for epoch in range(1,config.epochs):for i, data in enumerate(train_dataloader,0):img0, img1 , label = dataimg0, img1 , label = img0.cuda(), img1.cuda() , label.cuda()optimizer.zero_grad()output1,output2 = net(img0,img1)loss_contrastive = criterion(output1,output2,label)loss_contrastive.backward()optimizer.step()    print("Epoch {}\n Current loss {}\n".format(epoch,loss_contrastive.item()))iteration_number += 10counter.append(iteration_number)loss.append(loss_contrastive.item())show_plot(counter, loss)   return net
#将设备设置为cuda
device = torch.device('cuda' if th.cuda.is_available() else 'cpu')
model = train()
torch.save(model.state_dict(), "model.pt")
print("Model Saved Successfully") 

该模型在Google Colab上训练了20个周期,持续一个小时,以下是随时间变化的损失图:

图片

2.5 测试模型

在测试数据集上测试我们的签名验证系统。使用Pytorch的DataLoader类加载测试数据集,传递图像对和标签,找到图像之间的欧几里得距离,根据欧几里得距离输出结果。

# 加载测试数据集
test_dataset = SiameseDataset(training_csv=testing_csv,training_dir=testing_dir,transform=transforms.Compose([transforms.Resize((105,105)),transforms.ToTensor()]))test_dataloader = DataLoader(test_dataset,num_workers=6,batch_size=1,shuffle=True)
#测试网络
count=0
for i, data in enumerate(test_dataloader,0): x0, x1 , label = dataconcat = torch.cat((x0,x1),0)output1,output2 = model(x0.to(device),x1.to(device))eucledian_distance = F.pairwise_distance(output1, output2)if label==torch.FloatTensor([[0]]):label="Original Pair Of Signature"else:label="Forged Pair Of Signature"imshow(torchvision.utils.make_grid(concat))print("Predicted Eucledian Distance:-",eucledian_distance.item())print("Actual Label:-",label)count=count+1if count ==10:break

预测结果如下:

图片


http://www.ppmy.cn/server/113384.html

相关文章

汽车电子行业知识:关于车载中控屏

文章目录 1. 车载中控屏的功能2. 最新技术3. 最新产品4. 未来趋势5. 车载中控屏的供应商5.1. 电子元件制造商5.2. 显示技术公司5.3. 软件和系统集成商5.4. 汽车制造商5.5. 新兴科技公司 车载中控屏是现代汽车中不可或缺的组成部分,它不仅提供了车辆信息的显示&#…

【qt】多线程实现倒计时

1.界面设计 设置右边的intvalue从10开始倒计时 2.新建Thread类 新建Thread类,使其继承QThread类,多态重写run函数,相当于线程执行函数 3.重写run函数 重写run函数,让另一个进程每隔1s发出一个信号,主线程使用conne…

vue-seamless-scroll(二)点击事件

继上一篇文章vue-seamless-scroll(一)使用 在表格数据滚动的时候想要点击某一行获取当前行的数据,但是当数据在第二轮数据滚动的时候点击没有反应,获取不到数据,该怎么解决? 首先需要在父级添加一个点击事件,然后在这…

Android调整第三方库PickerView宽高--回忆录

一、效果 // 时间选择implementation com.contrarywind:Android-PickerView:4.1.9 多年前,使用到事件选择器,但是PickerView默认宽度使满屏的,不太符合业务需求,当时为此花了许多时间,最终找到了解决方案,…

指针与函数(三)

三 .指向函数的指针 函数和数组一样,经系统编译后,其目标代码在内存中连续存放,其名字本身就是一个地址,是函数的入口地址。C语言中,指针可以指向变量,也可以指向函数。 指问函数的指针的定义格式为 类型名(*指针变量名)参数表 其中参数表为函数指针所…

go急速入门API开发

go急速入门 1、安装Go和对应编辑器2、编写helloWord3、项目目录开发4、编写一个http服务器5、 使用Gin框架基本使用使用部分中间件自定义中间件 6、部署 1、安装Go和对应编辑器 go官网,下载自己电脑对应版本即可。安装完成之后打开cmd输入go即可弹出对应提示。 对…

vue3和vue2的双向绑定原理

Vue 的双向绑定是其核心特性之一,允许数据和视图之间保持同步。在 Vue 2 和 Vue 3 中,双向绑定的实现原理有所不同。以下是两者的原理对比: Vue 2 的双向绑定原理 在 Vue 2 中,双向绑定是通过以下机制实现的: 响应式…

【图论】网络流

算法进阶课网络流部分总结 文章目录 基本概念流网络可行流最大流残留网络增广路径割最小割最大流最小割定理 算法EK算法Dinic算法 最大流模型基本思想二分图匹配P2756 飞行员配对方案问题P3254 圆桌问题 上下界可行流无源汇上下界可行流有源汇上下界可行流有源汇上下界最大流有…