25.05.19 코딩 공부 시작

코딩 척척석박사 분들 피드백 환영합니다.

공대생이 코딩에서 살아남기

특강/머신러닝

[머신러닝] 4회차 앙상블, 부스팅(06.27)

코딩 아가 2025. 6. 27. 16:10

수치형 변수 변환

log 변환

def log_transform(X, columns):
    X_transformed = X.copy()
    for col in columns:
        X_transformed[f'{col}_log'] = np.log1p(X_transformed[col])

# 시각화
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
        sns.histplot(X[col], ax=ax1)
        ax1.set_title(f'Original Distribution of {col}')
        sns.histplot(X_transformed[f'{col}_log'], ax=ax2)
        ax2.set_title(f'Log Transformed Distribution of {col}')
        plt.show()

    return X_transformed

Box-Cox 변환

from scipy import stats

def boxcox_transform(X, columns):
    X_transformed = X.copy()
    for col in columns:
        X_transformed[f'{col}_boxcox'], lambda_param = stats.boxcox(X_transformed[col])

# 시각화
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
        sns.histplot(X[col], ax=ax1)
        ax1.set_title(f'Original Distribution of {col}')
        sns.histplot(X_transformed[f'{col}_boxcox'], ax=ax2)
        ax2.set_title(f'Box-Cox Transformed Distribution of {col}')
        plt.show()

    return X_transformed

앙상블모델이란?

여러 모델의 예측을 결합하여 더 나은 결과를 얻는 방법

1. 배깅: 랜덤 포레스트

  • 여러 개의 결정 트리를 독립적으로 학습
  • 각 트리가 서로 다른 데이터와 특성을 사용

2. 부스팅: XGBoost, LightGBM

  • 이전 모델의 오차를 보와하는 방향으로 학습
  • 높은 예측 성능으로 실무에서 널리 사용됨

장점

  • 안정적, 강건한 예측 가능
  • 과적합 위험 적음
  • 더 높은 예측 성능

단점

  • 학습과 예측에 많은 시간, 자원 필요
  • 복잡하여 해석 어려움

1. 결정 트리란?

  • 불순도(지니계수나 엔트로피)가 가장 크게 감소하는 방향으로 특성과 분할 기준 선택
  • (장점): 모델의 의사결정 과정을 시각적으로 표현가능하여 해석이 쉬움
  • (단점): 과적합 >> 가지치기(pruning) 기법 사용

불순도란?

각 노드에서 데이터가 얼마나 섞여있는지 측정하는 지표

단일 클래스: 불순도 = 0

여러 클래스 균등 mix: 불순도 = 최대

ex. 부채 상환비율을 예측하는 결정 트리 모델

2. 랜덤 포레스트란?

  • 무작위로 추출된 샘플의 특성만 고려하여 학습
  • (장점) 과적합 위험성 낮음, 특성 중요도 계산 쉬움, 이상치에 강함, 대규모 데이터셋 사용가능
  • (단점) 계산 비용/시간 많이듬, 복잡하고 해석 어려움, 하이퍼파라미터 튜닝의 복잡성

작동원리

  1. 부트스트랩 샘플링 (Bootstrap Sampling)
    • 복원추출(중복 추출 허용)
  2. 특성의 무작위 선택 (Random Feature Selection)
    • 각 분기점에서 일부 특성만 고려
  3. 개별 트리의 학습
    • 서로 다른 데이터와 특성으로 학습
  4. 앙상블 예측 (Ensemble Prediction)
    • 분류 문제(각 트리의 예측을 투표로 결정), 회귀 문제(각 트리의 예측 평균 사용)

실습

1. 라이브러리 불러오기

# 라이브러리 불러오기
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import DecisionTreeregressor    #회귀
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

2. 데이터 준비

# 가상의 대출 데이터 생성
np.random.seed(42)  # 재현성을 위한 시드 설정
n_samples = 1000    # 1000개의 샘플 생성

