[Numpy] 딥러닝을 위한 Numpy2 - 행렬의 연산
- -
앞선 포스팅에서
2020/03/05 - [SW개발/Framework Library] - [파이썬패키지] 딥러닝을 위한 Numpy 공부1 - Numpy기초
Numpy의 큰 장점은 벡터 연산이 가능하다는 것이라고 했는데요.
행렬의 연산이 수학에서 말하는 벡터의 내적이 아닌, 같은 위치의 요소들끼리 합, 곱 등의 연산이 가능하다는 것이었습니다. 이것을 'Element-wise'방식이라고 하는데요. 그러면 행렬에서 dot()연산을 하고 싶거나 행렬의 크기가 다른 행렬끼리의 연산은 어떻게 하는지, 기존 행렬의 연산은 어떤 것이 있고 이를 Numpy로 어떻게 구현하는지 살펴보도록 하겠습니다.
● Element-wise : 요소별 연산
차원(축)을 기준으로 행렬 내에서 같은 위치에 있는 원소끼리 연산을 하는 방식입니다. 신경망에서도 자주 다루어지는 방식으로 Numpy는 이 Element-wise 연산을 쉽고 빠르게 할 수 있도록 해줍니다.
이렇게 Numpy는 Element-wise 방식을 지원하며 이를 '벡터화 연산(vectorized operation)'이라고 합니다.
벡터화 연산을 사용하면 명시적으로 반복문을 사용하지 않고도 배열의 모든 원소에 대해 반복연산을 할 수 있습니다.
만약 Numpy가 벡터화 연산을 지원하지 않는다면 우리는 for i in x: for j in y: i+j 와 같은 반복문을 이용해서 행렬의 연산을 수행해야 했을 것입니다. 연산 속도도 벡터화 연산이 훨씬 빠릅니다.
논리 연산도 벡터화 연산을 지원합니다.
Numpy array의 장점 중 하나인 모든 요소에 대해 한번에 comparison 연산이 가능합니다. (파이썬 map()과 비슷)
a = np.array([1,2,3,4])가 있을 때 sum(a>10)이면 0 False(0)이 4개가 반환됩니다.
논리 연산으로는
- np.any, np.all ,np.logical_and, np.logical_or, np.where.
위 함수들을 많이 사용하게 됩니다. 조건문을 파라미터로 넣을 수 있는데 해당 조건을 만족하는 np.array의 인덱스 값을 반환합니다. 또는 if( condition, a, b)와 동일하게 where( condition, a, b)라고 한다면 condition을 만족하면 a로 만족하지 않으면 b로 값을 변환합니다.
데이터 전처리에서 많이 사용됩니다.
- np.isnan 도 많이 사용합니다. NaN 값이냐 아니냐 논리 연산을 하는 것입니다. 데이터 탐색할 때 NULL 값이 있는지 있다면 얼마나 있는지 확인할 때 사용합니다.
array[ condition ] 형태를 사용하면 condition을 만족하는 인덱스의 값들만을 추출할 수 있습니다. 이는 R의 데이터프레임에 익숙하신 분들에게는 익숙한 방법입니다. condition 자체가 불리언 인덱스로 나타나기 때문에 ( np.array > 0 을 할 때 True 또는 False로 구성된 동일한 차원을 가진 np.array가 반환) 해당 불리언 인덱스만 만족하는 array를 반환합니다.
array[ index array ] 또는 array.take( index array ) 를 할 때는 array의 인덱스 값을 가지고 있는 array를 입력해서 해당 인덱스의 값으로 구성된 값을 반환해주는데 일반적으로 y의 값을 추출할 때 주로 사용하는 방법입니다.
지수 함수, 로그 함수 등의 수학 함수도 벡터화 연산을 지원하고,
스칼라*벡터, 스칼라*행렬 연산도 지원합니다. 이는 Numpy의 브로드캐스팅(broadcasting)이라는 개념 때문에 가능한 것입니다.
● 브로드캐스팅(Broadcasting)
원래 덧셈과 뺄셈은 크기(차원)가 같은 두 벡터나 행렬에 대해서만 할 수 있습니다. 하지만 벡터와 스칼라의 경우는 관례적으로 1-벡터를 사용하여 스칼라를 벡터로 변환한 연산을 허용합니다. 이를 브로드캐스팅(broadcasting)이라고 합니다.
x벡터 [10, 11, 12]라고 하고 여기에 10을 빼는 연산을 해본다고 하면,
내부에서 [10, 10, 10] 벡터를 만들어 x벡터와 뺄셈을 수행하는 것입니다.
데이터 분석에서는 어떤 벡터x가 있을 때, 그 벡터의 각 원소의 평균값을 뺀 '평균제거(mean removed)벡터' 혹은 '0-평균(zero-mean) 벡터'를 사용하는 경우가 많습니다.
브로드캐스팅이 일어날 수 있는 조건
- 차원의 크기가 1일때 (벡터일때)
두 배열간 연산에서 최소한 하나의 배열의 차원이 1이면 가능하다.
- 차원의 짝이 맞을 때 가능하다
차원에 대해 축의 길이가 동일하면 브로드캐스팅이 가능하다.
차원에 대해 축의 길이가 동일하다는 것에 대해 예를 들면,
(3,4,2)의 크기인 3차원의 배열x와 (4,2)의 크기인 2차원의 배열y가 있을 때 두 배열을 연산하게 되면 y는 (3,4,2)행렬로 브로드캐스팅 되어 연산이 됩니다. 브로드캐스팅은 저차원의 배열을 연산을 위해 고차원의 배열로 확장 시키는 것입니다.
- 선형조합(Linear combination)
벡터/행렬에 스칼라 값을 곱한 후 더하거나 뺀 것을 벡터/행렬의 선형조합(Linear combination)이라고 합니다.
벡터나 행렬을 선형조합해도 크기는 변하지 않습니다.
● 차원 축소 연산
행렬의 하나의 행에 있는 원소들을 하나의 데이터 집합으로 보고 그 집합의 평균을 구하면 각 행에 대해 하나의 숫자가 나오게 된다. 예를 들어 10x5 크기의 2차원 배열에 대해 행-평균을 구하면 10개의 숫자를 가진 1차원 벡터가 나오게 됩니다. 이러한 연산을 차원 축소(dimension reduction) 연산이라고 합니다.
※참고
np.argmax 라는 함수도 데이터 전후처리 부분에서 많이 사용되는데 자주 들어본 image classification의 결정함수로 사용되는 softmax에서 argmax를 찾아볼 수 있습니다.
간단하게 리스트 값에서 가장 큰 값이 있는 인덱스(index)를 반환하는 것입니다. MNIST에서는 argmax로 반환되는 index 값 자체가 target의 값과 동일하기 때문에 argmax 값을 그대로 사용합니다.
연산의 대상이 2차원 이상인 경우에는 어느 차원으로 계산을 할 지를 axis 인수를 사용하여 지시합니다.
axis=0인 경우는 열 연산, axis=1인 경우는 행 연산, 디폴트 값은 axis=0
axis 인수는 대부분의 차원 축소 명령에 적용할 수 있습니다.
※ 참고 : 차원의 축(axis)
예를 들어서 (a, b, c)라는 차원을 가진 텐서의 axis는 a는 axis가 0, b는 axis가 1, c는 axis가 2 입니다.
● 벡터의 곱
행렬의 곱셈을 정의하기 전에 두 벡터의 곱셈에 대해 알아봅니다.
벡터를 곱셈하는 방법은 여러가지가 있지만 여기서는 '내적(inner product)'에 대해서만 다룹니다.
벡터x와 벡터y의 내적은 Numpy에서는 닷 프로덕트(dot product)로 표기합니다.
두 벡터를 내적하려면 두 벡터의 차원(길이)가 같아야 하고, 앞의 벡터가 행 벡터이고 뒤의 벡터가 열 벡터여야 합니다.
numpy.matmul()함수는 numpy.dot()함수와 비슷해 보이지만, 3차원 이상의 행렬곱을 계산하는 방식이 서로 다릅니다. numpy.matmul()은 애터리스크처럼 골뱅이(@) 연산자로 사용할 수 있습니다.
matmul과 dot의 큰 차이점은
- 첫번째는 차이로, dot는 행렬과 상수(constant)의 곱셈을 허용하지만, matmul은 Error를 일으킵니다.
- 두번째 차이는, 3차원 이상의 행렬곱(Tensor multiplication)을 수행할 경우, dot와 matmul은 전혀 다른 결과를 냅니다.
- np.dot VS np.matmul
Function | Condition (3-D) | Formula (3-D) |
np.dot | A.shape # (a1, a2, a3) B.shape # (b1, b2, b3) --> a3==b2 >>> C = np.dot(A,B) >>> C.shape (a1, a2, b1, b3) |
C[i,j,k,m] = np.sum(A[i,j,:] * B[k,:,m]) |
np.matmul | A.shape # (a1, a2, a3) B.shape # (b1, b2, b3) --> (a1==b1) and (a3==b2) >>> C = np.matmul(A,B) >>> C.shape (a1, a2, b3) |
C[i,j,k] = np.sum(A[i,j,:] * B[i,:,k]) |
np.dot은 두 배열의 내적곱(dot product)이며 np.natmul은 두 배열의 행렬곱(matrix product)라고 합니다.
np.dot() : 만약 a가 N차원 배열이고 b가 2이상의 M차원 배열이라면, dot(a,b)는 a의 마지막 축과 b의 뒤에서 두번째 축과의 내적으로 계산된다. |
||
np.matmul() : 만약 배열이 2차원보다 클 경우, 마지막 2개의 축으로 이루어진 행렬을 나머지 축에 따라 쌓아놓은 것이라고 생각한다. |
Numpy 시리즈
Numpy 기초
2020/03/05 - [SW개발/Framework Library] - [파이썬패키지] 딥러닝을 위한 Numpy1 - Numpy기초
Numpy 행렬 연산
2020/03/10 - [SW개발/Framework Library] - [파이썬패키지] 딥러닝을 위한 Numpy2 - 행렬의 연산
Numpy 함수 총 정리
2020/03/16 - [SW개발/Framework Library] - [파이썬 패키지] 딥러닝을 위한 Numpy3 - Numpy함수 총 정리
'AI 개발' 카테고리의 다른 글
[Flask] Heroku에 Flask 배포 (0) | 2020.08.11 |
---|---|
[Numpy] 딥러닝을 위한 Numpy3 - 함수 정리 (2) | 2020.03.16 |
[Numpy] 딥러닝을 위한 Numpy1 - Numpy기초 (0) | 2020.03.05 |
[Keras] Embedding Layer에 word2vec 주입하기 (4) | 2020.02.21 |
[Gensim] 자연어 처리3 - Gensim의 Word2Vec으로 토픽모델링 (0) | 2020.02.17 |
소중한 공감 감사합니다