NumPy之计算两个矩阵的成对平方欧氏距离
问题描述
设 \({X_{m \times k}} = \left[ {\vec x_1^T;\vec x_2^T; \cdots ;\vec x_m^T} \right]\) (; 表示纵向连接) 和 \({Y_{n \times k}} = \left[ {\vec y_1^T;\vec y_2^T; \cdots ;\vec y_n^T} \right]\), 计算矩阵 \({X_{m \times k}}\) 中每一个行向量和矩阵 \({Y_{n \times k}}\) 中每一个行向量的平方欧氏距离 (pairwise squared Euclidean distance), 即计算:
\(\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec x}_1} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_1} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_1} - {{\vec y}_n}} \right\|_2^2} \\
{\left\| {{{\vec x}_2} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_2} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_2} - {{\vec y}_n}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec x}_m} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_m} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_m} - {{\vec y}_n}} \right\|_2^2}
\end{array}} \right]\) (这是一个 \(m \times n\) 矩阵).
这个计算在度量学习, 图像检索, 行人重识别等算法的性能评估中有着广泛的应用.
公式推导
在 NumPy 中直接利用上述原式来计算两个矩阵的成对平方欧氏距离, 要显式地使用二重循环, 而在 Python 中循环的效率是相当低下的. 如果想提高计算效率, 最好是利用 NumPy 的特性将原式转化为数组/矩阵运算. 下面就尝试进行这种转化.
先将原式展开为:
\(\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec x}_1}} \right\|_2^2}&{\left\| {{{\vec x}_1}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_1}} \right\|_2^2} \\
{\left\| {{{\vec x}_2}} \right\|_2^2}&{\left\| {{{\vec x}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_2}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec x}_m}} \right\|_2^2}&{\left\| {{{\vec x}_m}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_m}} \right\|_2^2}
\end{array}} \right] + \left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2} \\
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2}
\end{array}} \right] - 2\left[ {\begin{array}{*{20}{c}}
{\left\langle {{{\vec x}_1},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_1},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_1},{{\vec y}_n}} \right\rangle } \\
{\left\langle {{{\vec x}_2},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_2},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_2},{{\vec y}_n}} \right\rangle } \\
\vdots & \vdots & \ddots & \vdots \\
{\left\langle {{{\vec x}_m},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_m},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_m},{{\vec y}_n}} \right\rangle }
\end{array}} \right]\)
下面逐项地化简或转化为数组/矩阵运算的形式:
\(\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec x}_1}} \right\|_2^2}&{\left\| {{{\vec x}_1}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_1}} \right\|_2^2} \\
{\left\| {{{\vec x}_2}} \right\|_2^2}&{\left\| {{{\vec x}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_2}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec x}_m}} \right\|_2^2}&{\left\| {{{\vec x}_m}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_m}} \right\|_2^2}
\end{array}} \right] = \left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec x}_1}} \right\|_2^2} \\
{\left\| {{{\vec x}_2}} \right\|_2^2} \\
\vdots \\
{\left\| {{{\vec x}_m}} \right\|_2^2}
\end{array}} \right]\vec 1_n^T = \left( {\left( {X \circ X} \right){{\vec 1}_k}} \right)\vec 1_n^T = \left( {X \circ X} \right){\vec 1_k}\vec 1_n^T\)
式中, \(\circ\) 表示按元素积 (element-wise product), 又称为 Hadamard 积; \({\vec 1_k}\) 表示维的全1向量 (all-ones vector), 余者类推. 上式中 \({\vec 1_k}\) 的作用是计算 \(X \circ X\) 每行元素的和, 返回一个列向量; \(\vec 1_n^T\) 的作用类似于 NumPy 中的广播机制, 在这里是将一个列向量扩展为一个矩阵, 矩阵的每一列都是相同的.
\(\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2} \\
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec y}_n}} \right\|_2^2}
\end{array}} \right] = {\vec 1_m}{\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec y}_1}} \right\|_2^2} \\
{\left\| {{{\vec y}_2}} \right\|_2^2} \\
\vdots \\
{\left\| {{{\vec y}_n}} \right\|_2^2}
\end{array}} \right]^T} = {\vec 1_m}{\left( {\left( {Y \circ Y} \right){{\vec 1}_k}} \right)^T} = {\vec 1_m}\vec 1_k^T{\left( {Y \circ Y} \right)^T}\)
\(\left[ {\begin{array}{*{20}{c}}
{\left\langle {{{\vec x}_1},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_1},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_1},{{\vec y}_n}} \right\rangle } \\
{\left\langle {{{\vec x}_2},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_2},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_2},{{\vec y}_n}} \right\rangle } \\
\vdots & \vdots & \ddots & \vdots \\
{\left\langle {{{\vec x}_m},{{\vec y}_1}} \right\rangle }&{\left\langle {{{\vec x}_m},{{\vec y}_2}} \right\rangle }& \cdots &{\left\langle {{{\vec x}_m},{{\vec y}_n}} \right\rangle }
\end{array}} \right] = \left[ {\begin{array}{*{20}{c}}
{\vec x_1^T} \\
{\vec x_2^T} \\
\vdots \\
{\vec x_m^T}
\end{array}} \right]\left[ {\begin{array}{*{20}{c}}
{{{\vec y}_1}}&{{{\vec y}_2}}& \cdots &{{{\vec y}_n}}
\end{array}} \right] = X{Y^T}\)
所以:
\(\left[ {\begin{array}{*{20}{c}}
{\left\| {{{\vec x}_1} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_1} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_1} - {{\vec y}_n}} \right\|_2^2} \\
{\left\| {{{\vec x}_2} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_2} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_2} - {{\vec y}_n}} \right\|_2^2} \\
\vdots & \vdots & \ddots & \vdots \\
{\left\| {{{\vec x}_m} - {{\vec y}_1}} \right\|_2^2}&{\left\| {{{\vec x}_m} - {{\vec y}_2}} \right\|_2^2}& \cdots &{\left\| {{{\vec x}_m} - {{\vec y}_n}} \right\|_2^2}
\end{array}} \right] = \left( {X \circ X} \right){\vec 1_k}\vec 1_n^T + {\vec 1_m}\vec 1_k^T{\left( {Y \circ Y} \right)^T} - 2X{Y^T}\)
上述转化式中出现了 \(X{Y^T}\) (矩阵乘) , 矩阵乘在 NumPy 等在很多库中都有高效的实现, 对代码的优化是有好处的.
代码实现
sklearn 中已经包含了用 NumPy 实现的计算 "两个矩阵的成对平方欧氏距离" 的函数 (sklearn.metrics.euclidean_distances), 它利用的就是上面的转化公式. 这里, 我们利用上面的转化公式并借鉴 sklearn, 用 NumPy 重新实现一个轻量级且易于理解的版本:
import numpy as np
def euclidean_distances(x, y, squared=True):
"""Compute pairwise (squared) Euclidean distances.
"""
assert isinstance(x, np.ndarray) and x.ndim == 2
assert isinstance(y, np.ndarray) and y.ndim == 2
assert x.shape[1] == y.shape[1]
x_square = np.sum(x*x, axis=1, keepdims=True)
if x is y:
y_square = x_square.T
else:
y_square = np.sum(y*y, axis=1, keepdims=True).T
distances = np.dot(x, y.T)
# use inplace operation to accelerate
distances *= -2
distances += x_square
distances += y_square
# result maybe less than 0 due to floating point rounding errors.
np.maximum(distances, 0, distances)
if x is y:
# Ensure that distances between vectors and themselves are set to 0.0.
# This may not be the case due to floating point rounding errors.
distances.flat[::distances.shape[0] + 1] = 0.0
if not squared:
np.sqrt(distances, distances)
return distances
如果想进一步加速, 可以将
x_square = np.sum(x*x, axis=1, keepdims=True)
替换为
x_square = np.expand_dims(np.einsum('ij,ij->i', x, x), axis=1)
将
y_square = np.sum(y*y, axis=1, keepdims=True).T
替换为
y_square = np.expand_dims(np.einsum('ij,ij->i', y, y), axis=0)
使用 np.einsum 的好处是不会产生一个和 x 或 y 同样形状的临时数组 (x*x
或 y*y
会产生一个和 x 或 y 同样形状的临时数组).
PyTorch 中也包含了计算 "两个矩阵的成对平方欧氏距离" 的函数, 不过它利用了如下的转化公式, 感兴趣的朋友可以自己用 NumPy 实现一下.
\(\begin{aligned}
\left( {X \circ X} \right){{\vec 1}_k}\vec 1_n^T + {{\vec 1}_m}\vec 1_k^T{\left( {Y \circ Y} \right)^T} - 2X{Y^T} &= \left[ {\begin{array}{*{20}{c}}
{ - 2X}&{\left( {X \circ X} \right){{\vec 1}_k}}&{{{\vec 1}_m}}
\end{array}} \right]\left[ {\begin{array}{*{20}{c}}
{{Y^T}} \\
{\vec 1_n^T} \\
{{{\left( {Y \circ Y} \right)}^T}}
\end{array}} \right] \\
&= \left[ {\begin{array}{*{20}{c}}
{ - 2X}&{\left( {X \circ X} \right){{\vec 1}_k}}&{{{\vec 1}_m}}
\end{array}} \right]{\left[ {\begin{array}{*{20}{c}}
Y&{{{\vec 1}_n}}&{Y \circ Y}
\end{array}} \right]^T} \\
\end{aligned}\)
参考
版权声明
版权声明:自由分享,保持署名-非商业用途-非衍生,知识共享3.0协议。
如果你对本文有疑问或建议,欢迎留言!转载请保留版权声明!
如果你觉得本文不错, 也可以用微信赞赏一下哈.
NumPy之计算两个矩阵的成对平方欧氏距离的更多相关文章
- 【机器学习实战】计算两个矩阵的成对距离(pair-wise distances)
矩阵中每一行是一个样本,计算两个矩阵样本之间的距离,即成对距离(pair-wise distances),可以采用 sklearn 或 scipy 中的函数,方便计算. sklearn: sklear ...
- python基础练习题(题目 计算两个矩阵相加)
day30 --------------------------------------------------------------- 实例044:矩阵相加 题目 计算两个矩阵相加. 分析:矩阵可 ...
- 实现两个矩阵相乘的C语言程序
程序功能:实现两个矩阵相乘的C语言程序,并将其输出 代码如下: #include "stdafx.h" #include "windows.h" void Mu ...
- 机器学习-文本数据-文本的相关性矩阵 1.cosing_similarity(用于计算两两特征之间的相关性)
函数说明: 1. cosing_similarity(array) 输入的样本为array格式,为经过词袋模型编码以后的向量化特征,用于计算两两样本之间的相关性 当我们使用词频或者TFidf构造出 ...
- OpenCV,计算两幅图像的单应矩阵
平面射影变换是关于其次3维矢量的一种线性变换,可以使用一个非奇异的$3 \times 3$矩阵H表示,$X' = HX$,射影变换也叫做单应(Homography).计算出两幅图像之间的单应矩阵H,那 ...
- Python的工具包[0] -> numpy科学计算 -> numpy 库及使用总结
NumPy 目录 关于 numpy numpy 库 numpy 基本操作 numpy 复制操作 numpy 计算 numpy 常用函数 1 关于numpy / About numpy NumPy系统是 ...
- Numpy科学计算
NumPy介绍 NumPy(Numerical Python)是一个开源的Python科学计算库,用于快速处理任意维度的数组. NumPy支持常见的数组和矩阵操作.对于同样的数值计算任务,使用Nu ...
- 利用编辑距离(Edit Distance)计算两个字符串的相似度
利用编辑距离(Edit Distance)计算两个字符串的相似度 编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数.许可 ...
- 【OpenCV学习】计算两幅图像的重叠区域
问题描述:已知两幅图像Image1和Image2,计算出两幅图像的重叠区域,并在Image1和Image2标识出重叠区域. 算法思想: 若两幅图像存在重叠区域,则进行图像匹配后,会得到一张完整的全景图 ...
随机推荐
- java ->多线程_线程池
线程池概念 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 我们详细的解释一下为什么要使用线程池?(程序优化) 在jav ...
- Sentinel源码解析一(流程总览)
引言 Sentinel作为ali开源的一款轻量级流控框架,主要以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度来帮助用户保护服务的稳定性.相比于Hystrix,Sentinel的设计更加简 ...
- 00004-form 表单的清空、重置 (jquery)
form 表单的清空.重置 $(':input').not(':button, :submit, :reset').val('').removeAttr('selected').removeAttr( ...
- Java创建线程的方式
Java中线程的创建有四i种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 3.应用程序 ...
- React面试题汇总
1.如何理解React中的组件间数据传递? ①父-子 通过props传递 ②子-父 在父中创建一个可以修改state的方法,之后把这个方法通过props传递给子,在子中调用这个方法 从而达到修改父 ...
- Python 图像处理 OpenCV (4):图像算数运算以及修改颜色空间
前文传送门: 「Python 图像处理 OpenCV (1):入门」 「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」 「Python ...
- 21-8 数据检索2 top和distinct
--distinct关键字,根据已经查询出的结果然后去除重复 select distinct * from TblStudent --Top(一般会配合order by一起使用) ---------- ...
- 8.8SQL Server数据类型介绍1
image类型:存储二进制字节数组. (相当于C#中的byte[]字节类型) sql server常用的数据类型 1.image类型,用来存储byte[](字节). 2.字符串类型 char ncha ...
- Hystrix入门
hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用 ...
- 如何在Teamcenter中使用PMI?
1 .什么是PMI 在设计制造领域,PMI指的是产品制造信息(Productand Manufacturing Information),其目的在于在三维环境下,将制造信息从设计部门传递到制造部门.其 ...