작성자: admin 작성일시: 2016-05-15 22:54:16 조회수: 3678 다운로드: 167
카테고리: Python 태그목록:

NumPy 배열의 연산

벡터화 연산

NumPy는 코드를 간단하게 만들고 계산 속도를 빠르게 하기 위한 벡터화 연산(vectorized operation)을 지원한다. 벡터화 연산이란 반복문(loop)을 사용하지 않고 선형 대수의 벡터 혹은 행렬 연산과 유사한 코드를 사용하는 것을 말한다.

예를 들어 다음과 같은 연산을 해야 한다고 하자.

$$ x = \begin{bmatrix}1 \\ 2 \\ 3 \\ \vdots \\ 1000 \end{bmatrix}, \;\;\;\; y = \begin{bmatrix}1001 \\ 1002 \\ 1003 \\ \vdots \\ 2000 \end{bmatrix}, $$$$z = x + y = \begin{bmatrix}1+101 \\ 2+1002 \\ 3+1003 \\ \vdots \\ 1000+2000 \end{bmatrix}= \begin{bmatrix}1002 \\ 1004 \\ 1006 \\ \vdots \\ 3000 \end{bmatrix} $$

만약 벡터화 연산을 사용하지 않는다면 이 연산은 반복문을 사용하여 다음과 같이 만들어야 한다.

In:
x = np.arange(1, 1001)
y = np.arange(1001, 2001)
In:
%%time
z = np.zeros_like(x)
for i in range(1000):
    z[i] = x[i] + y[i]
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 339 µs
In:
z[:10]
Out:
array([1002, 1004, 1006, 1008, 1010, 1012, 1014, 1016, 1018, 1020])

이 코드에서 %%time은 셀 코드의 실행시간을 측정하는 IPython 매직 명령이다. 매직 명령에 대한 자세한 내용은 다음 웹사이트를 참조한다.

그러나 벡터화 연산을 사용하면 덧셈 연산 하나로 끝난다. 위에서 보인 선형 대수의 벡터 기호를 사용한 연산과 코드가 완전히 동일하다.

In:
%%time
z = x + y
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 27.2 µs
In:
z[:10]
Out:
array([1002, 1004, 1006, 1008, 1010, 1012, 1014, 1016, 1018, 1020])

연산 속도도 벡터화 연산이 훨씬 빠른 것을 볼 수 있다.

비교 연산도 벡터화 연산이 가능하다.

In:
a = np.array([1, 2, 3, 4])
b = np.array([4, 2, 2, 4])
In:
a == b
Out:
array([False,  True, False,  True], dtype=bool)
In:
a >= b
Out:
array([False,  True,  True,  True], dtype=bool)

만약 배열 전체를 비교하고 싶다면 all 명령을 사용한다.

In:
a = np.array([1, 2, 3, 4])
b = np.array([4, 2, 2, 4])
c = np.array([1, 2, 3, 4])
In:
np.all(a == b)
Out:
False
In:
np.all(a == c)
Out:
True

지수 함수, 로그 함수 등의 수학 함수도 벡터화 연산을 지원한다.

In:
a = np.arange(5)
a
Out:
array([0, 1, 2, 3, 4])
In:
np.exp(a)
Out:
array([  1.        ,   2.71828183,   7.3890561 ,  20.08553692,  54.59815003])
In:
10 ** a
Out:
array([    1,    10,   100,  1000, 10000])
In:
np.log(a)
Out:
array([       -inf,  0.        ,  0.69314718,  1.09861229,  1.38629436])
In:
np.log10(a)
Out:
array([       -inf,  0.        ,  0.30103   ,  0.47712125,  0.60205999])

연습 문제 1

벡터 x가 다음과 같을 때, 여러가지 수식을 사용하여 같은 크기의 벡터 y를 만든다.

x = np.arange(10)

스칼라와 벡터/행렬의 곱셈

스칼라와 벡터/행렬의 곱도 선형 대수에서 사용하는 식과 NumPy 코드가 일치한다.

