Ch5. 머신러닝 기초 — 실전 프로젝트와 모델 평가
ML 프로젝트 전체 파이프라인
실제 머신러닝 프로젝트는 모델 학습만이 전부가 아닙니다. 문제 정의부터 배포·모니터링까지 체계적인 파이프라인이 필요합니다.
ML 프로젝트 전체 흐름:
1. 문제 정의 (Problem Definition)
└─ 비즈니스 목표 → ML 문제 변환
└─ 지표(Metric) 결정
2. 데이터 수집 (Data Collection)
└─ 내부 DB, 공개 데이터셋, 크롤링, 레이블링
3. 탐색적 데이터 분석 (EDA)
└─ 분포 확인, 결측치·이상치 탐지, 특성 간 상관관계
4. 전처리 · 특성 공학 (Preprocessing & Feature Engineering)
└─ 결측치 처리, 스케일링, 인코딩, 파생 변수 생성
5. 모델 학습 및 평가 (Modeling & Evaluation)
└─ 베이스라인 → 실험 → 교차검증 → 하이퍼파라미터 튜닝
6. 모델 배포 (Deployment)
└─ API 서버, 배치 처리, 엣지 디바이스
7. 모니터링 (Monitoring)
└─ 데이터 드리프트, 성능 저하 감지, 재학습
탐색적 데이터 분석 (EDA)
EDA는 모델링 전에 데이터를 깊이 이해하는 과정입니다. “데이터를 먼저 알아야 좋은 모델을 만들 수 있다”는 원칙에서 출발합니다.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('data.csv')
# 기본 정보 확인
print(df.shape) # 행·열 수
print(df.dtypes) # 각 열의 데이터 타입
print(df.describe()) # 수치형 특성 기술통계
# 결측치 확인
print(df.isnull().sum())
# 타깃 분포 확인 (분류 문제)
print(df['label'].value_counts())
# 특성 간 상관관계
plt.figure(figsize=(10, 8))
sns.heatmap(df.corr(), annot=True, cmap='coolwarm')
plt.show()
결측치 처리 전략
결측치 처리 방법:
1. 삭제 (Deletion)
→ 행 삭제: 결측 비율이 낮고 데이터 충분할 때
→ 열 삭제: 결측 비율이 50% 이상일 때
2. 단순 대체 (Imputation)
→ 수치형: 평균, 중앙값, 최빈값
→ 범주형: 최빈값, 별도 카테고리("Unknown")
3. 고급 대체 (Advanced Imputation)
→ KNN Imputation: 유사 샘플의 값으로 대체
→ 회귀 대체: 다른 특성으로 결측값 예측
→ MICE: 다중 대체법
교차검증 (Cross-Validation)
단순 훈련/테스트 분리는 분리 방식에 따라 평가 결과가 크게 달라질 수 있습니다. 교차검증은 이 문제를 해결하여 모델의 일반화 성능을 더 신뢰성 있게 추정합니다.
K-Fold 교차검증
K=5 Fold 교차검증 과정:
전체 데이터를 5개 폴드로 균등 분할
Fold 1: [검증] [훈련] [훈련] [훈련] [훈련] → 성능 점수 1
Fold 2: [훈련] [검증] [훈련] [훈련] [훈련] → 성능 점수 2
Fold 3: [훈련] [훈련] [검증] [훈련] [훈련] → 성능 점수 3
Fold 4: [훈련] [훈련] [훈련] [검증] [훈련] → 성능 점수 4
Fold 5: [훈련] [훈련] [훈련] [훈련] [검증] → 성능 점수 5
최종 성능 = 5개 점수의 평균 ± 표준편차
from sklearn.model_selection import cross_val_score, KFold
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='f1_macro')
print(f"F1 평균: {scores.mean():.3f} ± {scores.std():.3f}")
Stratified K-Fold
클래스 불균형 데이터에서는 각 폴드의 클래스 비율을 원본과 동일하게 유지하는 Stratified K-Fold를 사용합니다.
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 각 폴드마다 클래스 비율 보존
scores = cross_val_score(model, X, y, cv=skf, scoring='f1')
하이퍼파라미터 튜닝
모델의 **하이퍼파라미터(학습 전에 사람이 설정하는 값)**를 최적화하여 모델 성능을 향상시키는 과정입니다.
파라미터 vs 하이퍼파라미터:
파라미터: 모델이 학습을 통해 스스로 결정 (가중치 w, 편향 b)
하이퍼파라미터: 사람이 학습 전에 설정 (학습률 α, 트리 깊이, 필터 수 등)
그리드 서치 (Grid Search)
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 5, 10, 20],
'min_samples_split': [2, 5, 10]
}
# 총 3 × 4 × 3 = 36가지 조합을 5-Fold CV로 평가
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring='f1_macro',
n_jobs=-1 # 모든 CPU 코어 사용
)
grid_search.fit(X_train, y_train)
print(f"최적 파라미터: {grid_search.best_params_}")
print(f"최고 CV 점수: {grid_search.best_score_:.3f}")
단점: 조합 수가 지수적으로 증가 → 하이퍼파라미터가 많으면 계산 비용 폭발
랜덤 서치 (Random Search)
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_dist = {
'n_estimators': randint(50, 500),
'max_depth': [None] + list(range(3, 30)),
'min_samples_split': randint(2, 20),
'max_features': uniform(0.1, 0.9) # 연속 분포
}
# 100회 랜덤 샘플링
random_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_dist,
n_iter=100, # 시도할 조합 수
cv=5,
scoring='f1_macro',
n_jobs=-1,
random_state=42
)
random_search.fit(X_train, y_train)
장점: 그리드 서치보다 같은 예산에서 더 넓은 탐색 공간 커버 가능
앙상블 학습 심화
배깅 (Bagging)
핵심: 동일 알고리즘을 서로 다른 서브셋으로 학습 → 다수결/평균
대표 알고리즘: 랜덤포레스트
장점: 분산 감소, 병렬 학습 가능
단점: 편향 감소 효과 적음
부스팅 (Boosting)
핵심: 이전 모델의 실수에 집중하여 순차적으로 모델 추가
AdaBoost:
→ 잘못 분류된 샘플에 더 큰 가중치 부여
→ 다음 모델이 어려운 샘플에 집중
Gradient Boosting:
→ 잔차(Residual)를 예측하는 모델을 순차 추가
→ 학습률(η)로 과적합 제어
장점: 편향·분산 모두 감소, 높은 성능
단점: 순차 학습 → 병렬 불가, 과적합 위험
XGBoost · LightGBM 개요
XGBoost (eXtreme Gradient Boosting)
Gradient Boosting의 최적화 구현체
주요 특징:
- 정규화 항 내장 (L1, L2) → 과적합 제어
- 결측치 자동 처리
- 병렬 처리 지원 (열 기반 병렬화)
- 조기 종료(Early Stopping) 지원
주요 하이퍼파라미터:
n_estimators: 트리 수 (100~1000)
learning_rate: 학습률 (0.01~0.3)
max_depth: 트리 깊이 (3~10)
subsample: 행 샘플링 비율 (0.5~1.0)
colsample_bytree: 열 샘플링 비율 (0.5~1.0)
from xgboost import XGBClassifier
xgb = XGBClassifier(
n_estimators=300,
learning_rate=0.05,
max_depth=6,
subsample=0.8,
colsample_bytree=0.8,
eval_metric='logloss',
random_state=42
)
xgb.fit(
X_train, y_train,
eval_set=[(X_val, y_val)],
early_stopping_rounds=20, # 20 라운드 개선 없으면 중단
verbose=50
)
LightGBM
Microsoft가 개발한 초고속 Gradient Boosting
XGBoost 대비 장점:
- Leaf-wise 트리 성장 → 더 깊은 트리, 낮은 손실
- GOSS 샘플링: 기울기 큰 샘플 우선 사용
- 범주형 특성 직접 처리 (인코딩 불필요)
- 대용량 데이터(수백만 행)에서 훨씬 빠름
단점: 소규모 데이터에서 과적합 위험
모델 저장과 배포 기초
모델 직렬화 (Serialization)
import joblib
import pickle
# joblib으로 저장 (sklearn 모델에 권장)
joblib.dump(model, 'random_forest_v1.joblib')
# 로드
loaded_model = joblib.load('random_forest_v1.joblib')
# 예측
predictions = loaded_model.predict(X_new)
Flask를 이용한 간단한 API 서버
from flask import Flask, request, jsonify
import joblib
import numpy as np
app = Flask(__name__)
model = joblib.load('model.joblib')
@app.route('/predict', methods=['POST'])
def predict():
data = request.json
features = np.array(data['features']).reshape(1, -1)
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0].tolist()
return jsonify({
'prediction': int(prediction),
'probability': probability
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
실전 케이스스터디: 고객 이탈 예측
문제 정의: 통신사 고객 데이터로 다음 달 이탈 여부 예측 (이진 분류)
데이터 특성:
행: 고객 7,000명
특성: 가입 기간, 월 요금, 데이터 사용량, 고객센터 문의 횟수 등 20개
타깃: 이탈(1) / 유지(0), 클래스 비율 = 85:15 (불균형)
파이프라인:
1. EDA → 이탈 고객의 고객센터 문의 횟수 평균 2.3배 높음 발견
2. 전처리 → 결측치 중앙값 대체, 표준화
3. 불균형 처리 → SMOTE 오버샘플링
4. 베이스라인 → 로지스틱회귀 F1=0.62
5. 개선 → LightGBM F1=0.81
6. 튜닝 → RandomizedSearchCV → F1=0.83
7. 특성 중요도 → "고객센터 문의 횟수", "마지막 요금 인상 후 기간" 최중요
8. 배포 → 매일 배치로 이탈 위험 고객 Top 100명 마케팅팀 전달
핵심 개념 카드
K-Fold 교차검증 목적 ★★★★★ : 단순 훈련/테스트 분리의 분산 문제 해결. K번 학습·검증 반복 → 평균 성능이 더 신뢰성 있는 추정치. 암기 포인트: K=5 → 데이터 80% 훈련, 20% 검증을 5번 반복
그리드서치 vs 랜덤서치 ★★★★☆ : 그리드서치=모든 조합 탐색(완전하지만 느림), 랜덤서치=랜덤 샘플링(빠르고 넓은 탐색). 대부분 랜덤서치가 효율적. 암기 포인트: 하이퍼파라미터 5개 이상이면 랜덤서치
배깅 vs 부스팅 ★★★★★ : 배깅=병렬·독립 학습(분산 감소), 부스팅=순차·오류 집중 학습(편향+분산 감소). XGBoost/LightGBM=부스팅 계열. 암기 포인트: Random Forest=배깅, XGBoost=부스팅
XGBoost vs LightGBM ★★★☆☆ : XGBoost=안정적·정확, LightGBM=초고속·대용량. 소규모 데이터는 XGBoost, 수백만 행은 LightGBM. 암기 포인트: Light = 빠르다, 소규모에서 과적합 주의
모델 배포의 핵심 ★★★☆☆ : 훈련된 모델을 joblib/pickle로 직렬화 후 API(Flask/FastAPI)로 서빙. 프로덕션에서는 데이터 드리프트 모니터링 필수. 암기 포인트: 모델 저장 → 로드 → predict() 호출 → JSON 응답
실전 퀴즈
Q1. K-Fold 교차검증에서 K를 매우 크게(K=n, Leave-One-Out) 설정하면 어떤 장단점이 있는가?
장점: 각 검증 폴드가 단 1개 샘플이므로 훈련 데이터를 최대한 활용할 수 있어 편향이 낮습니다. 단점: n번의 학습을 반복해야 하므로 계산 비용이 매우 크고, 각 폴드의 검증 결과가 단일 샘플에 의존하여 분산이 높습니다. 일반적으로 K=5 또는 K=10이 편향-분산 트레이드오프 면에서 실용적입니다.
Q2. 랜덤서치가 그리드서치보다 효율적인 이유를 설명하라.
하이퍼파라미터 중요도는 균등하지 않습니다. 일부 파라미터는 성능에 거의 영향을 미치지 않지만 그리드서치는 그 파라미터의 모든 값 조합을 다 시도합니다. 반면 랜덤서치는 연속 분포에서 무작위 샘플링하여 중요한 파라미터의 다양한 값 조합을 더 폭넓게 탐색합니다. Bergstra & Bengio (2012) 연구에 따르면 같은 예산에서 랜덤서치가 그리드서치보다 더 좋은 파라미터를 찾는 경우가 많습니다.
Q3. 앙상블에서 배깅은 분산을 줄이고 부스팅은 편향을 줄이는 이유는?
배깅: 서로 다른 서브셋으로 학습한 독립적 모델들의 예측을 평균·다수결로 집계합니다. 각 모델의 오류가 무작위적이면 평균 시 상쇄되어 분산이 감소합니다. 부스팅: 이전 모델이 틀린 부분(잔차)에 집중하여 순차적으로 모델을 추가합니다. 반복할수록 체계적으로 틀렸던 부분(편향)이 보완되어 편향이 감소합니다.
Q4. 모델을 배포한 후 시간이 지나면 성능이 저하되는 이유는 무엇인가?
데이터 드리프트(Data Drift) 때문입니다. 훈련 시 데이터의 분포와 실제 운영 환경에서 들어오는 데이터의 분포가 시간이 지나면서 달라집니다. 예를 들어 고객 행동 패턴 변화, 계절성, 신규 상품 출시 등으로 특성의 통계가 바뀌면 기존 모델의 예측이 부정확해집니다. 이를 감지하기 위해 입력 데이터 분포와 예측 성능을 지속적으로 모니터링하고, 성능이 임계값 이하로 떨어지면 재학습해야 합니다.
Q5. 고객 이탈 예측 모델에서 Accuracy보다 F1 Score를 주요 지표로 사용해야 하는 이유는?
이탈 고객이 15%에 불과한 불균형 데이터에서 모든 고객을 “유지”로 예측해도 Accuracy = 85%를 얻을 수 있습니다. 하지만 이 모델은 실제로 이탈할 고객을 하나도 탐지하지 못합니다. F1 Score는 정밀도(예측한 이탈 중 실제 이탈 비율)와 재현율(실제 이탈 중 잡아낸 비율)의 조화평균으로, 소수 클래스(이탈 고객) 예측 성능을 균형 있게 반영합니다. 비즈니스 목표가 이탈 고객을 찾아내는 것이므로 F1이 훨씬 적합합니다.
OIYO 편집부
Content Editor지식 인큐베이터이자 전문 콘텐츠 크리에이터. 경영, 경제, 법률 및 실생활에 유용한 실무/자격증 중심의 깊이 있는 정보를 연구하고 공유합니다.