numpy array를 통해 매트릭스 및 텐서 연산을 할때, 연산의 규칙이 꽤나 복잡하다. 넘파이에서 제공하는 내적연산에는 dot과 matmul이 있다. dot은 벡터를 대상으로 하는 내적 연산이기 때문에 다차원 매트릭스에 대해 dot 연산을 수행하면, 의도했던 내적연산이 발생하지 않는다. 매트릭스와 텐서를 대상으로 하는 내적 연산은 matmul이다.
텐서에서 두 연산자의 주요한 차이를 이해하기 위해, 먼저 브로드캐스팅의 개념을 이해해야한다.
브로드캐스팅Broadcasting
numpy array에서 벡터 및 매트릭스 연산에서 제공되는 브로드캐스팅이 있다. 브로드캐스팅의 목적은 차원이 다른 두 텐서(벡터나 매트릭스도 포함)간의 연산을 지원하는 데에 있다. 구체적으론 두 스텝을 통해 브로드캐스팅이 이뤄진다.
- 차원확장Expand dimensions
- 크기정렬Align Shapes
Rule 1: 차원확장Expand dimensions
차원확장은 차원의 개수가 다를때, 1을 추가하여 개수를 맞춰주는 것이다. eg.) shape (2, 3, 4, 5, 6)과 shape (7, 8, 9)이 있을 때, 각각 차원의 개수가 5개, 3개로 다르다. 따라서 shape (7, 8, 9)를 shape (1, 1, 7, 8, 9)로 확장하여 차원수를 맞춰준다. *numpy array로 보면 다음과 같이 변하는 것이다 […] → […]
Rule 2: 크기정렬Align Shapes
크기정렬은 차원의 수가 같다고 연산이 가능해지는 것이 아니기 때문에 존재한다. 차원의 사이즈도 맞춰줘야한다. 즉, shape가 같도록 만들어줘야 한다. eg.) shape (2, 3, 4, 5, 6)과 shape (1, 1, 7, 8, 9)의 shape가 같아지려면 사이즈가 1인 차원들을 각각 2, 3으로 복제를 통해 정렬할 수 있다. 문제는 여기서 뒤 3개의 차원은 정렬이 불가능하다는 것이다. 복제를 통해 맞추는 것이 불가능하기 때문이다. (차원이 이미 여러개인데 무엇을 복제하겠는가?) 따라서 이 예시는 최종적으로 브로드캐스팅에 실패하고 shape mismatch 에러를 발생시킬 것이다.
따라서 align이 가능하려면 각 차원의 사이즈가 1 또는 서로 같아야 할것이다.
ex
A = np.random.rand(2, 3, 4, 5, 6) # shape: (2, 3, 4, 5, 6)
B = np.random.rand(3, 4, 6, 7) # shape: (3, 4, 6, 7)
# ex1
(A[:3] + B[:2]).shape # (2, 3, 4) + (3, 4) -> (2, 3, 4) + (2, 3, 4)
# 덧셈연산에서는 뒤의 두개의 차원을 포함하면 브로드캐스팅 에러가 발생한다.
>>> (2, 3, 4)
# ex2
(A @ B).shape # @은 matmul연산자
>>> (2, 3, 4, 5, 7)ex1) (2, 3, 4)와 (3, 4)에 대해 덧셈 연산이 불가능하기 때문에 먼저 브로드캐스팅을 수행하여 B: (3, 4) → (1, 3, 4) → (2, 3, 4)로 서로 동일한 차원 사이즈를 맞주고 덧셈을 수행한다.
ex2) matmul연산은 마지막 두 차원에서 매트릭스 내적을 하고 나머지 차원에서만 브로드캐스트 sum을 수행한다. 따라서 (5, 6)과 (6, 7)은 내적을 수행하여 (5, 7)의 shape가 되고 앞 차원은 ex1)과 동일하게 브로드캐스팅하여 합을 해준다. 이 부분은 후술할 matmul 연산의 메커니즘을 먼저 이해하면 된다.
dot vs matmul
numpy에서 dot 연산과 matmul 연산에는 한가지 중요한 차이가 있다. 바로 브로드캐스팅의 여부이다.
하지만 깊은 이해를 하기 위해서는 수식적 정의로 접근해야한다. 그 전에 우리가 사용하는 내적이라는 용어에 대해 수식적인 정의에서 시작하겠다.
내적
일반적으로 사용되는 정의는 벡터와 벡터를 곱하여 스칼라 결과를 얻는 것이다. 하지만 여기서 우리는 용어를 조금 확장해서 element-wise prodcut를 가리키는 용어로 사용한다.
결국 벡터든 매트릭스든 텐서든간에 차원이라는 것은 원소들로 이루어져 있다. 예컨대 1부터 100까지의 수 100개를 각각 원소로 보고 이를 A = reshape(10, 10, 10)하면, 3개의 차원으로 정렬한 셈이 되는 것이다. 그렇다면 각각의 원소는