🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Random Forest 앙상블 기법 완벽 가이드 - 슬라이드 1/13
A

AI Generated

2025. 12. 6. · 9 Views

Random Forest 앙상블 기법 완벽 가이드

머신러닝에서 가장 강력하고 실용적인 앙상블 기법인 Random Forest를 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 의사결정나무의 한계를 극복하고 더 정확한 예측 모델을 만드는 방법을 배워봅니다.


목차

  1. 앙상블_학습의_개념
  2. 의사결정나무의_한계
  3. Random_Forest의_핵심_원리
  4. 부트스트랩_샘플링
  5. 특성_무작위_선택
  6. Random_Forest_분류_실습
  7. Random_Forest_회귀_실습
  8. 특성_중요도_분석
  9. 하이퍼파라미터_튜닝
  10. Random_Forest의_장단점
  11. 실전_프로젝트_적용
  12. 다른_앙상블_기법과_비교

1. 앙상블 학습의 개념

김개발 씨는 데이터 분석팀에 배치된 지 한 달이 되었습니다. 오늘은 고객 이탈 예측 모델을 만들어야 하는데, 의사결정나무 하나로는 정확도가 70%밖에 나오지 않았습니다.

"어떻게 하면 더 정확한 모델을 만들 수 있을까요?" 선배 박시니어 씨가 웃으며 대답했습니다. "혼자서 결정하지 말고, 여러 명에게 물어보면 어떨까요?"

앙상블 학습은 여러 개의 모델을 결합하여 하나의 강력한 예측 모델을 만드는 기법입니다. 마치 어려운 문제를 혼자 풀기보다 여러 전문가의 의견을 모아 결정하는 것과 같습니다.

개별 모델의 약점을 서로 보완하여 더 안정적이고 정확한 예측이 가능해집니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import VotingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

# 세 명의 전문가(모델)를 모읍니다
model1 = DecisionTreeClassifier(random_state=42)
model2 = LogisticRegression(random_state=42)
model3 = SVC(probability=True, random_state=42)

# 앙상블: 세 모델의 의견을 종합합니다
ensemble = VotingClassifier(
    estimators=[('dt', model1), ('lr', model2), ('svc', model3)],
    voting='soft'  # 확률 기반 투표
)
ensemble.fit(X_train, y_train)
accuracy = ensemble.score(X_test, y_test)

김개발 씨는 입사 한 달 차 주니어 데이터 분석가입니다. 오늘 받은 과제는 고객 이탈을 예측하는 모델을 만드는 것이었습니다.

열심히 의사결정나무를 학습시켰지만, 테스트 데이터에서의 정확도는 겨우 70%에 머물렀습니다. 선배 개발자 박시니어 씨가 다가와 화면을 살펴봅니다.

"모델 하나만 쓰고 있네요? 여러 모델을 함께 사용해 보는 건 어때요?" 그렇다면 앙상블 학습이란 정확히 무엇일까요?

쉽게 비유하자면, 앙상블 학습은 마치 회사에서 중요한 결정을 내릴 때 한 사람의 의견만 듣지 않고 여러 전문가의 의견을 종합하는 것과 같습니다. 재무 담당자, 마케팅 담당자, 기술 담당자가 각자의 관점에서 의견을 제시하면, 이를 종합하여 더 현명한 결정을 내릴 수 있습니다.

앙상블 학습도 마찬가지로 여러 모델의 예측을 결합합니다. 앙상블이 없던 시절에는 어땠을까요?

개발자들은 하나의 모델에만 의존해야 했습니다. 의사결정나무는 데이터의 작은 변화에도 결과가 크게 달라지는 과적합 문제가 있었습니다.

로지스틱 회귀는 복잡한 패턴을 잡아내지 못했습니다. 어떤 모델을 선택하든 뚜렷한 한계가 있었습니다.

바로 이런 문제를 해결하기 위해 앙상블 학습이 등장했습니다. 앙상블을 사용하면 개별 모델의 약점을 서로 보완할 수 있습니다.

한 모델이 틀린 예측을 하더라도 다른 모델들이 올바른 예측을 하면 최종 결과는 정확해집니다. 이를 오류의 분산이라고 합니다.

위의 코드를 살펴보겠습니다. 먼저 세 가지 서로 다른 모델을 준비합니다.

DecisionTreeClassifier, LogisticRegression, SVC는 각각 다른 방식으로 데이터를 분석합니다. 그 다음 VotingClassifier를 사용하여 세 모델의 예측을 종합합니다.

voting='soft' 옵션은 각 모델이 예측한 확률을 평균 내어 최종 결정을 내립니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 은행에서 대출 심사를 한다고 가정해봅시다. 신용 점수만 보는 모델, 소득을 분석하는 모델, 거래 패턴을 보는 모델을 각각 만들고 이들의 의견을 종합하면 더 정확한 심사가 가능합니다.

하지만 주의할 점도 있습니다. 서로 비슷한 모델을 여러 개 사용하면 앙상블의 효과가 떨어집니다.

다양한 관점을 가진 모델들을 조합해야 시너지가 발생합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언대로 세 가지 모델을 앙상블하니 정확도가 82%로 올라갔습니다. "여럿이 함께하면 더 현명해지는군요!"

실전 팁

