분류(Classification)란?

1편에서 지도학습은 회귀와 분류로 나뉜다고 했습니다. 4편에서 회귀를 다뤘으니, 이번에는 분류를 파고듭니다.

분류는 데이터를 미리 정해진 범주(클래스)에 할당하는 문제입니다.

유형클래스 수예시
이진 분류2개스팸/정상, 양성/음성
다중 분류3개 이상붓꽃 종 분류, 숫자 인식(0~9)

1. 로지스틱 회귀 (Logistic Regression)

이름에 "회귀"가 들어가지만, 실제로는 분류 알고리즘입니다.

핵심 아이디어

선형 회귀의 출력을 시그모이드 함수에 통과시켜 0~1 사이의 확률로 변환합니다.

$$P(y=1|x) = \sigma(w^Tx + b) = \frac{1}{1 + e^{-(w^Tx + b)}}$$

  • 출력이 0.5 이상이면 클래스 1, 미만이면 클래스 0
  • 결정 경계는 $$w^Tx + b = 0$$인 직선(초평면)

손실 함수: 이진 교차 엔트로피 (Binary Cross-Entropy)

$$Loss = -\frac{1}{n}\sum_{i=1}^{n}[y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)]$$

  • 정답이 1인데 1에 가까운 예측을 하면 손실이 작음
  • 정답이 1인데 0에 가까운 예측을 하면 손실이 매우 큼

Python 실습

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

# 2차원 데이터 생성
X, y = make_classification(
    n_samples=200, n_features=2, n_redundant=0,
    n_informative=2, random_state=42, n_clusters_per_class=1
)

# 모델 학습
model = LogisticRegression()
model.fit(X, y)

print(f"가중치: {model.coef_[0]}")
print(f"편향: {model.intercept_[0]:.4f}")
print(f"정확도: {model.score(X, y):.4f}")

# 결정 경계 시각화
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                      np.linspace(y_min, y_max, 200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.3, cmap='RdBu')
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', edgecolors='black', s=30)
plt.title('로지스틱 회귀 결정 경계')
plt.xlabel('특성 1')
plt.ylabel('특성 2')
plt.show()

2. 서포트 벡터 머신 (SVM)

핵심 아이디어

두 클래스 사이의 마진(margin)을 최대화하는 결정 경계(초평면)를 찾습니다. 마진이 클수록 일반화 성능이 좋습니다.

  • 서포트 벡터: 결정 경계에 가장 가까운 데이터 포인트들
  • 하드 마진: 모든 데이터를 완벽히 분리 (이상치에 취약)
  • 소프트 마진: 일부 오분류를 허용 (C 파라미터로 제어)

커널 트릭 (Kernel Trick)

선형으로 분리 불가능한 데이터를 고차원 공간으로 매핑하여 분리합니다. 실제로 고차원으로 변환하지 않고 커널 함수로 효율적으로 계산합니다.

커널수식특징
선형$$K(x,z) = x^Tz$$선형 분리 가능할 때
RBF$$K(x,z) = e^{-\gamma|x-z|^2}$$가장 많이 사용, 비선형
다항식$$K(x,z) = (x^Tz + c)^d$$다항식 결정 경계

Python 실습: 커널별 비교

from sklearn.svm import SVC
from sklearn.datasets import make_moons

# 초승달 모양 데이터 (선형 분리 불가)
X, y = make_moons(n_samples=200, noise=0.2, random_state=42)

fig, axes = plt.subplots(1, 3, figsize=(15, 4))
kernels = ['linear', 'rbf', 'poly']
kernel_names = ['선형 커널', 'RBF 커널', '다항식 커널']

for ax, kernel, name in zip(axes, kernels, kernel_names):
    model = SVC(kernel=kernel, gamma='auto')
    model.fit(X, y)

    xx, yy = np.meshgrid(
        np.linspace(X[:, 0].min()-0.5, X[:, 0].max()+0.5, 200),
        np.linspace(X[:, 1].min()-0.5, X[:, 1].max()+0.5, 200)
    )
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    ax.contourf(xx, yy, Z, alpha=0.3, cmap='RdBu')
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', edgecolors='black', s=30)
    ax.set_title(f'{name} (정확도: {model.score(X, y):.2f})')

plt.tight_layout()
plt.show()

3. 의사결정 트리 (Decision Tree)

핵심 아이디어

