머신러닝 모델링의 후반부는 "평가(Evaluation)"와 "최적화(Optimization)"의 싸움입니다. 단순히 fit()하고 predict()하는 것을 넘어, 비즈니스(의료) 로직을 코드에 녹여내는 과정을 실습해 봅니다.
오늘의 시나리오: "응급실 총 환자 수(Total Count)를 예측하라" 하지만 우리는 알고 있습니다. 긴급하게 실려오는 환자(Ambulance)와 제 발로 걸어오는 환자는 발생하는 패턴이 완전히 다르다는 것을요!
1. Metric: 평가 지표 정의 (RMSLE)
먼저, 우리가 목표로 하는 점수판을 정의합니다. 앞서 배운 RMSLE(로그 오차)를 코드로 구현하고, 사이킷런 모델들이 알아들을 수 있도록 scorer로 만듭니다.
import numpy as np
from sklearn.metrics import make_scorer
# 1. RMSLE 계산 함수 (의료 현장의 과소평가 방지용)
def get_rmsle(y_actual, y_pred):
# 예측값이 음수가 나오면 로그 변환 시 에러가 나므로 0으로 처리
y_pred = np.maximum(0, y_pred)
# log(x+1) 변환
log_true = np.log1p(y_actual)
log_pred = np.log1p(y_pred)
# 오차 제곱의 평균 -> 루트
squared_error = (log_true - log_pred) ** 2
return np.sqrt(np.mean(squared_error))
# 2. Scikit-learn용 Scorer 생성
# greater_is_better=False: 오차(Error)는 작을수록 좋다는 뜻
rmsle_scorer = make_scorer(get_rmsle, greater_is_better=False)
print("Metric 준비 완료: RMSLE Scorer")
2. Model Evaluation: 타겟 분리 전략 (Target Splitting)
이 부분이 오늘 실습의 하이라이트입니다. 응급실 데이터에서 성격이 다른 두 그룹을 따로 예측해서 합치는 전략을 사용합니다.
- Ambulance (구급차): 교통사고, 심정지 등 중증 환자. 시간대나 날씨 영향이 큼.
- Walk-in (도보): 경증 환자, 외래 진료가 끝난 야간/주말에 몰림.
[Code] 분할 예측 함수 구현
def predict_er_total(model, X_train, df_train, X_test):
"""
모델 하나를 받아서 '구급차'와 '도보'를 각각 학습시키고,
최종적으로 두 예측값을 더해서 반환하는 함수
"""
# --- 1. 구급차(Ambulance) 예측 ---
# 타겟을 로그 변환하여 학습
y_train_amb = np.log1p(df_train['ambulance_count'])
model.fit(X_train, y_train_amb)
# 예측 후 지수 변환(복원)
pred_amb_log = model.predict(X_test)
pred_amb = np.expm1(pred_amb_log)
# --- 2. 도보(Walk-in) 예측 ---
# 타겟을 로그 변환하여 학습
y_train_walk = np.log1p(df_train['walkin_count'])
model.fit(X_train, y_train_walk)
# 예측 후 지수 변환(복원)
pred_walk_log = model.predict(X_test)
pred_walk = np.expm1(pred_walk_log)
# --- 3. 최종 합산 (Total = Ambulance + Walk-in) ---
total_pred = pred_amb + pred_walk
return total_pred
Why? 뭉뚱그려 예측하는 것보다, 각각의 패턴을 전문적으로 학습한 뒤 합치는 것이 훨씬 정확도가 높습니다.
3. Model Evaluation Test: 모델별 성능 검증
이제 준비된 Metric과 전략을 바탕으로 실제 모델들을 테스트해 봅니다. 가상의 데이터를 만들어 실험해 보겠습니다.
① 데이터 준비 (Mock Data)
import pandas as pd
# 가상의 데이터 생성 (1000시간)
data_size = 1000
df_er = pd.DataFrame({
'hour': np.random.randint(0, 24, data_size),
'dayofweek': np.random.randint(0, 7, data_size),
'temp': np.random.uniform(-10, 35, data_size)
})
# 타겟 데이터 생성 (패턴이 다름을 가정)
# 구급차: 무작위성이 강함
df_er['ambulance_count'] = np.random.randint(0, 10, data_size)
# 도보: 시간대 영향을 많이 받음
df_er['walkin_count'] = df_er['hour'].apply(lambda x: np.random.randint(10, 50) if x > 18 else np.random.randint(0, 10))
# 총 환자 수
df_er['total_count'] = df_er['ambulance_count'] + df_er['walkin_count']
# 학습/테스트 분리
train_size = 800
df_train = df_er.iloc[:train_size]
df_test = df_er.iloc[train_size:]
X_train = df_train[['hour', 'dayofweek', 'temp']]
X_test = df_test[['hour', 'dayofweek', 'temp']]
② 모델 테스트: Linear Regression vs Random Forest
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
# 1. 선형 회귀 (Linear Regression) 테스트
lr_model = LinearRegression()
lr_pred = predict_er_total(lr_model, X_train, df_train, X_test)
# 성능 평가 (RMSLE)
lr_score = get_rmsle(df_test['total_count'], lr_pred)
print(f"Linear Regression RMSLE: {lr_score:.4f}")
# 2. 랜덤 포레스트 (Random Forest) 테스트
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_pred = predict_er_total(rf_model, X_train, df_train, X_test)
# 성능 평가 (RMSLE)
rf_score = get_rmsle(df_test['total_count'], rf_pred)
print(f" Random Forest RMSLE : {rf_score:.4f}")

