Logistic Regression (로지스틱 회귀)¶
개요¶
문제 정의¶
이진 분류 문제에서 클래스 소속 확률을 추정하는 판별 모델:
- 입력 특성의 선형 결합을 시그모이드 함수로 변환
- 출력: 클래스 1에 속할 확률 P(Y=1|X)
- 결정 경계가 선형인 선형 분류기
핵심 아이디어¶
로지스틱 함수 (Sigmoid):
선형 회귀의 출력을 [0, 1] 확률로 변환:
Odds와 Log-odds:
- Log-odds가 선형이므로 해석이 용이
- 계수 w_i 증가 -> Odds가 exp(w_i)배 증가
알고리즘/수식¶
모델¶
손실 함수 (Binary Cross-Entropy)¶
여기서 p_i = P(Y=1|X=x_i)
정규화¶
| 종류 | 손실 함수 | 특징 |
|---|---|---|
| L2 (Ridge) | L + lambda * ||w||_2^2 | 계수 축소, 희소성 없음 |
| L1 (Lasso) | L + lambda * ||w||_1 | 희소 해, 특성 선택 |
| Elastic Net | L + lambda_1 * ||w||_1 + lambda_2 * ||w||_2^2 | L1 + L2 조합 |
최적화¶
Gradient Descent:
dL/dw_j = 1/n * sum_{i=1}^{n} (p_i - y_i) * x_{ij}
dL/db = 1/n * sum_{i=1}^{n} (p_i - y_i)
w := w - alpha * dL/dw
b := b - alpha * dL/db
실제로는 LBFGS, Newton-CG, SAG, SAGA 등 고급 최적화 알고리즘 사용.
다중 클래스 확장¶
One-vs-Rest (OvR): - K개 클래스에 대해 K개의 이진 분류기 학습 - 가장 높은 확률의 클래스 선택
Multinomial (Softmax):
시간 복잡도¶
| 단계 | 복잡도 |
|---|---|
| 학습 | O(n * d * iter) |
| 예측 | O(d) per sample |
하이퍼파라미터 가이드¶
| 파라미터 | 설명 | 권장 범위 | 기본값 |
|---|---|---|---|
| C | 정규화 강도 (1/lambda) | 0.001 ~ 1000 | 1.0 |
| penalty | 정규화 종류 | 'l1', 'l2', 'elasticnet', 'none' | 'l2' |
| solver | 최적화 알고리즘 | 'lbfgs', 'liblinear', 'saga' | 'lbfgs' |
| max_iter | 최대 반복 횟수 | 100 ~ 10000 | 100 |
| class_weight | 클래스 가중치 | 'balanced' 또는 dict | None |
Solver 선택 가이드:
| Solver | L1 | L2 | Multinomial | 대규모 | 특징 |
|---|---|---|---|---|---|
| lbfgs | X | O | O | O | 기본, 범용 |
| liblinear | O | O | X | X | 소규모, L1 |
| saga | O | O | O | O | 대규모, L1+다중 |
| newton-cg | X | O | O | O | 뉴턴법 |
| sag | X | O | O | O | 대규모, L2 |
Python 코드 예시¶
기본 사용법¶
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import (accuracy_score, precision_score, recall_score,
f1_score, roc_auc_score, classification_report,
confusion_matrix)
import matplotlib.pyplot as plt
# 데이터 로드
data = load_breast_cancer()
X, y = data.data, data.target
# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 스케일링
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 모델 학습
model = LogisticRegression(
C=1.0,
penalty='l2',
solver='lbfgs',
max_iter=1000,
random_state=42
)
model.fit(X_train_scaled, y_train)
# 예측
y_pred = model.predict(X_test_scaled)
y_prob = model.predict_proba(X_test_scaled)[:, 1]
# 평가
print("=== Logistic Regression Performance ===")
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred):.4f}")
print(f"Recall: {recall_score(y_test, y_pred):.4f}")
print(f"F1 Score: {f1_score(y_test, y_pred):.4f}")
print(f"ROC-AUC: {roc_auc_score(y_test, y_prob):.4f}")
print("\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=data.target_names))
계수 해석¶
# 특성 중요도 (계수 크기)
feature_importance = pd.DataFrame({
'feature': data.feature_names,
'coefficient': model.coef_[0],
'abs_coefficient': np.abs(model.coef_[0]),
'odds_ratio': np.exp(model.coef_[0])
}).sort_values('abs_coefficient', ascending=False)
print("\nTop 10 Important Features:")
print(feature_importance.head(10).to_string(index=False))
# Odds ratio 해석
# odds_ratio > 1: 해당 특성 증가 -> 클래스 1 확률 증가
# odds_ratio < 1: 해당 특성 증가 -> 클래스 1 확률 감소
정규화 비교¶
from sklearn.model_selection import cross_val_score
penalties = ['l1', 'l2', 'elasticnet', None]
results = {}
for penalty in penalties:
if penalty == 'elasticnet':
model = LogisticRegression(
penalty=penalty, solver='saga', l1_ratio=0.5,
max_iter=5000, random_state=42
)
elif penalty == 'l1':
model = LogisticRegression(
penalty=penalty, solver='saga',
max_iter=5000, random_state=42
)
else:
model = LogisticRegression(
penalty=penalty, solver='lbfgs',
max_iter=5000, random_state=42
)
scores = cross_val_score(model, X_train_scaled, y_train, cv=5)
model.fit(X_train_scaled, y_train)
n_nonzero = np.sum(model.coef_ != 0) if penalty else X.shape[1]
results[str(penalty)] = {
'cv_mean': scores.mean(),
'cv_std': scores.std(),
'n_features': n_nonzero
}
print("\n=== Regularization Comparison ===")
print(f"{'Penalty':<15} {'CV Score':>12} {'Std':>8} {'Features':>10}")
print("-" * 50)
for penalty, metrics in results.items():
print(f"{penalty:<15} {metrics['cv_mean']:>12.4f} {metrics['cv_std']:>8.4f} {metrics['n_features']:>10}")
하이퍼파라미터 튜닝¶
from sklearn.model_selection import GridSearchCV
param_grid = {
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'penalty': ['l1', 'l2'],
'solver': ['saga']
}
grid_search = GridSearchCV(
LogisticRegression(max_iter=5000, random_state=42),
param_grid,
cv=5,
scoring='roc_auc',
n_jobs=-1
)
grid_search.fit(X_train_scaled, y_train)
print(f"\nBest Parameters: {grid_search.best_params_}")
print(f"Best CV Score: {grid_search.best_score_:.4f}")
ROC Curve 시각화¶
from sklearn.metrics import roc_curve, auc
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2,
label=f'ROC curve (AUC = {roc_auc:.3f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc='lower right')
plt.savefig('logistic_roc.png', dpi=150)
plt.show()
언제 쓰나?¶
적합한 상황: - 클래스 간 선형 분리 가능한 경우 - 확률 추정이 필요한 경우 (신용평가, 질병 진단) - 계수 해석이 중요한 경우 (변수 영향력 분석) - 베이스라인 모델로 빠르게 테스트할 때 - 대규모 데이터 (수백만 샘플)
부적합한 상황: - 비선형 결정 경계가 필요한 경우 - 특성 간 복잡한 상호작용이 있는 경우 - 이미지, 텍스트 등 고차원 비정형 데이터
장단점¶
| 장점 | 단점 |
|---|---|
| 해석 용이 (Odds ratio) | 선형 결정 경계만 학습 |
| 확률 출력 | 특성 간 상호작용 학습 불가 |
| 학습/예측 빠름 | 이상치에 민감 |
| 정규화로 과적합 방지 | 다중공선성 문제 |
| 대규모 데이터 확장 가능 | 클래스 불균형 시 성능 저하 |
관련 논문/참고¶
- Cox, D.R. (1958). "The regression analysis of binary sequences". Journal of the Royal Statistical Society.
- Hosmer, D.W., & Lemeshow, S. (2000). "Applied Logistic Regression". Wiley.
- scikit-learn documentation: https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression