第13章
模式识别系统的评价
13.1 引言
模式识别和机器学习是一门方法性很强的学科。在从数据中学习和发现规律这一总目标下,针对各种不同的数据和背后不同的规律,人们发展了很多不同的方法。这些方法表面上看好像是归于不同学派,实际上是采用了对数据及其背后规律不同的认识和假设,以及采用了不同的数学工具。在这个意义上,不同类型的方法之间并没有绝对的优劣之分,而是不同方法各自有各自最适用的范围,也可能有各自的弱项。如果某种方法所采用的数学模型或假设比较符合实际数据情况,方法的效果就会好;反之,方法就可能失败。
针对一个复杂的实际问题,尤其是从现实中提取出来的新问题,而不是人们早已设计好的用于各类竞赛的标准问题,我们往往很难事先知道什么是最好的方法,需要通过适当的实验来评估方法的性能和在不同方法之间进行比较。
在前面各章的介绍中,在多处都涉及了分类错误率等评估方法性能的指标,但并未进行系统的介绍。事实上,很多学者也认为如何评价模式识别和机器学习方法的表现是一件很简单的事,不需要专门进行研究。这种认识其实在很多情况下是有问题的。本章将尝试比较系统地介绍机器学习中评估监督模式识别和非监督模式识别方法性能的基本问题和常用方法,并讨论其中需要特别注意的问题。我们将会看到,在很多情况下,不恰当的评估方式可能会导致假象和错误结论。
13.2 监督模式识别的错误率估计
监督模式识别有确定的分类目标,即把每一个样本划分到若干目标类别中的一个类,并希望这种划分相对于样本真实所属的类别来说错误率尽可能小。这里的错误率就是错误的
决策在全部决策中所占比例的数学期望。在2.6节中,已经介绍了在正态分布情况下错误率的理论分析方法,一般情况下,这种理论分析很难做到,人们更多的是采用实验方法来估计分类器的错误率。这里来介绍几种典型的做法。
13.2.1 训练错误率
最简单的错误率估计方法是在分类器设计完成后,用分类器来对全部训练样本进行分类,统计其中分类错误的样本占总样本数的比例,用这个比例作为错误率的估计。这个错误率叫做训练错误率(training error rate 或简称 training error),在统计上也被叫做视在错误率(apparent error)或重代入错误率(re-substitution error)。
显然,这种做法是偏乐观的,因为分类器设计过程中已经用到了所有样本的信息,因此这种训练错误率不能忠实地反映分类器在未来样本上的表现,即不能反映分类器的推广能力。例如在极端情况下,分类器可以通过记忆把每个训练样本的类别都记住,只要没有特征完全相同的样本分属于不同类,这样就能够做到训练错误率为0,但是显然这种0错误率不能反映分类器对未来的样本是否能分类正确。
训练错误率就是第7章中讨论的经验风险。这个错误率虽然偏乐观,但也并不是完全没有用,当学习机器设计合理和训练样本满足一定条件时,经验风险能够一定程度上反映机器的推广能力,这也就是为什么很多采用经验风险最小化原则训练的分类器仍然有较好表现的原因。统计学习理论系统地研究了经验风险与期望风险的关系,说明了要使经验风险最小化的解收敛于期望风险最小化的解(即学习过程一致)的充分必要条件,给出了有限数目的样本下期望风险与经验风险之间差的上界,提出了所谓“结构风险最小化”准则,并在此准则下发展出了支持向量机这种具有较好推广能力的学习机器。
在实际应用中,虽然人们一般不把训练错误率当作评价分类器性能的指标,但训练错误率仍然可以帮助粗略判断分类器的效果、模型的适合程度或数据的可分性。
13.2.2 测试错误率
如果实际问题中的样本可以划分出一部分来用作独立的测试集(也称做检验集或考试集),或者可以在目前已有样本之外有条件采集更多的样本,那么就可以用测试集数据来估计分类器性能。这样得到的错误率估计叫做测试错误率。从测试的角度看,这种估计也可以叫做已设计好分类器情况下错误率的估计问题。
假定测试集中有 N 个样本,直观上可以用测试集中被分错的样本在 N 中的比例作为测试错误率。但是,这种估计有理论依据吗?估计量的性质如何?测试集的样本数目是否影响估计的准确性?
下面分两种情况来讨论这些问题。这里只讨论两类分类情况。
1. 先验概率 $ P(\omega_{1}) $, $ P(\omega_{2}) $ 未知——随机抽样
当不知道先验概率 $ P(\omega_{i}) $, i=1,2 时,可以简单地随机抽取 N 个样本作为检验集,样本的这种抽取方法叫随机抽样。
假如对 N 个样本进行考试,结果错分了 k 个,k 是一个离散随机变量,用 $ \varepsilon $ 表示真实错误率,在给定 $ \varepsilon $ 后,k 的密度函数为二项分布
$$ P(k)=C_{N}^{k}\varepsilon^{k}\left(1-\varepsilon\right)^{N-k} $$
其中
$$ C_{N}^{k}=\frac{N!}{k!\ (N-k)!} $$
$ \varepsilon $ 的最大似然估计 $ \hat{\varepsilon} $ 就是下列方程的解
$$ \frac{\partial\mathrm{ln}P(k)}{\partial\varepsilon}=\frac{k}{\varepsilon}-\frac{N-k}{1-\varepsilon}=0 $$
解之得
$$ \hat{\epsilon}=\frac{k}{N} $$
也就是被错分的样本数 k 与总考试样本数之比给出了错误率 $ \varepsilon $ 的最大似然估计 $ \hat{\varepsilon} $,它的意义是很直观的。
由于 $ \hat{\epsilon} $是一个估计量,考试集中的样本是随机抽取的,错分样本数k也是随机变量,作为k的函数的 $ \hat{\epsilon} $也是随机变量。所以有必要就 $ \hat{\epsilon} $的期望,方差和置信区间进行讨论。
二项分布的特征函数、期望和方差分别为
$$ \varphi(t)=\left[\varepsilon\mathrm{e}^{\mathrm{j}t}+(1-\varepsilon)\right]^{N} $$
$$ E(k)=N\epsilon $$
$$ \mathrm{Var}(k)=N\varepsilon(1-\varepsilon) $$
因此,的期望为
$$ E(\hat{\varepsilon})=E\left[\frac{k}{N}\right]=E[k]/N=\frac{N\varepsilon}{N}=\varepsilon $$
方差为
$$ \mathrm{Var}\big[\hat{\varepsilon}\big]=\mathrm{Var}\big[k\big]/N^{2}=\varepsilon(1-\varepsilon)/N $$
由式(13-7)可知, $ \hat{\varepsilon} $ 是 $ \varepsilon $ 的无偏估计量。
Highleyman 给出了 95% 置信系数下的置信区间 $ (\varepsilon_{1}, \varepsilon_{2}) $ 与 $ \hat{\varepsilon} $ 和 N 的关系,见图 13-1。
$$ P\left(\varepsilon_{1}\leqslant\varepsilon\leqslant\varepsilon_{2}\right)=1-\frac{\beta}{100}=0.95 $$
其中 $ \beta $为置信水平。
显然,考试样本数 N 越多,则估计出的错误率 $ \varepsilon $ 的置信区间越小。例如,在 50 个考试样本上未发生错分,即 k=0,则错误率的估计 $ \hat{\varepsilon}=\frac{k}{N}=0 $,而从图 13-1 看出真实错误的置信区间为 $ 0\sim0.08 $,即 $ \varepsilon $ 在 $ (0,0.08) $ 内。又如,若考试样本数 N=250,考试结果 k=0,即 $ \hat{\varepsilon}=0 $,此时却可保证真实错误率 $ \varepsilon\leq0.02 $。
2. 先验概率 $ P(\omega_{1}) $, $ P(\omega_{2}) $ 已知——选择性抽样
当知道两类的先验概率 $ P(\omega_{i}), i=1,2 $ 时,可分别从类别 $ \omega_{1} $ 和 $ \omega_{2} $ 总体中抽取 $ N_{1}=P(\omega_{1})N $ 和 $ N_{2}=P(\omega_{2})N $ 个样本,并用 $ N_{1}+N_{2}=N $ 个样本作为检验集。这样的抽取方法称为选择性抽样。

设 $ k_{1} $ 和 $ k_{2} $ 分别为考试集中属于 $ \omega_{1} $ 及 $ \omega_{2} $ 类别但被错分的样本数,因 $ k_{1}, k_{2} $ 是相互独立的,故 $ k_{1}, k_{2} $ 的联合概率为
$$ P\left(k_{1},k_{2}\right)=P\left(k_{1}\right)P\left(k_{2}\right)=\prod_{i=1}^{2}C_{N_{i}}^{k_{i}}\varepsilon_{i}^{k_{i}}\left(1-\varepsilon_{i}\right)^{N_{i}-k_{i}} $$
其中 $ \varepsilon_{i} $为 $ \omega_{i} $类别的真实错误率。
利用同样的方法可得出 $ \varepsilon_{i} $的最大似然估计为
$$ \hat{\varepsilon}_{i}=\frac{k_{i}}{N_{i}},\quad i=1,2 $$
而总的错误率估计为
$$ \hat{\epsilon}^{\prime}=P(\omega_{1})\hat{\epsilon}_{1}+P(\omega_{2})\hat{\epsilon}_{2}=\sum_{i=1}^{2}P(\omega_{i})\hat{\epsilon}_{i} $$
从而 $ \hat{\varepsilon}' $ 的期望和方差分别为
$$ E\big[\hat{\epsilon}^{\prime}\big]=P(\omega_{1})E\big[\hat{\epsilon}_{1}\big]+P(\omega_{2})E\big[\hat{\epsilon}_{2}\big]=P(\omega_{1})\epsilon_{1}+P(\omega_{2})\epsilon_{2}=\epsilon_{1}\epsilon_{2} $$
$$ \mathrm{V a r}\big[\hat{\varepsilon}^{\prime}\big]=\frac{1}{N}\sum_{i=1}^{2}P(\omega_{i})\varepsilon_{i}\left(1-\varepsilon_{i}\right) $$
由式(13-13)和无偏估计量的定义可知, $ \hat{\varepsilon}' $ 是 $ \varepsilon $ 的无偏估计量。
以上讨论了两种情况下的错误率估计,未知先验概率时得到的错误率 $ \varepsilon $的估计量 $ \hat{\varepsilon} $,与已知先验概率时得到的错误率 $ \varepsilon $的估计量 $ \hat{\varepsilon} $是有所不同的。因此,要分析究竟哪一种方法得到的错误率估计更好。
由于它们都是随机变量,无法就某个估计值来评价它们的好坏,而只能研究它们的统计特性,这里来比较方差间的差别。
$$ \mathrm{V a r}[\hat{\varepsilon}]-\mathrm{V a r}[\hat{\varepsilon}^{\prime}]=[\varepsilon(1-\varepsilon)-P(\omega_{1})\varepsilon_{1}(1-\varepsilon_{1})-P(\omega_{2})\varepsilon_{2}(1-\varepsilon_{2})]/N $$
$$ =\left[P\left(\omega_{1}\right)P\left(\omega_{2}\right)\left(\varepsilon_{1}-\varepsilon_{2}\right)^{2}\right]/N\geqslant0 $$
式(10-15)说明用选择性抽样得到的错误率估计的方差 $ \mathrm{Var}[\hat{\varepsilon}^{\prime}] $ 一般小于用随机抽样得到的错误率估计的方差。这在直观上也是不难理解的,因为前者在估计中利用了先验概率 $ P(\omega_{i}) $ 的信息。
以上讨论可以推广到多类问题,这里只需将上面公式中的连乘符号 $ \prod $ 和求和符号 $ \sum $ 的上限从2改为c就可以了,其中c为类数。
现在我们可以回答本节开始时提出的三个问题:
(1)这些估计是在最大似然估计意义上最好的估计。
(2)它们是错误率 $ \varepsilon $的无偏估计量。
(3)从置信区间的讨论可见,随着样本数 N 的增加,其置信区间相应的缩小。
在实际应用中,如果总样本量充足,则建议采用尽可能多的样本组成测试集,这样能够保证对分类器错误率的估计比较准确。当然,在总样本量一定的情况下,采用更多的样本作测试样本就意味着只能用相对更少的样本作为训练样本,这可能会降低分类器的性能,需要根据实际情况进行折中。如果不考虑对性能的评估,而是希望在现有样本情况下得到尽可能好的分类器,那么就应该尽可能充分利用所有样本来设计分类器。
13.2.3 交叉验证
当总的样本数目不是很大时,如果把其中一部分样本划分为测试集,则训练样本数目就大大减少,分类器性能可能会受到影响,这样得到的测试错误率不能反映将所有样本用来设计分类器所能得到的最好的性能;同时,由于测试集本身也不大,所以测试错误率估计的方差本身也可能较大。在这种两难的情况下,人们通常使用交叉验证(cross-validation,CV)法来估计分类器的性能。
交叉验证的基本思想就是在现有总样本不变的情况下,随机选用一部分样本作为临时的训练集,用剩余样本作为临时的测试集,得到一个错误率估计;然后随机选用另外一部分样本作为临时训练集,其余样本作为临时测试集,再得到一个错误率估计……如此反复多次,最后将各个错误率求平均,得到交叉验证错误率(cross-validation error rate,CV error)。在进行交叉验证时,一般让临时训练集较大,临时测试集较小,这样得到的错误率估计就更接近用全部样本作为训练样本时的错误率。而测试集过小带来的错误率估计方差大的问题通过多轮实验的平均可以得到一定的缓解。
交叉验证法的典型做法是所谓 n 倍交叉验证法(n-fold cross-validation),其做法是:把全部样本随机地划分为 n 个等份,在一轮实验中轮流抽出其中的 1 份样本作为测试样本,用其余 $ (n-1) $ 份作为训练样本,得到 n 个错误率后进行平均,作为一轮交叉验证的错误率;由于对样本的一次划分是随意的,人们往往进行多轮这样的划分(例如 k 轮),得到多个交叉验证错误率估计,最后将多个估计再求平均。这种做法又称作 k 轮 n 倍交叉验证。图 13-2 给出了 k 轮 n 倍交叉验证法的示意图。人们经常用的 n 包括 3、5、10 等,分别称作三倍交叉验证(3-fold cross-validation)、五倍交叉验证(5-fold cross-validation)、十倍交叉验证(10-fold cross-validation)等。

