Differenza tra numpy punto () e moltiplicazione di matrici Python 3.5+ @


119

Di recente sono passato a Python 3.5 e ho notato che il nuovo operatore di moltiplicazione di matrici (@) a volte si comporta in modo diverso dall'operatore di punto numpy . Ad esempio, per array 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)

L' @operatore restituisce un array di forma:

c.shape
(8, 13, 13)

mentre la np.dot()funzione restituisce:

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

Come posso riprodurre lo stesso risultato con numpy dot? Ci sono altre differenze significative?


5
Non puoi ottenere quel risultato dal punto. Penso che le persone generalmente concordassero sul fatto che la gestione di input di dimensioni elevate da parte di dot fosse la decisione di progettazione sbagliata.
user2357112 supporta Monica

Perché non hanno implementato la matmulfunzione anni fa? @come operatore infisso è nuovo, ma la funzione funziona altrettanto bene senza di esso.
hpaulj

Risposte:


140

L' @operatore chiama il __matmul__metodo dell'array , non dot. Questo metodo è presente anche nell'API come funzione np.matmul.

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

Dalla documentazione:

matmuldifferisce da dotin due modi importanti.

  • La moltiplicazione per scalari non è consentita.
  • Pile di matrici vengono trasmesse insieme come se le matrici fossero elementi.

L'ultimo punto rende chiaro che i metodi dote si matmulcomportano in modo diverso quando passano array 3D (o di dimensioni superiori). Citando ancora dalla documentazione:

Per matmul:

Se uno degli argomenti è ND, N> 2, viene trattato come una pila di matrici che risiede negli ultimi due indici e trasmesso di conseguenza.

Per np.dot:

Per le matrici 2-D è equivalente alla moltiplicazione di matrici e per le matrici 1-D al prodotto interno di vettori (senza coniugazione complessa). Per N dimensioni è un prodotto somma sull'ultimo asse di a e sul penultimo di b


13
La confusione qui è probabilmente dovuta alle note di rilascio, che equiparano direttamente il simbolo "@" alla funzione punto () di numpy nel codice di esempio.
Alex K

13

La risposta di @ajcr spiega come differiscono dote matmul(invocati dal @simbolo). Guardando un semplice esempio, si vede chiaramente come i due si comportano in modo diverso quando operano su "pile di matricole" o tensori.

Per chiarire le differenze, prendere un array 4x4 e restituire il dotprodotto e il matmulprodotto con una "pila di matrici" o tensore 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)))

I prodotti di ciascuna operazione vengono visualizzati di seguito. Nota come è il prodotto puntino,

... un prodotto della somma sull'ultimo asse di a e sul penultimo di b

e come si forma il prodotto matrice trasmettendo insieme la matrice.

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
punto (a, b) [i, j, k, m] = sum (a [i, j ,:] * b [k,:, m]) ------- come la documentazione dice: è un somma del prodotto sull'ultimo asse di a e sul penultimo asse di b:
Ronak Agrawal

Buona cattura comunque, è un 3x4x2. Un altro modo per costruire la matrice sarebbe quello di a = np.arange(24).reshape(3, 4, 2)creare un array con le dimensioni 3x4x2.
Nathan

8

Solo FYI, @e i suoi equivalenti insensibili dote matmulsono tutti più o meno ugualmente veloci. (Trama creata con perfplot , un mio progetto.)

inserisci qui la descrizione dell'immagine

Codice per riprodurre la trama:

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

In matematica, penso che il punto in numpy abbia più senso

punto (a, b) _ {i, j, k, a, b, c} =formula

poiché fornisce il prodotto scalare quando aeb sono vettori, o la moltiplicazione di matrici quando aeb sono matrici


Per quanto riguarda l' operazione matmul in numpy, consiste di parti del risultato del punto e può essere definita come

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

Quindi, puoi vedere che matmul (a, b) restituisce un array con una forma piccola, che ha un consumo di memoria inferiore e ha più senso nelle applicazioni. In particolare, combinando con la trasmissione , puoi ottenere

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

per esempio.


Dalle due definizioni precedenti, puoi vedere i requisiti per utilizzare queste due operazioni. Supponiamo a.shape = (s1, s2, s3, s4) e b.shape = (t1, t2, t3, t4)

  • Per usare il punto (a, b) è necessario

    1. t3 = s4 ;
  • Per usare matmul (a, b) è necessario

    1. t3 = s4
    2. t2 = s2 o uno tra t2 e s2 è 1
    3. t1 = s1 , o uno tra t1 e s1 è 1

Usa il seguente pezzo di codice per convincerti.

Esempio di codice

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.matmulfornisce anche il prodotto scalare sui vettori e il prodotto matrice sulle matrici.
Subhaneil Lahiri,

2

Ecco un confronto con np.einsumper mostrare come sono proiettati gli indici

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

La mia esperienza con MATMUL e DOT

Ricevo costantemente "ValueError: la forma dei valori passati è (200, 1), gli indici implicano (200, 3)" quando cercavo di usare MATMUL. Volevo una soluzione rapida e ho scoperto che DOT offriva la stessa funzionalità. Non ricevo alcun errore utilizzando DOT. Ottengo la risposta corretta

con 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)"

con DOT

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

YY.shape

>>> (200, )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.