Julia 中矩阵之间的逐行运算

Row-wise operations between matrices in Julia

我正在尝试将以下 Python 代码(来自 SMT GEKPLS)的等价物翻译成 Julia:

def differences(X, Y):
    D = X[:, np.newaxis, :] - Y[np.newaxis, :, :]
    return D.reshape((-1, X.shape[1]))

所以,给定这样的输入:

X = np.array([[1.0,1.0,1.0], [2.0,2.0,2.0]])
Y = np.array([[1.0,2.0,3.0], [4.0,5.0,6.0], [7.0,8.0,9.0]])
diff = differences(X,Y)

我们得到如下所示的输出(差异):

[[ 0. -1. -2.]
 [-3. -4. -5.]
 [-6. -7. -8.]
 [ 1.  0. -1.]
 [-2. -3. -4.]
 [-5. -6. -7.]]

使用 Julia 代码执行此操作的有效方法是什么?我希望 X 和 Y 输入矩阵非常大。

经过一番思考,得出了这个函数:

function differences(X, Y)
    Rx = repeat(X, inner=(size(Y, 1), 1))
    Ry = repeat(Y, size(X, 1))
    Rx - Ry
end

希望对您有所帮助。

这是一个避免 repeat 的版本,它会产生不必要的数据重复:

function diffs_row(X, Y)
    N = size(X, 2)
    return reshape(reshape(X', 1, N, :) .- Y', N, :)'
end

所有伴随词 ' 的原因是在 Julia 中操作 row-wise 并不是很自然。 Julia 数组是 column-major,因此 reshape 将检索数据 column-wise。如果您决定改为更改数据的方向,您可以写

function diffs_col(X, Y)
    N = size(X, 1)
    return reshape(reshape(X, N, 1, :) .- Y, N, :)
end

相反。

人们在将 numpy 代码翻译成 Julia 时经常看到这一点。 Numpy 是原生的 row-major,所以翻译起来有点别扭。在许多情况下,您应该考虑将数据布局更改为主要列。

这可能比其他替代方案更快,同时仍然易于理解。

[x .- y for x ∈ X for y ∈ Y]

6-element Vector{Vector{Float64}}:
 [0.0, -1.0, -2.0]
 [-3.0, -4.0, -5.0]
 [-6.0, -7.0, -8.0]
 [1.0, 0.0, -1.0]
 [-2.0, -3.0, -4.0]
 [-5.0, -6.0, -7.0]

我不喜欢 numpy 的一件事是,必须准确记住每个函数以及输入参数的组合。在 Julia 中,传统循环可以作为大多数算法的有效 drop-in 替代。

附录: 以上可能是我所说的最快的解决方案,前提是使用 Vector{Vector{Float64}} 不是问题。如果是,这是另一个输出 Matrix{Float64} 同时速度也很快的解决方案。

function diffr(X,Y) 
    i, l, m, n = 0, length(first(X)), length(X), length(Y)
    Z = Matrix{Float64}(undef, m*n, l)
    for x in X, y in Y
        Z[i+=1,:] .= x .- y
    end
    Z
end

这里是我电脑上所有已发布解决方案的性能比较。

@btime [x.-y for x∈$X for y∈$Y] # 312.245 ns (9  allocations: 656 bytes)
@btime diffr($X, $Y)            #  73.868 ns (1  allocation:  208 bytes)
@btime differences($X, $Y)      # 439.000 ns (12 allocations: 896 bytes)
@btime diffs_row($X, $Y)        # 463.131 ns (11 allocations: 784 bytes)