欧氏距离和余弦相似度:
为什么要引入这两个东西呢,打个比方当我们使用facenet或者其他模型提取一张人脸的1024个特征点后怎么才能知道它是属于谁的人脸呢。这时候这两种方法就起关键作用。
Sample(假如提取的一个人的人脸特征向量如下):
[0.79533959,0.31125495,0.33145159 … 0.21212415,0.7672149 ,0.68650482](1024列)
首先可以把它看成一个1024维度的向量,如果进行人脸匹配的话,就需要与数据库进行比对,假如数据库中已录入有一万张人脸的话,一张人脸对应1024个人脸特征,那么可以把数据库看成一个10000(行)X1024(列)矩阵。我们需要将我们测得的1024个特征与矩阵中的每一行里对应的1024个(列)元素进行比对。此时我们通过常用的欧氏距离或余弦相似度来比较两个向量之间的差距。简单的来说欧氏距离比较的是向量之间的实际距离。 由于1024维度太大不方便举例,我们先举一个三维向量对比差距的例子(假如一个人脸只对应三个维度的数据)。
举个栗子
我们首先使用欧氏距离来比较(1,2,3)和(2,4,6)两个向量之间的距离
根据参考公式如下:
欧氏距离=
而余弦相似度表示的是向量之间的夹角大小,比如(1,2,3),(2,4,6)
参考向量求余弦夹角公式:
在使用余弦相似度来判断时这就是同一个人,因为二者的夹角是0,二者为平行关系,cosx在定义域(0,π)之间的值域是(-1,1)所以当值越大代表角度越小,为1时方向相同,值越小代表角度越大,值为-1代表两个向量方向完全相反。
我们求得图片的特征向量后可通过欧氏距离和余弦相似度来进行人脸匹配,欧氏距离越小代表两个人脸之间的特征值差值越小,人脸越相似,余弦值越大代表两个向量之间的角度越小,人脸越相似。
所以我们通过计算出当前检测到人脸的特征与数据库录入的一万行人脸特征进行比对找到欧氏距离最小或余弦相似度最大的那一行人脸ID,就可以确认当前检测到人脸的身份,如果是未录入的人脸,由于我们设置了阈值,当欧氏距离与余弦相似度未超过此阈值就会显示为unknown,说明此人信息未录入人脸库中,因此我们并不需要保存该人的照片在数据库中,仅仅需要录入其人脸特征向量(以数值矩阵形式保存人脸特征向量)在数据库中。
对比欧氏距离与余弦相似度的相关知识参考
https://blog.csdn.net/huangfei711/article/details/78469614
http://blog.sina.com.cn/s/blog_407e5c1c0102vxyb.html
https://blog.csdn.net/weixin_37589896/article/details/78011003
再举个非常形象简单的关于聚类的例子:
歌手大赛,三个评委给三个歌手打分,第一个评委的打分(10,8,9), 第二个评委的打分(4,2,3),第三个评委的打分(8,10,9),如果采用余弦相似度来看每个评委的差异,虽然每个评委对同一个选手的评分不一样,但第一、第二两个评委对这三位歌手实力的排序是一样的,只是第二个评委对满分有更高的评判标准,说明第一、第二个评委对音乐的品味上是一致的。
因此,用余弦相似度来看,第一、第二个评委为一类人,第三个评委为另外一类。
如果采用欧氏距离, 第一和第三个评委的欧氏距离更近,就分成一类人了,但其实不太合理,因为他们对于三位选手的排名都是完全颠倒的。
两者各自的适用模型:
欧氏距离能够体现个体数值特征的绝对差异,所以更多的用于需要从维度的数值大小中体现差异的分析,如使用用户行为指标分析用户价值的相似度或差异。
余弦距离更多的是从方向上区分差异,而对绝对的数值不敏感,更多的用于使用用户对内容评分来区分兴趣的相似度和差异,同时修正了用户间可能存在的度量标准不统一的问题(因为余弦距离对绝对数值不敏感)。
向量点乘(内积)和叉乘(外积、向量积)概念及几何意义解读
https://blog.csdn.net/allenjiao/article/details/83828021
矩阵的内积点积
https://blog.csdn.net/zkq_1986/article/details/78203972
https://blog.csdn.net/Mr_EvanChen/article/details/77511312
Python实现如下:
欧氏距离
import numpy as np
from numpy.random import random
def EuclideanDistances(A, B):
BT = B.transpose()
vecProd = np.dot(A,BT)
SqA = A**2
sumSqA = np.matrix(np.sum(SqA, axis=1))
sumSqAEx = np.tile(sumSqA.transpose(), (1, vecProd.shape[1]))
SqB = B**2
sumSqB = np.sum(SqB, axis=1)
sumSqBEx = np.tile(sumSqB, (vecProd.shape[0], 1))
SqED = sumSqBEx + sumSqAEx - 2*vecProd
SqED[SqED<0]=0.0
ED = np.sqrt(SqED)
return ED
matrix1= random(size=(1,1024))#单个人脸的特征向量
matrix2= random(size=(10000,1024))#数据库中一万个人脸的特征向量
#matrix1=np.array([[1,1],[1,2]])
#matrix2=np.array([[2,1],[2,2],[2,3]])
Euclidean_dis=EuclideanDistances(matrix1,matrix2)
Euclidean_dis=np.array(Euclidean_dis)#转换成ndarray形式
print(Euclidean_dis[0])
print(type(Euclidean_dis))
print('计算出欧氏距离的个数:',Euclidean_dis.shape[1])
print('欧氏距离最小值是:',min(Euclidean_dis[0]))#最小欧氏距离的值
loc = np.argmin(Euclidean_dis[0])#最小欧氏距离对应的元素位置
print('欧氏距离最小值位于第%d个元素,它的值为%.15f'%(loc,(Euclidean_dis[0][loc])))
余弦距离
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
from numpy.random import random
import time
def cosine_distance(matrix1, matrix2):
matrix1_matrix2 = np.dot(matrix1, matrix2.transpose())#内积
matrix1_norm = np.sqrt(np.multiply(matrix1, matrix1).sum(axis=1))#求模
matrix1_norm = matrix1_norm[:, np.newaxis]
matrix2_norm = np.sqrt(np.multiply(matrix2, matrix2).sum(axis=1))
matrix2_norm = matrix2_norm[:, np.newaxis]
cosine_distance = np.divide(matrix1_matrix2, np.dot(matrix1_norm, matrix2_norm.transpose()))
return cosine_distance
matrix1= random(size=(1,1024))#单个人脸的特征向量
matrix2= random(size=(10000,1024))#数据库中一万个人脸的特征向量
#matrix1=np.array([[1,1],[1,2]])
#matrix2=np.array([[2,1],[2,2],[2,3]])
start = time.process_time()
cosine_dis=cosine_distance(matrix1,matrix2)
elapsed = (time.process_time() - start)
print('自己编写代码计算的cosine_dis:',cosine_dis)
print('计算用时:',elapsed)
#print((type(cosine_dis)))
print('数据库里有%d张人脸数据'%cosine_dis.shape[1])#1个人脸与一万个人脸之间的10000个余弦相似度
print('余弦最大值是:',max(cosine_dis[0]))#最大余弦相似度的值
loc = np.argmax(cosine_dis[0])#最大余弦相似度对应的元素位置
print('余弦最大值位于第%d个元素,它的值为%.16f'%(loc,(cosine_dis[0][loc])))
#print(cosine_dis[0][loc])#最大相似度对应的元素位置的元素值
start_2=time.process_time()
cosine_dis2 = cosine_similarity(matrix1,matrix2)
elapsed_2=(time.process_time()-start_2)
print('调库计算的cosine_dis2:',cosine_dis2)
print('调库计算用时:',elapsed_2)