💡 - 서로 다른 특성을 가진 모델을 조합해야 앙상블 효과가 극대화됩니다

  • voting='hard'는 다수결, voting='soft'는 확률 평균 방식입니다

2. 의사결정나무의 한계

김개발 씨는 의사결정나무가 학습 데이터에서 95%의 정확도를 보여서 기뻤습니다. 그런데 새로운 데이터로 테스트하니 65%밖에 나오지 않았습니다.

"분명히 학습은 잘 됐는데 왜 이러죠?" 박시니어 씨가 고개를 저으며 말했습니다. "그게 바로 의사결정나무의 고질병이에요."

의사결정나무는 학습 데이터에 지나치게 맞추려다 보면 과적합이 발생합니다. 또한 데이터가 조금만 바뀌어도 나무 구조가 완전히 달라지는 높은 분산 문제가 있습니다.

이런 한계를 극복하기 위해 Random Forest가 탄생했습니다.

다음 코드를 살펴봅시다.

from sklearn.tree import DecisionTreeClassifier
import numpy as np

# 의사결정나무의 과적합 문제 확인
tree = DecisionTreeClassifier(max_depth=None)  # 깊이 제한 없음
tree.fit(X_train, y_train)

# 학습 데이터: 높은 정확도
train_acc = tree.score(X_train, y_train)
print(f"학습 데이터 정확도: {train_acc:.2%}")  # 거의 100%

# 테스트 데이터: 낮은 정확도 (과적합 증거)
test_acc = tree.score(X_test, y_test)
print(f"테스트 데이터 정확도: {test_acc:.2%}")  # 크게 떨어짐

# 데이터 한 점만 바꿔도 나무 구조가 변함
X_train_modified = X_train.copy()
X_train_modified[0] = X_train_modified[0] + 0.1

김개발 씨는 의사결정나무의 결과를 보고 흐뭇했습니다. 학습 데이터에서 무려 95%의 정확도를 기록했으니까요.

하지만 기쁨도 잠시, 새로운 테스트 데이터에서는 65%라는 실망스러운 수치가 나왔습니다. 선배 박시니어 씨가 설명을 시작했습니다.

"의사결정나무의 대표적인 문제점을 경험한 거예요." 그렇다면 의사결정나무의 과적합이란 무엇일까요? 마치 시험 문제를 달달 외워서 그 시험에서는 만점을 받지만, 조금만 다른 문제가 나오면 풀지 못하는 학생과 같습니다.

의사결정나무도 학습 데이터의 패턴을 너무 세세하게 외워버립니다. 노이즈까지 학습해버리는 것입니다.

또 다른 문제는 높은 분산입니다. 데이터 포인트 하나만 바뀌어도 나무의 구조가 완전히 달라질 수 있습니다.

마치 작은 돌멩이 하나에 전체 건물이 흔들리는 것과 같습니다. 이런 불안정성은 실무에서 큰 문제가 됩니다.

위의 코드에서 이 문제를 확인할 수 있습니다. max_depth=None으로 설정하면 나무가 무한히 깊어질 수 있습니다.

그 결과 학습 데이터에는 거의 완벽하게 맞추지만, 테스트 데이터에서는 성능이 급격히 떨어집니다. 이것이 바로 과적합의 전형적인 증상입니다.

왜 이런 일이 발생할까요? 의사결정나무는 탐욕적 알고리즘을 사용합니다.

각 분기점에서 당장 가장 좋아 보이는 선택을 합니다. 이 방식은 학습 데이터에 과도하게 맞춰지는 결과를 낳습니다.

현업에서 이 문제는 심각합니다. 예를 들어 주가 예측 모델을 만들었는데, 과거 데이터에서는 완벽하게 맞추지만 실제 투자에서는 손실만 난다면 무슨 소용이 있을까요?

모델의 일반화 능력이 핵심입니다. 다시 김개발 씨 이야기로 돌아갑시다.

"그러면 의사결정나무는 쓸모가 없는 건가요?" 박시니어 씨가 미소를 지었습니다. "아니요, 이 문제를 해결한 방법이 있어요.

바로 Random Forest입니다."

실전 팁

💡 - max_depth, min_samples_split 등으로 나무 깊이를 제한하면 과적합을 줄일 수 있습니다

  • 단일 의사결정나무보다 앙상블 방법을 사용하는 것이 일반적입니다

3. Random Forest의 핵심 원리

박시니어 씨가 화이트보드에 나무 여러 그루를 그렸습니다. "의사결정나무 하나는 불안정하지만, 수백 그루를 모으면 어떨까요?" 김개발 씨가 고개를 갸웃거렸습니다.

"똑같은 나무를 여러 개 만들면 결과도 똑같지 않을까요?" 박시니어 씨가 웃으며 말했습니다. "바로 그 점이 Random Forest의 핵심이에요."

Random Forest는 서로 다른 의사결정나무 수백 개를 만들어 투표로 결과를 결정하는 앙상블 기법입니다. 각 나무는 부트스트랩 샘플링으로 다른 데이터를, 특성 무작위 선택으로 다른 관점을 갖습니다.

이 두 가지 무작위성이 다양성을 만들어 과적합을 방지합니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier

# Random Forest 생성
rf = RandomForestClassifier(
    n_estimators=100,      # 나무 100그루
    max_features='sqrt',   # 각 분기에서 sqrt(전체 특성) 만큼만 고려
    bootstrap=True,        # 부트스트랩 샘플링 사용
    oob_score=True,        # Out-of-Bag 점수 계산
    random_state=42
)

