Diferença entre numpy dot () e multiplicação de matriz Python 3.5+ @


119

Recentemente, mudei para Python 3.5 e notei que o novo operador de multiplicação de matriz (@) às vezes se comporta de maneira diferente do operador numpy dot . Por exemplo, para matrizes 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)

O @operador retorna uma matriz de forma:

c.shape
(8, 13, 13)

enquanto a np.dot()função retorna:

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

Como posso reproduzir o mesmo resultado com ponto numpy? Existem outras diferenças significativas?


5
Você não pode obter esse resultado fora do ponto. Acho que as pessoas geralmente concordam que o manuseio do ponto de entradas de alta dimensão foi a decisão de design errada.
user2357112 suporta Monica

Por que eles não implementaram a matmulfunção anos atrás? @como um operador infixo é novo, mas a função funciona tão bem sem ele.
hpaulj

Respostas:


140

O @operador chama o __matmul__método da matriz , não dot. Este método também está presente na API como a função np.matmul.

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

Da documentação:

matmuldifere de dotduas maneiras importantes.

  • A multiplicação por escalares não é permitida.
  • Pilhas de matrizes são transmitidas juntas como se as matrizes fossem elementos.

O último ponto deixa claro que os métodos dote matmulse comportam de maneira diferente quando passados ​​para matrizes 3D (ou dimensões superiores). Citando um pouco mais da documentação:

Para matmul:

Se qualquer um dos argumentos for ND, N> 2, ele será tratado como uma pilha de matrizes que residem nos dois últimos índices e será transmitido de acordo.

Para np.dot:

Para matrizes 2-D, é equivalente à multiplicação de matrizes, e para matrizes 1-D ao produto interno de vetores (sem conjugação complexa). Para N dimensões, é um produto da soma entre o último eixo de a e o penúltimo de b


13
A confusão aqui é provavelmente devido às notas de lançamento, que equiparam diretamente o símbolo "@" à função ponto () de numpy no código de exemplo.
Alex K

12

A resposta de @ajcr explica como o dote matmul(invocado pelo @símbolo) diferem. Olhando para um exemplo simples, pode-se ver claramente como os dois se comportam de maneira diferente quando operam em 'pilhas de matrizes' ou tensores.

Para esclarecer as diferenças, pegue uma matriz 4x4 e retorne o dotproduto e o matmulproduto com uma 'pilha de matrizes' ou tensor 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)))

Os produtos de cada operação aparecem abaixo. Observe como o produto escalar é,

... um produto de soma sobre o último eixo de a e o penúltimo de b

e como o produto da matriz é formado pela difusão da matriz em conjunto.

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
ponto (a, b) [i, j, k, m] = soma (a [i, j ,:] * b [k,:, m]) ------- como a documentação diz: é um somar o produto sobre o último eixo de
ae o

Boa captura, no entanto, é um 3x4x2. Outra forma de construir a matriz a = np.arange(24).reshape(3, 4, 2)seria criar um array com as dimensões 3x4x2.
Nathan

8

Apenas para sua informação, @e seus equivalentes numpy dote matmulsão todos igualmente rápidos. (Plot criado com perfplot , um projeto meu.)

insira a descrição da imagem aqui

Código para reproduzir o gráfico:

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

Em matemática, acho que o ponto em numpy faz mais sentido

ponto (a, b) _ {i, j, k, a, b, c} =Fórmula

uma vez que dá o produto escalar quando a e b são vetores, ou a multiplicação da matriz quando a e b são matrizes


Quanto à operação matmul em numpy, consiste em partes do resultado de ponto , e pode ser definido como

> matmul (a, b) _ {i, j, k, c} =Fórmula

Portanto, você pode ver que matmul (a, b) retorna um array com um formato pequeno, que tem menor consumo de memória e faz mais sentido nos aplicativos. Em particular, combinando com a transmissão , você pode obter

matmul (a, b) _ {i, j, k, l} =Fórmula

por exemplo.


A partir das duas definições acima, você pode ver os requisitos para usar essas duas operações. Assuma a.shape = (s1, s2, s3, s4) e b.shape = (t1, t2, t3, t4)

  • Para usar o ponto (a, b) você precisa

    1. t3 = s4 ;
  • Para usar matmul (a, b) você precisa

    1. t3 = s4
    2. t2 = s2 , ou um de t2 e s2 é 1
    3. t1 = s1 , ou um de t1 e s1 é 1

Use o código a seguir para se convencer.

Amostra de código

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.matmultambém fornece o produto escalar em vetores e o produto matricial em matrizes.
Subhaneil Lahiri

2

Aqui está uma comparação com np.einsumpara mostrar como os índices são projetados

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

Minha experiência com MATMUL e DOT

Eu recebia constantemente "ValueError: A forma dos valores passados ​​é (200, 1), os índices implicam (200, 3)" ao tentar usar o MATMUL. Eu queria uma solução alternativa rápida e descobri que o DOT oferece a mesma funcionalidade. Não recebo nenhum erro ao usar o DOT. Eu recebo a resposta correta

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

com DOT

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

YY.shape

>>> (200, )
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.