Julia: 弃用`ones`?

创建于 2017-11-02  ·  46评论  ·  资料来源: JuliaLang/julia

现在我们区分了oneoneunitones(T, sz)似乎用词不当。 弃用fill(oneunit(T), sz) ? 如果是这样,我们也应该放弃zeros吗?

decision linear algebra stdlib

最有用的评论

对我来说,与ones(T, sz)相比, fill(oneunit(T), sz)看起来在可读性上损失不小。

所有46条评论

外部参照https://github.com/JuliaLang/julia/issues/11557#issuecomment -339776065 及以下,以及@Sacha0的 PR #24389

我正在进行这项工作,我希望在接下来的一两天内发布:)。 最好的事物!

对我来说,与ones(T, sz)相比, fill(oneunit(T), sz)看起来在可读性上损失不小。

请注意,您很少需要编写像fill(oneunit(T), sz)那样冗长的内容,因为通常文字或类似的紧凑内容足以代替oneunit(T) 。 随着未来对数组构造函数的更改,更短的咒语也可能成为可能。 此外,一旦你过渡到fill ,你就会喜欢它,我向你保证:)。 最好的事物!

我们可以选择ones使用one还是oneunitoneszeros应该算是方便函数,只要它们在更通用的函数方面有明确的含义就可以了。

按分类计划:下周重新访问,特别考虑将oneszeros移至 MATLAB 兼容层,以支持fill中的基础。 最好的事物!

onesoneunits将区分这两个选项。

不知道我对把这个和zeros移到 MATLAB 兼容性有什么感觉。 仅仅因为 Mathworks 发明了它,并不意味着它不是一个我们可以引以为豪的便捷 API。 简单地说——这个想法背后的原因是什么? (抱歉,我不得不说分流似乎更有效,但在没有给出推理时明显不透明)。

这也不是让我感到兴奋的变化,但我提出它是因为这是逻辑上的不一致。 我认为可以公平地说ones(T, sz)意味着fill(one(T), sz)但实际上它在幕后所做的是fill(oneunit(T), sz)

一种选择是将其重命名为oneunits 。 另一种方法是做一个可怕的交换: one -> algebraic_oneoneunit -> one 。 这是破坏性的,不可能通过弃用来实现,这就是我说“可怕”的原因。

是的,我会说添加oneunits并将ones更改为fill(one(T), ...)将是明显的解决方法,不是吗?

我会没事的。 出于好奇, fill(one(T), sz)的用途是什么? 我们甚至需要ones吗?

哈哈 :) 我想问 - 你用fill(oneunits(T), sz)做什么? (例如,填充 1 米、1 公斤或 1 秒的数组将用于什么?)

我认为fill(one(T), sz)的使用原因与zeros(T, sz)大致相同,即为自定义缩减类型操作初始化数组。 数组z = zeros(T, sz)已准备好将其元素添加到z[i] += xo = fill(one(T), sz)已准备好将其元素乘以o[i] *= x 。 例如,我正在考虑数组元素可能代表(相对)概率的情况。 在这两种情况下,我分别寻找+*运算符(而不是加法生成器)的标识。

另见#23544

哈哈 :) 我想问 - 你用 fill(oneunits(T), sz) 做什么? (例如,填充 1 米、1 公斤或 1 秒的数组将用于什么?)

ODE 的因变量。 如果它只是在您要求T类型的数组时随机切割单位,那会很奇怪吗?

偏爱fill的根本原因是,一组强大的、正交的、可组合的工具比更多的临时、有限和重叠的工具(如ones / zeros服务更好trues / falses 。 上面的讨论强调了这一点:虽然fill明确且(在大多数实际使用中)简洁地容纳了上述所有用例,但ones / zeros / trues / falses方法需要为每个用例创建一个新函数。

base 中重写的几个相关示例:

complex.(ones(size(Ac, 1)), ones(size(Ac, 1)))

变成

fill(1.0 + 1.0im, size(Ac, 1))

2ones(Int, 2, 2, 2)

变成

fill(2, (2, 2, 2)) # or fill(2, 2, 2, 2) if you prefer

请注意,这些fill咒语比它们的非fill对应物更简单、更易读、更紧凑、更高效,并且base/test/是充斥着这样的例子。 与所有变化一样,过渡到fill需要一些初步的心理调整。 但是调整后你会发现你拥有更多的力量和优雅触手可及:)。 最好的事物!