rf.fit(X_train, y_train)

# 각 나무의 예측을 종합하여 최종 결정
print(f"OOB 점수: {rf.oob_score_:.2%}")
print(f"테스트 정확도: {rf.score(X_test, y_test):.2%}")

# 개별 나무 확인
print(f"나무 개수: {len(rf.estimators_)}")

박시니어 씨의 설명이 계속되었습니다. "Random Forest는 이름 그대로 무작위 숲이에요.

나무 하나하나가 조금씩 다르게 자라납니다." 그렇다면 어떻게 다른 나무들을 만들까요? 첫 번째 비밀은 부트스트랩 샘플링입니다.

마치 설문조사를 할 때 매번 다른 사람들을 무작위로 뽑아 질문하는 것과 같습니다. 전체 데이터에서 중복을 허용하여 무작위로 샘플을 뽑습니다.

그 결과 각 나무는 조금씩 다른 데이터로 학습됩니다. 두 번째 비밀은 특성의 무작위 선택입니다.

각 나무가 분기점을 만들 때 모든 특성을 보지 않고, 무작위로 선택된 일부 특성만 고려합니다. 마치 여러 심사위원이 각자 다른 평가 기준으로 점수를 매기는 것과 같습니다.

이 두 가지 무작위성이 만나면 마법이 일어납니다. 100그루의 나무가 있다면, 각 나무는 서로 다른 데이터와 다른 관점으로 학습됩니다.

어떤 나무가 실수를 하더라도 대다수의 나무가 올바른 예측을 하면 최종 결과는 정확해집니다. 이것이 집단 지성의 힘입니다.

코드를 살펴보겠습니다. n_estimators=100은 100그루의 나무를 만들겠다는 의미입니다.

max_features='sqrt'는 각 분기에서 전체 특성의 제곱근만큼만 무작위로 선택합니다. bootstrap=True는 부트스트랩 샘플링을 활성화합니다.

OOB(Out-of-Bag) 점수도 중요한 개념입니다. 부트스트랩 샘플링에서 선택되지 않은 데이터가 약 37% 정도 됩니다.

이 데이터로 각 나무의 성능을 평가할 수 있습니다. 별도의 검증 데이터 없이도 모델 성능을 추정할 수 있는 편리한 기능입니다.

김개발 씨가 감탄했습니다. "그러니까 각자 다른 시각을 가진 전문가 100명이 투표하는 거군요!"

실전 팁

💡 - n_estimators는 보통 100~500 사이로 설정합니다

  • max_features='sqrt'는 분류 문제, 'auto' 또는 None은 회귀 문제에 적합합니다

4. 부트스트랩 샘플링

김개발 씨가 질문했습니다. "부트스트랩 샘플링이 정확히 뭔가요?

왜 굳이 중복을 허용하면서 샘플을 뽑나요?" 박시니어 씨가 종이를 꺼내 그림을 그리기 시작했습니다. "아주 좋은 질문이에요.

이게 Random Forest의 심장과도 같은 부분이거든요."

부트스트랩 샘플링은 원본 데이터에서 중복을 허용하여 동일한 크기의 새로운 데이터셋을 만드는 기법입니다. N개의 데이터에서 N번 무작위로 뽑으면, 평균적으로 원본의 63.2%만 포함됩니다.

나머지 36.8%는 OOB 데이터가 되어 검증에 사용됩니다.

다음 코드를 살펴봅시다.

import numpy as np

def bootstrap_sample(X, y):
    """부트스트랩 샘플링 구현"""
    n_samples = len(X)

    # 중복을 허용하여 무작위 인덱스 선택
    indices = np.random.choice(n_samples, size=n_samples, replace=True)

    # 선택된 데이터 (약 63.2%)
    X_bootstrap = X[indices]
    y_bootstrap = y[indices]

    # 선택되지 않은 OOB 데이터 (약 36.8%)
    oob_indices = list(set(range(n_samples)) - set(indices))
    X_oob = X[oob_indices]
    y_oob = y[oob_indices]

    return X_bootstrap, y_bootstrap, X_oob, y_oob

# 사용 예시
X_boot, y_boot, X_oob, y_oob = bootstrap_sample(X_train, y_train)
print(f"원본 데이터: {len(X_train)}, 부트스트랩: {len(X_boot)}")

박시니어 씨가 쉬운 비유로 설명을 시작했습니다. "제비뽑기를 생각해 보세요.

10개의 제비가 있는데, 뽑고 나서 다시 넣고 또 뽑는 거예요." 이것이 바로 복원 추출입니다. 10번 뽑으면 어떤 제비는 여러 번 뽑히고, 어떤 제비는 한 번도 뽑히지 않습니다.

수학적으로 계산하면, 한 번도 뽑히지 않을 확률은 약 36.8%입니다. 왜 이런 방식을 사용할까요?

통계학에서 부트스트랩은 제한된 데이터로 다양한 상황을 시뮬레이션하는 강력한 기법입니다. 데이터가 충분하지 않을 때, 마치 더 많은 데이터가 있는 것처럼 활용할 수 있습니다.

