在机器学习领域,多标签分类问题是一种常见的任务,其中每个样本可以同时属于多个类别。解决这类问题的一个简单方法是为每个标签独立地训练一个二分类器。在预测阶段,这些二分类器的集成被用来进行多任务预测。然而,这种方法并不能建模不同任务之间的关系。
为了解决这个问题,可以使用一种更高级的策略,即分类器链(ClassifierChain)。这种元估计器(即一个内部估计器)使用二分类器的集成,其中链中的一个分类器的预测被用作训练下一个分类器的新标签的特征。因此,这些额外的特征允许每个链利用标签之间的相关性。
在酵母数据集上进行实验时,发现分类器链的Jaccard相似度分数往往高于独立基础模型的分数。酵母数据集包含2,417个数据点,每个数据点有103个特征和14个可能的标签。每个数据点至少有一个标签。首先,为14个标签中的每一个训练一个逻辑回归分类器作为基线。然后,在保留的测试集上进行预测,并计算每个样本的Jaccard相似度。
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
# 从 https://www.openml.org/d/40597 加载多标签数据集
X, Y = fetch_openml("yeast", version=4, return_X_y=True)
Y = Y == "TRUE"
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)
接下来,拟合了逻辑回归模型,该模型被OneVsRestClassifier包装,并使用多个分类器链的集成。由于逻辑回归默认不能处理具有多个目标的数据,需要使用OneVsRestClassifier。在拟合模型后,计算了Jaccard相似度。
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import jaccard_score
from sklearn.multiclass import OneVsRestClassifier
base_lr = LogisticRegression()
ovr = OneVsRestClassifier(base_lr)
ovr.fit(X_train, Y_train)
Y_pred_ovr = ovr.predict(X_test)
ovr_jaccard_score = jaccard_score(Y_test, Y_pred_ovr, average="samples")
由于链中的模型是随机排列的,链的性能在链之间有显著的变化。可以假设存在一个最佳的类别链的顺序,这将产生最佳性能。然而,事先并不知道这个顺序。相反,可以通过平均链的二进制预测来构建一个分类器链的投票集成,并对阈值0.5应用。集成的Jaccard相似度分数大于独立模型的分数,并且往往超过集成中每个链的分数(尽管这并不保证在随机排序的链中)。
from sklearn.multioutput import ClassifierChain
chains = [ClassifierChain(base_lr, order="random", random_state=i) for i in range(10)]
for chain in chains:
chain.fit(X_train, Y_train)
Y_pred_chains = np.array([chain.predict_proba(X_test) for chain in chains])
chain_jaccard_scores = [jaccard_score(Y_test, Y_pred_chain >= 0.5, average="samples") for Y_pred_chain in Y_pred_chains]
Y_pred_ensemble = Y_pred_chains.mean(axis=0)
ensemble_jaccard_score = jaccard_score(Y_test, Y_pred_ensemble >= 0.5, average="samples")
最后,绘制了独立模型、每个链和集成的Jaccard相似度分数(注意这个图表的垂直轴不从0开始)。从这个图表中,可以得出三个主要的结论:被OneVsRestClassifier包装的独立模型的性能比分类器链的集成和一些单独的链要差。这是因为逻辑回归没有建模标签之间的关系。ClassifierChain利用了标签之间的相关性,但由于标签排序的随机性,它可能会产生比独立模型更差的结果。链的集成表现更好,因为它不仅捕捉了标签之间的关系,而且对它们的正确顺序也没有强烈的假设。
model_scores = [ovr_jaccard_score] + chain_jaccard_scores + [ensemble_jaccard_score]
model_names = ("Independent", "Chain 1", "Chain 2", "Chain 3", "Chain 4", "Chain 5", "Chain 6", "Chain 7", "Chain 8", "Chain 9", "Chain 10", "Ensemble",)
x_pos = np.arange(len(model_names))
fig, ax = plt.subplots(figsize=(7, 4))
ax.grid(True)
ax.set_title("Classifier Chain Ensemble Performance Comparison")
ax.set_xticks(x_pos)
ax.set_xticklabels(model_names, rotation="vertical")
ax.set_ylabel("Jaccard Similarity Score")
ax.set_ylim([min(model_scores) * 0.9, max(model_scores) * 1.1])
colors = ["r"] + ["b"] * len(chain_jaccard_scores) + ["g"]
ax.bar(x_pos, model_scores, alpha=0.5, color=colors)
plt.tight_layout()
plt.show()