PyTorch 텐서 집계 연산

  • 최초 작성일: 2025년 5월 29일 (목)

목차

  1. 집계 연산의 이해
  2. 기본 집계 함수 사용하기
  3. 차원별 집계 연산
  4. 다중 차원 집계
  5. argmax와 argmin 활용
  6. 실전 활용 예제

📊 집계 연산의 이해

집계(aggregation) 연산은 텐서의 여러 값을 하나 또는 더 적은 수의 값으로 요약하는 핵심 연산이다. 딥러닝에서는 손실 계산, 통계 분석, 특징 추출 등에 필수적으로 사용된다.

주요 집계 함수

  • sum: 원소들의 합
  • mean: 평균값
  • max/min: 최대/최소값
  • std/var: 표준편차/분산
  • argmax/argmin: 최대/최소값의 인덱스
import torch

# 간단한 1D 텐서로 시작
scores = torch.tensor([85.5, 92.3, 78.9, 95.1, 88.7])
print(f"학생 점수: {scores}")
# 출력: 학생 점수: tensor([85.5000, 92.3000, 78.9000, 95.1000, 88.7000])

print(f"총점: {scores.sum():.1f}")
# 출력: 총점: 440.5

print(f"평균: {scores.mean():.1f}")
# 출력: 평균: 88.1

print(f"최고점: {scores.max():.1f}")
# 출력: 최고점: 95.1

print(f"최저점: {scores.min():.1f}")
# 출력: 최저점: 78.9



🔢 기본 집계 함수 사용하기

2D 텐서에서의 전체 집계

# 2D 성적표: 5명 학생 x 3과목
grades = torch.tensor([
    [85, 90, 88],  # 학생 1
    [92, 88, 95],  # 학생 2
    [78, 85, 82],  # 학생 3
    [90, 92, 91],  # 학생 4
    [88, 86, 89]   # 학생 5
], dtype=torch.float32)

print("성적표:")
print(grades)
# 출력: 성적표:
#       tensor([[85., 90., 88.],
#               [92., 88., 95.],
#               [78., 85., 82.],
#               [90., 92., 91.],
#               [88., 86., 89.]])

# 전체 원소에 대한 집계
print(f"전체 평균: {grades.mean():.2f}")
# 출력: 전체 평균: 87.87

print(f"전체 최고점: {grades.max()}")
# 출력: 전체 최고점: 95.0

print(f"전체 최저점: {grades.min()}")
# 출력: 전체 최저점: 78.0

print(f"전체 합계: {grades.sum()}")
# 출력: 전체 합계: 1318.0

통계적 집계 함수

# 표준편차와 분산
print(f"표준편차: {grades.std():.2f}")
# 출력: 표준편차: 4.89

print(f"분산: {grades.var():.2f}")
# 출력: 분산: 23.92

# 중앙값과 분위수
print(f"중앙값: {grades.median()}")
# 출력: 중앙값: 88.0

print(f"75% 분위수: {grades.quantile(0.75)}")
# 출력: 75% 분위수: 91.0

📐 차원별 집계 연산

dim 파라미터를 사용하면 특정 차원을 따라 집계를 수행할 수 있다. 이는 딥러닝에서 배치 통계, 채널별 정규화 등에 필수적이다.

![차원별 집계 시각화] [이미지 위치: 2D 텐서에서 dim=0과 dim=1의 차이를 보여주는 화살표 다이어그램]

dim 파라미터의 이해

# 3x4 행렬 생성
mat = torch.arange(12).reshape(3, 4)
print("원본 행렬:")
print(mat)
# 출력: 원본 행렬:
#       tensor([[ 0,  1,  2,  3],
#               [ 4,  5,  6,  7],
#               [ 8,  9, 10, 11]])

# dim=0: 행 방향으로 집계 (세로 방향)
print("\ndim=0 집계 (각 열의 합):")
print(mat.sum(dim=0))
# 출력: dim=0 집계 (각 열의 합):
#       tensor([12, 15, 18, 21])