Random Forest에서 이 기법은 핵심적인 역할을 합니다. 각 나무가 서로 다른 부트스트랩 샘플로 학습하면, 각 나무는 데이터의 다른 측면을 포착합니다.

이런 다양성이 앙상블의 힘을 만듭니다. 코드를 보면 np.random.choice 함수가 핵심입니다.

replace=True 옵션이 중복 허용을 의미합니다. 이렇게 뽑힌 데이터로 나무를 학습하고, 뽑히지 않은 OOB 데이터로 성능을 평가합니다.

OOB 데이터의 활용은 매우 유용합니다. 별도의 검증 데이터를 떼어놓지 않아도 모델의 일반화 성능을 추정할 수 있습니다.

데이터가 귀한 상황에서 큰 장점입니다. 김개발 씨가 고개를 끄덕였습니다.

"매번 다른 제비가 뽑히니까, 매번 다른 나무가 자라나는 거군요!"

실전 팁

💡 - 부트스트랩 샘플링 덕분에 별도의 검증 데이터 없이 OOB 점수로 성능을 평가할 수 있습니다

  • bootstrap=False로 설정하면 전체 데이터를 사용하지만, 과적합 위험이 높아집니다

5. 특성 무작위 선택

김개발 씨가 또 질문했습니다. "부트스트랩으로 데이터가 달라지는 건 알겠는데, 그래도 같은 특성을 보면 비슷한 나무가 만들어지지 않을까요?" 박시니어 씨가 손가락을 치켜세웠습니다.

"바로 그 점을 해결하는 두 번째 무작위성이 있어요."

특성 무작위 선택은 각 분기점에서 모든 특성이 아닌 무작위로 선택된 일부 특성만 고려하는 기법입니다. 분류 문제에서는 보통 전체 특성의 제곱근, 회귀 문제에서는 전체 특성의 1/3을 사용합니다.

이로써 나무 간의 상관관계가 낮아지고 앙상블 효과가 극대화됩니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier
import numpy as np

# 특성 무작위 선택 옵션 비교
# 전체 특성 수: 10개라고 가정

# sqrt: 분류에 권장 (약 3개 특성)
rf_sqrt = RandomForestClassifier(max_features='sqrt', n_estimators=100)

# log2: 더 적은 특성 (약 3개 특성)
rf_log2 = RandomForestClassifier(max_features='log2', n_estimators=100)

# 직접 지정: 5개 특성
rf_fixed = RandomForestClassifier(max_features=5, n_estimators=100)

# 전체 특성 사용 (권장하지 않음)
rf_all = RandomForestClassifier(max_features=None, n_estimators=100)

# 각 분기에서 무작위 특성 선택 시뮬레이션
n_features = 10
selected = np.random.choice(n_features, size=int(np.sqrt(n_features)), replace=False)
print(f"선택된 특성 인덱스: {selected}")

박시니어 씨가 비유를 들었습니다. "여러 기자가 같은 사건을 취재한다고 생각해 보세요.

모든 기자가 똑같은 취재원만 만나면 기사도 비슷해지겠죠?" 그렇습니다. Random Forest의 두 번째 무작위성이 바로 여기에 있습니다.

각 나무가 분기점을 만들 때, 전체 특성 중 일부만 무작위로 선택하여 최적의 분기를 찾습니다. 마치 각 기자가 서로 다른 취재원을 만나 다양한 시각의 기사를 쓰는 것과 같습니다.

이 기법의 효과는 무엇일까요? 만약 모든 나무가 모든 특성을 본다면, 가장 강력한 특성이 항상 첫 번째 분기점이 될 것입니다.

그러면 모든 나무가 비슷해집니다. 특성을 무작위로 제한하면 이런 획일화를 막을 수 있습니다.

코드에서 max_features 파라미터가 이를 제어합니다. 'sqrt'는 전체 특성의 제곱근만큼 사용합니다.

예를 들어 특성이 100개라면 10개만 봅니다. 'log2'는 더 적은 특성을 사용하고, None은 모든 특성을 사용합니다.

분류 vs 회귀에서의 권장값이 다릅니다. 분류 문제에서는 'sqrt'가 일반적으로 좋은 성능을 보입니다.

회귀 문제에서는 특성의 1/3 정도를 사용하는 것이 권장됩니다. 왜 적은 특성이 더 좋을까요?

나무들 사이의 상관관계가 낮아지기 때문입니다. 서로 독립적인 나무들이 모이면 앙상블의 분산 감소 효과가 극대화됩니다.

김개발 씨가 이해했습니다. "모든 기자가 다른 취재원을 만나니까, 기사도 다양해지고 종합하면 더 객관적인 보도가 되는 거군요!"

실전 팁

💡 - max_features를 너무 작게 설정하면 개별 나무의 성능이 떨어질 수 있습니다

  • 특성이 많을수록 이 기법의 효과가 두드러집니다

6. Random Forest 분류 실습

이론은 충분히 배웠습니다. 김개발 씨가 키보드에 손을 올렸습니다.

"이제 직접 코드를 작성해 보고 싶어요." 박시니어 씨가 고개를 끄덕였습니다. "좋아요, 실제 데이터로 분류 모델을 만들어 봅시다."

scikit-learn의 RandomForestClassifier를 사용하면 몇 줄의 코드로 Random Forest 분류 모델을 만들 수 있습니다. 모델 학습, 예측, 평가의 전체 과정을 실습하며, 특성 중요도 확인과 하이퍼파라미터 튜닝까지 다루어 봅니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score

