Różnica między numpy dot () a mnożeniem macierzy w Pythonie 3.5+ @


119

Niedawno przeniosłem się do Pythona 3.5 i zauważyłem, że nowy operator mnożenia macierzy (@) czasami zachowuje się inaczej niż operator kropki numpy . Na przykład dla tablic 3D:

import numpy as np

a = np.random.rand(8,13,13)
b = np.random.rand(8,13,13)
c = a @ b  # Python 3.5+
d = np.dot(a, b)

@Operator zwraca tablicę kształcie:

c.shape
(8, 13, 13)

podczas gdy np.dot()funkcja zwraca:

d.shape
(8, 13, 8, 13)

Jak mogę odtworzyć ten sam wynik za pomocą numpy dot? Czy są jakieś inne istotne różnice?


5
Nie możesz uzyskać tego wyniku bez kropki. Myślę, że ludzie ogólnie zgodzili się, że obsługa danych wejściowych o dużych wymiarach przez kropkę była złą decyzją projektową.
user2357112 obsługuje Monikę

Dlaczego nie zaimplementowali tej matmulfunkcji lata temu? @jako operator wrostek jest nowy, ale funkcja działa równie dobrze bez niego.
hpaulj

Odpowiedzi:


140

@Operator wywoływany jest konstruktor Array jest __matmul__metoda, nie dot. Ta metoda jest również obecna w API jako funkcja np.matmul.

>>> a = np.random.rand(8,13,13)
>>> b = np.random.rand(8,13,13)
>>> np.matmul(a, b).shape
(8, 13, 13)

Z dokumentacji:

matmulróżni się od dotna dwa ważne sposoby.

  • Mnożenie przez skalary jest niedozwolone.
  • Stosy macierzy są przesyłane razem tak, jakby macierze były elementami.

Ostatni punkt wyjaśnia, że metody doti matmulmetody zachowują się inaczej, gdy przekazywane są tablice 3D (lub wyższego wymiaru). Cytując trochę z dokumentacji:

Dla matmul:

Jeśli którykolwiek z argumentów ma wartość ND, N> 2, jest traktowany jako stos macierzy znajdujących się w dwóch ostatnich indeksach i odpowiednio rozgłaszany.

Dla np.dot:

Dla tablic 2-W jest to równoważne mnożeniu macierzy, a dla tablic 1-D - iloczynowi wewnętrznemu wektorów (bez koniugacji zespolonej). Dla wymiarów N jest to iloczyn sumaryczny ostatniej osi a i przedostatniej części b


13
Zamieszanie tutaj jest prawdopodobnie spowodowane uwagami do wydania, które bezpośrednio zrównują symbol „@” z funkcją kropki () funkcji numpy w przykładowym kodzie.
Alex K

13

Odpowiedź @ajcr wyjaśnia, czym różnią się znaki doti matmul(wywoływane przez @symbol). Patrząc na prosty przykład, wyraźnie widać, jak zachowują się one inaczej, gdy operujemy na „stosach matricies” lub tensorach.

Aby wyjaśnić różnice, weź tablicę 4x4 i zwróć dotprodukt i matmulprodukt ze stosem matryc lub tensorem 3x4x2.

import numpy as np
fourbyfour = np.array([
                       [1,2,3,4],
                       [3,2,1,4],
                       [5,4,6,7],
                       [11,12,13,14]
                      ])


threebyfourbytwo = np.array([
                             [[2,3],[11,9],[32,21],[28,17]],
                             [[2,3],[1,9],[3,21],[28,7]],
                             [[2,3],[1,9],[3,21],[28,7]],
                            ])

print('4x4*3x4x2 dot:\n {}\n'.format(np.dot(fourbyfour,twobyfourbythree)))
print('4x4*3x4x2 matmul:\n {}\n'.format(np.matmul(fourbyfour,twobyfourbythree)))

Produkty każdej operacji przedstawiono poniżej. Zwróć uwagę na iloczyn skalarny,

... iloczyn sumaryczny ostatniej osi a i przedostatniej części b

i jak jest tworzony iloczyn matrycy przez wspólne nadawanie matrycy.

4x4*3x4x2 dot:
 [[[232 152]
  [125 112]
  [125 112]]

 [[172 116]
  [123  76]
  [123  76]]

 [[442 296]
  [228 226]
  [228 226]]

 [[962 652]
  [465 512]
  [465 512]]]

4x4*3x4x2 matmul:
 [[[232 152]
  [172 116]
  [442 296]
  [962 652]]

 [[125 112]
  [123  76]
  [228 226]
  [465 512]]

 [[125 112]
  [123  76]
  [228 226]
  [465 512]]]