# dim=1: 열 방향으로 집계 (가로 방향)  
print("\ndim=1 집계 (각 행의 합):")
print(mat.sum(dim=1))
# 출력: dim=1 집계 (각 행의 합):
#       tensor([ 6, 22, 38])

학생 성적 예제로 이해하기

# 다시 성적표 예제 사용
print("성적표 shape:", grades.shape)  # [5 학생, 3 과목]
# 출력: 성적표 shape: torch.Size([5, 3])

# 과목별 평균 (dim=0: 학생 차원을 따라)
subj_avg = grades.mean(dim=0)
print(f"과목별 평균: {subj_avg}")
# 출력: 과목별 평균: tensor([86.6000, 88.2000, 89.0000])

# 학생별 평균 (dim=1: 과목 차원을 따라)
stud_avg = grades.mean(dim=1)
print(f"학생별 평균: {stud_avg}")
# 출력: 학생별 평균: tensor([87.6667, 91.6667, 81.6667, 91.0000, 87.6667])

max와 min의 특별한 반환값

# max와 min은 값과 인덱스를 함께 반환
print("\n과목별 최고점과 해당 학생 인덱스:")
max_vals, max_idx = grades.max(dim=0)
print(f"최고점: {max_vals}")
# 출력: 최고점: tensor([92., 92., 95.])

print(f"학생 인덱스: {max_idx}")
# 출력: 학생 인덱스: tensor([1, 3, 1])

print("\n학생별 최고 과목:")
max_vals, max_idx = grades.max(dim=1)
print(f"최고점: {max_vals}")
# 출력: 최고점: tensor([90., 95., 85., 92., 89.])

print(f"과목 인덱스: {max_idx}")
# 출력: 과목 인덱스: tensor([1, 2, 1, 1, 2])

🔄 다중 차원 집계

여러 차원을 동시에 집계할 수 있다. 이는 고차원 텐서를 다룰 때 특히 유용하다.

3D 텐서 예제

# 3D 텐서: 2개 반, 4명 학생, 3과목
class_scores = torch.arange(24).reshape(2, 4, 3)
print("반별 성적 데이터:")
print(class_scores)
# 출력: 반별 성적 데이터:
#       tensor([[[ 0,  1,  2],
#                [ 3,  4,  5],
#                [ 6,  7,  8],
#                [ 9, 10, 11]],
#               [[12, 13, 14],
#                [15, 16, 17],
#                [18, 19, 20],
#                [21, 22, 23]]])

# 각 차원별 집계
print(f"\n원본 shape: {class_scores.shape}")
# 출력: 원본 shape: torch.Size([2, 4, 3])

print(f"dim=0 sum shape: {class_scores.sum(dim=0).shape}")
# 출력: dim=0 sum shape: torch.Size([4, 3])

print("dim=0 sum (반 차원 집계):")
print(class_scores.sum(dim=0))
# 출력: dim=0 sum (반 차원 집계):
#       tensor([[12, 14, 16],
#               [18, 20, 22],
#               [24, 26, 28],
#               [30, 32, 34]])


다중 차원 동시 집계

# 여러 차원 동시 집계
print("\ndim=(1,2) 집계 (각 반의 전체 합):")
class_totals = class_scores.sum(dim=(1, 2))
print(class_totals)
# 출력: dim=(1,2) 집계 (각 반의 전체 합):
#       tensor([ 66, 210])

# 순서는 상관없음
print("dim=(2,1) 집계 (동일한 결과):")
print(class_scores.sum(dim=(2, 1)))
# 출력: dim=(2,1) 집계 (동일한 결과):
#       tensor([ 66, 210])

# 음수 인덱스 사용
print("\ndim=-1 집계 (마지막 차원):")
print(class_scores.sum(dim=-1))
# 출력: dim=-1 집계 (마지막 차원):
#       tensor([[ 3, 12, 21, 30],
#               [39, 48, 57, 66]])

