들어가며: 회귀만 대비하면 안 됩니다
AICE Professional 시험의 1번 Tabular 문제는 회귀(Regression)가 나올 수도 있지만, **다중 분류(Multi-class Classification)**가 출제될 가능성도 충분히 있습니다. 이전 글에서 회귀 문제 풀이를 다뤘으니, 이번에는 다중 분류 유형을 정리합니다.
저는 1, 2번을 만점으로 통과했지만 3번 Image 문제에서 불합격한 경험이 있습니다. 1번은 비교적 접근하기 쉬운 문제이므로, 회귀든 분류든 어떤 유형이 나와도 확실하게 만점을 받아 두는 것이 합격 전략의 핵심입니다.
AICE Professional 시험 구조 (복습)
| 문제 | 유형 | 배점 |
|---|---|---|
| 1번 | Tabular (정형 데이터) | 30점 |
| 2번 | Text (텍스트 데이터) | 35점 |
| 3번 | Image (이미지 데이터) | 35점 |
합격 기준은 80점 이상입니다. 한 문제라도 완전히 틀리면 사실상 불합격이므로, 1번에서 확실하게 30점을 확보해야 합니다.
1번 문제: Tabular 다중 분류 — 출제 유형 분석
예상 문제 형태
수치형 변수 여러 개가 주어지고, 이를 기반으로 3개 이상의 클래스 중 하나를 예측하는 다중 분류 문제가 출제됩니다.
회귀 문제와의 핵심 차이점은 다음과 같습니다.
- target 컬럼이 연속 수치가 아니라 범주형 라벨(문자열 또는 정수)입니다
- 평가 지표가 RMSE가 아니라 Accuracy, F1-score 등 분류 지표입니다
- 라벨이 문자열일 경우 LabelEncoder로 인코딩이 필요합니다
- 제출 시 예측값을 원래 라벨명으로 복원해야 합니다
회귀 vs 다중 분류 — 차이점 한눈에 보기
시험장에서 데이터를 열었을 때, target 컬럼을 보고 바로 판단해야 합니다.
| 항목 | 회귀 | 다중 분류 |
|---|---|---|
| target 형태 | 연속 수치 (예: 151.0, 200.5) | 범주형 (예: ‘cat’, ‘dog’, ‘bird’) |
| 모델 | RandomForestRegressor | RandomForestClassifier |
| 평가 지표 | RMSE | Accuracy, F1-score |
| 라벨 처리 | 불필요 | LabelEncoder 필요 (문자열 라벨 시) |
| 제출 값 | 예측 수치 그대로 | 원래 라벨명으로 복원 |
풀이 전략: 6단계 접근법
Step 1. 데이터 로드 및 기본 설정
python
import pandas as pd
import numpy as np
from pathlib import Path
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
accuracy_score, f1_score, classification_report,
confusion_matrix, roc_auc_score
)
from sklearn.preprocessing import LabelEncoder, label_binarize
import joblib
# ── 경로 설정 ──
DATA_DIR = Path('/content/drive/MyDrive/Colab Notebooks/data')
TRAIN_CSV = DATA_DIR / '01_train.csv'
TEST_CSV = DATA_DIR / '01_test_x.csv'
# ── 제출 파일 설정 ──
PHONE = '01012345678' # 본인 전화번호로 변경
OUT_CSV = DATA_DIR / f'{PHONE}_1.csv'
OUT_PKL = DATA_DIR / f'{PHONE}_1.pkl'
TARGET_COL = 'label' # 문제에서 제시하는 타겟 컬럼명으로 변경
ID_COL = 'id' # ID 컬럼이 있는 경우
# ── 데이터 로드 ──
train = pd.read_csv(TRAIN_CSV)
test = pd.read_csv(TEST_CSV)
Step 2. 전처리 및 라벨 인코딩
python
# X, y 분리
y_raw = train[TARGET_COL].values # 문자열 라벨일 수 있음
X = train.drop(columns=[TARGET_COL])
# 결측치 안전장치
if X.isnull().any().any():
X = X.fillna(X.mean(numeric_only=True))
if test.isnull().any().any():
test = test.fillna(test.mean(numeric_only=True))
# 라벨 인코딩: 문자열 라벨 → 정수
lbl = LabelEncoder()
y = lbl.fit_transform(y_raw) # 예: ['cat','dog','bird'] → [0, 1, 2]
class_names = list(lbl.classes_) # 나중에 원복할 때 사용
print("클래스:", class_names)
회귀 문제에서는 필요 없었던 LabelEncoder가 다중 분류에서는 핵심입니다. 라벨이 이미 정수(0, 1, 2)로 되어 있더라도 LabelEncoder를 적용해 두면, 나중에 inverse_transform()으로 안전하게 원복할 수 있어 실수를 줄일 수 있습니다.
팁:
class_names를 반드시 출력해서 확인하세요. 제출 시 이 이름들이 그대로 CSV에 들어갑니다. 오타나 대소문자 차이가 있으면 채점에서 틀릴 수 있습니다.
Step 3. 검증 데이터 분리
python
X_tr, X_va, y_tr, y_va = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
회귀와 다른 점이 하나 있습니다. stratify=y를 반드시 추가하세요. 다중 분류에서 stratify 없이 분리하면, 특정 클래스가 검증 세트에 아예 빠지거나 비율이 크게 왜곡될 수 있습니다. stratify를 지정하면 원본 데이터의 클래스 비율이 train/validation에 동일하게 유지됩니다.
Step 4. 모델 학습 및 검증
python
clf = RandomForestClassifier(
n_estimators=500,
max_depth=None,
min_samples_leaf=2,
n_jobs=-1,
random_state=42
)
# 학습
clf.fit(X_tr, y_tr)
# 예측
va_pred = clf.predict(X_va)
va_proba = clf.predict_proba(X_va)
# ── 평가 지표 ──
acc = accuracy_score(y_va, va_pred)
f1_macro = f1_score(y_va, va_pred, average='macro')
f1_weighted = f1_score(y_va, va_pred, average='weighted')
print(f'Validation Accuracy : {acc:.4f}')
print(f'Validation F1 (macro) : {f1_macro:.4f}')
print(f'Validation F1 (weighted): {f1_weighted:.4f}')
# 클래스별 상세 리포트
print('\nClassification Report:\n',
classification_report(y_va, va_pred, target_names=class_names))
print('Confusion Matrix:\n', confusion_matrix(y_va, va_pred))
다중 분류에서 확인해야 할 지표를 정리하면 이렇습니다.
| 지표 | 의미 | 언제 중요한가 |
|---|---|---|
| Accuracy | 전체 정답 비율 | 클래스가 균등할 때 |
| F1 (macro) | 클래스별 F1의 단순 평균 | 클래스 불균형이 있을 때 |
| F1 (weighted) | 클래스별 F1의 가중 평균 | 다수 클래스 성능도 중요할 때 |
문제에서 어떤 지표를 기준으로 평가하는지 반드시 확인하세요. 지표에 따라 튜닝 방향이 달라집니다.
(선택) 다중 클래스 ROC-AUC 계산
문제에서 AUC를 요구하는 경우에 대비해, 계산 방법도 알아 두면 좋습니다.
python
if len(class_names) > 2:
y_va_bin = label_binarize(y_va, classes=clf.classes_)
auc_macro = roc_auc_score(
y_va_bin, va_proba, average='macro', multi_class='ovr'
)
print(f'Validation ROC-AUC (macro, OVR): {auc_macro:.4f}')
여기서 주의할 점은, predict_proba()의 열 순서가 clf.classes_와 동일하다는 것입니다. label_binarize()에도 같은 classes 순서를 넘겨줘야 확률과 라벨이 올바르게 매칭됩니다.
Step 5. 최종 학습 및 테스트 예측
python
# 전체 데이터로 최종 학습
clf.fit(X, y)
# 테스트 예측
test_pred_int = clf.predict(test)
test_pred_lbl = lbl.inverse_transform(test_pred_int) # 원래 라벨명으로 복원
inverse_transform()을 잊지 마세요. 모델이 예측한 값은 정수(0, 1, 2)이지만, 제출 파일에는 원래 라벨명(‘cat’, ‘dog’, ‘bird’)을 넣어야 합니다. 이 변환을 빠뜨리고 정수를 그대로 제출하면 채점에서 틀릴 수 있습니다.
Step 6. 제출 파일 저장
python
# CSV 저장
if ID_COL in test.columns:
sub = pd.DataFrame({ID_COL: test[ID_COL], TARGET_COL: test_pred_lbl})
else:
sub = pd.DataFrame({'id': np.arange(len(test)), TARGET_COL: test_pred_lbl})
sub.to_csv(OUT_CSV, index=False)
print(f'Wrote: {OUT_CSV}')
# 모델 저장
joblib.dump(clf, OUT_PKL)
print(f'Wrote: {OUT_PKL}')
주의: 제출 CSV의 컬럼명과 라벨 포맷은 문제마다 다를 수 있습니다. 정수를 요구하는지, 문자열 클래스명을 요구하는지 문제 지시사항을 꼼꼼히 읽으세요.
성능이 안 나올 때 — 간단 튜닝 가이드
| 우선순위 | 조정 항목 | 방법 | 기대 효과 |
|---|---|---|---|
| 1 | n_estimators | 500 → 800 → 1000 | 트리 수 증가로 안정성 향상 |
| 2 | min_samples_leaf | 2 → 1 → 4 | 과적합/과소적합 균형 조절 |
| 3 | max_depth | None → 20 → 12 | 트리 깊이 제한으로 일반화 |
| 4 | class_weight | 'balanced' 추가 | 클래스 불균형 대응 |
| 5 | max_features | 1.0 → 0.7 → ‘sqrt’ | 각 트리가 보는 변수 비율 축소 |
클래스 불균형이 심한 경우, class_weight='balanced'를 추가하면 소수 클래스에 더 높은 가중치를 부여해서 성능이 개선될 수 있습니다. 이 한 줄 추가만으로 F1 (macro)가 크게 오르는 경우가 있으니, 반드시 시도해 보세요.
python
clf = RandomForestClassifier(
n_estimators=500,
max_depth=None,
min_samples_leaf=2,
class_weight='balanced', # 이 한 줄 추가
n_jobs=-1,
random_state=42
)
실전 체크리스트
시험장에서 제출 전, 이 순서대로 확인하세요.
- target 컬럼을 확인했는가? (연속 수치 → 회귀, 범주형 → 분류)
LabelEncoder로 라벨을 인코딩했는가?train_test_split에stratify=y를 넣었는가?- 검증 Accuracy/F1이 문제에서 요구하는 기준을 넘겼는가?
- 최종 학습은 전체 데이터로 했는가?
inverse_transform()으로 예측값을 원래 라벨명으로 복원했는가?- 제출 CSV의 컬럼명과 라벨 포맷이 문제 요구사항과 일치하는가?
- pkl 파일이 정상 저장되었는가?
마무리
1번 Tabular 문제는 회귀든 다중 분류든, RandomForest 하나로 충분히 만점을 노릴 수 있는 문제입니다. 다만 분류 문제가 나왔을 때 당황하지 않으려면, 회귀와의 차이점을 명확히 알아 두어야 합니다.
핵심을 세 가지로 요약하면 이렇습니다. LabelEncoder로 인코딩하고, stratify로 분리하고, inverse_transform으로 복원하세요. 이 세 단계만 빠뜨리지 않으면 30점은 확보할 수 있습니다.