실전 인덱싱 패턴
패턴 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])패턴 2: 원-핫 인코딩과 인덱싱
섹션 제목: “패턴 2: 원-핫 인코딩과 인덱싱”원-핫 벡터를 만든 뒤 행렬 곱셈으로 임베딩을 추출하는 패턴은 gather 인덱싱과 동등합니다.
import torchimport torch.nn.functional as F
num_classes = 6labels = 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 @ embeddingprint(emb_b.shape) # torch.Size([4, 8])
print(torch.allclose(emb_a, emb_b)) # True패턴 3: 조건부 행 필터링
섹션 제목: “패턴 3: 조건부 행 필터링”import torch
# (N=100, 특징=8) 데이터셋data = torch.randn(100, 8)labels = torch.randint(0, 3, (100,)) # 0, 1, 2 중 하나
# 클래스 1인 샘플만 선택class1_mask = labels == 1class1_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 - 1last_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])흔한 실수와 해결법
섹션 제목: “흔한 실수와 해결법”실수 1: 인덱싱 후 차원 불일치
섹션 제목: “실수 1: 인덱싱 후 차원 불일치”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) — 올바른 브로드캐스팅실수 2: 뷰 수정으로 원본 훼손
섹션 제목: “실수 2: 뷰 수정으로 원본 훼손”import torch
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])y = x[1:4] # 뷰 (메모리 공유)
# 실수: y를 독립 데이터로 착각하고 수정y[0] = 99.0print(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.0print(x) # tensor([1., 2., 3., 4., 5.]) ← 원본 보호됨실수 3: gather에서 index shape 오류
섹션 제목: “실수 3: gather에서 index shape 오류”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면 squeezecorrect_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] | 배치 처리 호환 |
퀴즈를 불러오는 중...