像逻辑回归和支持向量机这样的机器学习算法,是为二分类(二元)分类问题设计的。
因此,这些算法要么必须修改以适应多类(两个以上)分类问题,要么根本不使用。纠错输出码方法是一种技术,它允许将多类分类问题重构为多个二元分类问题,从而可以直接使用原生的二元分类模型。
与提供类似解决方案的“一对多”和“一对一”方法不同,后者通过将多类分类问题划分为固定数量的二元分类问题来解决,纠错输出码技术允许将每个类编码为任意数量的二元分类问题。当使用过完备表示时,它允许额外的模型充当“纠错”预测,从而可能带来更好的预测性能。
在本教程中,您将了解如何使用纠错输出码进行分类。
完成本教程后,您将了解:
- 纠错输出码是一种使用二元分类模型进行多类分类预测任务的技术。
- 如何拟合、评估和使用纠错输出码分类模型进行预测。
- 如何调整和评估纠错输出码使用的每类位数超参数的不同值。
开始您的项目,阅读我的新书《Python中的集成学习算法》,其中包含分步教程和所有示例的Python源代码文件。
让我们开始吧。

用于机器学习的纠错输出码 (ECOC)
照片由Fred Hsu提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 纠错输出码
- 评估和使用ECOC分类器
- 调整每类位数
纠错输出码
分类任务是指给定输入变量,标签具有预测性的任务。
二元分类任务是指目标包含两个值的分类问题,而多类分类问题是指目标具有两个以上类标签的问题。
许多机器学习模型都是为二元分类开发的,尽管它们可能需要修改才能处理多类分类问题。例如,逻辑回归和支持向量机是专门为二元分类设计的。
许多机器学习算法,例如SVM,最初仅用于解决二元分类任务。
— 第 133 页,《使用集成方法的模式分类》,2010年。
与其限制算法的选择或修改算法以适应多类问题,不如将多类分类问题重构为多个二元分类问题。实现这一目标的两种常用方法包括“一对多”(OvR)和“一对一”(OvO)技术。
- 一对多:将多类问题拆分为每个类一个二元问题。
- 一对一:将多类问题拆分为每对类一个二元问题。
拆分为子任务后,可以在每个任务上拟合二元分类模型,然后将响应最大的模型作为预测结果。
一对多和一对一都可以被认为是集成学习模型的一种,因为它们为预测建模任务拟合了多个独立的模型,并协同工作以做出预测。在这两种情况下,“集成成员”的预测都是简单的“赢者通吃”方法。
…将多类任务转换为二元分类任务的集成,然后组合这些任务的结果。
— 第 134 页,《使用集成方法的模式分类》,2010年。
有关一对多和一对一模型的更多信息,请参阅教程
一种相关的方法是准备一个二元编码(例如位串)来表示问题中的每个类。字符串中的每个位都可以由单独的二元分类问题来预测。可以为给定的多类分类问题任意选择长度编码。
为了清楚起见,每个模型都接收完整的输入模式,并且只预测输出字符串中的一个位置。在训练过程中,每个模型都可以被训练以产生二元分类任务的正确0或1输出。然后,可以通过使用每个模型对输入进行预测来生成二元字符串,然后将二元字符串与每个类的已知编码进行比较,从而对新示例进行预测。选择与预测距离最小的类编码作为输出。
每个类都被分配一个长度为l的码字。通常,码字的大小比唯一表示每个类所需的位数要多。
— 第 138 页,《使用集成方法的模式分类》,2010年。
这是一个有趣的方法,它允许类表示比必需的(可能过完备)更复杂,而与独热编码相比,它在问题的表示和建模中引入了冗余。这是故意的,因为表示中的附加位就像纠错码一样,可以修复、纠正或改进预测。
… 想法是,冗余的“纠错”位允许一些不准确性,并可以提高性能。
— 第 606 页,《统计学习要素》,2016年。
这使得该技术得名:纠错输出码,简称ECOC。
纠错输出码(ECOC)是一种简单而强大的方法,它通过组合二元分类器来处理多类问题。
— 第 90 页,《集成方法》,2012年。
可以采取措施确保每个编码的类都具有非常不同的二进制字符串编码。已经探索了一系列不同的编码方案以及用于构建编码以确保它们在编码空间中足够远的方法。有趣的是,随机编码已被发现效果差不多。
… 分析了ECOC方法,并表明随机码分配的效果与最优构造的纠错码一样好
— 第 606 页,《统计学习要素》,2016年。
有关各种不同编码方案和将预测字符串映射到编码类的详细回顾,我推荐《使用集成方法的模式分类》一书的第 6 章“纠错输出码”。
想开始学习集成学习吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
评估和使用ECOC分类器
scikit-learn库通过OutputCodeClassifier类提供了ECOC的实现。
该类将用于拟合每个二元分类器的模型作为参数,并且可以使用任何机器学习模型。在这种情况下,我们将使用逻辑回归模型,该模型用于二元分类。
该类还提供了“code_size”参数,该参数指定类的编码大小,作为类数量的倍数,例如每个类标签需要编码的位数。
例如,如果我们想要一个长度为6位的编码,并且我们有三个类,那么我们可以将编码大小指定为2
- encoding_length = code_size * num_classes
- encoding_length = 2 * 3
- encoding_length = 6
下面的示例演示了如何定义一个OutputCodeClassifier的示例,该示例包含每类2位,并为编码中的每一位使用LogisticRegression模型。
1 2 3 4 5 |
... # 定义二元分类模型 model = LogisticRegression() # 定义ECOC模型 ecoc = OutputCodeClassifier(model, code_size=2, random_state=1) |
尽管有许多复杂的方法来构建每个类的编码,但OutputCodeClassifier类目前(撰写本文时)为每个类选择一个随机位字符串编码。
我们可以探索在合成多类分类问题上使用OutputCodeClassifier。
我们可以使用make_classification()函数来定义一个包含1000个样本、20个输入特征和三个类别的多类分类问题。
下面的示例演示了如何创建数据集并总结数据集中行数、列数和类别数。
1 2 3 4 5 6 7 8 9 |
# 多类分类数据集 from collections import Counter from sklearn.datasets import make_classification # 定义数据集 X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=1, n_classes=3) # 汇总数据集 print(X.shape, y.shape) # 总结类别数 print(Counter(y)) |
运行示例创建数据集并报告行数和列数,确认数据集已按预期创建。
然后报告每个类别的示例数量,显示每个配置类别都有接近相等数量的案例。
1 2 |
(1000, 20) (1000,) Counter({2: 335, 1: 333, 0: 332}) |
接下来,我们可以在数据集上评估纠错输出码模型。
我们将使用逻辑回归,每类2位,如上所述。然后使用重复分层k折交叉验证对模型进行评估,重复三次,折数为10。我们将通过所有重复和折叠的分类准确率的平均值和标准差来总结模型的性能。
1 2 3 4 5 6 7 |
... # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型并收集分数 n_scores = cross_val_score(ecoc, X, y, scoring='accuracy', cv=cv, n_jobs=-1) # 总结性能 print('Accuracy: %.3f (%.3f)' % (mean(n_scores), std(n_scores))) |
将这些结合起来,完整的示例列在下面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 评估用于多类分类的纠错输出码 from numpy import mean from numpy import std from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_score from sklearn.model_selection import RepeatedStratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.multiclass import OutputCodeClassifier # 定义数据集 X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=1, n_classes=3) # 定义二元分类模型 model = LogisticRegression() # 定义ECOC模型 ecoc = OutputCodeClassifier(model, code_size=2, random_state=1) # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型并收集分数 n_scores = cross_val_score(ecoc, X, y, scoring='accuracy', cv=cv, n_jobs=-1) # 总结性能 print('Accuracy: %.3f (%.3f)' % (mean(n_scores), std(n_scores))) |
运行示例定义模型并在我们定义的测试程序上使用合成多类分类数据集对其进行评估。
注意:由于算法的随机性或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行示例几次并比较平均结果。
在这种情况下,我们可以看到该模型实现了约76.6%的平均分类准确率。
1 |
Accuracy: 0.766 (0.037) |
我们可以选择将其作为最终模型。
这要求我们在所有可用数据上拟合模型,并使用它来预测新数据。
下面的示例提供了如何拟合和使用纠错输出模型作为最终模型的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 使用纠错输出码模型作为最终模型并进行预测 from sklearn.datasets import make_classification from sklearn.linear_model import LogisticRegression from sklearn.multiclass import OutputCodeClassifier # 定义数据集 X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=1, n_classes=3) # 定义二元分类模型 model = LogisticRegression() # 定义ECOC模型 ecoc = OutputCodeClassifier(model, code_size=2, random_state=1) # 在整个数据集上拟合模型 ecoc.fit(X, y) # 进行单次预测 row = [[0.04339387, 2.75542632, -3.79522705, -0.71310994, -3.08888853, -1.2963487, -1.92065166, -3.15609907, 1.37532356, 3.61293237, 1.00353523, -3.77126962, 2.26638828, -10.22368666, -0.35137382, 1.84443763, 3.7040748, 2.50964286, 2.18839505, -2.31211692]] yhat = ecoc.predict(row) print('Predicted Class: %d' % yhat[0]) |
运行示例将ECOC模型拟合到整个数据集,并使用该模型预测单个数据行的类别标签。
在这种情况下,我们可以看到模型预测的类别标签为0。
1 |
预测类别:0 |
现在我们已经熟悉了如何拟合和使用ECOC模型,让我们仔细看看如何配置它。
调整每类位数
ECOC模型的一个关键超参数是类别标签的编码。
这包括诸如
- 表示的选择(位、实数等)
- 每个类标签的编码(随机等)
- 表示的长度(位数等)
- 预测如何映射到类别(距离等)
scikit-learn实现OutputCodeClassifier目前并未提供对这些元素的过多控制。
它确实提供了控制的元素是用于编码每个类标签的位数。
在本节中,我们可以对每类位数进行手动网格搜索,并比较结果。这提供了一个模板,您可以根据自己的项目进行调整和使用。
首先,我们可以定义一个函数来创建并返回数据集。
1 2 3 4 |
# 获取数据集 定义 获取_数据集(): X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=1, n_classes=3) return X, y |
然后,我们可以定义一个函数来创建要评估的模型集合。
每个模型都将是OutputCodeClassifier的一个示例,为每个二元分类问题使用LogisticRegression。我们将为每个模型的code_size配置不同的值,范围从1到20。
1 2 3 4 5 6 7 8 9 |
# 获取要评估的模型列表 定义 获取_模型(): models = dict() for i in range(1,21): # 创建模型 model = LogisticRegression() # 创建纠错输出码分类器 models[str(i)] = OutputCodeClassifier(model, code_size=i, random_state=1) 返回 models |
我们可以使用相关的k折交叉验证来评估每个模型,就像我们在上一节中所做的那样,以提供分类准确率分数的样本。
1 2 3 4 5 |
# 使用交叉验证评估给定模型 def evaluate_model(model): cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1) return scores |
我们可以报告每个配置的分数平均值和标准差,并将分布绘制成并排的箱形图和须形图,以直观地比较结果。
1 2 3 4 5 6 7 8 9 10 11 |
... # 评估模型并存储结果 results, names = list(), list() for name, model in models.items(): scores = evaluate_model(model) results.append(scores) names.append(name) print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores))) # 绘制模型性能以供比较 pyplot.boxplot(results, labels=names, showmeans=True) pyplot.show() |
将所有内容汇总起来,下面是比较ECOC分类与每类位数网格的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# 比较纠错输出码分类的每类位数 from numpy import mean from numpy import std from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_score from sklearn.model_selection import RepeatedStratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.multiclass import OutputCodeClassifier from matplotlib import pyplot # 获取数据集 定义 获取_数据集(): X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, random_state=1, n_classes=3) 返回 X, y # 获取要评估的模型列表 定义 获取_模型(): models = dict() for i in range(1,21): # 创建模型 model = LogisticRegression() # 创建纠错输出码分类器 models[str(i)] = OutputCodeClassifier(model, code_size=i, random_state=1) 返回 模型 # 使用交叉验证评估给定模型 def evaluate_model(model): cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1) 返回 分数 # 定义数据集 X, y = get_dataset() # 获取要评估的模型 模型 = 获取_模型() # 评估模型并存储结果 results, names = list(), list() for name, model in models.items(): scores = evaluate_model(model) results.append(scores) names.append(name) print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores))) # 绘制模型性能以供比较 pyplot.boxplot(results, labels=names, showmeans=True) pyplot.show() |
运行示例首先评估每个模型配置,并报告准确率分数的平均值和标准差。
注意:由于算法的随机性或评估程序的随机性,或者数值精度的差异,您的结果可能会有所不同。考虑运行示例几次并比较平均结果。
在这种情况下,我们可以看到每类5或6位可能带来最佳性能,报告的平均准确率分别约为78.2%和78.0%。我们还看到9、13、17和20位数的表现也不错,每类17位数可能带来约78.5%的最佳结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
>1 0.545 (0.032) >2 0.766 (0.037) >3 0.776 (0.036) >4 0.769 (0.035) >5 0.782 (0.037) >6 0.780 (0.037) >7 0.776 (0.039) >8 0.775 (0.036) >9 0.782 (0.038) >10 0.779 (0.036) >11 0.770 (0.033) >12 0.777 (0.037) >13 0.781 (0.037) >14 0.779 (0.039) >15 0.771 (0.033) >16 0.769 (0.035) >17 0.785 (0.034) >18 0.776 (0.038) >19 0.776 (0.034) >20 0.780 (0.038) |
创建了一个图,显示了每个模型配置的准确率分数的箱须图。
我们可以看到,除了1位之外,每类位数在数据的分布和平均准确率分数方面都提供了相似的结果,这些分数聚集在77%左右。这表明该方法在不同配置下相当稳定。