# 데이터 준비
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# Random Forest 모델 생성 및 학습
rf_clf = RandomForestClassifier(
    n_estimators=100,
    max_depth=5,
    min_samples_split=2,
    random_state=42
)
rf_clf.fit(X_train, y_train)

# 예측 및 평가
y_pred = rf_clf.predict(X_test)
print(f"정확도: {accuracy_score(y_test, y_pred):.2%}")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

드디어 실습 시간입니다. 김개발 씨와 함께 붓꽃 데이터로 분류 모델을 만들어 봅시다.

먼저 필요한 라이브러리를 불러옵니다. sklearn.ensemble에서 RandomForestClassifier를 가져옵니다.

데이터 분할을 위한 train_test_split과 성능 평가를 위한 metrics도 함께 불러옵니다. 붓꽃 데이터는 머신러닝의 'Hello World'와 같습니다.

4개의 특성(꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비)으로 3종류의 붓꽃을 분류합니다. 데이터를 학습용 80%, 테스트용 20%로 나눕니다.

모델 생성 시 주요 파라미터를 살펴봅시다. n_estimators=100은 100그루의 나무를 만듭니다.

max_depth=5는 각 나무의 최대 깊이를 5로 제한하여 과적합을 방지합니다. min_samples_split=2는 분기를 만들기 위한 최소 샘플 수입니다.

fit 메서드로 학습이 완료됩니다. 내부적으로 100개의 의사결정나무가 각각 부트스트랩 샘플과 무작위 특성으로 학습됩니다.

이 모든 과정이 한 줄의 코드로 이루어집니다. predict 메서드로 예측합니다.

100개 나무의 예측을 모아 다수결로 최종 클래스를 결정합니다. classification_report는 클래스별 정밀도, 재현율, F1 점수를 보여줍니다.

김개발 씨가 결과를 보고 놀랐습니다. "단일 의사결정나무보다 훨씬 안정적인 결과가 나오네요!"

실전 팁

💡 - random_state를 고정하면 재현 가능한 결과를 얻을 수 있습니다

  • classification_report로 클래스별 성능을 자세히 확인하세요

7. Random Forest 회귀 실습

김개발 씨가 다음 과제를 받았습니다. 이번에는 집값을 예측하는 회귀 문제입니다.

"분류와 회귀는 어떻게 다른가요?" 박시니어 씨가 설명했습니다. "투표 대신 평균을 내는 거예요.

코드는 거의 비슷합니다."

RandomForestRegressor는 연속적인 값을 예측하는 회귀 문제에 사용됩니다. 분류와 달리 각 나무의 예측값을 평균하여 최종 값을 결정합니다.

집값, 주가, 온도 등 숫자를 예측할 때 사용합니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
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
)

# Random Forest 회귀 모델
rf_reg = RandomForestRegressor(
    n_estimators=100,
    max_depth=10,
    min_samples_leaf=4,
    random_state=42
)
rf_reg.fit(X_train, y_train)

# 예측 및 평가
y_pred = rf_reg.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}, R2 Score: {r2:.4f}")

회귀 문제는 분류와 다릅니다. '어떤 클래스인가'가 아니라 '얼마인가'를 예측합니다.

캘리포니아 집값 데이터를 사용해 봅시다. 이 데이터셋은 20,640개의 지역별 집값 정보를 담고 있습니다.

중위 소득, 주택 나이, 방 개수 등 8개의 특성으로 집값을 예측합니다. 코드는 분류와 매우 유사합니다.

RandomForestClassifier 대신 RandomForestRegressor를 사용합니다. 파라미터도 거의 동일합니다.

min_samples_leaf=4는 리프 노드의 최소 샘플 수로, 과적합 방지에 도움됩니다. 분류와의 핵심 차이는 최종 예측 방식입니다.

분류에서는 100개 나무가 투표하여 가장 많은 표를 받은 클래스를 선택합니다. 회귀에서는 100개 나무의 예측값을 평균합니다.

평가 지표도 다릅니다. 정확도 대신 **RMSE(Root Mean Squared Error)**와 R2 Score를 사용합니다.

RMSE는 예측 오차의 크기를, R2는 모델이 데이터의 변동을 얼마나 설명하는지를 나타냅니다. 김개발 씨가 결과를 해석했습니다.

"R2가 0.8이면 집값 변동의 80%를 설명할 수 있다는 거군요!"

실전 팁

💡 - 회귀에서 max_features의 기본값은 특성 전체(None)입니다

  • min_samples_leaf를 늘리면 더 안정적인 예측을 할 수 있습니다

8. 특성 중요도 분석

김개발 씨가 모델을 완성했습니다. 그런데 팀장이 질문했습니다.

"이 모델이 어떤 특성을 중요하게 봤는지 설명할 수 있나요?" 블랙박스 모델은 설명하기 어렵다고 들었는데, Random Forest는 어떨까요?

Random Forest는 각 특성이 예측에 얼마나 기여하는지를 측정하는 특성 중요도를 제공합니다. 불순도 감소량을 기반으로 계산되며, 이를 통해 어떤 특성이 예측에 중요한지 파악할 수 있습니다.

모델 해석과 특성 선택에 유용합니다.

