회귀의 한 종류인 다중 라벨 분류에 대해 정리하려고 한다

분류기가 샘플마다 여러 개의 클래스를 출력해야할 때가 있다, 이럴 때 사용되는 것이 바로 다중 레이블 분류다!

다중 레이블의 예시를 찾아보면 아래와 같다! 생각보다 다양하다.

  • 음악 장르 분류
    • 한 곡이 '재즈', '블루스' 등 여러 장르로 분류될 수 있다.
  • 영화 태그 분류
    • 한 영화가 '액션', 'SF' 등 여러 장르에 속할 수 있으며, '범죄수사물', '' 등 다양한 태그를 가질 수 있다!
  • 의료 진단
    • 환자가 여러 질병을 동시에 가질 수 있기에 의료 진단 역시 다중 레이블 분류다!
    • 한 환자가 '고혈압', '당뇨' 등 여러 질환을 동시에 진단 받을 수 있다.
  • 텍스트 분류
    • 블로그 게시물, 뉴스 기사 등 여러 가지 주제를 동시에 할당할 수 있다.
    • 뉴스 카테고리 분류를 생각하면 좋을 것 같다! 한 기사가 '정치', '경제' 등 여러 개의 카테고리에 속할 수 있다.

만약 '정치', '경제', '사회' 세 가지 카테고리에 따라 뉴스 기사를 분류하도록 훈련된 분류기가 있다고 해보자. 만약 정치와 사회 카테고리에 속하는 기사를 본다면 [1, 0, 1]을 출력할 것이다. 즉, [정치 카테고리 맞음, 경제 카테고리 아님, 사회 카테고리 맞음] 이렇게 분류되는 것이다.

이렇게 여러 개의 이진 꼬리표를 출력하는 분류 시스템이 바로 다중 레이블 분류 시스템이다.

🔥 다중 레이블 분류를 할 수 있는 Scikit-learn 함수

설계와 알고리즘 차이에 의해 기본적으로 모든 분류기가 다중 레이블 분류를 지원하지 않는다.

1) KNeighborsClassifier

class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, *, 
weights='uniform', algorithm='auto', leaf_size=30, p=2, 
metric='minkowski', metric_params=None, n_jobs=None)[source]

KNeighborsClassifier는 K-최근접 이웃(K-NN) 알고리즘을 사용하여 샘플의 주변 이웃의 라벨을 보고, 가장 많은 라벨을 갖는 클래스를 할당한다.
각 클래스에 대해 독립적인 판단을 내릴 수 있기에 다중 레이블 문제에서 유연하게 작동 가능하다고 한다.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score

# ...중략

# K-최근접 이웃 분류기 생성 (k=3)
knn = KNeighborsClassifier(n_neighbors=3)

# 학습
knn.fit(X_train, y_train)

# 예측
y_pred = knn.predict(X_test)

# 정확도 평가
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")

참고 : 사이킷런 공식 문서

2) OneVsRestClassifier

class sklearn.multiclass.OneVsRestClassifier(estimator, *, 
n_jobs=None, verbose=0)

OneVsRestClassifier는 본래 다중 클래스 분류 문제를 다중 이진 분류 문제로 변환하는 방법이다. 즉 이진 분류 문제로 나누어 학습하는 것이다. 각 레이블에 대한 이진 분류기를 학습시켜서 각 레이블에 대한 예측을 개별적으로 처리한다. 즉 직접 다중 레이블 분류를 지원하는 함수는 아니다.

from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.datasets import make_multilabel_classification
from sklearn.model_selection import train_test_split

# 다중 레이블 데이터를 생성
X, y = make_multilabel_classification(n_samples=100, n_classes=3, random_state=42)

# 데이터셋 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# SVM 분류기를 OneVsRestClassifier로 래핑하여 다중 레이블 분류 수행
clf = OneVsRestClassifier(SVC())
clf.fit(X_train, y_train)

# 테스트 데이터에 대한 예측
y_pred = clf.predict(X_test)

참고 : 사이킷런 공식 문서

3) DecisionTreeClassifier

class sklearn.tree.DecisionTreeClassifier(*, criterion='gini', 
splitter='best', max_depth=None, min_samples_split=2, 
min_samples_leaf=1, min_weight_fraction_leaf=0.0, 
max_features=None, random_state=None, max_leaf_nodes=None, 
min_impurity_decrease=0.0, class_weight=None, ccp_alpha=0.0, 
monotonic_cst=None)

뭔 인자가 이렇게 괴랄하게 많지...

이 함수는 기본적으로 다중 레이블 분류를 지원한다.

결정 트리 모델이 각 레이블에 대해 독립적인 트리를 만들어서, 각 레이블에 대한 예측을 동시에 할 수 있다. 그래서 다중 출력 결정 트리라고 부른다.

참고로 별도의 추가적인 알고리즘 없이도 다중 레이블 출력이 가능하다.

from sklearn.tree import DecisionTreeClassifier

# 다중 레이블 분류용 데이터셋을 학습시키기 위한 결정 트리 분류기 생성
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)  # X_train: 입력 데이터, y_train: 다중 레이블 (2D 배열)

# 예측
y_pred = clf.predict(X_test)

참고 : 사이킷런 공식 문서

4) RandomForestClassifier

class sklearn.ensemble.RandomForestClassifier(n_estimators=100, *,
criterion='gini', max_depth=None, min_samples_split=2, 
min_samples_leaf=1, min_weight_fraction_leaf=0.0, 
max_features='sqrt', max_leaf_nodes=None, 
min_impurity_decrease=0.0, bootstrap=True, oob_score=False, 
n_jobs=None, random_state=None, verbose=0, warm_start=False, 
class_weight=None, ccp_alpha=0.0, max_samples=None, 
monotonic_cst=None)