交叉验证的一种特殊形式是所谓的留一法交叉验证(leave-one-out cross-validation,LOOCV),这也是样本数较少时最常用的方法。它的做法是不把样本进行分组,而是每轮实验拿出一个样本来作为测试样本,用其余的N-1个样本作为训练样本集,训练分类器,测试对留出的那个样本的分类是否正确;在下一轮实验中,把之前测试的样本放回,拿出另外一个样本作为测试样本,用剩余的N-1个样本作训练,再对留出的样本作测试;依次类推,直到每个样本都被作为测试样本一次。全部N轮实验完成后,统计总共出现的测试错误数(不妨记作m),m占总样本数的比例就是留一法交叉验证错误率。
可以证明,交叉验证法得到的估计是对错误率的一种最大似然估计。但是,当样本数有限时,这种估计是略微有偏的,偏差来源于每次测试的分类器是在 $ N(1-1/n) $个样本(对于n倍交叉验证)或者N-1个样本(对于留一法交叉验证)上训练得到的,因此最后的估计不是对N个样本上训练出的分类器的性能的估计。从这一角度看,留一法的估计偏差更小些,但是,由于每次只能用一个样本作测试,留一法错误率估计的方差要比n倍交叉验证估计的方差大一些。
除了用于评估分类器或分类算法的性能外,交叉验证还经常被用于分类器参数的选择。在很多应用中,可以首先用不同的参数在训练数据上用交叉验证进行试验,然后按照一定的步长调整参数,再进行交叉验证。如此反复多次后,就可以发现分类器在该数据或该类数据上用什么参数配置效果最好。确定这些参数后,再在所有样本上训练分类器。
在深度学习中,由于存在更多的超参数需要人为设定,当样本数目足够时,人们经常用交叉验证的方法帮助进行超参数选择。典型的做法是,如果问题中给定了训练集和测试集,测试集是在训练阶段无法使用的,人们往往把训练集再分出一部分来作为验证集,用验证集来评估在剩余训练样本上训练出来的方法效果,尝试不同的超参数以在验证集上取得最好的效果,然后用这样的超参数对所有训练样本进行训练,得到最终的分类器。如果训练样本数不足够多,也可以在训练样本集上用交叉验证的方法进行这种评估试验。
深度学习中另一种常见的用法是,在训练过程中实时观察当前训练结果在验证集上的
表现,与训练过程中得到的训练错误率进行比较。如果发现学习过程中某个时刻训练错误率仍在下降,但验证集上的错误率开始不降反升,则往往可以提示开始出现了过学习现象,这时应该停止训练。
13.2.4 自举法与0.632估计
错误率的实验估计,本质上可以看作是有限样本下对某个参数的估计问题。自举法(bootstrap)是统计学中一种常用的估计方法,其基础是对样本集的自举采样,也有人翻译为“靴带法”。在第8章8.3.4节中,我们已经讨论过在随机森林方法中使用自举采样的策略。
设有一个大小为 N 的样本集,随机地从中有放回地抽取 N 个样本组成一个新样本集,这个新样本集就称作自举样本集。(由于是有放回的抽样,所以自举样本集中肯定会有一部分重复样本。)在一个实际问题中,人们拥有的样本集其实是某个未知的样本总体中一次采样的结果,如果只用这个样本集进行某个参数的估计,结果就难免带有偶然性。自举的基本思想是,从原始样本集中进行 B 次自举抽样,目的就是为了模仿从总体中得到多个同样大小的样本集,从每个自举样本集得到一个估计,用 B 个估计的平均作为最后的估计结果。统计学研究表明,这种自举估计能够有效提高估计量的性能。
用自举方法估计错误率,就是从原数据中抽取 B 个自举样本集,用每个自举样本集训练一个分类器,用它来预测在该自举样本集中没有被抽到的样本,统计预测错误率。将用 B 个自举样本集得到的预测错误率平均,就是自举法估计的错误率。
由于自举采样是有放回的抽样,通常其中会有重复样本。统计研究和实验表明,一个自举样本集中会包含原样本集中63.2%左右的样本。因此,与交叉验证方法类似,自举法估计的错误率也会偏保守。
考虑到训练错误率是对真实错误率偏乐观的估计,而自举错误率是偏保守的估计,人们提出可以将这两种估计按照一定的方式结合起来,例如Efron提出了下面的0.632估计
$$ B.632=0.368\times AE+0.632\times B1 $$
其中,AE是在全部样本上的训练错误率(视在错误率),B1是自举错误率。理论和实验研究表明,B.632是对错误率更好的估计。有关的理论和实验研究者可以参考相关文献 $ ^{①} $。
13.3 有限样本下错误率的区间估计
13.3.1 问题的提出
根据有限数目的样本估计错误率,不论用以上哪种方法,得到的都是对错误率的一个点估计,没有考虑样本集本身的随机性对分类性能的影响。
例如,在某个固定的样本集上,比较两种分类器,都采取样本划分法划出一定数目的测试样本集,如果两种方法所得到的测试错误率分别是0.05和0.04,那么能否有把握地说,后一种方法好于前一种方法?这微小的差别是否是在这个样本集上的偶然性?
又例如,如果在两个不同的医院采集了两组病人数据,用相同的方法在两组数据上分别做了相同的交叉验证测试,结果在两组数据上得到的分类正确率分别是92%和94%,那么能否有理由推断在第二组数据对应的病人上,所研究的问题具有更好的可分性?
再例如,SVM 的核函数类型选择是在面对一个实际应用时首先需要考虑的重要问题,如果在给定的数据上比较线性 SVM 和多项式核 SVM 的性能,那么,当采用一种核函数的实验性能比采用另外一种核函数高出多少时,才能确切判断用这种核函数的优势?
当样本数目比较有限时,单独靠对错误率的某个估计值(点估计)是无法回答上述问题的。
近年来,人们开始认识到,要系统地比较方法的优劣,不能单独靠性能指标的一个点估计来比较。因此,人们在对一些常用的标准数据集进行实验时,采用了把样本集进行多次划分的做法:事先把同一套数据做成若干个版本,每个版本的数据里又分成训练集和测试集。这种做法与交叉验证相似,不同的是,标准数据集是事先把样本进行了固定的划分,以便不同的人在使用这套数据时能够有统一的标准;另外一个不同是,在对每一套数据进行训练和测试后,不但要统计测试错误率的平均值,而且要统计各次的测试错误率分布的方差。图13-3给出了文献中用这种方法比较不同分类器的一个例子。其中,不但给出了各个分类器在不同数据上的平均错误率,而且给出了在多次实验中得到的错误率的标准差。
| | SVM | KFD | RBF | AB | $ AB_{{R}} $ |
| --- | --- | --- | --- | --- | --- |
| Banana | $ 11.5\pm0.07 $ | $ 10.8\pm0.05 $ | $ 10.8\pm0.06 $ | $ 12.3\pm0.07 $ | $ 10.9\pm0.04 $ |
| B. Cancer | $ 26.0\pm0.47 $ | $ 25.8\pm0.46 $ | $ 27.6\pm0.47 $ | $ 30.4\pm0.47 $ | $ 26.5\pm0.45 $ |
| Diabetes | $ 23.5\pm0.17 $ | $ 23.2\pm0.16 $ | $ 24.3\pm0.19 $ | $ 26.5\pm0.23 $ | $ 23.8\pm0.18 $ |
| German | $ 23.6\pm0.21 $ | $ 23.7\pm0.22 $ | $ 24.7\pm0.24 $ | $ 27.5\pm0.25 $ | $ 24.3\pm0.21 $ |
| Heart | $ 16.0\pm0.33 $ | $ 16.1\pm0.34 $ | $ 17.6\pm0.33 $ | $ 20.3\pm0.34 $ | $ 16.5\pm0.35 $ |
| Image | $ 3.0\pm0.06 $ | $ 3.3\pm0.06 $ | $ 3.3\pm0.06 $ | $ 2.7\pm0.07 $ | $ 2.7\pm0.06 $ |
| Ringnorm | $ 1.7\pm0.01 $ | $ 1.5\pm0.01 $ | $ 1.7\pm0.02 $ | $ 1.9\pm0.03 $ | $ 1.6\pm0.01 $ |
| ESonar | $ 32.4\pm0.18 $ | $ 33.2\pm0.17 $ | $ 34.4\pm0.20 $ | $ 35.7\pm0.18 $ | $ 34.2\pm0.22 $ |
| Splice | $ 10.9\pm0.07 $ | $ 10.5\pm0.06 $ | $ 10.0\pm0.10 $ | $ 10.1\pm0.05 $ | $ 9.5\pm0.07 $ |
| Thyroid | $ 4.8\pm0.22 $ | $ 4.2\pm0.21 $ | $ 4.5\pm0.21 $ | $ 4.4\pm0.22 $ | $ 4.6\pm0.22 $ |
| Titanic | $ 22.4\pm0.10 $ | $ 23.2\pm0.20 $ | $ 23.3\pm0.13 $ | $ 22.6\pm0.12 $ | $ 22.6\pm0.12 $ |
| Twonorm | $ 3.0\pm0.02 $ | $ 2.6\pm0.02 $ | $ 2.9\pm0.03 $ | $ 3.0\pm0.03 $ | $ 2.7\pm0.02 $ |
| Waveform | $ 9.9\pm0.04 $ | $ 9.9\pm0.04 $ | $ 10.7\pm0.11 $ | $ 10.8\pm0.06 $ | $ 9.8\pm0.08 $ |
与依靠点估计的性能评价相比,这种样本多次划分的方法在一定程度上考虑了样本随机性对评价结果的影响。但是,由于不同版本的数据只是训练数据和测试数据的划分不同,各个版本的训练数据之间以及测试数据之间存在一定的重合,因此,各次实验之间不是独立的,所估计出的错误率区间会偏小,当总样本数不是太大时尤其如此。事实上,人们已经证明,考虑样本随机性,如果仅基于交叉验证,不存在错误率估计量方差的无偏估计。Bengio
和 Gradvalet 指出,即使样本数相对较多,多重交叉验证样本划分所得错误率间的相关性也可能很大 $ ^{①} $。如何更好地评估错误率点估计的不确定性是一个在理论研究和实际应用中都值得关注的问题。
13.3.2 用扰动重采样估计 SVM 错误率的置信区间
之所以单纯靠样本划分或重采样无法获得对分类器错误率变换范围的无偏估计,是因为在划分或重采样得到的数据集之间存在不可避免的相关性。这一问题可以通过适当引入扰动的方法来解决。在对回归误差的置信区间分析中,Tian等发展了一种利用扰动重采样(perturbation-resampling)的策略,并进行了系统的理论分析 $ ^{②} $。我们将这一思想发展到支持向量机的错误率方差估计上,下面对其基本原理进行简要介绍,详细的分析和证明读者可以参阅相关文献。
首先把所研究的问题形式化表述一下。
设有一组训练样本
$$ (x_{1},y_{1}),(x_{2},y_{2}),\cdots,(x_{n},y_{n}) $$
其中,x 是样本的特征向量,y 是对应的类别标号。在这组样本上,训练一个线性的支持向量机,得到分类器 $ f(x;\hat{\theta}) $,其中 $ \hat{\theta} $ 表示支持向量机训练得到的最优参数。
假设从与样本集式(13-17)相同的分布中独立抽取一个样本 $ (x_{0}, y_{0}) $,在这个样本上分类器的输出是 $ f(x_{0}; \hat{\theta}) $。定义期望绝对错误率
$$ \varepsilon=E(|y_{0}-f(x_{0};\hat{\theta})|) $$
这也就是要估计的错误率,其中, $ E(\cdot) $ 表示求数学期望。
需要注意的是,通常只考虑对于未见到的所有可能样本的错误率,即式(13-18)中只对测试样本 $ (x_{0}, y_{0}) $求数学期望,这样得到的错误率是对在给定的训练样本上得到的固定分类器的评价。实际上,训练样本本身也是从总体分布中的一次随机抽样,在对方法进行研究和比较时,需要考查的是方法在所研究的问题上而不是该特定数据上的性能,因此,式(13-18)中还应该对样本集式(13-17)求数学期望。
训练错误率即重代入错误率是对期望错误率的最简单的估计
$$ \varepsilon_{re}=\frac{1}{n}\sum_{i=1}^{n}\left|y_{i}-f(x_{i};\hat{\theta})\right| $$
正如本章开始讨论的,当样本数 n 不是充分大时,这种估计会偏乐观,即过低估计错误率。
13.2 节介绍的交叉验证法,就是把样本集随机地划分成 k 个子集进行 k 轮实验,在第 k 轮实验中,用除第 k 个子集外的样本训练分类器,得到参数 $ \hat{\theta}_{(-k)} $,用所得的分类器得到在第 k 个子集上的错误率 $ \varepsilon_{(k)}(\hat{\theta}_{(-k)}) $,各轮实验的错误率的平均就是交叉验证错误率
$$ \varepsilon_{\mathrm{C V}}=\frac{1}{K}\sum_{k=1}^{K}\varepsilon_{(k)}\left(\hat{\theta}_{(-k)}\right) $$
为了研究在考虑到训练和测试样本两方面的随机性的情况下,训练错误率、交叉验证错误率与期望错误率的关系,我们考查了统计量
$$ W=n^{1/2}(\varepsilon_{\mathrm{r e}}-\varepsilon) $$
的分布情况,并且证明,该统计量渐近收敛于一个均值为0的正态分布。对于K倍交叉验证,当K固定且相对于样本数较小,同样可以证明,统计量
$$ W_{\mathrm{C V}}=n^{1/2}(\varepsilon_{\mathrm{C V}}-\varepsilon) $$
在渐近意义下等价于 W,可以通过估计 W 的分布来近似 $ W_{CV} $ 的分布。
从理论上可以推导 $ W_{CV} $ 分布的方差,但是其估计需要计算未知非参数函数的梯度,尤其是当待定参数 $ \theta $ 的维数较高时计算困难。为了克服这些困难,我们提出了一种扰动采样的方法来估计统计量 $ W_{CV} $ 的分布。
令 $ \{G_{i}, i=1,2,\cdots,n\} $为一组与观测数据相互独立且具有单位均值和方差的独立同分布正值随机数。实际应用中,可以用指数分布来产生这样的一组随机数。对于给定的一组随机数 $ \{G_{i}, i=1,2,\cdots,n\} $,定义目标函数
$$ \hat{Q}_{n}^{\mathrm{~s~}}(\theta)=\frac{1}{n}\sum_{i=1}^{n}G_{i}\left\{\left[1-y_{i}f(x_{i},\theta)\right]_{+}+\lambda_{n}w\cdot w\right\} $$
并令 $ \theta^{*} $为最小化 $ \hat{Q}_{n}^{*}(\theta) $的解。令
$$ W^{*}=n^{1/2}\sum_{i=1}^{n}G_{i}\left\{\|y_{i}-f(x_{i};\theta^{*})\|-\varepsilon_{\mathrm{r e}}\right\} $$
可以证明,在给定数据式(13-17)的条件下,式(13-24)中 $ W^{*} $ 的分布可以很好地近似 $ W_{CV} $ 的分布。
通过与支持向量机的目标函数比较可以看到,式(13-23)的目标函数具有与支持向量机目标函数相同的形式,其中第一项就是支持向量机中的松弛因子项,第二项就是分类间隔项。式(13-23)与支持向量机目标函数的区别,就是对每个训练样本都引入了一个随机的扰动因子 $ G_{i} $。因此,这种方法称作扰动重采样法。
与标准的支持向量机相同,为了求解式(13-23)的优化问题,可以将它转化为对偶问题,即在约束条件
$$ \sum_{i=1}^{n}\alpha_{i}y_{i}=0\quad 和 \quad0\leqslant\alpha_{i}\leqslant CG_{i},\quad i=1,2,\cdots,n $$
下,对对偶参数 $ a_{i}, i=1,\cdots,n $ 最大化目标函数
$$ \sum_{i=1}^{n}\alpha_{i}-\frac{1}{2}\sum_{i,j=1}^{n}\alpha_{i}y_{i}(x_{i}\cdot x_{j})y_{j}\alpha_{j} $$
求得的原问题式(13-23)的解是
$$ \mathcal{w}^{*}=\frac{\displaystyle\sum_{i=1}^{n}\alpha_{i}y_{i}x_{i}}{n^{-1}\sum_{i=1}^{n}G_{i}} $$
不难看出,这里与标准的支持向量机的唯一区别在于式 $ (13-25a) $的条件中对应每个样
本有一个随机数因子。
刘一组随机扰动因子 $ \{G_{i}, i=1,2,\cdots,n\} $,求解式(13-25)的支持向量机,用得到的解就可以得到式(13-24)定义的 $ W^{*} $的一次实现值。由于 $ W^{*} $的分布能够近似 $ W_{CV} $的分布,我们可以通过产生多组随机扰动因子来得到大量 $ W^{*} $的取值,即对 $ W^{*} $分布的大量采样,利用这些数据就可以估计 $ W_{CV} $的分布,进而估计根据交叉验证错误率 $ \varepsilon_{CV} $给出对期望错误率的区间估计。例如, $ \varepsilon $的100(1- $ \alpha $)%置信区间可以根据下式估计
$$ [\varepsilon_{\mathrm{C V}}-n^{-1/2}\hat{\xi}_{1-\alpha/2},\varepsilon_{\mathrm{C V}}+n^{-1/2}\hat{\xi}_{1-\alpha/2}] $$
其中, $ \hat{\xi}_{1-\alpha/2} $ 是 $ W^{*} $ 的采样分布的 $ \alpha $ 百分点。
下面给出采用扰动重采样法估计错误率的置信区间的基本过程:
(1) 用给定的数据式(13-17)训练支持向量机并用式(13-20)计算交叉验证错误率 $ \varepsilon_{CV} $ 。
(2)事先设定重采样次数R,对每一次重采样:
①用指数分布产生均值和方差为1的独立正随机数 $ \{G_{i}, i=1,2,\cdots,n\} $;
②解扰动后的支持向量机式(13-25),并根据式(13-24)计算 $ W_{r}^{*} $
(3)根据(2)中得到的 $ \{W_{r}^{*}, r=1,\cdots,R\} $估计 $ W^{*} $的采样分布。
(4)根据式(13-27)估计错误率的置信区间;如果要对不同分类器作比较,还可以根据重采样分布对不同分类器的性能差别进行统计显著性分析。
扰动重采样方法估计错误率置信区间,不但考虑了测试样本的不确定性,而且考虑了现有训练样本的不确定性,能够得到对方法性能更全面的评价。这种方法可以在模型选择、分类器比较、特征选择及基于分类结果的统计推断等多个方面发挥作用。
在我们发表的文章 $ ^{①} $和蒋博的硕士学位论文 $ ^{②} $中,给出了对扰动重采样方法详细的理论推导和实验分析,以及一些应用的实例。
应当指出,有关扰动重采样方法的理论证明,是在样本数目趋于无穷大的渐近条件下给出的,在有限样本下的结论尚有待进一步研究。但是,大量的数值实验结果表明,在样本数有限的情况下,该方法仍然具有良好的性能。同时,目前关于此方法的结论都是针对线性核的支持向量机分类器的,如何推广到其他核函数以及更多类型的分类器,仍然是一个有待进一步研究的问题。
13.4 特征提取与选择对分类器性能估计的影响
在前面讨论错误率估计时,都只考虑了在固定特征集合的情况下对分类器错误率的估计。在很多实际问题中,特征选择是与分类器设计同样重要的一个环节,有时甚至比分类器设计更关键,因此,经常需要连同特征提取和选择一起来评价分类器的性能,即评价特征提
取与选择和分类器组成的模式识别系统的性能,而不单纯评价是在确定的特征下分类器的性能,这个问题在传统模式识别研究中较少被单独拿出来讨论。
如果是把特征提取与选择和分类器看成是一个整体,那么前面介绍的任何方法都可以用来评价系统的性能。
但是,在一些实际应用中,由于特征选择和提取往往是一个单独的程序,人们有时候向于在评价系统时忽略特征选择与提取过程,而只对系统中的分类器部分进行评价。经常见到的做法是,把所有样本都用来进行特征选择与提取,而后把所选择和提取的特征固定下来,再把样本分成训练集和测试集,或者采用交叉验证的方法来估计分类器性能。我们把采用这种方法进行交叉验证的做法称作 CV1。在一些应用中,这种做法可能会导致对分类性能的估计偏乐观,极端情况下可能会引导出错误的结论。
当样本数目比较多,而样本的初始特征维数并不是太高时,CV1交叉验证一般不会暴露出明显的问题。但是,当初始特征维数很高,样本数目相对又很小时,即使很多特征中并不包含对实际分类有贡献的信息,但是由于样本的随机性,从大量特征中总能选择或变换出在有限样本上能把两类较好分开的一些特征,如果是在确定这些特征后再对分类器进行交叉验证,势必得到较高的分类正确率,而这并不能反映样本的真实情况。我们曾经做过试验,按照一定的概率模型产生一组1000维特征的40个样本,样本都是用同一个模型产生的,但是随意地指派它们为两个类别,即两个类是完全用同一个分布产生的,是两个假类别。但是,经过一系列特征选择后,我们能在20个特征上观察到分类器交叉验证错误率为0,得到两类能完全可分的假象。这也可以理解为由特征选择或特征提取引起的过学习现象。如果基于这种交叉验证结果做结论,将导致与实际情况巨大的偏离。
导致这种现象的原因,是在特征选择或提取过程中的“信息泄露”。一个模式识别系统是由特征选择与提取和分类器共同组成的,CV1方法在特征选择与提取时利用了全部样本,即在测试分类器性能前已经利用了来自将来测试样本的信息,因此导致最终对分类性能的过乐观估计。当样本数目较大和特征维数较低时,样本集中是否拿出一部分样本对特征选择和提取的影响不大,因此CV1交叉验证还是基本无偏的。但当样本数很少或维数很高时,这种信息泄露造成的估计偏差就会很严重。
严格的做法应该是,在对样本进行任何处理之前就把待测试的样本拿走,只用训练样本进行特征提取和选择,然后进行分类器设计,再用预留的测试样本对分类器进行测试。如果采用交叉验证,那就需要在未作任何特征选择与提取前把测试样本和训练样本分开,在每一轮里只用训练样本选择和提取特征,我们把这种策略称作 CV2。还是用上面提到的假类别数据进行实验,当采用 CV2 的交叉验证策略时,无论采取怎样的特征选择方法,最后得到的错误率总在 50% 左右,反映了两类并不可分的真实状况。
采用 CV2 策略进行交叉验证,可以得到对包括特征选择与提取部分在内的模式识别系统性能的真实估计,但是却没有得到一组唯一的用于分类的特征。这是因为在交叉验证的每一轮运行中所选出的特征都有可能不同。这也从另一个方面说明了 CV1 的策略为什么会导致偏离的估计。如果样本数较大,每一轮所选择或变换出的特征会差别很小甚至相同;当样本数较小时,CV2 实际上得到了一组不同的特征选择或提取的方案。交叉验证只是为了估计模式识别系统的性能,而真正在未来的样本上应用时,还需要设计出一个唯一的、根据目前数据性能最好的特征和分类器方案。这时,一种做法是利用所有样本重新进行一次
特征选择与提取,得到唯一的特征组合,用所有样本设计分类器;另外一种做法是,将 CV2交叉验证中得到的各个特征组合方案进行综合,从中选择在各轮交叉验证中被选中次数最多的若干特征组成最后的特征集,用这些特征在所有样本上设计分类器。这两种做法都是行之有效的经验性做法,尚缺乏严格的理论分析。更多讨论读者可以参考相关文献 $ ^{①} $。实际上,有限样本下模式识别系统的性能估计和特征选择与提取的可重复性问题是一个仍然需要深入研究的开放领域。
13.5 用分类性能进行关系推断
在概论中我们讨论过,要把一个问题描述成模式识别问题,基本的前提是特征和分类之间存在依赖关系,即图1-3中的系统S是存在的,虽然人们并不知道。然而,在一些实际问题中,尤其是在一些数据挖掘(即从大量数据中寻找规律和知识)问题中,有时只能假设或者猜测某些特征与某种分类间可能有关系,此时,应用模式识别方法的目的之一就是通过分类的效果来判断特征与分类间是否的确存在可以用于预测的关系。
例如,科学家知道很多癌症都是与细胞中基因的某些变化有关的,因此,人们用基因芯片或高通量测序等手段来研究很多癌症及其不同的病理性质,例如研究基因表达数据对某种癌症亚型分类的作用,这叫做癌症的分子分型。典型的做法是,采集一些分别属于两种亚型的病例样本,用基因芯片来检测每个样本的成千上万个基因的表达,组成初始的高维特征向量x,亚型就是类别标号y,利用这些已知样本来进行特征选择、提取和训练分类器。这看上去很像一个典型的模式识别问题。
但是,这种问题与传统的模式识别问题相比至少有两个重要的区别。一个区别是,这类问题中的初始特征维数特别高,而样本却非常少,通常只有几十到几百个。这种情况给特征选择与提取带来了很大的挑战,也向分类器算法提出了很大挑战。近十几年来,人们发展了多种针对这种情况的特征选择和分类器设计方法,第9章9.6节提到的R-SVM和SVM-RFE方法都是在这种背景下提出的,还有一些方法结合了一定的生物背景研究特征选择问题,有兴趣的读者可以阅读相关参考文献。
另一个区别是,在这类问题中,x与y之间的系统S往往是完全未知的,其存在性本身就是一个未知:虽然人们知道癌症的发生和发展中会涉及很多基因层面上的变化,但是,对利用基因芯片或其他技术得到的基因在mRNA水平上的表达是否与所研究的特定癌症分型有关系,人们并没有充分的认识。在这种情形下,如果采用上述的模式识别策略得到了很高的分类精度,例如交叉验证正确率(CV2策略下)或独立测试集上的测试正确率达到90%或更高,那么基本可以判定所用的基因表达特征与这种癌症分型有很强的联系。但是,如果正确率只是70%,那么还能否得出同样的结论?如果是80%或60%呢?
这里讨论的就是一个新的问题,即,如何根据分类器在实际数据上取得的性能,来推断
特征与分类间是否存在函数依赖关系?利用统计检验的语言,就是要研究所得到的分类错误率(或正确率)的统计显著性问题。为了回答这一问题,我们需要考查:如果特征与分类间不存在函数依赖关系,即样本的类别标号与样本特征之间的关系是随机的,那么我们有多少机会得到这样的错误率(或正确率)?这个机会就是假设检验中的 P 值。
由于无法知道分类器在任意样本集上分类性能的分布,因此无法定义一个显式的统计量来进行这种检验。一种有效的方法是随机置换(permutation)法,即在保持已知样本集中两类样本比例不变的情况下,随机地打乱样本的类别标号。这样,即使原样本集上特征与分类之间存在依赖关系,随机置换过程也把这种关系完全打破了。此时,用同样的特征选择、提取和分类方法进行分类,得到的分类性能就反映了这样的模式识别方法在无分类信息的数据上的表现。多次重新随机置换样本类别标号,就可以统计出在没有分类信息情况下模式识别分类性能的空分布,然后把在真实数据上得到的性能估计与这个空分布进行比较,得到分类器性能的随机置换P值。如果该P值很小(通常以小于0.05为参考),则说明在原样本集上得到的分类性能具有统计显著性,初步推断系统S很可能真实存在。
13.6 非监督模式识别系统性能的评价
作为非监督学习的聚类分析,也需要进行性能的评估,但是,由于非监督学习本身的性质决定了人们无法得到已知答案的训练样本,因此无法利用类似分类器性能估计的方法估计聚类的错误率。
在一些关于聚类分析方法的文献中,人们也使用一些有标准答案的数据集来测试聚类方法的“错误率”,并借以比较不同的方法。这种评价属于外部评价。从严格的意义上讲,这样做已经偏离了非监督学习问题的性质,因为需要一定数量的类别已知的样本。这种策略得到的评价,很大程度上依赖于所用的特征和聚类准则是否符合已知类别定义,对于一个完全没有先验认识的非监督学习问题,无法使用这种策略进行性能估计。
对于一个真正非监督学习问题来说,人们通常依靠人工的主观判断来考查聚类分析结果的意义,这一过程依赖于对研究对象相关领域的认识,无法从数学上进行一般性研究。需要特别注意的是,当人们用聚类分析作为一种手段来探索未知的科学问题时,这种主观的判断有时会在无意识中加强研究者本来的猜测,或者加强人们之前已经看到或猜想过的规律,而忽略未事先想象到或与以前认识不符的现象,从而导致错过发现新规律和新模式的机会。
13.6.1 聚类质量的评价
为评估聚类质量,人们发展了一些评价聚类性能的数学指标,希望这些指标可以帮助人们客观地理解和解释所得的聚类结果,也可以作为一种比较不同聚类方法的基础。进一步,聚类的目的是发现数据中自然存在的类别结构,但是,即使数据中不存在这样的类别,多数聚类方法仍然会给出聚类结果,这样就需要有一定的准则来判断所得结果的显著性(即类别结构是否真实存在)。这种评价称作内部评价。本节对一些常见的聚类评价指标进行简要介绍,更详细的内容可以查阅相关文献。
第一种评价指标是紧致性(compactness)或一致性(homogeneity)。最常见的指标是类内方差或者平方误差和,即C均值算法所局部优化的目标。除此之外,还有很多其他类型的类内一致性度量,例如类内两两样本之间的平均或最大距离、平均或最大的基于质心的相似度,或基于图理论的紧致性度量等。例如,可以采用下面的指标
$$ V(C)=\sqrt{\frac{1}{N}\sum_{C_{k}\in C}\sum_{i\in C_{k}}\delta(i,\mu_{k})} $$
其中,N 是样本数目,C 是所有聚类的集合, $ \mu_{k} $ 是聚类 $ C_{k} $ 的质心, $ \delta(\cdot,\cdot) $ 是所采用的距离度量。这个指标越小,说明聚类效果越好,其取值范围是 $ [0,\infty) $。
第二种评价指标是连接性质(connectedness),它衡量了聚类是否遵循了样本的局部密度分布及相邻的样本是否被划分到同一类。这类指标中有代表性的是连接度(connectivity),即样本中相邻的数据点被划分到同一个聚类中的程度。公式如下
$$ \mathrm{Conn}(C)=\sum_{i=1}^{N}\sum_{j=1}^{L}x_{i,nn_{i(j)}} $$
其中,N 是样本数目,L 控制有多少个近邻样本参与连接度计算, $ x_{i,mn_{ij}} $ 取值为:如果第 i 个样本与其第 j 个近邻不在同一个聚类中,则 $ x_{i,mn_{ij}}=\frac{1}{j} $,否则为 0。也就是,连接度 Conn(C) 描述了各个样本的 L 个近邻中不在同一聚类内的程度。在评价一个算法的结果时,这个连接度指标越小越好,其取值范围是 $ [0,\infty) $。
第三种评价指标是分离度(separation),包括各种衡量聚类间分离程度的度量,例如平均或最小类间距离等。可用两类中心间的距离或两类最近样本之间的距离来计算两类间的距离。分离度指标越大则各类间分离越好。
上面三种指标描述了“自然的”聚类所应该具有的一般性质,但是,如果只关注其中某一方面,可能会导致平凡的聚类。因此,人们将某些指标组合,定义出一些能反映多方面综合性质的评价准则。例如,紧致性与分离度是两个极端,如果不断增加聚类数目,紧致性将会随之增加,但分离度也会相应的减小。
因此,正如在 Fisher 线性判别和特征选择时考虑的那样,可以将这两个指标组合起来,定义能同时反映类内距离和类间距离的新指标,例如
$$ S(i)=\frac{b_{i}-a_{i}}{\max(b_{i},a_{i})} $$
其中, $ a_{i} $ 代表样本 i 到和它同类的所有样本的平均距离, $ b_{i} $ 表示样本 i 到其他聚类中最近一个聚类的所有样本的平均距离,这样定义的 S(i) 叫做 Silhouette 值,所有样本的 Silhouette 值的平均称作 Silhouette 宽度 (Silhouette width),其取值在 $ [-1, 1] $ 之间。Silhouette 宽度越大,则聚类效果越好。
Dunn 指数(Dunn index)是另外一个类似的指标
$$ D\left(C\right)=\min_{C_{k}\in C}\left(\min_{C_{l}\in C}\frac{\mathrm{dist}\left(C_{k},C_{l}\right)}{\max_{C_{l}\in C}\mathrm{diam}\left(C_{m}\right)}\right) $$
其中,$\mathrm{diam}(C_{m})$ 是聚类 $C_{m}$ 中最大的类内距离,$\mathrm{dist}(C_{k}, C_{l})$ 是 $C_{k}$ 和 $C_{l}$ 两类中相邻最近的样本对间的距离。$D(C)$ 的取值范围是 $[0, \infty)$,此指数的目标是最大化。
显然,由于没有先验认识,非监督学习的目标是多样的,无论采用什么方法混合多个指
标,都不可避免地导致某些方面信息的损失。另外一种不同的策略是,同时评价各个指标,并且当且仅当一个方法在某一个指标上超出另一个方法、同时在所有指标上都等同于或超出另一方法时,才断定该方法在聚类性能上胜过另一方法。这是个多目标优化的问题。人们可以用多目标优化的方法,来寻找在多个指标上都优胜的聚类方法,或确定方法中的可变参数。这部分内容已经超出了本教材范畴,读者需要时可以查阅相关文献。
除了上述描述聚类自身性质的指标,评价一个非监督模式识别系统的另外一个重要方面是其稳定性,即其结果的可重复性。不管一种聚类方法得到的结果在上述指标上如何,如果在样本有微小变化时聚类结果会有很大差别,则这种聚类结果就很难让人接受,更无法依据这样的结果做出任何可靠的推断。
因此,人们研究了一些评价聚类方法可靠性的方法,典型思想是,采用重复地随机重采样或者对样本加入随机扰动等方法获得多个不同的样本集,在不同的样本集上实施同样的聚类算法,定义某个统计量来衡量在这些重采样或扰动的样本集上得到的聚类结果的一致性,用它来评价从原始的数据上得到的聚类结果的显著性。
例如,Tibshirani 等定义了一个叫做“预测效力”(predictive power)的指标 $ ^{①} $,用来衡量聚类结果的可靠性。具体做法是:将样本随机地划分成两份,两份样本上都各自进行聚类;然后用其中一份中得到的聚类结果作为临时训练样本,对另外一份中的样本实行最近邻法分类,比较这样的分类与直接在这份样本上的聚类划分之间的重合程度,重合程度越大则聚类结果越稳定。实际应用中,这样的实验通常需要多次重复进行,最后以指标的平均值来作为稳定性的度量。
在讨论对非监督模式识别系统的评价时,需要意识到,在非监督学习问题中,没有明确的学习目标,因此不同的方法采取了对学习目标的不同假定。同样的道理,这里讨论的评价准则也反映了对学习结果应该具有的性质的一些假定。评价的目的并不单纯是为了比较不同方法的优劣,更重要的是判断所研究的数据中是否存在聚类结果所反映出的分类模式。而在一个实际问题中,所使用的特征和聚类方法能否有效地发现有意义的聚类、所采用的评价准则能否有效地检验聚类的显著性,首要地取决于对非监督学习目标的假定是否适合所研究的问题。
13.6.2 聚类结果的比较
除了以上讨论的对聚类结果性质的评价外,在聚类分析中我们经常还遇到需要比较两套聚类结果,或者把聚类结果与已知或预期的划分方案进行比较的问题。在有些做非监督学习的文献中,用“错误率”把聚类结果与预期结果相比较,这种做法实际上是不完全妥当的,因为当以错误率为目标时,问题本身的性质变成了监督学习,只是没有使用监督学习的方法而已。
混淆矩阵
把聚类结果与已知的分类方案比较,或者比较两种不同的聚类结果或分类方案,人们经常用混淆矩阵(confusion matrix)来汇总两套分类方案直接的对应关系。在两类情况下,混
滑矩阵如第2章表2-3所示。在多类情况下,我们可以用类似的方式来直观展示两套类别划分方案之间的关系,如图13-4的例子所示。
| 方案1\n方案2 | C1 | C2 | C3 | C4 |
| S1 | 23 | 38 | 122 | 0 |
| S2 | 309 | 12 | 0 | 13 |
| S3 | 0 | 0 | 3 | 98 |
在图13-4的例子中,方案1给出了4个类别,而方案2给出了3个类别,混淆矩阵中的数值是对应两个方案对应位置上的样本数量,例如方案1中划分到C1类的样本和方案2中划分的S1类的样本有23个重合,依此类推。在非监督学习场景下,通常聚类方法给出的类别顺序是随意的,所以两个方案中的类别对应关系事先也是没有定义的,需要根据混淆矩阵找到它们之间可能存在的对应。在这个例子中,我们可以看到,方案2的S1类样本相对比较多与方案1的C3类重合,可以推断S1类可能对应C3类,同理,S2类可能对应C1类,
S3 类可能对应 C4 类,但显然这种对应并不完美。有时人们为了显示效果明显,人工根据对应关系把类别顺序进行调整,使重合样本数目最大的数字出现在矩阵对角线位置上。
混淆矩阵直观地展示了两套划分方案之间的关系,要对两套方案进行定量比较,常用的度量有:
F度量
F度量(F-measure)实际上是第2章2.4节中介绍的F度量在多类情况下的推广。设方案1的类别为 $ C_{k}, k=1,\cdots,K $,每类的样本数为 $ N_{k} $;方案2的类别为 $ S_{t}, t=1,\cdots,T $,每类的样本数为 $ N_{t}, C_{k} $和 $ S_{t} $共有的样本数为 $ N_{tk} $。我们分别用 $ P(S_{t}, C_{k})=\frac{N_{tk}}{N_{k}} $和 $ R(S_{t}, C_{k})=\frac{N_{tk}}{N_{t}} $来表示站在方案1角度和方案2角度看有多大比例的样本与另外方案中的类重合。根据第2章2.4节中的定义,某一对 $ C_{k} $和 $ S_{t} $比较的F度量是
$$ F(S_{t},C_{k})=\frac{2P(S_{t},C_{k})R(S_{t},C_{k})}{P(S_{t},C_{k})+R(S_{t},C_{k})} $$
如果我们认为 $ S_{t} $ 是期望的答案或用来比较的标准,则可以通过一个加权系数 b 来调整 F 度量:
$$ F(S_{t},C_{k})=\frac{(b^{2}+1)P(S_{t},C_{k})R(S_{t},C_{k})}{b^{2}P(S_{t},C_{k})+R(S_{t},C_{k})} $$
计算两套方案中所有两两类之间的 F 度量,寻找最大的对应类,并对标准中的各类进行求和,就得到方案1与方案2相比的总的 F 度量:
$$ F(C)=\sum_{t=1,\cdots,T}\frac{N_{t}}{N_{k=1,\cdots,K}}\max_{k=1,\cdots,K}F(S_{t},C_{k}) $$
其中,N 是总样本数。
F 度量的取值范围是 [0,1],取值越大则两套方案越接近。
Rand 指数和 ARI
Rand 指数 (Rand Index) 是基于对两套方案中具有相同类别关系的样本对所占比例定义的比较两套方案的方法。设计数 a、b、c、d 分别记录了所有 n 个样本在两套方案 U 和 V 中具有不同类别关系情况样本对数目:a 是在两套方案中都被分在一起的样本对的数目,b 是在两套方案中都被分开的样本对数目,c 是在方案 1 中在一起但在方案 2 中被分开的样本对数目,d 是在方案 1 中被分开但在方案 2 中在一起的样本对数目。Rand 指数定义为:
$$ R\left(U,V\right)=\frac{a+b}{a+b+c+d}=\frac{a+b}{n\choose2} $$
Rand 指数的取值范围为 [0,1],取值越大则两套方案越接近。但是,如果对样本集有两套随机划分,它们之间的 Rand 指数的期望并不是一个常数(例如 0),尤其是当类别数较多时,由于大部分样本对都会不在同一个类别中,所以两套随机的划分方案也可能得到接近 1 的 Rand 指数。所以这个指数不易用来评估两套方案的重合程度与随机方案有多大差别。
为解决这个问题,Hubert 和 Arabie 提出了调整的 Rand 指数 ARI(Adjusted Rand Index) $ ^{①} $。他们用广义超几何分布作为随机模型的分布,考虑在随机划分情况下 Rand 指数的期望,把 ARI 定义为
$$ \frac{ 实际指数 - 期望指数 }{ 最大指数 - 期望指数 } $$
它的上界为1,下界为0,当实际指数等于随机划分下的期望指数时取得。在随机划分是广义超几何分布的假设下,可以证明,比较划分方案U和V的ARI指标为:
$$ ARI(U,V)=\frac{\sum_{lk}\binom{n_{lk}}{2}-\frac{\left[\sum_{l}\binom{n_{l,}}{2}\sum_{k}\binom{n_{,k}}{2}\right]}{\binom{n}{2}}}{\frac{1}{2}\left[\sum_{l}\binom{n_{l,}}{2}+\sum_{k}\binom{n_{,k}}{2}\right]-\frac{\left[\sum_{l}\binom{n_{l,}}{2}\sum_{k}\binom{n_{,k}}{2}\right]}{\binom{n}{2}}} $$
其中, $ n_{lk} $ 表示所有样本中被划分到 U 方案的 l 类和 V 方案的 k 类的样本数, $ n_{l} $ 表示所有样本中被划分到 U 方案的 l 类的样本数,余类推。
F 度量和 ARI 是比较常用的比较两套划分方案的方法,也还有其他方法可以采用。例如,如果把在划分方案 U 中所有样本是否属于同一类表示为一个矩阵,同属一类的样本在矩阵交叉点元素上取值为 1,不同属一类的样本交叉点元素取值 0,则对每一套划分方案就形成了一个矩阵 $ C_{U} $,称作共表型矩阵(cophenetic matrix)。可以两套方案的共表型矩阵之间的归一化明可夫斯基距离(Minkowski Distance)来作为两套方案差异程度的度量,
$$ M(U,V)=\frac{\|C_{U}-C_{V}\|}{\|C_{U}\|} $$
称作明可夫斯基打分(Minkowski Score),其取值范围为 $ [0,\infty) $,分值越小则两套方案越接近,完全相同的两套方案分值为0。
13.7 讨论
在第1章最后谈到人们学习的目的应该是为了掌握知识和提高技能,而不是为了应付某种考试。同样,机器学习和模式识别的目的也是为了让机器能够从数据中总结或发现规律,而不是为了一味追求某种指标。但是,要评价学习的效果,采用适当的方法进行测试是必需的,正如学生学习时需要考试一样。
从本章的讨论可以看到,评价模式识别系统性能的方法有多种,每种方法都有各自的特点和适用范围,当人们学习一种模式识别方法或者自己提出一种新的方法时,需要认真、客观地对待评价指标,选择能够反映问题本质的测试方法。应该指出,在一些已发表的文献中,作者有时会有意无意地采用一些对自己的方法有利的评价指标或测试方法,这时他们所得到的结论就不一定准确。大家在学习时需要注意这一点,同时在自己的工作中也需要注意这种可能的问题。
由于不同的模式识别方法有不同的前提和假设,而不同的数据又有不同的特点,所以,要想脱离实际问题而一般性地比较方法的性能往往是不现实的。在一个实际问题中,最好的模式识别方法是那些能够最好地适应数据特点的方法。
另一方面,本章所讨论的模式识别系统的评价,都是对系统分类结果的评价,没有讨论对过程的评价。实际上,除了分类和聚类效果外,不同的方法还有很多不同的计算上的特点,导致方法的计算效率、内存开销等可能很不相同,实现不同方法的难易程度也不同,这也是实际问题中需要考虑的。我们在介绍部分方法时对它们计算上的特点有些简单的讨论,如果要对这方面进行深入学习,读者可以参考关于算法复杂度分析的教材和文献。
常用模式识别与机器学习软件平台
14.1 引言
随着模式识别技术的飞速发展,各类常用编程语言也都发展出各自的模式识别与机器学习工具包,方便用户的使用。本节我们将对 Python、MATLAB 和 R 三种常用的科学计算语言中的模式识别与机器学习工具做简单介绍,并通过简单的回归和分类问题示例,向读者展示在这些平台上基本的软件编程方法。
14.2 Python 中的模式识别工具包
Python 作为一种面向对象的、跨平台的计算机语言,被广泛应用于模式识别与机器学习等领域。尤其是近年来随着深度学习的快速发展,Python 已成为最受欢迎的程序设计语言之一。Python 语言简洁易读,并拥有大量第三方扩展,方便用户使用。
英文中 python 一词的含义是蟒蛇。很多读者可能感觉 Python 是最近一些年新出现的计算机语言,其实这种语言的历史已经有三十多年。Python 语言的创造者是荷兰的软件工程师 Guido van Rossum,他从 20 世纪 80 年代末在他先前设计的 ABC 语言基础上创建一种新的计算机语言,1989 年推出了 Python 语言,1991 年正式发布。他把这种语言命名为 Python 的原因,据说是他是英国经典的喜剧团体 Monty Python(巨蟒)的爱好者,在创造 Python 语言的同时正在看《巨蟒的飞行马戏团》的剧本,认为这种新的计算机语言具有与《巨蟒》系列喜剧相通的特点,例如简洁易懂、丰富多彩、超然脱俗、功能强大、无所不及等。
本节我们将就其中的 scikit-learn 模块的部分功能做介绍。
scikit-learn,简称 sklearn,已经成为 Python 重要的机器学习库。它集成了包含分类、
回归、降维、模型选择等在内的大量机器学习、模式识别算法,已经被广泛应用于数据挖掘和数据分析领域。下面,我们将基于Python 3.6.9展示如何使用sklearn解决回归和分类问题。
14.2.1 sklearn 中的回归方法使用举例
回归是一种监督的模式识别问题。在 sklearn 中,开发者提供了众多解决回归问题的方法,其中包括本书第 5 章 5.2 节讲到的最小二乘法和第 7 章 7.7.4 节讲到的 Lasso、弹性网(Elastic-Net)等。回归问题一般需要对数据有一定的了解,否则所选的模型将会有比较大的偏差。这里我们以最简单的一元一次方程为例,讲解如何使用 sklearn 中的 LinearRegression 函数,采用最小二乘方法解决回归问题。
假设我们的模型是线性模型,满足下式
$$ y=w x $$
那么对于最小二乘法来说,我们的目的就是令观测值和预测值之间的均方误差最小,即
$$ \min_{\omega}\|\mathbf{\nabla}w x-\mathbf{\nabla}y\|_{2}^{2} $$
现在,我们按照 y = 2x 来生成数据,然后加入噪声,并使用 sklearn 中的 LinearRegression 方法求解该回归问题:
a)导入需要的 Python 模块:这里主要用到 numpy、sklearn 和 matplotlib 这三个模块。其中 numpy 用于生成数据,matplotlib 用于画图,sklearn 用于求解回归问题。
b) 使用 numpy 生成变量 x,然后根据 y=2x 生成 y,并加入噪声 $ \varepsilon \in N(0,2) $,即
$$ y=2x+\varepsilon,\quad\varepsilon\in N(0,2) $$
1. # 导入需要的模块
2. import numpy as np
3. from sklearn.linear_model import LinearRegression
4. import matplotlib.pyplot as plt
5.
6. # 生成仿真数据,为保证可重复性,设置随机数 seed 为 1
7. np.random.seed(1)
8. x = np.range(20).reshape((20, 1))
9. y = 2 * x + np.random.normal(loc = 0, scale = 2, size = (20, 1))
c) 调用 sklearn 中的 LinearRegression 类,并使用 fit 方法对生成的 x 和 y 来进行拟合,得到回归器 reg。这里 reg 仍然是一个 LinearRegression 类,并保存了回归得到的各种参数。
d) 得到 LinearRegression 类,并在样本点 x 上应用 predict 函数,求出预测值并绘图比较。
10. # 进行线性拟合
11. reg = LinearRegression().fit(x, y)
12.
13. # 输出预测参数和相关系数
14. print(reg.coef_)
15. print(reg.score(x, y))
16.
17. # 使用线性模型进行预测
18. $ y_{y} = \text{reg.predict}(x) $
19.
20. # 绘制出真实点与预测直线
21. $ \text{plt.scatter}(x, y) $
22. $ \text{plt.plot}(x, y) $
23. $ \text{plt.xlabel("x"))} $
24. $ \text{plt.ylabel("y"))} $
e)通过上面的代码,我们可以得到拟合的参数为1.98,样本点与拟合直线如图14-1所示。