다음 코드를 살펴봅시다.

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier

# 모델 학습 후 특성 중요도 추출
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)

# 특성 중요도 확인
importances = rf.feature_importances_
feature_names = iris.feature_names  # 또는 데이터의 컬럼명

# 중요도 순으로 정렬
feature_importance_df = pd.DataFrame({
    'feature': feature_names,
    'importance': importances
}).sort_values('importance', ascending=False)

print(feature_importance_df)

# 시각화
plt.barh(feature_importance_df['feature'], feature_importance_df['importance'])
plt.xlabel('Importance')
plt.title('Feature Importance')
plt.gca().invert_yaxis()
plt.tight_layout()

머신러닝 모델이 '왜' 그런 예측을 했는지 설명하는 것은 매우 중요합니다. Random Forest는 다행히 특성 중요도를 제공합니다.

모델이 학습되면 feature_importances_ 속성에 각 특성의 중요도가 저장됩니다. 이 값은 0과 1 사이이며, 합은 1입니다.

특성 중요도는 어떻게 계산될까요? 각 나무에서 특정 특성이 사용될 때 불순도가 얼마나 감소하는지를 측정합니다.

불순도 감소가 클수록 그 특성이 분류에 중요하다는 의미입니다. 모든 나무에서의 감소량을 평균하여 최종 중요도를 계산합니다.

코드를 살펴보면 매우 간단합니다. rf.feature_importances_로 중요도 배열을 얻고, 특성 이름과 함께 DataFrame으로 만듭니다.

중요도 순으로 정렬하면 어떤 특성이 가장 중요한지 한눈에 볼 수 있습니다. 시각화도 중요합니다.

막대 그래프로 표현하면 비전문가도 쉽게 이해할 수 있습니다. "이 모델은 꽃잎 길이를 가장 중요하게 봅니다"라고 설명할 수 있습니다.

하지만 주의점도 있습니다. 상관관계가 높은 특성들은 중요도가 분산될 수 있습니다.

또한 스케일이 큰 특성이 과대평가될 수 있습니다. 순열 중요도(permutation importance)를 함께 확인하면 더 신뢰할 수 있습니다.

김개발 씨가 팀장에게 보고했습니다. "이 모델은 주로 고객의 구매 빈도와 평균 구매 금액을 보고 이탈을 예측합니다."

실전 팁

💡 - 특성 중요도는 상대적인 값이므로, 특성 간 비교에 사용하세요

  • 순열 중요도(permutation_importance)와 함께 확인하면 더 정확합니다

9. 하이퍼파라미터 튜닝

김개발 씨의 모델은 잘 동작했지만, 정확도를 더 높일 수 있을까요? 박시니어 씨가 조언했습니다.

"Random Forest는 설정할 수 있는 옵션이 많아요. 최적의 조합을 찾아봅시다."

하이퍼파라미터는 모델 학습 전에 설정하는 값들입니다. n_estimators, max_depth, min_samples_split 등을 조절하여 모델 성능을 최적화할 수 있습니다.

GridSearchCV나 RandomizedSearchCV를 사용하면 자동으로 최적의 조합을 찾을 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV

# 탐색할 파라미터 범위 정의
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [5, 10, 15, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'max_features': ['sqrt', 'log2']
}

# Grid Search: 모든 조합 시도
rf = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(
    rf, param_grid, cv=5, scoring='accuracy', n_jobs=-1
)
grid_search.fit(X_train, y_train)

# 최적 파라미터 확인
print(f"최적 파라미터: {grid_search.best_params_}")
print(f"최고 점수: {grid_search.best_score_:.4f}")

# 최적 모델로 예측
best_model = grid_search.best_estimator_
accuracy = best_model.score(X_test, y_test)

하이퍼파라미터 튜닝은 모델 성능을 높이는 핵심 단계입니다. Random Forest의 주요 하이퍼파라미터를 살펴봅시다.

n_estimators는 나무의 개수입니다. 많을수록 좋지만 학습 시간이 증가합니다.

max_depth는 나무의 최대 깊이로, 과적합 방지에 중요합니다. min_samples_splitmin_samples_leaf는 분기 조건을 제어합니다.

모든 조합을 직접 시도하는 것은 비효율적입니다. GridSearchCV가 이를 자동화합니다.

param_grid에 탐색할 값들을 정의하면, 모든 조합에 대해 교차 검증을 수행하고 최적의 조합을 찾아줍니다. 코드를 살펴보겠습니다.

cv=5는 5-fold 교차 검증을 의미합니다. 데이터를 5등분하여 4개로 학습하고 1개로 검증하는 과정을 5번 반복합니다.

n_jobs=-1은 모든 CPU 코어를 사용하여 병렬 처리합니다. RandomizedSearchCV도 있습니다.

Grid Search는 모든 조합을 시도하므로 시간이 오래 걸릴 수 있습니다. Randomized Search는 무작위로 일부 조합만 시도하여 시간을 절약합니다.

대규모 데이터셋에서 효율적입니다. 주의할 점이 있습니다.

테스트 데이터로 튜닝하면 안 됩니다. 튜닝은 학습 데이터의 교차 검증으로만 해야 합니다.

테스트 데이터는 최종 평가에만 사용해야 공정한 성능 측정이 가능합니다. 김개발 씨가 Grid Search를 돌린 후 만족했습니다.

