🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

머신러닝 모델 비교와 선택 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 12. 17. · 7 Views

머신러닝 모델 비교와 선택 완벽 가이드

여러 머신러닝 모델을 동시에 학습하고 성능을 비교하여 최적의 모델을 선택하는 방법을 다룹니다. 혼동 행렬, ROC 곡선, GridSearchCV 등 실무에서 필수적인 모델 평가 기법을 초급자도 쉽게 이해할 수 있도록 설명합니다.


목차

  1. 여러_모델_동시_학습
  2. 성능_지표_비교
  3. 혼동_행렬_비교
  4. ROC_곡선과_AUC
  5. 모델_선택_기준
  6. 하이퍼파라미터_튜닝_소개
  7. GridSearchCV_활용

1. 여러 모델 동시 학습

데이터 분석팀에 입사한 지 한 달 된 김개발 씨는 고객 이탈 예측 프로젝트를 맡았습니다. 선배 박시니어 씨가 물었습니다.

"어떤 모델을 쓸 건가요?" 김개발 씨는 당황했습니다. 로지스틱 회귀, 랜덤 포레스트, SVM...

어떤 것을 선택해야 할까요?

여러 모델 동시 학습은 한 번에 다양한 알고리즘을 학습시켜 성능을 비교하는 것입니다. 마치 여러 요리사에게 같은 재료를 주고 누가 가장 맛있는 요리를 만드는지 비교하는 것과 같습니다.

이를 통해 데이터에 가장 적합한 모델을 객관적으로 선택할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.datasets import load_iris

# 데이터 준비
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 여러 모델을 딕셔너리로 관리합니다
models = {
    'Logistic Regression': LogisticRegression(max_iter=200),
    'Random Forest': RandomForestClassifier(n_estimators=100),
    'SVM': SVC(kernel='rbf')
}

# 모든 모델을 학습시킵니다
for name, model in models.items():
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    print(f"{name}: {score:.4f}")

김개발 씨는 첫 머신러닝 프로젝트를 앞두고 고민에 빠졌습니다. 회사 데이터로 고객 이탈을 예측해야 하는데, 어떤 알고리즘을 사용해야 할지 감이 오지 않았습니다.

선배 박시니어 씨가 커피를 들고 다가왔습니다. "김 대리, 하나만 골라서 하려고 해요?

그러면 안 되죠. 여러 모델을 동시에 돌려보고 비교해야 합니다." 김개발 씨는 고개를 갸웃했습니다.

"여러 개를 다 해봐야 하나요? 시간이 오래 걸리지 않을까요?" 여러 모델 동시 학습의 필요성 머신러닝 모델을 선택하는 것은 마치 신발을 고르는 것과 같습니다.

운동화, 구두, 샌들이 있을 때, 어떤 신발이 가장 편한지는 직접 신어봐야 알 수 있습니다. 마찬가지로 어떤 모델이 특정 데이터에 가장 잘 맞는지는 실제로 학습시켜봐야 알 수 있습니다.

과거에는 개발자들이 하나의 모델을 선택하고, 결과가 좋지 않으면 다른 모델로 바꿔가며 반복 작업을 했습니다. 이 과정은 지루하고 시간이 오래 걸렸습니다.

더 큰 문제는 일관성이 없다는 것이었습니다. 각 모델마다 다른 데이터 분할을 사용하면 공정한 비교가 어려웠습니다.

딕셔너리로 모델 관리하기 박시니어 씨가 코드를 보여줬습니다. "이렇게 딕셔너리에 모델들을 담아두면 깔끔하게 관리할 수 있어요." 코드의 핵심은 models 딕셔너리입니다.

여기에 비교하고 싶은 모든 모델을 담아둡니다. 모델 이름을 키로, 모델 객체를 값으로 저장하는 방식입니다.

그다음 for 반복문으로 각 모델을 순회하며 학습시킵니다. fit() 메서드로 학습하고, score() 메서드로 테스트 세트에서의 정확도를 측정합니다.

모든 모델이 똑같은 훈련 데이터와 테스트 데이터를 사용하므로 공정한 비교가 가능합니다. 같은 데이터로 공정하게 비교하기 중요한 점은 train_test_split()을 한 번만 호출한다는 것입니다.

모든 모델이 동일한 훈련 세트와 테스트 세트를 사용해야 공정한 비교가 됩니다. 만약 모델마다 다른 데이터 분할을 사용하면, 성능 차이가 모델 자체의 차이인지 데이터 분할의 차이인지 알 수 없습니다.

김개발 씨가 코드를 실행했습니다. 결과가 한눈에 들어왔습니다.

로지스틱 회귀는 0.9556, 랜덤 포레스트는 0.9778, SVM은 0.9778의 정확도를 보였습니다. "와, 이렇게 간단하게 비교할 수 있네요!" 김개발 씨가 감탄했습니다.

실무에서의 활용 실제 프로젝트에서는 보통 5개에서 10개 정도의 모델을 비교합니다. 로지스틱 회귀 같은 간단한 모델부터 시작해서, 트리 기반 모델, 서포트 벡터 머신, 신경망까지 다양하게 테스트합니다.

각 모델은 서로 다른 강점을 가지고 있습니다. 로지스틱 회귀는 빠르고 해석하기 쉽습니다.

랜덤 포레스트는 성능이 좋고 과적합에 강합니다. SVM은 고차원 데이터에서 강력합니다.

주의할 점 모델을 너무 많이 비교하는 것도 문제입니다. 20개, 30개의 모델을 무작정 돌리면 시간도 오래 걸리고, 결과 해석도 어려워집니다.

보통은 문제의 특성을 고려해서 5-7개 정도의 후보 모델을 선정하는 것이 좋습니다. 또한 random_state 파라미터를 설정해서 재현 가능한 결과를 얻는 것이 중요합니다.

