Note
Go to the end to download the full example code. or to run this example in your browser via Binder
文本特征提取和评估的示例管道#
本示例中使用的数据集是 The 20 newsgroups text dataset ,它将自动下载、缓存并在文档分类示例中重复使用。
在本示例中,我们使用 RandomizedSearchCV
调整特定分类器的超参数。有关其他分类器性能的演示,请参见
使用稀疏特征对文本文档进行分类 笔记本。
# 作者:scikit-learn 开发者
# SPDX 许可证标识符: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
我们定义了一个超参数网格,由 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
会使分析更具信息量。结果是计算时间增加。我们可以通过增加使用的 CPU 数量(通过参数 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')}
Fitting 5 folds for each of 40 candidates, totalling 200 fits
Done in 15.868s
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
前缀 vect
和 clf
是为了避免在管道中可能出现的歧义,但在可视化结果时并不是必需的。因此,我们定义了一个函数来重命名调整后的超参数,以提高可读性。
import pandas as pd
def shorten_param(param_name):
"""移除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
请注意,图表左上角的模型集群在准确性和评分时间之间达到了最佳平衡。在这种情况下,使用二元组(bigrams)会增加所需的评分时间,但并未显著提高管道的准确性。 .. NOTE:: 有关如何自定义自动调优以最大化得分和最小化评分时间的更多信息,请参阅示例笔记本 网格搜索与交叉验证的自定义重拟合策略 。
我们还可以使用 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)
# 使用对数刻度表示α
transform_funcs["alpha"] = math.log10
# L1 范数映射到索引 1,L2 范数映射到索引 2
transform_funcs["norm"] = lambda x: 2 if x == "l2" else 1
# 单字映射到索引1,双字映射到索引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_df
、 min_df
和正则化强度 alpha
之间的权衡。原因是包含噪声特征(即 max_df
接近 \(1.0\) 或 min_df
接近 \(0\) )往往会导致过拟合,因此需要更强的正则化来补偿。较少的特征需要较少的正则化和较少的评分时间。
当 alpha
介于 \(10^{-6}\) 和 \(10^0\) 之间时,无论超参数 norm
如何,都会获得最佳的准确率分数。
Total running time of the script: (0 minutes 17.203 seconds)
Related examples