1_3_Information_About_ndarray

NumPy Master Class

Chapter1 ndarray

Notebook3 Information About ndarray

우리가 Numpy를 이용하여 ndarray를 다룰 때 ndarray들에 대한 정보가 필요할 때가 많다. 이번

In [1]:
import numpy as np

shape

가장 먼저 다룰 ndarray에 대한 정보는 shape이다. Numpy는 vector, matrix, tensor 연산에 특화되어 있기 때문에 그만큼 차원이 높은 ndarray를 많이 사용하게 된다.

이때 많은 경우 ndarray가 어떤 모양을 가지고 있는지에 대한 정보가 필요해진다.

먼저 test ndarray를 다음과 같이 만들어보자.

In [3]:
test_np1 = np.array([1, 2, 3, 4, 5])
test_np2 = np.ones(shape = (2,3))
test_np3 = np.zeros(shape = (3,4,5))
test_np4 = np.full(shape = (2,2,3,4), fill_value = 3.14)
test_np5 = np.empty(shape = (3,4,5,6,7))

ndarray에 대한 shape을 얻어내기 위해선 ndarray의 attribute 중 shape을 이용하면 된다.

In [4]:
shape1 = test_np1.shape
shape2 = test_np2.shape
shape3 = test_np3.shape
shape4 = test_np4.shape
shape5 = test_np5.shape

print(shape1)
print(shape2)
print(shape3)
print(shape4)
print(shape5)
(5,)
(2, 3)
(3, 4, 5)
(2, 2, 3, 4)
(3, 4, 5, 6, 7)

dtype

위에서 말했듯이, Numpy는 고차원의 정보를 많이 이용하고 2000X30000 matrix와 같이 큰 data들을 다루게 된다.

이때 적당한 data type을 지정해주지 않으면 쓸데없이 큰 memory를 차지할 수도 있고,

정밀한 값을 얻어내야 할 때도 치명적인 오류를 만들어낼 수 있다.

https://docs.scipy.org/doc/numpy/user/basics.types.html

위의 페이지에서 Numpy가 제공하는 다양한 data type들을 볼 수 있다.

먼저 우리가 많이 사용하는 uint, int, float를 몇 비트인지 지정해주지 않았을 때를 살펴보자

dtype을 얻어내기 위해선 ndarray의 dtype attribute를 이용하면 된다.

In [7]:
uint_np = np.ones(shape = (2,2), dtype = np.uint)
int_np = np.ones(shape = (2,2), dtype = np.int)
float_np = np.ones(shape = (2,2), dtype = np.float)

print("uint_np.dtype:", uint_np.dtype)
print("int_np.dtype:", int_np.dtype)
print("float_np.dtype:", float_np.dtype)
uint_np.dtype: uint64
int_np.dtype: int64
float_np.dtype: float64

위의 결과는 생각보다 중요한 의미를 가진다. 우리가 별다른 비트를 정해주지 않고 uint, int, float를 했을 때 기본적으로 Numpy는 64 bit를 할당한다.

만약 우리가 이미지 정보를 이용하여 R,G,B에 0~255까지 8비트가 필요한 상황인데, np.uint를 이용하여 ndarray를 만들었다면 같은 이미지 정보를 담고 있어도 memory를 8배나 더 차지하게 되는 상황이 벌어진다.

만약 동영상을 다루는 경우였다면 이 memory consumption에 대한 loss는 훨씬 더 심해질 것이다.

이처럼 Numpy는 기본적으로 uint, int, float에 대하여 가장 큰 data type인 64비트를 할당해주기 때문에 다음과 같이 명시적으로 비트를 정해주는 것이 바람직하다.

In [8]:
uint_np = np.ones(shape = (2,2), dtype = np.uint8)
int_np = np.ones(shape = (2,2), dtype = np.int16)
float_np = np.ones(shape = (2,2), dtype = np.float32)

print("uint_np.dtype:", uint_np.dtype)
print("int_np.dtype:", int_np.dtype)
print("float_np.dtype:", float_np.dtype)
uint_np.dtype: uint8
int_np.dtype: int16
float_np.dtype: float32

size

itemsize

다음으로 ndarray를 사용하면서 또 자주 사용하게 되는 attribute 2가지를 배워보자.

이 size와 itemsize는 보통 ndarray의 용량을 파악할 때 함께 사용된다.

In [19]:
test_np1 = np.array([1, 2, 3, 4, 5])
test_np2 = np.ones(shape = (2,3))
test_np3 = np.zeros(shape = (3,4,5))
test_np4 = np.full(shape = (2,2,3,4), fill_value = 3.14)
test_np5 = np.empty(shape = (3,4,5,6,7))

