Numpy: векторизация в контексте операции 1: много - PullRequest
1 голос
/ 30 января 2020

Предположим, у меня есть следующие функции, определенные только для создания топологии функций, о которых идет речь:

def foo(x,y):
  return np.asarray([x for i in range(y)])

bar = lambda x: foo(x,10)
barv = np.vectorize(bar)

z = np.asarray([1, 2, 3])

И следующая подпрограмма:

for i in range(z.shape[0]):
  rng = np.arange(z[i],100)
  # res = barv(rng)
  res = np.asarray(list(map(bar,rng)))

Вышеприведенная подпрограмма работает. Однако, если я раскомментирую и запусту векторизованную версию, например:

for i in range(z.shape[0]):
  rng = np.arange(z[i],100)
  res = barv(rng)

Код завершится ошибкой с последующей ошибкой:

Traceback (most recent call last):
  File "C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3326, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-14-660195661e55>", line 3, in <module>
    res = barv(rng)
  File "C:\ProgramData\Anaconda3\lib\site-packages\numpy\lib\function_base.py", line 2091, in __call__
    return self._vectorize_call(func=func, args=vargs)
  File "C:\ProgramData\Anaconda3\lib\site-packages\numpy\lib\function_base.py", line 2170, in _vectorize_call
    res = array(outputs, copy=False, subok=True, dtype=otypes[0])
ValueError: setting an array element with a sequence.

Ошибка имеет смысл. Однако должен быть какой-то способ сделать векторизованную операцию 1: много в numpy?

1 Ответ

0 голосов
/ 31 января 2020

vectorize предназначен для использования с scalar функциями, которые принимают скалярные входные данные и возвращают скалярные выходные данные.

In [729]: foo(z,10)                                                                              
Out[729]: 
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])
In [730]: bar(z)                                                                                 
Out[730]: 
array([[1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3],
       [1, 2, 3]])

Ваш bar возвращает 2d массив, если ему дано 1d вход. Или 1d массив, если дан скалярный вход

In [734]: bar(4)                                                                                 
Out[734]: array([4, 4, 4, 4, 4, 4, 4, 4, 4, 4])

Мы можем vectorize ожидать, что object возврат

In [735]: barv = np.vectorize(bar, otypes=[object])                                              
In [736]: barv(4)                                                                                
Out[736]: array([4, 4, 4, 4, 4, 4, 4, 4, 4, 4], dtype=object)
In [737]: barv(z)                                                                                
Out[737]: 
array([array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]),
       array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2]),
       array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])], dtype=object)

, который можно превратить в 2d массив :

In [738]: np.stack(_)                                                                            
Out[738]: 
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]])

vectorize также имеет параметр подписи, который может помочь в этом случае. Но по моему опыту это даже медленнее.

Но нам не нужно vectorize здесь - простое понимание списка такое же хорошее, возможно, лучше:

In [739]: np.stack([bar(i) for i in z])                                                          
Out[739]: 
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]])

vectorize делает «вещание» с несколькими входными массивами проще, но это не улучшает скорость. Посмотрите, сможете ли вы выяснить, что vectorize делает здесь с foo:

In [743]: f = np.vectorize(foo, otypes=[object])                                                 
In [744]: f(np.array([1,2,3]), np.array([2,3,4]))                                                
Out[744]: array([array([1, 1]), array([2, 2, 2]), array([3, 3, 3, 3])], dtype=object)
In [745]: f(np.array([1,2,3]), np.array([[2],[3]]))                                              
Out[745]: 
array([[array([1, 1]), array([2, 2]), array([3, 3])],
       [array([1, 1, 1]), array([2, 2, 2]), array([3, 3, 3])]],
      dtype=object)

edit

Правильно numpy векторизация:

In [762]: np.repeat(z[:,None],10,1)                                                              
Out[762]: 
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]])

Некоторые Сравнение времени:

In [766]: timeit np.stack(barv(z))                                                               
60 µs ± 1.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [767]: timeit np.stack([bar(i) for i in z])                                                   
39.6 µs ± 132 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [768]: timeit np.repeat(z[:,None],10,1)                                                       
4.12 µs ± 14.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Ваш foo уже работает с входом массива. Не было необходимости в np.vectorize обертке.

In [783]: foo(z,10).T                                                                            
Out[783]: 
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]])
In [784]: timeit foo(z,10).T                                                                     
10.8 µs ± 364 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...