14.2.2 sklearn 中的分类方法使用举例
分类也是一种监督学习的模式识别问题。在 sklearn 中,分类的方法包括前面各章介绍的 K 近邻法、支持向量机、决策树等等。本小节示例使用 sklearn 中的 K 近邻方法,对两类二维正态分布随机数进行分类。具体步骤如下:
$$ m_{2}=[-2,0] $$
a)依据以下均值和协方差信息生成2类二维正态分布随机点,每类随机点各500个。两类的下标分别为1和2,采用相同的协方差矩阵。
$$ m_{1}=[2,0] $$
$$ \mathrm{cov}=\begin{bmatrix}1&0\\ 0&1\end{bmatrix} $$
1. # 导入需要的模块
2. import numpy as np
3. from sklearn.neighbors import KNeighborsClassifier
4. from sklearn.utils import shuffle
5. from sklearn.model_selection import train_test_split
6. from sklearn.metrics import accuracy_score
7. import matplotlib.pyplot as plt
8.
9. # 二维正态分布均值与协方差矩阵
10. mean1 = [2, 0]
11. mean2 = [-2, 0]
12. cov = [[1, 0],
13. [0, 1]]
14.
15. # 产生500个数据点
16. np.random.seed(1)
17. x1 = np.random.multivariate_normal(mean1, cov, (500,))
18. x2 = np.random.multivariate_normal(mean2, cov, (500,))
19.
20. # 绘制散点图
21. plt.scatter(x = x1[:], 0], y = x1[:], 1], c = "b", marker = ".")
22. plt.scatter(x = x2[:], 0], y = x2[:], 1], c = "r", marker = "*")
23. plt.xlim(-6, 6)
24. plt.ylim(-6, 6)
25. plt.xlabel("dimension 1")
26. plt.ylabel("dimension 2")

这里,我们绘制出这两类点在二维空间的分布,第一类用“·”表示,第二类用“★”表示,如图14-2所示。
彩图14-2

上面的图像中,两类点的分布有一部分是交叠在一起的。因此在不知道真实标签的基础上,我们很难区分交叠部分的点究竟属于哪一类,即交叠部分的点不可分。
b)给予这两类点不同的标签,“·”对应的标签为1,“★”对应的标签为“-1”。并把这些点划分为训练集和测试集,使用训练集训练K近邻分类器,并对测试集进行预测,给出分类的正确率。
27. x = np.concatenate((x1, x2), axis=0)
28. y = np.concatenate((np.repeat(1, 500), np.repeat(-1, 500)), axis=0)
29. x, y = shuffle(x, y)
30.
31. # 划分数据集
32. x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3)
33.
34. # 训练 K 近邻模型
35. neigh = KNeighborsClassifier(n_neighbors = 2).fit(x_train, y_train)
36.
37. # 对测试集数据进行分类,并输出分类正确率
38. y = neigh.predict(x_test)
39. print(accuracy_score(y_true = y_test, y_pred = y_))
c)运行上述代码,可以得到在测试集上的正确率为0.97。有兴趣的读者可以尝试计算出理论的错误率,并采用不同的分类器进行测试和比较,判断不同分类器的优劣。
14.2.3 Python 下的深度学习编程举例
TensorFlow 和 PyTorch 都是基于 Python 的深度学习开源平台,或称为深度学习框架,分别由谷歌公司和脸书公司的团队开发,可以用于快速构建深度神经网络。它们集成了基于深度神经网络的训练、验证、预测及可视化等单元,可以实现 GPU 算法加速。深度学习中的典型神经网络,包括全连接神经网络、卷积神经网络、循环神经网络、对抗生成网络等,皆可使用该类模块搭建。同时,用户可以通过调用模块内部的基本类进行自定义深度学习构建。
Keras 早期是另一个独立的机器学习框架,支持众多的深度学习后端(如 TensorFlow、Theano 等),致力于提供用户友好的深度神经网络构建平台。2017 年,谷歌 TensorFlow 团队决定将 Keras 纳入 TensorFlow 核心框架,作为高阶 API 提供给用户。在已经发布的 TensorFlow 2.0 版本中,用户可以直接调用 Keras 进行深度神经网络的构建和训练,而不需要单独安装 Keras 模块。
2018年,数据科学网站KDnuggets发布的调查显示,在深度学习框架方面,TensorFlow和Keras分别占据了29.9%和22.2%的使用率,PyTorch仅占据6.4%。但随着PyTorch的进一步发展,越来越多的科研工作者选择PyTorch作为其生产工具。
在神经网络处理器学习问题的过程中,通常需要进行四个步骤:数据载入和预处理、基本神经网络搭建、损失函数及优化方式设置、网络训练和测试。下面,我们将以 MNIST 手写字符数据集为例,按照上述四个步骤分别说明如何用 Pytorch 和 TensorFlow 这两个框架构建卷积神经网络进行字符识别。
MNIST 是美国标准和技术研究所(NIST)收集的一个手写数字图像数据集,是在模式识别中最常用的数据集之一。数据集包含从 0~9 的手写体数字,共 60 000 个用于训练的样本和 10 000 个用于测试的样本。图 14-3 是数据集中 0~9 的数字图片样例。
我们首先以 Pytorch 为例,说明如何进行基于卷积神经网络的手写体数字分类。
a)数据的载入及预处理:通过 torchvision 载入 MNIST 数据集,通过 DataLoader 构建数据载入方式。
1. # 导入需要的模块
2. import torch
3. import torch.nn as nn
4. import torchvision
5. import torchvision.transforms as transforms

