ML02. 로지스틱 회귀 (Logistic Regression) 합격이나 불합격이냐
개요
지난 ML01 강좌에서는 공부 시간에 따른 시험 점수를 예측하는 선형 회귀(Linear Regression)를 알아봤습니다.
하지만 세상에는 "몇 점인가?"보다 "합격인가, 불합격인가?" 혹은 "스팸 메일인가, 아닌가?" 처럼
두 가지 상태 중 하나를 결정(분류)해야 하는 문제가 더 많습니다.
오늘은 머신러닝 Classification 알고리즘의 기초이자 핵심인 로지스틱 회귀(Logistic Regression)에 대해 알아보겠습니다.
분류의 문제
아래 표처럼 시험공부를 1시간부터 9시간까지 진행한다고 가정할 때, 7시간정도 하면 합격이라는 가상의 데이터가 주어집니다.
7시간쯤 공부하면 합격한다는 결정 경계선을 학습하고 싶다면 어떻게 해야 할까요?
| 공부 시간 (X) | 결과 (y, 합격=1, 불합격=0) |
|---|---|
| 1시간 | 0 (불합격) |
| 3시간 | 0 (불합격) |
| 5시간 | 0 (불합격) |
| 7시간 | 1 (합격) |
| 9시간 | 1 (합격) |
미리 말씀드리면, 우리가 원하는 결과는 아래와 같습니다.
|
| [학습 후 6시간 공부의 예측결과] |
그리고 합격, 불합격 여부를 학습하는 것은 인간이 개입하지 않고,
데이터만 던진 후 기계가 학습하도록 하고 싶은 것이죠.😊
1. 입출력 데이터 정의
지도 학습(Supervised Learning)이므로 몇시간 공부하면 합격, 불합격이 결정되는지는 미리 정의되어야 합니다.
파이썬 코드와 같이 진행해 보겠습니다.
import numpy as np X = np.array([1, 3, 5, 7, 9]) y = np.array([0,0,0,1,1])
2. 하이퍼 파라미터 정의
이제 학습을 위한 가중치(W), 편향(b), 학습률(lr), 학습횟수(epochs), 정규화(_lambda) 등 파라미터를 초기화 합니다.
하이퍼파라미터(Hyperparameter)는 머신러닝, 딥러닝 모델 학습을 시작하기 전, 사용자가 직접 설정하는 외부 구성 변수입니다.
모델 내부에서 학습되는 '가중치, 편향 파라미터 '와 달리, 학습률(Learning rate), 배치 크기(Batch size), 에포크(Epochs) 등 모델의 구조와 성능(과적합 방지 등)을 제어하는 핵심적인 역할을 합니다.
W = 0 b = 0 lr = 0.01 epochs = 3000 _lambda = 0
3. 기계학습
학습을 진행하기 위해 logistic_func.py 파일에서 gradient_descent, sigmoid 함수를 불러옵니다.
학습은 gradient_descent() 함수를 호출함으로서 시작됩니다.
from logistic_func import gradient_descent, sigmoid W_final, b_final = gradient_descent(X, y, W, b, lr, epochs, _lambda)
4. logistic_func.py
학습에 필요한 함수를 정리해둔 logistic_func.py 입니다.
import numpy as np
def sigmoid(Z):
return 1/(1+np.exp(-Z))
def compute_cost(X, y, W, b, _lambda=0):
m = X.shape[0]
cost = 0
for i in range(m):
z = X[i]*W + b
y_hat = sigmoid(z)
cost += -y[i]*np.log(y_hat)-(1-y[i])*np.log(1-y_hat)
L2 = _lambda/(2*m)*(W**2)
cost = cost / m + L2
return cost
def compute_gradient(X, y, W, b, _lambda=0):
m = X.shape[0]
dj_dw = 0
dj_db = 0
for i in range(m):
z = X[i]*W+b
y_hat = sigmoid(z)
dj_dw += (y_hat-y[i])*X[i]
dj_db += (y_hat-y[i])
L2 = _lambda/m*W
dj_dw = dj_dw / m + L2
dj_db = dj_db / m
return dj_dw, dj_db
def gradient_descent(X, y, W, b, lr, epochs, _lambda=0):
for i in range(epochs):
cost = compute_cost(X, y, W, b, _lambda)
dj_dw, dj_db = compute_gradient(X, y, W, b, _lambda)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
print(f'epoch={i}, W={W:.3f}, b={b:.3f}, cost={cost:.3f}')
return W, b
파일에 작성된 순서가 아니라 학습이 진행되는 과정에서 함수의 호출순서기준으로 설명할께요.
- gradient_descent Func
- 학습과정 중 비용감소 표시를 위해 현재 Cost 얻기.
- 선형결합을 진행하고 결과를 시그모이드 입력으로 넣기 (순전파)
- y_hat(예측치)를 y(실제값)과 비교한 Loss 를 미분 (역전파)
- W, b를 업데이트
- 위 과정을 반복해 Loss를 최소화하는 W, b 찾기
- 람다항이 있다면 L2 정규화
- compute_cost Func
- BCE(Binary Cross-Entropy) 함수로 Loss 계산
- Loss를 합쳐 전체 Cost 비용 계산
- 람다항이 있다면 L2 정규화
- sigmoid Func
- 선형결합의 결과를 0~1 사이 출력으로 변환
- compute_gradient Func
- 순전파, 역전파를 진행
위 epochs 만큼의 순전파, 역전파 과정이 끝나면 gradient_descent Func.의 W, b는 경사하강법에 의해 Loss를 줄이는 방향으로 업데이트됩니다. 😀
|
| [경사하강법이 W를 업데이트하는 과정 예시] |
5. 예측
이제 학습이 끝나면 w, b는 주어진 입력에서 Loss를 최소화하는 값으로 업데이트 됩니다.
모든 머신러닝, 딥러닝의 목적은 바로 여기에 있습니다.
남은것은 X_test를 4, 6, 8시간 등으로 넣어보며 합격선에 대한 결정경계를 잘 학습했는지 확인하면 끝입니다.
def predict(X, W, b):
z = W*X+b
return sigmoid(z)
# predict
X_test = 6
y_pred = predict(X_test, W_final, b_final)
result = 'Pass' if y_pred >=0.5 else 'Fail'
print(f'X_test={X_test}, y_pred={y_pred:.2f}, Result={result}')
학습이 진행됨에 따라 Cost가 감소하는 것을 확인할 수 있으며 이는 w, b가 업데이트되며 손실을 줄이는 과정입니다.
|
| [epoch 진행 과정, 예측결과] |
마지막에 6시간 공부하면 합격~ 성공적으로 잘 학습되었음을 확인가능합니다.
6. 시각화
아래 코드를 추가하면 학습의 결과물을 시각화 가능합니다.
# 그래프 설정
import matplotlib.pyplot as plt
X_range = np.linspace(0, 10, 100)
y_range = predict(X_range, W_final, b_final)
plt.figure(figsize=(10, 6))
plt.scatter(X, y, color='red', s=100, label='Input', zorder=5)
# 모델이 예측한 시그모이드 곡선 (파란 선)
plt.plot(X_range, y_range, color='blue', linewidth=2, label='Logistic Curve')
# 결정 경계선(확률 0.5 지점)
plt.axhline(y=0.5, color='gray', linestyle='--', label='Threshold (0.5)')
plt.axvline(x=-b/W if W != 0 else 0, color='green', linestyle=':', label='Decision Boundary')
# 테스트 포인트 (5시간 지점 표시)
y_test = predict(X_test, W_final, b_final)
plt.scatter(X_test, y_test, color='orange', marker='x', s=150, linewidth=3, label=f'X={X_test} (Prob={y_test:.2f})', zorder=6)
# 그래프 범례
plt.title('Logistic Regression: Study Hour vs Pass,Fail', fontsize=15)
plt.xlabel('Study Hours (X)', fontsize=12)
plt.ylabel('Pro. Passing (y_hat)', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
7. 전체 코드
학습에 사용된 전체 코드이며, 위에서 설명한 logistic_func.py 가 필요합니다.
import numpy as np
X = np.array([1, 3, 5, 7, 9])
y = np.array([0,0,0,1,1])
W = 0
b = 0
lr = 0.01
epochs = 3000
_lambda = 0
from logistic_func import gradient_descent, sigmoid
W_final, b_final = gradient_descent(X, y, W, b, lr, epochs, _lambda)
def predict(X, W, b):
z = W*X+b
return sigmoid(z)
# predict
X_test = 6
y_pred = predict(X_test, W_final, b_final)
result = 'Pass' if y_pred >=0.5 else 'Fail'
print(f'X_test={X_test}, y_pred={y_pred:.2f}, Result={result}')
# 그래프 설정
import matplotlib.pyplot as plt
X_range = np.linspace(0, 10, 100)
y_range = predict(X_range, W_final, b_final)
plt.figure(figsize=(10, 6))
plt.scatter(X, y, color='red', s=100, label='Input', zorder=5)
# 모델이 예측한 시그모이드 곡선 (파란 선)
plt.plot(X_range, y_range, color='blue', linewidth=2, label='Logistic Curve')
# 결정 경계선(확률 0.5 지점)
plt.axhline(y=0.5, color='gray', linestyle='--', label='Threshold (0.5)')
plt.axvline(x=-b/W if W != 0 else 0, color='green', linestyle=':', label='Decision Boundary')
# 테스트 포인트 (5시간 지점 표시)
y_test = predict(X_test, W_final, b_final)
plt.scatter(X_test, y_test, color='orange', marker='x', s=150, linewidth=3, label=f'X={X_test} (Prob={y_test:.2f})', zorder=6)
# 그래프 범례
plt.title('Logistic Regression: Study Hour vs Pass,Fail', fontsize=15)
plt.xlabel('Study Hours (X)', fontsize=12)
plt.ylabel('Pro. Passing (y_hat)', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
보너스 : 수식 정리
로지스틱 회귀를 진행하며 작성된 코드를 수식으로 정리해두었습니다.
1. 가설함수 (Hypothesis Function)
로지스틱 회귀는 입력(x), 가중치(w)를 곱하고 편향(b)를 더한 선형 결합 결과인 $z$를 시그모이드(Sigmoid) 함수에 통과시켜 0과 1 사이의 확률값으로 변환합니다.
- 선형결합
$$z = \mathbf{w}^T\mathbf{x} + b$$
- 시그모이드 함수 (Sigmoid Function)
$$\sigma(z) = \frac{1}{1 + e^{-z}}$$
- 최종 가설 (Model), 위 둘 합침
2. 손실 함수 (Loss Function)
로지스틱 회귀에서는 평균 제곱 오차(MSE) 대신 교차 엔트로피(Cross-Entropy) 손실 함수를 사용합니다.
이는 로그 함수를 이용해 정답과 예측값 사이의 오차를 극대화하여 학습 효율을 높이기 위함입니다.
- 손실함수, 바이너리 교차 엔트로피 (Binary Cross-Entropy)
(여기서 $y$는 실제 라벨, $\hat{y}$는 모델의 예측값 $H(\mathbf{x})$입니다.)
- 비용함수, (Cost Funcstion)
$$\begin{aligned}
J(\mathbf{w}, b) = -\frac{1}{m} \sum_{i=1}^{m} \big[ &y^{(i)} \log(\hat{y}^{(i)}) \\
& + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)}) \big]
\end{aligned}$$
- 비용함수, (Cost Funcstion)
3. 경사 하강법을 위한 미분 (Derivatives)
가중치($\mathbf{w}$)와 편향($b$)을 업데이트하기 위해 손실 함수 $J$를 각각 편미분합니다.
체인 룰(Chain Rule)을 적용하면 놀랍게도 선형 회귀와 매우 유사한 깔끔한 형태로 정리됩니다.
-
가중치에 대한 편미분 ($\frac{\partial J}{\partial \mathbf{w}}$)
-
편향에 대한 편미분 ($\frac{\partial J}{\partial b}$)
4. 파라미터 업데이트 (Update Rule)
구해진 기울기에 학습률(Learning Rate, $\alpha$)을 곱해 파라미터를 반복적으로 업데이트합니다.
이상으로 모든 설명을 마칩니다. 감사합니다.



댓글
댓글 쓰기