加速地面集中所有对之间的L1距离

时间:2015-05-25 22:17:52

标签: c++ algorithm matrix parallel-processing eigen

我有一个描述地面集的矩阵NxM(通常是10k X 10k元素)。每行代表一个对象,每列代表一个特定的特征。例如,在矩阵

   f1 f2 f3
x1 0  4  -1
x2 1  0  5
x3 4  0  0
x4 0  1  0

对象x1在特征1中具有值0,在特征1中具有值4,在特征-1中具有值0。这个值是一般实数(double's)。

我必须在所有对象对之间计算几个自定义距离/不相似度(所有对线)。为了比较,我想计算L1(曼哈顿)和 L2(欧几里德)距离。

我使用Eigen库来执行我的大部分计算。为了计算L2(欧几里德),我使用以下观察:对于大小 n 的两个向量 a b ,我们有:

||a - b||^2 = (a_1 - b_1)^2 + (a_2 - b_2)^2 + ... +(a_n - b_n)^2
            = a_1^2 + b_1^2 - 2 a_1 b_1 + a_2^2 + b_2^2 - 2 a_2 b_2 + ... + a_n^2 + b_n^2 - 2 a_n b_n
            = a . a + b . b - 2ab

换句话说,我们使用矢量的点积来重写平方范数,并将它们之间的点积减去两倍。从那以后,我们就走广场,我们就完成了。最近,我在很久以前就找到了这个技巧,不幸的是我丢失了作者的参考资料。

无论如何,这使得能够使用Eigen(在C ++中)编写一个奇特的代码:

Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> matrix, XX, D;

// Load matrix here, for example
// matrix << 0, 4, -1,
//           1, 0,  5,
//           4, 0,  0,
//           0, 1,  0;

const auto N = matrix.rows();

XX.resize(N, 1);
D.resize(N, N);

XX = matrix.array().square().rowwise().sum();

D.noalias() = XX * Eigen::MatrixXd::Ones(1, N) +
              Eigen::MatrixXd::Ones(N, 1) * XX.transpose();

D -= 2 * matrix * matrix.transpose();
D = D.cwiseSqrt();

对于矩阵10k X 10k,我们能够在不到1分钟(2个核心/ 4个线程)内计算所有对象/线对的L2距离,我个人认为这对我的目的来说是一个很好的表现。 Eigen能够组合操作并使用几个低/高级优化来执行这些计算。在这种情况下,Eigen使用所有可用的核心(当然,我们可以调整它)。

然而,我仍然需要计算L1距离,但我无法找出与Eigen一起使用的良好代数形式并获得良好的性能。到现在为止,我有以下内容:

const auto N = matrix.rows();
for(long i = 0; i < N - 1; ++i) {
    const auto &row = matrix.row(i);

    #ifdef _OPENMP
    #pragma omp parallel for shared(row)
    #endif
    for(long j = i + 1; j < N; ++j) {
        distance(i, j) = (row - matrix.row(j)).lpNorm<1>();
    }
}

但这需要更长的时间:对于相同的10k X 10k矩阵,此代码使用3.5分钟,考虑到L1和L2非常接近其原始形式,情况要糟糕得多:

L1(a, b) = sum_i |a_i - b_i|
L2(a, b) = sqrt(sum_i |a_i - b_i|^2)

如何更改L1以使用与Eigen的快速计算?或者是一个更好的形式来做到这一点,我只是没想到。

非常感谢你的帮助!

卡洛斯

2 个答案:

答案 0 :(得分:2)

让我们同时计算两个距离。他们只是真正分享行差异(虽然两者都可能是绝对差异,欧几里德距离使用的是平方,但实际上并不相同)。所以现在我们只循环遍历n ^ 2行。

const auto N = matrix.rows();
for(long i = 0; i < N - 1; ++i) {
    const auto &row = matrix.row(i);

    #ifdef _OPENMP
    #pragma omp parallel for shared(row)
    #endif
    for(long j = i + 1; j < N; ++j) {
        const auto &rowDiff = row - matrix.row(j);
        distanceL1(i, j) = rowDiff.cwiseAbs().sum(); // or .lpNorm<1>(); if it's faster
        distanceL2(i, j) = rowDiff.norm()
    }
}

编辑另一种内存密集型/未经测试的方法可能是每次迭代计算整个距离行(不知道这是否会有所改善)

const auto N = matrix.rows();
#ifdef _OPENMP
#pragma omp parallel for shared(matrix)
#endif
for(long i = 0; i < N - 1; ++i) {
    const auto &row = matrix.row(i);
    // you could use matrix.block(i,j,k,l) to cut down on the number of unnecessary operations
    const auto &mat = matrix.rowwise() - row;

    distanceL1(i) = mat.cwiseAbs().sum().transpose();
    distanceL2(i) = mat.rowwise().norm().transpose();
}

答案 1 :(得分:0)

这是图像处理中两种非常常见的操作。第一个是Sum of Squared Differences (SSD),第二个是Sum of Absolute Differences (SAD)

您已经正确地确定SSD只需要一个来计算两个系列之间的cross-correlation作为主要计算。 但是,您可能需要考虑使用FFT来计算这些a.b项,它将减少L2案例所需的操作数量(不过我不知道多少,这取决于什么矩阵 - 矩阵乘法算法特征使用。)如果你需要我解释一下,我可以,but I figure you can also look it up as its a standard use of FFTsOpenCV有一个(非常糟糕/错误)模板匹配的实现,这是你在使用CV_TM_SQDIFF时想要的。

L1案件比较棘手。 L1情况不能很好地分解,但它也是你可以做的最简单的操作之一(只是加法和绝对值。)因此,a lot of computation architectures have parallelized implementations作为指令或硬件实现的函数。其他架构有researchers experimenting with the best way to compute this.

您可能还想查看Intel Imaging Primitives的互相关和快速FFT库,例如FFTW&amp; CUFFT。如果您买不起Intel Imaging Primitves,您可以使用SSE instructions大大加快处理速度,达到几乎相同的效果。