동료와 결과를 공유하거나, 나중에 같은 실험을 재현할 때 필수적입니다. 정리 박시니어 씨가 말했습니다.

"이제 알겠죠? 하나만 골라서 하는 게 아니라, 여러 모델을 동시에 돌려보고 가장 좋은 것을 선택하는 거예요." 김개발 씨는 이제 자신 있게 프로젝트를 진행할 수 있었습니다.

여러 모델을 체계적으로 비교하는 방법을 배웠으니까요.

실전 팁

💡 - 모델을 딕셔너리로 관리하면 코드가 깔끔하고 확장하기 쉽습니다

  • 모든 모델에 동일한 train_test_split 결과를 사용해야 공정한 비교가 됩니다
  • random_state를 고정해서 재현 가능한 결과를 얻으세요

2. 성능 지표 비교

김개발 씨는 모델들의 정확도를 비교했지만, 뭔가 부족한 느낌이 들었습니다. 박시니어 씨가 물었습니다.

"정확도만 봤어요? 정밀도와 재현율도 확인해야죠." 김개발 씨는 또다시 당황했습니다.

성능 지표는 모델의 예측 품질을 다양한 각도에서 측정하는 수치입니다. 마치 학생을 평가할 때 시험 점수뿐만 아니라 출석률, 과제 제출률도 함께 보는 것과 같습니다.

정확도, 정밀도, 재현율, F1 점수를 함께 보면 모델의 진짜 성능을 파악할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.metrics import classification_report
import pandas as pd

# 모델 예측
results = {}
for name, model in models.items():
    # 예측 수행
    y_pred = model.predict(X_test)

    # 여러 지표 계산 (다중 클래스이므로 average='macro' 사용)
    results[name] = {
        'Accuracy': accuracy_score(y_test, y_pred),
        'Precision': precision_score(y_test, y_pred, average='macro'),
        'Recall': recall_score(y_test, y_pred, average='macro'),
        'F1 Score': f1_score(y_test, y_pred, average='macro')
    }

# 데이터프레임으로 보기 좋게 정리
df_results = pd.DataFrame(results).T
print(df_results.round(4))

김개발 씨는 자신의 결과 리포트를 박시니어 씨에게 보여줬습니다. "랜덤 포레스트가 정확도 97%로 가장 좋네요!" 박시니어 씨가 고개를 저었습니다.

"정확도만 보면 안 됩니다. 특히 우리 데이터는 클래스 불균형이 있잖아요." 김개발 씨는 클래스 불균형이 뭔지 몰랐습니다.

박시니어 씨가 설명을 시작했습니다. 정확도만으로는 부족한 이유 정확도는 전체 예측 중에서 맞춘 비율입니다.

언뜻 보기에는 완벽한 지표 같지만, 큰 함정이 있습니다. 예를 들어 스팸 메일 분류기를 만든다고 가정해봅시다.

전체 메일 중 스팸이 1%밖에 안 된다면 어떻게 될까요? 모든 메일을 "정상"이라고 예측해도 99%의 정확도를 얻을 수 있습니다.

하지만 이런 모델은 실제로는 쓸모가 없습니다. 이런 문제를 클래스 불균형 문제라고 합니다.

특정 클래스의 데이터가 압도적으로 많을 때, 정확도는 좋아 보이지만 실제로는 소수 클래스를 전혀 예측하지 못하는 문제가 생깁니다. 정밀도와 재현율 그래서 등장한 것이 **정밀도(Precision)**와 **재현율(Recall)**입니다.

정밀도는 "양성으로 예측한 것 중에서 실제로 양성인 비율"입니다. 마치 경찰이 범인을 체포할 때, 체포한 사람 중에 진짜 범인이 몇 명인지를 측정하는 것과 같습니다.

잘못된 체포를 최소화하는 것이 중요할 때 정밀도를 봅니다. 재현율은 "실제 양성 중에서 양성으로 예측한 비율"입니다.

전체 범인 중에서 몇 명을 잡았는지를 측정합니다. 범인을 놓치지 않는 것이 중요할 때 재현율을 봅니다.

F1 점수로 균형 잡기 정밀도와 재현율은 서로 트레이드오프 관계입니다. 하나를 높이면 다른 하나가 낮아지는 경우가 많습니다.

F1 점수는 이 두 지표의 조화 평균입니다. 정밀도와 재현율이 모두 높아야 F1 점수가 높아집니다.

둘 중 하나라도 낮으면 F1 점수도 낮아집니다. 균형 잡힌 모델을 원할 때 F1 점수를 주로 봅니다.

코드 분석 위 코드를 살펴보겠습니다. 각 모델에 대해 predict() 메서드로 예측을 수행합니다.

그다음 sklearn.metrics에서 제공하는 함수들로 각 지표를 계산합니다. average='macro' 파라미터가 중요합니다.

다중 클래스 분류에서는 각 클래스별로 지표를 계산한 다음 평균을 내는 방식을 지정해야 합니다. macro는 각 클래스를 동등하게 취급합니다.

결과를 딕셔너리에 저장하고, 판다스 데이터프레임으로 변환하면 보기 좋은 표 형태로 출력됩니다. 실무 적용 실제 프로젝트에서는 문제의 특성에 따라 중요한 지표가 다릅니다.

의료 진단 시스템에서는 재현율이 중요합니다. 병을 놓치면 안 되니까요.

반면 스팸 필터에서는 정밀도가 중요합니다. 중요한 메일을 스팸으로 잘못 분류하면 안 되니까요.

김개발 씨의 고객 이탈 예측 프로젝트에서는 어떨까요? 이탈할 고객을 놓치는 것도, 이탈하지 않을 고객에게 불필요한 마케팅을 하는 것도 모두 비용입니다.

따라서 F1 점수로 균형을 맞추는 것이 좋습니다. 주의사항 다중 클래스 분류에서는 average 파라미터를 반드시 지정해야 합니다.

