Différence entre numpy dot () et multiplication matricielle Python 3.5+ @

119

Je suis récemment passé à Python 3.5 et j'ai remarqué que le nouvel opérateur de multiplication matricielle (@) se comporte parfois différemment de l' opérateur numpy dot . Par exemple, pour les tableaux 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' @opérateur renvoie un tableau de forme:

c.shape
(8, 13, 13)

tandis que la np.dot()fonction renvoie:

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

Comment puis-je reproduire le même résultat avec numpy dot? Y a-t-il d'autres différences significatives?

blaz
la source
5
Vous ne pouvez pas obtenir ce résultat par point. Je pense que les gens ont généralement convenu que la gestion par point des entrées de grande dimension était la mauvaise décision de conception.
user2357112 prend en charge Monica
Pourquoi n'ont-ils pas implémenté la matmulfonction il y a des années? @en tant qu'opérateur infixe est nouveau, mais la fonction fonctionne aussi bien sans lui.
hpaulj

Réponses:

140

L' @opérateur appelle la __matmul__méthode du tableau , non dot. Cette méthode est également présente dans l'API en tant que fonction np.matmul.

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

De la documentation:

matmuldiffère de dotdeux manières importantes.

  • La multiplication par des scalaires n'est pas autorisée.
  • Les piles de matrices sont diffusées ensemble comme si les matrices étaient des éléments.

Le dernier point montre clairement que dotetmatmul méthodes se comportent différemment lorsqu'elles sont transmises à des tableaux 3D (ou de dimension supérieure). Citant un peu plus la documentation:

Pour matmul:

Si l'un des arguments est ND, N> 2, il est traité comme une pile de matrices résidant dans les deux derniers index et diffusé en conséquence.

Pour np.dot:

Pour les tableaux 2-D, cela équivaut à la multiplication matricielle, et pour les tableaux 1-D, au produit interne des vecteurs (sans conjugaison complexe). Pour N dimensions, il s'agit d'un produit de somme sur le dernier axe de a et l'avant-dernier de b

Alex Riley
la source
13
La confusion ici est probablement due aux notes de publication, qui assimilent directement le symbole "@" à la fonction dot () de numpy dans l'exemple de code.
Alex K
13

La réponse de @ajcr explique comment le dotet matmul(invoqué par le@ symbole) diffèrent. En regardant un exemple simple, on voit clairement comment les deux se comportent différemment lorsqu'ils fonctionnent sur des «piles de matrices» ou tenseurs.

Pour clarifier les différences, prenez un tableau 4x4 et retournez le dotproduit et le matmulproduit avec une «pile de matrices» ou tenseur 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)))

Les produits de chaque opération apparaissent ci-dessous. Remarquez comment le produit scalaire est,

... un produit somme sur le dernier axe de a et l'avant-dernier de b

et comment le produit matriciel est formé en diffusant la matrice ensemble.

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]]]
Nathan
la source
2
dot (a, b) [i, j, k, m] = sum (a [i, j ,:] * b [k,:, m]) ------- comme la documentation dit: c'est un produit somme sur le dernier axe de a et l'avant-dernier axe de b:
Ronak Agrawal
Bonne prise cependant, c'est un 3x4x2. Une autre façon de construire la matrice serait de a = np.arange(24).reshape(3, 4, 2)créer un tableau avec les dimensions 3x4x2.
Nathan
8

Juste @pour info , et ses équivalents numpy dotet matmulsont tous à peu près également rapides. (Terrain créé avec perfplot , un de mes projets.)

entrez la description de l'image ici

Code pour reproduire le tracé:

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,
)
Nico Schlömer
la source
7

En mathématiques, je pense que le point dans numpy a plus de sens

point (a, b) _ {i, j, k, a, b, c} =formule

puisqu'elle donne le produit scalaire lorsque a et b sont des vecteurs, ou la multiplication matricielle lorsque a et b sont des matrices


Quant à l' opération matmul dans numpy, elle se compose de parties de résultat de point et peut être définie comme

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

Ainsi, vous pouvez voir que matmul (a, b) renvoie un tableau avec une petite forme, qui consomme moins de mémoire et qui a plus de sens dans les applications. En particulier, en combinant avec la diffusion , vous pouvez obtenir

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

par exemple.


À partir des deux définitions ci-dessus, vous pouvez voir les conditions requises pour utiliser ces deux opérations. Supposons que a.shape = (s1, s2, s3, s4) et b.shape = (t1, t2, t3, t4)

  • Pour utiliser le point (a, b), vous avez besoin

    1. t3 = s4 ;
  • Pour utiliser matmul (a, b), vous avez besoin

    1. t3 = s4
    2. t2 = s2 , ou l'un de t2 et s2 est 1
    3. t1 = s1 , ou l'un de t1 et s1 est 1

Utilisez le morceau de code suivant pour vous convaincre.

Exemple de code

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
Yong Yang
la source
np.matmuldonne également le produit scalaire sur les vecteurs et le produit matriciel sur les matrices.
Subhaneil Lahiri
2

Voici une comparaison avec np.einsumpour montrer comment les indices sont projetés

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
Friedrich
la source
0

Mon expérience avec MATMUL et DOT

J'obtenais constamment "ValueError: la forme des valeurs passées est (200, 1), les indices impliquent (200, 3)" en essayant d'utiliser MATMUL. Je voulais une solution de contournement rapide et j'ai trouvé que DOT offrait la même fonctionnalité. Je n'obtiens aucune erreur en utilisant DOT. J'ai la bonne réponse

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

avec DOT

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

YY.shape

>>> (200, )
Sambath Parthasarathy
la source