🤖 AI/ML

Softmax

소프트맥스

로짓을 확률 분포로 변환하는 함수. 다중 분류의 출력층에서 사용. 모든 출력의 합이 1이 되도록 정규화.

📖 상세 설명

Softmax는 임의의 실수 벡터(로짓)를 확률 분포로 변환하는 활성화 함수입니다. 각 원소에 지수함수(exp)를 적용한 후 전체 합으로 나눠, 모든 출력이 0~1 사이이고 합이 정확히 1이 됩니다. 수식: softmax(x_i) = exp(x_i) / sum(exp(x_j))

1959년 Robert Luce가 선택 모델에서 처음 제안하고, 1989년 John Bridle이 신경망에 적용했습니다. "soft" + "max"라는 이름처럼, argmax의 미분 가능한(soft) 버전으로 볼 수 있습니다. 가장 큰 값이 높은 확률을 가지면서도 다른 클래스의 확률도 유지합니다.

핵심 특성은 지수함수로 인한 차이 증폭입니다. 로짓이 [2, 1, 0]이면 softmax 후 약 [0.67, 0.24, 0.09]가 됩니다. 입력 차이가 클수록 승자독식(winner-take-all)에 가까워집니다. Temperature(T) 파라미터로 이 특성을 조절할 수 있습니다: softmax(x/T)

다중 클래스 분류(이미지 분류, 자연어 처리), Transformer의 Attention 가중치 계산, LLM의 다음 토큰 확률 계산에 사용됩니다. Cross-Entropy Loss와 함께 사용하면 그래디언트가 (예측 - 정답)으로 깔끔하게 계산되어 학습이 안정적입니다.

💻 코드 예제

import numpy as np
import torch
import torch.nn.functional as F

# 1. NumPy로 직접 구현 (수치 안정성 포함)
def softmax_numpy(x):
    """Numerically stable softmax"""
    x_max = np.max(x, axis=-1, keepdims=True)
    exp_x = np.exp(x - x_max)  # overflow 방지
    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)

logits = np.array([2.0, 1.0, 0.1])
probs = softmax_numpy(logits)
print(f"로짓: {logits}")
print(f"확률: {probs}")       # [0.659, 0.242, 0.099]
print(f"합계: {probs.sum()}")  # 1.0

# 2. PyTorch 내장 함수
logits_torch = torch.tensor([2.0, 1.0, 0.1])
probs_torch = F.softmax(logits_torch, dim=-1)
print(f"PyTorch softmax: {probs_torch}")

# 3. Temperature로 분포 조절
def softmax_with_temperature(logits, temperature=1.0):
    """Temperature가 낮을수록 sharp, 높을수록 uniform"""
    return F.softmax(logits / temperature, dim=-1)

logits = torch.tensor([3.0, 1.0, 0.5])
print(f"T=0.5 (sharp): {softmax_with_temperature(logits, 0.5)}")  # [0.93, 0.05, 0.02]
print(f"T=1.0 (normal): {softmax_with_temperature(logits, 1.0)}") # [0.84, 0.11, 0.05]
print(f"T=2.0 (soft): {softmax_with_temperature(logits, 2.0)}")   # [0.65, 0.21, 0.14]

# 4. 분류 모델에서 사용
class Classifier(torch.nn.Module):
    def __init__(self, input_dim, num_classes):
        super().__init__()
        self.linear = torch.nn.Linear(input_dim, num_classes)

    def forward(self, x):
        logits = self.linear(x)
        # 학습 시: CrossEntropyLoss가 내부적으로 softmax 적용
        # 추론 시: F.softmax(logits, dim=-1)로 확률 변환
        return logits

# CrossEntropyLoss = LogSoftmax + NLLLoss
loss_fn = torch.nn.CrossEntropyLoss()
logits = torch.randn(4, 10)  # batch=4, classes=10
labels = torch.randint(0, 10, (4,))
loss = loss_fn(logits, labels)  # softmax 내장

🗣️ 실무에서 이렇게 말하세요

💬 모델 출력 해석 회의에서
"모델 출력이 로짓이라 바로 해석하면 안 됩니다. softmax 적용해서 확률로 바꾼 다음, 가장 높은 클래스가 고양이 0.85, 개 0.10 이런 식으로 보시면 됩니다. 0.85면 신뢰도가 높은 예측이에요."
💬 면접에서
"Softmax는 로짓을 확률 분포로 변환합니다. 지수함수로 양수화한 뒤 정규화하죠. Cross-Entropy Loss와 함께 쓰면 그래디언트가 (예측-정답)으로 단순해져서 학습이 안정적입니다. 수치 안정성을 위해 max를 빼는 것도 중요합니다."
💬 LLM 샘플링 논의에서
"Temperature 0.7로 설정하면 softmax가 더 sharp해져서 고확률 토큰 위주로 샘플링됩니다. 창의적 출력이 필요하면 temperature를 1.2 정도로 올리면 분포가 평평해져서 다양한 토큰이 선택돼요."

⚠️ 흔한 실수 & 주의사항

CrossEntropyLoss에 softmax를 중복 적용

PyTorch의 nn.CrossEntropyLoss는 내부적으로 LogSoftmax를 적용합니다. 입력에 softmax를 미리 적용하면 이중 적용되어 학습이 망가집니다.

큰 로짓에서 overflow 발생

exp(1000)은 inf가 됩니다. 반드시 max를 빼서 수치 안정성을 확보하세요: exp(x - max(x)). 대부분의 라이브러리 내장 함수는 이미 처리되어 있습니다.

올바른 방법

학습 시 CrossEntropyLoss에 로짓을 직접 전달하고, 추론 시에만 F.softmax()로 확률 변환하세요. Temperature 조절이 필요하면 softmax 전에 로짓을 나눕니다.

🔗 관련 용어

📚 더 배우기