'macro', 'micro', 'weighted' 중에서 선택할 수 있습니다. 각각의 의미를 이해하고 상황에 맞게 선택하세요.

또한 classification_report() 함수를 사용하면 모든 지표를 한 번에 볼 수 있습니다. 클래스별 상세 정보도 함께 제공되어 편리합니다.

정리 박시니어 씨가 말했습니다. "이제 보이죠?

정확도만 봤을 때와 다른 모델이 우수할 수도 있어요." 김개발 씨는 여러 지표를 종합적으로 보는 것이 얼마나 중요한지 깨달았습니다.

실전 팁

💡 - 클래스 불균형이 있을 때는 정확도보다 정밀도, 재현율, F1 점수가 더 중요합니다

  • 다중 클래스 분류에서는 average 파라미터를 반드시 지정하세요
  • classification_report()를 사용하면 모든 지표를 한눈에 볼 수 있습니다

3. 혼동 행렬 비교

김개발 씨는 수치로 된 지표를 보다가 문득 궁금해졌습니다. "어떤 클래스를 잘못 예측했는지 알 수 있을까요?" 박시니어 씨가 웃으며 답했습니다.

"혼동 행렬을 보면 한눈에 알 수 있어요."

**혼동 행렬(Confusion Matrix)**은 모델이 각 클래스를 어떻게 예측했는지 표로 정리한 것입니다. 마치 학생들의 답안을 문제별로 분류해서 어떤 문제를 많이 틀렸는지 파악하는 것과 같습니다.

어떤 클래스 간에 혼동이 일어나는지 시각적으로 확인할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

# 랜덤 포레스트 모델로 예측
rf_model = models['Random Forest']
y_pred = rf_model.predict(X_test)

# 혼동 행렬 생성
cm = confusion_matrix(y_test, y_pred)

# 시각화
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=['Setosa', 'Versicolor', 'Virginica'],
            yticklabels=['Setosa', 'Versicolor', 'Virginica'])
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix - Random Forest')
plt.savefig('confusion_matrix.png')
print("혼동 행렬이 저장되었습니다.")

김개발 씨는 F1 점수가 높은 모델을 선택했지만, 뭔가 찝찝했습니다. 숫자만으로는 실제로 어떤 실수를 하는지 감이 오지 않았습니다.

박시니어 씨가 모니터를 가리켰습니다. "혼동 행렬을 그려보세요.

모델의 실수 패턴이 보일 겁니다." 혼동 행렬이란 무엇인가 혼동 행렬은 모델의 예측 결과를 표로 정리한 것입니다. 행은 실제 클래스, 열은 예측한 클래스를 나타냅니다.

예를 들어 꽃의 종류를 분류하는 문제라면, 실제로는 Setosa인데 Setosa로 예측한 개수, Setosa인데 Versicolor로 잘못 예측한 개수 등이 모두 표시됩니다. 대각선에 있는 값들은 올바르게 예측한 개수입니다.

대각선 밖의 값들은 잘못 예측한 개수입니다. 이상적인 혼동 행렬은 대각선만 값이 크고 나머지는 0에 가까운 형태입니다.

왜 혼동 행렬이 필요한가 정확도나 F1 점수 같은 요약 지표는 편리하지만, 세부 정보를 잃어버립니다. 어떤 클래스를 잘 맞추고, 어떤 클래스를 자주 틀리는지 알 수 없습니다.

혼동 행렬을 보면 모델의 약점이 드러납니다. 예를 들어 Versicolor와 Virginica를 자주 헷갈린다면, 이 두 클래스를 구분하는 특성을 더 추가해야 한다는 힌트를 얻을 수 있습니다.

코드 분석 먼저 confusion_matrix() 함수로 혼동 행렬을 계산합니다. y_test는 실제 레이블, y_pred는 예측 레이블입니다.

반환되는 것은 2차원 넘파이 배열입니다. 이 배열을 숫자로만 보면 이해하기 어렵습니다.

그래서 **seaborn의 heatmap()**으로 시각화합니다. 히트맵은 값의 크기를 색깔로 표현해서 한눈에 패턴을 파악할 수 있게 해줍니다.

annot=True는 각 셀에 숫자를 표시합니다. **fmt='d'**는 정수 형식으로 표시합니다.

**cmap='Blues'**는 파란색 계열의 색상 맵을 사용합니다. 값이 클수록 진한 파란색으로 표시됩니다.

xticklabelsyticklabels로 축 레이블을 지정하면 0, 1, 2 대신 실제 클래스 이름이 표시되어 이해하기 쉽습니다. 실무에서의 활용 실제 프로젝트에서 혼동 행렬은 매우 유용합니다.

예를 들어 제품 불량 검사 시스템을 만든다고 해봅시다. 혼동 행렬을 보니 불량품을 정상으로 잘못 판정하는 경우가 많다면, 이것은 심각한 문제입니다.

불량품이 출하되면 고객 클레임이 발생하니까요. 반대로 정상 제품을 불량으로 잘못 판정한다면, 비용 낭비는 있지만 고객에게는 피해가 없습니다.

두 가지 실수의 심각도가 다른 것입니다. 혼동 행렬을 보면 이런 비대칭적인 비용을 고려한 의사결정을 할 수 있습니다.

여러 모델 비교하기 김개발 씨는 여러 모델의 혼동 행렬을 나란히 그려봤습니다. 로지스틱 회귀는 Versicolor와 Virginica를 자주 헷갈렸지만, 랜덤 포레스트는 거의 완벽하게 구분했습니다.

"아, 그래서 랜덤 포레스트의 F1 점수가 더 높았구나!" 김개발 씨가 이해했습니다. 주의사항 혼동 행렬은 클래스가 많아지면 복잡해집니다.

클래스가 10개, 20개가 되면 100개, 400개의 셀이 생깁니다. 이럴 때는 자주 헷갈리는 클래스 쌍만 따로 분석하는 것이 좋습니다.