데이터를 if-then 규칙으로 반복적으로 분할하여 분류합니다. 사람이 읽을 수 있는 규칙을 생성하므로 해석 가능성이 매우 높습니다.

분할 기준: 불순도 (Impurity)

지니 불순도 (Gini Impurity):

$$Gini = 1 - \sum_{i=1}^{C}p_i^2$$

정보 이득 (엔트로피 기반):

$$Entropy = -\sum_{i=1}^{C}p_i \log_2(p_i)$$

  • $$p_i$$: 클래스 $$i$$의 비율
  • 불순도가 가장 많이 감소하는 분할을 선택

Python 실습: 트리 시각화

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris

# 붓꽃 데이터 (2개 특성만 사용하여 시각화)
iris = load_iris()
X = iris.data[:, [2, 3]]  # 꽃잎 길이, 꽃잎 너비
y = iris.target

# 깊이 제한된 트리
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X, y)

# 트리 시각화
plt.figure(figsize=(15, 8))
plot_tree(model,
          feature_names=['꽃잎 길이', '꽃잎 너비'],
          class_names=iris.target_names,
          filled=True, rounded=True,
          fontsize=10)
plt.title('의사결정 트리 (max_depth=3)')
plt.show()

# 결정 경계 시각화
xx, yy = np.meshgrid(
    np.linspace(X[:, 0].min()-0.5, X[:, 0].max()+0.5, 200),
    np.linspace(X[:, 1].min()-0.5, X[:, 1].max()+0.5, 200)
)
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis',
                      edgecolors='black', s=30)
plt.xlabel('꽃잎 길이 (cm)')
plt.ylabel('꽃잎 너비 (cm)')
plt.title('의사결정 트리 결정 경계')
plt.colorbar(scatter)
plt.show()

4. k-최근접 이웃 (k-NN)

핵심 아이디어

새로운 데이터가 들어오면 가장 가까운 k개의 이웃을 찾아서 다수결 투표로 분류합니다. 별도의 학습 과정이 없는 **게으른 학습기(lazy learner)**입니다.

  • k가 작으면: 결정 경계가 복잡 → 과적합 위험
  • k가 크면: 결정 경계가 단순 → 과소적합 위험

거리 측정 방법

거리수식특징
유클리드$$\sqrt{\sum(x_i-y_i)^2}$$가장 일반적
맨해튼$$\sum|x_i-y_i|$$고차원에서 유용
민코프스키$$(\sum|x_i-y_i|^p)^{1/p}$$일반화된 거리

Python 실습: k값에 따른 변화

from sklearn.neighbors import KNeighborsClassifier

X, y = make_moons(n_samples=200, noise=0.3, random_state=42)

fig, axes = plt.subplots(1, 3, figsize=(15, 4))
k_values = [1, 5, 30]

for ax, k in zip(axes, k_values):
    model = KNeighborsClassifier(n_neighbors=k)
    model.fit(X, y)

    xx, yy = np.meshgrid(
        np.linspace(X[:, 0].min()-0.5, X[:, 0].max()+0.5, 200),
        np.linspace(X[:, 1].min()-0.5, X[:, 1].max()+0.5, 200)
    )
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

    ax.contourf(xx, yy, Z, alpha=0.3, cmap='RdBu')
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', edgecolors='black', s=30)
    ax.set_title(f'k = {k} (정확도: {model.score(X, y):.2f})')

plt.tight_layout()
plt.show()

5. 나이브 베이즈 (Naive Bayes)

핵심 아이디어

베이즈 정리를 기반으로 각 클래스의 사후 확률을 계산하고, 확률이 가장 높은 클래스로 분류합니다.

$$P(y|x) = \frac{P(x|y) \cdot P(y)}{P(x)}$$

"나이브(Naive)"인 이유: 모든 특성이 서로 독립이라고 가정합니다. 현실에서는 거의 성립하지 않지만, 놀랍게도 실전에서 잘 작동합니다.

변형

변형데이터 분포 가정적합한 데이터
가우시안정규분포연속형 수치 데이터
다항다항분포텍스트 (단어 빈도)
베르누이이진분포이진 특성 (있다/없다)

Python 실습

from sklearn.naive_bayes import GaussianNB

X, y = make_classification(
    n_samples=200, n_features=2, n_redundant=0,
    n_informative=2, random_state=42
)

model = GaussianNB()
model.fit(X, y)