7. # 超参数的设置:涉及之后会使用的一些超参数
8. num_epochs = 5
9. batch_size = 512
10. learning_rate = 0.001
9. batch_size = 512
10. learning_rate = 0.001
11.
12. # CPU/GPU 运行设置:如果该机器已经成功配置 cuda 模块,则运用 gpu 方式运行,否则运用 cpu 方式运行
13. device = torch.device("cuda: 0" if torch.cuda.is_available() else "cpu")
14.
15. # 载入 MNIST 数据集:需要指定数据集的存储目录以及格式转换等参数
16. train_dataset = torchvision.datasets.MNIST(
root = ". /data/" , train = True, transform = transforms.ToTensor(), download = True)
17. test_dataset = torchvision.datasets.MNIST(
root = ". /data/" , train = False, transform = transforms.ToTensor()
)
20.
21. # 配置 Data loader:构建数据的载入方式,一次训练载入一个 batch_size 的数据
22. train_loader = torch.utils.data.DataLoader(
dataset = train_dataset, batch_size = batch_size, shuffle = True)
23. test_loader = torch.utils.data.DataLoader(
dataset = test_dataset, batch_size = batch_size, shuffle = False)
b)基本神经网络的搭建:定义卷积神经网络类ConvNet,该网络由两个自定义卷积模块和一个全连接模块构成。其中卷积模块包括卷积(convolution)层、批量归一化(batch normalization)层、ReLU激活函数层以及最大汇聚(max pooling)层。全连接模块包括一个全连接层,该全连接层的输出节点个数为分类的类别数量,其中输出最大的节点即为该样本所属类别。
26. # 卷积神经网络(两个卷积层与一个全连接层)
27. class ConvNet(nn.Module):
28. def __init__(self, num_classes=10):
29. super(ConvNet, self).__init__(
30. # 定义卷积模块 self_layer1 及 self_layer2
31. self.layer1 = nn.Sequential(
32. nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2), # 输入卷积核个数
为1,输出为16,卷积核大小为5*5,且使用2个单位的边衬(padding).
33. nn.BatchNorm2d(16), # 批量归一化层
34. nn.ReLU(), # 激活函数层
35. nn.MaxPool2d(kernel_size=2, stride=2), # 最大汇聚层
36. }
37. self.layer2 = nn.Sequential(
38. nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2), # 输入卷积核个数为16,输出为32,卷积核大小为5*5,且使用2个单位的边衬(padding).
39. nn.BatchNorm2d(32),
40. nn.ReLU(),
41. nn.MaxPool2d(kernel_size=2, stride=2),
42. }
43. self.fc = nn.Linear(7 * 7 * 32, num_classes) # 定义全连接模块,输出为类别数量
44.
45. def forward(self, x):
46. out = self.layer1(x)
47. out = self.layer2(out)
48. out = out.reshape(out.size(0), -1)
49. out = self.fc(out)
50. return out
51.
52. model = ConvNet().to(device) # model即为搭建的神经网络
c)损失函数及优化方式的设置:由于是多分类问题,采用交叉熵损失 $ ^{①} $(Cross-Entropy Loss);并选用 Adam(adaptive moment estimation)梯度下降算法,对神经网络参数作出优化。
53. # Loss and optimizer
54. criterion = nn.CrossEntropyLoss()
55. optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
d)模型的训练与测试:在训练过程中,需要自定义前向传播过程与梯度反向优化方式;在测试过程中,将图片输入训练好的神经网络中,最大输出即为图片的类别。通过与真实类别相匹配,得到网络分类正确率。经过模型训练,该神经网络在MNIST数据集上获得了98.75%的分类正确率。图14-4给出了几个错分样本的例子,可以看出,除了部分难以区分的样本,网络能基本区分大多数手写体数字。