또한 클래스 순서를 일관되게 유지해야 합니다. 모델마다 클래스 순서가 다르면 혼동 행렬을 비교하기 어렵습니다.

정리 박시니어 씨가 말했습니다. "이제 숫자 너머의 이야기가 보이죠?

모델이 어디서 실수하는지 알면 개선 방향도 보입니다." 김개발 씨는 이제 단순히 점수만 보는 것이 아니라, 모델의 행동을 이해하게 되었습니다.

실전 팁

💡 - 혼동 행렬로 모델이 어떤 클래스를 헷갈리는지 파악하세요

  • seaborn의 heatmap으로 시각화하면 패턴이 한눈에 보입니다
  • 클래스 레이블을 명시적으로 지정해서 가독성을 높이세요

4. ROC 곡선과 AUC

김개발 씨는 이진 분류 문제를 하나 더 맡았습니다. 대출 연체 예측 모델이었습니다.

박시니어 씨가 말했습니다. "이번엔 ROC 곡선을 꼭 그려보세요.

임계값에 따른 성능 변화를 볼 수 있어요."

**ROC 곡선(Receiver Operating Characteristic Curve)**은 분류 임계값을 변화시키면서 재현율과 거짓 양성 비율의 관계를 그린 그래프입니다. 마치 경보 시스템의 민감도를 조절하면서 놓치는 위험과 오경보의 균형을 찾는 것과 같습니다.

**AUC(Area Under the Curve)**는 ROC 곡선 아래 면적으로, 모델 성능의 종합 지표입니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import roc_curve, auc, RocCurveDisplay
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import label_binarize

# 이진 분류 데이터 준비
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 모델 학습
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# 예측 확률 구하기 (양성 클래스에 대한 확률)
y_proba = rf_model.predict_proba(X_test)[:, 1]

# ROC 곡선 데이터 계산
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
roc_auc = auc(fpr, tpr)

# ROC 곡선 시각화
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Classifier')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate (Recall)')
plt.title('ROC Curve - Random Forest')
plt.legend(loc="lower right")
plt.savefig('roc_curve.png')
print(f"AUC: {roc_auc:.4f}")

김개발 씨는 대출 연체 예측 모델을 만들었습니다. 정확도는 95%가 나왔습니다.

뿌듯했습니다. 하지만 박시니어 씨는 만족하지 않았습니다.

"김 대리, 연체할 사람을 몇 퍼센트나 잡아냈어요?" 김개발 씨가 확인해보니 연체자의 30%밖에 예측하지 못했습니다. 정확도는 높지만 실제로 중요한 연체자를 대부분 놓친 것입니다.

분류 임계값의 문제 대부분의 분류 모델은 확률을 출력합니다. 예를 들어 "이 고객이 연체할 확률은 0.3입니다"라고 예측합니다.

그런데 이것을 "연체한다" 또는 "연체하지 않는다"로 최종 판정하려면 **임계값(threshold)**이 필요합니다. 보통 0.5를 사용합니다.

확률이 0.5 이상이면 양성, 미만이면 음성으로 분류합니다. 하지만 이 0.5라는 값은 임의적입니다.

문제의 특성에 따라 다른 임계값이 더 적합할 수 있습니다. 연체 예측에서는 어떨까요?

연체자를 놓치는 것이 큰 손실이라면, 임계값을 0.3으로 낮춰서 더 많은 고객을 연체 위험군으로 분류하는 것이 나을 수 있습니다. ROC 곡선의 의미 ROC 곡선은 임계값을 0부터 1까지 변화시키면서 **재현율(TPR, True Positive Rate)**과 **거짓 양성 비율(FPR, False Positive Rate)**을 그린 그래프입니다.

TPR은 실제 양성 중에서 양성으로 예측한 비율입니다. 연체자를 얼마나 잘 잡아내는지를 나타냅니다.

FPR은 실제 음성 중에서 양성으로 잘못 예측한 비율입니다. 정상 고객을 연체자로 잘못 판단하는 비율입니다.

왼쪽 아래(0,0)는 모든 것을 음성으로 예측하는 경우이고, 오른쪽 위(1,1)는 모든 것을 양성으로 예측하는 경우입니다. 이상적인 모델은 왼쪽 위(0,1)에 가까워야 합니다.

FPR은 0이고 TPR은 1인 상태입니다. AUC의 의미 AUC는 ROC 곡선 아래의 면적입니다.

0.5에서 1 사이의 값을 가집니다. AUC가 0.5라는 것은 무작위로 찍는 것과 같다는 의미입니다.

동전 던지기와 다를 바 없습니다. AUC가 1이라는 것은 완벽한 분류기라는 의미입니다.

보통 AUC가 0.7 이상이면 괜찮은 모델, 0.8 이상이면 좋은 모델, 0.9 이상이면 매우 우수한 모델로 평가합니다. 코드 분석 먼저 predict_proba() 메서드로 확률을 예측합니다.

[:, 1]은 양성 클래스(클래스 1)에 대한 확률만 가져오는 것입니다. roc_curve() 함수는 fpr, tpr, thresholds를 반환합니다.

각 임계값에서의 FPR과 TPR 값들입니다. auc() 함수로 곡선 아래 면적을 계산합니다.

그래프에는 **대각선(점선)**도 함께 그립니다. 이것은 무작위 분류기의 ROC 곡선입니다.

우리 모델의 곡선이 이 대각선보다 위에 있어야 의미가 있습니다. 실무 활용 김개발 씨는 ROC 곡선을 보며 임계값을 조정했습니다.

0.3으로 낮추니 연체자의 70%를 잡아낼 수 있었습니다. 대신 정상 고객 중 20%를 연체자로 잘못 판단했습니다.

비즈니스 팀과 협의한 결과, 연체 손실이 마케팅 비용보다 크므로 이 정도 거짓 양성은 감수하기로 했습니다. 여러 모델 비교 ROC 곡선을 한 그래프에 여러 개 그리면 모델 간 비교가 쉽습니다.