"최적 파라미터를 찾으니 정확도가 3%나 올랐어요!"

실전 팁

💡 - 파라미터 조합이 많으면 RandomizedSearchCV를 사용하세요

  • 교차 검증 결과와 테스트 결과의 차이가 크면 과적합을 의심하세요

10. Random Forest의 장단점

김개발 씨가 Random Forest를 열심히 사용하고 있습니다. 어느 날 다른 팀 동료가 물었습니다.

"모든 문제에 Random Forest를 쓰면 되는 건가요?" 박시니어 씨가 대답했습니다. "만능은 아니에요.

장단점을 알아야 적재적소에 사용할 수 있습니다."

Random Forest는 높은 정확도, 과적합 방지, 특성 중요도 제공 등 많은 장점이 있습니다. 하지만 학습 시간이 길고, 메모리 사용량이 크며, 실시간 예측에 부적합할 수 있습니다.

데이터와 상황에 맞게 선택해야 합니다.

다음 코드를 살펴봅시다.

# Random Forest의 장점 활용 예시
from sklearn.ensemble import RandomForestClassifier
import time

rf = RandomForestClassifier(n_estimators=100, n_jobs=-1, random_state=42)

# 장점 1: 병렬 처리로 학습 시간 단축
start = time.time()
rf.fit(X_train, y_train)
print(f"학습 시간: {time.time() - start:.2f}초")

# 장점 2: 결측치 처리 (일부 구현체)
# 장점 3: 스케일링 불필요 (트리 기반 모델)
# 장점 4: 과적합 방지 (앙상블 효과)
print(f"학습 정확도: {rf.score(X_train, y_train):.4f}")
print(f"테스트 정확도: {rf.score(X_test, y_test):.4f}")

# 단점: 예측 시간 (모든 나무 순회)
start = time.time()
predictions = rf.predict(X_test)
print(f"예측 시간: {time.time() - start:.4f}초")

모든 알고리즘에는 장단점이 있습니다. Random Forest도 예외가 아닙니다.

먼저 장점을 살펴봅시다. 높은 정확도가 가장 큰 장점입니다.

많은 데이터 과학 대회에서 Random Forest가 상위권을 차지합니다. 앙상블의 힘 덕분에 단일 모델보다 안정적입니다.

과적합에 강합니다. 부트스트랩과 특성 무작위 선택이 과적합을 자연스럽게 방지합니다.

복잡한 정규화 기법 없이도 일반화 성능이 좋습니다. 전처리가 간단합니다.

트리 기반 모델은 특성 스케일링이 필요 없습니다. 이상치에도 비교적 강건합니다.

범주형 변수도 쉽게 처리합니다. 해석이 가능합니다.

특성 중요도를 통해 모델을 설명할 수 있습니다. 블랙박스가 아닌 '회색 박스' 정도로 볼 수 있습니다.

이제 단점을 알아봅시다. 학습 시간이 깁니다.

나무 수백 개를 만들어야 하므로 데이터가 크면 시간이 오래 걸립니다. n_jobs=-1로 병렬 처리를 활용하세요.

메모리 사용량이 큽니다. 모든 나무를 메모리에 저장해야 합니다.

모바일이나 임베디드 환경에는 적합하지 않을 수 있습니다. 실시간 예측에 불리합니다.

예측 시 모든 나무를 순회해야 합니다. 초당 수천 건의 예측이 필요한 서비스에는 부담이 됩니다.

김개발 씨가 정리했습니다. "정확도가 중요하고 시간 여유가 있으면 Random Forest, 빠른 응답이 필요하면 다른 방법을 찾아야겠네요."

실전 팁

💡 - 대용량 데이터에서는 LightGBM이나 XGBoost가 더 효율적일 수 있습니다

  • 예측 속도가 중요하면 n_estimators를 줄이거나 나무 깊이를 제한하세요

11. 실전 프로젝트 적용

김개발 씨가 드디어 실제 프로젝트에 Random Forest를 적용하게 되었습니다. 고객 이탈 예측 모델을 완성해야 합니다.

지금까지 배운 모든 것을 종합해 봅시다.

실전 프로젝트에서는 데이터 전처리, 모델 학습, 하이퍼파라미터 튜닝, 평가, 저장까지 전체 파이프라인을 구축해야 합니다. 실무에서 바로 활용할 수 있는 완전한 워크플로우를 살펴봅니다.

다음 코드를 살펴봅시다.

import pandas as pd
import joblib
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# 1. 데이터 로드 및 전처리
# df = pd.read_csv('customer_data.csv')
# X = df.drop('churned', axis=1)
# y = df['churned']

# 2. 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# 3. 모델 파이프라인 구축
pipeline = Pipeline([
    ('classifier', RandomForestClassifier(
        n_estimators=200, max_depth=10, min_samples_leaf=4,
        class_weight='balanced', random_state=42, n_jobs=-1
    ))
])

# 4. 교차 검증
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5)
print(f"CV 평균: {cv_scores.mean():.4f} (+/- {cv_scores.std()*2:.4f})")

# 5. 최종 학습 및 저장
pipeline.fit(X_train, y_train)
joblib.dump(pipeline, 'churn_model.pkl')

실전 프로젝트는 단순히 모델을 학습시키는 것 이상입니다. 전체 워크플로우를 살펴봅시다.