@Sacha0trues / falses不能直接替换为fill ,但需要使用fill!BitArray初始值设定项。 它们也不包含在oneoneunit之间的歧义中。 因此,我认为它们根本不适合本次讨论。

至于ones ,我通常反对弃用它,我看不到好处。 在我看来,使用fill可以更有效地编写某些表达式的论点并不是很有说服力,因为所有这些示例都使用ones作为执行其他操作的中间步骤; 但是当你真的想要一个数组的时候呢? 然后必须使用fill更长,不那么明显并且更烦人。 我最喜欢@JeffBezanson的提议。 如果basetest可以使用fill而不是ones更有效地重写,那么现在没有什么可以阻止的。

@carlobaldassi虽然这是真的,但在 GitHub 上的快速搜索显示,几乎所有ones都应该真正使用fill以避免中间分配......

在这些情况下使用fill对我来说很有意义。 请注意,我们需要一个新的fill(x, A) = fill!(x, copymutable(A))方法来替换ones等的相应方法。

@TotalVerb从该搜索的前 10 页的快速浏览来看,我不会说“几乎所有”是一个公平的评估。 充其量是一个“重要的部分”。 ones有很多合法的用例,甚至可能是大多数(即使他们只是说 20%,我认为我的论点仍然成立)。

(实际上我也对可读性参数有所保留,我认为声称例如fill(2, 2, 2, 2)2 * ones(Int, 2, 2, 2)更具可读性是有问题的,或者说, fill(k * 1.0, 3)会更比k * ones(3)可读;我完全不相信这只是一个习惯问题。虽然这是一个小问题。)

trues/falses 不能直接用fill代替,而是需要用fill! 使用 BitArray 初始值设定项。 它们也不包含在 one 和 oneunit 之间的歧义中。 因此,我认为它们根本不适合本次讨论。

实际上, truesfalses不能直接替换为fill :)。 相反, truesfalses是上述一般问题的附加实例/与#11557 相关(并且数组构造函数的最新方向有望解决)。 其他示例包括bonesbzerosbrandbrandnbeye在 BandedMatrices.jl 中的存在以及具有d前缀在 DistributedArrays.jl 中。

至于那些,我通常反对弃用它,我看不出有什么好处。 在我看来,使用 fill 可以更有效地编写某些表达式的论点并不是很有说服力,因为所有这些示例都将它们用作执行其他操作的中间步骤

刚刚在 base 中重写了ones几百个用法,我可以肯定@TotalVerb的声明

一个快速的 GitHub 搜索显示,几乎所有的用途都应该使用填充来避免中间分配......

(编辑:虽然我会说大约一半而不是几乎全部,并且适当的重写可能不是fill 。)此外,重写经验教会了我......

但是当你真的想要一个数组的时候呢? 然后必须使用填充更长,不那么明显并且更烦人。

...另一方面,在这种情况下, fill通常更短更简单:请求的通常不是Float64 (而是例如ones(Int, n...)ones(Complex{Float64}, n...) ),在这种情况下, fill通过接受文字(例如fill(1, n...)fill(1.0 + 0im, n...) )而更短更简单。 从可衡量的角度来看,我在 base 中重写ones调用的分支从ones -> fill重写的字符数减少了约 5%。 最好的事物!

为了客观地了解ones是如何出现的,我收集了所有ones在 GitHub 搜索的前十页中出现的所有ones in Julia 代码,重写每个这样的调用适当地对相应的变化进行分类(见这个要点),然后将分类数据减少到以下摘要:

