waterman 发表于 2025-1-16 19:38

《深度学习的数学——使用Python语言》7. 线代进阶之”向量范数和距离度量“

在深度学习中,范数和向量距离是两个不同的概念。向量范数是一种函数,用于将一个实数或复数向量映射为一个值。虽然范数通常用于度量向量之间的距离,但是同样也有其它的一些表示距离的方式。
# 范数距离
范数是具有“长度”概念的函数。在向量空间内,为所有的向量的赋予非零的增长度或者大小。不同的范数,所求的向量的长度或者大小是不同的。

L1范数是指向量中各个元素绝对值之和,L2范数定义为向量所有元素的平方和的开平方。

L1范数的定义:


L2范数的定义:


在深度学习中,二范数可用于正则化的权值衰减法,从而避免模型的权重变得过大。
此外,在facenet人脸识别模型中,就是将将人脸图片表征为一个128维向量,通过计算两个人脸向量之间的二范数距离,来判断两张人脸图片是否属于同一个人。
下面的代码读取了两张人脸图片,分别获得了128维的输出向量并进行正则化,之后使用L2范数计算它们之间的距离。
```
    # Set inputs and inference
    image_1 = Image.open("./dataset/facenet/1_001.jpg")
    image_1 = image_1.resize((160,160), Image.BICUBIC)
    img1 = np.asarray(image_1, np.uint8)
    outputs1 = np.array(rknn.inference(data_format='nhwc', inputs=))
    outputs1 = preprocessing.normalize(outputs1, norm='l2')
   
    image_2 = Image.open("./dataset/facenet/1_002.jpg")
    image_2 = image_2.resize((160,160), Image.BICUBIC)
    img2 = np.asarray(image_2, np.uint8)
    outputs2 = np.array(rknn.inference(data_format='nhwc', inputs=))
    outputs2 = preprocessing.normalize(outputs2, norm='l2')

    # Get distance
    distance = np.linalg.norm(outputs2 - outputs1, axis=1)
    print("distance:", distance)
```
# 马氏距离
当需要度量某个特征向量与多个特征向量之间的距离时,如果直接采用欧式距离,衡量的是两个特征向量之间的直接距离,而没有考虑数据的分布特性。而采用马氏距离,在计算中对协方差进行归一化,则可以规避欧式距离对于数据特征方差不同的风险,从而使所谓的“距离”更加符合数据分布特征以及实际意义。

其中Σ是多维随机变量的协方差矩阵,μ为样本均值向量。马氏距离刻画了x与以μ为均值的某个分布之间的距离。如果协方差矩阵是单位向量,即各维度独立同分布,此时马氏距离就变成了欧氏距离。

直观的解释如上图,虽然Point1和Point2距离样本中心点的距离相同,但是在右图中,可以明显看出来,Point2不属于该分布。
因此,我们可以使用马氏距离来构建一个简单的分类器。比如,给定数据集,判断输入样本与所有类别质心之间的马氏距离,并选择最小距离作为该输入的类别。
下面以sklearn中的乳腺癌数据集来构建基于马氏距离的最近质心分类器,从而进一步加深理解与认识。
代码实现如下:
导入需要的库
```
import numpy as np
from sklearn import datasets
from scipy.spatial.distance import mahalanobis
读取数据集并打乱,取前400个样本作为训练数据并将剩下的样本作为测试数据
bc = datasets.load_breast_cancer()
d = bc.data
l = bc.target
i = np.argsort(np.random.random(len(d)))
d = d
l = l
xtrn, ytrn = d[:400], l[:400]
xtst, ytst = d, l
```
计算每个类别数据的均值以及训练数据集的协方差矩阵和协方差矩阵的逆
```
i = np.where(ytrn == 0)
m0 = xtrn.mean(axis=0)
i = np.where(ytrn == 1)
m1 = xtrn.mean(axis=0)
S = np.cov(xtrn, rowvar=False)
SI= np.linalg.inv(S)
```
定义函数,对测试数据集进行分类
```
def score(xtst, ytst, m, SI):
    nc = 0
    for i in range(len(ytst)):
      d = np.array(,m,SI),
                      mahalanobis(xtst,m,SI)])
      c = np.argmin(d)
      if (c == ytst):
            nc += 1
    return nc / len(ytst)
```
分别计算马氏距离和欧氏距离的结果得分
```
mscore = score(xtst, ytst, , SI)
escore = score(xtst, ytst, , np.identity(30))
print("Mahalanobis score = %0.4f" % mscore)
print("Euclidean   score = %0.4f" % escore)
```
运行结果如下:

