결정 트리를 처음 배울 때 꽤 만족스러웠던 기억이 있습니다. 분기 조건이 눈에 보이고, 어떤 변수가 예측에 작용했는지 직접 확인할 수 있으니까요.
그런데 실제로 여러 데이터에 적용해보면 생각보다 결과가 불안정하다는 걸 금방 느끼게 됩니다. 훈련 데이터를 조금만 바꿔도 트리 구조가 크게 달라지고, 깊게 학습할수록 새로운 데이터에서는 성능이 뚝 떨어집니다. 랜덤 포레스트는 바로 그 지점에서 출발합니다.
오늘은 결정 트리의 어떤 한계가 랜덤 포레스트라는 아이디어로 이어지는지, 그리고 왜 나무가 많아질수록 더 안정적인 예측이 가능해지는지 정리해보겠습니다. 🌲
1. 결정 트리의 구조적 한계

결정 트리는 훈련 데이터의 패턴을 분기 조건으로 직접 학습합니다. 깊이 제한 없이 학습하면 노이즈까지 전부 외워버리는 과적합이 발생하는데, 이건 단순히 깊이 문제가 아닙니다. 더 근본적인 구조적 약점이 있습니다.
바로 분산(Variance)이 크다는 것입니다.
분산이 크다는 건, 훈련 데이터가 조금 달라지면 모델이 크게 바뀐다는 뜻입니다.
같은 분포에서 뽑은 두 데이터셋으로 각각 결정 트리를 학습시키면, 두 트리의 구조가 상당히 달라지는 경우가 많습니다.
샘플링의 작은 차이가 트리 전체를 흔들어버리기 때문입니다.
1.1 분산을 줄이는 통계적 원리
통계적으로, 독립적인 확률변수 N개의 평균의 분산은 개별 분산의 1/N입니다.
Var(X̄) = σ² / N
즉, 서로 독립적인 모델을 많이 만들어 평균 내면 분산이 줄어듭니다. 이 원리를 실제로 구현하려면 두 가지 조건이 필요합니다.
- 모델들이 충분히 정확해야 한다 (편향이 너무 크면 안 된다)
- 모델들이 서로 독립적이어야 한다 (상관관계가 높으면 분산 감소 효과가 사라진다)
랜덤 포레스트의 설계 전체가 이 두 조건을 동시에 충족시키는 방향으로 만들어져 있습니다. 어떻게 그걸 구현하는지 하나씩 살펴보겠습니다.
2. 배깅: 독립적인 트리를 만드는 방법

