Sự khác biệt giữa phép nhân ma trận numpy dot () và Python 3.5+ @


119

Gần đây tôi đã chuyển sang Python 3.5 và nhận thấy toán tử nhân ma trận mới (@) đôi khi hoạt động khác với toán tử chấm numpy . Ví dụ, đối với mảng 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ác @nhà điều hành trả về một mảng các hình dạng:

c.shape
(8, 13, 13)

trong khi np.dot()hàm trả về:

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

Làm cách nào để tạo lại kết quả tương tự với chấm numpy? Có sự khác biệt đáng kể nào khác không?


5
Bạn không thể lấy kết quả đó ra khỏi dấu chấm. Tôi nghĩ mọi người thường đồng ý rằng việc xử lý đầu vào kích thước cao của dot là một quyết định thiết kế sai lầm.
user2357112 hỗ trợ Monica

Tại sao họ không thực hiện matmulchức năng từ nhiều năm trước? @vì một toán tử infix là mới, nhưng hàm hoạt động tốt khi không có nó.
hpaulj

Câu trả lời:


140

Các @nhà điều hành gọi của mảng __matmul__phương pháp, không dot. Phương thức này cũng có trong API dưới dạng hàm np.matmul.

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

Từ tài liệu:

matmulkhác với dothai cách quan trọng.

  • Không được phép nhân với các đại lượng vô hướng.
  • Các chồng ma trận được phát cùng nhau như thể các ma trận là phần tử.

Điểm cuối cùng làm rõ điều đó dotmatmulcác phương thức hoạt động khác nhau khi truyền mảng 3D (hoặc chiều cao hơn). Trích dẫn từ tài liệu một số chi tiết:

Đối với matmul:

Nếu một trong hai đối số là ND, N> 2, nó được coi là một chồng ma trận nằm trong hai chỉ mục cuối cùng và phát sóng tương ứng.

Đối với np.dot:

Đối với mảng 2-D, nó tương đương với phép nhân ma trận và đối với mảng 1-D là tích bên trong của vectơ (không có liên hợp phức tạp). Đối với N kích thước, nó là một tích tổng trên trục cuối cùng của a và tích thứ hai đến cuối cùng của b


13
Sự nhầm lẫn ở đây có lẽ là do các ghi chú phát hành, trực tiếp tương đương với ký hiệu "@" với hàm dấu chấm () của numpy trong mã ví dụ.
Alex K

12

Câu trả lời của @ajcr giải thích sự khác nhau giữa dotmatmul(được gọi bằng @biểu tượng) như thế nào . Bằng cách xem xét một ví dụ đơn giản, người ta có thể thấy rõ ràng cả hai hoạt động khác nhau như thế nào khi hoạt động trên 'các ma trận' hoặc tensors.

Để làm rõ sự khác biệt, hãy lấy mảng 4x4 và trả về dotsản phẩm và matmulsản phẩm bằng 'chồng ma trận' 3x4x2 hoặc tensor.

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

Các sản phẩm của mỗi hoạt động xuất hiện bên dưới. Để ý sản phẩm chấm như thế nào,

... tổng tích trên trục cuối cùng của a và tích thứ hai đến cuối cùng của b

và sản phẩm ma trận được hình thành như thế nào bằng cách phát ma trận lại với nhau.

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
dot (a, b) [i, j, k, m] = sum (a [i, j ,:] * b [k,:, m]) ------- như tài liệu nói: nó là a sản phẩm tổng hợp trên trục cuối cùng của a và trục thứ hai-to-cuối cùng của b:
Ronak Agrawal

Tuy nhiên, bắt tốt, nó là 3x4x2. Một cách khác để xây dựng ma trận sẽ là a = np.arange(24).reshape(3, 4, 2)tạo một mảng có kích thước 3x4x2.
Nathan

8

Chỉ là FYI, @và các sản phẩm tương đương của nó dotmatmulđều nhanh như nhau. (Âm mưu được tạo bằng perfplot , một dự án của tôi.)

nhập mô tả hình ảnh ở đây

Mã để tái tạo cốt truyện:

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

Trong toán học, tôi nghĩ dấu chấm trong numpy có ý nghĩa hơn

dấu chấm (a, b) _ {i, j, k, a, b, c} =công thức

vì nó cho tích số chấm khi a và b là vectơ hoặc phép nhân ma trận khi a và b là ma trận


Đối với hoạt động matmul trong numpy, nó bao gồm các phần của kết quả chấm và nó có thể được định nghĩa là

> matmul (a, b) _ {i, j, k, c} =công thức

Vì vậy, bạn có thể thấy rằng matmul (a, b) trả về một mảng có hình dạng nhỏ, có mức tiêu thụ bộ nhớ nhỏ hơn và có ý nghĩa hơn trong các ứng dụng. Đặc biệt, kết hợp với phát sóng , bạn có thể nhận được

matmul (a, b) _ {i, j, k, l} =công thức

ví dụ.


Từ hai định nghĩa trên, bạn có thể thấy các yêu cầu để sử dụng hai thao tác đó. Giả sử a.shape = (s1, s2, s3, s4)b.shape = (t1, t2, t3, t4)

  • Để sử dụng dấu chấm (a, b) bạn cần

    1. t3 = s4 ;
  • Để sử dụng matmul (a, b) bạn cần

    1. t3 = s4
    2. t2 = s2 , hoặc một trong t2 và s2 là 1
    3. t1 = s1 , hoặc một trong t1 và s1 là 1

Sử dụng đoạn mã sau để thuyết phục bản thân.

Mẫu mã

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.matmulcũng cho tích điểm trên vectơ và tích ma trận trên ma trận.
Subhaneil Lahiri

2

Đây là một so sánh với np.einsumđể cho thấy cách các chỉ số được dự báo

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

Kinh nghiệm của tôi với MATMUL và DOT

Tôi liên tục nhận được "ValueError: Hình dạng của các giá trị được truyền là (200, 1), các chỉ số ngụ ý (200, 3)" khi cố gắng sử dụng MATMUL. Tôi muốn có một giải pháp nhanh chóng và nhận thấy DOT cung cấp chức năng tương tự. Tôi không gặp bất kỳ lỗi nào khi sử dụng DOT. Tôi nhận được câu trả lời chính xác

với 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)"

với DOT

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

YY.shape

>>> (200, )
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.