Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

13 P4 실습: 결정 트리와 앙상블 학습

Updated: 30 okt 2026

감사의 글

오렐리앙 제롱Aurélien Géron의 Hands-On Machine Learning with Scikit-Learn and PyTorch (O’Reilly, 2025)에 사용된 코드를 참고한 강의노트이다. 보다 심화된 이해를 위해 책 원본을 읽을 것을 강력하게 권장한다. 자료를 공개한 오렐리앙 제롱과 일부 그림 자료를 제공해 준 한빛아카데미에게 진심어린 감사를 전한다.

코드 실행

(구글 코랩) P4 실습: 결정트리와 앙상블 학습에서 코드를 실행할 수 있다.

환경설정

Python: 3.13.12
scikit-learn: 1.8.0

13.11단계: 결정 트리는 어떻게 분류하는가?

결정 트리는 입력 특성에 관한 질문을 반복하여 데이터를 나눈다. 각 갈림길의 조건을 따라 내려가면 마지막 잎 노드(leaf node)에서 예측이 결정된다. 이 예제에서는 붓꽃의 꽃잎 길이와 너비만 사용한다.

붓꽃 데이터셋(Iris dataset)은 150개 붓꽃의 길이·너비 정보를 담고 있으며, 품종은 setosa, versicolor, virginica 세 클래스다. 원래 특성은 꽃받침 길이·너비와 꽃잎 길이·너비의 4개지만, 이 실습에서는 분할과 결정 경계를 시각화하기 위해 꽃잎 길이·너비 2개만 사용한다. 각 품종의 샘플은 50개씩이다.

이 단계의 트리 구조 및 훈련 정확도는 모델의 분할 원리 관찰용이다. 새로운 데이터의 성능을 평가할 때는 별도 검증·테스트 데이터가 필요하다.

   petal length (cm)  petal width (cm)
0                1.4               0.2
1                1.4               0.2
2                1.3               0.2
3                1.5               0.2
4                1.4               0.2
클래스: [np.str_('setosa'), np.str_('versicolor'), np.str_('virginica')]

13.1.1트리를 훈련하고 구조 확인하기

max_depth는 결정 트리가 루트 노드에서 잎 노드까지 얼마나 깊게 분할할 수 있는지 정하는 하이퍼파라미터(hyperparameter)다. 루트 노드의 깊이는 0이다.

먼저 max_depth=2로 트리를 훈련한다. 트리 그림에서 각 노드의 분할 특성, 임곗값, 불순도와 샘플 수를 관찰하자.

다른 잎 노드의 지니 불순도 직접 계산하기

깊이 2 트리의 가장 오른쪽 잎 노드에 도달한 샘플을 세어 보자. 해당 노드의 클래스 비율로 지니 불순도를 직접 계산한 뒤, 트리가 저장한 impurity 값과 비교한다. 앞서 소개한 50:50 노드가 아니라 한 클래스가 압도적으로 많은 노드를 사용한다.

<Figure size 1200x600 with 1 Axes>
오른쪽 잎의 품종별 샘플 수: {np.str_('setosa'): np.int64(0), np.str_('versicolor'): np.int64(1), np.str_('virginica'): np.int64(45)}
직접 계산한 지니 불순도: 0.0425
트리의 지니 불순도: 0.0425

13.1.2깊이가 2인 트리와 3인 트리 비교하기

이번에는 같은 붓꽃 데이터와 같은 특성을 사용하되 max_depth=3으로 트리를 훈련한다.

  • max_depth=2: 루트에서 최대 두 번의 분할을 거쳐 잎 노드에 도달한다.

  • max_depth=3: 필요한 경우 한 번 더 분할할 수 있으므로 더 세밀한 규칙을 만들 수 있다.

max_depth=3이라고 해서 모든 가지가 깊이 3까지 자라는 것은 아니다. 추가 분할이 필요하지 않으면 더 일찍 멈출 수 있다. 아래 두 트리의 실제 깊이, 잎 노드 수와 구조를 비교해 보자.

         모델  실제 깊이  잎 노드 수   훈련 정확도
