1.3 分类

其实在生活中,我们不经意间就能完成一个分类任务,能轻易判断一个人是男的还是女的,一朵花是红色的还是黄色的……这种将一个事物归入特定类别的过程叫作分类。现在越来越多的智能手机开始支持垃圾短信识别、人脸识别等功能,这些都是机器学习完成分类任务的实例。

分类是机器学习中一种重要的方法,该方法能够将数据库中的数据记录映射到某个给定的类别,从而用于数据预测。分类问题是机器学习的基础,很多问题都可以转化成分类问题来解决,比如本书将会介绍的图像分割问题,就可以转化成像素级别的分类问题来求解。

分类器是机器学习中对样本进行分类的方法的统称。sklearn中提供了许多定义好的分类器,常用的几种模型及其优缺点如下。

  • K 近邻分类:该方法的思路是,如果数据库中与某个样本最相似的K 个样本大多数属于某一类别,那么这个样本也属于这个类别。
    • 缺点K 值是一个超参数,需要人为指定,并且算法复杂度较高。
    • 优点:这是一个无须训练的无参数模型。
  • 逻辑回归:一种广义的线性回归分析模型,线性回归是找到一条与数据最接近的线(或一个超平面),而逻辑回归是找到能够将不同类别数据分割开的线(或超平面)。
    • 缺点:容易受到噪声影响。
    • 优点:模型简单,且可以使用梯度下降实现增量式训练。
  • 朴素贝叶斯:朴素贝叶斯算法是基于贝叶斯理论和特征条件独立性假设,利用概率统计知识对样本数据集进行分类的方法。
    • 缺点:使用了独立性假设,对于关联性较强的数据效果比较差。
    • 优点:简化了概率计算,节约了时间和内存。
  • SVM(支持向量机):支持向量机也是一种广义线性分类器,模型的思路是寻找最大几何间隔的分类界面。
    • 缺点:计算速度较慢。
    • 优点:受噪声影响较小。
  • 决策树:一种通过对数据进行归纳总结,生成分类规则的算法。
    • 缺点:训练比较耗时,容易过拟合。
    • 优点:可解释性好,能适应各种形式的训练数据。
  • 集成型的分类器:如随机森林、GBDT、Adaboost等。集成分类器的思路是通过多个小型弱分类器组合成一个强分类器。
    • 缺点:训练速度较慢。
    • 优点:模型精度较高,且不容易过拟合。

有这么多分类器,该如何选择呢?可以参考Fern在2014年发表的论文“Do we Need Hundreds of Classifiers to Solve Real World Classification Problems”,该论文使用了121种公开数据集对17个大类(朴素贝叶斯、决策树、神经网络、SVM、K 近邻分类、基于boosting/bagging/stacking的集成算法、逻辑回归等)中的179种分类模型进行测试,结果很具有参考意义。

为了让读者对sklearn中的分类模型建模有一个直观的认识,下面展示一下使用sklearn进行鸢尾花数据集分类的流程。

1.3.1 加载数据与模型

首先从sklearn.datasets中加载数据,然后从sklearn.linear_model中加载逻辑回归模型。加载并整理鸢尾花数据集和逻辑回归模型的代码如下:

>>> from sklearn.datasets import load_iris
>>> # 导入逻辑回归模型
>>> from sklearn.linear_model import LogisticRegression
>>> import matplotlib.pyplot as plt
>>> data = load_iris()
>>> data.keys()
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
>>> x = data['data']
>>> y = data['target']

上述代码从sklearn中的linear_model模块中导入了LogisticRegression类,并利用load_iris函数加载了鸢尾花数据集,加载数据集之后将数据集分为了特征x和标签y两个部分。

1.3.2 建立分类模型

这里选择的分类器是逻辑回归模型。逻辑回归中的所有参数都有预设的默认值,在对精度要求不高的情况下,直接使用默认参数即可。

下面是逻辑回归中的可选参数:

LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                       intercept_scaling=1, l1_ratio=None, max_iter=100,
                       multi_class='warn', n_jobs=None, penalty='l2',
                       random_state=None, solver='warn', tol=0.0001, verbose=0,
                       warm_start=False)

其中常用的参数有如下几个。

  • penalty:正则化参数,用于给损失函数(后面PyTorch部分会介绍到)添加惩罚项,避免模型过拟合。可以选择的有L1正则化和L2正则化,默认是L2。
  • solver:根据损失函数对模型参数进行调整的算法,有liblinearlbfgsnewton-cgsag等4种选择。
    • liblinear:使用坐标轴下降法来迭代优化损失函数,因为L1正则项的损失函数不是连续可导的,所以只能使用这种方法。L2正则项的函数连续可导,所以4种方法都可以选择。
    • lbfgsnewton-cg都属于牛顿迭代法,利用损失函数二阶导数矩阵(即海森矩阵)来迭代优化损失函数。
    • sag:随机平均梯度下降,是梯度下降法的变种,和普通梯度下降法的区别是每次迭代仅用一部分样本来计算梯度。
  • multi_class:有ovrmultinomial两种方式,ovr速度较快,精度略差,multinomial会进行多次分类,速度较慢,精度较高。
  • class_weight:用于应对样本类别不平衡的情况,可以设置为balanced,模型会自动根据对应类别的样本数量计算应该分配的权重。部分业务场景会特别看重模型对某个特定类别的识别能力,这时可以通过class_weight来调节。
  • sample_weight:与class_weight类似,可以在样本不平衡的情况下与class_weight协同作用。