56. # 训练模型
57. total_step = len(train_loader)
58. for epoch in range(num_epochs):
59. for i, (images, labels) in enumerate(train_loader):
60. images = images.to(device)
61. labels = labels.to(device)
62.
63. # 前向传播
64. outputs = model(images)
65. loss = criterion(outputs, labels)
66.
67. # 反向优化
68. optimizer.zero_grad()
69. loss.backward()
70. optimizer.step()
71.
72. if (i + 1) % 100 == 0: # 每 100 次训练打印一次结果
73. print(
"Epoch [{}/{}], Step [{}/{}], Loss: {: .4f)".format(
epoch + 1, num_epochs, i + 1, total_step, loss.item()
)
74.
75.
76.
77.
78.
79. # 测试模型效果
80. model.eval() # 模型转为测试模式
81. with torch.no_grad():
82. correct = 0
83. total = 0
84. for images, labels in test_loader:
85. images = images.to(device)
86. labels = labels.to(device)
87. outputs = model(images) # 模型的输出
88._, predicted = torch.max(outputs.data, 1) # 图片的预测标签
89. total += labels.size(0)
90. correct += (predicted == labels).sum().item()
91.
92. print(
"Test Accuracy of the model on the 10000 test images: {} %".format(
100 * correct / total
)
96.
下面给出一个基于 Tensorflow 的 MNIST 手写体分类问题示例,其基本逻辑与 Pytorch 版本相同,也将分四个步骤进行讲解:
a)数据的载入及预处理:通过 tf.keras 载入 MNIST 数据集,及通过 tf.data 构建数据载入方式。
1. from __future__ import absolute_import, division, print_function, unicode_literals
2. import tensorflow as tf
3. from tensorflow.keras.layers import Dense, Flatten, Conv2D
4. from tensorflow.keras import Model
5.
6. # 载入数据并进行预处理
7. mnist = tf.keras.datasets.mnist
8. (x_train, y_train), (x_test, y_test) = mnist.load_data()
9. x_train, x_test = x_train / 255.0, x_test / 255.0
10.
11. # 加入一个新的维度
12. x_train = x_train[..., tf.newaxis]
13. x_test = x_test[..., tf.newaxis]
14.
15. # 构建训练与测试数据集
16. train_ds = {
17. tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)
18. }
19. test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
b)损失函数及优化方式的设置:定义卷积神经网络类 Mymodel,该网络由一个卷积层以及两个全连接层构成;卷积层中卷积核的大小为 $ 3 \times 3 $,卷积核数量为 32。
20.并通过tensorflow的顶层APIkeras搭建神经网络
21.class MyModel(Model):
22.def __init__(self):
23. super(MyModel, self).__init__(())
24. self.conv1 = Conv2D(32, 3, activation="relu")
25. self.flatten = Flatten()
26. self.d1 = Dense(128, activation="relu")
27. self.d2 = Dense(10, activation="softmax")
28.
29. def call(self, x):
30. x = self.conv1(x)
31. x = self.flatten(x)
32. x = self.d1(x)
33. return self.d2(x)
34.#类实例化
35.model = MyModel()
c)损失函数与优化方式的设置:这里依旧选用交叉熵为损失函数与 Adam 为优化器;除此之外,我们引入了监测指标,衡量模型的学习效果。
36. # 选择优化器与损失函数
37. loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
38. optimizer = tf.keras.optimizers.Adam()
39.
40. # 选择监测模型效果的指标
41. train_loss = tf.keras.metrics.Mean(name="train_loss")
42. train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name="train_accuracy")
43.
44. test_loss = tf.keras.metrics.Mean(name="test_loss")
45. test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name="test_accuracy")
d) 模型的训练与测试:在训练过程中,通过前向传播计算损失,并根据梯度优化算法进行梯度更新;同时,加入模型损失与正确率两个指标,对训练过程进行监测;测试过程中,同样进行前向传播与计算损失,并通过模型损失与正确率,对测试过程进行监测。经过模型训练,该神经网络在 MNIST 数据集上获得了 98.28% 的分类正确率。
47. def train_step(images, labels):
48. with tf.GradientTape() as tape:
49. predictions = model(images) # 前向传播
50. loss = loss_object(labels, predictions) # 计算损失
51. gradients = tape.gradient(loss, model.trainable_variables) # 计算梯度
52. optimizer.apply_gradients(zip(gradients, model.trainable_variables)) # 梯度优化
53. train_loss(loss) # 监测指标1:模型损失
54. train_accuracy(labels, predictions) # 监测指标2:模型正确率
55.
56. # 模型测试
57. def test_step(images, labels):
58. predictions = model(images) # 测试集输出结果
59. t_loss = loss_object(labels, predictions) # 计算损失
60. test_loss(t_loss) # 监测指标1:模型损失
61. test_accuracy(labels, predictions) # 监测指标2:模型正确率
62.
63. # 执行训练与测试过程
64. EPOCHS = 5
65.
66. for epoch in range(EPOCHS):
67. for images, labels in train_ds:
68. train_step(images, labels) # 模型训练
69.
70. for test_images, test_labels in test_ds:
71. test_step(test_images, test_labels) # 模型测试
72.
73. # 输出训练集与测试集监测指标
74. template = "Epoch {}', Loss: {}', Accuracy: {}', Test Loss: {}', Test Accuracy: {}"
75. print(
76. template.format(
77. epoch + 1,
78. train_loss.result(),
79. train_accuracy.result() * 100,
80. test_loss.result(),
81. test_accuracy.result() * 100,
82. )
83. )
84. # 重置监测指标
85. train_loss.reset_states()
86. train_accuracy.reset_states()
87. test_loss.reset_states()
88. test_accuracy.reset_states()
综上,我们介绍了卷积神经网络在 MNIST 分类问题中的使用。通过使用一种通用深
度学习框架,读者可以完成任意深度网络的构建。有兴趣的读者可以对 TensorFlow 与 PyTorch 作进一步的了解,它们的官方网站分别是 https://www.tensorflow.org/和 https://pytorch.org/。
14.2.4 国内研发的深度学习平台简介
除国外发布的几个深度学习框架之外,近年来国内高校、研究所以及大型互联网企业在深度学习框架的研发方面也做出了不少努力。典型的代表有清华大学计算机系图形学研究室开发的计图(Jittor)深度学习框架,以及由百度公司主导研发的“飞桨”PaddlePaddle框架等。当然,国内的深度学习框架不止上述两种,这里仅就这两个代表性的框架作一简要介绍。
飞桨 PaddlePaddle 是近年来百度公司推出的工业级深度学习框架,于 2016 年正式开源,是中国第一个开源深度学习开发框架,其官网是 https://www.paddlepaddle.org.cn/。该产品前端采用 Python 实现,底层实现则采用 C++,保证运算高效。框架兼容静态图与动态图编程范式,且支持多机并行架构,具有稳定性强,文档完善,功能完备等优势。近年来,百度公司不断更新优化该框架,与众多高校签订了教育合作伙伴计划,已经有很多成功的应用案例。读者也可以从百度百科的飞桨或 PaddlePaddle 词条中获得对该框架更全面的介绍。
计图(Jittor)是清华大学于2020年3月发布的开源的深度学习框架,它采用元算子表达神经网络计算单元,完全基于动态编译。该框架的前端部分采用Python实现,整体语法与PyTorch接近,能够与PyTorch的代码一键转换。该框架将神经网络所需的基本运算定义为元算子,并能够通过互相融合构成深度学习所需的各项运算。另外,该框架融合了静态计算图与动态计算图的诸多优点,提供了高性能的优化策略;与同类型框架相比,Jittor在收敛精度一致情况下,推理速度取得了10%~50%的性能提升。这是中国首个高校自研深度学习训练框架,其未来值得期待。Jittor的介绍网页地址是https://cg.cs.tsinghua.edu.cn/jittor/,GitHub网页地址是https://github.com/Jittor/Jittor,其中包括了比较详细的中英文文档和示例。
14.3 MATLAB 中的模式识别工具包
MATLAB是由MathWorks公司出品的商用数值计算软件,既是有交互式应用界面的计算、分析、展示和仿真软件,也是一种数学计算语言和开发平台,在很多教学、科研和工程中都有广泛的应用。MATLAB的基本数据单元是矩阵,针对矩阵运算进行了大量优化。MATLAB语法简单,可以方便地进行矩阵计算、非线性动态系统的建模和仿真等功能,已经被广泛应用到信号与图像处理、金融建模设计与分析等领域。
MATLAB 中已经集成了很多机器学习方法。本节我们使用 2019a 版本的 MATLAB,给出回归和分类两大机器学习问题的基础示例。
MATLAB 中,模式识别中的主流算法有两种实现方式:一种是依据 MATLAB 内部已
经写好的函数,例如 polyfit、polyval、PCA 等,通过写程序的方式来对数据进行操作;另一种则是利用 MATLAB 中集成好的应用,手动导入数据,采用图形界面对数据进行操作。第一种方式和 Python 中的用法类似,需要读者查阅 MATLAB 的用户手册,找到相应的函数,然后编程实现。我们主要介绍后一种方法。
14.3.1 MATLAB 中的回归方法使用举例
在 MATLAB 的图形界面的最上方,可以看到许多主选项,包括主页、绘图和 APP 等。我们需要的模式识别工具就已经集成在了选项卡 APP 里,如图 14-5 所示。APP 中“机器学习和深度学习”子选项中,涵盖了分类、回归、深度学习网络等模式识别常用应用。