每类位数与ECOC分类准确率分布的箱须图
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
相关教程
论文
- 通过纠错输出码解决多类学习问题, 1995.
书籍
- 集成方法, 2012.
- 使用集成方法进行模式分类, 2010.
- 统计学习基础, 2016.
API
总结
在本教程中,您了解了如何使用纠错输出码进行分类。
具体来说,你学到了:
- 纠错输出码是一种使用二元分类模型进行多类分类预测任务的技术。
- 如何拟合、评估和使用纠错输出码分类模型进行预测。
- 如何调整和评估纠错输出码使用的每类位数超参数的不同值。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
尊敬的Jason博士,
两个问题,一个关于误差的含义,另一个关于纠错码。
(1) 纠错在哪里执行?
(2) 教程中的ECOC概念是否与纠错码有关?也就是说,信号由0和1组成,包含数据和纠错信息。信号会受到噪声影响,接收者利用纠错信息来解码正确的数据。
也就是说,ECOC是否与纠错系统相同,其中数字信号受到噪声影响,并且利用数据流中的纠错信息来解码最可能的数据?
谢谢你,
悉尼的Anthony
过完备表示就像纠错码一样。像纠错位。也许可以重新阅读第1节“纠错输出码”。
不,它与纠错码不是相同的,但它受到该技术启发。
尊敬的Jason博士,
首先,我想感谢您提供这篇文章。它真的帮助了我。
我有一个问题
如果我想调整“code_size”以外的其他参数,我该怎么办?
你好Neda…你可能会发现超参数优化概念有益
https://machinelearning.org.cn/combined-algorithm-selection-and-hyperparameter-optimization/