STUDY/ML DL

NumPy : reshape, transpose, squeeze, newaxis

sed 2026. 5. 9. 00:28
SMALL

reshape

reshape()는 배열의 모양을 바꾸는 함수이다.

 

np.random.randint(-1, 5, size=(12,))는 -1 이상 5 미만의 정수 중에서 랜덤하게 12개를 뽑아 1차원 배열을 만든다.

a = np.random.randint(-1, 5, size=(12,))
print(a)
print(a.shape)
[ 2  1 -1  2  2  3  1  2  1  3  0  4]
(12,)

 

 

reshape(2, 2, 3)은 원소 12개짜리 배열을 2×2×3 형태로 바꾼다.

2행 3열 짜리의 데이터가 2개 있다는 뜻이다. (데이터 개수, 행, 열)

b = a.reshape(2, 2, 3)
print(b)
[[[ 2  1 -1]
  [ 2  2  3]]

 [[ 1  2  1]
  [ 3  0  4]]]
 

중요한 점은 reshape을 하더라도 전체 원소 개수는 같아야 한다는 것이다. 여기서는 원래 원소가 12개이고, 2 * 2 * 3도 12이므로 reshape이 가능하다.

 

 

벡터 내적과 transpose

numpy에서는 배열의 곱셈과 행렬곱을 구분해야 한다.

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.sum(a * b))
 

a * b는 원소별 곱이다.

[1*4, 2*5, 3*6]
 

 

따라서 이를 모두 더하면 다음과 같다.

1*4 + 2*5 + 3*6 = 32
 

 

출력 결과는 다음과 같다.

32
 

 

 

이번에는 두 배열을 열 벡터로 바꿔보자.

a = a.reshape(3, 1)
b = b.reshape(3, 1)

print(a.transpose() @ b)
print(a.T @ b)
 

 

a.reshape(3, 1)은 a를 3행 1열짜리 열 벡터로 바꾼다. a.transpose() 또는 a.T는 전치 행렬을 의미한다.

따라서 a.T @ b는 다음과 같은 행렬곱이 된다.

[[1, 2, 3]] @ [[4],
               [5],
               [6]]
 

결과는 다음과 같다.

[[32]]
[[32]]
 

 

a.transpose()와 a.T는 2차원 배열에서는 같은 의미로 생각해도 된다.

a = np.random.randn(4, 3, 2)
print(a.transpose(2, 1, 0).shape)
 

 

3차원 이상의 배열에서는 transpose()에 축의 순서를 직접 지정할 수 있다.

원래 shape이 (4, 3, 2)인데, transpose(2, 1, 0)을 하면 축의 순서가 바뀌어 (2, 3, 4)가 된다.

(2, 3, 4)
 
 

reshape에서 -1 사용하기

reshape()에서는 -1을 사용할 수 있다. 이때 -1은 남은 차원의 크기를 numpy가 자동으로 계산하라는 의미이다.

np.arange(20)은 0부터 19까지의 정수를 만든다.

a = np.arange(20)
print(a)
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]

 

원소 20개를 4행 5열로 바꾼다.

print(a.reshape(4, 5))
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
 
 

reshape(4, -1)은 행을 4개로 만들고, 열의 수는 numpy가 알아서 계산하라는 뜻이다. 전체 원소가 20개이므로 열은 5개가 된다.

print(a.reshape(4, -1).shape)
(4, 5)
 
2 * 5 * ? = 20이 되어야 하므로, 마지막 차원은 2가 된다.
print(a.reshape(2, 5, -1).shape)
 
(2, 5, 2)
 

reshape(1, -1)은 1행짜리 2차원 행 벡터를 만든다. 반대로 reshape(-1, 1)은 1열짜리 2차원 열 벡터를 만든다.

print(a.reshape(1, -1).shape)
print(a.reshape(-1, 1).shape)
(1, 20)
(20, 1)
 
 
아래 예시를 보면 이해하기 쉽다.
# 행은 1개이고 열은 5개인 행 벡터(row vector)
[[0 1 2 3 4]]
(1, 5)