下面以使用 MATLAB 的 Regression Learner 为例说明其应用过程:
a)导入所需的数据。这里选用 MATLAB 中自带的 accidents 数据集为例,该数据集中记录了美国 51 个州的人口数量和事故数量数据。
1. load accidents
2. % 第一列为州人口数据,第二列为州事故数据
3.data = hwdata(: , [14, 4]);
b) 打开 MATLAB 中的 Regression Learner,点击 New Session→From WorkSpace。在 Workspace Variable 中选中变量 data,选择 Use columns as variable,选择回归的响应变量和解释变量:Response 选 column_2,Predictors 中选 column_1,即分别以州人口数据为解释变量(自变量)、州事故数据为响应变量(因变量),右侧选择 No Validation 即不作验证,如图 14-6(a) 所示。随后点击 Start Session。
c) 点击 Start Session 后,会回到 Regression Learner 界面。从该界面中,我们处在一个叫做 Fine Tree 的模型中,而不是回归模型。因此,我们在 LINEAR REGRESSION MODEL 中选择 Linear,如图 14-6(b) 所示。
d) 点击上方的 Train 按钮进行训练,左下角的 Current Model 框中记录了训练的结果。如图 14-6(c) 所示,当前的模型已经训练完成,RMSE 为 336.89,相关系数为 0.84。
e)点击上方的 Predict vs. Actual Plot,可以看到真实值与预测值的图像,如图 14-6(d) 所示。


