안녕하세요. 오늘은 "넘파일 알고 쓰자" 카테고리의 본격적인 시작을 알리는 포스팅입니다. 오늘은 넘파이 라이브러리를 사용하기 위한 환경설정 및 간단한 사용법을 설명해드리도록 하겠습니다. 코드는 제 깃허브를 참고하시면 됩니다.(정말 기본적인 코드입니다.)
1. 환경 설정
저는 아나콘다 설치를 추천드립니다. 아나콘다 설치는 OS별(윈도우, 우분투)로 정리해두었으니 확인하시면 될 거 같습니다.
위에서 설명드린 환경 설정을 마친 후 새로운 주피터 노트북을 생성하겠습니다. 일단 아나콘다를 설치하면 넘파이 라이브러리는 자동으로 설치가 됩니다. 따라서, 편하게 사용하실 수 있겠죠?
넘파이 라이브러리를 사용하기 위해서는 주피터 노트북에서 아래의 코드를 입력했을 때 어떠한 오류도 없어야합니다.
import numpy as np # 넘파이 라이브러리 로드
2. 넘파이 배열 선언과 ndarray 객체의 속성
이제 넘파이 배열을 선언하는 방법에 대해서 알아보도록 하겠습니다. 이전에 넘파이 라이브러리에 대한 소개 포스팅에서 설명해드렸다싶이 넘파이 라이브러리에서는 행렬 및 벡터 연산을 지원한다고 하였습니다. 따라서 넘파이 라이브러리의 기초는 행렬과 벡터의 선언이라고 볼 수 있습니다. 이 선언은 전부 np.array() 함수를 사용합니다.
v1 = np.array([1, 2, 3]) # = <1, 2, 3>
v2 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # = <1, 2, 3, 4, 5 ,6, 7, 8, 9, 10>
m1 = np.array([[1, 2, 3], [4, 5, 6]])
# [[1, 2, 3],
# [4, 5, 6]]
m2 = np.array([[1, 2, 3, 4, 5], [7, 8, 9, 10, 11]])
# [[ 1, 2, 3, 4, 5],
# [ 7, 8, 9, 10, 11]]
굉장히 간단합니다. 넘파이 벡터나 행렬로 만들고자 하는 리스트를 np.array() 함수의 인자로 넘겨주면 됩니다. 여기서 np.array()함수에는 특별한 인자인 "dtype"이 있습니다. 이 인자는 선언된 벡터의 데이터 타입을 정해주게 됩니다. 하지만, 저희는 위 코드에서 명시적으로 정의하지 않았지만 파이썬은 내부적은 벡터의 요소들이 정수형이라고 판단하였습니다. 이와 같이 np.array() 함수를 이용해서 벡터나 행렬을 정의하면 파이썬에서는 요소들의 자료형을 추론하고 자동으로 데이터 타입을 결정합니다.
그리고 여러분들은 선언된 벡터나 행렬의 데이터 타입을 선언된 ndarray의 dtype 속성을 이용해서 확인해볼 수 있습니다.
print(v1.dtype) # int64
print(v2.dtype) # int64
print(m1.dtype) # int64
print(m2.dtype) # int64
실제로 출력해보면 전부 정수형으로 자동으로 정해졌습니다. 여기서 64는 64개의 비트를 이용해서 표현한 정수형이라는 의미입니다. 여러분들은 명시적으로 다른 자료형을 사용하도록 선언할 때 미리 정할 수 있습니다.
v1 = np.array([1, 2, 3], dtype=np.int32) # = <1, 2, 3>
v2 = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float32) # = <1, 2, 3, 4, 5 ,6, 7, 8, 9, 10>
m1 = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
# [[1, 2, 3],
# [4, 5, 6]]
m2 = np.array([[1, 2, 3, 4, 5], [7, 8, 9, 10, 11]], dtype=np.int16)
# [[ 1, 2, 3, 4, 5],
# [ 7, 8, 9, 10, 11]]
넘파이 객체를 선언하면서 dtype을 명시적으로 적었습니다. 이때, 넘파이에서 지원하는 자료형은 np.int~, np.float~를 사용해야합니다. 이제 선언된 객체의 dtype 속성을 이용해서 각 요소의 자료형을 확인해보겠습니다.
print(v1.dtype) # int32
print(v2.dtype) # float32
print(m1.dtype) # flaot64
print(m2.dtype) # int16
이렇게 넘파이 배열의 각 요소가 저희가 원하는 자료형을 가지도록 만들었습니다!
2.1 넘파이 객체 속성
넘파이 라이브러리로 배열을 선언하게 되면 ndarray라는 객체가 된다고 하였습니다. 이 객체에는 바로 이전에 설명한 dtype 속성뿐만 아니라 여러가지 중요한 속성들이 포함되어 있습니다. 하나씩 설명드리도록 하겠습니다.
1). ndarray.shape
넘파이 객체의 모양(shape)을 반환하는 속성입니다. 머신러닝이나 딥러닝을 하면서 제일 많이 사용하게 되는 속성 중에 하나입니다.
print(v1.shape) # (3,)
print(v2.shape) # (10,)
print(m1.shape) # (2, 3)
print(m2.shape) # (2, 5)
2). ndarray.ndim
넘파이 객체의 차원을 반환하는 속성입니다. 이 속성을 이용해서 넘파이 객체가 몇 차원인지 알 수 있습니다.
print(v1.ndim) # 1
print(v2.ndim) # 1
print(m1.ndim) # 2
print(m2.ndim) # 2
3). ndarray.size
넘파이 객체의 요소 개수를 반환하는 속성입니다. 벡터나 행렬의 원소의 개수를 쉽게 알 수 있습니다.
print(v1.size) # 3
print(v2.size) # 10
print(m1.size) # 6
print(m2.size) # 10
4). ndarray.dtype
위에서 설명한 것과 동일하므로 생략하겠습니다.
2.2 특수 넘파이 객체 선언
넘파이 벡터, 행렬을 선언하기 위해서 처음에 설명한 것처럼 각 숫자를 할당할수도 있습니다. 하지만, 만약 전부 같은 숫자로 채워진 벡터, 행렬을 선언하려고 한다면 아래의 함수들을 사용해볼 수 있습니다.
1). np.zeros()/np.zeros_like()
np.zeros()/np.zeros_like()는 둘 다 0으로 채워진 행렬이나 벡터를 생성하는 방법입니다. 다만 np.zeros()는 생성할 행렬, 벡터의 shape을 명시적으로 넣어줘야하지만, np.zeros_like() 특정 행렬이나 벡터의 shape과 동일한 행렬, 벡터를 생성해줍니다.
z1 = np.zeros((10)) # [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
z2 = np.zeros((10, 20))
# [[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. 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. 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. 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. 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.]
# [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
z3 = np.zeros_like(v1) # [0 0 0]
z4 = np.zeros_like(m2)
# [[0 0 0 0 0]
# [0 0 0 0 0]]
print(z1.shape) # (10,)
print(z2.shape) # (10, 20)
print(z3.shape) # (3,)
print(z4.shape) # (2, 5)
저희가 처음에 선언한 v1과 m2의 shape와 z3, z4의 shape을 비교해보세요. 동일한 것을 알 수 있습니다. 또한 모든 요소의 값이 0입니다.
2). np.ones()/np.ones_like()
np.zeros()/np.zeros_like()와 99% 동일한 기능을 하는 함수입니다. 다만!!! np.zeros()/np.zeros_like()는 모든 요소를 0으로 채우지만, np.ones()/np.ones_like()는 1로 채우는 기능입니다. 그 외의 기능은 동일합니다.
o1 = np.ones((10)) # [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
o2 = np.ones((10, 20))
# [[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
o3 = np.ones_like(v1) # [1 1 1]
o4 = np.ones_like(m2)
# [[1 1 1 1 1]
# [1 1 1 1 1]]
print(o1.shape) # (10,)
print(o2.shape) # (10, 20)
print(o3.shape) # (3,)
print(o4.shape) # (2, 5)
3). np.full()/np.full_like()
그렇다면 2, 3, 10과 같은 값으로 채우고 싶을 때는 어떻게 해야할까요? 그럴때 사용하는 함수가 np.full()/np.full_like()입니다. 이 함수의 기능 역시 np.zeros()/np.zeros_like()와 99% 동일한 기능이지만 다른 점은 채우려는 값을 명시해주어야한다는 점입니다.
f1 = np.full((10), 10) # [10 10 10 10 10 10 10 10 10 10]
f2 = np.full((10, 20), 50)
# [[50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]
# [50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50]]
f3 = np.full_like(v1, 54) # [54 54 54]
f4 = np.full_like(m2, 12)
# [[12 12 12 12 12]
# [12 12 12 12 12]]
print(f1.shape) # (10,)
print(f2.shape) # (10, 20)
print(f3.shape) # (3,)
print(f4.shape) # (2, 5)
이때, 위해서 설명한 각각의 함수에서 dtype을 명시해주면 명시된 데이터 타입을 따르는 행렬이나 벡터가 생성됩니다.
2.3 기타 넘파이 객체 선언
1). np.arange()
다음으로 설명드릴 함수는 np.arange()입니다. 이 함수는 역시 넘파이 객체를 생성하지만, 다른 점은 오직 벡터만 생성할 수 있는 함수입니다.
# np.arange(start(default=0), end, step, dtype)
ar1 = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
ar2 = np.arange(5, 10) # [5 6 7 8 9]
ar3 = np.arange(1, 10, 2) # [1 3 5 7 9]
이 함수는 시작값(start) ~ 마지막값(end)을 제외한 마지막에서 두번째값까지의 간격(step)에 맞는 벡터를 선언합니다. 이때, 시작값은 디폴트 값으로 0부터 시작하게 됩니다. 따라서 마지막값만 정해주면 알아서 생성해줍니다. ar2에서는 시작값을 5로 바꾸어 5~9까지 생성된 것을 볼 수 있습니다. 마지막으로 ar3는 시작값을 1, 마지막값을 10, 간격을 2로 지정하여 1부터 시작하여 2씩 건너뛰는 것을 볼 수 있습니다.
np.arange()와 자주 사용되는 함수는 reshape() 입니다. 이 함수는 ndarray가 가지는 고유 연산 방법이며 벡터나 행렬이 가지는 속성인 shape을 조건에만 맞으면 강제로 바꾸는 함수입니다.
arm1 = np.arange(10).reshape(2, 5)
# [[0 1 2 3 4]
# [5 6 7 8 9]]
arm2 = np.arange(5, 10).reshape(1, 5) # [[5 6 7 8 9]]
arm3 = np.arange(1, 10, 2).reshape(5, 1)
# [[1]
# [3]
# [5]
# [7]
# [9]]
print(arm1.shape) # (2, 5)
print(arm2.shape) # (1, 5)
print(arm3.shape) # (5, 1)
결과를 보면 그냥 np.arange()를 하면 벡터를 생성하지만 reshape 함수를 함께 활용해서 행렬이나 벡터의 shape이 바뀌는 것을 알 수 있습니다.
reshape()가 올바르게 작동하게 위해서는 reshape 전 요소의 개수 = reshape 후 요소의 개수 여야합니다. 위의 예제 코드의 arm1의 reshape 전 size는 10입니다. reshape을 하게 되면 (2 x 5) 크기의 행렬로 바뀝니다. 이것의 size 역시 10이기 때문에 reshape()이 동작하는 것이죠. 만약 같지 않다면 아래와 같은 오류가 발생합니다.
arm4 = np.arange(10).reshape(1, 5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-85-4b93c156c0b0> in <module>
----> 1 arm1 = np.arange(10).reshape(1, 5)
ValueError: cannot reshape array of size 10 into shape (1,5)
이 예시는 변환 전에는 size가 10이지만, 변환 후의 size가 5이기 때문에 오류가 발생하는 것입니다.
2). np.linspace()
np.arange()와 유사한 함수는 np.linspace()입니다. np.linspace()는 np.arange()와 다르게 시작, 끝값, 그리고 요소의 개수를 입력합니다.
# np.linspace(start, end, size)
li1 = np.linspace(0, 2, 9) # [0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]
li2 = np.linspace(-10, 20, 10)
# [-10. -6.66666667 -3.33333333 0. 3.33333333
# 6.66666667 10. 13.33333333 16.66666667 20. ]
li3 = np.linspace(100, 10000, 20)
# [ 100. 621.05263158 1142.10526316 1663.15789474
# 2184.21052632 2705.26315789 3226.31578947 3747.36842105
# 4268.42105263 4789.47368421 5310.52631579 5831.57894737
# 6352.63157895 6873.68421053 7394.73684211 7915.78947368
# 8436.84210526 8957.89473684 9478.94736842 10000. ]
print(li1.shape) # (9,)
print(li2.shape) # (10,)
print(li3.shape) # (20,)
np.linspace()를 이용해서 얻은 벡터의 shape을 보면 저희가 명시한 size와 동일합니다.
3. 무작위 넘파이 객체 생성
넘파이 객체를 생성하는 마지막 방법입니다. 이 방법은 특정값으로 고정하는 방법이 아니라 요소를 무작위로 선택하여 인자로 넘긴 shape에 맞게 만들어줍니다. 이때, 무작위로 선택할 때, 실수형으로 선택할 수도 있고, 정수형으로 선택할 수도 있습니다.
1). np.random.rand()
인자로 넘긴 shape을 따르는 실수형 넘파이 객체를 생성하는 함수입니다.
# 값이 다를 수 있습니다.
r1 = np.random.rand(2, 5)
r2 = np.random.rand(5, 5)
r3 = np.random.rand(8)
print(r1)
# [[0.57614483 0.73063258 0.27246055 0.21661481 0.08877755]
# [0.41265303 0.67935852 0.38921903 0.66649004 0.47586199]]
print(r2)
# [[0.07353442 0.3770628 0.06070803 0.10123741 0.63826632]
# [0.98591366 0.99821502 0.4428011 0.01528441 0.7924465 ]
# [0.42231836 0.79382126 0.62384548 0.7091891 0.43492582]
# [0.653554 0.80376989 0.11360542 0.39434994 0.34881749]
# [0.72407747 0.66944767 0.39421464 0.25446252 0.91609148]]
print(r3)
# [0.08064439 0.38030084 0.7269083 0.81481334 0.3170198 0.35690691 0.36763132 0.88280171]
이때 넘파이 객체 생성을 랜덤으로 수행하기 때문에 제가 적은 결과와 여러분들이 실제로 실행한 결과가 서로 다를 수 있다는 점에 유의하시길 바랍니다. 만약!! 언제든지 동일한 값으로 고정하고 싶다면 seed 값을 고정하면 됩니다. seed 값에 대한 자세한 설명은 이후에 설명하도록 하겠습니다. 간단한 개념 정도만 이해하시면 될 거 같습니다.
np.random.seed(42)
r4 = np.random.rand(2, 5)
r5 = np.random.rand(5, 5)
r6 = np.random.rand(8)
print(r4)
# [[0.37454012 0.95071431 0.73199394 0.59865848 0.15601864]
# [0.15599452 0.05808361 0.86617615 0.60111501 0.70807258]]
print(r5)
# [[0.02058449 0.96990985 0.83244264 0.21233911 0.18182497]
# [0.18340451 0.30424224 0.52475643 0.43194502 0.29122914]
# [0.61185289 0.13949386 0.29214465 0.36636184 0.45606998]
# [0.78517596 0.19967378 0.51423444 0.59241457 0.04645041]
# [0.60754485 0.17052412 0.06505159 0.94888554 0.96563203]]
print(r6)
# [0.80839735 0.30461377 0.09767211 0.68423303 0.44015249 0.12203823 0.49517691 0.03438852]
이 코드를 실행하면 적어도 여러분들의 컴퓨터에서는 몇 번을 실행해도 동일한 값이 나올 것입니다. 그 이유는 코드 셀 상단에 np.random.seed(42) 때문입니다. 이것은 간단하게 설명하면 컴퓨터는 매 랜덤한 값을 생성할 때마다 seed 값을 바꾸어 seed 값에 맞는 배열에서 값을 추출합니다. 따라서 seed 값을 고정하게 되면 동일한 배열값을 얻는 것입니다. 자세한 설명은 너무 어려우니 여기까지만 진행하도록 하겠습니다.
2). np.random.randint()
다음으로 볼 함수는 np.random.rand()와 동일하지만!! 다른 점은 값이 정수형이라는 것과 shape을 넘기는 게 아니라 추출할 정수의 범위와 size를 넘겨주게 됩니다. 아래의 코드를 보도록 하겠습니다.
# 값이 다를 수 있습니다.
ri1 = np.random.randint(0, 10, 5)
ri2 = np.random.randint(10, 20, 5)
ri3 = np.random.randint(0, 10, 8)
print(ri1)
# [0 3 1 7 3]
print(ri2)
# [11 15 15 19 13]
print(ri3)
# [5 1 9 1 9 3 7 6]
이 역시 seed 값을 고정하게 되면 추출되는 값이 고정됩니다.
np.random.seed(42)
ri4 = np.random.randint(0, 10, 5)
ri5 = np.random.randint(10, 20, 5)
ri6 = np.random.randint(0, 10, 8)
print(ri4)
# [6 3 7 4 6]
print(ri5)
# [19 12 16 17 14]
print(ri6)
# [3 7 7 2 5 4 1 7]
오늘은 넘파이 객체의 선언에 대해서 알아보았습니다. 다음 포스팅에서는 넘파이 객체에서 중요한 연산에 대해서 알아보도록 하겠습니다.
변경사항 : 8월 15일 "3.무작위 넘파이 객체 생성" 추가
'Programming > Python' 카테고리의 다른 글
넘파이 알고 쓰자 - Random Module (0) | 2020.08.22 |
---|---|
넘파이 알고 쓰자 - 브로드캐스팅 (0) | 2020.08.19 |
넘파이 알고 쓰자 - 넘파이 인덱싱과 슬라이싱 (0) | 2020.08.18 |
넘파이 알고 쓰자 - 넘파이 기본 연산 (3) | 2020.08.16 |
넘파이 알고 쓰자 - 소개 (0) | 2020.08.12 |