[실행 결과 해석] 어떤 모델이 '응급실'을 더 잘 이해했을까?
위 코드를 실행하면 다음과 같은 결과가 나옵니다 (이미지 참조).
- Linear Regression RMSLE: 0.6256
- Random Forest RMSLE: 0.5817
Insight: 왜 Random Forest가 이겼을까?
- 점수의 의미 (Lower is Better):
- RMSLE는 '오차'이므로 작을수록 좋은 모델입니다.
- 랜덤 포레스트(0.5817)가 선형 회귀(0.6256)보다 오차가 더 적으므로 성능이 우수합니다.
- 데이터의 비선형성 (Non-linearity):
- 선형 회귀: "시간이 지날수록 환자가 늘어난다" 같은 단순한 직선 관계만 찾습니다.
- 랜덤 포레스트: "오전 9시엔 확 늘었다가, 오후 2시에 줄었다가, 밤 10시에 다시 늘어난다" 같은 구불구불한(비선형) 패턴을 잘 잡아냅니다.
- 응급실 데이터는 시간대별로 변동이 심하기 때문에(Peak Time 존재), 직선보다는 나무(Tree) 모델이 훨씬 유리합니다.
결론: 의료 시계열 데이터에서는 단순한 선형 모델보다, 복잡한 패턴을 학습할 수 있는 트리 기반 모델(Random Forest, XGBoost)을 사용하고, 타겟을 세분화(Splitting)하는 전략이 승리의 열쇠입니다.
팁
- Metric의 역할: get_rmsle를 통해 모델이 얼마나 '비율적으로' 잘 맞췄는지 검증했습니다. 점수가 낮을수록 좋습니다.
- Evaluation 전략: 단순히 total_count를 학습시킨 것보다, ambulance와 walkin을 따로 학습시켜 더한 것이, 각 환자군의 특성(심야 시간 급증 등)을 더 잘 반영합니다.
- 모델 비교: 보통 시계열 패턴이 복잡한 의료 데이터에서는 단순한 Linear Regression보다 Random Forest나 XGBoost 같은 트리 기반 모델이 더 좋은 성능(낮은 RMSLE)을 보여줍니다.
1. Metric의 핵심 정의: "우리가 합의한 성공의 기준"
- 학교 시험: 학생이 문제를 풀면, 선생님이 "100점 만점에 몇 점인지" 매기죠? 여기서 '점수 계산법'이 바로 Metric입니다.
- 병원: 의사 선생님이 환자 상태를 볼 때, "건강하다"라고 뭉뚱그려 말하지 않죠? "혈압 120/80, 당화혈색소 5.6" 같은 숫자 기준을 봅니다. 이 기준이 바로 Metric입니다.
즉, Metric이란? "내 AI 모델이 일을 잘했니?"라고 물었을 때, "응, 90점이야" 또는 "아니, 오차가 10명이야"라고 대답해주는 숫자입니다.
2. 왜 그냥 '점수'라고 안 하고 Metric이라고 할까요?
머신러닝에서는 "공부하는 방법(Loss Function)"과 "성적 매기는 방법(Metric)"이 다를 수 있기 때문입니다. 이 차이가 헷갈리지만 중요합니다.
- 손실 함수 (Loss Function):
- 누가 봄? 모델(기계)이 봅니다.
- 목적: 정답을 맞히기 위해 학습(미분)할 때 쓰는 수학 공식입니다. (예: logloss, mse)
- 비유: 학생이 공부할 때 "틀린 문제 오답 노트 쓰면서 외우는 과정".
- 평가 지표 (Metric):
- 누가 봄? 사람(분석가, 의사)이 봅니다.
- 목적: 비즈니스적으로 이 모델이 쓸모 있는지 판단하는 기준입니다. (예: Accuracy, RMSLE, F1-Score)
- 비유: 기말고사 성적표. "너 공부 어떻게 했든 상관없고, 그래서 몇 점인데?"
3. Metric의 종류 (상황별 채점표)
상황에 따라 채점 기준(Metric)을 바꿔야 합니다.
| 문제 종류 | Metric 예시 | 의료 데이터 비유 |
| 희귀 (숫자 예측) | MAE, RMSE, RMSLE | "응급실 환자 수 예측 시, 실제랑 몇 명 차이 나냐?" |
| 분류 (O/X 맞추기) | Accuracy (정확도) | "질환자 100명 중 몇 명 맞췄냐?" |
| 분류(심화) | Recall (재현율) | "실제 질병환자를 놓치지 않고 다 찾았냐?" (가장 중요) |
요약
Metric은 모델에게 주는 "시험 채점표"입니다. 분석하는 사람이 채점표를 RMSE로 할지, RMSLE로 할지, 정확도로 할지 상황에 맞게 골라주는 출제위원 역할을 하시는 것
'Study Note > Machine Learning' 카테고리의 다른 글
| 의료 AI의 협진 시스템: Random Forest와 XGBoost 섞어 쓰기 (Blending) (0) | 2025.12.29 |
|---|---|
| 응급실 예측 2편: 로그의 마법과 5대 회귀 모델 총출동 (Linear부터 XGBoost까지) (0) | 2025.12.24 |
| 응급실은 언제 붐빌까? : 시계열 회귀, 로그(Log)의 마법, 그리고 4대 평가지표 (0) | 2025.12.23 |
| 수치 예측의 정석과 과적합을 막는 방패: 회귀(Regression)와 규제(Regularization) 정복하기 (0) | 2025.12.22 |
| 정확도(Accuracy)의 함정에서 탈출하기: OOB Score와 ROC-AUC 완벽 해설 (0) | 2025.12.22 |