Programming

앙상블 학습 2편: 배깅(Bagging)과 랜덤 포레스트(Random Forest) 완전 이해

Lucas.Kim 2025. 12. 17. 13:11
반응형

1편에서는 앙상블 학습의 개념과 가장 직관적인 방식인 **보팅(Voting)**을 살펴보았습니다.
이번 **2편에서는 배깅(Bagging)**과, 배깅의 대표적인 알고리즘인 **랜덤 포레스트(Random Forest)**를 다룹니다.

랜덤 포레스트는

  • 구현이 비교적 쉽고
  • 성능이 안정적이며
  • 다양한 데이터 유형에서 좋은 성능을 보이는

가장 실무 친화적인 앙상블 알고리즘 중 하나입니다.

1. 배깅(Bagging)이란 무엇인가

배깅(Bagging)은

Bootstrap Aggregating의 줄임말로,
학습 데이터에서 여러 개의 샘플을 랜덤하게 추출해 각각의 모델을 학습시키고,
이들의 예측 결과를 결합하는 앙상블 기법

을 의미합니다.

배깅의 핵심 아이디어는 다음과 같습니다.

  • 같은 알고리즘을 사용하되
  • 서로 다른 데이터 샘플로 여러 모델을 학습
  • 개별 모델의 분산(Variance)을 줄여
  • 전체 모델의 일반화 성능을 향상

2. 부트스트랩 샘플링(Bootstrap Sampling)

배깅에서 사용하는 데이터 분할 방식은
부트스트랩 샘플링입니다.

부트스트랩 샘플링의 특징은 다음과 같습니다.

  • 원본 데이터에서 중복을 허용하며 샘플링
  • 하나의 학습 데이터셋 크기 = 원본 데이터 크기
  • 각 모델이 서로 조금씩 다른 데이터 분포를 학습

이 때문에 모델 간 예측 오류 패턴이 달라지고,
이를 결합하면 성능이 개선됩니다.

 

3. 랜덤 포레스트(Random Forest)란?

랜덤 포레스트는

배깅을 기반으로 여러 개의 결정트리(Decision Tree)를 학습시키고,
이들의 예측 결과를 보팅 방식으로 결합하는 알고리즘

입니다.

랜덤(Random)의 의미

랜덤 포레스트가 “랜덤”이라는 이름을 갖는 이유는 다음 두 가지입니다.

  • 데이터 샘플링의 랜덤성
    • 부트스트랩 샘플링을 통해 각 트리가 서로 다른 데이터로 학습
  • 피처 선택의 랜덤성
    • 각 노드 분할 시, 전체 피처가 아닌 일부 피처만 랜덤하게 선택

이 두 가지 랜덤성이 결합되어
👉 트리 간 상관관계를 줄이고
👉 앙상블 효과를 극대화합니다.

4. 랜덤 포레스트 주요 하이퍼파라미터

(1) n_estimators

  • 생성할 결정트리 개수
  • 값이 클수록 성능은 안정적이지만 학습 시간 증가

(2) max_features

  • 각 노드에서 분할에 사용할 최대 피처 개수
  • 기본값: auto (분류에서는 sqrt)
  • 예: 피처가 16개라면 √16 = 4개 사용

👉 트리 간 다양성을 높여 과적합을 완화합니다.

(3) 과적합 제어 파라미터

결정트리에서 사용했던 파라미터는
랜덤 포레스트에서도 그대로 적용됩니다.

  • max_depth
  • min_samples_split
  • min_samples_leaf

👉 랜덤 포레스트는 과적합에 비교적 강하지만,
👉 여전히 튜닝이 중요합니다.

5. 실습 데이터 로딩 (Human Activity Recognition 데이터)

이 예제에서는 사람의 활동(Human Activity Recognition) 데이터를 사용합니다.
데이터 특성상 피처 수가 많아 랜덤 포레스트의 장점을 잘 확인할 수 있습니다.

5-1. 중복 피처명 처리 함수

import pandas as pd

def get_new_feature_name_df(old_feature_name_df):
    """
    중복된 피처 이름을 처리하기 위한 함수
    동일한 컬럼명이 있을 경우 _1, _2 와 같이 뒤에 숫자를 붙여 고유하게 만듭니다.
    """
    feature_dup_df = pd.DataFrame(
        data=old_feature_name_df.groupby('column_name').cumcount(),
        columns=['dup_cnt']
    )

    feature_dup_df = feature_dup_df.reset_index()

    new_feature_name_df = pd.merge(
        old_feature_name_df.reset_index(),
        feature_dup_df,
        how='outer'
    )

    new_feature_name_df['column_name'] = new_feature_name_df[
        ['column_name', 'dup_cnt']
    ].apply(
        lambda x: x[0] + '_' + str(x[1]) if x[1] > 0 else x[0],
        axis=1
    )

    new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)

    return new_feature_name_df

 