max_depth=2      2       3 0.960000
max_depth=3      3       5 0.973333
<Figure size 1800x600 with 2 Axes>

13.1.3예측 클래스와 예측 확률

새 샘플은 트리의 분할 규칙을 따라 한 잎에 도착한다. predict_proba()는 해당 잎의 훈련 샘플들이 각 클래스에 속하는 비율에 기초한 확률을 반환한다.

예측 확률: [[0.    0.907 0.093]]
예측 클래스: versicolor

13.1.4결정 경계 확인하기

트리는 축에 수직인 분할을 반복한다. 아래 그림에서 영역의 색이 바뀌는 위치와 트리의 임곗값을 비교한다.

<Figure size 800x500 with 1 Axes>

13.1.5깊이에 따른 결정 경계 비교하기

두 모델의 결정 경계를 같은 축에서 비교해 보자. 깊이 3인 트리는 깊이 2인 트리보다 영역을 더 세분할 수 있지만, 훈련 정확도가 높아진다고 해서 새로운 데이터의 성능도 반드시 높아지는 것은 아니다. 일반화 성능 비교는 2단계에서 훈련셋과 테스트셋을 나누어 살펴본다.

<Figure size 1200x400 with 2 Axes>

확인 질문

  • 결정 트리가 새 샘플을 분류하는 과정을 루트 노드, 분할 조건, 잎 노드로 설명해 보자.

  • 트리의 max_depth는 모델 파라미터와 하이퍼파라미터 중 무엇인가?

  • 결정 경계가 주로 가로·세로 선으로 나타나는 이유는 무엇인가?

  • max_depth=2와 max_depth=3의 트리 그림 및 결정 경계를 비교했을 때 어떤 분할이 추가되었는가?

  • max_depth=3의 훈련 정확도가 같거나 높더라도 테스트 성능까지 더 좋다고 말할 수 있는가?

13.22단계: 트리가 복잡해지면 성능은 어떻게 변하는가?

초승달 데이터로 훈련셋과 테스트셋을 나눈다. 훈련 성능뿐 아니라 별도로 보관한 테스트 성능을 함께 살펴보자. 이 절에서는 모델 구조의 차이를 시각화하기 위해 두 모델만 비교하며, 테스트셋을 이용해 하이퍼파라미터를 탐색하지 않는다.

훈련: (375, 2) 테스트: (125, 2)

13.2.1규제하지 않은 트리와 규제한 트리 비교

min_samples_leaf=5는 잎 노드에 남아야 하는 최소 훈련 샘플 수를 제한한다. 이러한 제한은 복잡한 분할을 억제한다.

Loading...
<Figure size 1100x400 with 2 Axes>

확인 질문

  • 훈련 정확도가 더 높다고 해서 반드시 일반화 성능이 더 좋은가?

  • min_samples_leaf를 크게 하면 트리 구조는 일반적으로 어떻게 달라지는가?

  • 결정 트리가 과대적합하기 쉬운 이유를 그림과 함께 설명해 보자.

13.2.2교차 검증으로 복잡도 선택하기

P1에서 사용한 GridSearchCV를 결정 트리에도 적용할 수 있다. 후보 하이퍼파라미터는 훈련셋 내부의 교차 검증으로 선택하고, 테스트셋은 마지막에만 평가한다.

선택된 설정: {'max_depth': 6, 'min_samples_leaf': 5}
교차 검증 정확도: 0.941
최종 테스트 정확도: 0.944

13.33단계: 여러 트리의 예측을 결합하면 어떻게 될까?

앙상블 학습(ensemble learning)은 여러 예측기의 결과를 결합한다. 배깅(bagging)은 훈련셋에서 중복을 허용하여 샘플을 뽑는 부트스트랩(bootstrap)을 이용해 각 모델을 훈련한다. 분류에서는 여러 모델의 예측을 결합한다.

