NumPy 인덱싱 & 슬라이싱: 텐서를 자유자재로 자르는 법

1. 왜 인덱싱/슬라이싱이 이렇게 중요할까?



딥러닝을 하다 보면 텐서를 이렇게 자주 다루게 됩니다.

  • 배치에서 앞 몇 개 샘플만 뽑기
  • 이미지에서 특정 채널(R/G/B) 만 골라 쓰기
  • 시퀀스 데이터에서 일부 타임스텝만 자르기
  • 라벨에서 특정 클래스만 골라내기

이 모든 작업이 결국 “인덱싱(indexing) & 슬라이싱(slicing)” 입니다.

그리고 PyTorch의 Tensor 인덱싱/슬라이싱 문법은 NumPy와 거의 동일하기 때문에, NumPy에서 확실히 익혀두면 딥러닝 코드 작성이 훨씬 편해집니다.


2. 기본 인덱싱: 1차원부터 감잡기

2.1 1차원 배열 인덱싱

import numpy as np

x = np.array([10, 20, 30, 40, 50])

print(x[0])  # 10
print(x[1])  # 20
print(x[4])  # 50
  • 인덱스는 0부터 시작
  • x[i]는 i번째 원소

2.2 음수 인덱싱

뒤에서부터 세고 싶을 때는 음수 인덱스를 사용합니다.

print(x[-1])  # 50 (맨 끝)
print(x[-2])  # 40

PyTorch에서도 똑같이 동작합니다.


3. 슬라이싱 기본: start:stop:step



슬라이싱은 x[start:stop:step] 형태로 사용합니다.

x = np.array([10, 20, 30, 40, 50])

print(x[1:4])    # [20 30 40], 1 이상 4 미만
print(x[:3])     # [10 20 30], 처음부터 3 미만
print(x[2:])     # [30 40 50], 2부터 끝까지
print(x[:])      # 전체 복사 느낌

step을 넣으면 간격을 둘 수 있습니다.

print(x[0:5:2])  # [10 30 50], 0~4까지 2칸씩
print(x[::2])    # [10 30 50], 위와 동일

딥러닝에서 예를 들면, 타임스텝을 2개씩 건너뛰거나, 특정 간격으로 샘플을 선택할 때 유용합니다.


4. 2차원 이상 배열 인덱싱: 행과 열 다루기

2차원 배열부터가 사실상 “행렬/배치”이기 때문에 딥러닝 느낌이 나기 시작합니다.

import numpy as np

X = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])  # shape: (3, 3)

4.1 행/열 단일 인덱싱

print(X[0])      # 첫 번째 행: [1 2 3]
print(X[1])      # 두 번째 행: [4 5 6]

print(X[0, 0])   # 1행 1열: 1
print(X[1, 2])   # 2행 3열: 6
  • X[i] → i번째 행 (1D 배열)
  • X[i, j] → i번째 행, j번째 열의 값 (스칼라)

4.2 행 슬라이싱

print(X[0:2])    # 0~1행
# [[1 2 3]
#  [4 5 6]]

print(X[1:])     # 1행부터 끝까지
# [[4 5 6]
#  [7 8 9]]

이건 보통 배치에서 앞/뒤 일부 샘플만 자를 때 PyTorch에서 그대로 쓰는 패턴입니다.

4.3 열 슬라이싱

print(X[:, 0])   # 모든 행, 0번째 열 → [1 4 7]
print(X[:, 1])   # 모든 행, 1번째 열 → [2 5 8]
  • : 는 “해당 차원 전체”를 의미
  • X[:, 0]는 “행은 전부, 열은 0번째”라는 뜻

딥러닝에서:

  • (batch_size, feature_dim) 배열에서 특정 feature만 뽑고 싶을 때: X[:, k]

5. 3차원 이상: 배치 × 채널 × 높이 × 너비

이미지 데이터를 예로 들어 보겠습니다.

# (batch, height, width) 라고 가정
images = np.random.randn(32, 28, 28)  # 32개, 28x28 이미지

5.1 특정 샘플 하나만 뽑기

img0 = images[0]        # 첫 번째 이미지, shape: (28, 28)
img_last = images[-1]   # 마지막 이미지

5.2 배치 일부만 사용하기

first_8 = images[:8]    # 앞에서 8개만, shape: (8, 28, 28)

5.3 이미지의 일부 영역만 자르기 (crop)

# 가운데 20x20 영역만
crop = images[:, 4:24, 4:24]  # shape: (32, 20, 20)

PyTorch에서도:

# images_torch: (32, 1, 28, 28) 같은 텐서라고 가정
center_crop = images_torch[:, :, 4:24, 4:24]

처럼 인덱싱/슬라이싱 개념이 거의 그대로 입니다.


6. 슬라이싱은 보통 “뷰(view)”이다

중요한 포인트 하나:

