Sklearn Random Forest: Python에서 분류와 회귀를 위한 완벽 가이드
Updated on
의사결정나무를 만들어 훈련 정확도 95%를 달성했는데, 새로운 데이터에서는 62%를 기록했다면, 단일 의사결정나무는 훈련 세트를 암기합니다 -- 모든 분기, 모든 잎은 본 샘플에 정확히 맞춰져 있습니다. 결과는 서류상으로는 훌륭해 보이지만 실제 운영에서는 실패하는 모델입니다.
이 과적합(overfitting) 문제는 단순한 학술적 문제가 아닙니다. 개발 노트북에서는 잘 수행되지만 실제 데이터에서 신뢰할 수 없는 예측을 생성하는 모델을 팀이 배포합니다. 단일 의사결정나무는 분산이 높습니다: 훈련 데이터의 작은 변화가 전혀 다른 나무 구조를 생성합니다. 훈련 데이터에 대해 이렇게 민감한 모델은 신뢰할 수 없습니다.
Random Forest는 데이터와 특성의 무작위 하위 집합에 대해 수백 개의 의사결정나무를 구축한 다음, 다수결 투표(분류) 또는 평균(회귀)을 통해 예측을 결합함으로써 이를 해결합니다. 이 앙상블 접근법은 정확도를 유지하면서 분산을 극적으로 감소시킵니다. Scikit-learn의 RandomForestClassifier와 RandomForestRegressor는 내장된 특성 중요도, out-of-bag 평가, 병렬 훈련을 제공하는 프로덕션 준비가 된 구현체를 제공합니다.
Random Forest란 무엇인가?
Random Forest는 여러 의사결정나무를 결합하여 단일하고 더 강건한 예측을 생성하는 앙상블 학습 방법입니다. bagging(Bootstrap Aggregating)이라는 기술을 사용합니다:
- 부트스트랩 샘플링(Bootstrap sampling): 복원 추출을 통해 훈련 데이터의 여러 무작위 하위 집합을 생성합니다. 각 하위 집합은 원본 데이터의 대략 63%입니다.
- 무작위 특성 선택(Random feature selection): 각 나무의 각 분기에서 무작위 특성 하위 집합만 고려합니다(일반적으로 분류의 경우
sqrt(n_features), 회귀의 경우n_features/3). - 독립적 훈련(Independent training): 무작위 특성 제약을 가진 각 부트스트랩 샘플에 대해 의사결정나무를 훈련합니다.
- 집계(Aggregation): 다수결 투표(분류) 또는 평균(회귀)을 통해 예측을 결합합니다.
데이터 샘플링과 특성 선택 모두에서의 무작위성은 개별 나무들이 서로 무상관(decorrelated)이 되도록 보장합니다. 하나의 나무가 특정 패턴에 과적합되더라도, 대다수의 다른 나무들은 그렇지 않을 것이며, 앙상블은 노이즈를 평균화합니다.
Random Forest를 사용해야 하는 경우
| 시나리오 | Random Forest? | 이유 |
|---|---|---|
| 혼합된 특성 유형을 가진 테이블 형태 데이터 | 예 | 수치형과 범주형 특성을 처리하고, 스케일링이 필요 없음 |
| 특성 중요도 순위가 필요한 경우 | 예 | 내장된 feature_importances_ 속성 제공 |
| 중소규모 데이터셋(~100K 행까지) | 예 | 병렬 처리를 통한 빠른 훈련 |
| 불균형 분류 | 예 | class_weight='balanced' 지원 |
| 해석 가능한 예측이 필요한 경우 | 보통 | 개별 나무는 해석 가능하지만 앙상블은 덜함 |
| 매우 고차원의 희소 데이터(텍스트) | 아니오 | 선형 모델이나 그래디언트 부스팅이 일반적으로 더 나음 |
| 엄격한 지연 시간을 요구하는 실시간 추론 | 주의 | 큰 포레스트는 예측 시간이 느릴 수 있음 |
RandomForestClassifier: 분류 예제
다음은 와인 데이터셋을 사용한 완전한 분류 예제입니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
from sklearn.datasets import load_wine
# Load dataset
wine = load_wine()
X, y = wine.data, wine.target
feature_names = wine.feature_names
print(f"Dataset: {X.shape[0]} samples, {X.shape[1]} features")
print(f"Classes: {wine.target_names}")
# Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Train Random Forest
rf = RandomForestClassifier(
n_estimators=100,
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
random_state=42,
n_jobs=-1
)
rf.fit(X_train, y_train)
# Evaluate
y_pred = rf.predict(X_test)
print(f"\nAccuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))주요 매개변수 설명
| 매개변수 | 기본값 | 설명 | 튜닝 팁 |
|---|---|---|---|
n_estimators | 100 | 포레스트의 나무 개수 | 나무가 많을수록 성능은 좋아지지만 느려짐. 100-500이 일반적임 |
max_depth | None | 각 나무의 최대 깊이 | None은 완전 성장. 과적합을 줄이려면 10-30으로 설정 |
min_samples_split | 2 | 노드를 분할하기 위한 최소 샘플 수 | 5-20으로 증가시켜 노이즈 데이터에서 과적합 방지 |
min_samples_leaf | 1 | 리프 노드의 최소 샘플 수 | 더 부드러운 예측을 위해 2-10으로 증가 |
max_features | 'sqrt' | 각 분할에서 고려할 특성 수 | 분류에는 'sqrt', 대안으로 'log2' 또는 비율 |
bootstrap | True | 부트스트랩 샘플링 사용 | 작은 데이터셋의 경우 False로 설정하여 각 나무가 모든 데이터 사용 |
class_weight | None | 각 클래스의 가중치 | 불균형 데이터셋의 경우 'balanced' 사용 |
n_jobs | None | 병렬 작업 수 | -1로 설정하여 모든 CPU 코어 사용 |
oob_score | False | out-of-bag 샘플을 평가에 사용 | 홀드아웃 세트 없이 내장 검증 추정을 위해 True로 설정 |
Out-of-Bag (OOB) 점수
각 나무는 대략 데이터의 63%로 훈련됩니다. 나머지 37%(out-of-bag 샘플)는 무료 검증 세트로 사용할 수 있습니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
rf = RandomForestClassifier(
n_estimators=200,
oob_score=True,
random_state=42,
n_jobs=-1
)
rf.fit(X_train, y_train)
print(f"OOB Score: {rf.oob_score_:.4f}")
print(f"Test Score: {rf.score(X_test, y_test):.4f}")OOB 점수는 별도의 홀드아웃 세트 없이도 검증 추정치를 제공합니다. 데이터가 제한적일 때 특히 유용합니다.
RandomForestRegressor: 회귀 예제
Random Forest 회귀는 모든 나무의 출력을 평균화하여 연속적인 값을 예측합니다:
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.datasets import fetch_california_housing
import numpy as np
# Load California housing dataset
housing = fetch_california_housing()
X, y = housing.data, housing.target
feature_names = housing.feature_names
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Train regressor
rf_reg = RandomForestRegressor(
n_estimators=200,
max_depth=20,
min_samples_leaf=5,
random_state=42,
n_jobs=-1
)
rf_reg.fit(X_train, y_train)
y_pred = rf_reg.predict(X_test)
# Evaluation metrics
r2 = r2_score(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
print(f"R-squared: {r2:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"MAE: {mae:.4f}")회귀 모델 비교
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.datasets import fetch_california_housing
import numpy as np
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
housing.data, housing.target, test_size=0.2, random_state=42
)
regressors = {
'Linear Regression': LinearRegression(),
'Ridge': Ridge(alpha=1.0),
'Decision Tree': DecisionTreeRegressor(max_depth=10, random_state=42),
'Random Forest': RandomForestRegressor(n_estimators=100, max_depth=20, random_state=42, n_jobs=-1),
'Gradient Boosting': GradientBoostingRegressor(n_estimators=100, max_depth=5, random_state=42),
}
print(f"{'Model':<25} {'CV R² (mean)':>12} {'CV R² (std)':>12}")
print("-" * 52)
for name, model in regressors.items():
scores = cross_val_score(model, X_train, y_train, cv=5, scoring='r2', n_jobs=-1)
print(f"{name:<25} {scores.mean():>12.4f} {scores.std():>12.4f}")Random Forest는 일반적으로 단일 의사결정나무나 선형 모델보다 비선형 관계가 있는 데이터셋에서 우수한 성능을 보이며, 그래디언트 부스팅과 경쟁력을 갖습니다.
하이퍼파라미터 튜닝
GridSearchCV: 철저한 검색
GridSearchCV는 지정된 매개변수 값의 모든 조합을 테스트합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import load_wine
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [None, 10, 20],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4],
}
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
grid_search = GridSearchCV(
rf,
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1,
verbose=1
)
grid_search.fit(X_train, y_train)
print(f"Best Parameters: {grid_search.best_params_}")
print(f"Best CV Score: {grid_search.best_score_:.4f}")
print(f"Test Score: {grid_search.score(X_test, y_test):.4f}")RandomizedSearchCV: 효율적인 검색
매개변수 공간이 클 때 RandomizedSearchCV는 모든 조합을 시도하는 대신 고정된 수의 매개변수 조합을 샘플링합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV, train_test_split
from sklearn.datasets import load_wine
from scipy.stats import randint, uniform
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
param_distributions = {
'n_estimators': randint(50, 500),
'max_depth': [None, 5, 10, 15, 20, 30],
'min_samples_split': randint(2, 20),
'min_samples_leaf': randint(1, 10),
'max_features': ['sqrt', 'log2', 0.3, 0.5, 0.7],
'bootstrap': [True, False],
}
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
random_search = RandomizedSearchCV(
rf,
param_distributions,
n_iter=100,
cv=5,
scoring='accuracy',
random_state=42,
n_jobs=-1,
verbose=1
)
random_search.fit(X_train, y_train)
print(f"Best Parameters: {random_search.best_params_}")
print(f"Best CV Score: {random_search.best_score_:.4f}")
print(f"Test Score: {random_search.score(X_test, y_test):.4f}")튜닝을 위한 매개변수 중요도
모든 매개변수가 동등한 영향을 미치는 것은 아닙니다. 가장 중요한 매개변수에 튜닝 예산을 집중하세요:
| 매개변수 | 영향 | 우선순위 | 참고 |
|---|---|---|---|
n_estimators | 높음 | 1위 | 나무가 많을수록 거의 항상 도움이 되나, 수확 체감(~200-500)까지 |
max_depth | 높음 | 2위 | 과적합을 직접 제어. None, 10, 20, 30 시도 |
min_samples_leaf | 중간 | 3위 | 예측을 부드럽게 만듦. 1, 2, 5, 10 시도 |
max_features | 중간 | 4위 | 나무 다양성을 제어. 분류에는 'sqrt'가 일반적으로 좋음 |
min_samples_split | 낮음 | 5위 | 실제로는 min_samples_leaf보다 영향이 적음 |
bootstrap | 낮음 | 6위 | True가 거의 항상 더 나음. 매우 작은 데이터셋에서만 False 시도 |
특성 중요도
Random Forest의 가장 강력한 장점 중 하나는 내장된 특성 중요도입니다. 어떤 특성이 예측을 주도하는지 이해하면 모델 해석, 특성 선택, 도메인 인사이트에 도움이 됩니다.
불순도 기반 특성 중요도
기본 feature_importances_ 속성은 모든 나무에 걸쳐 각 특성이 불순도(분류의 경우 Gini, 회귀의 경우 분산)를 얼마나 감소시키는지 측정합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
# Get feature importances
importances = rf.feature_importances_
feature_names = wine.feature_names
indices = np.argsort(importances)[::-1]
# Print ranked features
print("Feature Ranking:")
for i, idx in enumerate(indices):
print(f" {i+1}. {feature_names[idx]:25s} ({importances[idx]:.4f})")
# Plot
plt.figure(figsize=(10, 6))
plt.barh(range(len(indices)), importances[indices[::-1]], align='center')
plt.yticks(range(len(indices)), [feature_names[i] for i in indices[::-1]])
plt.xlabel('Feature Importance (Gini)')
plt.title('Random Forest Feature Importance - Wine Dataset')
plt.tight_layout()
plt.savefig('rf_feature_importance.png', dpi=150)
plt.show()순열 중요도
불순도 기반 중요도는 고카디널리티 특성에 편향될 수 있습니다. 순열 중요도는 특성의 값을 무작위로 섞었을 때 모델 성능이 얼마나 떨어지는지 측정합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
# Compute permutation importance on the test set
perm_imp = permutation_importance(
rf, X_test, y_test,
n_repeats=30,
random_state=42,
n_jobs=-1
)
# Sort and display
sorted_idx = perm_imp.importances_mean.argsort()[::-1]
print("Permutation Importance (test set):")
for idx in sorted_idx:
mean = perm_imp.importances_mean[idx]
std = perm_imp.importances_std[idx]
print(f" {wine.feature_names[idx]:25s}: {mean:.4f} +/- {std:.4f}")
# Plot with error bars
plt.figure(figsize=(10, 6))
plt.barh(
range(len(sorted_idx)),
perm_imp.importances_mean[sorted_idx[::-1]],
xerr=perm_imp.importances_std[sorted_idx[::-1]],
align='center'
)
plt.yticks(range(len(sorted_idx)), [wine.feature_names[i] for i in sorted_idx[::-1]])
plt.xlabel('Decrease in Accuracy')
plt.title('Permutation Importance - Wine Dataset')
plt.tight_layout()
plt.savefig('rf_permutation_importance.png', dpi=150)
plt.show()어떤 중요도 방법을 사용해야 하나?
| 방법 | 장점 | 단점 | 가장 적합한 경우 |
|---|---|---|---|
불순도 기반 (feature_importances_) | 빠름, 추가 계산 불필요 | 고카디널리티 특성에 편향 | 빠른 스크리닝, 초기 탐색 |
| 순열 중요도 | 편향 없음, 테스트 데이터에서 작동 | 느림, 상관된 특성에 영향 받음 | 최종 특성 선택, 보고 |
Random Forest를 이용한 교차 검증
기본 교차 검증
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_wine
wine = load_wine()
X, y = wine.data, wine.target
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
scores = cross_val_score(rf, X, y, cv=5, scoring='accuracy')
print(f"CV Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")
print(f"Per-fold: {scores}")불균형 데이터를 위한 StratifiedKFold
StratifiedKFold는 각 폴드에서 클래스 분포를 유지하므로 불균형 데이터셋에 필수적입니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.datasets import load_wine
import numpy as np
wine = load_wine()
X, y = wine.data, wine.target
# Stratified 10-fold cross-validation
skf = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
scores = cross_val_score(rf, X, y, cv=skf, scoring='accuracy')
print(f"Stratified 10-Fold Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")
# Multiple metrics
from sklearn.model_selection import cross_validate
results = cross_validate(
rf, X, y, cv=skf,
scoring=['accuracy', 'f1_weighted', 'precision_weighted', 'recall_weighted'],
n_jobs=-1
)
for metric in ['test_accuracy', 'test_f1_weighted', 'test_precision_weighted', 'test_recall_weighted']:
vals = results[metric]
name = metric.replace('test_', '')
print(f"{name:>20s}: {vals.mean():.4f} (+/- {vals.std():.4f})")불균형 데이터 처리
한 클래스가 다른 클래스보다 훨씬 많은 샘플을 가질 때, 모델은 항상 다수 클래스를 예측함으로써 높은 정확도를 달성할 수 있습니다. Random Forest는 이를 처리하기 위한 여러 도구를 제공합니다.
class_weight='balanced' 사용하기
class_weight='balanced' 매개변수는 클래스 빈도에 반비례하여 가중치를 자동으로 조정합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
# Create imbalanced dataset (95% class 0, 5% class 1)
X, y = make_classification(
n_samples=2000,
n_features=20,
weights=[0.95, 0.05],
flip_y=0,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# Without class weight
rf_default = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf_default.fit(X_train, y_train)
print("=== Without class_weight ===")
print(classification_report(y_test, rf_default.predict(X_test)))
# With balanced class weight
rf_balanced = RandomForestClassifier(
n_estimators=200,
class_weight='balanced',
random_state=42,
n_jobs=-1
)
rf_balanced.fit(X_train, y_train)
print("=== With class_weight='balanced' ===")
print(classification_report(y_test, rf_balanced.predict(X_test)))오버샘플링을 위한 SMOTE 통합
SMOTE(소수 클래스 합성 샘플링 기법)는 소수 클래스에 대한 합성 샘플을 생성합니다. imblearn의 파이프라인과 함께 사용하세요:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
# Create imbalanced dataset
X, y = make_classification(
n_samples=2000,
n_features=20,
weights=[0.95, 0.05],
flip_y=0,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# SMOTE + Random Forest pipeline
pipeline = ImbPipeline([
('smote', SMOTE(random_state=42)),
('rf', RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1))
])
pipeline.fit(X_train, y_train)
print("=== SMOTE + Random Forest ===")
print(classification_report(y_test, pipeline.predict(X_test)))모델 평가
분류 보고서와 혼동 행렬
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
classification_report, confusion_matrix,
ConfusionMatrixDisplay, accuracy_score
)
from sklearn.datasets import load_wine
import matplotlib.pyplot as plt
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
# Metrics
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\n{classification_report(y_test, y_pred, target_names=wine.target_names)}")
# Confusion matrix plot
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=wine.target_names)
disp.plot(cmap='Blues')
plt.title('Random Forest - Wine Classification')
plt.tight_layout()
plt.savefig('rf_confusion_matrix.png', dpi=150)
plt.show()이진 분류를 위한 ROC 곡선
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, roc_auc_score, RocCurveDisplay
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, test_size=0.2, random_state=42, stratify=cancer.target
)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
# Predict probabilities
y_prob = rf.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_prob)
# Plot ROC curve
RocCurveDisplay.from_estimator(rf, X_test, y_test)
plt.title(f'Random Forest ROC Curve (AUC = {auc:.4f})')
plt.tight_layout()
plt.savefig('rf_roc_curve.png', dpi=150)
plt.show()Random Forest vs 다른 알고리즘
| 특성 | Random Forest | XGBoost | Gradient Boosting | Decision Tree |
|---|---|---|---|---|
| 앙상블 유형 | Bagging (병렬) | Boosting (순차) | Boosting (순차) | 단일 모델 |
| 정확도 | 높음 | 매우 높음 | 매우 높음 | 보통 |
| 훈련 속도 | 빠름 (병렬 가능) | 보통 | 느림 (순차) | 매우 빠름 |
| 예측 속도 | 보통 | 빠름 | 보통 | 매우 빠름 |
| 과적합 위험 | 낮음 | 낮음 (튜닝 시) | 낮음 (튜닝 시) | 높음 |
| 하이퍼파라미터 민감도 | 낮음 | 높음 | 높음 | 보통 |
| 특성 스케일링 필요 | 불필요 | 불필요 | 불필요 | 불필요 |
| 결측값 처리 | 불가능 (대치 필요) | 가능 (내장) | 불가능 (대치 필요) | 불가능 |
| 내장 특성 중요도 | 있음 | 있음 | 있음 | 있음 |
| 해석 가능성 | 보통 | 낮음 | 낮음 | 높음 |
| 가장 적합한 경우 | 범용, 첫 번째 모델 | Kaggle 대회, 최대 정확도 | 구조화된 테이블 형태 데이터 | 빠른 베이스라인, 작은 데이터셋 |
대안 대신 Random Forest를 선택해야 하는 경우:
- 최소한의 튜닝으로 강력한 베이스라인 모델이 필요할 때
- 훈련 속도가 중요하고 여러 CPU 코어를 보유할 때
- 신뢰할 수 있는 특성 중요도 추정이 필요할 때
- 부스팅 방법이 제공할 수 있는 마지막 0.5%의 정확도를 쫓지 않을 때
실전 파이프라인: 종단간 예제
이 파이프라인은 전처리, 특성 엔지니어링, 모델 훈련, 평가, 예측을 프로덕션 스타일 워크플로우로 결합합니다:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.datasets import load_breast_cancer
import numpy as np
import pandas as pd
# Load and prepare data
cancer = load_breast_cancer()
df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
df['target'] = cancer.target
# Introduce some missing values to simulate real data
np.random.seed(42)
mask = np.random.random(df.shape) < 0.05
df_missing = df.mask(mask.astype(bool))
df_missing['target'] = cancer.target # Keep target clean
X = df_missing.drop('target', axis=1)
y = df_missing['target']
# Split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Build preprocessing + model pipeline
numeric_features = X.columns.tolist()
numeric_transformer = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
])
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
]
)
pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(
n_estimators=300,
max_depth=20,
min_samples_leaf=2,
class_weight='balanced',
random_state=42,
n_jobs=-1
))
])
# Cross-validation
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=skf, scoring='accuracy')
print(f"Cross-validation accuracy: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")
# Train final model
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
# Evaluation
print(f"\nTest Set Results:")
print(classification_report(y_test, y_pred, target_names=cancer.target_names))
# Make predictions on new data
sample = X_test.iloc[:3]
predictions = pipeline.predict(sample)
probabilities = pipeline.predict_proba(sample)
print(f"\nSample Predictions:")
for i, (pred, prob) in enumerate(zip(predictions, probabilities)):
class_name = cancer.target_names[pred]
confidence = prob[pred]
print(f" Sample {i+1}: {class_name} (confidence: {confidence:.2%})")모델 저장 및 로드
import joblib
# Save the trained pipeline
joblib.dump(pipeline, 'rf_pipeline.joblib')
# Load and use later
loaded_pipeline = joblib.load('rf_pipeline.joblib')
new_predictions = loaded_pipeline.predict(X_test[:5])
print(f"Loaded model predictions: {new_predictions}")PyGWalker로 결과 탐색하기
Random Forest 모델을 훈련한 후, 특성 중요도 패턴, 예측 분포, 오분류 사례를 자세히 탐색해야 할 때가 많습니다. PyGWalker (opens in a new tab)를 사용하면 결과 DataFrame을 Jupyter에서 바로 Tableau와 같은 대화형 탐색 인터페이스로 변환할 수 있습니다:
import pandas as pd
import pygwalker as pyg
# Build a results DataFrame
results = pd.DataFrame(X_test.values, columns=cancer.feature_names)
results['actual'] = y_test.values
results['predicted'] = y_pred
results['correct'] = y_test.values == y_pred
results['prob_malignant'] = pipeline.predict_proba(X_test)[:, 0]
results['prob_benign'] = pipeline.predict_proba(X_test)[:, 1]
# Launch interactive exploration
walker = pyg.walk(results)특성을 축으로 드래그하고, 오분류된 샘플로 필터링하며, 예측 신뢰도로 색상 코딩하여 모델이 어려워하는 부분을 파악하세요. 이러한 시각적 분석은 어떤 특성을 엔지니어링하거나 어떤 샘플을 더 자세히 검사할지 결정하는 데 도움이 됩니다.
전체 ML 실험 워크플로우를 실행하기 위해 -- 데이터 로딩부터 모델 비교, 최종 평가까지 -- RunCell (opens in a new tab)은 실험을 더 빠르게 반복하고, 평가 코드를 자동 생성하며, 노트북 워크플로우를 관리하는 AI 기반 Jupyter 환경을 제공합니다.
자주 묻는 질문
Random Forest에서 몇 개의 나무를 사용해야 하나요?
100-200개의 나무로 시작하세요. 나무가 많을수록 정확도가 일반적으로 향상되지만 특정 지점 이후에는 평탄해집니다. 교차 검증을 사용하여 적절한 지점을 찾으세요. 500개를 넘어서면 이득은 보통 미미하고 훈련 시간이 증가합니다. n_estimators를 늘리면서 OOB 점수를 모니터링하세요 -- 더 이상 개선되지 않을 때 충분한 나무를 가진 것입니다.
Random Forest에 특성 스케일링이 필요한가요?
아니요. Random Forest는 특성 값 임계값을 기반으로 분기를 생성하므로, 특성의 절대적인 스케일은 분기 결정에 영향을 미치지 않습니다. 로지스틱 회귀, SVM, 또는 신경망과 달리, Random Forest는 자연스럽게 다른 범위의 특성을 처리합니다. 그러나 파이프라인에 다른 구성 요소(PCA 또는 거리 기반 전처리 등)가 포함된 경우, 해당 단계에서는 여전히 스케일링이 필요할 수 있습니다.
Random Forest는 결측값을 어떻게 처리하나요?
Scikit-learn의 RandomForestClassifier와 RandomForestRegressor는 기본적으로 결측값을 처리하지 않습니다. 훈련 전에 결측 데이터를 대치해야 합니다 -- 수치형 특성의 경우 중간값 또는 평균 전략을 사용하는 SimpleImputer를 사용하거나, IterativeImputer와 같은 더 고급 대치 방법을 사용하세요. H2O나 LightGBM과 같은 다른 구현체는 결측값을 직접 처리할 수 있습니다.
Random Forest와 Gradient Boosting의 차이점은 무엇인가요?
Random Forest는 병렬로 독립적으로 나무를 구축하는 반면(bagging), Gradient Boosting은 순차적으로 나무를 구축하여 이전 나무의 오류를 수정합니다(boosting). Random Forest는 분산을 감소시키고, Gradient Boosting은 편향을 감소시킵니다. 실제로 Gradient Boosting(특히 XGBoost)은 약간 더 높은 정확도를 달성하는 경우가 많지만, Random Forest는 튜닝이 더 쉽고 과적합에 덜 취약합니다.
Random Forest를 특성 선택에 사용할 수 있나요?
예. feature_importances_를 사용하여 빠른 순위를 매기거나, permutation_importance를 사용하여 더 신뢰할 수 있는 추정치를 얻으세요. 그런 다음 중요도가 낮은 특성을 제거하고 재훈련할 수 있습니다. 또는 임계값 이상의 특성을 자동으로 선택하기 위해 파이프라인 내에서 Random Forest 추정기와 함께 SelectFromModel을 사용하세요.
결론
Random Forest는 머신러닝에서 가장 신뢰할 수 있고 다재다능한 알고리즘 중 하나입니다. 수백 개의 무상관 의사결정나무를 결합하여 과적합을 감소시키고, 특성 스케일링 없이 분류와 회귀 작업을 모두 처리하며, 내장된 특성 중요도 순위를 제공합니다. 대부분의 테이블 형태 데이터 문제에 대해 프로덕션 사용에 충분히 잘 수행되는 우수한 첫 번째 모델로 사용할 수 있습니다.
RandomForestClassifier 또는 RandomForestRegressor를 기본 매개변수로 시작하여 베이스라인으로 삼으세요. 수확 체감 분석을 위해 먼저 n_estimators를 튜닝하고, 과적합을 제어하기 위해 max_depth와 min_samples_leaf를 튜닝하세요. 불균형 데이터의 경우 class_weight='balanced'를 사용하고, 신뢰할 수 있는 특성 순위를 위해 순열 중요도를 사용하며, 강건한 평가를 위해 StratifiedKFold 교차 검증을 사용하세요. 구조화된 데이터에서 절대적인 최고 정확도가 필요할 때는 Gradient Boosting이나 XGBoost를 고려하되, Random Forest는 거의 실패하지 않는 가장 안전한 기본 선택으로 남아있습니다.