In:
x = np.arange(10)
x
Out:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In:
100 * x
Out:
array([  0, 100, 200, 300, 400, 500, 600, 700, 800, 900])
In:
x = np.arange(12).reshape(3, 4)
x
Out:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
In:
100 * x
Out:
array([[   0,  100,  200,  300],
       [ 400,  500,  600,  700],
       [ 800,  900, 1000, 1100]])

브로드캐스팅

선형 대수에서는 벡터(또는 행렬)끼리 덧셈 혹은 뺄셈을 하려면 두 벡터(또는 행렬)의 크기가 같아야 한다. 그러나 NumPy에서는 서로 다른 크기를 가진 두 배열의 사칙 연산도 지원한다. 이 기능을 브로드캐스팅(broadcasting)이라고 하는데 크기가 작은 배열을 자동으로 반복 확장하여 크기가 큰 배열에 맞추는 방벙이다.

예를 들어 다음과 같이 벡터와 스칼라를 더하는 경우를 생각하자. 선형 대수에서는 이러한 연산이 불가능하다.

$$ x = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix}, \;\;\;\; x + 1 = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} + 1 = ? $$

그러나 NumPy는 브로드캐스팅 기능을 사용하여 스칼라를 벡터와 같은 크기로 확장시켜서 덧셈 계산을 한다.

$$ \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} \overset{\text{numpy}}+ 1 = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} + \begin{bmatrix}1 \\ 1 \\ 1 \\ 1 \\ 1 \end{bmatrix} = \begin{bmatrix}1 \\ 2 \\ 3 \\ 4 \\ 5 \end{bmatrix} $$
In:
x = np.arange(5)
x
Out:
array([0, 1, 2, 3, 4])
In:
y = np.ones_like(x)
y
Out:
array([1, 1, 1, 1, 1])
In:
x + y
Out:
array([1, 2, 3, 4, 5])
In:
x + 1
Out:
array([1, 2, 3, 4, 5])

브로드캐스팅은 더 차원이 높은 경우에도 적용된다. 다음 그림을 참조하라.

이 그림을 코드로 구현하면 아래와 같다. 코드에서 .T 는 2차원 배열의 행과 열을 바꾸는 전치(transpose) 명령이다. 이 명령은 메서드가 아닌 속성이므로 ()를 붙이지 않아도 된다.

In:
a = np.tile(np.arange(0, 40, 10), (3, 1)).T
a
Out:
array([[ 0,  0,  0],
       [10, 10, 10],
       [20, 20, 20],
       [30, 30, 30]])
In:
b = np.array([0, 1, 2])
b
Out:
array([0, 1, 2])
In:
a + b
Out:
array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22],
       [30, 31, 32]])
In:
a = np.arange(0, 40, 10)[:, np.newaxis]
a
Out:
array([[ 0],
       [10],
       [20],
       [30]])
In:
a + b
Out:
array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22],
       [30, 31, 32]])

차원 축소 연산

행렬의 하나의 행에 있는 원소들을 하나의 데이터 집합으로 보고 그 집합의 평균을 구하면 각 행에 대해 하나의 숫자가 나오게 된다. 예를 들어 10x5 크기의 2차원 배열에 대해 행-평균을 구하면 10개의 숫자를 가진 1차원 벡터가 나오게 된다. 이러한 연산을 차원 축소(dimension reduction) 연산이라고 한다.

NumPy는 다음과 같은 차원 축소 연산 명령 혹은 메서드를 지원한다.

  • 최대/최소: min, max, argmin, argmax
  • 통계: sum, mean, median, std, var
  • 불리언: all, any
In:
x = np.array([1, 2, 3, 4])
x
Out:
array([1, 2, 3, 4])
In:
np.sum(x)
Out:
10
In:
x.sum()
Out:
10
In:
x = np.array([1, 3, 2])
In:
x.min()
Out:
1
In:
x.max()
Out:
3
In:
x.argmin()  # 최솟값의 위치
Out:
0
In:
x.argmax()  # 최댓값의 위치
Out:
1
In:
x = np.array([1, 2, 3, 1])
In:
x.mean()
Out:
1.75
In:
np.median(x)
Out:
1.5
In:
np.all([True, True, False])
Out:
False
In:
np.any([True, True, False])
Out:
True
In:
a = np.zeros((100, 100), dtype=np.int)
a
Out:
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ..., 
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]])
In:
np.any(a != 0)
Out:
False
In:
np.all(a == a)
Out:
True
In:
a = np.array([1, 2, 3, 2])
b = np.array([2, 2, 3, 2])
c = np.array([6, 4, 4, 5])
In:
((a <= b) & (b <= c)).all()
Out:
True