可以看到,马氏距离分类的效果比欧氏距离的效果有所提升。
# K-L散度
K-L散度又称相对熵,用于衡量两个概率分布的相似程度。若K-L散度越小,则说明两个概率分布越相似。
我们设定两个概率分布分别为P和Q,则连续和离散情况下的K-L散度计算公式分别为


其中log为以2为底的对数,在scipy.special中使用rel_entr函数来实现K-L散度的计算,但是它使用的是自然对数而不是以2为底的对数。此外,K-L散度不满足对称性,并非数学意义上的距离度量。
下面我们通过一个实验来加深对于K-L散度的理解。
代码实现如下:
首先导入使用到的库
```
import numpy as np
from scipy.special import rel_entr
import matplotlib.pylab as plt
```
分别生成服从均匀分布和两组二项分布B(12,0.4)以及B(12,0.9)。
```
N = 1000000
p = np.random.randint(0,13,size=N)
p = np.bincount(p)
p = p / p.sum()
q = np.random.binomial(12,0.9,size=N)
q = np.bincount(q)
q = q / q.sum()
w = np.random.binomial(12,0.4,size=N)
w = np.bincount(w)
w = w / w.sum()
```
使用rel_entr函数计算两组二项分布与均匀分布的K-L散度,判断哪个二项分布更接近于均匀分布
```
print(rel_entr(q,p).sum())
print(rel_entr(w,p).sum())
```
输出结果如下:

可以看到,w与p的K-L散度更小,说明w与p的概率分布更为相似。事实是否如此呢?我们来看一下三个不同的离散概率分布的图像。
```
plt.bar(np.arange(13),p,0.333,hatch="///",edgecolor='k')
plt.bar(np.arange(13)+0.333,q,0.333,hatch="---",edgecolor='k')
plt.bar(np.arange(13)+0.666,w,0.333,hatch="\\\\",edgecolor='k')
plt.xlabel("Value")
plt.ylabel("Proportion")
plt.tight_layout(pad=0,h_pad=0,w_pad=0)
plt.savefig("kl_divergence.png", dpi=300)
plt.show()
```

其中蓝色为p,橙色为q,绿色为w。可以看到与q相比,p确实更像w,说明我们通过K-L散度的判断是正确的。

此外,既然前面提到了K-L散度是非对称的,那么我们在计算K-L散度时将输入变量换一下位置会产生什么样的后果呢?我们将计算w与p的散度时的输入换一下顺序
```
print(rel_entr(q,p).sum())
print(rel_entr(p,w).sum())
```
得到结果如下:

可以看到第二项反而比第一项更大了,这显然不是我们期望的结果。那么在实际使用中,应该如何确定输入变量的顺序呢?
为了解决这个问题,我们首先需要理解K-L散度的意义,KL(P||Q)衡量的是分布P相对于Q的信息损失,即当Q被用作P的近似时,描述P所需的额外信息量。如果将P设为目标分布、Q设为近似分布,则KL(P||Q)越小说明用Q描述P所需的额外信息量越小,即P和Q越接近。相反KL(Q||P)越小,则说明用P描述Q所需的额外信息量越小。因此,在上面比较q和w与p之间的距离时,我们需要固定输入中p的位置,这样得到的数值才具有可比性。
然而,若我们将p都作为第一个输入变量,下面又出现了新的问题
```
print(rel_entr(p,q).sum())
print(rel_entr(p,w).sum())
```
输出结果如下:

可以看到,结果中竟然出现了inf,虽然这并不影响我们的判断,但是inf在计算中会给我们带来极大的不便,如何避免这个问题呢?下面给出了解答。

Jacktang 发表于 2025-1-18 10:14

<p>结果中竟然出现了inf,虽然这并不影响我们的判断</p>

<p>这个inf出现的原因是什么</p>

waterman 发表于 2025-1-18 13:57

Jacktang 发表于 2025-1-18 10:14
结果中竟然出现了inf,虽然这并不影响我们的判断

这个inf出现的原因是什么

<p>下面是打印出来的p q和w的值&nbsp; 根据离散情况下的K-L散度计算公式,因为q中出现了0,所以在计算KL(p||q)时,由于分母为零导致出现无穷大inf的情况。</p>
页: [1]
查看完整版本: 《深度学习的数学——使用Python语言》7. 线代进阶之”向量范数和距离度量“