[Python] ML-Random Forest
[Python] ML-Random Forest
Random Forest 개념
Random Forest는 여러 개의 Decision Tree를 독립적으로 학습시키고 그 결과를 다수결(분류) 또는 평균(회귀)으로 합치는 앙상블(Ensemble) 알고리즘
1
2
3
4
5
단일 Decision Tree의 문제:
→ 데이터 변화에 민감하고 과적합하기 쉬움
Random Forest의 해결책:
→ 다양한 트리를 만들고 결과를 합쳐 분산을 줄임
두 가지 핵심 전략
1. Bagging (Bootstrap Aggregating)
1
2
3
원본 데이터에서 복원 추출(Bootstrap)로 서브셋 생성
→ 각 트리가 서로 다른 데이터로 학습
→ 트리마다 다른 패턴을 학습하여 다양성 확보
2. Feature Randomness (변수 무작위 선택)
1
2
3
각 노드 분할 시 전체 변수가 아닌 일부만 랜덤하게 사용
→ 트리 간 상관관계 낮춤
→ 더 독립적인 앙상블 구성
언제 사용하는가
사용해야 할 때
| 상황 | 이유 |
|---|---|
| 빠르게 좋은 성능의 베이스라인이 필요할 때 | 기본 파라미터만으로도 준수한 성능, 튜닝 부담 적음 |
| 결측값이 있고 전처리를 줄이고 싶을 때 | 결측값에 비교적 강건, 스케일링 불필요 |
| 변수 중요도를 파악하고 싶을 때 | Feature Importance가 안정적이고 신뢰도 높음 |
| 과적합이 걱정될 때 | Bagging으로 분산 감소, Decision Tree보다 훨씬 안정적 |
| 데이터 크기가 중간 규모 (수만 ~ 수십만 건) | 병렬 학습으로 빠른 처리 |
사용하지 말아야 할 때
| 상황 | 이유 |
|---|---|
| 실시간 예측 or 메모리가 제한적일 때 | 수백 개의 트리를 메모리에 저장 → 무거움 |
| 최고 성능이 필요할 때 | XGBoost, LightGBM이 일반적으로 성능 우위(XGBoost, LightGBM은 오차를 점진적으로 개선해 나감) |
| 선형 관계가 강한 데이터 | 선형 모델(Logistic Regression, Ridge)이 더 적합 |
실무 활용 사례
1
2
3
4
금융 : 사기 거래 탐지 (불균형 데이터에서도 강건)
의료 : 환자 재입원 예측
제조 : 불량품 탐지, 예측 정비
마케팅 : 고객 이탈 예측 (빠른 배포 필요 시)
Parameters
| Parameters | Explanation | Default |
|---|---|---|
n_estimators | 트리 개수 | 100 |
max_depth | 각 트리의 최대 깊이 | None |
max_features | 분할 시 사용할 변수 수 (sqrt 권장) | sqrt |
min_samples_split | 노드 분할 최소 샘플 수 | 2 |
min_samples_leaf | Leaf 노드 최소 샘플 수 | 1 |
random_state | 고정값 | None |
n_jobs | 사용할 CPU 코어 개수 | None |
n_estimators: 여러 개의 Decision Tree를 몇 개 만들지 결정하는 파라미터(보통 100 ~ 300데이터 크면 500 이상도 사용) - 값 변화별 효과
- 클수록 → 성능 안정화 (Variance 감소), 과적합 ↓
- 작을수록 → 모델 불안정, 성능 변동 큼
- 단점 → 학습 시간 증가
- 값 변화별 효과
max_depth: 각 트리의 최대 깊이를 제한하는 파라미터- Random Forest의 ‘랜덤성’을 결정하는 주요 파라미터
- 값 변화별 효과
- 클수록 → 복잡한 패턴 학습, 과적합 ↑
- 작을수록 → 단순한 모델, 과적합 ↓
mxa_features: 각 노드에서 분할(Split)을 결정할 때, 전체 피처(Feature) 중 일부만 무작위로 골라서 그중 최적의 피처를 찾도록 제한하는 설정- 값 변화별 효과
- 클수록 → 트리들이 비슷해짐, 과적합 ↑
- 작을수록 → 트리 다양성 증가, 일반화 ↑ (너무 작으면 성능 ↓)
- Options
int: 사용할 feature 개수를 직접 지정float: (예: 5개)float (실수): 전체 피처 대비 비율로 지정합니다. (예: 0.3이면 전체의 30%)sqrt or auto: 전체 피처 개수가 $M$일 때, $\sqrt{M}$ 개만 사용합니다. (분류 문제에서 권장)log2: $\log_2(M)$ 개를 사용None: 모든 피처를 다 고려, 이는 Bagging 방식과 동일해지며 무작위성이 줄어듦.
- 값 변화별 효과
min_samples_split: 노드를 분할하기 위해 필요한 최소 샘플 수- 값 변화별 효과
- 클수록 → 분할 덜함 → 모델 단순 → 과적합 ↓
- 작을수록 → 계속 분할 → 모델 복잡 → 과적합 ↑
- Options
int: 최소 샘플 개수floate: 전체 데이터 대비 비율
- 값 변화별 효과
min_samples_leaf: leaf node(최종 노드)에 있어야 하는 최소 샘플 수- 값 변화별 효과
- 클수록 → leaf가 커짐 → 부드러운 모델 → 과적합 ↓
- 작을수록 → leaf가 작아짐 → 복잡한 모델 → 과적합 ↑
- Options
int: 최소 샘플 개수floate: 전체 데이터 대비 비율
- 값 변화별 효과
실습 — Titanic 생존자 예측
I. Library & Data Load
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# warning ignore
import warnings
warnings.filterwarnings(action = 'ignore')
# Data Preprocessing
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler, LabelEncoder
# Visualization
import seaborn as sns
import matplotlib.pyplot as plt
plt.rcParams['font.family'] ='AppleGothic' # mac 한글 깨짐 현상 해결
# Model Definition
from sklearn.ensemble import RandomForestClassifier
# Evaluation
from sklearn.metrics import roc_curve, accuracy_score, confusion_matrix, roc_auc_score
1
2
3
# Data Load
titanic = pd.read_csv("./Data/Titanic.csv")
titanic
1
2
3
4
5
6
7
8
9
10
11
12
13
Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
... ... ... ... ... ... ... ... ... ... ... ...
886 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q
891 rows × 11 columns
II. Preprocessing
II-I. Feature Engineering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 가족 변수 추가
titanic['FamSize'] = titanic['SibSp'] + titanic['Parch']
# 분석에 사용할 변수만 선택
use_cols = ['Survived', 'Pclass', 'Sex', 'Age', 'FamSize', 'Fare', 'Embarked']
# 결측값 제거
titanic = titanic[use_cols].dropna(subset = ['Age'])
# 변수 형태 변경
titanic[['Survived', 'Pclass', 'Sex', 'Embarked']] = titanic[['Survived', 'Pclass', 'Sex', 'Embarked']].astype('category')
titanic['Age'] = titanic['Age'].astype('int')
# One-Hot-Encoding
titanic = pd.get_dummies(titanic, columns = ['Pclass', 'Sex', 'Embarked'], drop_first = True)
titanic
1
2
3
4
5
6
7
8
9
10
11
12
13
Survived Age FamSize Fare Pclass_2 Pclass_3 Sex_male Embarked_Q Embarked_S
0 0 22 1 7.2500 False True True False True
1 1 38 1 71.2833 False False False False False
2 1 26 0 7.9250 False True False False True
3 1 35 1 53.1000 False False False False True
4 0 35 0 8.0500 False True True False True
... ... ... ... ... ... ... ... ... ...
885 0 39 5 29.1250 False True False True False
886 0 27 0 13.0000 True False True False True
887 1 19 0 30.0000 False False False False True
889 1 26 0 30.0000 False False True False False
890 0 32 0 7.7500 False True True True False
714 rows × 9 columns
II-II. 수치형 변수 시각화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 수치형 변수 시각화
def numberic_plot(df, target):
g = sns.PairGrid(df, hue = target) # 주어진 데이터 컬럼에 대한 모든 조합을 만들어주는 빈 틀을 위한 코드
g.map_diag(sns.histplot) # 삼각행렬의 중간 부분
g.map_lower(sns.scatterplot) # 아래 부분
# 상관 계수 행렬을 구하고 상관 계수 값 표시
corr_matrix = df.corr()
for i, j in zip(*plt.np.triu_indices_from(g.axes, k = 1)): # np.triu_indices_from : 삼각행렬의 위쪽 삼각형의 인덱스 (k = 0 : 대각 행렬 포함, 1 : 제외)
g.axes[i, j].annotate(f"corr : {corr_matrix.iloc[i, j]:.2f}", # 상관계수
(0.5, 0.5), xycoords = "axes fraction", ha = 'center', va = 'center', # 중앙 정렬
fontsize = 12, # 글자 크기
color = 'black') # 글자 색
g.add_legend() # 범례 표시
plt.show()
Columns = ['Age', 'FamSize', 'Fare', 'Survived'] # 수치형 변수
numberic_plot(titanic[Columns], 'Survived')
II-III. Train & Test Split
1
2
3
4
5
y = titanic['Survived']
X = titanic.drop(['Survived'], axis = 1)
# 75 : 25 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0)
III. Model Train
1
2
3
4
5
6
7
8
9
10
11
RF = RandomForestClassifier(
n_estimators = 200, # 생성할 트리 개수
max_depth = 10, # 각 트리의 최대 깊이
max_features = 'sqrt', # 분할 시 사용할 변수
min_samples_split = 2, # 노드 분할 최소 샘플 수
min_samples_leaf = 1, # Leaf 노드의 최소 샘플 수
random_state = 0, # random seed
n_jobs = None # 사용할 CPU 코어 개수
)
RF.fit(X_train, y_train)
IV. OOB Score (Out-of-Bag Score)
Bagging에서 각 트리는 복원 추출로 데이터를 선택하기 때문에, 선택되지 않은 약 37%의 데이터가 생깁니다. 이를 OOB(Out-of-Bag) 샘플이라 하며, 별도의 검증셋 없이 성능을 추정할 수 있음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
RF_oob = RandomForestClassifier(
n_estimators = 200, # 생성할 트리 개수
max_depth = 10, # 각 트리의 최대 깊이
max_features = 'sqrt', # 분할 시 사용할 변수
min_samples_split = 2, # 노드 분할 최소 샘플 수
min_samples_leaf = 1, # Leaf 노드의 최소 샘플 수
random_state = 0, # random seed
n_jobs = None, # 사용할 CPU 코어 개수
oob_score = True, # OOB Score 활성화
)
RF_oob.fit(X_train, y_train)
print(f"OOB Score : {RF_oob.oob_score_ * 100 : .2f}%")
# OOB Score는 교차 검증 점수와 유사하게 해석 가능
1
OOB Score : 80.37%
V. Variable Importance Visualization
1
2
3
4
5
6
importances = pd.Series(RF.feature_importances_, index = X.columns)
importances.sort_values().plot(kind = 'barh', figsize = (8, 5))
plt.title('Feature Importance')
plt.xlabel('중요도')
plt.tight_layout()
plt.show()
VI. Evaluation Score
1
2
3
4
5
6
7
8
9
pred = RF.predict(X_test)
cfx = confusion_matrix(y_test, pred) # Confusion Matrix
sensitivity = cfx[0, 0] / (cfx[0, 0] + cfx[0, 1]) # 민감도 계산
specificity = cfx[1, 1] / (cfx[1, 0] + cfx[1, 1]) # 특이도 계산
print(f"정확도(accuracy) : {accuracy_score(y_test, pred) * 100 :.2f}%")
print(f"Confusion_Matrix :\n{cfx}")
print(f"민감도(sensitivity) : {sensitivity * 100 :.2f}%")
print(f"특이도(specificity) : {specificity * 100 :.2f}%")
1
2
3
4
5
6
정확도(accuracy) : 79.89%
Confusion_Matrix :
[[88 15]
[21 55]]
민감도(sensitivity) : 85.44%
특이도(specificity) : 72.37%
VII. Roc Curve
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fpr, tpr, thresholds = roc_curve(y_test, pred)
J = tpr - fpr
ix = np.argmax(J) # 가장 큰 원소의 위치(최대값의 인덱스)
best_thresh = thresholds[ix]
#plot roc and best threshold
sens, spec = tpr[ix], 1 - fpr[ix]
# plot the roc curve for the model
plt.plot([0,1], [0,1], linestyle = '--', markersize = 0.01, color = 'black') # 중간 기준 선
plt.plot(fpr, tpr, marker = '.', color = 'black', markersize = 0.01, label = "Ridge AUC = %.2f" % roc_auc_score(y_test, pred))
plt.scatter(fpr[ix], tpr[ix], marker = '+', s = 100, color = 'r',
label = f"Best threshold = {best_thresh:.3f}, \nSensitivity = {sens:.3f}, \nSpecificity = {spec:.3f}")
# Title & Axis Labels
plt.title("ROC Curve")
plt.xlabel("False Positive Rate(1 - Specificity)")
plt.ylabel("True Positive Rate(Sensitivity)")
plt.legend(loc = 4)
# show the plot
plt.show()
Bagging vs Boosting
Random Forest가 속한 Bagging과 다음 포스팅부터 다룰 Boosting을 비교합니다.
| Bagging (Random Forest) | Boosting (AdaBoost, XGBoost…) | |
|---|---|---|
| 학습 방식 | 병렬 | 순차 |
| 각 트리 관계 | 독립적 | 이전 결과에 의존 |
| 목적 | 분산 감소 | 편향 감소 |
| 과적합 | 상대적으로 강함 | 노이즈에 민감할 수 있음 |
| 속도 | 빠름 (병렬 가능) | 느림 |
장단점
장점
- Decision Tree 대비 과적합에 강함
- 변수 중요도 제공 → 해석 가능
- 스케일링 불필요
- 결측값에 비교적 강건
단점
- 트리 수가 많아지면 메모리/시간 비용 증가
- 개별 트리보다 해석이 어려움
- Boosting 계열보다 성능이 낮을 수 있음
This post is licensed under CC BY 4.0 by the author.



