Chapter1 ndarray: Notebook6 Reshape Resize and Vectorization
NumPy Master Class¶
Chapter1 ndarray¶
Notebook6 Reshape Resize and Vectorization¶
이번 notebook에서는 ndarray를 사용하다보면 정말 많이 사용하게 되는 reshape, resize를 알아본다.
단어로만 봤을 때는 reshape, resize가 뭔 차이가 있는지 헷갈릴 수 있다.
하지만 걱정할 것 없이 둘 중 편한 것 하나만 골라서 사용해도 괜찮다. 이 notebook에서 둘 다 다루는 이유는 사람들이 reshape, resize를 둘 다 많이 사용하고 있고, 따라서 다른 사람의 코드를 볼 때 많이 나오기 때문에 이를 이해하기 위해선 reshape, resize 둘 다 이해하는 것이 필요하다.
import numpy as np
np.random.seed(0)
먼저 알아둬야 할 것은 reshape, resize가 각각 2개의 method를 가지고 있다. 이를 각각 살펴보면
1.numpy.reshape
2.numpy.resize
3.ndarray.reshape
4.ndarray.resize
이렇게 4가지고 있고, numpy로 시작하는 함수들은
np.reshape, np.resize
위와 같이 사용하는 함수들이고, 만약 test_np라는 ndarray가 있을 때 ndarray.reshape, ndarray.resize method들은
test_np.reshape, test_np.resize
위와 같이 사용하는 method들이다. 따라서 np.으로 시작하는 함수들은 어떤 ndarray에 대해 reshape, resize를 할지 정해줘야 하지만 test_np.로 시작하는 reshape, resize는 이미 test_np라는 ndarray에 대해 reshape, resize를 해준다는 뜻이 담겨있으므로 target ndarray를 지정해줄 필요가 없다.
따라서 저자는 np.보다는 test_np.와 같이 ndarray에 대한 method를 자주 사용한다.
위의 4가지 method들 중에서 3, 4번 method를 집중적으로 다루며 reshape, resize를 살펴보도록 하자.
먼저 reshape의 예제를 위해 다음과 같은 test_np를 만들어보자
test_np = np.random.randint(low = 0, high = 3, size = (4,5))
print(test_np)
위에서 볼 수 있듯이, shape을 지정해주면 원하는 모양으로 ndarray를 만들 수 있다. shape에는 보통 int의 tuple 형태를 많이 사용한다.
reshaped_np = test_np.reshape(5,4)
print(reshaped_np)
reshaped_np2 = test_np.reshape(2,10)
print(reshaped_np2)
위와 같이 원래 4X5 ndarray를 5X4와 2X10의 형태로 바뀌는 것을 알 수 있다.
이때 다음 resize와 비교하기 위해 원래 test_np를 한 번 출력해보도록 하자.
print(test_np)
위와 같이 원래 test_np는 원래의 형태를 유지하고 있는 것을 알 수 있다.
이번엔 resize를 살펴보자. 주목할 점은
https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.resize.html#numpy.ndarray.resize
에서 알 수 있듯이 return value가 None이다. 그러면 어떻게 작동하는지 살펴보자.
resized_np = test_np.resize(5,4)
print(resized_np)
정말 return value가 None인 것을 확인할 수 있다. 그러면 무슨 일이 일어난걸까?
원래의 ndarray인 test_np를 출력해보자.
print(test_np)
원래 (4,5) ndarray가 (5,4)로 바뀐 것을 알 수 있다. 이렇게 ndarray.resize() method는 원래의 ndarray를 바꿔주는, 즉 in-place 작용을 하는 것을 알 수 있다.
위의 두 reshape, resize를 살펴보면 일단 공통적으로 우리가 원하는 shape으로 바꿔주는 method들인 것을 알 수 있고, 다른 점은 in-place인지 in-place가 아닌지로 나눌 수 있다.
그럼 각각은 어떨 때 사용할까?
먼저 reshape을 살펴보면 Notebook5에서 다룬것처럼 return을 reshaped ndarray를 돌려주지만 참조를 이용한다. 따라서 원래의 ndarray를 바꾸면 reshaped ndarray로 바뀌게 된다.
test_np = np.random.randint(low = 0, high = 3, size = (4,5))
print("original test_np:\n", test_np, '\n')
reshaped_np = test_np.reshape(5,4) # reshaped ndarray 생성
test_np[0] = 100 # test_np의 첫 번째 row를 모두 100으로 치환
print("test_np:\n", test_np)
print("reshaped_np:\n:", reshaped_np)
위와 같이 원래의 ndarray를 바꾸면 reshaped도 영향을 받으므로 이를 원치 않을 경우 copy() method를 같이 이용할 수 있다.
test_np = np.random.randint(low = 0, high = 3, size = (4,5))
print("original test_np:\n", test_np, '\n')
reshaped_np = test_np.reshape(5,4).copy() # reshaped ndarray 생성
test_np[0] = 100 # test_np의 첫 번째 row를 모두 100으로 치환
print("test_np:\n", test_np)
print("reshaped_np:\n:", reshaped_np)
copy()를 곁들여주면 위와 같이 test_np를 바꿔도 reshaped_np는 영향을 받지 않는 것을 알 수 있다.
이처럼 reshaped는 copy와 함께 사용하면 기존의 ndarray를 다른 shape을 가진 ndarray로 만들어 연산을 진행할 수 있다.
반면에 resize() method는 in-place 작용을 한다고 했으므로 현재 가지고 있는 ndarray를 resize하고 다시 돌려놓을 필요가 없을 때 사용한다. 예를 들어
우리가 처음 받은 data의 모양이 마음에 들지 않을 때 우리는 resizing을 하게 된다. 그리고 이 resizing은 다음에 다시 돌려놓을 필요가 없으므로 이 in-place resize() method를 이용하면 코드도 더 간단하면서도 reshape() method와 같은 역할을 수행할 수 있다.
참고로 위의 reshape, resize는 나중에 사용할 때 생각보다 헷갈리는 경우가 많으므로 이참에 확실히 외워두고 넘어가는 것을 추천한다.
Vectorization with -1¶
많은 경우에 reshape, resize method들은 vectorization을 할 때 사용된다.
즉, matrix나 tensor를 1차원 vector로 만드는 역할을 할 때 자주 사용된다.
그럴 때, -1을 이용하면 훨씬 편하게 vectorization을 진행할 수 있다.
예를 들어 Deep learning을 이용하여 다음과 같은 MNIST data를 분류한다고 해보자.
그러면 각각 image들은 28X28의 모양을 가지고 있고, 다음과 같이 이 이미지를 vector로 만들어 classification을 해본다고 가정하자.
Deep learning을 모르는 사람들도 "이 network의 input은 28x28 image를 vector로 만든 784차원 vector"라는 것만 알면 된다.
그러면 우리가 가진 MNIST data는 다음과 같은 모습을 보여준다.
digit1 = np.zeros(shape = (28,28))
print(digit1.shape)
위에서 말했듯이 우리는 이 digit1을 vector로 만들어줘야 하므로 다음과 같이 vector로 만들 수 있다.
vec_digit1 = digit1.reshape(784,1)
print(digit1.shape)
print(vec_digit1.shape)
하지만 위와 같이진행하면 다른 크기의 image를 받는다면 쓸모없는 코드가 되어버린다. 하지만 -1을 이용하면 다음과 같다.
vec_digit1 = digit1.reshape(-1,1)
print(digit1.shape)
print(vec_digit1.shape)
즉 -1은 reshape을 할 때 다른 차원을 먼저 맞춰주고 그에 맞게 -1을 쓴 차원을 맞춰준다.
우리가 2번째 차원을 1로 fix시켰으므로 원소의 개수가 맞기 위해선 첫 번재 차원이 784가 돼야하는 것이다.
이렇게 -1을 사용하면 vectorization의 입장에서 동일한 결과를 얻을 수 있지만
image가 28x28일 때를 제외하더라도 계속 사용할 수 있다는 장점이 있다.
또한 무엇보다 편리하게 vectorization을 할 수 있다.