问题描述

设 \({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*xy*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之计算两个矩阵的成对平方欧氏距离的更多相关文章

  1. 【机器学习实战】计算两个矩阵的成对距离(pair-wise distances)

    矩阵中每一行是一个样本,计算两个矩阵样本之间的距离,即成对距离(pair-wise distances),可以采用 sklearn 或 scipy 中的函数,方便计算. sklearn: sklear ...

  2. python基础练习题(题目 计算两个矩阵相加)

    day30 --------------------------------------------------------------- 实例044:矩阵相加 题目 计算两个矩阵相加. 分析:矩阵可 ...

  3. 实现两个矩阵相乘的C语言程序

    程序功能:实现两个矩阵相乘的C语言程序,并将其输出 代码如下: #include "stdafx.h" #include "windows.h" void Mu ...

  4. 机器学习-文本数据-文本的相关性矩阵 1.cosing_similarity(用于计算两两特征之间的相关性)

    函数说明: 1. cosing_similarity(array)   输入的样本为array格式,为经过词袋模型编码以后的向量化特征,用于计算两两样本之间的相关性 当我们使用词频或者TFidf构造出 ...

  5. OpenCV,计算两幅图像的单应矩阵

    平面射影变换是关于其次3维矢量的一种线性变换,可以使用一个非奇异的$3 \times 3$矩阵H表示,$X' = HX$,射影变换也叫做单应(Homography).计算出两幅图像之间的单应矩阵H,那 ...

  6. Python的工具包[0] -> numpy科学计算 -> numpy 库及使用总结

    NumPy 目录 关于 numpy numpy 库 numpy 基本操作 numpy 复制操作 numpy 计算 numpy 常用函数 1 关于numpy / About numpy NumPy系统是 ...

  7. Numpy科学计算

    NumPy介绍   NumPy(Numerical Python)是一个开源的Python科学计算库,用于快速处理任意维度的数组. NumPy支持常见的数组和矩阵操作.对于同样的数值计算任务,使用Nu ...

  8. 利用编辑距离(Edit Distance)计算两个字符串的相似度

    利用编辑距离(Edit Distance)计算两个字符串的相似度 编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数.许可 ...

  9. 【OpenCV学习】计算两幅图像的重叠区域

    问题描述:已知两幅图像Image1和Image2,计算出两幅图像的重叠区域,并在Image1和Image2标识出重叠区域. 算法思想: 若两幅图像存在重叠区域,则进行图像匹配后,会得到一张完整的全景图 ...

随机推荐

  1. Oracle细粒度审计

    场景 管理信息化应用,想审计某张表的数据是否做了删除.Oracle中专门有自带的函数.可以满足这个需求 1.查询审计日志的语句 select timestamp, db_user, os_user, ...

  2. mysql 一台服务器中装两个mysql

    个人经验: 服务器中已有mysql5.0 现要安装mysql5.5 下载安装包,安装后,mysql5.5中没有my.ini文件,就在我自己的电脑上复制了mysql5.5的my.ini文件进去. 1.在 ...

  3. iframe中请求页面而session失效时页面跳转问题

    iframe中请求页面而session失效时页面跳转问题 分类: Web2009-12-11 15:01 656人阅读 评论(0) 收藏 举报 sessioniframejsp 有时候做了就忘了,我记 ...

  4. Java面试题小结(一)

    ---恢复内容开始--- 1.用Java解析xml有几种方式,实现场景.区别以及优缺点: 有四种 分别是JDOM.SEX.DOM.JDOM4J 实现场景: JDOM:实现功能简单的地方,例如:解析: ...

  5. 27-1 分组-having

    group by select * from TblStudent --1.请从学生表中查询出每个班的班级id和班级人数 select tsclassId as 班级id, 班级人数=count(*) ...

  6. 加密通信软件Signal 2.92版本编译安装折腾手记(Ubuntu 18.04)

    加密通信软件Signal 2.92版本编译安装折腾手记(Ubuntu 18.04) 前言 加密通信软件Signal是开源的,安全性很高,号称斯诺登也推荐大家使用.既然这么好,那必然会有不少人去尝试复制 ...

  7. 本地安装并运行http-server、browser-sync、webpack

    有一些自带命令的辅助开发.测试类的工具,官方都推荐全局安装,如果不想全局安装只想在某个项目下用该怎么办呢? 如http-server.browser-sync.webpack这种自带CLI的工具 使用 ...

  8. mybatis的多表联查

    多对一连表查询简单记录

  9. 脚本学习一(echo、echo off、@、start)

    1.echo表示显示此命令后的字符 脚本: 输出结果: 2.echo off表示在此语句后所有运行的命令都不显示命令行本身 脚本: 输出结果: 3.@与echo off相象,但它是加在每个命令行的最前 ...

  10. [Unity2d系列教程] 002.引用外部DLL - C

    上一篇我们学习了Unity调用C#生成的外部DLL,但是有时候我们需要访问底层,不能不适用C生成的DLL.下面就让我们一起学习下,C如何生成. 1.创建一个C的控制台程序 2.点击确定->点击下 ...