이건 더 많네...

랜덤 포레스트는 여러 결정 트리의 앙상블을 통해 예측 성능을 향상시키며, 다중 레이블 문제에서도 동일한 방식으로 동작한다.

다중 레이블 분류 문제에서 랜덤 포레스트는 각 레이블에 대해 독립적인 트리를 생성하여 예측을 수행할 수 있다. 결정 트리처럼 다중 출력 지원이 가능하며, 기본적으로 다중 레이블 분류에 적합하게 동작한다.

from sklearn.ensemble import RandomForestClassifier

# 다중 레이블 분류용 랜덤 포레스트 분류기 생성
clf = RandomForestClassifier()
clf.fit(X_train, y_train)  # X_train: 입력 데이터, y_train: 다중 레이블 (2D 배열)

# 예측
y_pred = clf.predict(X_test)

참고 : 사이킷런 공식 문서

주의! 다중 레이블 분류 데이터 형식

위의 네 가지 함수들을 사용할 때, target label을 2차원 바이너리(0, 1) 배열 형식으로 준비해야 한다! 이 배열의 크기는 (n_samples, n_labels)로, 각 행은 하나의 샘플에 대해 여러 개의 레이블을 가질 수 있는지 여부를 나타낸다고 한다.

y_train = [
  [1, 0, 1],
  [0, 1, 0], 
  [1, 1, 0]
]

🔥 OvR 전략을 다중 레이블 분류에 사용

One-vs-the-rest (OvR) 다중 클래스 전략 = OvA 전략 이라고 한다.
cf) 다중 클래스 OvA OvO 전략

작동 과정

다중 레이블 문제에서 OvR을 사용할 때는 각 레이블에 대해 하나의 이진 분류기를 학습한다. 이 방식은 Binary Relevance라고도 불리며, 각 레이블을 독립적으로 분류한다.

  1. 각 레이블을 이진 분류 문제로 변환

  2. 각 레이블에 대해 독립적인 이진 분류기를 학습

    • 분류기 1: 레이블 A vs 나머지 (A의 유무를 예측)
    • 분류기 2: 레이블 B vs 나머지 (B의 유무를 예측)
    • 분류기 3: 레이블 C vs 나머지 (C의 유무를 예측)
  3. 예측 시, 각 레이블에 대해 독립적으로 예측을 수행

    • 결과적으로 각 샘플에 대해 레이블 A, B, C 중 어떤 것이 포함되어 있는지를 예측

🔥 ClassifierChain

class sklearn.multioutput.ClassifierChain(base_estimator, *, 
order=None, cv=None, chain_method='predict', 
random_state=None, verbose=False)

OvR과는 작동 방식이 다르니 주의해 주어야 한다!

다중 레이블 분류에서 레이블 간의 의존성을 고려하는 방식을 사용한다. 각 레이블을 순차적으로 예측하면서, 이전에 예측한 레이블을 그 다음 레이블의 특징(feature)으로 사용한다는 것이 핵심이다 🔥 즉, 이전에 예측된 레이블들이 그 이후 레이블을 예측하는 데 영향을 미치게 된다!

1) ClassifierChain 작동 방식

1. 순차적 예측

  • 각 레이블에 대해 분류기를 하나씩 학습시키는데,
    이때 각 분류기의 입력으로 원래 입력 데이터뿐만 아니라,
    앞서 예측한 레이블도 포함시킨다.
  • 앞서 관측한 것을 포함해야 하기에 순차적으로 관측하게 되는 것이다!

2. 순서 중요성

  • 첫 번째 분류기는 원래 입력 데이터만 사용하지만, 두 번째 분류기부터는 이전 분류기의 예측 결과를 추가로 사용하여 학습합니다. 이렇게 학습된 각 분류기는 그 이후의 레이블 예측에 영향을 미치게 된다.

2) 작동 방식 예시

다음과 같은 3개의 레이블이 있다고 가정해 보자! (L1, L2, L3).

분류기 1: 입력 데이터 X를 사용하여 레이블 L1을 예측
분류기 2: 입력 데이터 X와 앞서 예측한 레이블 L1을 함께 사용하여 L2를 예측
분류기 3: 입력 데이터 X, L1, L2를 모두 사용하여 L3을 예측

이렇게 각 분류기의 예측은 이전 분류기의 예측 결과에 의존하게 되는 방식이다.

정리

OvR (기본적으로 다중 클래스 분류에 이용)

  • 각 레이블에 대해 독립적인 이진 분류기를 학습
  • 레이블 간의 상관관계는 고려하지 않음
  • 각 분류기는 서로 독립적

ClassifierChain

  • 레이블 간의 상관관계를 반영
  • 각 레이블의 예측 결과를 다음 레이블 예측에 활용
  • 분류기가 순차적으로 연결된 방식으로 작동

🔥 평가 방식

적절한 지표는 프로젝트에 따라 다르다.

1) F1F_1 점수를 이용하는 방법
각 레이블의 F1F_1 점수를 구하여 간단하게 평균 점수를 계산하는 방법이 잇따.

만약 모든 레이블의 가중치가 같지 않다면,
레이블에 클래스에 지지도(support, target label에 속한 샘플 수)를 가중치로 주면 된다. f1_score(average = "weighted")이렇게 설정하면 된다.

2)SVC와 같이 기본적으로 다중 레이블 분류를 지원하지 않는 분류기 사용하는 경우

→ 레이블당 하나의 모델 학습

하지만 이 전략은 레이블 간의 의존성을 포착하기 어렵게 하기에 이 문제 해결을 위해 모델을 체인(chain)으로 구성하는 방법을 이용할 수 있다.

사이킷런에서 이런 작업을 수행하는 것이 위에서 다룬 ClassifierChain 클래스다.

profile
호로록

0개의 댓글