使用默认参数创建逻辑回归模型只需要如下代码:

>>> clf = LogisticRegression()

1.3.3 模型的训练及预测

sklearn中对模型进行了统一的接口封装,几乎所有的模型训练都只需要调用fit方法,即使对模型内部原理一无所知,也可以轻松使用。

与训练模型类似,只需要调用predict方法即可得到模型根据输入x得到的预测结果。模型训练和预测的代码如下:

>>> clf.fit(x,y)
>>> y_pred = clf.predict(x)

1.3.4 模型评价

模型训练和预测都完成之后,就需要对模型进行评价了,在模型评价指标中最常用也最容易计算的就是准确率accuracy

>>> accuracy = sum(y_pred == y) / len(y)
>>> accuracy
0.96

但是,准确率在样本不平衡的情况下不能真实地反映模型的效果,比如样本中有1个10和90个0,那么模型只需要将所有样本都预测成0就可以获得90%的准确率了,这显然是不合理的。所以在分类模型中,通常会综合考虑多个指标,sklearn中提供了classification_report函数来评价分类模型的效果,代码如下:

>>> from sklearn.metrics import classification_report
>>> classification_report(
...     y, y_pred, target_names=["setosa", "versicolor", "virginica"]
... )

得到的结果如下:

               precision     recall  f1-score   support
      setosa       1.00      1.00      1.00        50
  versicolor       0.98      0.90      0.94        50
   virginica       0.91      0.98      0.94        50

    accuracy                           0.96       150
   macro avg       0.96      0.96      0.96       150
weighted avg       0.96      0.96      0.96       150

其中比较常用的指标有3个:precision(精确率)、recall(召回率)和f1-score(平衡F分数)。为了让大家更好地理解这3个指标,我们先介绍4种分类情况。

  • TP:正例被预测为正例。
  • FP:负例被预测为正例。
  • FN:正例被预测为负例。
  • TN:负例被预测为负例。

以鸢尾花setosa为例,TP表示这朵花本来是setosa,被预测成了setosa;FP表示这朵花本来不是setosa,被预测成了setosa;FN表示这朵花本来是setosa,被预测成了别的花;TN表示这朵花本来不是setosa,预测结果也不是setosa。

precisionrecall的计算公式如下:

借助以上概念,我们也可以将准确率表示出来:

f1-scoreprecisionrecall的调和平均值,公式为:

可以综合反映两个指标的好坏。

当分类模型中的类别数量不太多时,可以通过混淆矩阵来更加直观地查看分类情况,得到混淆矩阵之后,可以利用matplotlib将混淆矩阵以图片的形式画出,计算并绘制混淆矩阵的代码如下:

>>> from sklearn.metrics import confusion_matrix
>>> c = confusion_matrix(y,y_pred)
>>> # 横纵坐标轴刻度
>>> xlocations = [0,1,2]
>>> ylocations = xlocations
>>> labels = data['target_names']
>>> # 使用文字替换刻度
>>> plt.xticks(xlocations,labels)
([<matplotlib.axis.XTick object at 0x7f47e01fe240>, <matplotlib.axis.XTick object at 0x7f47e01f5b38>, <matplotlib.axis.XTick object at 0x7f47e01f5860>], <a list of 3 Text xticklabel objects>)
>>> plt.yticks(ylocations,labels)
([<matplotlib.axis.YTick object at 0x7f47e0203080>, <matplotlib.axis.YTick object at 0x7f47e01fe8d0>, <matplotlib.axis.YTick object at 0x7f47e01f5898>], <a list of 3 Text yticklabel objects>)
>>> # 设置坐标轴名称
>>> plt.ylabel("True label")
Text(0, 0.5, 'True label')
>>> plt.xlabel("Predict label")
Text(0.5, 0, 'Predict label')
>>> plt.imshow(c)
<matplotlib.image.AxesImage object at 0x7f47e01f54e0>
>>> plt.show()

混淆矩阵展示如图1-7所示,横轴是预测标签,纵轴是真实标签。其中颜色最浅的部分是出现频次最多的情况,颜色最深的部分是出现频次最低的情况。我们可以看到,setosa品种分类情况最好,几乎所有的setosa品种的鸢尾花都被正确分类了。versicolor品种的鸢尾花分类效果最差,有不少versicolor鸢尾花被分类成了virginica鸢尾花。

图 1-7 混淆矩阵