연산의 대상이 2차원 이상인 경우에는 어느 차원으로 계산을 할 지를 axis 인수를 사용하여 지시한다. axis=0인 경우는 열 연산, axis=1인 경우는 행 연산이다. 디폴트 값은 axis=0이다.

In:
x = np.array([[1, 1], [2, 2]])
x
Out:
array([[1, 1],
       [2, 2]])
In:
x.sum()
Out:
6
In:
x.sum(axis=0)   # 열 합계
Out:
array([3, 3])
In:
x.sum(axis=1)   # 행 합계
Out:
array([2, 4])

연습 문제 2

5 x 6 형태의 데이터 행렬을 만들고 이 데이터에 대해 다음과 같은 값을 구한다.

  1. 전체의 최댓값
  2. 각 행의 합
  3. 각 열의 평균

정렬

sort 명령이나 메서드를 사용하여 배열 안의 원소를 크기에 따라 정렬하여 새로운 배열을 만들 수도 있다. 2차원 이상인 경우에는 마찬가지로 axis 인수를 사용하여 방향을 결정한다. 즉 axis로 지정한 축 방향을 무시하여 정렬하라는 뜻이다. 디폴트 값은 -1 즉 가장 안쪽의 차원이다.

In:
a = np.array([[4, 3, 5], [1, 2, 1]])
a
Out:
array([[4, 3, 5],
       [1, 2, 1]])
In:
np.sort(a)
Out:
array([[3, 4, 5],
       [1, 1, 2]])
In:
np.sort(a, axis=0)
Out:
array([[1, 2, 1],
       [4, 3, 5]])

sort 메서드는 해당 객체의 자료 자체가 변화하는 in-place 메서드이므로 사용할 때 주의를 기울여야 한다.

In:
a.sort(axis=1)
a
Out:
array([[3, 4, 5],
       [1, 1, 2]])

만약 자료를 정렬하는 것이 아니라 순서만 알고 싶다면 argsort 명령을 사용한다.

In:
a = np.array([4, 3, 1, 2])
j = np.argsort(a)
j
Out:
array([2, 3, 1, 0])
In:
a[j]
Out:
array([1, 2, 3, 4])

연습 문제 3

다음 배열은 첫번째 행(row)에 학번, 두번째 행에 영어 성적, 세번째 행에 수학 성적을 적은 배열이다. 영어 성적을 기준으로 각 열(column)을 재정렬하라.

array([[  1,    2,    3,    4],
       [ 46,   99,  100,   71],
       [ 81,   59,   90,  100]])

질문/덧글

좋은 정보 감사드려요. 2bko*** 2016년 10월 25일 2:31 오후

내용 중에 img src 속성이 잘 못됬어요.
<img src="https://datascienceschool.net/upfiles/edfaf93a7f124f359343d1dcfe7f29fc.png", style="margin: 0 auto 0 auto;">

답변: 좋은 정보 감사드려요. 관리자 2016년 10월 25일 3:10 오후

지적 감사드립니다. 수정하였습니다.

오타수정 supe*** 2016년 11월 17일 11:28 오전

"연산의 대상이 2차원 이상인 경우에는 어느 차원으로 계산을 할 지를 axis 인수를 사용하여 지시한다. axis=0인 경우는 행 연산, axis=1인 경우는 열 연산 등으로 사용한다. 디폴트 값은 0이다."
=>
"axis=1인 경우는 행 연산, axis=0인 경우는 열 연산 등으로 사용한다. 디폴트 값은 1이다. "
로 수정해야 할 것 같습니다.
좋은 자료 감사드리며 확인 부탁드립니다.
수고하세요.

답변: 오타수정 관리자 2016년 11월 18일 2:03 오후

지적 감사합니다.

사용자에 의해 삭제되었습니다. engk*** 2017년 3월 31일 6:35 오후

사용자에 의해 삭제되었습니다.