индексирование с использованием None для парных операций - PullRequest
0 голосов
/ 29 июня 2018

Если бы у меня было два массива, которые выглядели бы вот так

a = np.array([1, 2])
b = np.array([3, 4])

и я хотел добавить все попарные комбинации, я мог бы легко сделать

c = a + b[:, None]
c
array([[4, 5],
       [5, 6]])

, чтобы получить результат 1 + 3 , 2 + 3 и 1 + 4 , 2 + 4 .

Почему это работает? Что делает «Ни один»? Я могу распечатать

b[:, None]
[[3]
 [4]]

Но я не уверен, почему это говорит numpy делать парные комбинации. Мне также любопытно, насколько эффективно он реализован под капотом по сравнению, скажем, с itertools.combinsk.

Ответы [ 2 ]

0 голосов
/ 29 июня 2018

Чтобы ответить на первую часть вашего вопроса, b[:, None] - это особый тип нарезки, который имеет идентичное поведение с b[:, np.newaxis], так как он добавляет ось длины 1 к вашему массиву.

>>> b.shape
(2,)
>>> b[:, None].shape
(2, 1)

Это поведение задокументировано в numpy документах [1] , выделенном руднике:

Объект newaxis можно использовать во всех операциях среза для создания оси длины один. newaxis - это псевдоним для None, и вместо этого можно использовать None с тем же результатом.

Итак, теперь у нас есть два массива:

array([1, 2]) + array([[3],
                       [4]])

Суммирование этих двух массивов приводит к:

array([[4, 5],
       [5, 6]])

"Волшебство" за этим является numpy вещание [2] . Эта статья [3] - отличный ресурс для начала понимания темы.


Основные выводы из статьи:

numpy операции обычно выполняются поэлементно, для чего требуется, чтобы два массива имели одинаковую форму. Однако это ограничение ослабляется, если оба массива имеют одинаковую заднюю ось или если одна из задних осей равна одной (это поведение проявляется в вашем случае).

В вашем случае происходит вещание, поэтому операция эквивалентна суммированию следующих массивов 2x2:

array([[1, 2],    +  array([[3, 3],
       [1, 2]])            [4, 4]])

Который, поскольку numy-операции выполняются поэлементно, даст желаемый результат:

array([[4, 5],
       [5, 6]])

[1] https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#numpy.newaxis

[2] https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html

[3] http://scipy.github.io/old-wiki/pages/EricsBroadcastingDoc

0 голосов
/ 29 июня 2018

То, что вы делаете, это добавление новой оси: ось "колонна". b раньше такого не было, так что теперь это вектор столбца, и он будет добавлен по столбцам; по сути, он будет действовать так, как если бы он повторялся в столбцах, а a повторялся в строках:

a+b[:, None] = [1,2] + [[3], = [[1,2], + [[3],[3],
                        [4]]    [1,2]]    [4],[4]]

А вот как / почему:

Перво-наперво: numpy делает поэлементное сложение и умножение по умолчанию. Это означает, что если a=np.array([1,2]), то a+2=np.array([1+2,2+2])=np.array([3,5]).

import numpy as np
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[1,1],
              [0,0]])

Здесь A+B будет поэлементным, и мы получим

A+B = [[1+1,2+1], = [[2,3],
       [3+0,4+0]] =  [3,4]]

Если мы транспонируем матрицу B (используя B.T)

сейчас B.T будет иметь значение

B.T= np.array([[1,0],
               [1,0]])

И если мы сделаем это поэлементно, на этот раз мы получим:

A+B.T=[[1, 2]  + [[1,0]  =  [[2,2],
       [3, 4]]    [1,0]]     [4,4]]

Следует также отметить, что (B не транспонирован)

a = np.array([1,2])
B+a = [[1,2], + [[2, 3],
       [1,2]]    [1, 2]]

И это тоже поэлементно, но в обоих рядах! То есть a было практически "повторено", чтобы иметь две строки, и добавлено поэлементно к B.

Далее, Numpy docs говорит, что None в разрезании - это другой способ написания np.newaxis. В вашем случае, опция None в нарезке в основном транспонирует b -вектор перед добавлением!

Точно такой же результат мог быть получен

import numpy as np
a = np.array([1, 2])
b = np.array([3, 4])

c=a+b.reshape(2,1)
d=a+b.reshape(-1,1)
e=a+b[:, np.newaxis]

Здесь c, d и e имеют одинаковые значения!

...