AUC가 큰 모델일수록 곡선이 왼쪽 위에 가깝습니다. 김개발 씨는 로지스틱 회귀(AUC 0.92), 랜덤 포레스트(AUC 0.97), SVM(AUC 0.95)을 비교했습니다.

랜덤 포레스트가 가장 우수했습니다. 주의사항 ROC 곡선은 이진 분류에서만 사용할 수 있습니다.

다중 클래스 분류에서는 각 클래스를 "이 클래스 vs 나머지"로 나눠서 여러 개의 ROC 곡선을 그려야 합니다. 또한 클래스 불균형이 심할 때는 Precision-Recall 곡선이 더 유용할 수 있습니다.

정리 박시니어 씨가 말했습니다. "ROC 곡선을 보면 임계값을 어떻게 설정할지 판단할 수 있어요.

비즈니스 요구사항에 맞춰서요." 김개발 씨는 이제 단순히 기본 설정을 쓰는 것이 아니라, 상황에 맞게 모델을 조정할 수 있게 되었습니다.

실전 팁

💡 - ROC 곡선으로 임계값에 따른 성능 변화를 파악하세요

  • AUC는 단일 숫자로 모델을 비교할 수 있는 편리한 지표입니다
  • 비즈니스 요구사항에 따라 적절한 임계값을 선택하세요

5. 모델 선택 기준

김개발 씨는 이제 여러 지표를 계산할 수 있게 되었지만, 최종적으로 어떤 모델을 선택해야 할지 고민이 되었습니다. 박시니어 씨가 조언했습니다.

"지표만 보지 말고, 비즈니스 요구사항, 해석 가능성, 속도도 함께 고려해야 합니다."

모델 선택은 성능 지표뿐만 아니라 비즈니스 요구사항, 해석 가능성, 예측 속도, 학습 시간, 유지보수 용이성 등 다양한 요소를 종합적으로 고려하는 과정입니다. 마치 자동차를 선택할 때 연비뿐만 아니라 가격, 안전성, 디자인, 유지비 등을 함께 본다는 것과 같습니다.

다음 코드를 살펴봅시다.

import time
import numpy as np

# 모델 평가 프레임워크
def evaluate_model(name, model, X_train, X_test, y_train, y_test):
    # 학습 시간 측정
    start_time = time.time()
    model.fit(X_train, y_train)
    train_time = time.time() - start_time

    # 예측 시간 측정
    start_time = time.time()
    y_pred = model.predict(X_test)
    predict_time = time.time() - start_time

    # 성능 측정
    accuracy = accuracy_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred, average='macro')

    return {
        'Model': name,
        'Accuracy': accuracy,
        'F1 Score': f1,
        'Train Time (s)': train_time,
        'Predict Time (s)': predict_time,
        'Predict Speed (samples/s)': len(y_test) / predict_time
    }

# 모든 모델 평가
evaluation_results = []
for name, model in models.items():
    result = evaluate_model(name, model, X_train, X_test, y_train, y_test)
    evaluation_results.append(result)

# 결과를 데이터프레임으로 정리
df_eval = pd.DataFrame(evaluation_results)
print(df_eval.round(4))

김개발 씨는 랜덤 포레스트가 AUC 0.97로 가장 높다는 것을 확인했습니다. "이걸로 결정했습니다!" 보고서를 작성하려는데, 박시니어 씨가 손을 들어 막았습니다.

"잠깐만요. 이 모델이 실시간 서비스에서 쓰일 건가요, 배치로 처리할 건가요?" 김개발 씨는 멈칫했습니다.

실시간이냐 배치냐에 따라 뭐가 달라지는 걸까요? 성능만으로는 충분하지 않다 모델을 선택할 때 성능 지표만 보는 것은 위험합니다.

실제 서비스에 배포하면 다양한 제약과 요구사항이 있기 때문입니다. 예를 들어 신용카드 사기 탐지 시스템을 만든다고 가정해봅시다.

카드 결제는 1초 안에 승인 여부를 결정해야 합니다. 아무리 정확도가 높아도 예측에 10초가 걸린다면 쓸 수 없습니다.

또 다른 예로, 의료 진단 시스템에서는 의사가 결과를 신뢰해야 합니다. "왜 이런 진단이 나왔나요?"라는 질문에 답할 수 없는 블랙박스 모델은 아무리 정확해도 사용하기 어렵습니다.

고려해야 할 요소들 박시니어 씨가 화이트보드에 목록을 적었습니다. 첫째, 예측 속도입니다.

실시간 서비스라면 밀리초 단위의 응답이 필요할 수 있습니다. 배치 처리라면 몇 분이 걸려도 괜찮을 수 있습니다.

둘째, 학습 시간입니다. 모델을 자주 재학습해야 한다면 학습이 빠른 모델이 유리합니다.

한 달에 한 번만 학습한다면 학습 시간은 크게 중요하지 않을 수 있습니다. 셋째, 해석 가능성입니다.

규제가 엄격한 산업(금융, 의료 등)에서는 모델의 결정 근거를 설명할 수 있어야 합니다. 의사결정 트리나 로지스틱 회귀가 딥러닝보다 유리합니다.

넷째, 메모리 사용량입니다. 엣지 디바이스에 배포한다면 모델 크기가 작아야 합니다.

랜덤 포레스트는 수백 개의 트리를 저장해야 하므로 메모리를 많이 사용합니다. 다섯째, 유지보수 용이성입니다.

간단한 모델은 디버깅과 수정이 쉽습니다. 복잡한 앙상블 모델은 문제가 생겼을 때 원인을 찾기 어렵습니다.

코드 분석 위 코드는 성능뿐만 아니라 시간도 함께 측정합니다. **time.time()**으로 학습 전후의 시간을 재서 학습 시간을 계산합니다.

