Tips

[Tips] Numpy Array 곱연산

Jae. 2024. 2. 2. 01:37
728x90

 

 


1. Numpy Array 곱연산

 

 

 

스칼라곱(*)

 

 

두 Matrix (Array)가 Shape이 같을 때 Cross-Correlation 연산을 사용 (각 원소끼리의 곱의 합을 성분으로 갖는다)

 

 

  • [n,m] * [n,m] = [n,m]
  • Array간 shape이 같을 때 or Broadcasting 가능
  • 각 행렬의 원소끼리의 곱을 성분으로 갖는 행렬을 반환
  • Broadcasting 가능: Matrix * (column, row) Vector ONLY
    • [1,m] * [n,m] = [n,m] 
    • [m,1] * [m,n] = [m,n]
    • [m,n] * [m,1] = [m,n]
    • [n,m] * [1,m] = [n,m]

 

 

내적(np.dot)

 

 

 

둘 다 1D Vector일 때 np.dot( )으로 내적처리

 

 

  • Inner Product: $u \cdot v = u^{T}v$
  • [n,m].dot([m,k]) = [m,k]
  • np.dot([n,m], [m,k]) = [m,k]

 

 

 

행렬곱 (@)

 

 

2D Matrix가 하나라도 존재할 때 @ 사용

 

  • i번째 행벡터와 j번째 열벡터의 내적을 성분으로 갖는 행렬을 반환
  • 2차원에서는 .dot() & @는 동일한 기능
  • 3차원에서는 텐서곱(외적)을 하게 되어 내적과 달라짐
  • [n,m] @ [m,k] = [m,k]

 

 

 

np.dot(A, B)

 

[참고자료] https://jimmy-ai.tistory.com/75

 

[Numpy] 파이썬 내적, 행렬곱 함수 np.dot() 사용법 총정리

파이썬 넘파이 내적 함수 : np.dot() 안녕하세요. 이번 시간에는 파이썬 넘파이 라이브러리에서 제공하는 벡터 내적, 행렬곱 함수인 np.dot 함수의 사용법을 array의 차원에 따라서 총정리해보는 시간

jimmy-ai.tistory.com

 

 

  • 1차원 X 1차원: 벡터 내적 (element-wise 방식으로 각 원소를 곱한 값들을 더한 내적 연산)
    • np.dot( ) 사용을 권장
    • 수식 : 보통 열벡터로 처리 (column vector) 
    • 코드 : 보통 행벡터로 처리 (row vector)
    • Column vector로 들어오면 Transpose해서 Row vector로 처리

 

  • 2차원 X 2차원: 행렬 곱
    • @ 사용을 권장

 

  • N차원 X 스칼라: 단순 스칼라배 연산 (* 사용을 권장, np.dot 사용하지 말 것)

 

  • N차원 X 1차원
    • 왼쪽이 N차원인 경우 각 행마다 오른쪽의 벡터와 내적을 수행
    • 오른쪽이 N차원이면 각 열마다 왼쪽의 벡터와 내적을 수행

 

  • N차원 X M차원
    • 왼쪽 array의 각 행, 오른쪽 array의 각 열끼리 순서대로 내적을 수행한 결과를 반환

 

 

 

Caution!!

  • 행렬연산을 하기 위해서는 2차원 리스트가 필요함 : [ [ ] ]으로 []를 2번 넣어준다

 

a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[7, 8], [9, 10], [11, 12]])

dot_product = a.dot(b)
dot_product2 = np.dot(a, b)
print("행렬 내적 결과: ", dot_product, dot_product2, sep="\n")

dot_product3 = a @ b
print("행렬 곱 결과: ", dot_product3, sep="\n")

inner_product = np.inner(a, b.T)
print("np.inner 결과: ", inner_product, sep="\n")

# 행렬 내적 결과: 
# [[ 58  64]
#  [139 154]]
# [[ 58  64]
#  [139 154]]
# 행렬 곱 결과: 
# [[ 58  64]
#  [139 154]]
# np.inner 결과: 
# [[ 58  64]
#  [139 154]]

 

np.inner

 

  • i번째 행벡터와 j번째 행벡터의 내적을 성분으로 갖는 행렬을 반환
  • 수학에서는 말하는 내적과는 다르다!
  • $ np.inner(u,v) = uv^{T} $이지만 수학에서는 $u \cdot v = u^{T}v$이다!
  • np.inner([n,m], [k,m])

 

 


2. Caution: np.dot(A, B) vs np.dot(B, A) vs (A @ B) vs (B @ A)

 

 

 

np.dot(A, B) vs np.dot(B, A) vs (A @ B) vs (B @ A)

 

 

 