xx, yy = np.meshgrid(
    np.linspace(X[:, 0].min()-1, X[:, 0].max()+1, 200),
    np.linspace(X[:, 1].min()-1, X[:, 1].max()+1, 200)
)
Z = model.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1].reshape(xx.shape)

plt.contourf(xx, yy, Z, levels=20, alpha=0.6, cmap='RdBu')
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='RdBu', edgecolors='black', s=30)
plt.colorbar(label='P(y=1)')
plt.title(f'나이브 베이즈 확률 분포 (정확도: {model.score(X, y):.2f})')
plt.show()

6. 분류 평가 지표

분류는 단순 정확도만으로 평가하면 안 됩니다. 특히 불균형 데이터에서는 더욱 그렇습니다.

혼동 행렬 (Confusion Matrix)

예측: 양성예측: 음성
실제: 양성TP (참 양성)FN (거짓 음성)
실제: 음성FP (거짓 양성)TN (참 음성)

핵심 지표

지표수식의미
정확도$$\frac{TP+TN}{TP+TN+FP+FN}$$전체 중 맞춘 비율
정밀도$$\frac{TP}{TP+FP}$$양성 예측 중 실제 양성 비율
재현율$$\frac{TP}{TP+FN}$$실제 양성 중 찾아낸 비율
F1-Score$$2 \cdot \frac{정밀도 \cdot 재현율}{정밀도 + 재현율}$$정밀도와 재현율의 조화 평균

정밀도 vs 재현율: 스팸 필터는 정밀도가 중요 (정상 메일을 스팸으로 분류하면 안 됨). 암 진단은 재현율이 중요 (암 환자를 놓치면 안 됨).

from sklearn.metrics import (confusion_matrix, classification_report,
                             ConfusionMatrixDisplay)

# 예시 (나중 토이 프로블럼에서 사용)
def plot_confusion_matrix(y_true, y_pred, labels=None, title="혼동 행렬"):
    cm = confusion_matrix(y_true, y_pred)
    disp = ConfusionMatrixDisplay(cm, display_labels=labels)
    disp.plot(cmap='Blues')
    plt.title(title)
    plt.show()

    print(classification_report(y_true, y_pred, target_names=labels))

7. 토이 프로블럼 ①: 붓꽃 품종 분류

7-1. 데이터 탐색

import pandas as pd
from sklearn.datasets import load_iris

iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = pd.Series(iris.target, name='species')

print("=== 데이터 개요 ===")
print(f"샘플 수: {X.shape[0]}, 특성 수: {X.shape[1]}")
print(f"클래스: {iris.target_names} (각 50개)")
print()
print(X.describe().round(2))
# 특성 분포 시각화
import seaborn as sns

df = X.copy()
df['종'] = [iris.target_names[i] for i in y]

fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for ax, col in zip(axes.ravel(), iris.feature_names):
    sns.boxplot(data=df, x='종', y=col, ax=ax, palette='Set2')
    ax.set_title(col)

plt.tight_layout()
plt.show()
# 페어플롯 (특성 간 관계)
sns.pairplot(df, hue='종', palette='Set2', diag_kind='kde')
plt.suptitle('붓꽃 특성 페어플롯', y=1.02)
plt.show()

7-2. 전처리 및 학습

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42, stratify=iris.target
)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

7-3. 5가지 알고리즘 비교

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score

models = {
    '로지스틱 회귀': LogisticRegression(max_iter=200),
    'SVM (RBF)': SVC(kernel='rbf', gamma='auto'),
    '의사결정 트리': DecisionTreeClassifier(max_depth=4, random_state=42),
    'k-NN (k=5)': KNeighborsClassifier(n_neighbors=5),
    '나이브 베이즈': GaussianNB(),
}

print(f"{'모델':<16} {'Train 정확도':>12} {'Test 정확도':>12} {'CV 평균(5-fold)':>16}")
print("-" * 60)

for name, model in models.items():
    model.fit(X_train_scaled, y_train)
    train_acc = model.score(X_train_scaled, y_train)
    test_acc = model.score(X_test_scaled, y_test)
    cv_scores = cross_val_score(model, X_train_scaled, y_train, cv=5)

    print(f"{name:<16} {train_acc:>12.4f} {test_acc:>12.4f} {cv_scores.mean():>12.4f} ± {cv_scores.std():.4f}")

7-4. 최적 모델 상세 평가

# SVM이 일반적으로 가장 좋은 성능
best_model = SVC(kernel='rbf', gamma='auto')
best_model.fit(X_train_scaled, y_train)
y_pred = best_model.predict(X_test_scaled)

