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을 다중 분류로 확장한 형태 라고 이해하면 된다.
아래 그림처럼 여러 군데 흩어진 값들을 다중 분류하는 모델이다.
예를 들면, 인접한 좌표끼리 그룹 0, 1, 2...로 묶어 라벨링(X:입력, Y:정답)한 데이터셋을 제공하면,
- X[0]=(13, 10), X[1]=(11, 12) 이라면 y[0]=0, y[1]=0, 그룹 0
- X[2]=(35, 31), X[3]=(33, 29) 이라면 y[2]=1, y[3]=1, 그룹 1
|
| [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}}
\]
- $z$ : 선형결합 결과
- $i$ : 배치 안의 샘플 인덱스
- $K$: 전체 클래스 수
- $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] # max=1000 - [1000, 1000, 1000] = [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) 최종 예측
Argmax함수로 가장 큰 확률의 클래스를 선택한다.
y_pred = np.argmax(A, axis=1)
만약 Softmax의 결과가 아래와 같다면
A = [ [0.9, 0.05, 0.05], [0.1, 0.8 , 0.1 ], [0.2, 0.3 , 0.5 ] ]
argmax를 거친 결과는 아래와 같다.
[0.9, 0.05, 0.05] 라면 0.9가 가장 높으므로 0번 선택.
y_pred = [0, 1, 2] # 각 행의 확률중 가장 높은 값의 인덱스
확률값이 아닌 클래스 번호가 필요한 이유는 정답 y가 "0, 1, 2.." 같은 클래스기 때문.
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 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
데이터셋 입력(X)은
- 0~2시간 공부하면 정답 (y)=0
- 4~6시간 공부하면 정답 (y)=1
- 8~10시간 공부하면 정답(y)=2
를 의미하며 5000회 학습된 결과는 아래와 같다.
|
| [학습이 잘 된 결과] |
학습된 모델에 공부시간 X_test 세트 [1, 5, 9] 를 제공했을때
y_pred = [0, 1, 2] 로 예측이 모두 성공하였다.
11. 정리
Softmax Regression은
- Logistic Regression의 다중 클래스 버전
- 각 클래스 score를 계산
- Softmax로 확률화
- Cross Entropy로 학습
하는 모델이다.
딥러닝의 마지막 출력층에서도 매우 자주 등장한다.
예를 들면,
- 이미지 분류 CNN
- Transformer 분류기
- NLP 다중 클래스 분류
등 대부분의 분류 모델 마지막에 Softmax가 사용된다.
12. 참조문헌
Andrew Ng. 교수님 수업을 들으며 직접 작성한 내용.
|
| [페이지 1] |
|
| [페이지 2] |
감사합니다.





댓글
댓글 쓰기