카테고리 없음

내일배움캠프_[머신러닝]4회차 수업

iron-min 2025. 10. 29. 19:55

1. 오늘 배운것

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

 

1) 배깅 :  원래 데이터에서 여러 번 샘플링하여 다양한 데이터셋을 만들고, 각각에 대해 독립적으로 모델을 학습시킵니다. 마치 여러 의사가 각자의 경험을 바탕으로 독립적인 진단을 내리는 것과 비슷합니다.

ex) 랜덤포레스트

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

 

2) 부스팅 : 이전 모델이 잘못 예측한 데이터에 더 많은 가중치를 두어 순차적으로 모델을 개선합니다. 마치 의사가 이전의 잘못된 진단으로부터 배워 진단 능력을 향상시키는 것과 유사합니다.

ex) XGBoost, LightGBM(LGBM)

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

앙상블 모델의 장 ㆍ 단점

장점 단점
1. 더 안정적이고 강건한 예측 가능 1. 학습과 예측에 더 많은 시간과 자원이 필요
2. 과적합 위험을 줄일 수 있음 2. 모델이 복잡해져서 해석이 어려워질 수 있음.
3. 더높은 예측 성능을 얻을 수 있음. 3. 적절한 앙상블 방법과 파라미터 선택이 중요.

 

 

결정트리 : 의사결정 규칙을 나무 형태로 도표화한 것, 일련의 질문들을 통해 최종 결론에 도달하는 방식

  • 트리는 불순도(지니 계수나 엔트로피)가 가장 크게 감소하는 방향으로 특성과 분할 기준을 선택하면서 성장하며, 이 과정은 특정 종료 조건(최대 깊이, 최소 샘플 수 등)에 도달할 때까지 반복됩니다.
     - 불순도 : 각 노드에서 데이터가 얼마나 섞여있는지를 측정하는 지표(노드에 포함된 데이터가 단일 클래스로만 구성되면 불순도는 0, 모든 클래스가 균등하게 섞여 있으면 불순도는 최대)
  • 결정 트리 장단점?
    • 장점 : 모델의 의사결정 과정을 시각적으로 표현할 수 있어 해석이 쉽다
    • 단점 : 과적합되기 쉽다
      • 해결 방법 : 가지치기(pruning)와 같은 기법을 사용합니다.
  • 분류와 회귀 문제 모두에 사용할 수 있으며, 수치형과 범주형 변수를 자연스럽게 처리할 수 있다는 장점도 있습니다. 결정 트리는 단독으로도 사용되지만, 랜덤 포레스트나 그래디언트 부스팅과 같은 더 강력한 앙상블 모델의 기본 구성 요소로도 널리 활용

 

랜덤 포레스트 : 여러 개의 결정 트리(Decision Tree)를 생성하고, 이들의 예측을 종합하여 최종 결정을 내리는 앙상블 모델

 

랜덤 포레스트 작동 원리

부트스트랩 샘플링
  • 원본 데이터에서 복원 추출로 여러 개의 학습 데이터셋을 생성
  • 예를 들어, 10,000명의 대출 데이터가 있다면, 각 트리는 10,000개의 무작위 샘플(중복 허용)로 학습
  • 이 과정에서 일부 데이터는 여러 번 선택되고, 일부는 선택되지 않을 수 있음
특성의 무작위 선택
  • 각 분기점에서 가용한 모든 특성이 아닌 일부 특성만 고려합니다
  • 예를 들어, 총 20개의 특성(소득, 나이, 신용점수 등) 중 무작위로 5개만 선택하여 최적 분할을 찾습니다
  • 이를 통해 각 트리가 서로 다른 관점에서 데이터를 바라보게 됩니다
개별 트리의 학습 각 트리는 서로 다른 데이터와 특성으로 학습되므로, 다음과 같이 다양한 기준으로 판단할 수 있습니다:
  • 트리 1: 소득과 DTI를 중요하게 고려
  • 트리 2: 연체이력과 LTV를 중점적으로 평가
  • 트리 3: 신용점수와 직장 안정성을 주로 확인
