Как реализовать умножение объектов на массивы Numpy с левой стороны? - PullRequest
0 голосов
/ 26 мая 2020

Если кто-то хочет умножить произвольный объект с левой стороны на np.ndarray, он попадает в проблему.

Проблема в том, что numpy.ndarray.__mul__ поэлементно вызывает правую часть __rmul__, если правая часть является пока неизвестным типом.

Чтобы увидеть проблему, вы можете скопировать и вставить следующий код и запустить его из командной строки или SDK. Вы можете пройти через него с помощью отладчика. Комментарии помогут вам и проведут вас через это ...

import numpy as np


class Zora(object):

    def __init__(self, array):
        self._array = array
        self._values_field_name = array.dtype.names[-1]

    @property
    def a(self):
        return self._array

    def __repr__(self):
        return repr(self._array)

    def __mul__(self, other):
        result = self.copy()
        result *= other
        return result

    def __imul__(self, other):
        self._array[self._values_field_name] *= other
        return self

    def __rmul__(self, other):
        return self * other

    def copy(self):
        return self.__class__(self.a.copy())

    @classmethod
    def create(cls, fields, codes, data):
        array = np.array([(*c, d) for c, d in zip(codes, data)], dtype=fields)
        return cls(array)


if __name__ == '__main__':

    # Let's create a Zora dataset with  scenarios
    scenarios = 7

    fields = np.dtype([('LegalEntity', np.unicode_, 32), ('Division', np.unicode_, 32),
                       ('Scenarios', np.float64, (scenarios, ))])

    legal_entities = ['A', 'A', 'B', 'B', 'C']
    divisions = ['a', 'b', 'a', 'b', 'b']

    codes = list(zip(legal_entities, divisions))

    data = np.random.uniform(0., 1., (len(codes), scenarios))

    zora = Zora.create(fields, codes, data)

    # The dataset looks like the following
    print(zora)

    # We can multiply it from the left with scalars ...
    z = zora * 2
    print(zora * 2)

    # ... and with column vectors, for example ...
    # ... for this we generate a columns vector with some weights ...
    numrows = zora.a.shape[0]

    weights = np.expand_dims(np.array(list(range(numrows))), 1)
    # ... the weights ...
    print(weights)

    # ... left side multiplication works fine too with this
    print(zora * weights)

    # Let's show inplace multiplication ...
    # Which we apply on a copy, so that we can still compare ...
    z = zora.copy()
    z *= 2

    # Is pretty fine too, ...
    print(zora)
    print(z)

    # Now it becomes a bit special ...
    # ... when multiplying from the left.
    # It works fine with a scalar..
    z = 2 * zora
    print(z)

    # But becomes special with np.ndarrays ...
    print('-------------------------------------')
    print('-------------------------------------')
    print('The following result ...')
    z = weights * zora
    print(z)

    # which is not the same, but should, as ...
    print('-------------------------------------')
    print('... should be the same as this one ...')

    z = zora * weights
    print(z)

    # We got a list of arrays, where for each i-th array
    # the corresponding i-th weight has been used for
    # multiplication
    #
    # This has to do with numpy's implementation of calling
    # __rmul__ from the right hand side within the __mul__
    # from the np.ndarray ...
    #
    # The same is true for all other __r{...}__ methods

1 Ответ

1 голос
/ 26 мая 2020

Использование Numpy Hook __array_ufunc__

Numpy позволяет обойти эту проблему, правильно определив метод __array_ufunc__ для вашего объекта.

Следующий метод добавлен в Zora класс выше, выполняет свою работу. Его легко обобщить для всех ufunc бинарных операций, включая и некоммутативные операции. Но я показываю только это, чтобы не усложнять ситуацию.

def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
    lhs, rhs = inputs
    return rhs * lhs

Следовательно, следующие результаты покажут ожидаемые результаты ...

import numpy as np


class Zora(object):

    def __init__(self, array):
        self._array = array
        self._values_field_name = array.dtype.names[-1]

    @property
    def a(self):
        return self._array

    def __repr__(self):
        return repr(self._array)

    def __mul__(self, other):
        result = self.copy()
        result *= other
        return result

    def __imul__(self, other):
        self._array[self._values_field_name] *= other
        return self

    def __rmul__(self, other):
        return self * other

    def copy(self):
        return self.__class__(self.a.copy())

    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        lhs, rhs = inputs
        return rhs * lhs

    @classmethod
    def create(cls, fields, codes, data):
        array = np.array([(*c, d) for c, d in zip(codes, data)], dtype=fields)
        return cls(array)


if __name__ == '__main__':

    # Let's create a Zora dataset with  scenarios
    scenarios = 7

    fields = np.dtype([('LegalEntity', np.unicode_, 32), ('Division', np.unicode_, 32),
                       ('Scenarios', np.float64, (scenarios, ))])

    legal_entities = ['A', 'A', 'B', 'B', 'C']
    divisions = ['a', 'b', 'a', 'b', 'b']

    codes = list(zip(legal_entities, divisions))

    data = np.random.uniform(0., 1., (len(codes), scenarios))

    zora = Zora.create(fields, codes, data)

    # The dataset looks like the following
    print(zora)

    # We can multiply it from the left with scalars ...
    z = zora * 2
    print(zora * 2)

    # ... and with column vectors, for example ...
    # ... for this we generate a columns vector with some weights ...
    numrows = zora.a.shape[0]

    weights = np.expand_dims(np.array(list(range(numrows))), 1)
    # ... the weights ...
    print(weights)

    # ... left side multiplication works fine too with this
    print(zora * weights)

    # Let's show inplace multiplication ...
    # Which we apply on a copy, so that we can still compare ...
    z = zora.copy()
    z *= 2

    # Is pretty fine too, ...
    print(zora)
    print(z)

    # Now it becomes a bit special ...
    # ... when multiplying from the left.
    # It works fine with a scalar..
    z = 2 * zora
    print(z)

    # But becomes special with np.ndarrays ...
    print('-------------------------------------')
    print('-------------------------------------')
    print('The following result ...')
    z = weights * zora
    print(z)

    # which is now the same ...
    print('-------------------------------------')
    print('... should be the same as this one ...')

    z = zora * weights
    print(z)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...