BaggingClassifier의 기본 설정은 각 모델에서 모든 특성을 사용(max_features=1.0)하면서, 샘플은 복원추출(bootstrap=True)하는 것이다. max_samples=1.0이어도 복원추출이므로 같은 샘플이 여러 번 선택되고 선택되지 않은 샘플도 생긴다.

max_features를 줄이면 트리마다 사용할 특성의 일부를 선택할 수 있다. 이 선택은 각 트리를 훈련할 때 이루어지고, 랜덤 포레스트처럼 각 노드에서 새롭게 특성을 선택하는 방식과는 다르다.

같은 훈련 샘플이 중복해서 뽑힐 수 있고, 선택되지 않은 샘플은 해당 트리에 대한 OOB가 된다.

Loading...

13.3.1OOB 평가 이해하기

부트스트랩 추출에서 특정 트리의 훈련에 선택되지 않은 샘플을 OOB(out-of-bag) 샘플이라고 한다. 이를 이용해 훈련 과정 안에서 추가적인 평가 신호를 얻을 수 있다. OOB가 별도의 최종 테스트셋을 대신하는 것은 아니다.

OOB의 비율: 훈련 샘플 nn개 중 nn번 복원추출하면 한 트리의 OOB는 평균 약 **36.8%**다. 하지만 현재 실습은 max_samples=0.8이므로 한 트리당 약 **44.9%**가 OOB로 남는다. 아래 코드에서 각 트리의 OOB 비율을 실제로 계산해 이론적인 근삿값과 비교해 보자. 이 값은 트리별 평균이며 전체 학습 데이터에서 따로 떼어 놓은 테스트셋 비율이 아니다.

P(OOB)=(1−1n)k≈e−k/nP(\mathrm{OOB}) = \left(1-\frac{1}{n}\right)^k \approx e^{-k/n}
일반 부트스트랩(max_samples=1.0) 예상 OOB: 36.8%
현재 설정(max_samples=0.8) 예상 OOB: 44.9%
현재 배깅 트리별 평균 실제 OOB: 45.2%
OOB 정확도: 0.949
최종 테스트 정확도: 0.952

확인 질문

  • 배깅에서 부트스트랩은 무엇을 의미하는가?

  • 하나의 트리와 여러 트리의 투표 결과가 달라질 수 있는 이유는 무엇인가?

  • OOB 샘플은 어떤 트리에 대해 '훈련에 사용되지 않은 샘플’인가?

  • max_samples=0.8과 max_features=1.0은 각각 무엇을 지정하는가?

13.44단계: 랜덤 포레스트는 배깅과 무엇이 다른가?

랜덤 포레스트(random forest)는 여러 결정 트리를 결합할 뿐 아니라 각 노드에서 분할 후보 특성을 무작위로 선택한다. 반면 앞 단계의 기본 BaggingClassifier는 각 트리가 모든 특성을 사용할 수 있다.

예를 들어 RandomForestClassifier(max_features="sqrt")는 각 분할에서 전체 특성 수의 제곱근 정도를 후보로 고려한다. BaggingClassifier(max_features=0.7)은 트리마다 사용할 특성 70%를 미리 선택한다는 점이 다르다.

분류에서는 나무들의 결과를 결합하고, 회귀에서는 여러 트리의 예측값을 평균한다.

배깅과 마찬가지로 여러 트리를 결합하지만, 랜덤 포레스트는 각 트리의 노드에서 분할 후보 특성 일부를 다시 무작위로 선택한다. 아래 실습에서 트리 수와 테스트 성능을 확인하자.

Loading...
<Figure size 1400x400 with 3 Axes>

13.4.1랜덤 포레스트의 특성 중요도

랜덤 포레스트의 feature_importances_는 트리 분할 과정에서 불순도를 줄인 정도를 바탕으로 산출한 값이다. 크다고 해서 인과관계가 입증되거나, 해당 특성만으로 결과를 설명할 수 있는 것은 아니다.

