Numpy: BUG:np.vectorize()函数在第一个元素上操作两次(如该函数修改可变对象时所见)

创建于 2017-03-08  ·  7评论  ·  资料来源: numpy/numpy

我有一个字典清单。 我正在尝试使用np.vectorize来应用一个函数,该函数为列表中的每个字典修改字典元素。 结果似乎表明矢量化作用于第一个元素两次。 这是可以修复的错误吗?(也许与向量化检查第一个元素上的类型有关吗?)下面是一些示例情况和输出:

一个没有字典修改的简单测试用例:

def fcn1(x):
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn1)(a))
print(a, '\n\n')

输出:

[{'b': 1}, {'b': 1}, {'b': 1}]
[1 1 1]
[{'b': 1}, {'b': 1}, {'b': 1}]

现在修改字典,看看该函数两次应用于第一个元素:

def fcn2(x):
    x['b'] += 1
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn2)(a))
print(a, '\n\n')

输出:

[{'b': 1}, {'b': 1}, {'b': 1}]
[3 2 2]
[{'b': 3}, {'b': 2}, {'b': 2}]  

尝试进行其他修改以检查错误的一致性:

def fcn3(x):
    x['b'] *= 2
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn3)(a))
print(a, '\n\n')

输出:

[{'b': 1}, {'b': 1}, {'b': 1}]
[4 2 2]
[{'b': 4}, {'b': 2}, {'b': 2}]    

您可以执行相同的操作而无需实际提供返回值(这是我在用例中尝试使用的返回值):

def fcn4(x):
    x['b'] += 1
a = [{'b': 1} for _ in range(3) ]
print(a)
np.vectorize(fcn4)(a)
print(a, '\n\n')

输出:

[{'b': 1}, {'b': 1}, {'b': 1}]
[{'b': 3}, {'b': 2}, {'b': 2}]

顺便说一句,长度3的列表没有什么特别的,您可以更改它,并看到只有第一个元素被双重修改的相同行为。

我已确认同时使用Numpy版本1.11.3和1.12.0的行为

编辑:
我发现了一种变通方法,也可以确认其“在第一个元素上测试类型”问题。 如果指定otypes参数,则第一个元素不会被击中两次:

def fcn(x):
    x['b'] += 1
    return x['b']
a = [{'b': 1} for _ in range(3)]
print a
print np.vectorize(fcn, otypes=[dict])(a)
print a, '\n\n'

输出:

[{'b': 1}, {'b': 1}, {'b': 1}]
[2 2 2]
[{'b': 2}, {'b': 2}, {'b': 2}]
00 - Bug

最有用的评论

“如果未指定otypes,则将使用对第一个参数的函数调用来确定输出数量。如果cache为True,则将缓存此调用的结果,以防止两次调用该函数。但是,要实现在缓存中,必须包装原始函数,这会减慢后续调用的速度,因此,只有在您的函数很昂贵的情况下,才可以这样做。”
https://docs.scipy.org/doc/numpy/reference/generation/numpy.vectorize.html

因此,这是有据可查的行为,但似乎违反直觉。 因此,也许它比漏洞修复更能增强功能。

所有7条评论

我刚刚用新测试编辑了帖子,似乎可以确认检查第一个项目类型是什么导致了问题

更简单的测试用例:

a = np.array([1, 2, 3])
def f(x):
    print('got', x)
    return x
fv = np.vectorize(f)
y = fv(a)

给出:

got 1
got 1
got 2
got 3

“如果未指定otypes,则将使用对第一个参数的函数调用来确定输出数量。如果cache为True,则将缓存此调用的结果,以防止两次调用该函数。但是,要实现在缓存中,必须包装原始函数,这会减慢后续调用的速度,因此,只有在您的函数很昂贵的情况下,才可以这样做。”
https://docs.scipy.org/doc/numpy/reference/generation/numpy.vectorize.html

因此,这是有据可查的行为,但似乎违反直觉。 因此,也许它比漏洞修复更能增强功能。

嗯,很明显,我对所有文档的阅读不够充分。 这确实解决了重复调用的问题,但代价是执行速度较慢。 因此,我想有没有办法在保持性能的同时防止这种双重通话?

例如,在numpy/lib/function_base.py vectorize类函数_get_ufunc_and_otypes() ,我天真的认为您可以修改以下几行:

inputs = [arg.flat[0] for arg in args]
outputs = func(*inputs)

至:

#earlier
import copy

...

inputs = copy.deepcopy([arg.flat[0] for arg in args])
outputs = func(*inputs)

然后,您不必使用缓存或指定otype,但我认为您避免两次击中实际的可变元素。 但是我不知道与缓存相比会给性能带来多少影响。

我只是觉得缓存行为的设计考虑了昂贵的函数执行时间,而不是在考虑修改可变对象的情况。 我认为有可能在不考虑高速缓存而影响功能执行时间的情况下容纳可变对象修改。

我认为,对于非常大的对象数组的矢量化操作,对第一个元素进行深层复制实际上可能比在应用该函数上花费更多的计算量。 因此,如果用户未指定otype ,则具有该功能可能是一个好主意,但是在文档中说,除非otype大型数组指定

copy.deepcopy()在许多输入上都不安全,因此不幸的是这不是一个可行的选择。

解决此问题的唯一方法是重写vectorize的核心,该核心当前使用numpy.frompyfunc创建一个实际的numpy ufunc。 需要创建一个备用内部循环,类似于我们用于np.apply_along_axis循环。 不幸的是,为了避免性能下降,我认为我们需要在C中进行循环(就像frompyfunc在其构造的ufunc中所做的那样)。

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