슬라이싱 결과는 보통 원본 배열의 “뷰(view)” 입니다. 즉, 새로 데이터를 복사한 게 아니라 원본을 바라보는 창이라는 뜻입니다.

x = np.array([10, 20, 30, 40, 50])
y = x[1:4]   # view

print(y)     # [20 30 40]
y[0] = 999

print(y)     # [999  30  40]
print(x)     # [ 10 999  30  40  50]  ← 원본도 바뀜!

이 특징은:

  • 메모리를 아끼고 빠르다는 장점이 있지만
  • 실수로 원본을 바꿀 수도 있다는 단점이 있습니다.

완전히 별도의 배열로 만들고 싶다면 copy()를 사용합니다.

x = np.array([10, 20, 30, 40, 50])
y = x[1:4].copy()

y[0] = 999
print(x)  # [10 20 30 40 50], 원본 유지

PyTorch에서도 비슷한 개념이 있으니, “뷰 vs 복사” 감각을 잡아두면 디버깅이 훨씬 쉬워집니다.


7. 불리언 인덱싱: 조건으로 원소 뽑기

불리언 인덱싱은 “조건을 만족하는 원소만 선택”할 때 사용합니다.

import numpy as np

x = np.array([1, -2, 3, 0, -5, 6])

mask = x > 0
print(mask)      # [ True False  True False False  True]

pos = x[mask]
print(pos)       # [1 3 6]
  • x > 0True/False로 이루어진 배열
  • x[mask]True인 위치만 뽑기

조합 예시:

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

pos = X[X > 0]
print(pos)  # [1 2 3 4 5 6]

딥러닝에서 자주 활용되는 패턴:

  • 특정 조건을 만족하는 샘플만 추출 (예: 라벨이 특정 값인 것만)
  • 손실(loss) 계산 시, 마스크를 씌워서 일부 값만 평균 내기 등

PyTorch에서도:

import torch

x = torch.tensor([1, -2, 3, 0, -5, 6])
mask = x > 0
pos = x[mask]

처럼 거의 동일하게 사용됩니다.


8. 정수 배열 / 리스트 인덱싱 (Fancy Indexing)

정수 인덱스들의 배열/리스트로 원하는 위치를 한 번에 뽑을 수도 있습니다.

x = np.array([10, 20, 30, 40, 50])

idx = [0, 2, 4]
print(x[idx])  # [10 30 50]

2차원에서도 가능합니다.

X = np.array([[1, 2],
              [3, 4],
              [5, 6]])  # shape: (3, 2)

rows = [0, 2]
print(X[rows])  
# [[1 2]
#  [5 6]]

딥러닝에서 예를 들면:

  • 랜덤하게 섞인 인덱스 배열로 배치 샘플 뽑기
  • 특정 위치의 라벨/예측만 모아서 통계 내기

PyTorch도 같은 스타일로 사용할 수 있습니다.


9. 자주 쓰는 인덱싱 패턴 정리

딥러닝 관점에서 자주 등장하는 패턴을 모아보면:

import numpy as np

# (batch, feature)
X = np.random.randn(32, 10)

# 1) 앞 8개 샘플만
X_head = X[:8]              # (8, 10)

# 2) 특정 feature (예: 3번 컬럼)만
f3 = X[:, 3]                # (32,)

# 3) 짝수 인덱스 샘플만
X_even = X[::2]             # (16, 10)

# 4) 라벨이 1인 샘플만 선택
labels = np.random.randint(0, 3, size=(32,))
mask = labels == 1
X_cls1 = X[mask]            # 라벨 1에 해당하는 샘플만

# 5) 랜덤 셔플 후 앞 24개를 train, 뒤 8개를 val
indices = np.random.permutation(len(X))
train_idx = indices[:24]
val_idx = indices[24:]

X_train = X[train_idx]
X_val = X[val_idx]

위 패턴들은 PyTorch 텐서에서도 거의 그대로 사용됩니다. 결국 NumPy 인덱싱에 익숙해지는 것 = 텐서 데이터를 자유롭게 “쪼개고, 섞고, 골라내는 능력”을 기르는 것입니다.


10. 마무리

이번 글에서 본 것들을 정리하면:

  • 인덱싱: x[i], x[i, j], 음수 인덱스
  • 슬라이싱: start:stop:step, :, 다차원 인덱싱 (X[:, 0], X[:8], X[:, 4:8])
  • 슬라이싱은 보통 뷰(view) 이므로 원본이 바뀔 수 있음 → 필요하면 copy()
  • 불리언 인덱싱: 조건으로 필터링 (x[x > 0], X[labels == 1])
  • 정수 배열 인덱싱: 원하는 인덱스 모음으로 한 번에 선택 (x[[0,2,4]])

이 정도를 손에 익히면, PyTorch 텐서를 가지고 “배치 자르기 / 채널 선택 / 마스크 적용 / 샘플 섞기” 같은 작업을 훨씬 수월하게 할 수 있습니다.

image