【问题标题】:dropping singleton dimensions in julia在 Julia 中删除单例维度
【发布时间】:2018-09-25 19:51:46
【问题描述】:

只是在玩 Julia (1.0),我需要在 Python/numpy/matlab 中经常使用的一件事是 squeeze 函数来删除单例维度。

我发现在 Julia 中执行此操作的一种方法是:

a = rand(3, 3, 1);
a = dropdims(a, dims = tuple(findall(size(a) .== 1)...))

第二行似乎有点繁琐,不容易立即阅读和解析(这也可能是我从其他语言中带来的偏见)。但是,我想知道这是否是 Julia 中执行此操作的规范方式?

【问题讨论】:

  • 我想不出比你所做的更简单的事情了。我想这里有一个论点,对dropdims(a) 的调用应该依赖于您在上面定义的方法,即自动删除所有单例维度。编辑:原来它已经被广泛讨论,见herehere

标签: julia


【解决方案1】:

这个问题的实际答案让我感到惊讶。您的问题可以改写为:

为什么dropdims(a) 不删除所有单件维度?

我将在这里引用relevant issue 的 Tim Holy:

squeeze(A) 不可能返回编译器的类型 可以推断---输入矩阵的大小是一个运行时变量,所以 编译器无法知道输出的维数 会有。所以它不可能给你想要的类型稳定性。

除了类型稳定性之外,您所写的内容还有其他一些令人惊讶的含义。例如,请注意:

julia> f(a) = dropdims(a, dims = tuple(findall(size(a) .== 1)...))
f (generic function with 1 method)

julia> f(rand(1,1,1))
0-dimensional Array{Float64,0}:
0.9939103383167442

总而言之,在Base Julia 中包含这样的方法会鼓励用户使用它,从而导致潜在的类型不稳定代码,在某些情况下不会很快(核心开发人员极力避免这种情况) .在 Python 等语言中,不强制执行严格的类型稳定性,因此您会发现这样的函数。

当然,没有什么能阻止您按照自己的方式定义自己的方法。而且我认为您不会找到一种更简单的编写方式。例如,Base 的命题没有实现是方法:

function squeeze(A::AbstractArray)
    singleton_dims = tuple((d for d in 1:ndims(A) if size(A, d) == 1)...)
    return squeeze(A, singleton_dims)
end

请注意使用它的潜在影响。

【讨论】:

  • 也感谢这项出色的研究。我想由于运行时对大小的依赖,没有办法以类型有效的方式做到这一点?
  • @Luca 是的,这正是问题所在。请注意,在许多用例中,类型稳定性性能影响很小,并且可以使用屏障函数等技术来减轻,因此不要完全不鼓励这样做。您只需要了解其中的含义。 (我相信您现在可以看到他们为什么选择不将其包含在 Base 中)
  • 是的,当然。我明白为什么它不是标准库的一部分,很高兴看到这些决定中的讨论!
  • @ColinTBowers 你的意思是在最后一个 sn-p 中说 return dropdims(A, singleton_dims) 吗?
  • @TasosPapastylianou 不,我实际上直接从我在答案中链接的 PR 中复制了那个 sn-p。 PR 是从 2017 年年中开始的,当时是 squeeze 而不是 dropdims。我想我本可以将其更改为dropdims,但我真正想证明的是提交的 PR 与 OP 提出的方法没有显着不同。我也懒惰地复制/粘贴:-)
【解决方案2】:

让我简单地补充一点,“不受控制的”dropdims(删除任何单一维度)是错误的常见来源。例如,假设您有一些循环从某个外部源请求数据数组A,并且您在其上运行R = sum(A, dims=2),然后摆脱所有单例维度。但是假设在 10000 次中有一次,您的外部源返回 A,而 size(A, 1) 恰好是 1:繁荣,突然之间,您下降的维度超出了您的预期,并且可能有严重误解您的数据的风​​险。

如果您改为手动指定这些尺寸(例如,dropdims(R, dims=2)),那么您就不会受到此类错误的影响。

【讨论】:

  • 这是一个有效的观点,我也猜想 Julia 中 Base 函数设计背后的问题之一。
【解决方案3】:

您可以去掉tuple 以使用逗号,

dropdims(a, dims = (findall(size(a) .== 1)...,))

【讨论】:

    【解决方案4】:

    我对 Colin 的启示感到有些惊讶;依赖“重塑”的东西肯定是类型稳定的吗? (另外,作为奖励,返回视图而不是副本)。

    julia> function squeeze( A :: AbstractArray )
             keepdims = Tuple(i for i in size(A) if i != 1);
             return reshape( A, keepdims );
           end;
    
    julia> a = randn(2,1,3,1,4,1,5,1,6,1,7);
    
    julia> size( squeeze(a) )
    (2, 3, 4, 5, 6, 7)
    

    没有?

    【讨论】:

    • 嗯嗯好的。因此,由于 reshape 返回一个视图,它基本上可以将其转换为任何推断的数据类型并且没有惩罚?恐怕我对 Julia 太陌生了,无法对此发表评论或判断。我希望有更多经验的人能参与进来。
    • 不,这是不可推断的,因为数组的大小不是由类型系统编码的;考虑typeof(squeeze(rand(3,5))) vs typeof(squeeze(rand(1,5))) vs typeof(squeeze(rand(1,1)))。无法推断的是 keepdims 部分,而不是 reshape
    • 感谢@tholy 的澄清。从效率的角度来看,这真的那么重要吗?在我看来,这样的挤压功能将是一个简单的包装器,它在后台调用优化的方法。由于那里没有进一步的说明,我看不出编译版本会提供更多。我错过了这里的大局吗?
    • @Luca 这不是惯用语,更多的是它们代表不同的事物。 Tuple 是类型及其对应的构造函数,它需要一个可迭代的参数进行初始化。 tuple 是一个独立的函数,它接受一个 sequence 参数并构造一个相应的元组,大概是通过在后台适当地调用 Tuple。这 曾经 是一种常见的模式(例如,Julia 0.3 将 int64 作为转换为 Int64 的函数,但现在已经不支持直接使用构造函数了),但现在已经不多了.事情一直在变化,所以,谁知道呢。
    • @TasosPapastylianou,这取决于:如果你将结果数组传递给函数f,你可能没问题---julia 必须执行一次“动态调度”,以查找哪个版本f 使用(如有必要,可能编译它)。另一方面,如果你例如在同一个函数中迭代数组中的值,这可能很糟糕,因为 Julia 必须为每个条目进行动态调度。有关类似注意事项,请参阅docs.julialang.org/en/v1/manual/performance-tips/…。但另请参阅julialang.org/blog/2018/08/union-splitting
    猜你喜欢
    • 2018-02-27
    • 2021-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-26
    • 2022-01-16
    • 2015-04-24
    • 1970-01-01
    相关资源
    最近更新 更多