랜덤 포레스트의 기반이 되는 기법이 배깅(Bagging, Bootstrap Aggregating)입니다. 아이디어는 단순합니다. 하나의 훈련 데이터에서 복원 추출로 여러 서브셋을 만들고, 각각 다른 트리를 학습시킨 뒤 예측을 합산합니다.
2.1 복원 추출이 만드는 다양성
복원 추출(Bootstrap Sampling)은 하나를 뽑고 다시 돌려놓은 뒤 다음 것을 뽑는 방식입니다. N개 데이터에서 복원 추출로 N개를 뽑으면 수학적으로 약 63.2%의 데이터만 선택됩니다.
선택되지 않은 나머지 약 36.8%가 OOB(Out-of-Bag) 샘플입니다. 이 데이터는 해당 트리의 학습에 전혀 사용되지 않았으므로, 별도 검증 셋 없이도 자체적으로 성능을 평가하는 데 활용할 수 있습니다.
| 구분 | 내용 |
|---|---|
| 서브셋 크기 | 원본 데이터와 동일한 N개 (복원 추출) |
| 중복 허용 | 같은 샘플이 여러 번 뽑힐 수 있음 |
| OOB 샘플 | 선택되지 않은 약 37% → 자체 검증 활용 |
| 목적 | 트리마다 서로 다른 데이터를 보게 해서 다양성 확보 |
복원 추출로 만들어진 서브셋들은 서로 다르기 때문에, 각 서브셋에서 학습된 트리들도 서로 다른 구조를 갖게 됩니다. 이게 배깅이 트리 간 다양성을 만드는 방식입니다.
3. 랜덤 포레스트: 피처까지 랜덤하게
배깅만으로는 트리들 사이의 상관관계를 완전히 제거하기 어렵습니다. 모든 트리가 전체 피처를 동일하게 보기 때문에, 예측력이 강한 피처가 있다면 모든 트리가 그 피처를 중심으로 비슷하게 학습됩니다.
랜덤 포레스트는 여기서 한 가지를 더 추가합니다.
각 분기(split)마다 전체 피처 중 일부만 랜덤하게 선택해서, 그 안에서만 최선의 분기 기준을 찾습니다.
데이터의 무작위성(Bagging) + 피처의 무작위성(Feature Randomness)
→ 트리들 사이의 상관관계 감소
→ 독립적인 예측들의 평균 → 분산 감소
이 두 가지 무작위성이 함께 작동할 때 비로소 분산 감소 효과가 극대화됩니다. 트리를 많이 만든다고 무조건 좋아지는 게 아니라, 서로 충분히 다른 트리들이 많아야 의미가 있는 것입니다.
3.1 피처 샘플링 기준 (max_features)
분기마다 고려할 피처 수는 max_features 하이퍼파라미터로 결정합니다. 사이킷런의 기본값은 아래와 같습니다.
| 문제 유형 | 기본값 | 예시 |
|---|---|---|
| 분류 (Classification) | √p | 피처 100개 → 분기마다 10개만 고려 |
| 회귀 (Regression) | p/3 | 피처 30개 → 분기마다 10개만 고려 |
값을 너무 줄이면 각 트리의 예측 자체가 부정확해지고(편향 증가), 너무 늘리면 트리들이 비슷해져 분산 감소 효과가 사라집니다. 기본값은 이 균형을 경험적으로 찾은 결과입니다.
4. 예측 집계 방식
수백 개의 트리가 각자 예측을 내놓으면, 최종 예측은 다음 방식으로 결정됩니다.
| 문제 유형 | 집계 방식 | 예시 |
|---|---|---|
| 분류 | 다수결 투표 (Majority Voting) | 100개 트리 중 70개가 "1" → 최종 "1" |
| 회귀 | 평균 (Averaging) | 100개 트리 예측값의 산술 평균 → 최종값 |
개별 트리는 틀릴 수 있습니다. 그러나 서로 다른 관점에서 학습된 독립적인 트리들이 충분히 모이면, 각자의 오차가 서로 상쇄됩니다. 이것이 단일 트리보다 집단의 판단이 안정적인 이유입니다.
5. 특성 중요도 (Feature Importance)
랜덤 포레스트를 학습하고 나면 feature_importances_ 속성을 통해 각 변수가 예측에 얼마나 기여했는지 확인할 수 있습니다.
계산 원리는 다음과 같습니다. 특정 변수로 분기할 때 불순도(Gini 또는 Entropy)가 얼마나 감소했는지를 전체 트리에 걸쳐 가중 평균한 값입니다. 값이 클수록 해당 변수가 예측에 많이 기여했다는 의미입니다.
5.1 주의사항: 고카디널리티 변수 과대평가 문제
고유값이 많은 변수(예: ID, 날짜, 우편번호)는 분기 기회가 많아 중요도가 과대평가되는 경향이 있습니다. 이 경우 순열 중요도(Permutation Importance)를 함께 확인하는 것이 좋습니다.
순열 중요도는 특정 변수의 값을 랜덤하게 섞었을 때 모델 성능이 얼마나 떨어지는지를 측정합니다. 변수를 섞었는데 성능이 크게 떨어지면 그 변수가 실제로 중요하다는 뜻입니다. 불순도 기반 중요도보다 신뢰할 수 있는 측정값을 얻을 수 있습니다.
6. 코드로 확인하기
사이킷런으로 랜덤 포레스트를 학습하고 OOB 점수와 특성 중요도를 확인해보겠습니다.
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.inspection import permutation_importance
import pandas as pd
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
rf = RandomForestClassifier(
n_estimators=100,
max_features='sqrt', # 분기마다 √p개 피처만 고려
oob_score=True, # OOB 자체 검증 활성화
random_state=42
)
rf.fit(X_train, y_train)
print(f"테스트 정확도 : {rf.score(X_test, y_test):.4f}")
print(f"OOB 정확도 : {rf.oob_score_:.4f}")
# 불순도 기반 특성 중요도
imp_mdi = pd.Series(
rf.feature_importances_,
index=iris.feature_names
).sort_values(ascending=False)
print("\n[불순도 기반 Feature Importance]")
print(imp_mdi)
# 순열 중요도 (더 신뢰할 수 있는 측정)
result = permutation_importance(rf, X_test, y_test, n_repeats=10, random_state=42)
imp_perm = pd.Series(
result.importances_mean,
index=iris.feature_names
).sort_values(ascending=False)
print("\n[순열 기반 Permutation Importance]")
print(imp_perm)
테스트 정확도 : 1.0000
OOB 정확도 : 0.9583
[불순도 기반 Feature Importance]
petal length (cm) 0.441
petal width (cm) 0.415
sepal length (cm) 0.113
sepal width (cm) 0.031
[순열 기반 Permutation Importance]
petal length (cm) 0.352
petal width (cm) 0.311
sepal length (cm) 0.018
sepal width (cm) 0.002
두 방식 모두 꽃잎(petal) 관련 변수가 압도적으로 중요하다는 결과는 같습니다. 다만 sepal length의 경우 불순도 기반에서는 0.113이지만 순열 기반에서는 0.018로 크게 낮아지는 걸 확인할 수 있습니다. 실무에서 변수 해석이 중요한 상황이라면 두 값을 함께 보는 습관을 들이는 게 좋습니다.
OOB 정확도(0.958)가 테스트 정확도와 큰 차이 없이 나온다는 것도 주목할 만합니다. 데이터가 충분하지 않아 검증 셋 분리가 부담스러운 상황에서 유용하게 활용할 수 있습니다.
7. 결정 트리 vs 랜덤 포레스트
| 항목 | 결정 트리 | 랜덤 포레스트 |
|---|---|---|
| 해석 가능성 | 트리 구조 직접 확인 가능 | 개별 트리 해석 어려움 |
| 분산 | 높음 (과적합 취약) | 낮음 (과적합에 강함) |
| 학습 속도 | 빠름 | 트리 수에 비례해 느려짐 |
| 전처리 필요성 | 낮음 | 낮음 (스케일링 불필요) |
| 변수 중요도 | 단일 트리 기준 | 전체 트리 평균 → 더 안정적 |
| 검증 | 별도 검증 셋 필요 | OOB로 자체 검증 가능 |
정리
랜덤 포레스트가 결정 트리보다 강한 이유는 단순히 모델을 많이 만들어서가 아닙니다.
- 데이터 무작위성(Bagging): 복원 추출로 트리마다 서로 다른 서브셋을 학습
- 피처 무작위성: 분기마다 일부 피처만 고려해 트리 간 상관관계를 낮춤
- 이 두 가지가 함께 작동해야 분산 감소 효과가 실제로 나타남
나무가 많아질수록 강해지는 이유는 서로 독립적인 관점을 가진 트리들이 각자의 오차를 상쇄하기 때문입니다. 다양하게 쌓는 것이 핵심이지, 단순히 많이 쌓는 것과는 다릅니다.
다음 글에서는 XGBoost와 LightGBM을 다룹니다. 랜덤 포레스트가 트리들을 독립적으로 병렬 학습하는 것과 달리, 부스팅은 이전 트리의 오답을 다음 트리가 집중적으로 학습합니다. 같은 트리 계열이지만 접근 방식이 완전히 다른 만큼, 어떤 상황에서 어떤 모델이 유리한지도 함께 정리해보겠습니다. 🙂
'AI & ML > ML' 카테고리의 다른 글
| [머신러닝] XGBoost & LightGBM: 부스팅은 왜 순서가 중요한가 (0) | 2026.05.25 |
|---|---|
| [머신러닝] 결정 트리: 모델이 스스로 질문을 만드는 방법 (0) | 2026.05.06 |
| [머신러닝] 로지스틱 회귀: 분류 문제를 확률로 푸는 방법 (0) | 2026.04.26 |
| [머신러닝] 선형 회귀: 가장 단순한 모델의 작동 원리 (0) | 2026.04.26 |
| [머신러닝] 지도학습(회귀모델 vs 분류모델) (3) | 2026.02.17 |
HELLO WORLD
안녕하세요. 데이터로 말하는 분석가 모모입니다.
데이터를 구조화하고 분석하는 과정과 실무에 활용되는 도구 중심의 내용을 기록합니다.