# 행은 5개이고 열은 1개인 열 벡터(column vector)
[[0]
 [1]
 [2]
 [3]
 [4]]
(5, 1)
 
 
 

... 사용하기

numpy에서는 ...을 사용해 중간 차원을 생략할 수 있다.

 

아래 배열의 shape은 (2, 3, 4, 5, 6)이다. 즉, 5차원 배열이다.

x = np.random.randn(2, 3, 4, 5, 6)
 
 

아래 두 코드는 같은 의미이다.

앞의 두 축에서 각각 1, 2를 선택하고, 나머지 축은 전부 가져온다.

print(x[1, 2, :, :, :].shape)
print(x[1, 2, ...].shape)
(4, 5, 6)
(4, 5, 6)
 
 

두 코드도 같은 의미이다. 마지막 축에서 인덱스 3을 선택하고, 앞의 모든 축은 그대로 가져온다.

print(x[:, :, :, :, 3].shape)
print(x[..., 3].shape)
(2, 3, 4, 5)
(2, 3, 4, 5)
 
 

이 두 코드도 같은 의미이다. 첫 번째 축에서 1을 선택하고, 네 번째 축에서 3을 선택하며, 나머지 축은 그대로 가져온다.

print(x[1, :, :, 3, :].shape)
print(x[1, ..., 3, :].shape)
(3, 4, 6)
(3, 4, 6)
 

 

...은 차원이 많은 배열을 다룰 때 코드를 짧게 만들어준다.

딥러닝에서는 이미지, 배치, 채널처럼 차원이 많은 데이터를 다루기 때문에 종종 사용된다.

 

 

 

배열 붙이기

numpy 배열은 가로 또는 세로로 붙일 수 있다.

 

a는 1로 채워진 2행 2열 배열이고, b는 0으로 채워진 2행 2열 배열이다.

a = np.ones((2, 2))
b = np.zeros((2, 2))
 

 

np.hstack()은 배열을 가로 방향으로 붙인다.

c = np.hstack([a, b])
print(c)
[[1. 1. 0. 0.]
 [1. 1. 0. 0.]]
 
 

np.vstack()은 배열을 세로 방향으로 붙인다.

d = np.vstack([a, b])
print(d)
[[1. 1.]
 [1. 1.]
 [0. 0.]
 [0. 0.]]
 

 

np.block()을 사용해도 배열을 붙일 수 있다.

e = np.block([a, b])
f = np.block([[a], [b]])
g = np.block([[[a]], [[b]]])

print(e)
print(f)
print(g)
 

 

np.block([a, b])는 가로로 붙이는 것과 비슷하게 작동한다.

[[1. 1. 0. 0.]
 [1. 1. 0. 0.]]
 

 

np.block([[a], [b]])는 세로로 붙이는 것과 비슷하게 작동한다.

[[1. 1.]
 [1. 1.]
 [0. 0.]
 [0. 0.]]
 

 

np.block([[[a]], [[b]]])는 차원을 하나 더 유지하면서 블록 형태로 쌓는다.

[[[1. 1.]
  [1. 1.]]

 [[0. 0.]
  [0. 0.]]]
 

간단히 말하면 가로로 붙일 때는 hstack, 세로로 붙일 때는 vstack을 먼저 떠올리면 된다.

 

 

 

squeeze

squeeze()는 크기가 1인 차원을 제거하는 함수이다.

a = np.random.randn(1, 1, 1, 3, 1, 1, 4, 1)
print(a.shape)
 

 

이 배열의 shape은 다음과 같다.

(1, 1, 1, 3, 1, 1, 4, 1)
 
 

`a.squeeze()`는 크기가 1인 차원을 모두 제거한다. 남는 것은 크기가 3인 차원과 4인 차원뿐이다.

print(a.squeeze().shape)
(3, 4)
 
 