1단계: 데이터 로드 및 전처리입니다. 실제 데이터는 결측치, 이상치, 범주형 변수 등을 처리해야 합니다.

pandas를 사용하여 데이터를 정리합니다. 2단계: 데이터 분할입니다.

stratify=y 옵션은 클래스 비율을 유지하면서 분할합니다. 이탈 고객이 10%라면 학습 데이터와 테스트 데이터 모두 10%를 유지합니다.

3단계: 파이프라인 구축입니다. Pipeline을 사용하면 전처리와 모델을 하나로 묶을 수 있습니다.

이렇게 하면 새로운 데이터에 동일한 전처리를 자동으로 적용합니다. **class_weight='balanced'**는 중요한 옵션입니다.

이탈 고객이 10%밖에 없는 불균형 데이터에서, 이 옵션은 소수 클래스에 더 높은 가중치를 부여합니다. 모델이 이탈 고객을 더 잘 찾아냅니다.

4단계: 교차 검증입니다. 한 번의 분할로는 운이 좋았거나 나빴을 수 있습니다.

5-fold 교차 검증으로 신뢰할 수 있는 성능을 측정합니다. 5단계: 모델 저장입니다.

joblib.dump로 학습된 모델을 파일로 저장합니다. 나중에 joblib.load로 불러와서 예측에 사용할 수 있습니다.

김개발 씨의 모델이 배포되었습니다. 이탈 가능성이 높은 고객을 미리 파악하여 마케팅팀에 전달합니다.

회사의 고객 유지율이 15% 향상되었습니다.

실전 팁

💡 - 불균형 데이터에서는 class_weight='balanced' 또는 SMOTE를 고려하세요

  • 모델 저장 시 버전 관리를 하면 롤백이 가능합니다

12. 다른 앙상블 기법과 비교

김개발 씨가 Random Forest에 익숙해졌습니다. 박시니어 씨가 말했습니다.

"Random Forest만 알면 아쉽죠. 다른 앙상블 기법도 알아두면 상황에 맞게 선택할 수 있어요."

Random Forest 외에도 Bagging, Boosting, Stacking 등 다양한 앙상블 기법이 있습니다. 각각의 특성을 이해하면 문제에 맞는 최적의 방법을 선택할 수 있습니다.

특히 XGBoost, LightGBM은 현업에서 많이 사용됩니다.

다음 코드를 살펴봅시다.

from sklearn.ensemble import (
    RandomForestClassifier,
    GradientBoostingClassifier,
    AdaBoostClassifier,
    BaggingClassifier
)
# XGBoost, LightGBM은 별도 설치 필요
# from xgboost import XGBClassifier
# from lightgbm import LGBMClassifier

# Bagging: Random Forest의 기본 원리
bagging = BaggingClassifier(n_estimators=100, random_state=42)

# Boosting: 순차적으로 약점 보완
gradient_boost = GradientBoostingClassifier(n_estimators=100, random_state=42)
ada_boost = AdaBoostClassifier(n_estimators=100, random_state=42)

# 성능 비교
models = {
    'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting': gradient_boost,
    'AdaBoost': ada_boost
}

for name, model in models.items():
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    print(f"{name}: {score:.4f}")

앙상블 기법은 크게 세 가지로 나뉩니다. 첫째는 **Bagging(Bootstrap Aggregating)**입니다.

Random Forest가 대표적인 Bagging 기법입니다. 여러 모델을 독립적으로, 병렬로 학습합니다.

각 모델의 오류가 서로 상쇄되어 분산을 줄입니다. 둘째는 Boosting입니다.

Gradient Boosting, AdaBoost, XGBoost, LightGBM이 여기에 속합니다. 모델을 순차적으로 학습하되, 이전 모델이 틀린 부분에 집중합니다.

약한 학습기를 점점 강하게 만듭니다. Bagging vs Boosting의 차이를 비유하면 이렇습니다.

Bagging은 여러 학생이 각자 공부하고 답을 모아 다수결로 정하는 것입니다. Boosting은 한 학생이 틀린 문제를 다음 학생이 집중적으로 공부하여 점점 완벽해지는 것입니다.

XGBoost는 Boosting의 강자입니다. Kaggle 대회에서 수많은 우승을 차지했습니다.

정규화, 결측치 처리, 병렬 처리 등 다양한 최적화가 되어 있습니다. LightGBM은 속도가 장점입니다.

대용량 데이터에서 XGBoost보다 빠르면서도 비슷한 성능을 냅니다. Leaf-wise 성장 방식으로 효율적입니다.

어떤 것을 선택해야 할까요? 일반적으로 시작은 Random Forest로 합니다.

빠르게 괜찮은 성능을 얻을 수 있습니다. 더 높은 성능이 필요하면 XGBoost나 LightGBM을 시도합니다.

김개발 씨가 마무리했습니다. "Random Forest로 기본기를 다지고, 상황에 따라 다른 기법도 활용해야겠네요!"

실전 팁

💡 - 빠른 프로토타이핑에는 Random Forest, 최고 성능이 필요하면 XGBoost/LightGBM

  • Stacking은 여러 모델의 예측을 또 다른 모델의 입력으로 사용하는 고급 기법입니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#RandomForest#MachineLearning#Ensemble#DecisionTree#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.