# 현실적인 대출 데이터 특성 생성
data = {
    'income': np.random.normal(5000000, 2000000, n_samples),        # 연소득 (평균 5000만원)
    'credit_score': np.random.normal(650, 100, n_samples),          # 신용점수 (평균 650점)
    'employment_years': np.random.normal(5, 3, n_samples),          # 근속년수 (평균 5년)
    'dti_ratio': np.random.normal(0.3, 0.1, n_samples),            # 총부채상환비율 (평균 30%)
    'ltv_ratio': np.random.normal(0.6, 0.2, n_samples),            # 담보인정비율 (평균 60%)
    'age': np.random.normal(40, 10, n_samples)                      # 나이 (평균 40세)
}

df = pd.DataFrame(data)

# 현실적인 범위로 데이터 조정
df['credit_score'] = df['credit_score'].clip(300, 900)     # 신용점수는 300-900 사이
df['employment_years'] = df['employment_years'].clip(0, 40) # 근속년수는 0-40년 사이
df['dti_ratio'] = df['dti_ratio'].clip(0, 1)              # DTI는 0-100% 사이
df['ltv_ratio'] = df['ltv_ratio'].clip(0, 1)              # LTV는 0-100% 사이
df['age'] = df['age'].clip(20, 80)                        # 나이는 20-80세 사이

3. 목표변수(상환여부) 생성 > 간단 규칙 기반

def determine_repayment(row):
    score = 0
    # 각 조건별 점수 부여
    score += 1 if row['credit_score'] > 650 else 0
    score += 1 if row['dti_ratio'] < 0.4 else 0
    score += 1 if row['employment_years'] > 2 else 0
    score += 1 if row['income'] > 3000000 else 0
    score += 1 if row['ltv_ratio'] < 0.7 else 0
    
    # 약간의 무작위성 추가 (현실적인 노이즈)
    score += np.random.normal(0, 0.5)
    
    return 1 if score > 2.5 else 0  # 2.5점 이상이면 상환 가능

df['repaid'] = df.apply(determine_repayment, axis=1)

4. 데이터 분할

X = df.drop('repaid', axis=1)    # 특성(설명변수)
y = df['repaid']                 # 목표변수

# 훈련세트와 테스트세트로 분할 (80:20)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

5. 결정 트리 모델 구현

dt_model = DecisionTreeClassifier(
    max_depth=5,                # 트리의 최대 깊이 제한
    min_samples_split=50,       # 노드 분할에 필요한 최소 샘플 수
    min_samples_leaf=20,        # 리프 노드의 최소 샘플 수
    random_state=42
)

# 결정 트리 모델 학습
dt_model.fit(X_train, y_train)

6. 랜덤 포레스트 모델 구현

rf_model = RandomForestClassifier(
    n_estimators=100,           # 트리의 개수
    max_depth=5,                # 각 트리의 최대 깊이
    min_samples_split=50,       # 노드 분할에 필요한 최소 샘플 수
    min_samples_leaf=20,        # 리프 노드의 최소 샘플 수
    random_state=42
)

# 랜덤 포레스트 모델 학습
rf_model.fit(X_train, y_train)

7. 두 모델의 예측 수행

dt_pred = dt_model.predict(X_test)
rf_pred = rf_model.predict(X_test)

8. 성능 평가 함수 정의

def evaluate_model(y_true, y_pred, model_name):
    print(f"\n{model_name} 성능 평가")
    print("="* 50)
    
    # 정확도 출력
    accuracy = accuracy_score(y_true, y_pred)
    print(f"\n정확도: {accuracy:.4f}")
    
    # 분류 보고서 출력 (정밀도, 재현율, F1 점수 등)
    print("\n분류 보고서:")
    print(classification_report(y_true, y_pred))
    
    # 혼동 행렬 시각화
    plt.figure(figsize=(8, 6))
    sns.heatmap(confusion_matrix(y_true, y_pred), 
                annot=True, fmt='d', cmap='Blues',
                xticklabels=['상환불가', '상환가능'],
                yticklabels=['상환불가', '상환가능'])
    plt.title(f'{model_name} 혼동 행렬')
    plt.ylabel('실제 값')
    plt.xlabel('예측 값')
    plt.show()

