Різниця між множиною крапки () і множенням матриці Python 3.5+ @


119

Нещодавно я перейшов на Python 3.5 і помітив, що новий оператор множення матриць (@) іноді поводиться інакше, ніж оператор numpy dot . Наприклад, для 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)

@Оператор повертає масив форми:

c.shape
(8, 13, 13)

поки np.dot()функція повертається:

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

Як я можу відтворити той самий результат за допомогою нумерова точки? Чи є інші суттєві відмінності?


5
Ви не можете отримати результат від крапки. Я думаю, що люди, як правило, погоджувались з тим, що поводження з крапками з великими розмірами є неправильним дизайнерським рішенням.
user2357112 підтримує Моніку

Чому вони не реалізували цю matmulфункцію років тому? @як оператор інфікування новий, але функція працює так само добре, як без нього.
hpaulj

Відповіді:


140

@Оператор називає масиву в __matmul__метод, а НЕ dot. Цей метод також присутній в API як функція np.matmul.

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

З документації:

matmulвідрізняється від dotдвох важливих способів.

  • Множення на скаляри заборонено.
  • Стеки матриць передаються разом так, ніби матриці були елементами.

Останній пункт дає зрозуміти, що dotі matmulметоди поводяться по-різному, коли передаються 3D (або більш мірні) масиви. Цитуючи з документації ще кілька:

Для matmul:

Якщо будь-який аргумент ND, N> 2, він трактується як стек матриць, що знаходяться в останніх двох індексах, і передається відповідно.

Для np.dot:

Для 2-D масивів це еквівалентно множенню матриць, а для 1-D масивів - внутрішньому добутку векторів (без складного спряження). Для N розмірів - це добуток суми над останньою віссю a і другою-останньою b


13
Плутанина тут, ймовірно, пов’язана з примітками до випуску, які безпосередньо прирівнюють символ "@" дот () функції numpy у прикладі коду.
Алекс К

12

У відповіді @ajcr пояснюється, чим відрізняються знаки dotта matmul(на яких посилається @символ). Дивлячись на простий приклад, чітко видно, як вони поводяться по-різному під час роботи над "стеками матриць" або тензорами.

Щоб уточнити відмінності, візьміть масив 4x4 і поверніть dotпродукт і matmulпродукт 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)))

Продукти кожної операції відображаються нижче. Зауважте, як крапковий продукт,

... добуток суми за останньою віссю a і другою-останньою b

і як утворюється матричний продукт шляхом трансляції матриці разом.

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
крапка (a, b) [i, j, k, m] = сума (a [i, j ,:] * b [k,:, m]) ------- як документація говорить: це добуток суми за останньою віссю a і другою-останньою віссю b:
Ronak Agrawal

Хороший улов, проте, його 3x4x2. Іншим способом побудови матриці було a = np.arange(24).reshape(3, 4, 2)б створення масиву розмірами 3x4x2.
Натан

8

Просто FYI, @а також його численні еквіваленти - dotі matmulце приблизно однаково швидко. (Сюжет створений за допомогою perfplot , мій проект.)

введіть тут опис зображення

Код для відтворення сюжету:

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

У математиці я думаю, що крапка в нуме має більше сенсу

крапка (a, b) _ {i, j, k, a, b, c} =формула

оскільки він дає крапковий добуток, коли a і b є векторами, або множення матриці, коли a і b є матрицями


Що стосується операції matmul у numpy, вона складається з частин точкового результату, і її можна визначити як

> matmul (a, b) _ {i, j, k, c} =формула

Отже, ви можете бачити, що matmul (a, b) повертає масив невеликої форми, який має менший обсяг пам’яті та має більше сенсу в додатках. Зокрема, поєднуючись із мовленням , можна отримати

matmul (a, b) _ {i, j, k, l} =формула

наприклад.


З наведених вище визначень видно вимоги щодо використання цих двох операцій. Припустимо, а.шапе = (s1, s2, s3, s4) і b.shape = (t1, t2, t3, t4)

  • Для використання точки (a, b) вам потрібно

    1. t3 = s4 ;
  • Для використання matmul (a, b) вам потрібно

    1. t3 = s4
    2. t2 = s2 , або один з t2 і s2 дорівнює 1
    3. t1 = s1 , або один з t1 і s1 дорівнює 1

Використовуйте наступний фрагмент коду, щоб переконати себе.

Зразок коду

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.matmulтакож дає крапковий добуток на векторах, а матричний добуток на матрицях.
Subhaneil Lahiri

2

Ось порівняння, np.einsumщоб показати, як проектуються індекси

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

Мій досвід роботи з MATMUL і DOT

Я постійно отримував "ValueError: Форма переданих значень (200, 1), індекси означають (200, 3)" при спробі використовувати MATMUL. Я хотів швидкого вирішення і знайшов DOT, щоб забезпечити той самий функціонал. Я не отримую жодної помилки за допомогою DOT. Я отримую правильну відповідь

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

з DOT

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

YY.shape

>>> (200, )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.