petal length (cm)    0.459290
petal width (cm)     0.417860
sepal length (cm)    0.101969
sepal width (cm)     0.020881
dtype: float64
<Figure size 700x300 with 1 Axes>

확인 질문

  • 랜덤 포레스트와 단일 결정 트리의 차이를 설명해 보자.

  • 특성 중요도가 높다는 사실만으로 인과관계를 주장할 수 있는가?

  • 여러 트리의 예측 결과를 결합하는 이유는 무엇인가?

13.4.2랜덤 포레스트 회귀: 여러 트리의 예측값 평균하기

랜덤 포레스트는 연속적인 숫자를 예측하는 회귀(regression)에도 사용할 수 있다. 아래에서는 곡선 형태의 데이터를 훈련셋과 검증셋으로 나누고, 단일 회귀 트리와 랜덤 포레스트를 비교한다. 랜덤 포레스트 회귀는 여러 회귀 트리의 예측값을 평균한다.

Loading...
<Figure size 800x400 with 1 Axes>

확인 질문

  • 랜덤 포레스트 회귀가 여러 트리의 결과를 결합하는 방법은 무엇인가?

  • 단일 회귀 트리와 랜덤 포레스트의 훈련 RMSE와 검증 RMSE를 비교해 보자. 어느 쪽이 더 잘 일반화되었는가?

  • 랜덤 포레스트가 모든 데이터에서 항상 더 좋은 성능을 낼 것이라고 단정할 수 있는가?

13.55단계: 회귀 결정 트리와 부스팅

결정 트리는 분류뿐 아니라 회귀에도 사용된다. 회귀 트리에서는 각 잎의 예측값이 연속적인 숫자이며, 같은 잎에 속하는 샘플은 같은 예측값을 받는다.

<Figure size 640x480 with 1 Axes>

13.5.1부스팅은 이전 모델의 오류를 어떻게 보완할까?

그레이디언트 부스팅(gradient boosting)은 모델을 순차적으로 추가한다. 아래 회귀 예제에서는 첫 모델의 예측 후 남은 잔차(실제값 − 예측값)를 다음 결정 트리가 학습한다. 세 모델의 예측을 합하면 최종 예측이 된다.

트리 1개 MSE: 0.0127
트리 3개 합산 MSE: 0.0043
<Figure size 1100x400 with 2 Axes>

13.5.2GradientBoostingRegressor로 확인하기

위 과정을 직접 구현하지 않고 사이킷런의 그레이디언트 부스팅 회귀 모델을 사용할 수 있다. learning_rate는 새로 추가되는 트리의 기여 정도를 조절한다. 이는 P3에서 배운 파라미터 업데이트의 학습률과 이름은 같지만 적용되는 대상이 다르다.

<Figure size 640x480 with 1 Axes>

13.5.3과대적합을 피하려면?

트리 개수(n_estimators), 각 트리 깊이(max_depth), 학습률(learning_rate)은 모델 복잡도와 예측에 영향을 준다. 실제 모델 선택에서는 P1과 마찬가지로 검증 데이터나 교차 검증을 활용해야 한다. 아래의 훈련 성능은 최종 일반화 성능이 아니다.

Loading...

확인 질문

  • 배깅은 여러 트리를 어떤 방식으로 훈련하고 결합하는가?

  • 부스팅은 이전 트리의 오류를 다음 트리에서 어떻게 다루는가?

  • 트리 수를 늘렸을 때 훈련 MSE가 감소했다면 테스트 성능도 반드시 좋아진다고 할 수 있는가?

13.5.4그레이디언트 부스팅 분류: 랜덤 포레스트와 비교하기

부스팅은 회귀 전용 기법이 아니다. 그레이디언트 부스팅 분류(gradient boosting classification)는 분류에서도 여러 트리를 순차적으로 추가하며 손실을 줄인다. 분류의 로그 손실을 줄이는 과정은 앞의 회귀 예제처럼 단순히 숫자 잔차를 따라가는 과정과 동일하지 않다.