14.3.2 MATLAB 中的分类方法使用举例
下面用一个简单的例子来介绍如何用 MATLAB 来生成一定的仿真数据、并用其中 APP 保护的分类方法进行分类。
a)生成两个圆盘数据集,如图14-7所示。其中,设圆内部的数据点类别标签为1,环绕它的数据点类别标签为-1。
1.clc, clear
2. % 设置随机数种子
3. ring(1);
4. % 生成第一类数据点
5. r = sqrt(rand(100,1));
6. t = 2 * pi * rand(100,1);
7. data1 = [r. * cos(t), r. * sin(t)];
8.
9. % 生成第二类数据点
10. r2 = sqrt(3 * rand(100,1) + 1);
11. $ \pm2 = 2 \times \pi \times \text{rand}(100,1) $;
12. data2 = [r2. * cos(t2), r2. * sin(t2)];
14. $ \% $ 给两类数据点赋予标签,第一类为1,第二类为-1
13.
15.y = [ones(100,1); -ones(100,1)];
16. data = [[data1; data2], y];
17.
18. % 对数据点进行画图
19. figure;
20. plot(data1(:,1),data1(:,2),'r.','MarkerSize',15)
21. hold on
22. plot(data2(:,1),data2(:,2),'b*','MarkerSize',15)

23.ezpolar(@(x)1);ezpolar(@(x)2);
彩图14-7
24. axis([-2.5, 2.5, -2.5, 2.5])
25. hold off

b) 选取 APP 中的 Classification Learner,并采用 14.3.1 节中方法导入变量 data。变量 data 的前两列为每个数据点的两维信息,最后一列为标签。这里我们选取 5 倍交叉验证,如图 14-8(a) 所示。
c)点击 Start Session,进入 Classification Learner 界面。选择 NEAREST NEIGHBOR
CLASSIFIERS 中的 WEIGHTED KNN 模型,这是 MATLAB 中提供的 k 近邻法的一种改进形式。点击 Train 按钮进行模型的训练。同样,我们可以在左下角看到模型的训练结果,例如测试集正确率为 97%。此外,绘图框中直接画出了我们的数据点,如图 14-8(b) 所示,其中标记为“X”的数据点即在交叉验证中被错分的样本。
有兴趣的读者可以尝试使用多种模型对该数据进行分类,观察正确率的变化。

从这两个例子可以看出,只要理解了所用机器学习方法的原理,用 MATLAB 可以在自己不写代码的情况下对数据进行分析和实验,非常方便。
需要特别注意的是,因为这种软件提供了如此强大的“傻瓜式”应用程序,可以让用户在不了解或很粗浅了解方法原理的情况下就能轻松地使用很多方法进行实验,甚至可能得出有意义的结果,这在一定程度上“毒害”了用户对学习掌握方法原理的积极性,这在表面上看是提高了学习和工作效率,但却埋下了很大的隐患。通过前面各章我们可以看到,机器学习中每一种方法都是在一定的前提下提出来的,各自有其最适合和不适合的应用范围,应用中也有大量需要针对实际问题进行调试或定制的因素,如果在对方法原理没有充分了解的情况下盲目使用傻瓜式软件,有可能导致具有误导性的结果,使研究者得出错误的结论。
彩图14-8

同时还要看到,与14.2节介绍的机器学习平台和语言不同,MATLAB是商业化产品而不是开源软件,用户需要支付高额的使用费,且其使用受到开发商和供应商所在国法律的约束。有一个与MATLAB功能类似的开源软件GNU Octave,它是在GNU开放软件框架下开发的类MATLAB软件,可以替代MATLAB的大多数功能,可以在多种操作系统下运行。由于是完全免费和开源的软件,GNU Octave在界面设计和其他一些性能方面与MATLAB还有一定差距,但它也有占用空间小、同时可以运行MATLAB代码等优势,随着越来越多的人使用开源软件,势必有更大的发展空间。
14.4 R 中的模式识别工具包
R 语言是一种免费的开源计算机语言和软件环境,主要用于统计分析和绘图,在统计学、金融学、医药卫生等领域有极广泛的应用。R 语言起源于早年的一种称为 S 的统计学编程语言,该语言后来被公司买断并开发了与 MATLAB 性质类似的统计计算软件,虽然功能强大、界面友好,但要使用它需要付出高额的使用费。于是,当年在新西兰的两位名字以 R 开头的统计学家和计算机学家 Ross Ihaka 和 Robert Gentleman 就开发了具有相似功能但完全开源免费的 R 语言。经过多年学术界和产业界大量统计学家、软件工程师和机器学习研究者的集体努力,现在 R 语言已经成为在数据科学中最具影响力的软件环境之一。
R平台软件可以从CRAN(The Comprehensive R Archive Network)下载,同时,CRAN也管理着超过10,000个R包,每个R包都和Python的模块一样,有着自己独特的功能。作为一个通用型统计分析语言,基于R语言的Bioconductor平台收录着超过1800款生物数据分析包,极大地方便了众多生物科研人员。
在安装 R 平台软件时,部分核心包会被安装,例如涉及统计分析的 stats,包含有众多数据集的 datasets 等。这保证用户在安装好 R 的同时即可以上手编程进行数据分析。此外,R 语言自身有着包管理框架,可以实现一句命令安装 R 包,方便用户使用。
14.4.1 R 中的回归方法使用举例
R 中有许多包可以实现回归模型,这里以 R 中默认安装的 stats 包中的 lm 函数为例,介绍如何在 R 中实现线性回归:
a)首先,调用 datasets 包中的“cars”数据集作为例子,该数据集中保存的是 19 世纪 20 年代测量的刹车距离和速度的数据。该数据集中包含有两组数据,分别是表示车辆速度的 speed 组和表示刹车距离的 dist 组。
1. # 导入并查看 cars 数据集
2. data(cars)
3. head(cars, n = 5)
4.
5. # cars 数据集部分数据展示
6. # speed dist
7. # 1 4 2
8. # 2 4 10
9. # 3 7 4
10. # 4 7 22
11. # 5 8 16
b)使用 speed 作为自变量,dist 作为因变量,使用 lm 函数对该线性模型进行建模,并使用回归模型对 dist 重新预测。
1. # 通过 lm 构建回归模型,通过~来定义自变量和因变量
2. LR <- lm(dist ~ speed, data = cars)
3.
4. # 输出模型参数
5. print(LR)
6.
7. # 输出结果如下
8. # Call:
9. # lm(formula = dist ~ speed, data = cars)
10. #
11. # Coefficients:
12. # (Intercept) speed
13. # -17.579 3.932
14.
15. # 使用 speed 重新预测 dist
16. dist_pred <- predict(LR, cars)
17.
18. # 绘制 speed 和 dist 的图像
19. plot(x = cars$ speed, y = cars$ dist, type = "p", col = "blue", xlab = "speed", ylab = "dist")
21. lines(x = cars$ speed, y = dist_pred, type = "1", lwd = 2)
c)通过上面的编程实例,可以发现 R 语言的语法十分简洁,一句话就可以实现模型的训练。预测结果如图 14-9 所示。

14.4.2 R 中的分类方法使用举例
R 中集成了众多的分类算法。由于是全球学者共同开发和贡献,所以几乎所有主流的经典模式识别与机器学习算法都有相应的 R 包。作为例子,这里我们使用 rpart 包中的决策树方法在 kyphosis 数据集上对脊柱矫正手术后儿童是否仍会存在驼背进行分类。
a)在使用 rpart 包之前,用户可能需要安装 rpart 和与其相关的绘图工具包 rpart.plot,通过以下命令进行安装:
1. # 通过 install.packages 命令进行包的安装
2. install.packages(c("rpart", "rpart.plot", "caret"))
b)等待进度条完成后,调用已经安装包并导入数据。kyphosis是描述儿童纠正脊柱手术数据集,包含了驼背、年龄、编号、开始时间信息。在R中可以通过关键字“?kyphosis”对该数据集的每一维特征进行了解。
1. # 载入 rpart 包
2. library(rpart)
3. library(rpart.plot)
4. library(caret)
5.
6. # 载入 kyphosis 数据集
7. data(kyphosis)
8. head(kyphosis)
9.
10. # kyphosis 数据集部分数据展示
11. # Kyphosis Age Number Start
12. # 1 absent 71 3 5
13. # 2 absent 158 3 14
14. # 3 present 128 4 5
15. # 4 absent 2 5 1
16. # 5 absent 1 4 15
17. # 6 absent 1 2 16
c)接下来划分数据集并通过 rpart 构建决策树,输出测试集准确率。模型结果以树的形式绘制出来,结果如图 14-10 所示。
1. # 设置随机数种子,划分数据集
2. set.seed(542321)
3. trainIndex <- createDataPartition(kyphosis $ Kyphosis, p = 0.80, list = FALSE)
4.
5. trainData <- kyphosis[trainIndex, drop = FALSE]
6. testData <- kyphosis[- trainIndex, drop = FALSE]
7.
8. # 训练 Tree 模型
9. fit <- rpart(Kyphosis ~ Age + Number + Start,
10. method="class", data = trainData)
11.
12. # 画出树
13. rpart.plot(fit, uniform = TRUE, main="Classification Tree for Kyphosis")
14.
15. # 输出正确率
16. pred <- rpart.predict(fit, testData, type = "class")
17. confMat <- table(testData $ Kyphosis, pred)
18. accuracy <- sum(diag(confMat)) / sum(confMat)
19. print(accuracy)
d)最后得到测试集正确率为80%。从图14-10中可以看到树的深度及每个节点上的区分元素。

14.5 讨论
多种计算机高级语言和各种数学计算软件包的发展,尤其是近年来出现的多种深度学习软件框架,在模式识别和机器学习方法的发展和应用中发挥了重要作用。本章列举了这一领域最常用的几种平台,通过几个简单的示例初步展示类似软件平台的使用方法。由于本书范围和篇幅所限,不能也没有必要对各种平台进行深入介绍。读者在需要时可以利用相关软件平台的文档和网络上可以获取的大量资料进行学习。
需要再次指出的是,机器学习软件平台的发展是一把“双刃剑”,一方面极大地缩短了从方法学习和研究到实际应用的距离,大大推动了机器学习在很多领域中的应用,也大大扩展了机器学习研究者群体的规模;但另一方面,也使真正研究发展机器学习理论和方法的人成为机器学习研究者中的极少数,很多人容易把机器学习的关键误以为是对各种现有软件及其参数调整技巧的掌握,这种倾向不利于机器学习领域的未来发展。
因此,我们建议,模式识别和机器学习的研究者和学生一方面要善于充分利用各种软件平台,另一方面要对模式识别和机器学习相关的理论和方法进行深入的学习和研究,做到知其然更知其所以然,善于发现已有方法在理论和实践上可能存在的问题并加以研究解决。
除了机器学习的软件平台外,人们也认识到,很多机器学习方法的运算具有自身的特点,尤其是各种深度学习方法,它们往往需要庞大的计算量,但同时所涉及的计算类型又相对比较单一。因此,人们已经开始发展针对深度学习方法的专用计算结构和芯片,并探索借用人脑信息处理的机理来设计更高效的类脑计算方法和设备,以及用光电结合的方式来突破现有计算技术的瓶颈。