Numpy Julia中的批处理矩阵乘法

Numpy Julia中的批处理矩阵乘法,numpy,julia,matrix-multiplication,Numpy,Julia,Matrix Multiplication,我试图将Julia中的N维N>=3个数组作为矩阵的批次进行乘法,即沿最后两个维度执行矩阵乘法,保持其他维度不变 例如,如果x的维数为d1、d2、4、3,y的维数为d1、d2、3、2,则乘法结果应为d1、d2、4、2,即应执行一批矩阵乘法 这正是Python中发生的情况: 如果任一参数为N-D,N>2,则将其视为驻留在最后两个索引中的矩阵堆栈,并相应地进行广播 np.matmulrandn10,10,4,3,randn10,10,3,2.shape 10, 10, 4, 2 有没有办法重现《朱莉娅

我试图将Julia中的N维N>=3个数组作为矩阵的批次进行乘法,即沿最后两个维度执行矩阵乘法,保持其他维度不变

例如,如果x的维数为d1、d2、4、3,y的维数为d1、d2、3、2,则乘法结果应为d1、d2、4、2,即应执行一批矩阵乘法

这正是Python中发生的情况:

如果任一参数为N-D,N>2,则将其视为驻留在最后两个索引中的矩阵堆栈,并相应地进行广播

np.matmulrandn10,10,4,3,randn10,10,3,2.shape 10, 10, 4, 2 有没有办法重现《朱莉娅》中numpy.matmul的行为

我希望。*能起作用,但:

julia> randn(10,10,4,3) .* randn(10,10,3,2)
ERROR: DimensionMismatch("arrays could not be broadcast to a common size")
Stacktrace:
 [1] _bcs1 at ./broadcast.jl:485 [inlined]
 [2] _bcs at ./broadcast.jl:479 [inlined] (repeats 3 times)
 [3] broadcast_shape at ./broadcast.jl:473 [inlined]
 [4] combine_axes at ./broadcast.jl:468 [inlined]
 [5] instantiate at ./broadcast.jl:256 [inlined]
 [6] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{4},Nothing,typeof(*),Tuple{Array{Float64,4},Array{Float64,4}}}) at ./broadcast.jl:798
 [7] top-level scope at REPL[80]:1
我知道列表理解可能在三维中有效,但在更高的维度中会变得非常混乱。最好的解决方案是重新塑造或查看除最后两个维度以外的所有维度,使用列表理解,然后重新塑造它吗?还是有更好的办法


另外,我能找到的最接近的东西是,但不完全一样。Julia是新手,因此可能缺少Julia用户显而易见的功能。

我不知道有任何这样的功能,但可能在某些软件包中有。我认为在Julia中,更自然的做法是将数据组织为矩阵数组,并在其上广播矩阵乘法:

D = [rand(50, 60) for i in 1:4, j in 1:3]
E = [rand(60, 70) for i in 1:4, j in 1:3]
D .* E  # now you can use dot broadcasting!
也就是说,你很容易做出自己的决定。不过,我会做一个改变。Julia是column major,而numpy是最后一个维度major,因此您应该让矩阵沿前两个维度而不是后两个维度驻留

首先,我将定义一个就地方法,将其乘以数组C,然后定义一个调用就地版本的非就地方法,我将跳过维度检查等:

# In-place version, note the use of the @views macro, 
# which is essential to get in-place behaviour

using LinearAlgebra: mul!  # fast in-place matrix multiply

function batchmul!(C, A, B)
    for j in axes(A, 4), i in axes(A, 3)
        @views mul!(C[:, :, i, j], A[:, :, i, j], B[:, :, i, j])
    end
    return C
end

# The non-in-place version
function batchmul(A, B)
    T = promote_type(eltype(A), eltype(B))
    C = Array{T}(undef, size(A, 1), size(B)[2:end]...)
    return batchmul!(C, A, B)
end
您还可以将其设置为多线程。在我的计算机上,4个线程的加速比为2.5倍实际上,对于最后两个维度的较大值,我得到的加速比为3.5倍您得到的加速比取决于所涉及阵列的大小和形状:

function batchmul!(C, A, B)
    Threads.@threads for j in axes(A, 4)
        for i in axes(A, 3)
            @views mul!(C[:, :, i, j], A[:, :, i, j], B[:, :, i, j])
        end
    end
    return C
end
编辑:我刚才注意到你想要的是一般的N-D,而不仅仅是4-D。不过,概括起来应该不会太难。无论如何,我们更有理由选择矩阵数组,因为广播将自动适用于所有维度

Edit2:无法离开它,因此对于N-D的情况,这里有一个,还有更多的事情要做,比如处理基于非1的索引更新:Axis应该解决这个问题:

function batchmul!(C, A, B)
    Threads.@threads for I in CartesianIndices(axes(A)[3:end])
        @views mul!(C[:, :, Tuple(I)...], A[:, :, Tuple(I)...], B[:, :, Tuple(I)...])
    end
    return C
end

我不知道有任何这样的功能,但在某些软件包中可能有。我认为在Julia中,更自然的做法是将数据组织为矩阵数组,并在其上广播矩阵乘法:

D = [rand(50, 60) for i in 1:4, j in 1:3]
E = [rand(60, 70) for i in 1:4, j in 1:3]
D .* E  # now you can use dot broadcasting!
也就是说,你很容易做出自己的决定。不过,我会做一个改变。Julia是column major,而numpy是最后一个维度major,因此您应该让矩阵沿前两个维度而不是后两个维度驻留

首先,我将定义一个就地方法,将其乘以数组C,然后定义一个调用就地版本的非就地方法,我将跳过维度检查等:

# In-place version, note the use of the @views macro, 
# which is essential to get in-place behaviour

using LinearAlgebra: mul!  # fast in-place matrix multiply

function batchmul!(C, A, B)
    for j in axes(A, 4), i in axes(A, 3)
        @views mul!(C[:, :, i, j], A[:, :, i, j], B[:, :, i, j])
    end
    return C
end

# The non-in-place version
function batchmul(A, B)
    T = promote_type(eltype(A), eltype(B))
    C = Array{T}(undef, size(A, 1), size(B)[2:end]...)
    return batchmul!(C, A, B)
end
您还可以将其设置为多线程。在我的计算机上,4个线程的加速比为2.5倍实际上,对于最后两个维度的较大值,我得到的加速比为3.5倍您得到的加速比取决于所涉及阵列的大小和形状:

function batchmul!(C, A, B)
    Threads.@threads for j in axes(A, 4)
        for i in axes(A, 3)
            @views mul!(C[:, :, i, j], A[:, :, i, j], B[:, :, i, j])
        end
    end
    return C
end
编辑:我刚才注意到你想要的是一般的N-D,而不仅仅是4-D。不过,概括起来应该不会太难。无论如何,我们更有理由选择矩阵数组,因为广播将自动适用于所有维度

Edit2:无法离开它,因此对于N-D的情况,这里有一个,还有更多的事情要做,比如处理基于非1的索引更新:Axis应该解决这个问题:

function batchmul!(C, A, B)
    Threads.@threads for I in CartesianIndices(axes(A)[3:end])
        @views mul!(C[:, :, Tuple(I)...], A[:, :, Tuple(I)...], B[:, :, Tuple(I)...])
    end
    return C
end

对于N=3,您正在寻找NNlib.batched\u mul。请注意,如上所述,Julia的数组是列主数组,因此将最后一个索引(而不是第一个索引)视为在批上运行通常是有意义的:

julia> using NNlib

julia> randn(4,3,100) ⊠ randn(3,2,100)
4×2×100 Array{Float64, 3}:
[:, :, 1] =
  0.9292     -0.223521
...
这只是一个像batchmul一样的循环!C、 但它也将为GPU阵列调用相应的库函数

将其扩展到3个以上的维度并不困难,但必须有人来做,并决定规则。对于第三维,其行为类似于广播:

julia> randn(4,3,100) ⊠ randn(3,2) |> size
(4, 2, 100)

julia> randn(4,3,100) ⊠ randn(3,2,1) |> size
(4, 2, 100)

julia> try randn(4,3,100) ⊠ randn(3,2,2) catch e println(e) end
DimensionMismatch("batch size mismatch: A != B")

对于N=3,您正在寻找NNlib.batched\u mul。请注意,如上所述,Julia的数组是列主数组,因此将最后一个索引(而不是第一个索引)视为在批上运行通常是有意义的:

julia> using NNlib

julia> randn(4,3,100) ⊠ randn(3,2,100)
4×2×100 Array{Float64, 3}:
[:, :, 1] =
  0.9292     -0.223521
...
这只是一个像batchmul一样的循环!C、 但它也将为GPU阵列调用相应的库函数

将其扩展到3个以上的维度并不困难,但必须有人来做,并决定规则。对于第三维,其行为类似于广播:

julia> randn(4,3,100) ⊠ randn(3,2) |> size
(4, 2, 100)

julia> randn(4,3,100) ⊠ randn(3,2,1) |> size
(4, 2, 100)

julia> try randn(4,3,100) ⊠ randn(3,2,2) catch e println(e) end
DimensionMismatch("batch size mismatch: A != B")

我倾向于选择N-D数组而不是数组数组,因为它们更容易检查,例如,大小给了我一个很好的N元组,但在这种情况下,第一个解决方案似乎是最自然的。蔑视
不过,我会保留通用的N-D解决方案作为参考,谢谢你的回答!对于“阵列矩阵”解决方案,请记住,您可以使用mul在适当的位置执行此操作!:因为我在每一个角落!不幸的是,C[i]、A[i]、B[i]端点无法正常工作。我倾向于选择N-D数组,而不是数组数组数组,因为它们更容易检查,例如大小给了我一个很好的N元组,但在这种情况下,第一种解决方案似乎是最自然的。当然要保留通用的N-D解决方案作为参考,谢谢你的回答!对于“阵列矩阵”解决方案,请记住,您可以使用mul在适当的位置执行此操作!:因为我在每一个角落!不幸的是,C[i]、A[i]、B[i]端点无法在适当的位置工作。