5-2. 학습/테스트 데이터 로딩 함수

def get_human_dataset():
    """
    Human Activity Recognition 데이터셋 로딩 함수
    - 공백 기준으로 데이터가 구분되어 있음
    - 학습/테스트 데이터와 레이블을 각각 반환
    """

    # 피처 이름 로딩
    feature_name_df = pd.read_csv(
        './human_activity/humanActivity/features.txt',
        sep='\s+',
        header=None,
        names=['column_index', 'column_name']
    )

    # 중복 피처명 처리
    new_feature_name_df = get_new_feature_name_df(feature_name_df)

    # 피처명을 리스트로 변환
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()

    # 학습 데이터
    X_train = pd.read_csv(
        './human_activity/humanActivity/train/X_train.txt',
        sep='\s+',
        names=feature_name
    )

    # 테스트 데이터
    X_test = pd.read_csv(
        './human_activity/humanActivity/test/X_test.txt',
        sep='\s+',
        names=feature_name
    )

    # 학습 레이블
    y_train = pd.read_csv(
        './human_activity/humanActivity/train/y_train.txt',
        sep='\s+',
        header=None,
        names=['action']
    )

    # 테스트 레이블
    y_test = pd.read_csv(
        './human_activity/humanActivity/test/y_test.txt',
        sep='\s+',
        header=None,
        names=['action']
    )

    return X_train, X_test, y_train, y_test

 

6. 랜덤 포레스트 기본 모델 학습

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')

# 데이터 로딩
X_train, X_test, y_train, y_test = get_human_dataset()

# 기본 랜덤 포레스트 모델
rf_clf = RandomForestClassifier(
    n_estimators=100,     # 결정트리 개수
    random_state=0,
    max_depth=8           # 트리 깊이 제한 (과적합 제어)
)

# 모델 학습
rf_clf.fit(X_train, y_train)

# 예측 수행
pred = rf_clf.predict(X_test)

# 정확도 출력
print(f"랜덤포레스트 1 정확도 : {accuracy_score(y_test, pred)}")
print("=============================================")

7. GridSearchCV를 이용한 하이퍼파라미터 튜닝

from sklearn.model_selection import GridSearchCV

# 탐색할 하이퍼파라미터 조합
params = {
    "max_depth": [8, 16, 24],
    "min_samples_leaf": [1, 6, 12],
    "min_samples_split": [2, 8, 16]
}

# 랜덤 포레스트 모델 생성
rf_clf = RandomForestClassifier(
    n_estimators=100,
    random_state=0,
    n_jobs=-1
)

# GridSearchCV 수행
grid_cv = GridSearchCV(
    rf_clf,
    param_grid=params,
    cv=2,
    n_jobs=-1
)

grid_cv.fit(X_train, y_train)

print(
    f"GridSearchCV\n"
    f"최적 파라미터 : {grid_cv.best_params_}\n"
    f"최고 예측 정확도 : {grid_cv.best_score_}"
)
print("=============================================")

8. 최적 파라미터 기반 최종 모델 학습

rf_clf1 = RandomForestClassifier(
    n_estimators=100,
    min_samples_leaf=6,
    max_depth=16,
    min_samples_split=2,
    random_state=0
)

rf_clf1.fit(X_train, y_train)

pred = rf_clf1.predict(X_test)

print(f"랜덤포레스트 2 예측 정확도 : {accuracy_score(y_test, pred)}")

👉 기본 모델보다 성능이 개선된 것을 확인할 수 있습니다.

 

9. 랜덤 포레스트 피처 중요도 시각화

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

# 피처 중요도 추출
ftr_importances_values = rf_clf1.feature_importances_

# pandas Series로 변환
ftr_importances = pd.Series(
    ftr_importances_values,
    index=X_train.columns
)

# 중요도 상위 20개 추출
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

# 시각화
plt.figure(figsize=(8, 6))
plt.title('Feature Importances Top 20')
sns.barplot(x=ftr_top20, y=ftr_top20.index)
plt.show()

결론

이번 글에서는 배깅과 랜덤 포레스트를 정리했습니다.

핵심 요약

  • 배깅은 부트스트랩 샘플링 + 결과 결합 기법입니다.
  • 랜덤 포레스트는 배깅 기반의 대표적인 결정트리 앙상블입니다.
  • 랜덤 포레스트는 과적합에 강하고 안정적인 성능을 제공합니다.
  • 하이퍼파라미터 튜닝을 통해 성능을 더욱 개선할 수 있습니다.
  • 피처 중요도를 통해 모델 해석도 가능합니다.

다음 **3편에서는 부스팅(Boosting)**을 통해
앙상블이 어떻게 “약한 학습기”를 점진적으로 강화하는지 살펴보겠습니다.

반응형