ML03. 소프트맥스 회귀 (Softmax Regression), 다중분류
개요
이전 글에서 다뤘던 Logistic Regression은
0 또는 1처럼 두 개의 클래스(Binary Classification) 를 구분하는 모델이었다.
그렇다면 클래스가 3개 이상이라면 어떻게 해야 할까?
예를 들면,
- 성적(X) : 0~100, 등급(y) : A~F 학점
- 숫자 이미지 분류 (0~9)
- 감정 분류 (positive / neutral / negative)
여기서 등장하는 것이 바로 Softmax Regression 이다.
Softmax Regression은 다음과 같은 이름으로도 불린다.
- Multinomial Logistic Regression
- Softmax Classifier
즉, Logistic Regression을 다중 분류로 확장한 형태 라고 이해하면 된다.
아래 그림처럼 여러 군데 흩어진 값들을 다중 분류하는 모델이다.
|
| [2차원 좌표들을 Softmax로 분류한 결과] |
1. Softmax의 핵심 아이디어
Logistic Regression에서는 최종 출력이 하나였다.
z = w1*x1 + w2*x2 + b
선형결합(z) 의 범위는 음의 무한대에서 양의 무한대 사이의 값이다.
그리고 z를 sigmoid에 통과시켜 확률로 만들었다.
y_hat = sigmoid(z)
하지만 다중 분류에서는 클래스마다 점수가 필요하다.
예를 들어 클래스가 3개라면,
z = [
2.1, # class 0 score
0.3, # class 1 score
-1.2 # class 2 score
]
이 점수들을 확률로 변환해야 한다.
그 역할을 하는 것이 Softmax 함수다.
2. Softmax 함수
Softmax는 각 클래스 점수를 확률로 바꿔준다.
수식은 다음과 같다.
\[
\text{Softmax}(z_i)
=
\frac{e^{z_i}}
{\sum_{j=1}^{K}
e^{z_j}}
\]
핵심 특징은, 모든 값이 0~1 사이
따라서 전체 합은 항상 1
가장 큰 score(Z)가 가장 높은 확률이 되는 것이다.
아래 직접 필기한 내용에서 수치예시로 이해하면 쉽다.
|
| [Softmax 수치 예시] |
① 선형결합의 결과 Z = [1, 2, 3] 이면
② 각 값들을 밑이 자연상수 e인 지수함수(e^z)로 변환
a = [2.718, 7.389, 20.085]
여기서 a를 모두 합치면 ∑a = 30.192 가 되고, 이를 분모로, a를
분자로
그럼 a = [0.09, 0.244, 0.665] 의 합이 1인 확률분포가
된다.
이것이 Softmax 함수.
③ 만약 y 값이 [0, 0, 1] 이라면, 정답(2번 클래스)
④ a - y 를 진행하면 [0.09, 0.244, -0.335] 가 error가 된다
정답 클래스의 가중치 결과가 음수지만 정상인 이유는
⑤ W 가중치를 경사하강하면
\[
W = W - \alpha \frac{\partial J}{\partial W}
\]
결국 위 식의 "W-..."에 의해 가중치가 증가되게 된다. 😀
3. 왜 exp(지수함수)를 사용할까?
다음 score를 보자.
z = [2.0, 1.0, 0.1]
그냥 합으로 나누면
[2.0/3.1, 1.0/3.1, 0.1/3.1]
이렇게 되긴 하지만 문제가 있다.
- 음수가 들어오면 확률 해석이 어려움 (음수제거)
- score 차이를 강하게 반영하지 못함 (큰 값 강조)
- 미분 가능한 형태를 유지
그래서 exp를 사용한다.
exp([2.0, 1.0, 0.1]) = [7.39, 2.71, 1.10]
큰 값은 훨씬 더 커지고 작은 값은 상대적으로 작아진다.
즉, 모델의 "확신(confidence)" 을 강조할 수 있다.
4. 수치 안정성 (Numerical Stability)
Softmax 구현 시 매우 중요한 부분이 있다.
np.exp(1000)
이런 값은 overflow(inf)가 발생할 수 있다.
그래서 보통 최대값을 먼저 빼준다.
Z = Z - np.max(Z, axis=1, keepdims=True)
수치 예시 :
[1000, 999, 998] ↓ [0, -1, -2]
Softmax 결과는 동일하지만 overflow는 방지된다.
위 2번에서 설명한 대로 음수는 경사하강시 양수로 바뀌어 증가된다.
5. One-Hot Encoding
다중 분류에서는 정답을 보통 One-Hot 형태로 바꾼다.
예를 들어 클래스가 3개라면
y = [0, 1, 2] y_onehot = [ [1,0,0], [0,1,0], [0,0,1] ]
numpy에서는 이렇게 만들 수 있다.
C = len(np.unique(y)) I = np.eye(C) y_onehot = I[y]
np.unique(y) = [0, 1, 2] 이므로 C는 3 이고,
np.eye()는 단위행렬을 생성하므로
[ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]
y_onehot은 단위행렬 I의 [0], [1], [2] 번 인덱스가 된다.
6. 예측 과정
Softmax Regression의 전체 흐름은 다음과 같다.
(1) 선형 계산
Z = np.dot(X, W) + b # X : (m, n) # W : (n, C) # Z : (m, C)
즉, 샘플 m개, 특징 n개, 클래스 C개의 점수 생성
(2) Softmax 적용
A = softmax(Z)
수치 예시
A = [ [0.9, 0.05, 0.05], [0.1, 0.8 , 0.1 ], [0.2, 0.3 , 0.5 ] ]
각 행의 합은 항상 1이다.
(3) 최종 예측
가장 큰 확률의 클래스를 선택한다.
y_pred = np.argmax(A, axis=1)
7. Cost Function (Cross Entropy)
다중 분류에서는 보통 Cross Entropy Loss를 사용한다.
cost = -np.sum(y * np.log(y_hat)) / m
왜 이렇게 될까?
정답 클래스의 확률만 남기기 때문이다.
수치예시 (1번 클래스가 정답이라면)
y = [0, 1, 0] y_hat = [0.1, 0.8, 0.1] # 곱하면 [0, log(0.8), 0]
즉, 정답 클래스 확률만 loss에 반영된다.
8. log(0) 문제
만약 예측 확률이 0이라면, 음의 무한대가 된다.
np.log(0) = -∞
이를 방지하기 위해 clip을 사용한다.
epsilon = 1e-15 y_hat = np.clip(y_hat, epsilon, 1-epsilon)
여기서 엡실론(ε)은 아주 작은 값이다.
y_hat이 0라면 ε을 사용하고 1이면 1-ε 을 사용한다.
9. Gradient
Softmax + Cross Entropy 조합은 미분이 굉장히 깔끔하게 정리된다.
최종 오차
err = y_hat - y
gradient
dj_dw = np.dot(X.T, err) / m dj_db = np.sum(err, axis=0) / m
이 부분이 Softmax Regression의 핵심이다.
10. 전체 구현
아래 예제는 ML Lib. 사용을 지양하고 수식과 전개과정 이해를 위해 numpy로 작성.
(개념공부 시 PyTorch, TensorFlow 사용 X 추천)
두 파일을 같은 위치에 두고 main.py를 실행.
main.py
import numpy as np
X = np.array([0, 2, 4, 6, 8, 10]).reshape(-1, 1)
y = np.array([0, 0, 1, 1, 2, 2])
m, n = X.shape
#print(m, n)
# y -> one-hot vector
C = len(np.unique(y))
I = np.eye(C)
y_onehot = I[y]
W = np.random.randn(n, C)
b = np.zeros(C)
epochs = 5000
lr = 0.1
from softmax_func_01 import gradient_descent, softmax
W_final, b_final = gradient_descent(X, y_onehot, W, b, epochs, lr)
# predict
X_test = np.array([1, 5, 9]).reshape(-1, 1)
def predict(X, W, b):
z = np.dot(X, W) + b
return softmax(z)
y_pred = predict(X_test, W_final, b_final)
print(X_test)
print(y_pred)
print(np.argmax(y_pred, axis=1))
softmax_func.py
import numpy as np
def softmax(z):
z = z - np.max(z, axis=1, keepdims=True)
exp_z = np.exp(z)
A = exp_z / np.sum(exp_z, axis=1, keepdims=1)
return A
def compute_cost(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = softmax(z) # (m, C)
epsilon = 1e-15
y_hat = np.clip(y_hat, epsilon, 1-epsilon)
L = -np.sum(y * np.log(y_hat))
cost = L/m
return cost
def compute_gradient(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = softmax(z)
err = y_hat -y
dj_dw = np.dot(X.T, err) / m
dj_db = np.sum(err, axis=0) / m
return dj_dw, dj_db
def gradient_descent(X, y, W, b, epochs, lr):
for i in range(epochs):
dj_dw, dj_db = compute_gradient(X, y, W, b)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
cost = compute_cost(X, y, W, b)
print(f'epochs={i}, W={np.round(W, 3)}, b={np.round(b, 3)}, cost={cost:.3f}')
return W, b
결과는 아래와 같다.
|
| [학습이 잘 된 결과] |
11. 정리
Softmax Regression은
- Logistic Regression의 다중 클래스 버전
- 각 클래스 score를 계산
- Softmax로 확률화
- Cross Entropy로 학습
하는 모델이다.
딥러닝의 마지막 출력층에서도 매우 자주 등장한다.
예를 들면,
- 이미지 분류 CNN
- Transformer 분류기
- NLP 다중 클래스 분류
등 대부분의 분류 모델 마지막에 Softmax가 사용된다.
12. 참조문헌
Andrew Ng. 교수님 수업을 들으며 직접 작성한 내용.
|
| [페이지 1] |
|
| [페이지 2] |
감사합니다.





댓글
댓글 쓰기