이번엔 numpy 공부 중, 가장 헷갈렸던 .reshape()
과 이와 연관된 브로드캐스팅을 잠깐 복습 겸 기록해두려고 한다.
1. Reshape 사용법
너무나 당연한 이야기지만, .reshape
은 numpy array의 모양을 바꿔주는 역할을 한다. 잠깐 살펴보면
import numpy as np
a = np.array([[1,1,1,1], [2,2,2,2]])
print(a)
b = a.reshape((2,2,2))
print(b)
# output:
# [[1 1 1 1],
# [2 2 2 2]]
# [[[1 1],
# [1 1]]
# [[2 2],
# [2 2]]]
.reshape(2, 2, 2)
일 경우,
- 가장 안쪽부터 2개의 원소로 하나의 리스트
(ex. [2, 2])
를 만들고 - 이런 리스트를 하나로 2개 가진 리스트를 또 만든다.
(ex. [[2, 2], [2, 2]])
- 또 이런 리스트를 2개 가지는 리스트를 만든다.
(ex. [[[1, 1], [1, 1]], [[2, 2], [2, 2]]])
.reshape(a, b, c)
에서 c -> b -> a
순으로 모양을 바꿔간다고 이해하는게 빠른 듯 하다.
2. Reshape 과 broadcasting
broadcasting은, 연산에 사용되는 행렬(리스트)의 모양에 따라서, 연산되는 행렬의 모양이 자동으로 바뀐 뒤 연산되는 것을 말하는데, 설명하기 귀찮으니 그냥 그림으로 대체한다.
아래 그림에서 회색으로 되어있는 부분이 연산에 맞게 numpy가 알아서 행렬 모양을 바꾸는 부분이다. 이를 broadcasting이라 한다.
출처 : https://i.stack.imgur.com/JcKv1.png
여하튼 헷갈렸던건 다음 예제 였다.
행렬을 입력받아, 행렬의 원소들을 axis
인자 값에 따라 노멀라이즈 하는 문제였다.
X = np.arange(12, dtype=np.float32).reshape(6,2)
print(normalize_ndarray(X, 1))
# output:
# array([[-1., 1.],
# [-1., 1.],
# [-1., 1.],
# [-1., 1.],
# [-1., 1.],
# [-1., 1.]], dtype=float32)
print(normalize_ndarray(X, 0))
# output:
# array([[-1.46385002, -1.46385002],
# [-0.87831002, -0.87831002],
# [-0.29277 , -0.29277 ],
# [ 0.29277 , 0.29277 ],
# [ 0.87831002, 0.87831002],
# [ 1.46385002, 1.46385002]], dtype=float32)
위와 같이 주어지고, normalize_ndarray(X, axis=99, dtype=np.float32)
을 내가 직접 작성해야 했다.
일단 다음까지는 쉽게 작성할 수가 있었다.
def normalize_ndarray(X, axis=99, dtype=np.float32):
if axis == 0:
means = X.mean(axis=axis)
std = X.std(axis=axis)
return (X-means) / std # 여기서 broadcasting 이 이뤄짐.
elif axis == 1:
means = X.mean(axis=axis)
std = X.std(axis=axis)
return (X-means) / std # 여기서 broadcasting 이 이뤄짐.
else:
return (X-X.mean()) / X.std() # 여기서 broadcasting 이 이뤄짐.
문제는, 각 행렬에서 구한 means
와 std
가 X와 연산이 되려면, 아다리가 맞아야하는데, 이 아다리를 맞추는 작업이 위 코드에는 빠져있다.
하나씩 살펴보자. normalize_ndarray(X, 1)
의 경우,
# X = [[ 0. 1.]
# [ 2. 3.]
# [ 4. 5.]
# [ 6. 7.]
# [ 8. 9.]]
# [10. 11.]]
# normalize_ndarray(X, 1) 인 경우,
means = [0.5, 2.5, 4.5, 6.5, 8.5, 10.5]
std = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
means
와 std
는 모양이 (6, )
인데 반해, X는 (6, 2)
이다.
따라서, broadcasting이 되게하려면 means
와 std
는 모양을 (6, 1)
로 맞춰줄 필요가 있다.
여기서 중요한 것은 두 번째 요소인 1
이고, 앞에 6
은 이에 맞춰 변할 수 있으므로, (-1, 1)
로 reshape
해야한다.
따라서 다음과 같이 수정한다.
...
elif axis == 1:
means = X.mean(axis=axis).reshape(-1, 1)
std = X.std(axis=axis).reshape(-1, 1)
...
한편 normalize_ndarray(X, 0)
의 경우,
# normalize_ndarray(X, 0) 인 경우,
means = [5., 6.]
std = [3.4156504, 3.4156504]
means
와 std
는 모양이 (2, )
이고, X는 (6, 2)
이다.
그대로 돌려주면 문제없이 broadcasting이 일어난다.
마무리
Reshape과 broadcasting이 필요할 때, 혹은 자연스럽게 이뤄질 때, 항상 행렬 모양이 어떻게 생겨있나를 고려해야 하는 듯하다.
여기에 특별한 공식은 모르겠고, 다음을 좀 기억하고 유념해야겠다.
- 일단 직관적으로 모양을 생각하고,
- broadcasting의 경우, 내가 어떤 연산을 하려고 하는 것인지 생각하고 모양을 맞춰볼 것
.reshape()
인자로-1
을 적극 활용할 것
'데이터와 함께 탱고를 > 통계 기초 공부' 카테고리의 다른 글
두 집단의 평균차이에 대한 가설 검정 (1) | 2019.04.22 |
---|---|
가설검정 단계 (지금까지) (0) | 2019.04.18 |
표본 분산은 왜 n-1로 나눌까? (0) | 2019.04.18 |
L1, L2 Regularization (Lasso, Ridge) (0) | 2019.04.09 |
Normal equation 과 Gradient Descent (0) | 2019.04.09 |