【问题标题】:Broadcast failure: how to dictate the axes of broadcasting?广播失败:如何决定广播的轴心?
【发布时间】:2019-11-25 18:02:15
【问题描述】:

我有一个大小为 (1000, 30, 16, 16) 的张量。我正在做关于如何规范化它的实验。我正在尝试跨案例标准化,可能是频率轴等。

以下作品:

a = np.random.rand(1000, 30, 16, 16)
a - a.mean(axis=(0, )) #==> it works
a - a.mean(axis=(0, 1)) #==> successful broadcast
a - a.mean(axis=(0, 1, 2)) #==> works well
a - a.mean(axis=(0, 1, 2, 3)) #==> succesful broadcast of scalar mean to all a values


#Those however fail:
a - a.mean(axis=(2, 3)) 
#OR:
a - a.mean(axis=(0, 2, 3))

我明白了:

ValueError: 操作数不能与形状一起广播 (1000, 30, 16, 16) (30,)

它似乎在简单的情况下成功完成了缺少的轴,例如 (30, 16, 16)

(16, 16)

(16,)

(1,)

但是当缺少的轴在右边而不是左边时失败,例如: (1000, 30) 并且无法将其广播到 (1000, 30, 16, 16)。

具体来说我的问题是,我如何规定广播是如何进行的? 比如我有 (30,) 我想广播到 (1000, 30, 16, 16)

由于广播失败,它会引发错误。我有一个 hacky 解决方案,它排列轴并制作 (30,) 排在最后,以便广播工作,但我想知道是否有办法规定应该如何进行广播。此外,为什么这不是自动完成的?

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    默认情况下,NumPy 通过添加新轴在左侧进行广播。例如,如果一个数组的形状为(30, 16, 16),那么它可以自动广播到形状(1, 30, 16, 16)。长度为 1 的新轴可以进一步广播到匹配它被广播到的数组所需的任何大小。

    这解释了为什么广播在所有这些情况下都有效:

    a = np.random.rand(1000, 30, 16, 16)
    a - a.mean(axis=(0, )) #==> it works
    a - a.mean(axis=(0, 1)) #==> successful broadcast
    a - a.mean(axis=(0, 1, 2)) #==> works well
    a - a.mean(axis=(0, 1, 2, 3)) #==> succesful broadcast of scalar mean to all a values
    

    在每种情况下,a.mean(...) 都会从左侧移除坐标轴,并(可能)将坐标轴留在右侧。 所以广播在左侧自动添加新轴没有问题。

    相比之下,a - a.mean(axis=(2, 3)) 失败是因为a.mean(axis=(2,3)) 的形状为(1000, 30), 并且只能广播到(1, 1000, 30)(1, 1, 1000, 30)等形状。由于a 的形状为(1000, 30, 16, 16),因此右起最后两个轴的长度发生冲突。

    在这种情况下要广播成功,你需要在右边使用explicitly add new axes

    a - a.mean(axis=(2, 3))[..., np.newaxis, np.newaxis]
    

    a - a.mean(axis=(2, 3))[..., None, None]
    

    现在a.mean(axis=(2, 3))[..., None, None] 具有(1000, 30, 1, 1) 的形状,并且可以广播到(1000, 30, 16, 16) 以与a 的形状兼容。


    The docs用说解释广播

    根据广播规则排列这些数组的尾轴大小,表示它们是兼容的:

        Image  (3d array): 256 x 256 x 3
        Scale  (1d array):             3
        Result (3d array): 256 x 256 x 3
    

    请注意,对齐是通过将形状右对齐来完成的。空轴用 1 填充。说在左侧添加了新的轴只是谈论同样想法的另一种方式。

    【讨论】:

    • 非常彻底的答案。另外,您更正了混乱的数字。非常感谢ubunto
    • 有了这种洞察力,我对幕后发生的事情更有信心。我总是对这是否像我打算的那样做得非常好有一些疑虑。特别是当轴像 (16, 16, 16, 16) 一样模棱两可时
    • 关键是它基于位置匹配,而不是值。它不会寻找另一个 30 来匹配。为了避免歧义,它只在前面增加了新的尺寸。您必须明确添加任何新的尾随维度。
    【解决方案2】:

    您可以通过使用None 对要添加的每个轴进行切片来显式地创建附加轴,而不是隐式广播。

    要将 (30,) 广播到 (1000,30,16,16),像这样切片:

    a[None,:,None,None]
    

    您可以看到第二个轴被: 切片,表示“所有数据”,其余轴为None,表示“在此处创建一个新轴用于广播”。

    如果你仔细想想,隐式广播对它的工作方式有严格的规定,这很好。想象一下它可以以这种方式自动广播——如果这是真的,它将如何将 (30,) 广播到 (30,30)?这将是模棱两可的。按照目前的规则,这并不模棱两可。

    【讨论】:

    • 我认为你犯了一个错误。您向 a 添加了更多轴。您应该将其添加到 a.mean(...)[None, :, None, None]
    • 展示如何广播a,假设它的形状为 (30,)。
    猜你喜欢
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多