A, B의 dimension(차원)에 따라 결과가 같을수도, 다를 수도 있다

 

 

그렇기 때문에 np.dot( ) 연산이나 @ 연산시 argument의 순서를 반드시 주의할 것!

 

 

  • A, B 둘다 1차원인 경우 np.dot( )을 순서에 상관없이 사용하고
  • A, B 중에 2차원이 있는 경우 @ 연산을 순서를 고려하여 (행렬곱을 고려하여) 사용

 

 

 

 

A, B가 모두 1차원 (1 - dimension)

  • np.dot(A, B) & np.dot(B, A) & A @ B & B @ A: 4가지 경우 모두 동일
  • 되도록 np.dot( )을 사용할 것

 

a = np.array([1, 2, 3])
b = np.array([1, 2, 3])

print(np.dot(a, b))
print(np.dot(b, a))
print(a @ b)
print(b @ a)
14
14
14
14

 

 

A, B중 2차원 존재 (2 - dimension exist)

  • np.dot(A, B) & np.dot(B, A) & A @ B & B @ A : Matrix Multiplication의 shape이 맞아야 함
  • A, B가 (1차원, 2차원), (2차원, 1차원), (2차원, 2차원)인 어떤 경우라도 행렬곱의 shape이 맞아야 함
  • 되도록 @ 연산을 사용할 것

 

  • 2차원, 1차원인 경우
a = np.array([[1, 2, 3]])
b = np.array([1, 2, 3])

print(np.dot(a, b))
# print(np.dot(b, a))
print(a @ b)
# print(b @ a)
[14]
[14]

 

  • 모두 2차원인 경우
a = np.array([[1, 2, 3]])
b = np.array([[1, 2, 3]]).reshape(-1, 1)

print(np.dot(a, b))
print(np.dot(b, a))
print(a @ b)
print(b @ a)
[[14]]
[[1 2 3]
 [2 4 6]
 [3 6 9]]
[[14]]
[[1 2 3]
 [2 4 6]
 [3 6 9]]

 


3. Cross-Correlation Operation (*)

 

 

 

두 Matrix(Vector)가 동일한 Shape을 가지고 있을 때 각 원소끼리의 곱의 합을 원소로 가지는 행렬을 반환한다.

 

 

Convolution Operation (CNN)이 이러한 예시 중 하나이다.

 

 

CNN의 Input에 Filter(Kernel)를 Convolution하려면 Filter를 Reverse & Shift하여 적용해야 한다.


그런데 어차피 Filter(Kernel)의 weight을 학습하려는 것이 목적이기 때문에 Reverse & Shift를 하여 convolution을 하나 그냥 하나 동일하다.즉 학습과 추론 inference시에 필터만 일정하면 된다.


그래서 딥러닝 프레임워크들은 Convolution이 아니고 그냥 Cross-Correlation으로 구현되어 있다.


하지만 관습상 Convolution이라 부른다.

 

 

 

다음은 CNN의 Convolution operation을 Cross-Correlation으로 구현하는 과정이다.

 

import numpy as np

def convolution(input_matrix, filter_matrix):
    # 입력 행렬과 필터 행렬의 크기를 확인
    input_rows, input_cols = input_matrix.shape
    filter_rows, filter_cols = filter_matrix.shape

    # 필터가 입력 행렬보다 작아야 연산 가능
    if filter_rows > input_rows or filter_cols > input_cols:
        raise ValueError()

    # convolution 결과를 저장할 빈 행렬 생성 stride와 padding은 없다고 가정
    result_matrix = np.zeros((input_rows - filter_rows + 1, input_cols - filter_cols + 1))

    # convolution 연산 수행
    for i in range(result_matrix.shape[0]):
        for j in range(result_matrix.shape[1]):
            result_matrix[i, j] = np.sum(input_matrix[i:i+filter_rows, j:j+filter_cols] * filter_matrix)

    return result_matrix

# 예시를 위해 간단한 행렬 생성
input_matrix = np.array([[2, 4, -6, 6, 7],
                         [-5, 3, -2, 3, 4],
                         [2, -1, 4, 7, -6],
                         [4, 10, 1, -2, 2],
                         [-9, 1, -3, 6, -4]]) # 5X5 행렬로 원하는 값을 넣어주세요

filter_matrix = np.array([[2, 4, 4],
                          [6, 5, 5],
                          [5, 2, 1]]) # 3X3 행렬로 원하는 값을 넣어주세요

# convolution 연산 수행
result = convolution(input_matrix, filter_matrix)

print("입력 행렬:")
print(input_matrix)

print("\n필터 행렬:")
print(filter_matrix)

print("\nConvolution 결과:")
print(result)

 

 

 

 

 

 

728x90