Skip to main content

ndarray 기초

ndarray(N-dimensional array)는 NumPy의 핵심 자료구조입니다. Python 리스트와 달리 동일한 데이터 타입의 원소만 저장하며, C 언어 수준의 연속 메모리 배치로 연산 속도가 매우 빠릅니다. 머신러닝에서 다루는 피처 행렬, 이미지 텐서, 임베딩 벡터 모두 ndarray입니다.

학습 목표

  • ndarray의 핵심 속성(shape, dtype, ndim, size)을 이해하고 활용할 수 있다
  • 다양한 방법으로 배열을 생성할 수 있다
  • reshape, ravel, flatten으로 배열의 형태를 변환할 수 있다
  • copy와 view의 차이를 이해하고 데이터 안전성을 확보할 수 있다

왜 중요한가

ML/DL 파이프라인에서 데이터는 항상 ndarray(또는 이를 기반으로 한 텐서) 형태로 처리됩니다. 이미지 한 장은 (높이, 너비, 채널) shape의 3차원 배열이고, 학습 배치는 (배치크기, 높이, 너비, 채널)의 4차원 배열입니다. shape과 dtype을 정확히 이해하지 못하면 모델에 데이터를 올바르게 전달할 수 없습니다.

배열 생성

기본 생성 함수

import numpy as np

# 리스트에서 배열 생성
arr = np.array([1, 2, 3, 4, 5])
print(arr)  # [1 2 3 4 5]

# 2차원 배열 생성
matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(matrix.shape)  # (2, 3) — 2행 3열

# dtype 지정
float_arr = np.array([1, 2, 3], dtype=np.float32)
print(float_arr.dtype)  # float32

초기화 함수

# 0으로 채운 배열
zeros = np.zeros((3, 4))          # (3, 4) shape의 0 배열

# 1로 채운 배열
ones = np.ones((2, 3), dtype=np.int32)  # 정수형 1 배열

# 특정 값으로 채운 배열
full = np.full((2, 2), 7.0)      # [[7. 7.] [7. 7.]]

# 단위 행렬
eye = np.eye(3)                   # 3x3 단위 행렬

# 빈 배열 (초기화하지 않음 — 쓰레기 값 포함)
empty = np.empty((2, 3))          # 메모리 할당만, 값은 불확실
np.empty()는 메모리를 할당만 하고 초기화하지 않으므로 예측할 수 없는 값이 들어있습니다. 반드시 값을 채운 후 사용하세요.

순서와 범위 기반 생성

# 등간격 정수 배열
seq = np.arange(0, 10, 2)        # [0 2 4 6 8] — start, stop, step

# 등간격 실수 배열 (개수 지정)
lin = np.linspace(0, 1, 5)       # [0.   0.25 0.5  0.75 1.  ]

# 로그 간격 배열
log = np.logspace(0, 3, 4)       # [1. 10. 100. 1000.] — 10^0 ~ 10^3

핵심 속성

arr = np.array([[1, 2, 3],
                [4, 5, 6]], dtype=np.float64)

print(arr.shape)    # (2, 3) — 각 차원의 크기
print(arr.ndim)     # 2 — 차원 수
print(arr.size)     # 6 — 전체 원소 수
print(arr.dtype)    # float64 — 데이터 타입
print(arr.itemsize) # 8 — 원소 하나의 바이트 크기
print(arr.nbytes)   # 48 — 전체 메모리 사용량 (6 * 8)
속성설명예시
shape각 차원의 크기를 튜플로 반환(2, 3)
ndim차원의 수2
size전체 원소의 개수6
dtype원소의 데이터 타입float64
itemsize원소 하나의 바이트 크기8
nbytes배열 전체의 바이트 크기48

자주 사용하는 dtype

dtype설명메모리용도
np.int3232비트 정수4바이트인덱스, 카운트
np.int6464비트 정수8바이트큰 범위 정수
np.float3232비트 실수4바이트딥러닝 학습 (기본)
np.float6464비트 실수8바이트과학 계산 (기본)
np.bool_불리언1바이트마스크, 필터
np.str_유니코드 문자열가변텍스트
# dtype 변환
int_arr = np.array([1.7, 2.3, 3.9])
converted = int_arr.astype(np.int32)  # [1 2 3] — 소수점 버림 (반올림 아님)

배열 형태 변환

reshape

arr = np.arange(12)       # [ 0  1  2  3  4  5  6  7  8  9 10 11]

