Julia 计算多元正态密度部分的有效方法

Julia 计算多元正态密度部分的有效方法,julia,Julia,我对计算数量感兴趣: 其中,x_i是1xD向量(维度为D的N个数据中的一个),μ是DxK矩阵,W是K DxD矩阵列表 这将产生1XK向量。我以以下方式尝试所有N和K: res = zeros(N,K); for i in 1:N for k in 1:K res[i,k] = (x_matrix[i,:]-mus_matrix[:,k])'* w_matrix[k]*(x_matrix[i,:]-mus_matrix[:,k])

我对计算数量感兴趣:

其中,x_i是1xD向量(维度为D的N个数据中的一个),μ是DxK矩阵,W是K DxD矩阵列表

这将产生1XK向量。我以以下方式尝试所有N和K:

res = zeros(N,K);
for i in 1:N
    for k in 1:K
        res[i,k] = (x_matrix[i,:]-mus_matrix[:,k])'*
                   w_matrix[k]*(x_matrix[i,:]-mus_matrix[:,k])
如果我尝试使用以下方法将其矢量化:

 res = zeros(N,K);
for i in 1:N
        res[i,:] = (x_matrix[i,:].-mus_matrix)'.*w_matrix.*(x_matrix[i,:].-mus_matrix)
我得到以下错误:

ERROR: DimensionMismatch("arrays could not be broadcast to a common size")
Stacktrace:
 [1] _bcs1(::Base.OneTo{Int64}, ::Base.OneTo{Int64}) at ./broadcast.jl:70
 [2] _bcs at ./broadcast.jl:63 [inlined]
 [3] broadcast_shape(::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Vararg{Tuple{Base.OneTo{Int64},Base.OneTo{Int64}},N} where N) at ./broadcast.jl:57 (repeats 3 times)
 [4] broadcast_indices(::Array{Float64,2}, ::Array{Any,1}, ::Array{Float64,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:53
 [5] broadcast_c(::Function, ::Type{Array}, ::Array{Float64,2}, ::Array{Any,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:311
 [6] broadcast(::Function, ::Array{Float64,2}, ::Array{Any,1}, ::Array{Float64,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:434
以下是一个例子:

julia> N = 5
5

julia> D=2
2

julia> K = 4
4

julia> W=[]
0-element Array{Any,1}

julia> x = rand(N,D)
5×2 Array{Float64,2}:
 0.576477  0.9575  
 0.184454  0.660436
 0.470267  0.729649
 0.648879  0.782561
 0.626453  0.111332

julia> mu = rand(K,D)
4×2 Array{Float64,2}:
 0.989281  0.00126782
 0.659106  0.66136   
 0.50843   0.289442  
 0.327962  0.523229  

julia> for i in 1:K
           push!(W,rand(D,D))
       end
然后跑

julia> (x_matrix[i,:]-mus_matrix[:,k])'*
                               w_matrix[k]*(x_matrix[i,:]-mus_matrix[:,k])
34649.850360744866
但是第二个代码

julia> (x_matrix[i,:].-mus_matrix)'.*w_matrix.*(x_matrix[i,:].-mus_matrix)
ERROR: DimensionMismatch("arrays could not be broadcast to a common size")
Stacktrace:
 [1] _bcs1(::Base.OneTo{Int64}, ::Base.OneTo{Int64}) at ./broadcast.jl:70
 [2] _bcs at ./broadcast.jl:63 [inlined]
 [3] broadcast_shape(::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Vararg{Tuple{Base.OneTo{Int64},Base.OneTo{Int64}},N} where N) at ./broadcast.jl:57 (repeats 3 times)
 [4] broadcast_indices(::Array{Float64,2}, ::Array{Any,1}, ::Array{Float64,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:53
 [5] broadcast_c(::Function, ::Type{Array}, ::Array{Float64,2}, ::Array{Any,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:311
 [6] broadcast(::Function, ::Array{Float64,2}, ::Array{Any,1}, ::Array{Float64,1}, ::Vararg{Any,N} where N) at ./broadcast.jl:434

TL/DR:下面是优化的变体,但Einsum看起来更好,IMHO


看起来像是一个使用的案例。在Julia中,您可以这样做:

julia> N = 5
5

julia> D = 3
3

julia> K = 10
10

julia> x = rand(N, D)
5×3 Array{Float64,2}:
 0.587436  0.210529  0.261725
 0.527269  0.457477  0.482939
 0.52726   0.411209  0.138872
 0.89107   0.464789  0.758392
 0.885267  0.931014  0.672959

julia> μ = rand(D, K)
3×10 Array{Float64,2}:
 0.280792   0.265066   0.81437   0.503377  0.0717916  …  0.275872  0.609961   0.0820088  0.0042564
 0.0177643  0.0959438  0.563948  0.332433  0.088527      0.691971  0.0296638  0.604488   0.956057 
 0.668128   0.444816   0.74203   0.518232  0.48689       0.465067  0.117469   0.729514   0.109973 

julia> W = rand(K, D, D)
10×3×3 Array{Float64,3}:
[:, :, 1] =
 0.320861   0.662103  0.219234
 0.780944   0.769377  0.566203
 0.466207   0.428527  0.330901
 0.15534    0.035435  0.346737
 0.810676   0.328116  0.469505
 0.676575   0.668204  0.285334
 0.455551   0.211295  0.85295 
 0.229995   0.741487  0.783361
 0.0937583  0.401419  0.47032 
 0.956335   0.434213  0.967791

[:, :, 2] =
 0.275903   0.130298   0.184485
 0.941648   0.940107   0.439454
 0.425292   0.252654   0.797115
 0.0203406  0.594075   0.484809
 0.164309   0.941597   0.455314
 0.73628    0.109502   0.920664
 0.906305   0.177235   0.540193
 0.360038   0.0486971  0.20626 
 0.914357   0.699901   0.295872
 0.284143   0.659117   0.291479

[:, :, 3] =
 0.138311   0.921371  0.353719
 0.345247   0.70865   0.246736
 0.361364   0.636543  0.343837
 0.752149   0.581561  0.346399
 0.705888   0.24765   0.703952
 0.992327   0.369668  0.109407
 0.341624   0.223715  0.970667
 0.762169   0.94248   0.917569
 0.0367128  0.589345  0.121106
 0.826602   0.692111  0.229499

julia> using Einsum

julia> @einsum r[n,k] := (x[n,i] - μ[i,k]) * W[k,i,j] * (x[n,j] - μ[j,k])

julia> r
5×10 Array{Float64,2}:
  0.0176889  0.087092   0.522184    0.0417967   …  -0.0430999   0.041266   -0.0596579  0.432076
  0.0521066  0.364059   0.181515    0.00434307     -0.0248712   0.226976   -0.0686294  0.437169
 -0.0472136  0.127803   0.458812    0.0119074       0.0391649  -0.0190299  -0.0585371  0.264379
  0.468634   1.16498   -0.00263205  0.192809        0.273537    1.13787    -0.0653081  1.41321 
  0.749655   2.20266    0.0205068   0.420249        0.573358    1.42499     0.441232   1.67574 
其中
@macro将
s扩展到以下循环(加上准备和边界检查):


现在,为了找到更好的性能,我使用。你可以在我的笔记本电脑上看到完整的代码和结果。它表明,Einsum变体实际上已经优于原始版本:

# Original: 
#   memory estimate:  1017.73 MiB
#   allocs estimate:  3429967
#   median time:      361.982 ms (15.94% GC)

# Einsum: 
#   memory estimate:  2.64 MiB
#   allocs estimate:  76
#   median time:      127.536 ms (0.00% GC)
到目前为止,最有效且分配最少的变量是以下变量,它需要
x=x'
W=permutedims(W[2,3,1])
(假设您可以轻松更改表示形式):

这使我们陷入了困境

#   memory estimate:  2.63 MiB
#   allocs estimate:  2
#   median time:      521.215 μs (0.00% GC)
它使用了两个可以找到的“技巧”:用一个单独的方法填充一个预先分配的矩阵,按列的主要顺序访问跨距,以及使用
@inbounds
(尽管这只会在一微秒级上改善情况)


还有一个,我认为它在引擎盖下做了更聪明的事情,但它在这方面失败了:

julia> @tensor r[n,k] := (x[n,i] - μ[i,k]) * W[k,i,j] * (x[n,j] - μ[j,k])
ERROR: TensorOperations.IndexError{String}("invalid index specification: (:n, :i) to (:i, :k)")
Stacktrace:
 [1] add_indices(::Tuple{Symbol,Symbol}, ::Tuple{Symbol,Symbol}) at /home/philipp/.julia/v0.6/TensorOperations/src/implementation/indices.jl:22
 [2] + at /home/philipp/.julia/v0.6/TensorOperations/src/indexnotation/sum.jl:40 [inlined]
 [3] -(::TensorOperations.IndexedObject{(:n, :i),:N,Array{Float64,2},Int64}, ::TensorOperations.IndexedObject{(:i, :k),:N,Array{Float64,2},Int64}) at /home/philipp/.julia/v0.6/TensorOperations/src/indexnotation/sum.jl:44

我想这是经过深思熟虑的,与效率有关,请参见。

您的第二个代码和第一个代码可能存在一些问题。您正在对内部循环进行矢量化,但内部循环仍然存在。res[i,k]是一个
数字
,但右边是一个向量。有几种方法可以定义
W
的矩阵列表。你是如何定义它的?您能给出
大小(W)
的输出吗?如果您能为
x_矩阵
mu_矩阵
,尤其是
W
提供一些样本矩阵,那就太好了。是的,可能有一些方法可以摆脱外环。另一个问题是不能从大小为(K,D)的矩阵中获取大小为D的向量的差。具体地说,
x_矩阵[i,:]-mus_矩阵
给出了
维度不匹配
。我也不确定用
w_matrix
进行初等乘法是否会得到你想要的结果。你是对的,我的第二个代码有一些问题,但那是由于复制粘贴,我刚刚更正了它!仍然会出现错误,这次是另一个错误。我在上面的问题中添加了一个例子,希望对大家有所帮助!可能与我定义列表W的方式有关?您给出的示例中的维度似乎与问题中描述的不兼容。哪一个是正确的?另外,您如何定义
xi-u
?我从您提供的尺寸中了解到,应该将
xi
添加到
-u
的每一列中。然后维度是
D*K
,结果维度是
K*K
而不是
1*K
?我遗漏了什么?我不知道有一个数学公式可以处理张量!甚至这看起来也很有趣(Einsum.jl包)比我已经在使用的代码要慢得多。以下是N=9851,K=35,D=16的计算结果:=(x_矩阵[N,i]-mus矩阵[i,K])*w_矩阵[K,i,j]*(x_矩阵[N,j]-mus矩阵[j,K])julia>toc()的计算结果0x002d22f79f38006a julia>res=0(N,K);julia>for i in 1:N for k in 1:k res[i,k]=(x_矩阵[i,:]-mus_矩阵[:,k])**w_矩阵[k,:,:]*(x_矩阵[i,:]-mus_矩阵[:,k])结束julia结束时间:3.66330257秒在这两种情况下,结果完全一样,一个NxK矩阵!这不是衡量绩效的方式。不要在全局范围内度量性能。将代码放入函数中并调用两次
@time您的函数(params…
)。忽略第一个时间点,第二个时间点就是你想要的。请查看:@LeonidasSouliotis或,使用
BenchmarkTools.jl
。但是,是的,我并没有太多地考虑效率。我度假回来后会调查的。
#   memory estimate:  2.63 MiB
#   allocs estimate:  2
#   median time:      521.215 μs (0.00% GC)
julia> @tensor r[n,k] := (x[n,i] - μ[i,k]) * W[k,i,j] * (x[n,j] - μ[j,k])
ERROR: TensorOperations.IndexError{String}("invalid index specification: (:n, :i) to (:i, :k)")
Stacktrace:
 [1] add_indices(::Tuple{Symbol,Symbol}, ::Tuple{Symbol,Symbol}) at /home/philipp/.julia/v0.6/TensorOperations/src/implementation/indices.jl:22
 [2] + at /home/philipp/.julia/v0.6/TensorOperations/src/indexnotation/sum.jl:40 [inlined]
 [3] -(::TensorOperations.IndexedObject{(:n, :i),:N,Array{Float64,2},Int64}, ::TensorOperations.IndexedObject{(:i, :k),:N,Array{Float64,2},Int64}) at /home/philipp/.julia/v0.6/TensorOperations/src/indexnotation/sum.jl:44