【入门级】搭建一个基于余弦相似度的人脸比对模型
时间:2025-07-29 | 作者: | 阅读:0本文尝试基于飞桨框架,用SiameseNetwork和余弦距离构建人脸比对模型。使用face_224数据集,划分训练集和测试集,建立卷积神经网络输出1x128向量编码,通过余弦相似度判断人脸是否同一人。定义对比损失函数,经训练后批量验证准确率75.24%,还提及部署方式及后续改进方向。
前言
??偶然间发现飞桨官方文档里面有一篇关于图像相似度的示例【Paddle官方文档】基于图片相似度的图片搜索,就想着试试看能不能整一个通过相似度来进行人脸比对,然后发现了SiameseNetwork这个东西,打算照着试试自己能不能用余弦距离整一个人脸比对模型出来。
大致思路如下:
- 首先数据集用的是face_224这个数据集,划分好训练集和测试集后生成对应的列表文件
- 然后建立一个卷积神经网络,输入一张图片时,输出的是该图片的编码(一个1x128的向量)
- 通过比较两张图片编码的余弦相似度来判断两张图片是否来自同一个人
简单介绍一下余弦相似度(第一次接触这个,如果有错误欢迎各位大佬指正
- 先上个公式:
- 再上个代码:
point1 = point1 / paddle.norm(point1, axis=1, keepdim=True)#求范数,说人话就是对应元素的平方和再开方(来自某知乎暴躁老哥)
point2 = point1 / paddle.norm(point2, axis=1, keepdim=True)
CosineSimilarities = paddle.sum(paddle.multiply(img_emb1, img_emb2),axis=-1)
- 大概讲一下原理:
??实际上就是把两个点point1(X1,Y1)和point2(X2,Y2)到原点的连线之间的夹角的余弦值算出来,用来衡量point1(X1,Y1)和point2(X2,Y2)之间的距离(也就是相似度),其范围是[-1,1],也就是夹角越小,两个点越近,两个点的余弦相似度也就越接近1,反之就是-1
? ? ? ?关于上面的代码,实际上就是把那个公式实现一下,然后那俩point实际上就是换成了多维矩阵,只要动笔写一下就能理解(实际上是我懒得码字了
)还有就是为了配合对比损失函数ContrastiveLoss,需要用1减去余弦相似度获取余弦距离来进行损失函数的计算
下面是部署效果(临时抓的室友,不要在意细节,天太热了。。。。。。)
? ? ? ?一、准备工作
1. 导入所需的库
In [2]#导入所需库import osimport cv2import mathimport randomimport paddleimport numpy as npimport paddle.nn as nnimport paddlehub as hubimport paddle.nn.functional as Fimport matplotlib.pyplot as pltfrom PIL import Imagefrom tqdm import tqdmfrom paddle import ParamAttrfrom paddle.static import InputSpecfrom paddle.regularizer import L2Decayfrom paddle.nn.initializer import Uniform, KaimingNormalfrom paddle.nn import Conv2D, Linear, Dropout, BatchNorm, MaxPool2D, AvgPool2D, AdaptiveAvgPool2D登录后复制 ? ?
2. 准备数据集
In [?]#解压数据集!unzip -q data/data27604/face_224.zip -d data/!mv data/face_224/train.txt work/textpack/train.txt!mv data/face_224/test.txt work/textpack/test.txt登录后复制 ? ?In [3]
#数据列表读取#训练集train_list = []with open(”work/textpack/train.txt“, ”r“) as f: for line in f.readlines(): data = line.split('nt') for datastr in data: sub_str = datastr.split(' ') if sub_str: train_list.append(sub_str)random.shuffle(train_list)#测试集test_list = []with open(”work/textpack/test.txt“, ”r“) as f: for line in f.readlines(): data = line.split('nt') for datastr in data: sub_str = datastr.split(' ') if sub_str: test_list.append(sub_str)random.shuffle(test_list)登录后复制 ? ?In [4]
#数据生成器#训练集def reader_creator_train(): data_root = 'data/face_224/' imgnums = np.arange(2) def reader(BATCHSIZE): iter_step = 0 img1s = np.empty((BATCHSIZE, 3, 224, 224), dtype=np.float32) img2s = np.empty((BATCHSIZE, 3, 224, 224), dtype=np.float32) labels = np.empty(BATCHSIZE, dtype=np.float32) while True: if iter_step % 2 == 0: #相似 while True: for i, imgnum in enumerate(random.sample(range(len(train_list)),2)): imgnums[i] = imgnum if train_list[imgnums[0]][1] == train_list[imgnums[1]][1]: break label = 1. else: #不相似 while True: for i, imgnum in enumerate(random.sample(range(len(train_list)),2)): imgnums[i] = imgnum if train_list[imgnums[0]][1] != train_list[imgnums[1]][1]: break label = 0. #读取图片 img1 = Image.open(data_root+train_list[imgnums[0]][0]) img2 = Image.open(data_root+train_list[imgnums[1]][0]) img1 = np.array(img1) img2 = np.array(img2) #维度变换 img1 = np.swapaxes(img1, 1, 2) img1 = np.swapaxes(img1, 1, 0) img2 = np.swapaxes(img2, 1, 2) img2 = np.swapaxes(img2, 1, 0) #归一化 img1 = img1/255. img2 = img2/255. #图片整合 img1s[iter_step] = img1 img2s[iter_step] = img2 labels[iter_step] = label #弹出判断 iter_step += 1 if iter_step >= BATCHSIZE: break yield img1s, img2s, labels return readertrain_reader = reader_creator_train()#测试集def reader_creator_test(BATCHSIZE): data_root = 'data/face_224/' imgnums = np.arange(2) img1s = np.empty((BATCHSIZE, 3, 224, 224), dtype=np.float32) img2s = np.empty((BATCHSIZE, 3, 224, 224), dtype=np.float32) labels = np.empty(BATCHSIZE, dtype=np.float32) for iter_step in tqdm(range(BATCHSIZE)): if iter_step % 2 == 0: #相似 while True: for i, imgnum in enumerate(random.sample(range(len(test_list)),2)): imgnums[i] = imgnum if test_list[imgnums[0]][1] == test_list[imgnums[1]][1]: break label = 1. else: #不相似 while True: for i, imgnum in enumerate(random.sample(range(len(test_list)),2)): imgnums[i] = imgnum if test_list[imgnums[0]][1] != test_list[imgnums[1]][1]: break label = 0. #读取图片 img1 = Image.open(data_root+test_list[imgnums[0]][0]) img2 = Image.open(data_root+test_list[imgnums[1]][0]) img1 = np.array(img1) img2 = np.array(img2) #维度变换 img1 = np.swapaxes(img1, 1, 2) img1 = np.swapaxes(img1, 1, 0) img2 = np.swapaxes(img2, 1, 2) img2 = np.swapaxes(img2, 1, 0) #归一化 img1 = img1/255. img2 = img2/255. #图片整合 img1s[iter_step] = img1 img2s[iter_step] = img2 labels[iter_step] = label return img1s, img2s, labels登录后复制 ? ?
3. 模型组网
In [5]#自定义网络class MyNet(paddle.nn.Layer): def __init__(self): super(MyNet, self).__init__() self.conv1 = paddle.nn.Conv2D(in_channels=3, out_channels=32, kernel_size=(3,3), stride=2) self.conv2 = paddle.nn.Conv2D(in_channels=32, out_channels=64, kernel_size=(3,3), stride=2) self.conv3 = paddle.nn.Conv2D(in_channels=64, out_channels=128, kernel_size=(3,3), stride=2) self.conv4 = paddle.nn.Conv2D(in_channels=128, out_channels=256, kernel_size=(3,3), stride=2) self.gloabl_pool = paddle.nn.AdaptiveAvgPool2D((1,1)) self.fc1 = paddle.nn.Linear(in_features=256, out_features=128) @paddle.jit.to_static #这句导出的时候用的 def forward(self, x): x = self.conv1(x) x = F.relu(x) x = self.conv2(x) x = F.relu(x) x = self.conv3(x) x = F.relu(x) x = self.conv4(x) x = F.relu(x) x = self.gloabl_pool(x) x = paddle.squeeze(x, axis=[2, 3]) x = self.fc1(x) return x登录后复制 ? ?In [6]
#余弦距离对比损失函数def ContrastiveLoss(output1, output2, label): # 余弦距离 output1 = output1 / paddle.norm(output1, axis=1, keepdim=True) output2 = output2 / paddle.norm(output2, axis=1, keepdim=True) CosineSimilarities = paddle.sum(paddle.multiply(output1, output2),axis=-1) ones = paddle.ones(shape=CosineSimilarities.shape, dtype='float32') CosineDistance = paddle.subtract(ones, CosineSimilarities) margin = ones*1.25 # 对比损失ContrastiveLoss loss = paddle.add( paddle.multiply(label, paddle.pow(CosineDistance, 2)), paddle.multiply(paddle.subtract(ones, label), paddle.pow(F.relu(paddle.subtract(margin, CosineDistance)), 2)) ) avg_loss = paddle.mean(loss) return avg_loss登录后复制 ? ?
二、训练网络
1. 从零开始
In [?]#MyNet开始训练def train(model): print('start training ... ') model.train() EPOCH_NUM = 100000 BATCHSIZE = 512 opt = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()) for epoch in range(EPOCH_NUM): #训练模型 for data in train_reader(BATCHSIZE): img1_data, img2_data, label = data img1_tensor = paddle.to_tensor(img1_data) img2_tensor = paddle.to_tensor(img2_data) label_tensor = paddle.to_tensor(label) img1_embedding = model(img1_tensor) img2_embedding = model(img2_tensor) loss = ContrastiveLoss(img1_embedding, img2_embedding, label_tensor) print(”epoch: {}, loss is: {}“.format(epoch, loss.numpy())) loss.backward() opt.step() opt.clear_grad() #保存模型 if epoch % 10 == 0: paddle.jit.save(layer=model, path='work/modelsaveMyNet/' + str(epoch) + '/' + 'model', input_spec=[InputSpec(shape=[None, 40000], dtype='float32')]) #不严谨的验证(用来进行调试) model_val = paddle.jit.load('work/modelsaveMyNet/' + str(epoch) + '/' + 'model') model_val.eval() #相似 img1_temp = model_val(paddle.unsqueeze(img1_tensor[0], axis=0)) img2_temp = model_val(paddle.unsqueeze(img2_tensor[0], axis=0)) img1_temp = img1_temp / paddle.norm(img1_temp, axis=1, keepdim=True) img2_temp = img2_temp / paddle.norm(img2_temp, axis=1, keepdim=True) CosineSimilarities = paddle.sum(paddle.multiply(img1_temp, img2_temp),axis=-1) ones = paddle.ones(shape=CosineSimilarities.shape, dtype='float32') CosineDistance = paddle.subtract(ones, CosineSimilarities) print('CosineDistance: {}, Label: {}'.format(CosineDistance.numpy()[0], label[0])) #不相似 img1_temp = model_val(paddle.unsqueeze(img1_tensor[1], axis=0)) img2_temp = model_val(paddle.unsqueeze(img2_tensor[1], axis=0)) img1_temp = img1_temp / paddle.norm(img1_temp, axis=1, keepdim=True) img2_temp = img2_temp / paddle.norm(img2_temp, axis=1, keepdim=True) CosineSimilarities = paddle.sum(paddle.multiply(img1_temp, img2_temp),axis=-1) ones = paddle.ones(shape=CosineSimilarities.shape, dtype='float32') CosineDistance = paddle.subtract(ones, CosineSimilarities) print('CosineDistance: {}, Label: {}'.format(CosineDistance.numpy()[0], label[1])) model = MyNet()train(model)登录后复制 ? ?
2. 继续训练
In [?]#MyNet继续训练def continue_train(epochnumbercontinue,model): print('start training ... ') model.train() EPOCH_NUM = 100000 BATCHSIZE = 512 opt = paddle.optimizer.Adam(learning_rate=0.00001, parameters=model.parameters()) for epoch in range(epochnumbercontinue,EPOCH_NUM): #训练模型 for data in train_reader(BATCHSIZE): img1_data, img2_data, label = data img1_tensor = paddle.to_tensor(img1_data) img2_tensor = paddle.to_tensor(img2_data) label_tensor = paddle.to_tensor(label) img1_embedding = model(img1_tensor) img2_embedding = model(img2_tensor) loss = ContrastiveLoss(img1_embedding, img2_embedding, label_tensor) print(”epoch: {}, loss is: {}“.format(epoch, loss.numpy())) loss.backward() opt.step() opt.clear_grad() #保存模型 if epoch % 10 == 0: paddle.jit.save(layer=model, path='work/modelsaveMyNet/' + str(epoch) + '/' + 'model', input_spec=[InputSpec(shape=[None, 40000], dtype='float32')]) #不严谨的验证(用来进行调试) model_val = paddle.jit.load('work/modelsaveMyNet/' + str(epoch) + '/' + 'model') model_val.eval() #相似 img1_temp = model_val(paddle.unsqueeze(img1_tensor[0], axis=0)) img2_temp = model_val(paddle.unsqueeze(img2_tensor[0], axis=0)) img1_temp = img1_temp / paddle.norm(img1_temp, axis=1, keepdim=True) img2_temp = img2_temp / paddle.norm(img2_temp, axis=1, keepdim=True) CosineSimilarities = paddle.sum(paddle.multiply(img1_temp, img2_temp),axis=-1) ones = paddle.ones(shape=CosineSimilarities.shape, dtype='float32') CosineDistance = paddle.subtract(ones, CosineSimilarities) print('CosineDistance: {}, Label: {}'.format(CosineDistance.numpy()[0], label[0])) #不相似 img1_temp = model_val(paddle.unsqueeze(img1_tensor[1], axis=0)) img2_temp = model_val(paddle.unsqueeze(img2_tensor[1], axis=0)) img1_temp = img1_temp / paddle.norm(img1_temp, axis=1, keepdim=True) img2_temp = img2_temp / paddle.norm(img2_temp, axis=1, keepdim=True) CosineSimilarities = paddle.sum(paddle.multiply(img1_temp, img2_temp),axis=-1) ones = paddle.ones(shape=CosineSimilarities.shape, dtype='float32') CosineDistance = paddle.subtract(ones, CosineSimilarities) print('CosineDistance: {}, Label: {}'.format(CosineDistance.numpy()[0], label[1])) model_dir = 'work/modelsaveMyNet'model_list = os.listdir(model_dir)epochnumbers = [ int(epochnumber) for epochnumber in model_list ]epochnumbermax = max(epochnumbers)path = 'work/modelsaveMyNet/' + str(epochnumbermax) + '/' + 'model'model_continue = paddle.jit.load(path)continue_train(int(epochnumbermax), model_continue)登录后复制 ? ?
三、效果检验
1. 加载模型
In [8]# 加载模型model_dir = 'work/modelsaveMyNet'model_list = os.listdir(model_dir)epochnumbers = [ int(epochnumber) for epochnumber in model_list ]epochnumbermax = max(epochnumbers)path = 'work/modelsaveMyNet/' + str(epochnumbermax) + '/' + 'model'model_val = paddle.jit.load(path)model_val.eval()登录后复制 ? ?
2. 单张验证
In [9]#读取图片img1 = Image.open(”data/face_224/0/0_0000.jpg“)img2 = Image.open(”data/face_224/1/1_0000.jpg“)img1 = np.array(img1)img2 = np.array(img2)#维度变换img1 = np.swapaxes(img1, 1, 2)img1 = np.swapaxes(img1, 1, 0)img2 = np.swapaxes(img2, 1, 2)img2 = np.swapaxes(img2, 1, 0)#归一化img1 = img1/255.img2 = img2/255.#维度增加img1 = np.expand_dims(img1, axis=0).astype('float32')img2 = np.expand_dims(img2, axis=0).astype('float32')#To Tensorimg_t1 = paddle.to_tensor(img1)img_t2 = paddle.to_tensor(img2)#调用模型img_emb1 = model_val(img_t1)img_emb2 = model_val(img_t2)#计算相似度img_emb1 = img_emb1 / paddle.norm(img_emb1, axis=1, keepdim=True)img_emb2 = img_emb2 / paddle.norm(img_emb2, axis=1, keepdim=True)CosineSimilarity = paddle.sum(paddle.multiply(img_emb1, img_emb2),axis=-1)#结果打印print('CosineSimilarity = {}'.format(CosineSimilarity.numpy()[0]))登录后复制 ? ? ? ?
CosineSimilarity = -0.09816156327724457登录后复制 ? ? ? ?
3. 批量验证
In [10]#生成测试包(生成较慢,可适当减小TESTBATCHSIZE)TESTBATCHSIZE = 2048img1_data, img2_data, label = reader_creator_test(TESTBATCHSIZE)登录后复制 ? ? ? ?
100%|██████████| 2048/2048 [03:44<00:00, 9.12it/s]登录后复制 ? ? ? ?In [14]
#生成CosineSimilarities结果包img1_tensor = paddle.to_tensor(img1_data)img2_tensor = paddle.to_tensor(img2_data)img_emb1 = model_val(img1_tensor)img_emb2 = model_val(img2_tensor)img_emb1 = img_emb1 / paddle.norm(img_emb1, axis=1, keepdim=True)img_emb2 = img_emb2 / paddle.norm(img_emb2, axis=1, keepdim=True)CosineSimilarities = paddle.sum(paddle.multiply(img_emb1, img_emb2),axis=-1)#遍历计数比较TrueCount = 0TotalCount = 0similarityTHRE = 0.42for i in range(len(CosineSimilarities.numpy())): if(label[i] == 1 and CosineSimilarities.numpy()[i] >= similarityTHRE): TrueCount += 1 elif(label[i] == 0 and CosineSimilarities.numpy()[i] < similarityTHRE): TrueCount += 1 TotalCount += 1#显示准确率Acc = TrueCount/TotalCountprint(”TrueCount = {}“.format(TrueCount))print(”TotalCount = {}“.format(TotalCount))print(”Acc = {}“.format(Acc))登录后复制 ? ? ? ?
TrueCount = 1541TotalCount = 2048Acc = 0.75244140625登录后复制 ? ? ? ?
四、模型部署
- 请参照我之前的项目【PaddleHub】构建一个人脸识别模型并部署到Jetson Nano上(只要把模型文件换掉再把相似度计算部分改成余弦相似度就行了)
- 本项目开篇的展示图是使用PaddleInference部署在Windows10平台上的效果
五、项目总结
- 曾使用过欧式距离,效果没有余弦距离好
- 效果检验的结果并不是非常理想,准确率只有75.24%(但是部署的时候还行)
- 后续考虑更换其他网络进行测试,以及更换三元组损失(Triplet Loss)进行测试
- 实际上,正样本和负样本构建的时候采取的是随机选取的方式,个人感觉可能会有问题
- 如果出现报错invalid literal for int() with base 10: '.ipynb_checkpoints'则运行右方语句!rm -rf work/modelsaveMyNet/.ipynb_checkpoints
来源:https://www.php.cn/faq/1429365.html
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- nef 格式图片降噪处理用什么工具 效果如何
- 时间:2025-07-29
-
- 邮箱长时间未登录被注销了能恢复吗?
- 时间:2025-07-29
-
- Outlook收件箱邮件不同步怎么办?
- 时间:2025-07-29
-
- 为什么客户端收邮件总是延迟?
- 时间:2025-07-29
-
- 一英寸在磁带宽度中是多少 老式设备规格
- 时间:2025-07-29
-
- 大卡和年龄的关系 不同年龄段热量需求
- 时间:2025-07-29
-
- jif 格式是 gif 的变体吗 现在还常用吗
- 时间:2025-07-29
-
- hdr 格式图片在显示器上能完全显示吗 普通显示器有局限吗
- 时间:2025-07-29
大家都在玩
大家都在看
更多-
- 小红书笔记的修改或删除方法是什么?修改笔记之后会有影响吗?
- 时间:2025-09-30
-
- 抖音蓝v续费一年多少钱?蓝v年审过期了怎么办?
- 时间:2025-09-30
-
- 当地抖音来客服务商怎么找?服务商加入抖音来客有什么好处?
- 时间:2025-09-30
-
- 抖音账号违规申诉怎么写内容?违规申诉写内容有影响吗?
- 时间:2025-09-30
-
- 抖音的子账号怎么设置密码?它的超级福袋怎么设置?
- 时间:2025-09-30
-
- 漫威争锋怎么设置灵敏度
- 时间:2025-09-30
-
- 百家号养一个号要多久?养号有什么技巧吗?
- 时间:2025-09-30
-
- 心动小镇圣诞节限定活动都有什么
- 时间:2025-09-30