텍스트 문서 분류

Beginner

This tutorial is from open-source community. Access the source code

소개

이 실습에서는 scikit-learn 을 사용하여 텍스트 문서를 여러 카테고리로 분류하는 방법을 보여줍니다. 20 개의 주제에 대한 약 18,000 개의 뉴스그룹 게시물을 포함하는 20 뉴스그룹 데이터셋을 사용합니다. 단어 빈도 (Bag of Words) 접근 방식과 Tf-idf 가중치 문서 - 용어 희소 행렬을 사용하여 특징을 인코딩합니다. 이 실습에서는 희소 행렬을 효율적으로 처리할 수 있는 다양한 분류기를 보여줄 것입니다.

VM 팁

VM 시작이 완료되면 왼쪽 상단 모서리를 클릭하여 Notebook 탭으로 전환하여 연습을 위한 Jupyter Notebook에 접근합니다.

때때로 Jupyter Notebook 이 완전히 로드되기까지 몇 초 정도 기다려야 할 수 있습니다. Jupyter Notebook 의 제한으로 인해 작업 검증은 자동화될 수 없습니다.

학습 중 문제가 발생하면 Labby 에게 문의하십시오. 세션 후 피드백을 제공하면 문제를 신속하게 해결해 드리겠습니다.

20 뉴스그룹 텍스트 데이터셋 로드 및 벡터화

20 개 주제에 대한 약 18,000 개의 뉴스그룹 게시물로 구성된 20newsgroups 데이터셋에서 데이터를 로드하는 함수를 정의합니다. 이 데이터셋은 학습용과 테스트용 두 개의 하위 집합으로 나뉩니다. 메타데이터 제거 없이 데이터셋을 로드하고 벡터화합니다.

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer

categories = [
    "alt.atheism",
    "talk.religion.misc",
    "comp.graphics",
    "sci.space",
]

def load_dataset(verbose=False, remove=()):
    """20 뉴스그룹 데이터셋을 로드하고 벡터화합니다."""
    data_train = fetch_20newsgroups(
        subset="train",
        categories=categories,
        shuffle=True,
        random_state=42,
        remove=remove,
    )

    data_test = fetch_20newsgroups(
        subset="test",
        categories=categories,
        shuffle=True,
        random_state=42,
        remove=remove,
    )

    ## `target_names` 의 레이블 순서는 `categories` 와 다를 수 있습니다.
    target_names = data_train.target_names

    ## 학습용 및 테스트용 데이터셋으로 타겟 분할
    y_train, y_test = data_train.target, data_test.target

    ## 희소 벡터화기를 사용하여 학습 데이터에서 특징 추출
    vectorizer = TfidfVectorizer(
        sublinear_tf=True, max_df=0.5, min_df=5, stop_words="english"
    )
    X_train = vectorizer.fit_transform(data_train.data)

    ## 동일한 벡터화기를 사용하여 테스트 데이터에서 특징 추출
    X_test = vectorizer.transform(data_test.data)

    feature_names = vectorizer.get_feature_names_out()

    if verbose:
        print(f"{len(data_train.data)}개의 문서")
        print(f"{len(data_test.data)}개의 문서")
        print(f"{len(target_names)}개의 카테고리")
        print(f"n_samples: {X_train.shape[0]}, n_features: {X_train.shape[1]}")
        print(f"n_samples: {X_test.shape[0]}, n_features: {X_test.shape[1]}")

    return X_train, X_test, y_train, y_test, feature_names, target_names

X_train, X_test, y_train, y_test, feature_names, target_names = load_dataset(verbose=True)

단어 빈도 기반 문서 분류기 분석

이제 메타데이터를 포함한 텍스트 샘플과 메타데이터를 제거한 텍스트 샘플 각각에 대해 분류기를 두 번 학습시킵니다. 혼동 행렬을 사용하여 테스트 세트의 분류 오류를 분석하고, 학습된 모델의 분류 함수를 정의하는 계수를 검사합니다.

from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

clf = RidgeClassifier(tol=1e-2, solver="sparse_cg")
clf.fit(X_train, y_train)
pred = clf.predict(X_test)

fig, ax = plt.subplots(figsize=(10, 5))
ConfusionMatrixDisplay.from_predictions(y_test, pred, ax=ax)
ax.xaxis.set_ticklabels(target_names)
ax.yaxis.set_ticklabels(target_names)
_ = ax.set_title(
    f"Confusion Matrix for {clf.__class__.__name__}\non the original documents"
)

def plot_feature_effects():
    ## 출현 빈도에 가중치를 둔 학습된 계수
    average_feature_effects = clf.coef_ * np.asarray(X_train.mean(axis=0)).ravel()

    for i, label in enumerate(target_names):
        top5 = np.argsort(average_feature_effects[i])[-5:][::-1]
        if i == 0:
            top = pd.DataFrame(feature_names[top5], columns=[label])
            top_indices = top5
        else:
            top[label] = feature_names[top5]
            top_indices = np.concatenate((top_indices, top5), axis=None)
    top_indices = np.unique(top_indices)
    predictive_words = feature_names[top_indices]

    ## 특징 효과 시각화
    bar_size = 0.25
    padding = 0.75
    y_locs = np.arange(len(top_indices)) * (4 * bar_size + padding)

    fig, ax = plt.subplots(figsize=(10, 8))
    for i, label in enumerate(target_names):
        ax.barh(
            y_locs + (i - 2) * bar_size,
            average_feature_effects[i, top_indices],
            height=bar_size,
            label=label,
        )
    ax.set(
        yticks=y_locs,
        yticklabels=predictive_words,
        ylim=[
            0 - 4 * bar_size,
            len(top_indices) * (4 * bar_size + padding) - 4 * bar_size,
        ],
    )
    ax.legend(loc="lower right")

    print("각 클래스별 상위 5 개 키워드:")
    print(top)

    return ax

