프로필사진

IT Anthology/encyclopedia

[밑러닝] 1.5 넘파이(numpy as np)

다각 2020. 2. 28. 13:42

* 이 글은 <밑바닥부터 시작하는 딥러닝2 (저자: 사이토 고키)> 책을 읽으며 정리한 글입니다.
* 나중에라도 제가 참고하기 위해 정리해 두었으며, 모든 내용을 적은 것이 아닌,
필요하다고 생각되는 부분만 추려서 정리한 것임을 미리 밝힙니다.


목차

  1. 넘파이(numpy)의 정의
  2. 넘파이(numpy)의 연산
  3. 넘파이(numpy)의 N차원 배열
  4. 브로드캐스트(broadcast)
  5. 원소 접근

1. 넘파이(numpy)의 정의

넘파이(numpy)는 벡터나 행렬 연산을 빠르게 하는 도구다. 다르게 말하자면, 배열을 다루는 도구이다.
넘파이는 한 개 이상의 원소를 가진 배열 형태라면 모두 "넘파이"라는 그릇에 담아버린다.
앞으로 과학적인 분석이나 딥러닝을 할 때 심심찮게 볼 수 있을 것이다. numpy는 다들 풀네임 쓰기 귀찮아해서 관용적으로 np라고 쓴다.
외부 라이브러리이기 때문에 다음 코드를 이용해서 불러온다.

import numpy as np

배열의 형태를 가진 모든 것들은 넘파이(numpy)가 될 수 있다.

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

b = (1, 2, 3)
bb = np.array(b)

aa == bb

리스트 형태이든, 튜플 형태이든 상관 없이 '1'과 '2'와 '3'의 원소를 지닌 배열을 "넘파이(numpy)"라는 그릇 안에 담기 때문에 마지막 줄의 aa==bb 검사를 실행했을 때에 array([True, True, True])와 같은 결과가 나온다.

 

2. 넘파이(numpy)의 연산

많은 이들이 넘파이(numpy)를 사용하는 이유는 넘파이(numpy)의 강력한 연산 기능 때문이다.
[1, 2, 3]과 같은 리스트가 있을 때, 이 각각의 원소를 모두 -1 하는 코드를 생각해보자. 다음과 같이 작성할 수 있을 것이다.

a = [1, 2, 3]
result = []
for i in a:
    result.append(i-1)

혹은 리스트 컴프리핸션을 이용해 다음과 같이 작성할 수도 있을 것이다.

a = [1, 2, 3]
[ i-1 for i in a ]

하지만 넘파이(numpy)를 이용하면 간단해진다.

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

a라는 넘파이(numpy) 개체 선언 이후에 -1만 해주면 일일이 a안의 원소를 다 찾아가며 -1을 해줄 필요 없이 모든 원소에 적용되어 한번에 연산이 된다.
이러한 간편함은 넘파이(numpy) 개체끼리의 연산에서도 돋보인다. 길이가 같은 두 넘파이(numpy)배열 xy를 생각해보자.

x = np.array([1, 2, 3])
y = np.array([2, 4, 6])
x + y

위와 같은 코드가 실행되었을 때, 넘파이는 똑똑하게도 array([3, 6, 9])라는 결과를 내놓는다. x의 첫번째 요소와 y의 첫번째 요소, x의 두번째 요소와 y의 두번째 요소를 각각 매칭시켜 주는 것이다.
반면, 그냥 리스트 형태일 경우를 생각해보자.

# wrong case
x = [1, 2, 3]
y = [2, 4, 6]
x + y

리스트 자료형을 완벽하게 학습한 사용자라면, 위의 결과는 [1, 2, 3, 2, 4, 6]로 나옴을 금방 알 수 있다. 사실 덧셈 연산이어서 그나마 결과라도 나오지, -*, / 등의 리스트끼리의 연산은 계산 결과도 나오지 않고 에러가 뜬다.

 

