Tips | 如何用二元分类器解决一个多分类任务?

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.
二元分问题会是我们生活中比较常见的一类问题,比如邮件可以分为垃圾邮件和非垃圾邮件、一个人患病或者不患病,但除此之外也会遇到一些多元分类问题,比如天气可以分为晴、阴、雨、雪等等。
我们通过算法构建的分类器就以分为二元分类器和多元分类器,前者可以区分两个类别标签,后者则可以区分两个以上的类别标签。对于算法而言,像SVM、逻辑回归等是严格的二元分类算法,而像朴素贝叶斯、随机森林这类算法则可以直接处理多元分类问题。但利用二元分类器处理多分类问题是可行的,下面将以逻辑回归结合鸢尾花数据集为例介绍。

OvA、OvO策略

利用二元分类器解决多分类问题可以分为两种策略:

one-versus-all(OvA)策略,也可以称one-versus-rest(OvR),简称一对多。
one-versus-one(OvO)策略,简称一对一,应该有人用OvO当过文字表情吧。
用过鸢尾花数据集的伙伴应该知道这份数据集的类别标签共有三类,分别是山鸢尾(setosa)、变色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),因为有三个类别嘛,所以就构造三个二元分类器,假设为山-分类器、变-分类器和维-分类器。训练时将某个类别的样本归为一类,其余类别的样本归为另一类,这样对于某个未知类别的样本,三个分类器都会有一个决策分数(概率),然后取最高决策分数的一个类别作为该样本的类别,这种方式就属于一对多法。
而一对一的做法是构建多个任意两类样本间的二元分类器,原理类似于组队,比如上述三个标签变量可以组成山和变、山和维、变和维,如果类别为n的话,需要的分类器个数为$\frac{n(n-1)}{2}$。最后也是取决策分数最高的一个类别作为某个未知类别的样本最终分类。
从上面介绍中也很容易可得出两者的优缺点:

OvA缺点:因为是一个类别对多个类别(1:N的关系),所以在训练时可能会更偏向多类别的一方。
OvA优点:假设有n个类别,只需要构建n个分类器。
OvO缺点:在标签类别很多的情况下,需要构建很多个二元分类器,不论构建还是训练的过程都比较麻烦
OvO优点:每个分类器只需要在包含两个类别的部分数据上训练,无需在整个数据集上。
这两种策略的基本思想都是通过构建多个二元分类器来解决多分类问题。大多数二元分类算法比较适用OvA策略,当然并不是全部,并且还需要根据数据集的特点而定。下面利用逻辑回归在鸢尾花数据集上建模,因为数据集比较简单,我们又是只讲这种方法,所以省略掉了分析之类的操作。

手推实现OvA策略

我个人会习惯将数据集转化为容易观察的DataFrame格式:

import pandas as pd
from sklearn.datasets import load_iris
feature_names = load_iris().feature_names
dataset_data = pd.DataFrame(load_iris().data,columns=feature_names)
dataset_target = pd.DataFrame(load_iris().target,columns=['target'])
data = pd.concat([dataset_data,dataset_target],axis = 1)

数据集共有150个样本、四个特征和一个类别标签:
我们采用一对多(OvA策略)结合逻辑回归解决这个多分类问题,先介绍一下手推的方式,建模过程与二分类是一样的,只不过我们需要注意一下OvA策略的思想。
首先需要划分数据集,取七份作为训练集,剩余三份为测试集,基础部分就不贴代码啦,文末会给出完整代码获取方式。OvA策略是有多少个类别就构建多少个分类器,所以需要知道标签变量所有类别,可以利用unique索引,然后利用字典格式存储所有的分类器。

#获取标签变量的类别
unique_targets = data['target'].unique()
'''
array([0, 1, 2])
'''
# 采用OvA策略,三个类别对应三个模型,用字典格式存储
models = {}

每一个分类器都会把一个类别归为一类,剩余的类别归为另一类,所以这里暂定每次循环和target相同的为一类,标签设为1,剩余的两类标签设为0。为了代码的简洁度,这里利用了管道流将每个分类器和标准化处理封装起来。

y_train_copy = y_train.copy()
for target in unique_targets:
    #管道流封装
    models[target] = make_pipeline(StandardScaler(),LogisticRegression())
    y_train_list = y_train_copy.tolist()
    # 每次都要修改训练集的标签,将当前类别的标签设为1,其它类别设为0
    for i in range(len(y_train_list)):
        if y_train_list[i] == target:
            y_train_list[i] = 1
        else:
            y_train_list[i] = 0
    y_train = np.array(y_train_list)
    
    models[target].fit(X_train,y_train)

创建相应的分类器之后,下面需要做的就是在测试集上应用,三个分类器最终会得到三个标签的预测概率。

test_probs = pd.DataFrame(columns=unique_targets)
for target in unique_targets:
    #[:,1]返回的是属于1的概率,[:,0]是属于0的概率
    test_probs[target] = models[target].predict_proba(X_test)[:,1]
print(test_probs)

可以得到的关于概率的DataFrame如下:
在pandas中有一个idxmax()方法可以索引出一个样本中值最大的列索引,在这里就是一个样本最终被划分的类别。

predicted_target = test_probs.idxmax(axis=1)
'''
0     0
1     0
2     1
3     0
4     2
5     1
.......
模型错误率为:6.67%
'''

最后可以通过和原标签比对计算出模型的准确率,至此就是如何利用二元分类器实现多元分类问题的手推方法。

sklearn调用

在sklearn中也有可以实现OvA和OVO策略的类,和手推方法相比会更加简单便捷,分别为OneVsOneClassifier或OneVsRestClassifier。

from sklearn.multiclass import OneVsOneClassifier,OneVsRestClassifier
OvO = OneVsOneClassifier(make_pipeline(StandardScaler(),LogisticRegression()))
OvO.fit(X_train,y_train)
ovo_predict = OvO.predict(X_test)

对于这类库的调用应该都比较熟悉,不在过多介绍,利用相应的方法可以查看类标签和分类器的个数,3个类别比较巧,OvA和OvO两个策略所需构建二元分类器的个数都为3个。

print('类别标签有:%s' % OvO.classes_)
print('分类器个数:%d' % len(OvO.estimators_))
'''
类别标签有:[0 1 2]
分类器个数:3
'''

其实在逻辑回归的multi_class参数中,就有ovr(OvA)这个策略可供选择,但是没有OvO策略。毕竟手推的方式会更容易帮助理解一个策略的思想,理解之后我们再调用类或者调参时才会知道究竟做的是什幺操作,综上就是关于如何利用二分类算法解决多元分类问题的概述。
参考链接:


[1]. https://blog.csdn.net/zm71498…