【问题标题】:Is it possible to numpy.vectorize an instance method?是否可以 numpy.vectorize 实例方法?
【发布时间】:2018-08-05 11:54:23
【问题描述】:

我发现numpy.vectorize 允许将期望单个数字作为输入的“普通”函数转换为函数,该函数还可以将输入列表转换为函数已映射到每个函数的列表输入。例如,以下测试通过:

import numpy as np
import pytest


@np.vectorize
def f(x):
    if x == 0:
        return 1
    else:
        return 2


def test_1():
    assert list(f([0, 1, 2])) == [1, 2, 2]

def test_2():
    assert f(0) == 1

if __name__ == "__main__":
    pytest.main([__file__])

但是,对于使用实例属性的实例方法,我无法让它工作。例如:

class Dummy(object):
    def __init__(self, val=1):
        self.val = val

    @np.vectorize
    def f(self, x):
        if x == 0:
            return self.val
        else:
            return 2


def test_3():
    assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]

此测试失败:

=================================== FAILURES ===================================
____________________________________ test_3 ____________________________________

    def test_3():
>       assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]

test_numpy_vectorize.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numpy/lib/function_base.py:2739: in __call__
    return self._vectorize_call(func=func, args=vargs)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numpy/lib/function_base.py:2809: in _vectorize_call
    ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <numpy.lib.function_base.vectorize object at 0x106546470>
func = <function Dummy.f at 0x10653a2f0>, args = [array([0, 1, 2])]

    def _get_ufunc_and_otypes(self, func, args):
        """Return (ufunc, otypes)."""
        # frompyfunc will fail if args is empty
        if not args:
            raise ValueError('args can not be empty')

        if self.otypes is not None:
            otypes = self.otypes
            nout = len(otypes)

            # Note logic here: We only *use* self._ufunc if func is self.pyfunc
            # even though we set self._ufunc regardless.
            if func is self.pyfunc and self._ufunc is not None:
                ufunc = self._ufunc
            else:
                ufunc = self._ufunc = frompyfunc(func, len(args), nout)
        else:
            # Get number of outputs and output types by calling the function on
            # the first entries of args.  We also cache the result to prevent
            # the subsequent call when the ufunc is evaluated.
            # Assumes that ufunc first evaluates the 0th elements in the input
            # arrays (the input values are not checked to ensure this)
            args = [asarray(arg) for arg in args]
            if builtins.any(arg.size == 0 for arg in args):
                raise ValueError('cannot call `vectorize` on size 0 inputs '
                                 'unless `otypes` is set')

            inputs = [arg.flat[0] for arg in args]
>           outputs = func(*inputs)
E           TypeError: f() missing 1 required positional argument: 'x'

是否可以将numpy.vectorize 应用于实例方法?

【问题讨论】:

  • 装饰器版本@vectorize比函数版本更难应用。您的方法f 有两个参数,selfxvectorize 不够聪明,无法将 Dummy() 分配给 self,并在 [0, 1, 2] 上进行迭代。
  • 如果您打算对np.vectorize 做任何严肃的事情,我建议您研究它的代码。和np.frompyfunc一起玩。

标签: python numpy vectorization python-decorators


【解决方案1】:

不修改类的简单解决方案

您可以直接在实例上的方法上使用np.vectorize

class Dummy(object):

    def __init__(self, val=1):
        self.val = val

    def f(self, x):
        if x == 0:
            return self.val
        else:
            return 2


vec_f = np.vectorize(Dummy().f) 


def test_3():
    assert list(vec_f([0, 1, 2])) == [1, 2, 2]

test_3()

您还可以在__init__ 中创建矢量化函数vec_f

向实例添加矢量化版本

class Dummy(object):

    def __init__(self, val=1):
        self.val = val
        self.vec_f = np.vectorize(self.f) 

    def f(self, x):
        if x == 0:
            return self.val
        else:
            return 2


def test_3():
    assert list(Dummy().vec_f([0, 1, 2])) == [1, 2, 2]

或使用不同的命名方案:

class Dummy(object):

    def __init__(self, val=1):
        self.val = val
        self.f = np.vectorize(self.scalar_f) 

    def scalar_f(self, x):
        if x == 0:
            return self.val
        else:
            return 2


