프로그래밍/Python

텐서, 행렬의 곱셈 - numpy(*, dot, @), pytorch(mul, matmul, @) 비교

히또아빠 2023. 12. 1. 15:30

1.[Numpy]의 곱연산자

  • '*'
    • '*' 연산자는 원소별(element-wise) 곱셈
    • 스칼라 곱
    • 별연산은 같은 shape 여야만 계산
    • 다른 경우는 (1, n)꼴 일때 브로드캐스팅 적용
    • 브로드캐스팅 지원
  • 'dot'
    • 'dot'함수는 두 행렬간의 내적(dot product)
    • 앞의 행렬의 마지막 축과 뒤의 행렬의 두 번째 축간의 내적 수행
  • '@'
    • 행렬 곱셈
    • 행렬이라는 2차원 공간에서는 내적 'dot'과 같은 역할
    • 3차원부터는 텐서곱(외적)이라 다른 결과
    • 마지막 축에 대해서만 행렬 곱셈
    • pytorch의 'matmul', '@'와 동일한 행렬곱셈
    • np의 'matmul'과도 동일

2.[pytorch] 의 곱연산자

  • 'mul'
    • 원소별(element-wise) 곱셈
    • 브로드캐스팅 지원
  • 'matmul'
    • 행렬곱셈
    • '@'와 동일
    • 3차원부터는 텐서곱(외적)
  • '@'
    • 행렬곱셈
    • 'matmul'와 동일
    • 3차원부터는 텐서곱(외적)

3.numpy vs pytorch 비교

3-1.numpy - * vs pytorch - mul

동일한 연산결과

# NumPy에서의 *
A_numpy = np.array([[1, 2, 3]])
B_numpy = np.array([[2], [2]])

result_numpy = A_numpy * B_numpy

print(A_numpy.shape, B_numpy.shape)

# pytorch에서의 mul
A_torch = torch.tensor([[1, 2, 3]])
B_torch = torch.tensor([[2], [2]]) 

result_torch = torch.mul(A_torch, B_torch)


print("NumPy * result:\n", result_numpy)
print("PyTorch mul result:\n", result_torch)

(1, 3) (2, 1)

NumPy * result:
 [[2 4 6]
 [2 4 6]]

PyTorch mul result:
 tensor([[2, 4, 6],
        [2, 4, 6]])

3-2.numpy - dot, @(=matmul)

동일하지 않는 결과, 2차원 행렬곱에서는 np.dot 과 np.matmul(=@) 연산이 행렬내적으로 동일하나 3차원 이상의 텐서곱 연산에서는
np.matmul(=@) 외적으로 연산함.

# 3차원 배열 생성
A = np.random.rand(2, 3, 4)
B = np.random.rand(2, 4, 5)

# np.dot 수행
result_dot = np.dot(A, B)

# @ 연산자 수행
result_at = A @ B

print("Shape of A:", A.shape)
print("Shape of B:", B.shape)

# 앞의 행렬의 마지막 축과 뒤의 행렬의 두 번째 축간의 내적 수행
# 2차원 행렬인 경우 행렬내적으로 같은 결과가 나오겠지만
# 3차원 텐서부터 'dot' 결과값 다름
print("Shape of Result (dot):", result_dot.shape)
print("Shape of Result (@):", result_at.shape)

Shape of A: (2, 3, 4)
Shape of B: (2, 4, 5)
Shape of Result (dot): (2, 3, 2, 5)
Shape of Result (@): (2, 3, 5)

3-3.pytorch - matmul, @

동일한 결과, 2차원 torch.matmul, @, np.matmul(=@)는 2차원 행렬연산에서는 np.dot과 동일하지만 3차원 이상 텐서곱에서는 외적으로 np.dot과 다름.

# 3차원 텐서 생성
A = torch.rand(2, 3, 4)
B = torch.rand(2, 4, 5)

# torch.matmul 수행
result_matmul = torch.matmul(A, B)

# @ 연산자 수행
result_at = A @ B

print("Shape of A:", A.shape)
print("Shape of B:", B.shape)
print("Shape of Result (matmul):", result_matmul.shape)
print("Shape of Result (@):", result_at.shape)

Shape of A: torch.Size([2, 3, 4])
Shape of B: torch.Size([2, 4, 5])
Shape of Result (matmul): torch.Size([2, 3, 5])
Shape of Result (@): torch.Size([2, 3, 5])

4.결론

결론적으로, 헷갈린다. 헷갈리면 '@'로 numpy든 torch든 통일하는것도 나쁘지 않은 방법.
2차원 배열(=행렬)의 경우는 이러나 저러나 동일하다는 것을 기억하자.
그리고 스칼라 곱 연산자인 np의 '*'와 torch의 'mul'은 브로드 캐스팅이 적용되니 유의하자.

300x250
반응형