分析包括 156 个ones调用。 在这些电话中,

  • 84 次调用 (~54%) 是临时的fill s。 (例如, ones(100)/sqrt(100)*7简化为fill(7/sqrt(100), 100)或者更好的是fill(.7, 100) 。我最喜欢的是kron(0.997, ones(1, J*J*s) -> fill(0.997, 1, J*J*s) 。)

  • 3 个电话 (~2%) 是临时广播。 (例如, A - ones(n,n)简化为A .- 1. 。)

  • 5 个调用 (~3%) 是临时向量文字。 (例如, ones(1)简化为[1.] 。)

  • 1 次调用(~0.5%)在语义上是一个垃圾矩阵构造。 (虽然在野外相对不常见,但这种模式在test/很常见,因为我们没有为未初始化的Array s 提供简洁的便利构造函数,例如<strong i="32">@test_throws</strong> DimensionMismatch BLAS.trsv(...,Vector{elty}(n+1))<strong i="34">@test_throws</strong> DimensionMismatch BLAS.trsv(...,ones(elty,n+1)) 。)

其余的调用在语义上是合理的ones ,尽管通常ones仅仅因为它很短而被使用,而不是因为one特别是必要的。 在剩下的电话中,

  • 13 次调用 (~8%) 略短于fill 。 (例如, ones(Int, n, n) -> fill(1, n, n)ones(Float64, n) -> fill(1., n) 。)

  • 50 次调用 (~32%) 略长于fill 。 (例如, ones(n, n) -> fill(1., n, n) 。)

总体而言,在野外~60% 的ones调用以其他方式编写得更好,~8% 在语义上合理地ones和略短于fill ,~32% 是语义上合理的ones和稍长的fill

一个额外的观察:

我只遇到了一个接受数组参数的ones调用实例,并不清楚封闭的代码段是否是真正的代码。 因此,接受数组参数的ones方法在野外几乎没有使用。

真正有趣的讨论……从反对方变成了支持方……作为另一种语言的先例,R 使用repmatrix的方式相当于fill (仅对应于 1d 和 2d 情况)并且您很快就会习惯它——即使我来自一个零/一个世界。

哇,谢谢@Sacha0付出的努力!

问题自然会出现为“ zeros怎么样”? 我猜会有更多的使用,以及更多的使用类别(包括诸如“我根本不相信未初始化的数组”或“我不知道如何使用Array构造函数”之类的内容”)。

无论出于何种原因(我想这是对称性onezero ),我有点对两个都更换吸引oneszerosfill ,或两者都不是。

零的问题是您似乎处于以下情况之一:

  1. 您需要覆盖大部分零——在这种情况下,您最好使用推导式;
  2. 您不需要替换大部分零——在这种情况下,您最好使用稀疏矩阵;
  3. 您实际上需要一个全零矩阵 - 在这种情况下,您最好使用0I

真的没有用例实际分配一个密集的零矩阵实际上是一个好主意。

线性代数也许就是这样。 在我关于编译器和其他数据结构的工作中,需要一个零初始化的集合并不少见。 也许它们通常是稀疏的,但是将它们紧凑地表示出来并不值得对性能产生影响。

很公平 - 有时您不关心密度,简单是值得的。

分类:解决了我们只保留完全非通用的方法,即zeros(dims...)ones(dims...)以及可能zeros(dims)ones(dims)

@StefanKarpinski的使用建议是否意味着我们会推荐zeros(3, 3)不是fill(0.0, 3, 3)用于正常代码(当需要密集数组等时)? 效率等的一些细节是我无法实现的,我只是在想我将如何在 julia 中教授最佳/惯用的实践。

这个决定对我来说似乎非常令人惊讶,在基础上专门防止通用性并不常见。 背后的道理是什么? 是不是这个函数来自 matlab,它不是通用的(并且只适用于浮点数)?

分类:决定我们只保留完全非泛型的方法

背后的道理是什么?

此外,这个问题是否与目前似乎正在考虑的构造数组的一般问题有某种关系,如果是这样,这有什么帮助? 或者,我们是否试图减少从Base导出的方法的数量? (或者完全是别的什么?)

即将在 https://github.com/JuliaLang/julia/issues/24595 中撰写文章:)。 最好的事物!

24595 的 OP 详细介绍了这个问题的更广泛的背景,现在 #24595 中的后续内容专门解决了便利构造函数oneszerosfill的深度问题。 阅读前者对于欣赏后者很有价值。 最好的事物!

好吧,至少保存Float64案例总比没有好。
我相信整数零的情况也非常相关,我认为这基本上是@vtjnash这里提出的观点。

还应该注意的是, zeros不存在允许3 * ones(n)反模式的“问题”。 事实上,我真的不明白为什么oneszeros应该一起使用,除了作为便利构造函数的广义意义。 这两者之间没有真正的“对称性”。

关于统计分析的一些额外评论,因为它似乎是以下讨论和#24595 文章的基础。 首先,十页并不足以对野外发生的事情做出细粒度的结论,它们充其量只能给出一个粗略的想法。 例如,有些文件直接来自 matlab,从它们的名称/样式中可以清楚地看出。 其次,正如我所怀疑的那样,即使该分析也表明ones中大约有一半的用途是“合法的”。 第三,看这样的代码并不能说明什么时候写3 * ones(...)真的是一个会产生性能问题的反模式,或者它是一段完全没有性能影响的代码(作者可能会已经决定这样写起来更具可读性 - 我强烈认为在这种情况下没有其他人可以做出其他决定)。

与最后一点相关,我认为更重要的是,您从 github 搜索中看到的内容永远不会考虑在 Julia 中进行探索性/初步工作的人的 REPL 中发生的事情。 这正是便利功能最方便的地方,而无缘无故地将它们拿走是最烦人的。 我的观点是,拥有一组一致的正交原语以允许编写通用且高效的代码是一个伟大的目标,实现这一目标的努力确实值得称赞; 只是并不是所有的代码都应该是漂亮的、通用的、可组合的库代码。 只有我的两分钱。

关于

我相信整数零的情况也非常相关,我认为这基本上是@vtjnash在这里提出的观点。

指的是

在我关于编译器和其他数据结构的工作中,需要一个零初始化的集合并不少见。 也许它们通常是稀疏的,但是将它们紧凑地表示出来并不值得对性能产生影响。

请注意,在这种情况下, fill作用同样或更好: fill(0, shape...)zeros(Int, shape...)

关于您的其他观点,#24595 可能值得一读:)。 最好的事物!

我只是发现zeros(Int, 10, 10)fill(0, 10, 10)更具可读性/明确性,并且zeros(T, k)fill(zero(T), k)更好。 为什么我们不能两者兼得? 我不相信zeros遭受与ones相同的歧义问题的论点。

关于你的其他观点,#24595 可能值得一读

我读过它。 (我什至链接了它。)

我读过它。 (我什至链接了它。)

完整阅读#24595 并适当考虑后,您就会意识到#24595:(1) 涉及一个更广泛的问题,其中便利构造函数只是其中的一部分; (2) 考虑的不仅仅是上面发布的统计分析和您在这里关注的要点。

我很欣赏你对oneszeros强烈感受; 你的情绪已经清晰明了:)。 因此,与继续以目前的形式进行对话相比,我们的带宽可能会更好地用于推动其他方面的发展。 最好的事物!