def test_3():
    assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]

test_3()

    test_3()

【讨论】:

  • 是的。这也可以,但每次调用f 时都会调用np.vectorize。在我的例子中,向量化只在实例化时完成一次。
【解决方案2】:

如果您想使用方法的矢量化实现,可以使用excluded 参数,如下所示:

class MyClass:
    def __init__(self, data):
        self.data = data
        self.my_vectorized_func = np.vectorize(self.my_func, excluded='self')

    def my_func(self, x):
        return pow(x, self.data)

有了这个,你可以像非矢量化的那样使用你的方法:

 In[1]: myclass = MyClass(3) # '3' will be the power factor of our function
 In[2]: myclass.my_vectorized_func([1, 2, 3, 4, 5])
Out[3]: array([  1,   8,  27,  64, 125])

【讨论】:

    【解决方案3】:

    想起我在memoized 装饰器中看到的一种技术,我设法通过子类化numpy.vectorize 使装饰器也适用于实例方法,如下所示:

    import numpy as np
    import functools
    
    
    class vectorize(np.vectorize):
        def __get__(self, obj, objtype):
            return functools.partial(self.__call__, obj)
    

    现在,如果我用vectorize 而不是np.vectorize 来装饰Dummy 类的f 方法,则测试通过:

    class Dummy(object):
        def __init__(self, val=1):
            self.val = val
    
        @vectorize
        def f(self, x):
            if x == 0:
                return self.val
            else:
                return 2
    
    
    def test_3():
        assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
    
    if __name__ == "__main__":
        pytest.main([__file__])
    

    有输出

    test_numpy_vectorize.py .
    
    =========================== 1 passed in 0.01 seconds ===========================
    [Finished in 0.7s]
    

    【讨论】:

    • np.vectorize 用作装饰器只是因为它将函数作为参数并返回一个函数。但是这种vectorize 的简单使用是有限的。人们经常遇到vectorize 的问题,因为他们没有指定otypes 甚至signature(在最近的版本中)。
    【解决方案4】:

    这是一个适用于实例方法和函数的通用装饰器(请参阅 Numpy's documentation 了解 otypessignature):

    from functools import wraps
    
    import numpy as np
    
    def vectorize(otypes=None, signature=None):
        """Numpy vectorization wrapper that works with instance methods."""
        def decorator(fn):
            vectorized = np.vectorize(fn, otypes=otypes, signature=signature)
            @wraps(fn)
            def wrapper(*args):
                return vectorized(*args)
            return wrapper
        return decorator
    

    您可以使用它来向量化您的方法,如下所示:

    class Dummy(object):
        def __init__(self, val=1):
            self.val = val
    
        @vectorize(signature="(),()->()")
        def f(self, x):
            if x == 0:
                return self.val
            else:
                return 2
    
    
    def test_3():
        assert list(Dummy().f([0, 1, 2])) == [1, 2, 2]
    

    关键是使用signature kwarg。 -&gt; 左边的括号值指定输入参数,右边的值指定输出值。 () 代表一个标量(0维向量); (n) 代表一维向量; (m,n) 代表一个二维向量; (m,n,p)代表一个3维向量;在这里,signature="(),()-&gt;()" 向 Numpy 指定第一个参数 (self) 是一个标量,第二个 (x) 也是一个标量,并且该方法返回一个标量(self.val2 ,取决于x)。

    $ pytest /tmp/instance_vectorize.py
    ======================= test session starts ========================
    platform linux -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
    rootdir: /tmp, inifile:
    collected 1 item
    
    ../../tmp/instance_vectorize.py .                                                                                                                                                     [100%]
    
    ==================== 1 passed in 0.08 seconds ======================
    

    【讨论】:

      【解决方案5】:

      来自docs

      vectorized 输出的数据类型是通过调用输入的第一个元素的函数来确定的。这可以通过指定 otypes 参数来避免。

      函数f(self, x) 中的第一个输入是self。也许您可以将该函数作为staticmethod 函数的包装器?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-21
        • 2016-04-10
        • 2014-12-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多