콘텐츠로 이동

실전 인덱싱 패턴

패턴 1: 배치 차원 유지하며 인덱싱

섹션 제목: “패턴 1: 배치 차원 유지하며 인덱싱”

딥러닝에서 배치 차원(dim=0)은 거의 항상 유지해야 합니다. 정수 인덱싱은 차원을 줄이므로 주의가 필요합니다.

import torch
# (배치=8, 채널=3, H=32, W=32) 이미지 배치
images = torch.randn(8, 3, 32, 32)
# 나쁜 예: 단일 샘플 선택 시 배치 차원 소실
single = images[0]
print(single.shape) # torch.Size([3, 32, 32]) ← 배치 차원 없음
# 좋은 예: 슬라이싱으로 배치 차원 유지
single_with_batch = images[0:1]
print(single_with_batch.shape) # torch.Size([1, 3, 32, 32])
# 또는 unsqueeze로 복원
single_unsqueezed = images[0].unsqueeze(0)
print(single_unsqueezed.shape) # torch.Size([1, 3, 32, 32])

여러 샘플을 임의 순서로 선택할 때

섹션 제목: “여러 샘플을 임의 순서로 선택할 때”
import torch
batch = torch.randn(16, 512) # (배치=16, 특징=512)
# 특정 인덱스의 샘플들 선택 (배치 차원 유지)
selected_idx = torch.tensor([0, 3, 7, 15])
selected = batch[selected_idx]
print(selected.shape) # torch.Size([4, 512])
# 셔플된 배치 만들기
shuffled_idx = torch.randperm(16)
shuffled = batch[shuffled_idx]
print(shuffled.shape) # torch.Size([16, 512])

원-핫 벡터를 만든 뒤 행렬 곱셈으로 임베딩을 추출하는 패턴은 gather 인덱싱과 동등합니다.

import torch
import torch.nn.functional as F
num_classes = 6
labels = torch.tensor([2, 0, 5, 3])
# 방법 1: F.one_hot (권장)
one_hot = F.one_hot(labels, num_classes=num_classes).float()
print(one_hot)
# tensor([[0., 0., 1., 0., 0., 0.],
# [1., 0., 0., 0., 0., 0.],
# [0., 0., 0., 0., 0., 1.],
# [0., 0., 0., 1., 0., 0.]])
# 방법 2: scatter_ 수동 구현
one_hot_v2 = torch.zeros(len(labels), num_classes)
one_hot_v2.scatter_(1, labels.unsqueeze(1), 1.0)
# 임베딩 행렬에서 해당 행 추출 — gather vs 행렬 곱 비교
embedding = torch.randn(num_classes, 8) # (클래스=6, 임베딩 차원=8)
# 방법 A: 직접 인덱싱 (가장 간단)
emb_a = embedding[labels]
print(emb_a.shape) # torch.Size([4, 8])
# 방법 B: 원-핫 × 임베딩 행렬 (비효율적, 이해용)
emb_b = one_hot @ embedding
print(emb_b.shape) # torch.Size([4, 8])
print(torch.allclose(emb_a, emb_b)) # True
import torch
# (N=100, 특징=8) 데이터셋
data = torch.randn(100, 8)
labels = torch.randint(0, 3, (100,)) # 0, 1, 2 중 하나
# 클래스 1인 샘플만 선택
class1_mask = labels == 1
class1_data = data[class1_mask]
print(class1_data.shape) # torch.Size([약 33, 8])
# 특정 특징값 기준 필터링
feature_mask = data[:, 0] > 0.5 # 첫 번째 특징이 0.5 초과
filtered = data[feature_mask]
print(filtered.shape)

패턴 4: 시퀀스에서 마지막 유효 토큰 추출

섹션 제목: “패턴 4: 시퀀스에서 마지막 유효 토큰 추출”

NLP에서 패딩된 시퀀스의 실제 마지막 토큰을 추출하는 패턴입니다.

import torch
# (배치=4, 시퀀스=10, 히든=8)
hidden = torch.randn(4, 10, 8)
# 각 샘플의 실제 시퀀스 길이 (패딩 제외)
lengths = torch.tensor([7, 10, 4, 9])
# 마지막 유효 토큰 위치 = length - 1
last_idx = (lengths - 1).unsqueeze(1).unsqueeze(2) # (4, 1, 1)
last_idx = last_idx.expand(-1, 1, hidden.size(2)) # (4, 1, 8)
last_hidden = hidden.gather(1, last_idx).squeeze(1) # (4, 8)
print(last_hidden.shape) # torch.Size([4, 8])
import torch
m = torch.randn(8, 4)
# 실수: 정수 인덱싱 후 차원 소실로 연산 오류
row = m[0] # shape: (4,)
# result = row + m # 브로드캐스팅 의도치 않게 동작
# 해결: unsqueeze로 차원 복원
row = m[0].unsqueeze(0) # shape: (1, 4)
result = row + m # (8, 4) — 올바른 브로드캐스팅
import torch
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
y = x[1:4] # 뷰 (메모리 공유)
# 실수: y를 독립 데이터로 착각하고 수정
y[0] = 99.0
print(x) # tensor([ 1., 99., 3., 4., 5.]) ← 원본도 변경됨!
# 해결: clone()으로 독립 복사본 생성
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
y = x[1:4].clone()
y[0] = 99.0
print(x) # tensor([1., 2., 3., 4., 5.]) ← 원본 보호됨
import torch
logits = torch.randn(4, 10) # (배치=4, 클래스=10)
targets = torch.tensor([2, 5, 1, 8]) # 정답 클래스
# 실수: index의 shape가 input과 다름
# torch.gather(logits, 1, targets) # RuntimeError!
# 해결: unsqueeze로 차원 맞추기
correct_logits = torch.gather(logits, 1, targets.unsqueeze(1))
print(correct_logits.shape) # torch.Size([4, 1])
# 최종 결과가 1D면 squeeze
correct_logits = correct_logits.squeeze(1)
print(correct_logits.shape) # torch.Size([4])

실수 4: 불리언 마스크 shape 불일치

섹션 제목: “실수 4: 불리언 마스크 shape 불일치”
import torch
data = torch.randn(5, 3)
# 실수: 1D 마스크를 2D 텐서에 그대로 적용
mask_1d = torch.tensor([True, False, True, False, True]) # (5,)
# 행 선택은 정상
print(data[mask_1d].shape) # torch.Size([3, 3]) ← 3개 행 선택
# 오류 케이스: shape 불일치 마스크
mask_wrong = torch.tensor([True, False, True]) # (3,) — 크기 다름
# data[mask_wrong] # IndexError!
# 해결: 마스크와 데이터의 첫 번째 차원을 일치시킴
상황권장 기법이유
단일 위치 원소 접근tensor[i, j]가장 간단
연속 구간 선택tensor[a:b]뷰 반환, 메모리 효율
임의 위치 여러 개tensor[idx_list]Fancy indexing
조건 기반 필터tensor[mask]불리언 마스크
행마다 다른 열 선택torch.gather배치 단위 수집
특정 위치에 값 기록tensor.scatter_분산 대입
조건부 값 교체torch.where브랜치 없는 선택
차원 유지 단일 선택tensor[i:i+1]배치 처리 호환

퀴즈를 불러오는 중...