print("\ndim=(-2,-1) 집계 (마지막 두 차원):")
print(class_scores.sum(dim=(-2, -1)))
# 출력: dim=(-2,-1) 집계 (마지막 두 차원):
#       tensor([ 66, 210])

keepdim 옵션

# keepdim=True로 차원 유지
orig = torch.randn(3, 4, 5)
print(f"원본 shape: {orig.shape}")
# 출력: 원본 shape: torch.Size([3, 4, 5])

# keepdim=False (기본값)
reduced = orig.mean(dim=1)
print(f"keepdim=False: {reduced.shape}")
# 출력: keepdim=False: torch.Size([3, 5])

# keepdim=True
kept = orig.mean(dim=1, keepdim=True)
print(f"keepdim=True: {kept.shape}")
# 출력: keepdim=True: torch.Size([3, 1, 5])

🎯 argmax와 argmin 활용

argmaxargmin은 최대/최소값의 위치(인덱스)를 반환한다. 분류 문제에서 예측 클래스를 찾거나, 가장 중요한 특징을 선택할 때 필수적이다.

기본 사용법

torch.manual_seed(2025)

# 1D 확률 분포
probs = torch.rand(10)
print(f"확률 분포: {probs}")
# 출력: 확률 분포: tensor([0.1947, 0.9379, 0.0840, 0.4652, 0.7508, 0.9644, 0.8788, 0.0567,
#                        0.9274, 0.5426])

print(f"최대값: {probs.max():.4f}, 위치: {probs.argmax()}")
# 출력: 최대값: 0.9644, 위치: 5

print(f"최소값: {probs.min():.4f}, 위치: {probs.argmin()}")
# 출력: 최소값: 0.0567, 위치: 7

분류 문제에서의 활용

# 5개 샘플, 4개 클래스의 로짓
logits = torch.randn(5, 4)
print("분류 로짓:")
print(logits)
# 출력: 분류 로짓:
#       tensor([[ 0.3847, -0.6459,  1.2128, -0.3121],
#               [-0.8891,  0.7265,  0.1909, -1.5430],
#               [ 0.9061, -0.3742, -0.8321,  1.0216],
#               [-0.4639,  0.5442, -1.1524,  0.8927],
#               [ 1.3589, -0.7286, -0.2446,  0.5043]])

# 각 샘플의 예측 클래스
predictions = logits.argmax(dim=1)
print(f"예측 클래스: {predictions}")
# 출력: 예측 클래스: tensor([2, 1, 3, 3, 0])

# Softmax 확률로 변환 후 argmax
probs = torch.softmax(logits, dim=1)
print("\nSoftmax 확률:")
print(probs)
# 출력: Softmax 확률:
#       tensor([[0.2286, 0.0825, 0.5246, 0.1143],
#               [0.1029, 0.5199, 0.3037, 0.0536],
#               [0.3654, 0.1023, 0.0651, 0.4072],
#               [0.1208, 0.3376, 0.0616, 0.4800],
#               [0.5544, 0.0679, 0.1125, 0.2451]])

# 확률 기반 예측 (동일한 결과)
prob_predictions = probs.argmax(dim=1)
print(f"확률 기반 예측: {prob_predictions}")
# 출력: 확률 기반 예측: tensor([2, 1, 3, 3, 0])

top-k 값 찾기

# 상위 k개 값과 인덱스 찾기
values = torch.randn(10)
print(f"원본 값: {values}")
# 출력: 원본 값: tensor([-0.3456,  1.2345, -0.7890,  2.3456,  0.1234, -1.5678,  0.9876,
#                      1.8765, -0.4567,  0.5678])

# 상위 3개
top3_vals, top3_idx = torch.topk(values, k=3)
print(f"상위 3개 값: {top3_vals}")
# 출력: 상위 3개 값: tensor([2.3456, 1.8765, 1.2345])

print(f"상위 3개 인덱스: {top3_idx}")
# 출력: 상위 3개 인덱스: tensor([3, 7, 1])