# 3행 4열로 변환
reshaped = arr.reshape(3, 4)
print(reshaped)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# -1은 자동 계산
auto = arr.reshape(3, -1)   # (3, 4) — 12 / 3 = 4
auto2 = arr.reshape(-1, 6)  # (2, 6) — 12 / 6 = 2
reshape에서 -1은 한 축에만 사용할 수 있습니다. NumPy가 전체 원소 수에서 나머지 축의 크기를 나눠 자동으로 계산합니다. ML에서 배치 차원을 유지하면서 나머지를 평탄화할 때 자주 씁니다: arr.reshape(batch_size, -1).

flatten과 ravel

matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])

# flatten — 항상 복사본 반환 (안전)
flat = matrix.flatten()
flat[0] = 99
print(matrix[0, 0])  # 1 — 원본 변경 없음

# ravel — 가능하면 view 반환 (빠름, 원본 영향 가능)
rav = matrix.ravel()
rav[0] = 99
print(matrix[0, 0])  # 99 — 원본도 변경됨!

차원 추가/제거

arr = np.array([1, 2, 3])  # shape: (3,)

# 차원 추가 — 행 벡터로
row = arr[np.newaxis, :]       # shape: (1, 3)
row2 = np.expand_dims(arr, 0)  # shape: (1, 3) — 동일

# 차원 추가 — 열 벡터로
col = arr[:, np.newaxis]       # shape: (3, 1)

# 불필요한 차원 제거
squeezed = np.squeeze(row)     # shape: (3,) — 크기 1인 차원 제거

copy vs view

NumPy 배열 조작에서 가장 주의해야 할 개념입니다.
구분view (뷰)copy (복사)
메모리원본 데이터 공유별도 메모리에 복사
수정 영향원본에 반영됨원본에 영향 없음
속도빠름느림 (메모리 할당)
생성 방법슬라이싱, reshape, ravel.copy(), flatten
original = np.array([1, 2, 3, 4, 5])

# view — 원본과 메모리 공유
view = original[1:4]
view[0] = 99
print(original)  # [ 1 99  3  4  5] — 원본이 변경됨!

# copy — 독립적인 복사본
copy = original.copy()
copy[0] = 0
print(original)  # [ 1 99  3  4  5] — 원본 변경 없음

# view 여부 확인
print(view.base is original)  # True — view임
print(copy.base is None)      # True — 독립 배열
슬라이싱 결과를 수정하면 원본 배열도 함께 변경됩니다. 원본을 보존해야 한다면 반드시 .copy()를 사용하세요. ML 전처리에서 원본 데이터를 유지하면서 변환 작업을 할 때 특히 주의가 필요합니다.

AI/ML에서의 활용

  • 피처 행렬 구성: (샘플 수, 피처 수) shape의 2D 배열이 sklearn 모델의 입력 형식입니다
  • 이미지 데이터: (배치, 높이, 너비, 채널) shape으로 CNN에 입력합니다
  • dtype 최적화: float64 대신 float32를 사용하면 GPU 메모리를 절반으로 줄일 수 있습니다
  • reshape: 모델 입력에 맞게 데이터의 shape을 변환하는 것은 가장 빈번한 작업입니다
같은 연산을 수행할 때 ndarray는 Python 리스트 대비 10~100배 빠릅니다. 이는 ndarray가 C로 구현된 연속 메모리 블록에서 벡터화 연산을 수행하기 때문입니다. 원소가 많을수록 차이가 커집니다.
reshape()는 전체 원소 수가 동일해야 하며 새로운 view를 반환합니다. resize()는 원소 수가 달라도 되며 배열 자체를 수정합니다(in-place). 일반적으로 reshape()를 사용하는 것이 안전합니다.
scikit-learn은 float64를 기본으로 사용합니다. 딥러닝(PyTorch, TensorFlow)에서는 float32가 기본이며, 최근에는 메모리 절약을 위해 float16이나 bfloat16도 사용합니다.

체크리스트

  • np.array(), np.zeros(), np.ones(), np.arange(), np.linspace()로 배열을 생성할 수 있다
  • shape, ndim, size, dtype 속성의 의미를 설명할 수 있다
  • reshape에서 -1의 의미를 이해하고 활용할 수 있다
  • flatten과 ravel의 차이(copy vs view)를 설명할 수 있다
  • copy와 view의 차이를 이해하고 .copy()를 적절히 사용할 수 있다
  • ML 입력에 맞게 배열의 shape을 변환할 수 있다

다음 문서