文本特征提取与评估示例管道#

本示例使用的数据集是20 newsgroups 文本数据集,该数据集将自动下载、缓存并重新用于文档分类示例。

在本示例中,我们使用 RandomizedSearchCV 调整特定分类器的超参数。有关其他分类器性能的演示,请参阅使用稀疏特征对文本文档进行分类笔记本。

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

数据加载#

我们从训练集中加载两个类别。您可以通过将类别名称添加到列表中,或在调用数据集加载器 fetch_20newsgroups 时设置 categories=None 以获取全部 20 个类别,来调整类别数量。

from sklearn.datasets import fetch_20newsgroups

categories = [
    "alt.atheism",
    "talk.religion.misc",
]

data_train = fetch_20newsgroups(
    subset="train",
    categories=categories,
    shuffle=True,
    random_state=42,
    remove=("headers", "footers", "quotes"),
)

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

print(f"Loading 20 newsgroups dataset for {len(data_train.target_names)} categories:")
print(data_train.target_names)
print(f"{len(data_train.data)} documents")
Loading 20 newsgroups dataset for 2 categories:
['alt.atheism', 'talk.religion.misc']
857 documents

带超参数调优的管道#

我们定义了一个管道,将文本特征向量化器与一个简单但对文本分类有效的分类器相结合。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import ComplementNB
from sklearn.pipeline import Pipeline

pipeline = Pipeline(
    [
        ("vect", TfidfVectorizer()),
        ("clf", ComplementNB()),
    ]
)
pipeline
Pipeline(steps=[('vect', TfidfVectorizer()), ('clf', ComplementNB())])
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示或信任此笔记本。
在 GitHub 上,HTML 表示无法渲染,请尝试使用 nbviewer.org 加载此页面。


我们定义了一个超参数网格,供 RandomizedSearchCV 探索。如果使用 GridSearchCV,则会探索网格中所有可能的组合,这计算成本可能很高,而 RandomizedSearchCV 的参数 n_iter 控制评估的不同随机组合的数量。请注意,将 n_iter 设置为大于网格中可能组合的数量将导致重复探索过的组合。我们为特征提取 (vect__) 和分类器 (clf__) 寻找最佳参数组合。

import numpy as np

parameter_grid = {
    "vect__max_df": (0.2, 0.4, 0.6, 0.8, 1.0),
    "vect__min_df": (1, 3, 5, 10),
    "vect__ngram_range": ((1, 1), (1, 2)),  # unigrams or bigrams
    "vect__norm": ("l1", "l2"),
    "clf__alpha": np.logspace(-6, 6, 13),
}

在这种情况下,n_iter=40 并非对超参数网格的穷举搜索。实际上,增加参数 n_iter 以获得更具信息量的分析会很有趣。随之,计算时间也会增加。我们可以通过增加参数 n_jobs 来利用参数组合评估的并行化以减少计算时间。

from pprint import pprint

from sklearn.model_selection import RandomizedSearchCV

random_search = RandomizedSearchCV(
    estimator=pipeline,
    param_distributions=parameter_grid,
    n_iter=40,
    random_state=0,
    n_jobs=2,
    verbose=1,
)

print("Performing grid search...")
print("Hyperparameters to be evaluated:")
pprint(parameter_grid)
Performing grid search...
Hyperparameters to be evaluated:
{'clf__alpha': array([1.e-06, 1.e-05, 1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01,
       1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06]),
 'vect__max_df': (0.2, 0.4, 0.6, 0.8, 1.0),
 'vect__min_df': (1, 3, 5, 10),
 'vect__ngram_range': ((1, 1), (1, 2)),
 'vect__norm': ('l1', 'l2')}
from time import time

t0 = time()
random_search.fit(data_train.data, data_train.target)
print(f"Done in {time() - t0:.3f}s")
Fitting 5 folds for each of 40 candidates, totalling 200 fits
Done in 24.499s
print("Best parameters combination found:")
best_parameters = random_search.best_estimator_.get_params()
for param_name in sorted(parameter_grid.keys()):
    print(f"{param_name}: {best_parameters[param_name]}")
Best parameters combination found:
clf__alpha: 0.01
vect__max_df: 0.2
vect__min_df: 1
vect__ngram_range: (1, 1)
vect__norm: l1
test_accuracy = random_search.score(data_test.data, data_test.target)
print(
    "Accuracy of the best parameters using the inner CV of "
    f"the random search: {random_search.best_score_:.3f}"
)
print(f"Accuracy on test set: {test_accuracy:.3f}")
Accuracy of the best parameters using the inner CV of the random search: 0.816
Accuracy on test set: 0.709