# 하위 3개 (largest=False)
bottom3_vals, bottom3_idx = torch.topk(values, k=3, largest=False)
print(f"하위 3개 값: {bottom3_vals}")
# 출력: 하위 3개 값: tensor([-1.5678, -0.7890, -0.4567])

print(f"하위 3개 인덱스: {bottom3_idx}")
# 출력: 하위 3개 인덱스: tensor([5, 2, 8])

💼 실전 활용 예제

1. 배치 정규화 통계 계산

# 미니배치 정규화를 위한 통계 계산
# [배치, 채널, 높이, 너비] 형태의 이미지 데이터
batch_imgs = torch.randn(32, 3, 64, 64)

# 채널별 평균과 표준편차 계산 (배치, 높이, 너비에 대해)
channel_mean = batch_imgs.mean(dim=(0, 2, 3), keepdim=True)
channel_std = batch_imgs.std(dim=(0, 2, 3), keepdim=True)

print(f"원본 shape: {batch_imgs.shape}")
# 출력: 원본 shape: torch.Size([32, 3, 64, 64])

print(f"채널별 평균 shape: {channel_mean.shape}")
# 출력: 채널별 평균 shape: torch.Size([1, 3, 1, 1])

print(f"채널별 평균값: {channel_mean.squeeze()}")
# 출력: 채널별 평균값: tensor([-0.0012,  0.0023, -0.0008])

# 정규화 적용
normalized = (batch_imgs - channel_mean) / (channel_std + 1e-5)

2. 어텐션 스코어에서 최대값 찾기

# 어텐션 스코어 행렬
attn_scores = torch.randn(8, 12, 100, 100)  # [배치, 헤드, 시퀀스, 시퀀스]

# 각 위치에서 가장 주목하는 위치 찾기
max_attn_idx = attn_scores.argmax(dim=-1)
print(f"최대 어텐션 위치 shape: {max_attn_idx.shape}")
# 출력: 최대 어텐션 위치 shape: torch.Size([8, 12, 100])

# 어텐션 강도 통계
attn_mean = attn_scores.mean(dim=(2, 3))
attn_max = attn_scores.max(dim=(2, 3))[0]
print(f"헤드별 평균 어텐션: {attn_mean.shape}")
# 출력: 헤드별 평균 어텐션: torch.Size([8, 12])

3. 손실 함수에서의 집계

# 배치의 개별 손실값들
individual_losses = torch.rand(128)  # 128개 샘플의 손실

# 다양한 집계 방법
mean_loss = individual_losses.mean()
sum_loss = individual_losses.sum()
max_loss = individual_losses.max()

print(f"평균 손실: {mean_loss:.4f}")
# 출력: 평균 손실: 0.4987

print(f"총 손실: {sum_loss:.4f}")
# 출력: 총 손실: 63.8336

print(f"최대 손실: {max_loss:.4f}")
# 출력: 최대 손실: 0.9934

# 상위 10% 어려운 샘플들의 평균 손실 (Hard negative mining)
k = int(0.1 * len(individual_losses))
hard_losses, _ = torch.topk(individual_losses, k)
hard_mean = hard_losses.mean()
print(f"상위 10% 어려운 샘플 평균 손실: {hard_mean:.4f}")
# 출력: 상위 10% 어려운 샘플 평균 손실: 0.9523

📊 함수별 요약

함수 용도 반환값 주요 옵션
sum 합계 텐서 dim, keepdim
mean 평균 텐서 dim, keepdim, dtype
max/min 최대/최소 (값, 인덱스) dim, keepdim
std/var 표준편차/분산 텐서 dim, keepdim, unbiased
argmax/argmin 최대/최소 인덱스 인덱스 텐서 dim, keepdim
topk 상위 k개 (값, 인덱스) k, dim, largest

집계 연산은 딥러닝의 모든 단계에서 사용되는 핵심 기능이다. 이 가이드를 통해 다양한 상황에서 적절한 집계 함수를 선택하고 활용할 수 있기를 바란다!