타리스만

[파이썬] 넘파이 Numpy 배열의 연산 : broadcast, 벡터의 내적, 행렬곱

파이썬 라이브러리 넘파이 (numpy) 학습 두번째 시간, 지난 시간에는 numpy란 무엇인지와 기본적인 어레이(array)를 생성하는 방법에 대해 알아보았다.

 

이전글 : 넘파이 어레이 (numpy array) 생성과 dtype, 차원 변경

 

[파이썬] 넘파이 numpy array : 시퀀스, 난수 생성과 dtype, 차원변경

파이썬 넘파이(numpy) 라이브러리를 사용할 때 가장 기초인 array를 생성하는 방법, 그리고 데이터 타입과 ndarray 차원 변경에 대해 공부해본다. 넘파이(numpy)는 파이썬에서 사용할 수 있게 만들어 놓

tali.tistory.com

 

오늘은 numpy array의 연산에 대해 알아본다.

 

numpy 배열의 연산은 다음과 같은 특징을 가진다.

  • 반복문을 회피하여 수식이 간편하고 속도가 빠르다
  • broadcast (element-wise) 연산방식 : 같은 위치 요소들끼리의 사칙연산 가능
  • 기본적으론 shape이 동일해야 하나, 확장 가능한 경우 shape이 달라도 연산을 지원

 

이 특징들을 한가지씩 실습을 통해 익혀본다.

 


1. numpy 배열과 list 연산 비교

 

왜 넘파이 배열로 연산하는 게 좋은지,

파이썬 리스트를 만들어서 연산하는 경우와 비교해 보자.

 

test1 = list(range(10))
test1

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]   # 결과

🔺 test1 이라는 변수에 range함수를 이용하여 0에서 9까지의 값을 갖는 리스트를 만들었다. 이것을 각각 2배씩 곱해주고 싶다고 하자.

 

test1 * 2

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]   # 결과

🔺 리스트 변수명에 다짜고짜 곱하기 2를 하면 위와 같이 리스트 두개를 이어붙인 형태가 되어버린다. 원소들의 값 자체를 두배씩으로 바꿔주고 싶다면 반복문을 쓰는 수밖에 없다.

 

result1 = []
for i in test1:
  result1.append(i*2)
result1

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]  # 결과

🔺 for 반복문을 이용해서 리스트 내의 원소들을 하나씩 순차적으로 불러오고 그것들을 2배해서 새로 생성해놓은 빈 리스트에 추가해주는 작업을 하도록 만들었다.

 

만약 원소의 개수가 10000개라면 반복문을 10000번 수행해야 한다. 얼핏 생각해도 속도가 매우 느릴 것 같다.

 

반면에 넘파이 어레이를 통해 이 연산을 하면 어떤 식이 될까.

 

import numpy as np

test2 = np.arange(10)
test2

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])   # 결과

🔺 import로 numpy를 불러온 뒤, arange 함수를 이용해 0에서 9까지의 숫자를 갖는 1차원 어레이를 생성하였다. 각각을 두배씩 해주고 싶다면,

 

test2 *2

array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])   # 결과

🔺 그냥 이렇게 곱하기 2를 해주는 것으로 원하는 결과를 얻을 수 있다. 아까 리스트에서와 달리 array 배열은 사칙연산자 기호에 의해 각 요소별 연산이 된다. 따라서 반복문을 거치지 않고 보다 빠르게 작업을 수행할 수 있다.

 

그리고 매번 반복문 코드 짜는 것보다 numpy array를 써주는 것이 코드도 훨씬 간단해진다. 넘파이 배열의 연산 방법들을 좀 더 구체적으로 살펴보자.

 


2. broadcast 연산

 

broadcast 연산이라는 것은 element-wise 연산 방식이라고도 하는데, 배열의 각 요소별로 연산과정을 수행한다는 의미이다.

 

a = np.arange(15).reshape(3,5)
b = np.arange(20,35).reshape(3,5)

a			# a array 내용출력
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
       
b			# b array 내용출력
array([[20, 21, 22, 23, 24],
       [25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34]])

🔺 위와 같이 3행 5열 shape에 다른 원소값을 갖는 a, b 두개의 배열을 생성하였다. 이 두개로 사칙연산을 시켜보자.

 

더하기

a+b

array([[20, 22, 24, 26, 28],
       [30, 32, 34, 36, 38],
       [40, 42, 44, 46, 48]])
       
       
np.add(a,b)

array([[20, 22, 24, 26, 28],
       [30, 32, 34, 36, 38],
       [40, 42, 44, 46, 48]])

🔺 연산자 기호 + 또는 연산 함수 np.add(배열1, 배열2) 형태로 각각의 합을 구한다.

 

빼기

a-b

array([[-20, -20, -20, -20, -20],
       [-20, -20, -20, -20, -20],
       [-20, -20, -20, -20, -20]])


np.subtract(a,b)

array([[-20, -20, -20, -20, -20],
       [-20, -20, -20, -20, -20],
       [-20, -20, -20, -20, -20]])

🔺 연산자 기호 - 또는 연산 함수 np.subtract(배열1, 배열2) 형태로 각각의 합을 구한다.

 

곱하기

a*b

array([[  0,  21,  44,  69,  96],
       [125, 156, 189, 224, 261],
       [300, 341, 384, 429, 476]])


np.multiply(a,b)

array([[  0,  21,  44,  69,  96],
       [125, 156, 189, 224, 261],
       [300, 341, 384, 429, 476]])