_ = plot_feature_effects().set_title("원본 데이터의 평균 특징 효과")

메타데이터 제거 모델

이제 scikit-learn 의 20 뉴스그룹 데이터셋 로더의 remove 옵션을 사용하여 메타데이터에 지나치게 의존하지 않는 텍스트 분류기를 학습시킵니다. 또한 혼동 행렬을 사용하여 테스트 세트의 분류 오류를 분석하고, 학습된 모델의 분류 함수를 정의하는 계수를 검사합니다.

(
    X_train,
    X_test,
    y_train,
    y_test,
    feature_names,
    target_names,
) = load_dataset(remove=("headers", "footers", "quotes"))

clf = RidgeClassifier(tol=1e-2, solver="sparse_cg")
clf.fit(X_train, y_train)
pred = clf.predict(X_test)

fig, ax = plt.subplots(figsize=(10, 5))
ConfusionMatrixDisplay.from_predictions(y_test, pred, ax=ax)
ax.xaxis.set_ticklabels(target_names)
ax.yaxis.set_ticklabels(target_names)
_ = ax.set_title(
    f"Confusion Matrix for {clf.__class__.__name__}\non filtered documents"
)

_ = plot_feature_effects().set_title("Filtered documents 의 평균 특징 효과")

분류기 성능 비교

이제 여덟 가지 다른 분류 모델로 데이터셋을 학습 및 테스트하고 각 모델의 성능 결과를 얻을 것입니다. 이 연구의 목표는 다중 클래스 텍스트 분류 문제에 대해 서로 다른 유형의 분류기의 계산/정확도 절충을 보여주는 것입니다.

from sklearn.utils.extmath import density
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.linear_model import SGDClassifier
from sklearn.naive_bayes import ComplementNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neighbors import NearestCentroid
from sklearn.ensemble import RandomForestClassifier

results = []
for clf, name in (
    (LogisticRegression(C=5, max_iter=1000), "Logistic Regression"),
    (RidgeClassifier(alpha=1.0, solver="sparse_cg"), "Ridge Classifier"),
    (KNeighborsClassifier(n_neighbors=100), "kNN"),
    (RandomForestClassifier(), "Random Forest"),
    ## L2 penalty Linear SVC
    (LinearSVC(C=0.1, dual=False, max_iter=1000), "Linear SVC"),
    ## L2 penalty Linear SGD
    (
        SGDClassifier(
            loss="log_loss", alpha=1e-4, n_iter_no_change=3, early_stopping=True
        ),
        "log-loss SGD",
    ),
    ## NearestCentroid (aka Rocchio classifier)
    (NearestCentroid(), "NearestCentroid"),
    ## Sparse naive Bayes classifier
    (ComplementNB(alpha=0.1), "Complement naive Bayes"),
):
    print("=" * 80)
    print(name)
    results.append(benchmark(clf, name))

indices = np.arange(len(results))

results = [[x[i] for x in results] for i in range(4)]

clf_names, score, training_time, test_time = results
training_time = np.array(training_time)
test_time = np.array(test_time)

fig, ax1 = plt.subplots(figsize=(10, 8))
ax1.scatter(score, training_time, s=60)
ax1.set(
    title="Score-training time trade-off",
    yscale="log",
    xlabel="테스트 정확도",
    ylabel="학습 시간 (초)",
)
fig, ax2 = plt.subplots(figsize=(10, 8))
ax2.scatter(score, test_time, s=60)
ax2.set(
    title="Score-test time trade-off",
    yscale="log",
    xlabel="테스트 정확도",
    ylabel="테스트 시간 (초)",
)

for i, txt in enumerate(clf_names):
    ax1.annotate(txt, (score[i], training_time[i]))
    ax2.annotate(txt, (score[i], test_time[i]))

요약

이 실험에서는 scikit-learn 을 사용하여 텍스트 문서를 여러 카테고리로 분류하는 방법을 보여주었습니다. 20 뉴스그룹 데이터셋을 로드하고, 단어 빈도 (bag of words) 접근 방식과 Tf-idf 가중치 문서 - 용어 희소 행렬을 사용하여 특징을 인코딩했습니다. 메타데이터가 포함된 텍스트 샘플과 메타데이터를 제거한 텍스트 샘플 각각에 대해 분류기를 한 번씩 학습시켰습니다. 테스트 세트에서 혼동 행렬을 사용하여 분류 오류를 분석하고, 학습된 모델의 분류 함수를 정의하는 계수를 검사했습니다. 또한, 여덟 가지 다른 분류 모델로 데이터셋을 학습 및 테스트하여 각 모델의 성능 결과를 얻었습니다. 이 연구의 목표는 다중 클래스 텍스트 분류 문제에 대해 서로 다른 유형의 분류기의 계산/정확도 절충을 보여주는 것이었습니다.