文本文档分类

Machine LearningMachine LearningBeginner
立即练习

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

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本实验展示了如何使用 scikit-learn 将文本文档分类到不同类别。我们将使用 20 新闻组数据集,其中包含约 18,000 篇关于 20 个主题的新闻组帖子。我们将使用词袋方法和 Tf-idf 加权文档 - 术语稀疏矩阵来编码特征。本实验还将展示各种能够有效处理稀疏矩阵的分类器。

虚拟机使用提示

虚拟机启动完成后,点击左上角切换到笔记本标签页,以访问 Jupyter Notebook 进行练习。

有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。

如果你在学习过程中遇到问题,随时向 Labby 提问。课程结束后提供反馈,我们会及时为你解决问题。

加载并向量化 20 新闻组文本数据集

我们定义一个函数来从 20 新闻组数据集中加载数据,该数据集包含约 18,000 篇关于 20 个主题的新闻组帖子,分为两个子集:一个用于训练,另一个用于测试。我们将在不剥离元数据的情况下加载并向量化数据集。

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"样本数:{X_train.shape[0]}, 特征数:{X_train.shape[1]}")
        print(f"样本数:{X_test.shape[0]}, 特征数:{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"混淆矩阵,用于 {clf.__class__.__name__}\n在原始文档上"
)

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"{clf.__class__.__name__} 的混淆矩阵\n在过滤后的文档上"
)

_ = plot_feature_effects().set_title("对过滤后文档的平均特征效应")

对分类器进行基准测试

现在我们将使用八个不同的分类模型对数据集进行训练和测试,并获取每个模型的性能结果。本研究的目的是突出针对此类多类别文本分类问题,不同类型分类器在计算量/准确性方面的权衡。

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), "逻辑回归"),
    (RidgeClassifier(alpha=1.0, solver="sparse_cg"), "岭分类器"),
    (KNeighborsClassifier(n_neighbors=100), "k 近邻"),
    (RandomForestClassifier(), "随机森林"),
    ## L2 惩罚的线性支持向量分类器
    (LinearSVC(C=0.1, dual=False, max_iter=1000), "线性支持向量分类器"),
    ## L2 惩罚的线性随机梯度下降
    (
        SGDClassifier(
            loss="log_loss", alpha=1e-4, n_iter_no_change=3, early_stopping=True
        ),
        "对数损失随机梯度下降",
    ),
    ## 最近质心(又名罗基奥分类器)
    (NearestCentroid(), "最近质心"),
    ## 稀疏朴素贝叶斯分类器
    (ComplementNB(alpha=0.1), "互补朴素贝叶斯"),
):
    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="得分 - 训练时间权衡",
    yscale="log",
    xlabel="测试准确率",
    ylabel="训练时间(秒)",
)
fig, ax2 = plt.subplots(figsize=(10, 8))
ax2.scatter(score, test_time, s=60)
ax2.set(
    title="得分 - 测试时间权衡",
    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 新闻组数据集,并使用词袋方法和 TF-IDF 加权的文档 - 词项稀疏矩阵对特征进行编码。我们对分类器进行了两次训练,一次在包含元数据的文本样本上进行,另一次在去除元数据之后进行。我们使用混淆矩阵分析了测试集上的分类错误,并检查了定义训练模型分类函数的系数。我们还使用八个不同的分类模型对数据集进行了训练和测试,并获得了每个模型的性能结果。本研究的目的是突出针对此类多类别文本分类问题,不同类型分类器在计算量/准确性方面的权衡。