파이썬 넘파이 np.matmul vs np.dot 비교
이번 글에서는 np.dot 함수와의 차이 비교를 기준으로
np.matmul 함수의 사용 방법을 살펴보도록 하겠습니다.
참고로, 지난 번에 행렬곱 함수 중 하나인 np.dot 함수의 사용법에 대한 포스팅을
다룬 적이 있습니다. 해당 글의 링크는 아래에 첨부해두었습니다.
np.matmul 함수의 기본적인 사용법은 np.matmul(a, b) 식으로 두 개의 배열을
차례대로 input으로 넣어주거나, a @ b 처럼 @ 기호를 이용하여 간소화도 가능합니다.
np.matmul과 np.dot이 같은 결과를 나타내는 경우
np.matmul과 np.dot이 같은 결과를 나타내는 경우는 다음과 같이 요약이 가능합니다.
1. 1차원 * 1차원 (내적 연산)
2. 2차원 * 2차원 (2차원 행렬곱)
3. 2차원 * 1차원 or 1차원 * 2차원 (행렬 * 벡터곱 연산)
먼저 내적 연산의 경우부터 살펴보겠습니다.
import numpy as np
# 1차원 * 1차원
a = np.array([1, 3, 5])
b = np.array([4, 2, 0])
np.dot(a, b) # 10
a @ b # 혹은 np.matmul(a, b) # 10
dot과 matmul 모두 1 * 4 + 3 * 2 + 5 * 0 = 10 이라는 내적 연산 결과를 반환했습니다.
이번에는 2차원 * 2차원의 예시입니다.
# 2차원 * 2차원
a = np.array([[1, 3], [2, 4]])
b = np.array([[1, 0], [2, 1]])
np.dot(a, b)
'''
array([[ 7, 3],
[10, 4]])'''
a @ b # 혹은 np.matmul(a, b)
'''
array([[ 7, 3],
[10, 4]])'''
두 함수 모두 행렬곱 연산의 결과를 나타냈습니다.
2차원 * 1차원 혹은 1차원 * 2차원의 결과도 살펴보겠습니다.
# 2차원 * 1차원
a = np.array([[1, 3], [2, 4]])
b = np.array([2, 1])
np.dot(a, b) # array([5, 8])
a @ b # array([5, 8])
# 1차원 * 2차원
np.dot(b, a) # array([ 4, 10])
b @ a # array([ 4, 10])
마찬가지로 두 연산의 결과는 동일했습니다.
np.matmul과 np.dot이 다른 경우 1 : 배열과 스칼라의 곱
두 함수의 첫 번째 차이는
np.dot 함수는 np.multiply에서 제공하는 스칼라와 배열의 곱을 지원하지만,
np.matmul 함수는 스칼라와 배열의 곱에서 오류가 발생합니다.
# 배열 * 스칼라
a = np.array([1, 3, 5])
b = 2
np.dot(a, b) # array([ 2, 6, 10])
a @ b # ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)
np.dot 함수는 각 원소에 스칼라배가 취해진 값을 반환하는데 성공했지만,
np.matmul 함수는 ValueError가 발생한 것을 확인하였습니다.
np.matmul과 np.dot이 다른 경우 2 : 3차원 이상 다차원 배열곱
두 함수의 가장 큰 차이점은
3차원 이상의 다차원 배열에서 연산을 수행하는 방식이 다르다는 것입니다.
차이를 요약하면
np.dot(a, b)는 a의 마지막 axis와 b의 마지막에서 두번째 axis끼리 곱한 결과를 반영하고,
np.matmul(a, b)는 a의 마지막 두 axis 차원이 (n, k), b의 마지막 두 axis 차원이 (k, m)
일 때, 이 사이에서만 곱하여 (n, m) 차원이 되도록 반영합니다.
이해를 돕기 위하여 먼저 2차원과 3차원 배열 사이의 곱 예시를 보겠습니다.
# 2차원 * 3차원
a = np.array([[1, 3], [2, 4]])
b = np.array([[[1, 1], [0, 1]], [[5, 0], [0, 0]]])
np.dot(a, b)
'''
array([[[ 1, 4],
[ 5, 0]],
[[ 2, 6],
[10, 0]]])'''
a @ b
'''
array([[[ 1, 4],
[ 2, 6]],
[[ 5, 0],
[10, 0]]])'''
np.dot에서는 a의 마지막 axis 방향인 행 방향을 각각 가져와
b의 마지막에서 두번째 axis 방향인 열 방향을 각각 가져온 결과를 곱했습니다.
반면, np.matmul에서는 a 행렬의 마지막 axis 두개는 a 행렬 전체이기에,
a 행렬 전체를 가져와 b의 마지막 axis 두개에 해당하는 각각의 행렬 부분을 곱한 결과를
반환한 것을 알 수 있습니다.
이번에는 또 다른 예시로 다차원 배열끼리 곱의 shape을 비교해보겠습니다.
a = np.ones([5, 7, 4, 3])
b = np.ones([5, 7, 3, 6])
np.dot(a, b).shape # (5, 7, 4, 5, 7, 6)
np.matmul(a, b).shape # (5, 7, 4, 6)
np.dot에서는 a의 (5, 7, 4, 3)과 b의 (5, 7, 3, 6)에서 a의 마지막 axis(주황색 3)과
b의 마지막에서 두 번째 axis(초록색 3)끼리 곱하여
모든 값이 3인 (5, 7, 4, 5, 7, 6) 차원의 배열이 반환되었습니다.
여기서는 주황색과 초록색 부분의 숫자가 다르면 오류가 발생합니다.
np.matmul에서는 a의 (5, 7, 4, 3)과 b의 (5, 7, 3, 6) 부분에서 마지막 두 axis씩을
가져와 곱하면 (4, 3) * (3, 6) -> (4, 6)이 되어(행렬곱 : (n, k) * (k, m) -> (n, m))
최종 결과는 모든 값이 3인 (5, 7, 4, 6) 차원이 되었습니다.
여기서는 두 배열 사이의 k 부분 숫자(둘 다 3인 부분)가 다르거나,
앞의 (5, 7) 부분의 숫자가 다르면 오류가 발생합니다.
'Python > Numpy' 카테고리의 다른 글
[Numpy] 파이썬 SVD 차원 축소 예제 : np.linalg.svd() 또는 LA.svd() (0) | 2022.01.20 |
---|---|
[Numpy] np.squeeze 함수 사용법과 의미 (0) | 2022.01.14 |
[Numpy] 배열 shape 변경 : np.reshape 함수 사용법, -1 의미 (0) | 2022.01.13 |