web-dev-qa-db-ja.com

インスタンスメソッドをnumpy.vectorizeすることは可能ですか?

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をインスタンスメソッドに適用することは可能ですか?

12
Kurt Peek

クラスを変更しない簡単なソリューション

インスタンスのメソッドで直接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()

vec_fにベクトル化された関数__init__を作成することもできます。

ベクトル化されたバージョンをインスタンスに追加する

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()
6
Mike Müller

メソッドのベクトル化された実装を使用する場合は、次のように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])
1
jumpman24

memoized デコレータで見たテクニックを思い出し、次のようにnumpy.vectorizeをサブクラス化することで、デコレータをインスタンスメソッドでも機能させることができました。

import numpy as np
import functools


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

ここで、Dummy class'fメソッドをnp.vectorizeではなくvectorizeで装飾すると、テストに合格します。

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]
1
Kurt Peek

これは、インスタンスメソッドと関数で動作する一般的なデコレータです(otypessignatureについては Numpyのドキュメント を参照してください)。

_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]
_

重要なのは、signaturekwargを利用することです。 _->_の左側の括弧で囲まれた値は入力パラメーターを指定し、右側の値は出力値を指定します。 _()_はスカラー(0次元ベクトル)を表します。 _(n)_は1次元ベクトルを表します。 _(m,n)_は2次元ベクトルを表します。 _(m,n,p)_は3次元ベクトルを表します。ここで、signature="(),()->()"は、最初のパラメーター(self)がスカラーであり、2番目のパラメーター(x)もスカラーであることをNumpyに指定し、メソッドはスカラー(xに応じて、_self.val_または_2_のいずれか)。

_$ 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 ======================
_
1
Dylon

docs から:

ベクトル化された出力のデータ型は、入力の最初の要素で関数を呼び出すことによって決定されます。これは、otypes引数を指定することで回避できます。

関数f(self, x)の最初の入力はselfです。たぶん、その関数をstaticmethod関数のラッパーにすることができますか?

0
user7345804