아래에서는 2단계의 초승달 훈련셋을 사용하여 랜덤 포레스트와 그레이디언트 부스팅 분류 모델을 비교한다.

Loading...
<Figure size 1000x400 with 2 Axes>

확인 질문

  • 랜덤 포레스트와 그레이디언트 부스팅은 여러 트리를 각각 어떤 순서로 학습하는가?

  • 두 모델의 테스트 정확도를 비교해 보자. 이 결과만으로 어떤 모델이 항상 더 낫다고 할 수 있는가?

  • 부스팅의 트리 수와 학습률을 조정할 때 왜 별도의 검증이 필요한가?

13.5.5XGBoost: 분류와 회귀에 적용하기

XGBoost(Extreme Gradient Boosting) 역시 부스팅 계열이지만 scikit-learn의 GradientBoostingClassifier와는 다른 구현이다. 여기서는 복잡한 내부 최적화 수식보다 동일한 데이터를 사용해 같은 평가 지표로 비교하는 방법을 살펴본다.

XGBoost는 별도로 설치해야 한다. 설치되어 있지 않다면 다음 코드를 노트북에서 실행한 뒤 다시 시작할 수 있다(코랩에서도 사용 가능).

%pip install xgboost

설치하지 않은 환경에서는 아래 실습이 안내 메시지만 출력하고 다음 단계로 넘어간다.

               모델   훈련 정확도  테스트 정확도
    Random Forest 1.000000    0.944
Gradient Boosting 0.986667    0.944
          XGBoost 0.978667    0.936

다음은 같은 곡선형 회귀 데이터를 XGBRegressor로 학습하는 예제다. 위의 RandomForestRegressor와 같은 훈련셋·검증셋·RMSE를 사용하므로 결과를 비교할 수 있다.

XGBoost 회귀 검증 RMSE: 0.107
랜덤 포레스트 회귀 검증 RMSE: 0.122

확인 질문

  • XGBClassifier와 XGBRegressor는 각각 어떤 예측 문제에 사용하는가?

  • 랜덤 포레스트와 XGBoost는 트리를 학습하는 순서에서 어떤 차이가 있는가?

  • 동일한 테스트 성능이 나왔다고 해서 학습 방식도 같다고 볼 수 있는가?

  • XGBoost의 learning_rate, n_estimators, max_depth는 무엇을 조절하는가?

13.66단계: 세 가지 방법을 비교해 판단하기

실습에서 가장 중요한 것은 모델의 이름을 외우는 것보다, 어떤 데이터에서 어떤 차이가 나타났는지 설명하는 것이다.

모델핵심 작동 방식비교할 점
결정 트리데이터 분할 규칙을 학습구조 해석과 과대적합
배깅·랜덤 포레스트여러 트리의 예측을 결합분산과 일반화 성능
그레이디언트 부스팅이전 예측의 오류를 보완하며 모델 추가트리 수·학습률과 과대적합
XGBoost규제 등을 활용하여 트리를 순차적으로 추가학습률·트리 깊이·규제·검증 성능

마무리 확인

  1. 초승달 데이터에서 단일 트리와 랜덤 포레스트 중 테스트 정확도가 높은 모델은 무엇인가? 실행 결과를 근거로 답하자.

  2. 트리의 min_samples_leaf와 부스팅의 learning_rate는 각각 무엇을 조절하는가?

  3. feature_importances_를 보고 할 수 있는 해석과 할 수 없는 해석을 구분해 보자.

  4. 새 데이터에서의 성능을 판단하기 위해 P1·P2의 어떤 검증 원칙을 계속 적용해야 하는가?

  5. 랜덤 포레스트와 부스팅이 회귀·분류 양쪽에서 사용된다는 것을 어떤 예제로 확인했는가?

  6. XGBoost 분류·회귀 예제를 사용하면서 공통적으로 지켜야 하는 검증 원칙은 무엇인가?