2
kropka (a, b) [i, j, k, m] = suma (a [i, j ,:] * b [k,:, m]) ------- tak jak w dokumentacji: to jest iloczyn sumaryczny na ostatniej osi a i przedostatniej osi b:
Ronak Agrawal

Jednak dobry chwyt, to 3x4x2. Innym sposobem na zbudowanie macierzy a = np.arange(24).reshape(3, 4, 2)byłoby utworzenie tablicy o wymiarach 3x4x2.
Nathan

8

Po prostu do Twojej wiadomości, @i jego ponętnych odpowiedników doti matmulwszystkie są mniej więcej równie szybkie. (Działka stworzona za pomocą perfplot , mojego projektu).

wprowadź opis obrazu tutaj

Kod do odtworzenia fabuły:

import perfplot
import numpy


def setup(n):
    A = numpy.random.rand(n, n)
    x = numpy.random.rand(n)
    return A, x


def at(data):
    A, x = data
    return A @ x


def numpy_dot(data):
    A, x = data
    return numpy.dot(A, x)


def numpy_matmul(data):
    A, x = data
    return numpy.matmul(A, x)


perfplot.show(
    setup=setup,
    kernels=[at, numpy_dot, numpy_matmul],
    n_range=[2 ** k for k in range(12)],
    logx=True,
    logy=True,
)

7

W matematyce myślę, że kropka w numpy ma większy sens

kropka (a, b) _ {i, j, k, a, b, c} =formuła

ponieważ daje iloczyn skalarny, gdy a i b są wektorami, lub mnożenie macierzy, gdy a i b są macierzami


Jeśli chodzi o operację matmul w numpy, składa się z części wyniku kropkowego i można go zdefiniować jako

> matmul (a, b) _ {i, j, k, c} =formuła

Widzisz więc, że matmul (a, b) zwraca tablicę o małym kształcie, która ma mniejsze zużycie pamięci i ma większy sens w aplikacjach. W szczególności, łącząc się z nadawaniem , możesz uzyskać

matmul (a, b) _ {i, j, k, l} =formuła

na przykład.


Z powyższych dwóch definicji można zobaczyć wymagania dotyczące korzystania z tych dwóch operacji. Załóżmy, że a.shape = (s1, s2, s3, s4) i b.shape = (t1, t2, t3, t4)

  • Aby użyć kropki (a, b) , potrzebujesz

    1. t3 = s4 ;
  • Aby użyć matmul (a, b) potrzebujesz

    1. t3 = s4
    2. t2 = s2 lub jedno z t2 i s2 wynosi 1
    3. t1 = s1 lub jedno z t1 i s1 to 1

Użyj poniższego fragmentu kodu, aby się przekonać.

Przykład kodu

import numpy as np
for it in xrange(10000):
    a = np.random.rand(5,6,2,4)
    b = np.random.rand(6,4,3)
    c = np.matmul(a,b)
    d = np.dot(a,b)
    #print 'c shape: ', c.shape,'d shape:', d.shape

    for i in range(5):
        for j in range(6):
            for k in range(2):
                for l in range(3):
                    if not c[i,j,k,l] == d[i,j,k,j,l]:
                        print it,i,j,k,l,c[i,j,k,l]==d[i,j,k,j,l] #you will not see them

np.matmuldaje również iloczyn skalarny na wektorach i iloczyn macierzy na macierzach.
Subhaneil Lahiri

2

Oto porównanie z, np.einsumaby pokazać, jak prognozowane są wskaźniki

np.allclose(np.einsum('ijk,ijk->ijk', a,b), a*b)        # True 
np.allclose(np.einsum('ijk,ikl->ijl', a,b), a@b)        # True
np.allclose(np.einsum('ijk,lkm->ijlm',a,b), a.dot(b))   # True

0

Moje doświadczenie z MATMUL i DOT

Ciągle otrzymywałem komunikat „ValueError: Kształt przekazywanych wartości to (200, 1), indeksy implikują (200, 3)” podczas próby użycia MATMUL. Chciałem szybkiego obejścia problemu i stwierdziłem, że DOT zapewnia tę samą funkcjonalność. Nie otrzymuję żadnego błędu podczas używania DOT. Mam poprawną odpowiedź

z MATMUL

X.shape
>>>(200, 3)

type(X)

>>>pandas.core.frame.DataFrame

w

>>>array([0.37454012, 0.95071431, 0.73199394])

YY = np.matmul(X,w)

>>>  ValueError: Shape of passed values is (200, 1), indices imply (200, 3)"

z DOT

YY = np.dot(X,w)
# no error message
YY
>>>array([ 2.59206877,  1.06842193,  2.18533396,  2.11366346,  0.28505879, 

YY.shape

>>> (200, )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.