前缀 vectclf 是为了避免管道中可能出现的歧义而必需的,但对于结果可视化并非必需。因此,我们定义了一个函数来重命名已调优的超参数以提高可读性。

import pandas as pd


def shorten_param(param_name):
    """Remove components' prefixes in param_name."""
    if "__" in param_name:
        return param_name.rsplit("__", 1)[1]
    return param_name


cv_results = pd.DataFrame(random_search.cv_results_)
cv_results = cv_results.rename(shorten_param, axis=1)

我们可以使用 plotly.express.scatter 来可视化评分时间与平均测试分数(即“CV 分数”)之间的权衡。将光标悬停在给定点上会显示相应的参数。误差条对应于交叉验证不同折叠中计算的一个标准差。

import plotly.express as px

param_names = [shorten_param(name) for name in parameter_grid.keys()]
labels = {
    "mean_score_time": "CV Score time (s)",
    "mean_test_score": "CV score (accuracy)",
}
fig = px.scatter(
    cv_results,
    x="mean_score_time",
    y="mean_test_score",
    error_x="std_score_time",
    error_y="std_test_score",
    hover_data=param_names,
    labels=labels,
)
fig.update_layout(
    title={
        "text": "trade-off between scoring time and mean test score",
        "y": 0.95,
        "x": 0.5,
        "xanchor": "center",
        "yanchor": "top",
    }
)
fig


请注意,图中左上角的模型簇在准确性和评分时间之间具有最佳权衡。在这种情况下,使用二元语法会增加所需的评分时间,而不会显著提高管道的准确性。

注意

有关如何自定义自动调优以最大化分数和最小化评分时间的更多信息,请参阅示例笔记本带交叉验证的网格搜索的自定义重拟合策略

我们还可以使用 plotly.express.parallel_coordinates 进一步可视化平均测试分数作为已调优超参数的函数。这有助于发现两个以上超参数之间的相互作用,并为它们对提高管道性能的重要性提供直观理解。

我们对 alpha 轴应用 math.log10 转换,以展开活跃范围并提高图表的可读性。该轴上的值 \(x\) 应理解为 \(10^x\)

import math

column_results = param_names + ["mean_test_score", "mean_score_time"]

transform_funcs = dict.fromkeys(column_results, lambda x: x)
# Using a logarithmic scale for alpha
transform_funcs["alpha"] = math.log10
# L1 norms are mapped to index 1, and L2 norms to index 2
transform_funcs["norm"] = lambda x: 2 if x == "l2" else 1
# Unigrams are mapped to index 1 and bigrams to index 2
transform_funcs["ngram_range"] = lambda x: x[1]

fig = px.parallel_coordinates(
    cv_results[column_results].apply(transform_funcs),
    color="mean_test_score",
    color_continuous_scale=px.colors.sequential.Viridis_r,
    labels=labels,
)
fig.update_layout(
    title={
        "text": "Parallel coordinates plot of text classifier pipeline",
        "y": 0.99,
        "x": 0.5,
        "xanchor": "center",
        "yanchor": "top",
    }
)
fig


平行坐标图在不同列上显示超参数的值,而性能指标则以颜色编码。通过点击并按住平行坐标图的任意轴,可以选择一个结果范围。然后,您可以滑动(移动)范围选择,并交叉两个选择以查看交集。通过再次点击同一轴可以撤销选择。

特别是在这次超参数搜索中,有趣的是,性能最佳的模型似乎不依赖于正则化 norm,但它们确实依赖于 max_dfmin_df 和正则化强度 alpha 之间的权衡。原因是包含噪声特征(即 max_df 接近 \(1.0\)min_df 接近 \(0\))往往会导致过拟合,因此需要更强的正则化来补偿。拥有较少特征则需要较少的正则化和较短的评分时间。

alpha 介于 \(10^{-6}\)\(10^0\) 之间时,无论超参数 norm 如何,都能获得最佳准确率分数。

脚本总运行时间: (0 分 26.573 秒)

相关示例

使用稀疏特征对文本文档进行分类

使用稀疏特征对文本文档进行分类

比较随机森林和直方图梯度提升模型

比较随机森林和直方图梯度提升模型

带混合类型的列转换器

带混合类型的列转换器

比较随机搜索和网格搜索进行超参数估计

比较随机搜索和网格搜索进行超参数估计

由 Sphinx-Gallery 生成的画廊