# 혼동 행렬
plot_confusion_matrix(y_test, y_pred, labels=iris.target_names,
                      title='SVM - 붓꽃 분류 혼동 행렬')

8. 토이 프로블럼 ②: 유방암 진단 (이진 분류)

실제 의료 데이터를 활용한 이진 분류 문제입니다.

8-1. 데이터 탐색

from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()
X = pd.DataFrame(cancer.data, columns=cancer.feature_names)
y = pd.Series(cancer.target, name='diagnosis')

print("=== 데이터 개요 ===")
print(f"샘플 수: {X.shape[0]}, 특성 수: {X.shape[1]}")
print(f"클래스: {cancer.target_names}")
print(f"  악성(0): {(y == 0).sum()}개")
print(f"  양성(1): {(y == 1).sum()}개")
print(f"  양성 비율: {y.mean():.2%}")

8-2. 전처리

X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, test_size=0.2, random_state=42, stratify=cancer.target
)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

print(f"학습 세트: {X_train.shape[0]}개")
print(f"테스트 세트: {X_test.shape[0]}개")

8-3. 모델 비교

from sklearn.metrics import f1_score, recall_score, precision_score

models = {
    '로지스틱 회귀': LogisticRegression(max_iter=5000),
    'SVM (RBF)': SVC(kernel='rbf'),
    '의사결정 트리': DecisionTreeClassifier(max_depth=5, random_state=42),
    'k-NN (k=5)': KNeighborsClassifier(n_neighbors=5),
    '나이브 베이즈': GaussianNB(),
}

print(f"{'모델':<16} {'정확도':>8} {'정밀도':>8} {'재현율':>8} {'F1':>8}")
print("-" * 52)

for name, model in models.items():
    model.fit(X_train_scaled, y_train)
    y_pred = model.predict(X_test_scaled)

    acc = accuracy_score(y_test, y_pred)
    prec = precision_score(y_test, y_pred)
    rec = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)

    print(f"{name:<16} {acc:>8.4f} {prec:>8.4f} {rec:>8.4f} {f1:>8.4f}")

8-4. 최적 모델 상세 분석

# 로지스틱 회귀 - 해석 가능하고 성능도 좋음
best_model = LogisticRegression(max_iter=5000)
best_model.fit(X_train_scaled, y_train)
y_pred = best_model.predict(X_test_scaled)

# 혼동 행렬
plot_confusion_matrix(y_test, y_pred, labels=cancer.target_names,
                      title='로지스틱 회귀 - 유방암 진단 혼동 행렬')

의료 진단에서는 재현율이 핵심: FN(거짓 음성 = 암인데 정상 판정)이 가장 위험합니다. 재현율이 낮은 모델은 의료 현장에서 사용할 수 없습니다.

8-5. 특성 중요도 분석

# 로지스틱 회귀 가중치 분석
importance = pd.Series(
    np.abs(best_model.coef_[0]),
    index=cancer.feature_names
).sort_values(ascending=False).head(10)

plt.figure(figsize=(10, 6))
importance.sort_values().plot(kind='barh')
plt.title('유방암 진단: 상위 10개 중요 특성')
plt.xlabel('|가중치|')
plt.tight_layout()
plt.show()

# 상위 특성 해석
print("\n상위 5개 특성:")
for feat in importance.head(5).index:
    coef = best_model.coef_[0][list(cancer.feature_names).index(feat)]
    direction = "양성(정상)" if coef > 0 else "악성"
    print(f"  {feat}: 가중치 {coef:.4f} → 값이 클수록 {direction}일 확률 ↑")

알고리즘 선택 가이드

알고리즘장점단점추천 상황
로지스틱 회귀빠르고 해석 쉬움, 확률 출력비선형 패턴 포착 어려움기본 베이스라인, 의료/금융
SVM고차원 데이터에 강함대규모 데이터에 느림중소규모 + 고차원 데이터
의사결정 트리직관적 규칙, 전처리 불필요과적합 위험해석이 필수인 경우
k-NN단순하고 직관적차원의 저주, 예측 느림소규모 데이터, 프로토타이핑
나이브 베이즈매우 빠름, 적은 데이터독립 가정이 강함텍스트 분류, 실시간 처리

다음 글에서는 이 분류기들을 **앙상블(조합)**하여 더 강력한 모델을 만드는 랜덤 포레스트와 부스팅을 다룹니다.