print("test_np1.size:", test_np1.size)
print("test_np2.size:", test_np2.size)
print("test_np3.size:", test_np3.size)
print("test_np4.size:", test_np4.size)
print("test_np5.size:", test_np5.size)
test_np1.size: 5
test_np2.size: 6
test_np3.size: 60
test_np4.size: 48
test_np5.size: 2520

위에서 알 수 있듯이, shape이 A x B x C 처럼 각각 몇차원의 정보인지 알려주는 attribute였다면

size는 그 A, B, C를 모두 곱한 값을 보여준다. 즉 ndarray에 몇 개의 entry가 들어있는지 알려주는 attribute이다.

다음으로 itemsize를 보면 다음과 같다.

In [22]:
test_np1 = np.array([1, 2, 3, 4, 5], dtype = np.uint8)
test_np2 = np.ones(shape = (2,3), dtype = np.int16)
test_np3 = np.zeros(shape = (3,4,5), dtype = np.float32)
test_np4 = np.full(shape = (2,2,3,4), fill_value = 3.14, dtype = np.complex64)
test_np5 = np.empty(shape = (3,4,5,6,7), dtype = np.bool)

print("test_np1.itemsize:", test_np1.itemsize)
print("test_np2.itemsize:", test_np2.itemsize)
print("test_np3.itemsize:", test_np3.itemsize)
print("test_np4.itemsize:", test_np4.itemsize)
print("test_np5.itemsize:", test_np5.itemsize)
test_np1.itemsize: 1
test_np2.itemsize: 2
test_np3.itemsize: 4
test_np4.itemsize: 8
test_np5.itemsize: 1

이처럼 itemsize는 각각의 entry가 어떤 data type인지 파악한 뒤, 그 data가 몇 byte를 차지하는지 보여준다.

이때 itemsize는 단 하나의 entry에 대한 memory 크기를 알려준다.

이제 size와 itemsize를 합치면 ndarray가 얼만큼의 memory를 차지하는지 파악할 수 있다.

In [24]:
a = np.ones(shape = (100,300), dtype = np.int16)
print(a.size, a.itemsize)
print(str(a.size*a.itemsize) + " B and", str(a.size*a.itemsize/1024) + " KB")
30000 2
60000 B and 58.59375 KB

위처럼 size는 몇 개의 entry가 있는지 알려줬었고, itemsize는 각 entry가 몇 byte를 차지하는지 알려줬으므로 두 수를 곱하면 ndarray가 몇 byte를 차지하는지 알 수가 있다.

이제 이번 notebook에서 다뤘던 내용을 조금 합쳐서 memory consumption을 줄일 때 dtype이 얼마나 중요한지 살펴보도록 하자.

먼저 random이나 hstack같이 배우지 않은 함수들은 신경쓰지 말고, dtype만 집중해서 보도록 하자.

In [41]:
import matplotlib.pyplot as plt

img1 = np.random.randint(low = 0, high = 255, size = (1080,1920), dtype = np.uint)
img_space = np.full(shape = (1080, 30), fill_value = 255)
img2 = np.random.randint(low = 0, high = 255, size = (1080,1920), dtype = np.uint8)

img_stack = np.hstack((img1, img_space, img2))
plt.figure(figsize = (15,5))
plt.imshow(img_stack, cmap = 'gray')
Out[41]:
<matplotlib.image.AxesImage at 0x7fa58cd6aef0>

위에서 볼 수 있듯이, 둘 다 random noise를 만들어낸 모습이고,

왼쪽은 uint64, 오른쪽은 uint8로 만들어낸 것이다.

둘 다 우리가 imshow로 표현할 때는 8비트 정보를 이용하므로 우리 눈에는 똑같아 보인다.

그러면 두 image의 용량을 뽑아보도록 하자.

In [50]:
memory1 = img1.size * img1.itemsize
memory2 = img2.size * img2.itemsize
print("Memory of image 1: " + str(memory1/1024/1024) + " MB")
print("Memory of image 2: " + str(memory2/1024/1024) + " MB")

print("Memory ratio: ", memory1/memory2)
Memory of image 1: 15.8203125 MB
Memory of image 2: 1.9775390625 MB
Memory ratio:  8.0

즉 같은 이미지 한 장에서도 정확히 8배 차이가 나는 memory consumption을 확인할 수 있다. 이처럼 dtype은 실제 프로그래밍 중에 중요성을 크게 가진다.

+ Recent posts