是否有通用的fillzeros更改一起传入? zeros对泛型编程有一个非常合理的用途,因为它比similar安全得多,所以所有的 DiffEq,然后我知道最近 Optim 和 NLsolve 从使用similar分配到zeros因为知道所有东西都被分配了零,所以可以阻止很多错误。 但是,现在似乎没有方法可以用于:

zeros(X)

不再,除了:

similar(X); fill!(X,0)

因为当前的fill只构建Array s,因此不像similarzeros那样类型匹配。 我知道有些人误用了zeros来分配不应该分配的资源,但是在许多情况下使用zeros分配是非常合理的做法。 我希望添加一个fill(0,X)速记来填补这个空白。

非常感谢克里斯的周到帖子! :) 作为一个临时的速记替代品, zero(X)能解决问题吗?

请注意,此类用例正是zerosones中的歧义可能存在问题的地方: zeros(X)产生一个带有eltype(X)并用eltype(X)填充的对象fill!(similar(X), 0) ),或者代替eltype(X)填充乘法零的对象(可能不是eltype(X) )? (有关扩展,请参阅 #24595。)

fill(0, X)概念在#11557 中看到了一些讨论,我同意这可能是fill的有用概括。 谢谢和最好!

另一个问题是具有非常规索引的数组可能希望使用zeros(inds...) (因为索引类型决定了数组类型)。 但是对于一维情况, X “您想要相似的数组”还是“所需数组的索引”? (毕竟, AbstractUnitRange <: AbstractArray 。)具体来说, zeros(3:5)是指fill!(similar(3:5), 0)还是fill!(OffsetArray(Vector{Float64}(3), 3:5), 0)

链接https://github.com/JuliaLang/julia/pull/24656 ,其中包含对{ones|zeros }(A::AbstractArray, ...)便利构造函数的额外讨论。 最好的事物!

我很惊讶根据 #24656 使用zeros来创建缓存变量被认为很奇怪。 我认为,如果zeros减少到接近零开销,几乎所有使用similar都应该改为zeros因为这往往可以解决很多问题错误。 我认为我们应该鼓励更多的人这样做,因为similar可能非常不安全,并且没有函数而是将fill! + similar放在一起使得它不那么明显人们应该做什么。 这是 Optim.jl 中出现的有关此问题的评论:

https://github.com/JuliaNLSolvers/NLsolve.jl/issues/89#issuecomment -294585960

但是,我确实同意@timholy 的观点,即应该如何解释它并不明显。 让我向您指出 DiffEq 中一个非常不简单的例子。

https://github.com/JuliaDiffEq/MultiScaleArrays.jl

MultiScaleArrays.jl 创建了抽象数组,这些数组是递归图形结构,可用于 diffeq 求解器(我认为它现在可能与 Optim.jl 和 NLsolve.jl 兼容?)。 这对生物模型等来说是一个很好的便利。 当我们将它放入 ODE 求解器时,会有一个问题:我们应该如何制作缓存数组? 在某些情况下,用户取回他们想要的数组很重要,因为它会显示在他们的 ODE 函数f(t,u,du)并且他们希望相应地处理它。 但是,在其他情况下,它仅显示为内部广播的内容。 所以有两种不同类型的缓存变量。

要处理此问题,请查看其中一种算法的缓存:

https://github.com/JuliaDiffEq/OrdinaryDiffEq.jl/blob/master/src/caches/low_order_rk_caches.jl#L224 -L234

在这里, rate_prototype = similar(u,first(u)/t,indices(u)是相似的,但单位的 eltype 可能不同。 但请注意,这里有两种不同的类型: similar(u)similar(u,indices(u)) 。 我已经将这些解释为“匹配类型和形状”与“匹配形状和 eltype,但不需要是相同类型”。 所以对于AbstractMultiScaleArray ,第一个将创建另一个AbstractMultiScaleArray而另一个,因为用户看不到它的速度,将只创建一个Array尺寸。 然后将其扩展为similar(u,T)similar(u,T,indices(u))

也许这只是对已经存在的东西进行了双关语,但我认为这是一个重要的区别。 在进行泛型编程时,您有两个单独的缓存:您希望匹配类型以符合他们的期望的面向用户的缓存,以及仅由算法使用并且您希望尽可能快的速度的内部缓存。

请注意,这些使用similar是因为我懒得想出它的zeros版本。 我实际上有一个单独的地方可以将这些数组中的一些归零,因为如果用户只在他们的f(t,u,du)导数计算中设置du ,他们倾向于隐含的事情“我没有设置的意思是零",只有在分配zeros时才为真,所以我尝试尽可能多地使用zeros进行预分配(同样的问题出现在 NLsolve.jl 中) .

希望这个解释不会太令人困惑。 在我的所有情况下,我都可以切换到similar后跟fill! ,但我知道有些软件包不会,这将成为错误的来源。

有趣的。 您说得对, similar(A, inds)可能会创建与similar(A)不同的类型,但总的来说,我一直认为它可能会创建相同的类型但具有不同的索引。 例如,如果您需要一个一维缓存来对二维对象进行列操作,我会使用similar(A, first(inds)) 。 (当然它一个不同的类型,因为维度是一个类型参数,但它可能是相同的抽象容器类型。)你也可以用它来创建一个小瓦片的 5x5 缓存等。

总的来说,这似乎是一个具有挑战性的问题。 游戏有点晚了,但是我们应该引入same吗? 它可以具有与similar相同的参数,但约定是它需要返回相同的抽象容器。

我可以支持same的单参数形式,但即使这样也很棘手 - 请注意,如果a不支持setindex! same(a)无法返回相同的数组类型setindex!因为samesimilar仅在您之后要写入数组时才有用。 对于不可变的a ,我们可以将其设为错误,但作为AbstractArray的接口,这对于制作正确的通用代码似乎是不必要的(并且可能没有帮助)。

同样,我们不能假设每个AbstractArray都可以支持不同的 eltypes 或索引。 对我来说,拥有两个或三个参数形式的same只会在一堆地方引入运行时错误,同时给人们一种错误的安全感,即他们的通用代码对任何AbstractArray都可以正常工作

但是对于一维情况,X 是“您想要相似的数组”还是“所需数组的索引”?

这是我支持keys返回具有相同索引和值的容器的另一个原因,然后将其作为similar (除非您在假设Base.OneTo (CartesianRange) 的情况)。

这个讨论正在转向#18161,也许应该在那里继续:)。

最近的分类倾向于只保留ones

留着它们会痛吗? 我认为它可以帮助来自 numpy 的人在 Julia 感到宾至如归。

关闭,因为我们保留oneszeros

此页面是否有帮助?
0 / 5 - 0 等级