예측 시간도 마찬가지입니다. Predict Speed는 초당 몇 개의 샘플을 예측할 수 있는지를 나타냅니다.

실시간 서비스에서는 이 값이 중요합니다. 모든 결과를 딕셔너리로 모아서 데이터프레임으로 만들면, 성능과 속도를 한눈에 비교할 수 있습니다.

트레이드오프 이해하기 김개발 씨가 결과를 봤습니다. 로지스틱 회귀는 학습이 0.01초로 매우 빠르지만 F1 점수는 0.95였습니다.

랜덤 포레스트는 F1 점수가 0.98로 높지만 학습에 0.5초가 걸렸습니다. "어떤 걸 선택해야 하죠?" 김개발 씨가 물었습니다.

박시니어 씨가 답했습니다. "우리 서비스는 하루에 한 번 배치로 돌리니까 학습 시간은 중요하지 않아요.

대신 정확도가 1% 올라가면 수백만 원의 손실을 막을 수 있어요. 랜덤 포레스트를 쓰는 게 맞습니다." 실제 사례 실무에서는 이런 식으로 결정합니다.

웹 서비스의 추천 시스템이라면 예측 속도가 중요하므로 로지스틱 회귀나 경량 모델을 선택할 수 있습니다. 대신 성능은 조금 희생합니다.

주식 가격 예측처럼 정확도가 매우 중요한 문제라면, 예측 시간이 좀 걸려도 앙상블 모델이나 딥러닝을 사용합니다. 의료 진단 시스템이라면 해석 가능성이 중요하므로, 성능이 조금 낮더라도 의사결정 트리나 로지스틱 회귀를 선택할 수 있습니다.

주의사항 실험 환경과 실제 배포 환경이 다를 수 있습니다. 작은 데이터셋에서는 빠르던 모델이 실제 수백만 건의 데이터를 처리할 때는 느릴 수 있습니다.

또한 조기 최적화를 피해야 합니다. 처음부터 너무 복잡한 모델을 선택하지 말고, 간단한 모델로 시작해서 필요할 때 복잡도를 높이는 것이 좋습니다.

정리 박시니어 씨가 말했습니다. "모델 선택은 과학이면서 동시에 예술입니다.

데이터, 비즈니스, 시스템 요구사항을 모두 고려해야 해요." 김개발 씨는 이제 단순히 가장 정확한 모델이 아니라, 가장 적합한 모델을 선택하는 방법을 배웠습니다.

실전 팁

💡 - 성능, 속도, 해석 가능성, 메모리 사용량을 종합적으로 고려하세요

  • 비즈니스 요구사항에 따라 우선순위가 달라집니다
  • 간단한 모델로 시작해서 필요할 때 복잡도를 높이세요

6. 하이퍼파라미터 튜닝 소개

김개발 씨는 랜덤 포레스트를 선택했지만, 기본 설정을 그대로 사용했습니다. 박시니어 씨가 물었습니다.

"하이퍼파라미터는 조정해 봤어요?" 김개발 씨는 하이퍼파라미터가 뭔지도 몰랐습니다.

하이퍼파라미터는 모델 학습 전에 사용자가 설정하는 값입니다. 마치 요리할 때 불의 세기, 조리 시간을 조절하는 것과 같습니다.

같은 재료(데이터)와 레시피(알고리즘)를 사용해도 하이퍼파라미터에 따라 결과(모델 성능)가 크게 달라질 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier

# 기본 설정 모델
rf_default = RandomForestClassifier(random_state=42)
rf_default.fit(X_train, y_train)
default_score = rf_default.score(X_test, y_test)

# 하이퍼파라미터 조정 모델
rf_tuned = RandomForestClassifier(
    n_estimators=200,        # 트리 개수를 100에서 200으로 증가
    max_depth=10,            # 트리의 최대 깊이 제한
    min_samples_split=5,     # 노드를 분할하기 위한 최소 샘플 수
    min_samples_leaf=2,      # 리프 노드의 최소 샘플 수
    max_features='sqrt',     # 각 분할에서 고려할 특성 수
    random_state=42
)
rf_tuned.fit(X_train, y_train)
tuned_score = rf_tuned.score(X_test, y_test)

print(f"기본 설정 정확도: {default_score:.4f}")
print(f"튜닝 후 정확도: {tuned_score:.4f}")
print(f"성능 향상: {(tuned_score - default_score) * 100:.2f}%p")

김개발 씨는 랜덤 포레스트를 그냥 RandomForestClassifier()로 만들어서 사용했습니다. 그런데 박시니어 씨는 괄호 안에 여러 숫자를 넣고 있었습니다.

"이게 뭐죠?" 김개발 씨가 물었습니다. 박시니어 씨가 웃으며 답했습니다.

"하이퍼파라미터예요. 모델의 학습 방식을 조절하는 설정값이죠." 파라미터 vs 하이퍼파라미터 먼저 용어를 정리해야 합니다.

파라미터하이퍼파라미터는 다릅니다. 파라미터는 모델이 학습 과정에서 자동으로 학습하는 값입니다.

예를 들어 선형 회귀의 가중치와 편향은 파라미터입니다. 데이터를 보고 모델이 스스로 최적값을 찾아냅니다.

하이퍼파라미터는 학습 전에 사용자가 미리 설정하는 값입니다. 모델 구조나 학습 과정을 제어합니다.

예를 들어 랜덤 포레스트의 트리 개수, 트리 깊이 등이 하이퍼파라미터입니다. 요리에 비유하면, 재료의 양과 조리법은 레시피(알고리즘)가 정하지만, 불의 세기와 조리 시간은 요리사(개발자)가 직접 조절해야 합니다.

하이퍼파라미터가 바로 이 불의 세기와 조리 시간입니다. 왜 하이퍼파라미터가 중요한가 같은 알고리즘이라도 하이퍼파라미터에 따라 성능이 크게 달라집니다.