앙상블 예측
  • 분류 문제: 각 트리의 예측을 투표로 결정 (예: 100개 트리 중 65개가 '상환가능' 예측) ⇒ 상환가능
  • 회귀 문제: 각 트리의 예측 평균 사용 (예: 상환 확률 평균 65%)

 

 

랜덤 포레스트의 장단점

장점 단점
과적합 위험이 낮음 (여러 트리의 예측을 평균내기 때문) : 여러 트리의 예측을 평균 내어 안정적인 예측 성능을 보장함. 개별 트리의 오류가 상쇄되어 일반화 성능이 향상됨 계산 비용과 자원 요구량 : 다수의 트리를 동시에 운용해야 하므로 메모리와 처리 시간이 많이 필요합니다. 예를 들어 100개의 트리를 사용할 경우, 단일 트리 대비 100배의 자원이 필요할 수 있습니다.
특성 중요도를 쉽게 계산할 수 있음 : 각 특성이 예측에 미치는 영향을 수치화하여 제공, 중요 변수를 쉽게 파악 가능 모델의 복잡성과 해석의 어려움 : 각 트리가 서로 다른 특성과 기준으로 예측을 하기 때문에, 최종 결정의 정확한 이유를 설명하기 힘듦. 
이상치에 강건함 : 여러 트리의 앙상블 특성으로 인해 이상치나 노이즈에 강건한 예측 가능 하이퍼파라미터 튜닝의 복잡성 : 최적의 성능을 얻기 위해서는 여러 하이퍼파라미터(트리의 개수, 최대 깊이, 최소 샘플 수 등)를 적절히 설정해야함.
대규모 데이터셋에서도 잘 작동함 : 대규모 데이터셋에서도 안정적인 성능으로 보이며, 병렬 처리를 통해 학습 효율을 높일 수 있음  

 

코드예시)

# 필요한 라이브러리 임포트
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
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
import seaborn as sns

# 1. 데이터 준비 - 가상의 대출 데이터 생성
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),             # DTI
    'ltv_ratio': np.random.normal(0.6, 0.2, n_samples),             # LTV
    '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)

# 2. 회귀용 연속 타깃 생성 (0~1 사이의 상환 점수 repay_score)
#    – 분류 때 쓰던 규칙을 연속화 + 노이즈 추가 → 시그모이드로 0~1 압축
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 표준화에 가까운 간단한 스케일링(선형결합을 만들기 위한 기준화)
cs_z = (df['credit_score'] - 650) / 100          # 평균 650, 표준편차 100 기준
dti_z = (0.4 - df['dti_ratio']) / 0.1            # DTI 낮을수록 유리하게
emp_z = (df['employment_years'] - 2) / 3
inc_z = (df['income'] - 3000000) / 2000000
ltv_z = (0.7 - df['ltv_ratio']) / 0.2

linear_score = 0.9*cs_z + 0.8*dti_z + 0.6*emp_z + 0.7*inc_z + 0.6*ltv_z
noise = np.random.normal(0, 0.5, size=n_samples)  # 현실적인 잡음
df['repay_score'] = sigmoid(linear_score + noise) # 0~1 연속 타깃

# 3. 데이터 분할
X = df.drop('repay_score', axis=1)
y = df['repay_score']
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 4. 회귀 모델 정의 및 학습 (결정트리 회귀 / 랜덤포레스트 회귀)
dt_reg = DecisionTreeRegressor(
    max_depth=5,
    min_samples_split=50,
    min_samples_leaf=20,
    random_state=42
)
dt_reg.fit(X_train, y_train)

rf_reg = RandomForestRegressor(
    n_estimators=100,
    max_depth=5,
    min_samples_split=50,
    min_samples_leaf=20,
    random_state=42,
    n_jobs=-1
)
rf_reg.fit(X_train, y_train)

# 5. 예측
dt_pred = dt_reg.predict(X_test)
rf_pred = rf_reg.predict(X_test)

# 6. 회귀 평가지표: MSE, RMSE만 출력
dt_mse = mean_squared_error(y_test, dt_pred)
dt_rmse = np.sqrt(dt_mse)
rf_mse = mean_squared_error(y_test, rf_pred)
rf_rmse = np.sqrt(rf_mse)

