1. 오늘 배운것
앙상블 모델 : 여러 모델의 예측을 결합하여 더 나은 결과를 얻는 방법
1) 배깅 : 원래 데이터에서 여러 번 샘플링하여 다양한 데이터셋을 만들고, 각각에 대해 독립적으로 모델을 학습시킵니다. 마치 여러 의사가 각자의 경험을 바탕으로 독립적인 진단을 내리는 것과 비슷합니다.
ex) 랜덤포레스트
- 여러 개의 결정 트리를 독립적으로 학습
- 각 트리가 서로 다른 데이터와 특성을 사용
2) 부스팅 : 이전 모델이 잘못 예측한 데이터에 더 많은 가중치를 두어 순차적으로 모델을 개선합니다. 마치 의사가 이전의 잘못된 진단으로부터 배워 진단 능력을 향상시키는 것과 유사합니다.
ex) XGBoost, LightGBM(LGBM)
- 이전 모델의 오차를 보완하는 방향으로 학습
- 높은 예측 성능으로 실무에서 널리 사용됨
앙상블 모델의 장 ㆍ 단점
| 장점 | 단점 |
| 1. 더 안정적이고 강건한 예측 가능 | 1. 학습과 예측에 더 많은 시간과 자원이 필요 |
| 2. 과적합 위험을 줄일 수 있음 | 2. 모델이 복잡해져서 해석이 어려워질 수 있음. |
| 3. 더높은 예측 성능을 얻을 수 있음. | 3. 적절한 앙상블 방법과 파라미터 선택이 중요. |
결정트리 : 의사결정 규칙을 나무 형태로 도표화한 것, 일련의 질문들을 통해 최종 결론에 도달하는 방식
- 트리는 불순도(지니 계수나 엔트로피)가 가장 크게 감소하는 방향으로 특성과 분할 기준을 선택하면서 성장하며, 이 과정은 특정 종료 조건(최대 깊이, 최소 샘플 수 등)에 도달할 때까지 반복됩니다.
- 불순도 : 각 노드에서 데이터가 얼마나 섞여있는지를 측정하는 지표(노드에 포함된 데이터가 단일 클래스로만 구성되면 불순도는 0, 모든 클래스가 균등하게 섞여 있으면 불순도는 최대)
- 결정 트리 장단점?
- 장점 : 모델의 의사결정 과정을 시각적으로 표현할 수 있어 해석이 쉽다
- 단점 : 과적합되기 쉽다
- 해결 방법 : 가지치기(pruning)와 같은 기법을 사용합니다.
- 분류와 회귀 문제 모두에 사용할 수 있으며, 수치형과 범주형 변수를 자연스럽게 처리할 수 있다는 장점도 있습니다. 결정 트리는 단독으로도 사용되지만, 랜덤 포레스트나 그래디언트 부스팅과 같은 더 강력한 앙상블 모델의 기본 구성 요소로도 널리 활용

랜덤 포레스트 : 여러 개의 결정 트리(Decision Tree)를 생성하고, 이들의 예측을 종합하여 최종 결정을 내리는 앙상블 모델
랜덤 포레스트 작동 원리
| 부트스트랩 샘플링 |
|
| 특성의 무작위 선택 |
|
| 개별 트리의 학습 | 각 트리는 서로 다른 데이터와 특성으로 학습되므로, 다음과 같이 다양한 기준으로 판단할 수 있습니다:
|
| 앙상블 예측 |
|
랜덤 포레스트의 장단점
| 장점 | 단점 |
| 과적합 위험이 낮음 (여러 트리의 예측을 평균내기 때문) : 여러 트리의 예측을 평균 내어 안정적인 예측 성능을 보장함. 개별 트리의 오류가 상쇄되어 일반화 성능이 향상됨 | 계산 비용과 자원 요구량 : 다수의 트리를 동시에 운용해야 하므로 메모리와 처리 시간이 많이 필요합니다. 예를 들어 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}")