[Numpy]행렬곱(@)과 내적(dot) 그리고 별연산(*)

반응형
    반응형

    numpy array의 곱연산에 대해서 알아보도록 하겠습니다.

    곱연산에는 총 세가지 연산이 있는데요.

    선형대수에서 배우는 행렬의 곱을 하는 행렬곱(@)과 내적, 스칼라 곱을 하는 별연산(*) 이 있습니다.


    세개 모두 다른 부분이 있습니다.

    그 부분을 잘 알고 있어야 내가 무슨 계산을 한건지 알고 혹시 나올 오류에 대처할 수 있습니다.

     

    출력되는 모양과 가능한 연산모양을 표로 나타냈습니다.

     

    Operator Shape
    별연산(*) (n,m)*(n,m) = (n,m)

    브로드캐스팅시
    (1,m)*(n,m) = (n,m)
    or (m,1)*(m,n) = (m,n)
    or (m,n)*(m,1) = (m,n)
    or (n,m)*(1,m) = (n,m)
    내적(dot) (n,m).dot((m,k)) = (n,k)
    행렬곱(@) (n,m)@(m,k) = (n,k)

    스칼라 곱인 별연산은 브로드캐스팅이 가능합니다. 분간이 굉장히 필요합니다.

     

    별연산(*)

    별연산은 스칼라 곱을 하는 겁니다. 그래서 브로드 캐스팅이 가능합니다.

    단순한 스칼라곱은 앞 포스팅인 브로드캐스팅에서 확인하시면 되고 원소별 연산을 하는 것만 보여드리겠습니다.

    별연산은 같은 shape여야만 계산이 됩니다.

    브로드캐스팅이 되는 특수한 경우인 (1,m)꼴 일때만 모양이 달라도 계산이 가능합니다.

    먼저 똑같은 모양입니다.

    A = np.arange(6).reshape([2,3])
    B = np.arange(6).reshape([2,3])-1
    print(A)
    print(B)
    print(A*B)

    원소별로 곱하기가 성립되는 걸 볼 수 있습니다.

     

    브로드캐스팅가 된 경우입니다.

    A = np.arange(3).reshape([1,3])
    B = np.arange(6).reshape([2,3])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A*B')
    print(A*B)

    A가 B의 첫번째 행인 [-1 0 1]과 계산을 한 결과가 A*B의 첫번째 행에 나타나고 

    B의 두번째 행인 [2 3 4] 와 계산을 해서 A*B의 두번째 행에 나타났습니다.

    브로드 캐스팅을 하면 각 행 또는 열에 대해서 자동으로 계산을 하게 됩니다.

     

    열 같은 경우도 마찬가지로 이루어집니다. 

    A = np.arange(3).reshape([3,1])
    B = np.arange(6).reshape([3,2])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A*B')
    print(A*B)

     

    내적

    내적은 선형대수에서 배우는 내적과 같습니다. 결과값이 스칼라가 나오는 계산입니다.

    그림으로 표현하면 다음과 같은 계산을 하게 됩니다. 

    (m,p) dot (p,k) = (m,k) 모양으로 형성되고 위 그림처럼 계산을 합니다.

    즉, $C_{ij}$ = $A_{i1}*B_{1j} + A_{i2}*B_{2j} + A_{i3}*B_{3j}$ 으로 계산이 됩니다.

    백터 내적을 하면 숫자 하나만 나오게 됩니다. 즉, 스칼라를 만드는 곱이 되게 됩니다.

    행렬은 2차원 배열로 나타나게 됩니다. 

     

    벡터 내적부터 보겠습니다.

    A = np.arange(3).reshape([1,3])
    B = np.arange(3).reshape([3,1])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A.dot(B)')
    print(A.dot(B))

    1x1 모양으로 나오게 되기 때문에 1차원 배열이 나옵니다.

     

    행렬인 경우는 다음과 같습니다.

    A = np.arange(3).reshape([1,3])
    B = np.arange(6).reshape([3,2])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A.dot(B)')
    print(A.dot(B))

     

    항상 가운데 모양이 맞춰져야만 내적 연산을 하니 주의하시기 바랍니다.

     

     

    행렬곱(@)

    행렬곱은 우리가 흔히 아는 행렬의 곱셈을 하는 경우입니다.

    사실상 행렬이라는 2차원 공간에서는 내적과 같은 역할을 하게 됩니다.

    3차원부터는 텐서 곱(외적)을 하게 되어 약간 방식이 달라집니다.

    그래서 2차원에서는 내적과 행렬곱 중에  편한 것을 써도 무방합니다.

     

    2차원부터 해보겠습니다.

    A = np.arange(3).reshape([1,3])
    B = np.arange(6).reshape([3,2])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A@B')
    print(A@B)

    위의 내적의 예와 같은 것으로 해보았습니다. 보시다시피 같은 결과값을 가져오게 됩니다.

     

    3차원부터는 값이 다릅니다.

    A = np.arange(2*3*4).reshape([2,3,4])
    B = np.arange(2*3*4).reshape([2,4,3])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A@B')
    print(A@B)

    행렬곱은 이렇게 나옵니다. 

    내적을 하면 다음과 같이 나옵니다.

    A = np.arange(2*3*4).reshape([2,3,4])
    B = np.arange(2*3*4).reshape([2,4,3])-1
    print('A')
    print(A)
    print('B')
    print(B)
    print('A.dot(B)')
    print(A.dot(B))

    이건 방식이 달라서 그런거라서 보통 외적을 많이 구하니깐 외적을 구할 때는 @을 쓴다는 걸로 알아두시면 될 것 같습니다. 외적은 코딩쪽보다는 수학이론이기도 하고 간단하게 설명이 불가능해서 추후에 기회가 되면 포스팅을 하도록 하겠습니다.

     

    관련 포스팅

    [Python/Numpy] - [Numpy] Broadcasting(브로드캐스팅)

    [Python/Numpy] - 연산,통계,집계함수

     

    'Python > Numpy' 카테고리의 다른 글

    [Numpy] 구조체(structured array)  (0) 2021.05.01
    [Numpy] np.where()  (0) 2021.04.30
    [Numpy] ndarray에 값 추가하기  (2) 2021.03.21
    ndarray 데이터로 그래프 그리기(matplotlib)  (2) 2020.06.05
    [Numpy] Bool 이용하기  (4) 2020.06.02

    댓글

    Designed by JB FACTORY

    ....