如何创建具有函数和任意索引的 SVector/SMatrix?

How do I create an SVector/SMatrix with a function and arbitrary indices?

教授演示了使用 SMatrix 的二维高斯核(见下图)。我正在尝试以类似的方式实现任意大小的一维内核。我尝试了如下代码,但无法以正确的方式在网上找到任何好的资源来初始化它。所有示例都是静态的,在初始化时具有非常静态的元素。

begin
    G_1D(i, σ) = ℯ^(-(i^2)/(2*(σ^2)))/sqrt(2*π*(σ^2))
    G_2D(x, y, σ) = ℯ^(-((x^2)+(y^2))/(2*(σ^2)))/sqrt(2*π*(σ^2))
end
begin
    function gaussian_kernel_1D(n, σ = 1)
        if iseven(n)
            mid = n÷2
            kernel = SVector{n}(map(i -> G_1D(i, σ)), CartesianIndex(-mid:mid-1))
            kernel ./ sum(kernel)
            return kernel
        else
            mid = n÷2
            kernel = SVector{n}(map(i -> G_1D(i, σ)), CartesianIndex(-mid:mid))
            kernel ./ sum(kernel)
            return kernel
        end
    end
    
    function gaussian_kernel_2D(n, σ = 1)
        if iseven(n)
            mid = n÷2
            kernel = SMatrix{n,n}(map(x,y -> G_2D(x,y, σ)), CartesianIndices((-mid:mid-1, -mid:mid-1)))
            kernel ./ sum(kernel)
            return kernel
        else
            mid = n÷2
            kernel = SMatrix{n,n}(map(x,y -> G_2D(x,y, σ)), CartesianIndices((-mid:mid-1, -mid:mid-1)))
            kernel ./ sum(kernel)
            return kernel
        end
    end
end
  1. 使用 SVector 是正确的解决方案吗?如果是这样,你能帮我找到正确的初始化方法吗?
  2. 如果 SVector 不是正确的想法,您会推荐什么?

这里是调用高斯核函数的错误详情。

MethodError: no method matching (::Main.workspace68.var"#2#6"{Int64})()
Closest candidates are:
#2(!Matched::Any) at /Users/
map(::Main.workspace68.var"#2#6"{Int64})@abstractarray.jl:2247
gaussian_kernel_1D(::Int64, ::Int64)@Other: 10
gaussian_kernel_1D(::Int64)

我是一个比较新的学习者,所以如果代码不正确请指出,这将非常有帮助。

您的问题不在于 SVector,而在于您调用 G_1D 的方式,更具体地说,您在此处遇到错误:

julia> CartesianIndex(-mid:mid)
ERROR: MethodError: no method matching CartesianIndex(::UnitRange{Int64})

您可以使用 CartesianIndices,但我不明白您为什么要这样做。直接使用 -mid:mid ,广播。像这样:

G_1D.(-mid:mid, σ)

那么

kernel = SVector{n}(G_1D.(-mid:mid, σ))

对于 2D,您可以这样做:

kernel = SMatrix{n,n}(G_2D.(-mid:mid, (-mid:mid)', σ))

这里还有另一个问题:

kernel ./ sum(kernel)
return kernel

这不会更改 kernel 的值。你基本上是这样做的:

kernel_nonnormalized = kernel
kernel_normalized = kernel ./ sum(kernel)
return kernel_nonnormalized

如果你只写

return kernel ./ sum(kernel)

然后你得到一个标准化的值。

很难说您是否有理由使用 StaticArrays。维度应该很小,理想情况下它们的大小应该在编译时已知,而在您的代码中它是在运行时确定的。但这可能不是问题,只要这发生在一个孤立的地方,例如在你计算的开始。试试看吧。

编辑: 顺便说一句,如果你不介意我这么说,你就不必要地违反了 DRY(不要重复自己)原则,并且错过了使用多个派遣。这是避免这种情况的一种方法(可能不是最佳方法):

gauss(i, σ) = ℯ^(-(i^2)/(2*(σ^2)))/sqrt(2*π*(σ^2))
gauss(x, y, σ) = ℯ^(-((x^2)+(y^2))/(2*(σ^2)))/sqrt(2*π*(σ^2))

function makekernel(f, n, σ=1)
    ind = (0:n-1) .- (n÷2)
    kernel = SVector{n}(f.(ind, σ))
    return kernel ./ sum(kernel)
end

function makekernel(f, n::NTuple{2}, σ=1)
    ind1 = (0:n[1]-1) .- (n[1]÷2)
    ind2 = (0:n[2]-1) .- (n[2]÷2)
    kernel = SMatrix{n[1], n[2]}(f.(ind1, ind2', σ))
    return kernel ./ sum(kernel)
end

您只需要 一个 高斯函数的名称,一维和二维版本不需要单独的名称。 makekernel 在这两种情况下也可以具有相同的名称。无需创建单独的函数名称,名称中包含 1D 和 2D。

另请注意,偶数和奇数长度不需要单独的代码路径。

您可以这样称呼它们:

kernel1D = makekernel(gauss, 7)  # here's 1D kernel
kernel2D = makekernel(gauss, (7, 6))  # here's a 7x6 2D kernel

现在也有使用不同于 gauss 的函数的空间,您还会看到将其扩展到更高维度的模式。