9. 두 모델의 성능 평가 실행

evaluate_model(y_test, dt_pred, "결정 트리")
evaluate_model(y_test, rf_pred, "랜덤 포레스트")

10. 특성 중요도 시각화 함수

def plot_feature_importance(model, model_name):
    importances = pd.DataFrame({
        'feature': X.columns,
        'importance': model.feature_importances_
    }).sort_values('importance', ascending=False)
    
    plt.figure(figsize=(10, 6))
    sns.barplot(x='importance', y='feature', data=importances)
    plt.title(f'{model_name} 특성 중요도')
    plt.show()

# 두 모델의 특성 중요도 비교
plot_feature_importance(dt_model, "결정 트리")
plot_feature_importance(rf_model, "랜덤 포레스트")

11. 새로운 고객 데이터에 대한 예측

new_customer = pd.DataFrame({
    'income': [6000000],        # 연소득 6000만원
    'credit_score': [750],      # 신용점수 750점
    'employment_years': [5],    # 근속년수 5년
    'dti_ratio': [0.3],        # DTI 30%
    'ltv_ratio': [0.6],        # LTV 60%
    'age': [35]                # 35세
})

print("\n새로운 고객에 대한 예측")
print("="* 50)
print("결정 트리 예측:", "상환 가능" if dt_model.predict(new_customer)[0] == 1 else "상환 불가")
print("랜덤 포레스트 예측:", "상환 가능" if rf_model.predict(new_customer)[0] == 1 else "상환 불가")

# 랜덤 포레스트의 확률값 예측
rf_proba = rf_model.predict_proba(new_customer)[0]
print(f"랜덤 포레스트 예측 확률: 상환불가 {rf_proba[0]:.1%}, 상환가능 {rf_proba[1]:.1%}")

부스팅이란?

  • 약한 학습기들을 순차적으로 학습시켜 강한 학습기로 만드는 방법
  • 약한 학습기(단순): 하나의 간단한 기준으로 판단하는 모델
  • 강한 학습기(복합): 여러 가지의 조건을 복합적으로 고려하는 모델
  • 핵심 원리: 순차적 학습(Sequential)

모델 종류

  1. AdaBoost (Adaptive Boosting): 가중치 업데이트
    • 실수한 부분을 중점적으로 학습
    • 모델 결합(더 좋은 성능을 보인 모델에 높은 가중치 부여)
  2. Gradient Boosting Machine (GBM)
    • 경사하강법 원리 적용(음의 기울기 방향으로 이동하며 최솟값 찾는 방식)
    • 비선형 관계 잘 포착 
  3. XG Boost (eXtreme Gradient Boosting)
    • 정규화 항 도입하여 과적합 방지(like 릿지, 라쏘)
    • 병렬 처리로 학습 속도 항샹
    • 가지치기로 복잡도 제어
    • 결측치 처리 자동화
    • 2차 미분하여 정확한 방향으로 모델 최적화
  4. LightGBM (Light한 GBM)
    • 데이터가 많을 때 사용 > 적을 때 과적합

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, AdaBoostRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import xgboost as xgb
# lightgbm은 설치되어 있지 않으므로 제외

# 데이터 생성
np.random.seed(42)
n_samples = 1000
data = {
    'income': np.random.normal(5000000, 2000000, n_samples),
    'credit_score': np.random.normal(650, 100, n_samples),
    'employment_years': np.random.normal(5, 3, n_samples),
    'dti_ratio': np.random.normal(0.3, 0.1, n_samples),
    'ltv_ratio': np.random.normal(0.6, 0.2, n_samples),
    'age': np.random.normal(40, 10, n_samples)
}
df = pd.DataFrame(data)
df['credit_score'] = df['credit_score'].clip(300, 900)
df['employment_years'] = df['employment_years'].clip(0, 40)
df['dti_ratio'] = df['dti_ratio'].clip(0, 1)
df['ltv_ratio'] = df['ltv_ratio'].clip(0, 1)
df['age'] = df['age'].clip(20, 80)