print("결정트리 회귀")
print("MSE:", round(dt_mse, 6))
print("RMSE:", round(dt_rmse, 6))
print()
print("랜덤포레스트 회귀")
print("MSE:", round(rf_mse, 6))
print("RMSE:", round(rf_rmse, 6))

# (선택) 예측 산점도 간단 시각화 – 모델 한 개만 보기 좋게
plt.figure(figsize=(6,5))
sns.scatterplot(x=y_test, y=rf_pred, alpha=0.4)
plt.plot([0,1],[0,1], ls='--')
plt.xlabel('실제 repay_score')
plt.ylabel('예측 repay_score (RF)')
plt.title('랜덤포레스트 회귀: 실제 vs 예측')
plt.show()

# 7. 새로운 고객 회귀 예측 (연속 점수)
new_customer = pd.DataFrame({
    'income': [6000000],
    'credit_score': [750],
    'employment_years': [5],
    'dti_ratio': [0.3],
    'ltv_ratio': [0.6],
    'age': [35]
})

dt_new = dt_reg.predict(new_customer)[0]
rf_new = rf_reg.predict(new_customer)[0]
print("\n새로운 고객 repay_score 예측 (0~1, 높을수록 상환 가능성이 높음)")
print("결정트리:", round(float(dt_new), 4))
print("랜덤포레스트:", round(float(rf_new), 4))

 

 

 

부스팅 모델

부스팅은 '강화하다' 또는 '북돋우다'라는 의미를 가집니다. 머신러닝에서 부스팅은 약한 학습기(Weak Learner)들을 순차적으로 학습시켜 강한 학습기(Strong Learner)를 만드는 방법입니다.

 

1) 약한 학습기 : 하나의 간단한 기준으로 판단하는 모델, 틀릴 때도 있지만, 완전히 랜덤한 예측보다는 나은수준

  • 생산라인에서 한 가지 검사 항목만 체크하는 것과 같습니다
  • 온도만 체크하거나, 크기만 체크하는 식의 단순한 검사
  • 불량품을 일부 찾아낼 수 있지만, 완벽하지 않습니다

2)  강한 학습기 : 여러가지 조건을 복합적으로 고려하는 모델, 높은 정확도로 불량품을 판별할 수 있는 모델

  • 여러 전문가가 다양한 검사를 수행하는 것과 같습니다
  • 전기 검사, 외관 검사, 성능 검사 등을 모두 통과해야 합격
  • 불량품을 매우 정확하게 찾아낼 수 있습니다

 

 

부스팅 모델의 종류

AdaBoost (Adaptive Boosting)
  • 가장 초기의 부스팅 알고리즘으로, 핵심 알고리즘은 잘못 분류된 데이터에 더 높은 가중치를 부여
  • 이전 모델의 오류에 가중치를 부여하고, 다음 모델은 이 가중치가 높은 데이터에 집중하는 방식 → 어려운 문제를 여러 번 반복해서 푸는 것과 같은 원리
Gradient Boosting Machine (GBM) AdaBoost의 아이디어를 더욱 일반화시킨 알고리즘으로, 경사하강법의 원리를 부스팅에 적용했습니다.
XG Boost (eXtreme Gradient Boosting) GBM의 성능과 속도를 대폭 개선한 알고리즘으로, 기존 GBM의 기본 원리를 유지하면서도 여러 가지 혁신 기능이 추가된 모델입니다.
LightGBM (Light한 GBM) ⇒ LGBM GBM 모델의 파생 모델로, 리프 중심 트리 분할(Leaf-wise tree growth) 방식을 사용한다는 점이 가장 큰 특징 (효율성과 속도)

 

 

모델 선택 가이드

 

AdaBoost를 선택해야 할 때:

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

*GBM을 선택해야 할 때:

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

*XGBoost를 선택해야 할 때:

  • 대규모 데이터셋을 다룰 때
  • 결측치가 많은 데이터를 다룰 때 ⇒ 결측치를 자동으로 처리 해줍니다.
  • 과적합 방지가 중요할 때
  • 높은 예측 성능과 적절한 학습 속도가 모두 필요할 때

*LightGBM을 선택해야 할 때:

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

코드예시)

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}")