3. 넘파이(numpy)의 N차원 배열

앞서 정의에서 언급했듯이, 넘파이(numpy)는 행렬을 손쉽게 다룰 수 있는 도구다.
호기심 많은 독자는 궁금증이 생길 것이다. '우리는 지금까지 숫자만 넘파이(numpy)에 담고 있는데, 다른 것도 담을 수 있나요? 이를테면 문자나 리스트 같은 것도요.'
당연히 가능하다. 앞서 정의한 바를 상기해 보면, 한 개 이상의 원소를 가진 배열 형태는 모두 넘파이(numpy) 형태로 표현할 수 있다.
문자는 각자 실험해 보시고, 이번 시간에는 배열을 담은 배열에 초점을 맞춰 설명하겠다. 리스트를 다뤄본 사용자라면, 리스트 안에 리스트를 담는 게 이상하지는 않을 것이다.

A = [[1, 2], [2, 3]]

혹은

a = [1, 2]
b = [2, 3]
A = [a, b]

와 같은 형태들이 이상하지 않다.
넘파이(numpy)도 마찬가지다.

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

일 때, print(A)하면 다음과 같은 결과가 나온다.

[[1 2]
 [2 3]]

 

4. 브로드캐스트(broadcast)

넘파이(numpy)에서는 형상이 다른 배열끼리라도 계산할 수 있고, 이것을 브로드캐스트(broadcast)라고 한다.
다음의 예제를 보자.

A = np.array([[1, 2], [3, 4]])
B = np.array([10, 20])
A * B

위의 코드를 수행하면 다음과 같은 결과가 나온다.

array([[10, 40],
       [30, 80]])

즉, B의 np.array([10, 20])가 A의 첫번째 원소인 np.array([1, 2])와 연산을 하고, A의 두번째 원소인 np.array([3, 4])와도 연산을 한다. 전역에 퍼지는 방송처럼, 모든 원소에 영향을 주는 것이다. 이것을 넘파이(numpy)에서의 브로드캐스트(broadcast)라고 한다.
앞서 소개한, 모든 원소를 -1하는 연산도 사실 브로드캐스트(broadcast)에 의한 것이다.

 

5. 원소 접근

넘파이(numpy)도 리스트와 동일하게, 슬라이싱(slicing), 인덱스 지정 등으로 원소에 접근할 수 있다.
하지만 리스트 자료형과 다른 점이 있는데, 여러 개의 원소를 한번에 인덱스 지정할 수 있다는 것이다.

a = np.array([10, 20, 30, 40, 50])
a[np.array([0, 2, 4])]

위와 같은 코드를 실행하면 다음과 같은 결과가 나온다. array([10, 30, 50]) 즉, 0번째, 2번째, 4번째 원소가 호출되는 것이다.
이러한 성격 때문인지 다음과 같은 코드도 실행이 가능하다.

a = np.array([10, 20, 30, 40, 50])
a[a>25]

array([30, 40, 50])의 결과가 나온다.

두번째 다른 점은 다차원 배열에서 도드라진다.
다음과 같은 행렬 A를 생각해보자. A = np.array([[1,2],[10,20],[50,100]]) 이것을 보기 쉽게 기입하면 다음과 같은 형태가 나올 것이다.

array([[  1,   2],
       [ 10,  20],
       [ 50, 100]])

첫번째 컬럼, 즉 [[1],[10],[50]]을 얻고 싶을 때 다음과 같이 실행하면 된다.

A[:,:1]

그럼 다음과 같은 결과를 볼 수 있다.

array([[ 1],
       [10],
       [50]])

 


정리
지금까지 넘파이(numpy)의 기본적 속성들과 간단한 규칙에 대해 알아보았다.
이외에도 넘파이(numpy)로 행할 수 있는 수많은 매서드가 있지만, 후에 시간이 될 때 다른 포스트로 기술하도록 하겠다.