是否有可以将 NumPy 的广播规则应用于形状列表和 return 最终形状的函数?
Is there a function that can apply NumPy's broadcasting rules to a list of shapes and return the final shape?
这不是关于广播如何工作的问题(即,它不是these questions的副本)。
我只想找到一个可以将 NumPy 的广播规则应用于形状列表和 return 最终形状的函数,例如:
>>> broadcast_shapes([6], [4, 2, 3, 1], [2, 1, 1])
[4, 2, 3, 6]
谢谢!
In [120]: shapes = [6], [4, 2, 3, 1], [2, 1, 1]
In [121]: arrs = np.broadcast_arrays(*[np.empty(shape,int) for shape in shapes])
...:
In [122]: [a.shape for a in arrs]
Out[122]: [(4, 2, 3, 6), (4, 2, 3, 6), (4, 2, 3, 6)]
In [124]: np.lib.stride_tricks._broadcast_shape(*[np.empty(shape,int) for shape
...: in shapes])
Out[124]: (4, 2, 3, 6)
In [131]: np.broadcast(*[np.empty(shape,int) for shape in shapes]).shape
Out[131]: (4, 2, 3, 6)
第二次快了很多,分别是 4.79 µs 和 42.4 µs。第三个快一点。
正如我最初评论的那样,我从broadcast_arrays
开始,并查看了代码。那我到_broadcast_shape
,然后到np.broadcast
。
这是一个简单的实现,以备不时之需(可能有助于理解广播)。不过我更喜欢使用 NumPy 函数。
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = [[1] * (max_rank - len(shape)) + shape for shape in shapes]
final_shape = [1] * max_rank
for shape in shapes:
for dim, size in enumerate(shape):
if size != 1:
final_size = final_shape[dim]
if final_size == 1:
final_shape[dim] = size
elif final_size != size:
raise ValueError("Cannot broadcast these shapes")
return final_shape
编辑
我根据其他几个答案对这个函数进行了计时,结果证明它是最快的(edit,Paul Panzer 写了一个更快的函数,看他的答案,我补充说它到下面的列表):
%timeit bs_pp(*shapes) # Peter Panzer's answer
2.33 µs ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes1(*shapes) # this answer
4.21 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes2(*shapes) # my other answer with shapes.max(axis=0)
12.8 µs ± 67.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes3(*shapes) # user2357112's answer
18 µs ± 26.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes4(*shapes) # hpaulj's answer
18.1 µs ± 263 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
我在 NumPy 文档中没有看到任何相关内容。您可以将单个 0 维数组广播到每个目标形状,然后将所有结果相互广播:
def broadcast_shapes(*shapes):
base = numpy.array(0)
broadcast1 = [numpy.broadcast_to(base, shape) for shape in shapes]
return numpy.broadcast(*broadcast1).shape
这避免了为大形状分配大量内存。不过,完全需要创建数组感觉有点傻。
假设形状确实可以广播,那么这个有效:
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
shapes[shapes==1] = -1
final_shape = shapes.max(axis=0)
final_shape[final_shape==-1] = 1
return final_shape
如果您假设没有空维度,那么 -1
hack 就没有必要了:
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
return shapes.max(axis=0)
这是另一个直接实现,恰好在示例中击败了其他实现。值得一提的是@hpaulj 和@Warren Weckesser 的 hack,它几乎一样快而且更简洁:
def bs_pp(*shapes):
ml = max(shapes, key=len)
out = list(ml)
for l in shapes:
if l is ml:
continue
for i, x in enumerate(l, -len(l)):
if x != 1 and x != out[i]:
if out[i] != 1:
raise ValueError
out[i] = x
return (*out,)
def bs_mq1(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = [[1] * (max_rank - len(shape)) + shape for shape in shapes]
final_shape = [1] * max_rank
for shape in shapes:
for dim, size in enumerate(shape):
if size != 1:
final_size = final_shape[dim]
if final_size == 1:
final_shape[dim] = size
elif final_size != size:
raise ValueError("Cannot broadcast these shapes")
return (*final_shape,)
import numpy as np
def bs_mq2(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
shapes[shapes==1] = -1
final_shape = shapes.max(axis=0)
final_shape[final_shape==-1] = 1
return (*final_shape,)
def bs_hp_ww(*shapes):
return np.broadcast(*[np.empty(shape + [0,], int) for shape in shapes]).shape[:-1]
L = [6], [4, 2, 3, 1], [2, 1, 1]
from timeit import timeit
print('pp: ', timeit(lambda: bs_pp(*L), number=10_000)/10)
print('mq 1: ', timeit(lambda: bs_mq1(*L), number=10_000)/10)
print('mq 2: ', timeit(lambda: bs_mq2(*L), number=10_000)/10)
print('hpaulj/ww:', timeit(lambda: bs_hp_ww(*L), number=10_000)/10)
assert bs_pp(*L) == bs_mq1(*L) and bs_pp(*L) == bs_mq2(*L) and bs_pp(*L) == bs_hp_ww(*L)
样本运行:
pp: 0.0021552839782088993
mq 1: 0.00398325570859015
mq 2: 0.01497043427079916
hpaulj/ww: 0.003267909213900566
这不是关于广播如何工作的问题(即,它不是these questions的副本)。
我只想找到一个可以将 NumPy 的广播规则应用于形状列表和 return 最终形状的函数,例如:
>>> broadcast_shapes([6], [4, 2, 3, 1], [2, 1, 1])
[4, 2, 3, 6]
谢谢!
In [120]: shapes = [6], [4, 2, 3, 1], [2, 1, 1]
In [121]: arrs = np.broadcast_arrays(*[np.empty(shape,int) for shape in shapes])
...:
In [122]: [a.shape for a in arrs]
Out[122]: [(4, 2, 3, 6), (4, 2, 3, 6), (4, 2, 3, 6)]
In [124]: np.lib.stride_tricks._broadcast_shape(*[np.empty(shape,int) for shape
...: in shapes])
Out[124]: (4, 2, 3, 6)
In [131]: np.broadcast(*[np.empty(shape,int) for shape in shapes]).shape
Out[131]: (4, 2, 3, 6)
第二次快了很多,分别是 4.79 µs 和 42.4 µs。第三个快一点。
正如我最初评论的那样,我从broadcast_arrays
开始,并查看了代码。那我到_broadcast_shape
,然后到np.broadcast
。
这是一个简单的实现,以备不时之需(可能有助于理解广播)。不过我更喜欢使用 NumPy 函数。
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = [[1] * (max_rank - len(shape)) + shape for shape in shapes]
final_shape = [1] * max_rank
for shape in shapes:
for dim, size in enumerate(shape):
if size != 1:
final_size = final_shape[dim]
if final_size == 1:
final_shape[dim] = size
elif final_size != size:
raise ValueError("Cannot broadcast these shapes")
return final_shape
编辑
我根据其他几个答案对这个函数进行了计时,结果证明它是最快的(edit,Paul Panzer 写了一个更快的函数,看他的答案,我补充说它到下面的列表):
%timeit bs_pp(*shapes) # Peter Panzer's answer
2.33 µs ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes1(*shapes) # this answer
4.21 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes2(*shapes) # my other answer with shapes.max(axis=0)
12.8 µs ± 67.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes3(*shapes) # user2357112's answer
18 µs ± 26.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit broadcast_shapes4(*shapes) # hpaulj's answer
18.1 µs ± 263 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
我在 NumPy 文档中没有看到任何相关内容。您可以将单个 0 维数组广播到每个目标形状,然后将所有结果相互广播:
def broadcast_shapes(*shapes):
base = numpy.array(0)
broadcast1 = [numpy.broadcast_to(base, shape) for shape in shapes]
return numpy.broadcast(*broadcast1).shape
这避免了为大形状分配大量内存。不过,完全需要创建数组感觉有点傻。
假设形状确实可以广播,那么这个有效:
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
shapes[shapes==1] = -1
final_shape = shapes.max(axis=0)
final_shape[final_shape==-1] = 1
return final_shape
如果您假设没有空维度,那么 -1
hack 就没有必要了:
def broadcast_shapes(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
return shapes.max(axis=0)
这是另一个直接实现,恰好在示例中击败了其他实现。值得一提的是@hpaulj 和@Warren Weckesser 的 hack,它几乎一样快而且更简洁:
def bs_pp(*shapes):
ml = max(shapes, key=len)
out = list(ml)
for l in shapes:
if l is ml:
continue
for i, x in enumerate(l, -len(l)):
if x != 1 and x != out[i]:
if out[i] != 1:
raise ValueError
out[i] = x
return (*out,)
def bs_mq1(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = [[1] * (max_rank - len(shape)) + shape for shape in shapes]
final_shape = [1] * max_rank
for shape in shapes:
for dim, size in enumerate(shape):
if size != 1:
final_size = final_shape[dim]
if final_size == 1:
final_shape[dim] = size
elif final_size != size:
raise ValueError("Cannot broadcast these shapes")
return (*final_shape,)
import numpy as np
def bs_mq2(*shapes):
max_rank = max([len(shape) for shape in shapes])
shapes = np.array([[1] * (max_rank - len(shape)) + shape
for shape in shapes])
shapes[shapes==1] = -1
final_shape = shapes.max(axis=0)
final_shape[final_shape==-1] = 1
return (*final_shape,)
def bs_hp_ww(*shapes):
return np.broadcast(*[np.empty(shape + [0,], int) for shape in shapes]).shape[:-1]
L = [6], [4, 2, 3, 1], [2, 1, 1]
from timeit import timeit
print('pp: ', timeit(lambda: bs_pp(*L), number=10_000)/10)
print('mq 1: ', timeit(lambda: bs_mq1(*L), number=10_000)/10)
print('mq 2: ', timeit(lambda: bs_mq2(*L), number=10_000)/10)
print('hpaulj/ww:', timeit(lambda: bs_hp_ww(*L), number=10_000)/10)
assert bs_pp(*L) == bs_mq1(*L) and bs_pp(*L) == bs_mq2(*L) and bs_pp(*L) == bs_hp_ww(*L)
样本运行:
pp: 0.0021552839782088993
mq 1: 0.00398325570859015
mq 2: 0.01497043427079916
hpaulj/ww: 0.003267909213900566