`squeeze(axis=...)`를 사용하면 특정 축만 선택해서 제거할 수 있다.

print(a.squeeze(axis=(0, 2, 4, 5)).shape)
(1, 3, 4, 1)
 

 

원래 shape은 다음과 같다.

축 번호:   0  1  2  3  4  5  6  7
shape:   (1, 1, 1, 3, 1, 1, 4, 1)
 

여기서 0번, 2번, 4번, 5번 축만 제거하면 1번, 3번, 6번, 7번 축이 남는다.
그래서 최종 shape은 (1, 3, 4, 1)이 된다.

 

단, squeeze()로 제거할 수 있는 축은 크기가 1인 축뿐이다.
크기가 3이나 4인 축은 제거할 수 없다.

 

 

np.newaxis와 expand_dims

반대로 차원을 추가하고 싶을 때는 np.newaxis 또는 np.expand_dims()를 사용할 수 있다.

a = np.random.randn(3, 4)
 

이 배열의 shape은 (3, 4)이다.

 
맨 앞에 새로운 축을 추가한다.
a1 = a[np.newaxis, ...]
print(a1.shape)
(1, 3, 4)
 

 

맨 뒤에 새로운 축을 추가한다.
a2 = a[..., np.newaxis]
print(a2.shape)
(3, 4, 1)
 
 

가운데에 새로운 축을 추가한다.

a3 = a[:, np.newaxis, :]
print(a3.shape)
(3, 1, 4)
 

 

같은 작업은 np.expand_dims()로도 할 수 있다.

b1 = np.expand_dims(a, axis=0)
print(b1.shape)

b2 = np.expand_dims(a, axis=1)
print(b2.shape)

b3 = np.expand_dims(a, axis=2)
print(b3.shape)
 

출력 결과는 다음과 같다.

(1, 3, 4)
(3, 1, 4)
(3, 4, 1)
 

 

즉, np.newaxis와 np.expand_dims()는 둘 다 새로운 차원을 추가할 때 사용한다.

 
a[np.newaxis, ...]        # axis=0에 차원 추가
a[:, np.newaxis, :]       # axis=1에 차원 추가
a[..., np.newaxis]        # 마지막 axis에 차원 추가
 

딥러닝에서는 배치 차원이나 채널 차원을 맞추기 위해 이런 차원 추가가 자주 필요하다.

 

 

 

np.newaxis는 None이다

마지막으로 np.newaxis의 정체를 확인해보자.

np.newaxis is None
 

출력 결과는 다음과 같다.

True
 

즉, np.newaxis는 실제로 None과 같은 객체이다.

다만 numpy 코드에서는 의미를 더 명확하게 보여주기 위해 np.newaxis라는 이름을 사용하는 경우가 많다.

 

주의할 점은 np.nan과 np.newaxis는 전혀 다르다는 것이다.

np.nan은 결측값을 의미하고, np.newaxis는 새로운 차원을 추가할 때 사용하는 표현이다.

 

 

정리

이번 글에서는 numpy에서 자주 사용하는 함수들을 살펴보았다.

np.abs(), np.sqrt(), np.exp(), np.log()는 배열의 각 원소에 수학 함수를 적용한다.

np.max(), np.min(), np.sum(), np.mean(), np.std()는 배열의 통계값을 구할 때 사용한다.

axis=0은 열 기준 계산, axis=1은 행 기준 계산으로 이해하면 된다.

reshape()는 배열의 모양을 바꾸고, -1을 사용하면 numpy가 알맞은 차원의 크기를 자동으로 계산한다.

transpose()는 축의 순서를 바꾸고, ...은 여러 차원을 생략해서 표현할 때 사용한다.

hstack()과 vstack()은 배열을 가로 또는 세로로 붙일 때 사용한다.

squeeze()는 크기가 1인 차원을 제거하고, np.newaxis와 expand_dims()는 새로운 차원을 추가할 때 사용한다.

LIST