def determine_repayment_score(row):
    score = 0
    score += 1 if row['credit_score'] > 650 else 0
    score += 1 if row['dti_ratio'] < 0.4 else 0
    score += 1 if row['employment_years'] > 2 else 0
    score += 1 if row['income'] > 3000000 else 0
    score += 1 if row['ltv_ratio'] < 0.7 else 0
    score += np.random.normal(0, 0.5)
    return np.clip(score / 5, 0, 1)

df['repayment_prob'] = df.apply(determine_repayment_score, axis=1)
X = df.drop('repayment_prob', axis=1)
y = df['repayment_prob']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 회귀 모델 정의
models = {
    # 🚀 AdaBoost 회귀
    'AdaBoost': AdaBoostRegressor(
        n_estimators=100,     # 약한 학습기(기본: 결정트리) 개수
        learning_rate=0.1,    # 학습률: 낮출수록 천천히 학습
        random_state=42
    ),

    # 📈 Gradient Boosting 회귀 (GBM)
    'Gradient Boosting': GradientBoostingRegressor(
        n_estimators=100,     # 트리 개수 (많을수록 성능↑, 과적합 주의)
        learning_rate=0.1,    # 학습률
        max_depth=3,          # 각 트리의 최대 깊이 (작을수록 일반화↑)
        min_samples_split=5,  # 노드 분할을 위한 최소 샘플 수
        random_state=42
    ),

    # 💥 XGBoost 회귀 (정규화 포함, 빠르고 성능 좋음)
    'XGBoost': xgb.XGBRegressor(
        n_estimators=100,         # 트리 개수
        learning_rate=0.1,        # 학습률
        max_depth=3,              # 트리 깊이
        min_child_weight=1,       # 리프 노드의 최소 가중치 합 (작으면 과적합↑)
        subsample=0.8,            # 트리당 사용할 샘플 비율 (과적합 방지)
        colsample_bytree=0.8,     # 트리당 사용할 특성 비율
        random_state=42
    ),

    # ⚡ LightGBM 회귀 (속도 매우 빠름, 대용량 데이터 적합)
    'LightGBM': lgb.LGBMRegressor(
        n_estimators=100,         # 트리 개수
        learning_rate=0.1,        # 학습률
        max_depth=3,              # 트리 최대 깊이
        num_leaves=31,            # 리프 노드 수 (너무 크면 과적합)
        subsample=0.8,            # 샘플링 비율
        colsample_bytree=0.8,     # 특성 샘플링 비율
        random_state=42
    )
}

# 모델 학습 및 평가
print("\n📊 회귀 모델 성능 비교 (RMSE, R²)")
print("="*50)
for name, model in models.items():
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    r2 = r2_score(y_test, y_pred)
    print(f"{name:<20} | RMSE: {rmse:.4f} | R²: {r2:.4f}")

모델 선택 가이드

AdaBoost를 선택해야 할 때:

  • 데이터셋이 비교적 작고 노이즈가 적을 때
  • 모델의 작동 원리를 명확하게 설명해야 할 때
  • 이진 분류 문제에서 특히 효과적

GBM을 선택해야 할 때:

  • 예측 성능이 가장 중요한 고려사항일 때
  • 데이터의 비선형성이 강할 때
  • 충분한 학습 시간을 확보할 수 있을 때

XGBoost를 선택해야 할 때:

  • 대규모 데이터셋을 다룰 때
  • 결측치가 많은 데이터를 다룰 때
  • 과적합 방지가 중요할 때
  • 높은 예측 성능과 적절한 학습 속도가 모두 필요할 때

LightGBM을 선택해야 할 때:

  • 매우 큰 데이터셋을 다룰 때
  • 빠른 학습 속도가 필수적일 때
  • 메모리 자원이 제한적일 때
  • 단, 데이터셋이 너무 작을 경우 과적합 위험이 있으므로 주의