小能豆

快速加权散度矩阵计算

py

在六个月前的这个问题中,jez 很好地帮助我提出了行差外积的快速近似值,即:

K = np.zeros((len(X), len(X)))
for i, Xi in enumerate(X):
  for j, Xj in enumerate(X):
    dij = Xi - Xj
    K += np.outer(dij, dij)

这对于找到一种 Fisher 判别分析形式的散点矩阵计算很有效。但现在我正尝试进行局部 Fisher 判别分析,其中每个外积都由一个矩阵 A 加权,该矩阵包含有关该对局部性的信息,因此新行是:

K += A[i][j] * np.outer(dij, dij)

不幸的是,前面答案中提出的计算非加权散点矩阵的快速方法对此不起作用,而且据我所知,进行快速改变并不容易。

线性代数绝对不是我的强项,我不擅长想出这类东西。有什么快速方法可以计算成对行差外积的加权和?


阅读 15

收藏
2024-11-17

共1个答案

小能豆

在你的问题中,你希望计算以下形式的加权和:

[
K = \sum_{i,j} A[i][j] \cdot (\mathbf{X}_i - \mathbf{X}_j)(\mathbf{X}_i - \mathbf{X}_j)^T
]

以下是利用 NumPy 提供的高效广播和 einsum 运算来解决此问题的方法。


问题分析

给定:
- ( \mathbf{X} ) 是一个形状为 ((N, D)) 的矩阵,表示 (N) 个样本的 (D) 维特征。
- ( A ) 是一个 (N \times N) 的加权矩阵,表示样本对的权重。

目标是高效地计算加权的成对行差外积的和。


高效解决方案

步骤 1:计算成对差

首先需要计算成对差值 ( \mathbf{X}_i - \mathbf{X}_j )。这可以通过 NumPy 的广播机制完成:

import numpy as np

# 示例数据
N, D = 100, 10  # N 个数据点,D 维特征
X = np.random.rand(N, D)  # 形状 (N, D)
A = np.random.rand(N, N)  # 形状 (N, N)

# 计算成对差
X_diff = X[:, np.newaxis, :] - X[np.newaxis, :, :]  # 形状 (N, N, D)
  • X[:, np.newaxis, :] 添加一个新轴,形状变为 ((N, 1, D))。
  • X[np.newaxis, :, :] 添加一个新轴,形状变为 ((1, N, D))。
  • 两者相减后,得到一个 ((N, N, D)) 的张量,其中每个条目 ((i, j)) 是 ( \mathbf{X}_i - \mathbf{X}_j )。

步骤 2:计算加权外积

对每对样本 ( (i, j) ),需要计算 ( (\mathbf{X}_i - \mathbf{X}_j)(\mathbf{X}_i - \mathbf{X}_j)^T ),并乘以权重 ( A[i][j] )。这可以通过 np.einsum 高效完成:

# 计算加权外积的加权和
K = np.einsum('ij,ijk,ijl->kl', A, X_diff, X_diff)
  • einsum 中的 'ij,ijk,ijl->kl' 表示:
  • ij 代表加权矩阵 ( A[i][j] ) 的权重。
  • ijkijl 分别代表行差张量的两个维度,计算外积。
  • kl 表示最终结果是一个形状为 ((D, D)) 的矩阵 ( K )。

为什么此方法高效?

  1. 广播机制: 避免了显式的双重循环,直接在数组上操作。
  2. einsum 优化: 将加权计算、外积运算和求和合并为一个高效的操作。
  3. 内存效率: 虽然 X_diff 占用一定内存,但计算过程避免了大量临时变量。

完整代码示例

以下是完整实现:

import numpy as np

# 示例数据
N, D = 100, 10  # N 个数据点,D 维特征
X = np.random.rand(N, D)  # 形状 (N, D)
A = np.random.rand(N, N)  # 形状 (N, N)

# 计算成对差
X_diff = X[:, np.newaxis, :] - X[np.newaxis, :, :]  # 形状 (N, N, D)

# 计算加权外积的加权和
K = np.einsum('ij,ijk,ijl->kl', A, X_diff, X_diff)  # 形状 (D, D)

print("加权散点矩阵的形状:", K.shape)

复杂度分析

  1. 时间复杂度: ( O(N^2 D) ),与双重循环的方法时间复杂度相同,但在 NumPy 的底层实现中优化了操作。
  2. 空间复杂度: ( O(N^2 D) ),主要来自于 X_diff 的存储。

如果 ( N ) 很大,内存可能会成为瓶颈,可以考虑将数据分块处理(例如通过分块计算 X_diff 或使用稀疏矩阵)。


总结

使用 np.einsum 配合广播机制是计算这种加权和的最优雅和高效的方式。它避免了 Python 的显式循环,充分利用了 NumPy 的矢量化能力,适合处理中等规模的 ( N ) 和 ( D )。

2024-11-17