예를 들어 랜덤 포레스트에서 트리 개수를 10개만 사용하면 성능이 낮습니다. 100개를 사용하면 성능이 올라갑니다.

하지만 1000개를 사용한다고 계속 좋아지는 것은 아닙니다. 어느 시점부터는 개선이 미미하고 학습 시간만 늘어납니다.

max_depth는 트리의 최대 깊이입니다. 너무 깊으면 과적합이 일어나고, 너무 얕으면 과소적합이 일어납니다.

적절한 깊이를 찾는 것이 중요합니다. 주요 하이퍼파라미터들 박시니어 씨가 랜덤 포레스트의 주요 하이퍼파라미터를 설명했습니다.

n_estimators는 트리의 개수입니다. 많을수록 성능이 좋아지지만 학습 시간도 늘어납니다.

보통 100에서 500 사이의 값을 사용합니다. max_depth는 각 트리의 최대 깊이입니다.

None으로 설정하면 제한이 없어서 과적합 위험이 있습니다. 5에서 20 사이의 값을 많이 사용합니다.

min_samples_split은 노드를 분할하기 위한 최소 샘플 수입니다. 값이 클수록 트리가 단순해져서 과적합을 방지할 수 있습니다.

min_samples_leaf는 리프 노드가 가져야 할 최소 샘플 수입니다. 이 값도 클수록 모델이 단순해집니다.

max_features는 각 분할에서 고려할 특성의 수입니다. 'sqrt'는 전체 특성 수의 제곱근만큼 사용하라는 의미입니다.

코드 분석 위 코드는 기본 설정과 튜닝된 설정을 비교합니다. 기본 설정 모델은 아무 파라미터도 지정하지 않았습니다.

사이킷런이 정한 기본값을 그대로 사용합니다. 튜닝된 모델은 여러 하이퍼파라미터를 명시적으로 지정했습니다.

n_estimators를 200으로 늘렸고, max_depth로 깊이를 제한했습니다. 두 모델을 같은 데이터로 학습시키고 점수를 비교하면, 하이퍼파라미터 조정의 효과를 확인할 수 있습니다.

실무에서의 접근 실제 프로젝트에서는 어떻게 하이퍼파라미터를 조정할까요? 처음에는 기본값으로 시작합니다.

그다음 한 번에 하나씩 파라미터를 바꿔가며 효과를 관찰합니다. 예를 들어 n_estimators를 50, 100, 200, 500으로 바꿔보면서 성능 변화를 봅니다.

어느 정도 감을 잡으면, 여러 파라미터를 조합해서 실험합니다. 이 과정이 지루하고 시간이 오래 걸립니다.

자동화의 필요성 김개발 씨가 물었습니다. "이거 일일이 다 해봐야 하나요?" 박시니어 씨가 고개를 저었습니다.

"아니요. GridSearchCV 같은 도구를 사용하면 자동으로 최적의 조합을 찾아줍니다.

다음에 배워봅시다." 주의사항 하이퍼파라미터를 과도하게 튜닝하면 테스트 세트에 과적합될 수 있습니다. 테스트 세트를 너무 자주 보면서 하이퍼파라미터를 조정하면, 결국 테스트 세트에만 좋은 모델이 됩니다.

따라서 데이터를 훈련, 검증, 테스트 세 부분으로 나누고, 검증 세트로 하이퍼파라미터를 조정하고, 마지막에 딱 한 번만 테스트 세트로 최종 성능을 측정해야 합니다. 정리 박시니어 씨가 말했습니다.

"하이퍼파라미터 튜닝은 모델 성능을 끌어올리는 중요한 과정입니다. 하지만 수동으로 하기엔 너무 많은 조합이 있어요." 김개발 씨는 이제 하이퍼파라미터의 개념을 이해했고, 자동화 도구가 필요하다는 것도 깨달았습니다.

실전 팁

💡 - 하이퍼파라미터는 모델의 학습 방식을 제어하는 사용자 설정값입니다

  • 기본값으로 시작해서 점진적으로 튜닝하세요
  • 테스트 세트에 과적합되지 않도록 검증 세트를 따로 사용하세요

7. GridSearchCV 활용

김개발 씨는 하이퍼파라미터를 하나하나 바꿔가며 실험하다가 지쳐버렸습니다. 조합이 너무 많았습니다.

박시니어 씨가 코드 한 줄을 보여줬습니다. "GridSearchCV를 쓰면 자동으로 최적의 조합을 찾아줍니다."

GridSearchCV는 지정한 하이퍼파라미터 조합을 자동으로 시도하고 교차 검증으로 성능을 평가하여 최적의 조합을 찾아주는 도구입니다. 마치 자동 온도 조절기가 최적의 실내 온도를 찾아주는 것처럼, GridSearchCV는 최적의 하이퍼파라미터를 찾아줍니다.

다음 코드를 살펴봅시다.

from sklearn.model_selection import GridSearchCV

# 탐색할 하이퍼파라미터 그리드 정의
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [5, 10, 15, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# GridSearchCV 객체 생성
grid_search = GridSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,                    # 5-fold 교차 검증
    scoring='f1_macro',      # F1 점수로 평가
    n_jobs=-1,               # 모든 CPU 코어 사용
    verbose=2                # 진행 상황 출력
)

# 그리드 서치 실행
print("GridSearchCV 시작...")
grid_search.fit(X_train, y_train)

# 최적 파라미터와 점수
print(f"\n최적 파라미터: {grid_search.best_params_}")
print(f"최고 F1 점수: {grid_search.best_score_:.4f}")

# 최적 모델로 테스트 세트 평가
best_model = grid_search.best_estimator_
test_score = best_model.score(X_test, y_test)
print(f"테스트 세트 정확도: {test_score:.4f}")

김개발 씨는 손으로 계산기를 두드렸습니다. n_estimators 3가지, max_depth 4가지, min_samples_split 3가지, min_samples_leaf 3가지...