🔺 연산자 기호 * 또는 연산 함수 np.multiply(배열1, 배열2) 형태로 각각의 합을 구한다.

 

나누기

a/b

array([[0.        , 0.04761905, 0.09090909, 0.13043478, 0.16666667],
       [0.2       , 0.23076923, 0.25925926, 0.28571429, 0.31034483],
       [0.33333333, 0.35483871, 0.375     , 0.39393939, 0.41176471]])


np.divide(a,b)

array([[0.        , 0.04761905, 0.09090909, 0.13043478, 0.16666667],
       [0.2       , 0.23076923, 0.25925926, 0.28571429, 0.31034483],
       [0.33333333, 0.35483871, 0.375     , 0.39393939, 0.41176471]])

🔺 연산자 기호 / 또는 연산 함수 np.divide(배열1, 배열2) 형태로 각각의 합을 구한다.

 

이것은 연산한 결과값만 보여주는 것이고 배열 a와 b의 원래 내용을 바꾸는 것은 아니다. 연산 결과를 새로운 array로 생성하고 싶다면, 

c = np.divide(a, b) 와 같이 새로운 변수명에 받아주면 된다.

 


3. 벡터의 내적, 행렬곱

 

행렬곱으로 벡터의 내적을 구할 수 있는데, 벡터와 내적의 의미 같은 것은 수학 공부시간에 따로 다루기로 하고 여기서는 numpy array를 통해 연산하는 방법만 공부한다.

 

🔺 행렬곱은 이렇게 첫번째 행렬의 m 행과 두번째 행렬의 n 열 요소들간에 곱의 합을 구해서 결과값을 m행 n열의 원소로 넣는 것이다.

 

내적 : 곱끼리의 합 이라고만 일단 알아둔다.

 

딱 저렇게 m x n  하나의 연산만 존재하도록 1차원 배열끼리 먼저 해보자.

a = np.array([10, 20, 30])
b = np.array([5, 10, 15])

a, a.shape, b, b.shape

(array([10, 20, 30]), (3,), array([ 5, 10, 15]), (3,))

이렇게 1행3열짜리 어레이 두개를 만든 뒤 벡터의 내적을 구할 때, 방법은 4가지이다.

 

np.sum(a * b)

np.dot(a, b)

a.dot(b)

a @ b


700     # 결과

🔺 4가지 중 어떤 방법으로 해도 결과는 모두 같게 나온다.

10*5 + 20*10 + 30*15 = 50 + 200 + 450 = 700

 

이제 2차원 배열끼리 연산을 해보자.

c = np.arange(12).reshape(3,4)
d = np.arange(10,22).reshape(3,4)


c, c.shape

(array([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]]), (3, 4))


d, d.shape

(array([[10, 11, 12, 13],
        [14, 15, 16, 17],
        [18, 19, 20, 21]]), (3, 4))

🔺 이렇게 3행 4열짜리 어레이 두개를 생성하고 벡터 내적 연산을 실행하면

 

ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 3 is different from 4)

 

ValueError가 발생한다. 벡터의 내적은 곱의 합이라고 했는데, 이걸 하려면 첫번째 행렬의 열 개수 (가로 원소 수)와 두번째 행렬의 행 개수 (세로 원소 수)가 일치해야 한다. 즉 shape이 (3, 4) 하고 (4, 3) 두개를 행렬곱 시켜야 정상적인 결과가 출력된다.

 

c @ d.T

array([[ 74,  98, 122],
       [258, 346, 434],
       [442, 594, 746]])

🔺 이때는 이렇게 d.T 를 이용해서 두번째 행렬의 shape을 변환해주고 내적 연산을 하면 결과를 얻을 수 있다. 

 

np.matmul(c, d.T)

array([[ 74,  98, 122],
       [258, 346, 434],
       [442, 594, 746]])

🔺 dot 함수들은 내적을 구하는 것이고, 행렬곱은 matmul 함수를 이용하여 계산하는데 이렇게 2차원 배열까지는 결과값이 동일하다. 3차원부터는 결과가 달라진다.

 


4. 크기다른 배열간의 연산

 

numpy array 연산은 element wise 방식이라 두 배열의 shape이 같아야 한다고 했는데, 크기가 다르더라도 연산이 가능한 경우가 있다.

 

🔺 이렇게 차원이 다른 배열간에 연산을 수행하는 경우, 작은 차원의 배열을 큰 차원의 배열만큼 확장해서 계산을 수행해 준다. 빈칸에 같은 내용이 반복되어 있다고 가정하고 확장해서 연산한다는 뜻이다.

 

a = np.arange(12).reshape(3,4)
a, a.shape

(array([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]]), (3, 4))
       

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

(array([1, 2, 3, 4]), (4,))


a + b

array([[ 1,  3,  5,  7],
       [ 5,  7,  9, 11],
       [ 9, 11, 13, 15]])

🔺 shape(3, 4) 배열 a에 shape(4, )인 배열 b를 더하면 서로 형태가 다르지만 마치 b의 한 행 데이터가 세 줄 있는 것처럼 확장해서 a의 각각의 행에 더하기를 해준다.

 

이렇게 numpy array 간의 간단한 연산 방법에 대해 알아보았다. 다음은 배열 내에서 특정 데이터를 찾고 추출하는 인덱싱과 슬라이싱에 대해 알아본다.

블로그의 정보

TALI's MANDALA

금융투자의 만다라를 찾아서

활동하기