총 3 × 4 × 3 × 3 = 108가지 조합이었습니다. "이걸 다 해보라고요?" 김개발 씨가 한숨을 쉬었습니다.

박시니어 씨가 코드를 보여줬습니다. "컴퓨터한테 시키면 되죠." GridSearchCV의 원리 GridSearchCV는 "격자 탐색"이라는 뜻입니다.

하이퍼파라미터 공간을 격자처럼 나누고, 모든 격자점을 시도해보는 방식입니다. 예를 들어 n_estimators가 [100, 200]이고 max_depth가 [5, 10]이라면, (100, 5), (100, 10), (200, 5), (200, 10) 네 가지 조합을 모두 시도합니다.

각 조합마다 교차 검증을 수행합니다. 교차 검증은 데이터를 여러 부분으로 나눠서 돌아가며 검증하는 방법입니다.

예를 들어 5-fold 교차 검증은 데이터를 5등분해서, 한 부분은 검증용으로 쓰고 나머지는 훈련용으로 씁니다. 이걸 5번 반복해서 평균 점수를 구합니다.

왜 교차 검증이 필요한가 단순히 훈련 세트와 검증 세트를 한 번만 나누면, 운이 좋거나 나쁠 수 있습니다. 어떤 데이터가 검증 세트에 들어가느냐에 따라 점수가 크게 달라질 수 있습니다.

교차 검증은 이런 운의 요소를 줄여줍니다. 여러 번 나눠서 평균을 내므로 더 신뢰할 수 있는 점수를 얻습니다.

코드 분석 먼저 param_grid에 시도할 하이퍼파라미터 범위를 딕셔너리로 정의합니다. 키는 파라미터 이름, 값은 시도할 값들의 리스트입니다.

GridSearchCV 객체를 생성할 때 여러 파라미터를 지정합니다. estimator는 사용할 모델입니다.

여기서는 랜덤 포레스트를 지정했습니다. cv=5는 5-fold 교차 검증을 의미합니다.

각 하이퍼파라미터 조합마다 5번의 학습과 검증을 수행합니다. **scoring='f1_macro'**는 F1 점수로 모델을 평가한다는 의미입니다.

다른 지표를 사용할 수도 있습니다. n_jobs=-1은 모든 CPU 코어를 사용하라는 의미입니다.

병렬 처리로 속도를 높입니다. verbose=2는 진행 상황을 자세히 출력합니다.

얼마나 진행됐는지 확인할 수 있어서 답답하지 않습니다. fit() 메서드를 호출하면 그리드 서치가 시작됩니다.

108가지 조합 × 5번 교차 검증 = 540번의 학습이 자동으로 수행됩니다. 결과 확인하기 grid_search가 완료되면 여러 유용한 속성을 제공합니다.

best_params_는 최고 성능을 낸 하이퍼파라미터 조합입니다. 예를 들어 {'n_estimators': 200, 'max_depth': 10, ...} 같은 형태로 나옵니다.

best_score_는 최고 점수입니다. 교차 검증의 평균 점수입니다.

best_estimator_는 최적 하이퍼파라미터로 훈련된 모델입니다. 이 모델을 바로 사용할 수 있습니다.

김개발 씨가 결과를 봤습니다. "와, 자동으로 최고의 조합을 찾았네요!" 실무 팁 박시니어 씨가 조언했습니다.

"처음에는 넓은 범위로 대략적으로 탐색하세요. 그다음 좋은 값 주변을 더 세밀하게 탐색하는 거죠." 예를 들어 처음에는 n_estimators를 [10, 100, 1000]으로 넓게 시도하고, 100이 좋다는 걸 알면 다음에는 [50, 100, 150, 200]으로 좁혀서 탐색합니다.

이런 방식을 **거친 탐색 후 세밀한 탐색(Coarse-to-Fine Search)**이라고 합니다. GridSearchCV의 한계 GridSearchCV는 강력하지만 단점도 있습니다.

하이퍼파라미터가 많고 각각의 범위가 넓으면 시간이 매우 오래 걸립니다. 예를 들어 10개의 파라미터에 각각 10개의 값을 시도하면 10^10 = 100억 가지 조합이 됩니다.

이건 현실적으로 불가능합니다. 이럴 때는 RandomizedSearchCV를 사용할 수 있습니다.

모든 조합을 시도하는 대신, 무작위로 일부만 시도하는 방식입니다. 시간을 절약하면서도 좋은 결과를 얻을 수 있습니다.

주의사항 GridSearchCV는 내부적으로 교차 검증을 수행하므로, 입력으로 전체 훈련 데이터를 주면 됩니다. 별도로 검증 세트를 나눌 필요가 없습니다.

하지만 테스트 세트는 절대 건드리면 안 됩니다. GridSearchCV로 최적 모델을 찾은 후, 마지막에 딱 한 번만 테스트 세트로 최종 성능을 평가해야 합니다.

정리 김개발 씨는 감탄했습니다. "이제 하루 종일 수동으로 실험하지 않아도 되겠네요!" 박시니어 씨가 웃으며 답했습니다.

"맞아요. GridSearchCV는 머신러닝 엔지니어의 필수 도구입니다.

시간을 절약하고 더 나은 모델을 만들 수 있어요." 김개발 씨는 이제 모델을 비교하고, 성능을 평가하고, 최적의 하이퍼파라미터를 찾는 전체 워크플로우를 이해하게 되었습니다. 자신감을 가지고 다음 프로젝트를 시작할 수 있었습니다.

실전 팁

💡 - GridSearchCV로 하이퍼파라미터 조합을 자동으로 탐색하세요

  • 처음에는 넓은 범위로 탐색하고, 그다음 좋은 값 주변을 세밀하게 탐색하세요
  • 조합이 너무 많으면 RandomizedSearchCV를 사용하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#MachineLearning#ModelSelection#Scikit-learn#GridSearchCV

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.