第 16 章 机器学习

CHAPTER STATUS编写进度30%查看全书进度

机器学习(machine learning, ML)旨在使模型从观测数据中学习潜在的规律,并利用所学习的规律完成预测或结构发现等任务。设一项研究共观测到\(n\)个样本,每个样本由\(p\)个特征(feature)描述。记第\(i\)个样本的特征向量为:

\[\begin{equation*} x_i=(x_{i1},x_{i2},\ldots,x_{ip})^{\top}\in X \end{equation*}\]

其中\(x_{ij}\)表示第\(i\)个样本在第\(j\)个特征上的取值,称\(X\)为特征空间。

根据观测数据是否包含需要预测的目标信息,机器学习方法可以大致分为监督学习(supervised learning)和无监督学习(unsupervised learning)。

在监督学习中,每个特征向量\(x_i\)都对应一个输出结果\(y_i\in Y\),其中\(y_i\)称为第\(i\)个样本的响应变量(response variable),\(Y\)称为输出空间。监督学习的观测数据集记为:

\[\begin{equation*} \mathcal{D}=\{(x_i,y_i)\}_{i=1}^n \end{equation*}\]

将所有样本的输入向量按行排列,可以得到特征矩阵:

\[\begin{equation*} \begin{pmatrix} x_1^{\top} \\ x_2^{\top} \\ \vdots \\ x_n^{\top} \end{pmatrix} = \begin{pmatrix} x_{11}&x_{12}&\cdots&x_{1p} \\ x_{21}&x_{22}&\cdots&x_{2p} \\ \vdots&\vdots&\ddots&\vdots \\ x_{n1}&x_{n2}&\cdots&x_{np} \end{pmatrix} \end{equation*}\]

相应的输出向量记为:

\[\begin{equation*} y=(y_1,y_2,\dots,y_n)^{\top} \end{equation*}\]

监督学习的目标是利用观测数据集\(\mathcal{D}\)学习一个从特征空间\(X\)到输出空间\(Y\)的预测函数:

\[\begin{equation*} f:X\longrightarrow Y \end{equation*}\]

使得对于观测数据中未出现的新输入\(x\in X\),模型能够给出相应的预测\(f(x)\)。

例如,考虑利用血常规检查结果辅助判断受检者是否患有某种疾病。对于每位既往患者,我们既记录其白细胞计数、中性粒细胞比例、血红蛋白浓度和血小板计数等血常规指标,又记录医生结合临床表现、血常规结果及其他检查所确定的诊断结果。于是,每位患者的血常规指标构成一个特征向量\(x_i\),其临床诊断结果构成相应的输出变量\(y_i\)。收集一批既往患者的数据后,可以得到监督学习的观测数据集\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^n\)。模型利用这些已知诊断结果的历史数据,学习血常规指标与患病状态之间的潜在关系。当获得一位新受检者的血常规数据时,便可以将其作为新的输入,由模型预测该受检者是否可能患病,从而为医生的临床判断提供辅助信息。表16.1给出了一个简化的观测数据集示例。

表 16.1 基于血常规指标判断是否患病的监督学习数据示例
样本 白细胞计数 中性粒细胞比例 血红蛋白 血小板计数 是否患病
\(1\) \(6.2\) \(58\) \(145\) \(245\) 否
\(2\) \(12.8\) \(82\) \(132\) \(310\) 是
\(3\) \(5.7\) \(54\) \(151\) \(228\) 否
\(4\) \(14.1\) \(86\) \(126\) \(335\) 是
\(5\) \(7.0\) \(61\) \(139\) \(260\) 否
\(6\) \(11.6\) \(79\) \(128\) \(298\) 是

在无监督学习中,观测数据不包含预先给定的输出变量,因而观测数据集记为:

\[\begin{equation*} \mathcal{D}=\{x_i\}_{i=1}^n \end{equation*}\]

无监督学习的目标不是预测某个已知的输出,而是从输入数据中发现潜在的结构或模式。例如,可以根据样本之间的相似性将其划分为若干组,或者使用较少的变量概括原始数据中的主要信息。

在监督学习中,根据输出空间\(Y\)的类型,可以进一步将问题分为分类问题和回归问题。

当响应变量只取有限个离散类别时,该问题称为分类问题。设所有可能的类别为\(Y=\{1,2,\dots,K\}\),其中\(K\)为类别数。分类模型需要根据特征判断一个样本所属的类别。例如,根据邮件内容判断其是否为垃圾邮件,或者根据医学检查结果判断患者是否患病。此时我们又称第\(i\)个样本的响应变量\(y_i\)为其标签(label)。

当响应变量为连续数值时,该问题称为回归问题,通常有\(Y\subseteq\mathbb{R}\)。回归模型需要学习特征与数值型输出之间的关系。例如,根据房屋面积和位置预测房价,或者根据患者的临床指标预测某项连续的生理数值。

分类问题在形式上也可以转化为回归问题:模型首先根据样本特征输出一个连续的分类得分,再根据预先设定的阈值确定样本所属的类别。例如,可以将分类得分大于\(0\)的样本判为一类,将分类得分小于或等于\(0\)的样本判为另一类。

16.1 模型评价

在监督学习中,模型评价是衡量模型预测能力、比较不同模型并指导模型选择的重要环节。

16.1.1 分类任务

分类问题可以根据类别数划分为二分类问题与多分类问题,二分类问题即\(K=2\)的分类问题,多分类问题对应\(K\geqslant3\)的分类问题。

16.1.1.1 二分类

在二分类问题中,通常将研究中重点关注、希望模型识别出的类别称为正类,将另一个类别称为负类。

Definition 16.1. 混淆矩阵(confusion matrix)按真实标签与预测标签的组合统计样本个数,其每个元素含义如下:

表 16.2 二分类问题的混淆矩阵
真实类别\(\backslash\)预测类别 正类(\(\hat{y}=1\)) 负类(\(\hat{y}=0\))
正类(\(y=1\)) TP FN
负类(\(y=0\)) FP TN

Definition 16.2. 准确率(accuracy)度量模型预测正确的比例:

\[\begin{equation*} \operatorname{Accuracy}=\frac{\operatorname{TP}+\operatorname{TN}}{\operatorname{TP}+\operatorname{TN}+\operatorname{FP}+\operatorname{FN}} \end{equation*}\]

Accuracy 将正类和负类的正确预测汇总为一个整体比例,因而默认两类样本具有相同的重要性,并且不同类型的分类错误具有相同的代价。然而在许多实际问题中,假正类和假负类造成的损失并不相同,我们也可能更关注模型在某一特定类别上的预测表现。

例如,在贷款违约预测中,若将违约客户定义为正类,则将实际违约的客户预测为不违约会使银行承担贷款损失,而将实际不违约的客户预测为违约主要意味着失去一次潜在的放贷机会。这两类错误的经济代价并不相同。因此,即使两个模型具有相同的 Accuracy,它们识别违约客户的能力也可能存在显著差异。为了考察模型针对特定类别的表现,需要进一步引入别的指标。

Definition 16.3. 精确率(precision)度量被预测为正类的样本中有多少是真正的正类:

\[\begin{equation*} \operatorname{Precision}=\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FP}} \end{equation*}\]

note 16.1. 精确率关心正类预测结果的准确性,在系统对错误正类具有高成本的任务(如垃圾邮件过滤)中尤为重要。较高的精确率意味着模型产生的误报较少。没有任何样本被预测为正样本时我们一般约定精确率为\(0\)。

Definition 16.4. 召回率(recall)又称灵敏度(sensitivity),衡量真实正类中有多少被模型成功识别,定义为:

\[\begin{equation*} \operatorname{Recall}=\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FN}} \end{equation*}\]

note 16.2. 召回率关注对正类的漏判率,适用于需要尽可能检出正类的场景(如疾病筛查或欺诈检测)。高召回率意味着漏报较少,但可能伴随较多假阳性,需要结合精确率来进行分析。

Definition 16.5. F1分数(F1 score)是精确率与召回率的调和平均,用于在二者之间取得平衡:

\[\begin{equation*} \operatorname{F1}=2\times\frac{\operatorname{Precision}\times\operatorname{Recall}}{\operatorname{Precision}+\operatorname{Recall}} \end{equation*}\]

note 16.3. 调和平均的性质使\(\operatorname{F1}\)得分对低值敏感——当精确率或召回率其中之一很低时,\(\operatorname{F1}\)会显著下降,因此它常用作综合评价指标,适用于精确率和召回率同等重要的情形。

Definition 16.6. \(\operatorname{F}_{\beta}\)分数是\(\operatorname{F1}\)得分的推广,通过参数\(\beta>0\)调节精确率与召回率的重要性:

\[\begin{equation*} \operatorname{F}_{\beta}=(1+\beta^2)\frac{\operatorname{Precision}\times\operatorname{Recall}}{\beta^2\operatorname{Precision}+\operatorname{Recall}}=\dfrac{1}{\dfrac{\beta^2}{1+\beta^2}\dfrac{1}{\operatorname{Recall}}+\dfrac{1}{1+\beta^2}\dfrac{1}{\operatorname{Precision}}} \end{equation*}\]

note 16.4. 当\(\beta=1\)时,\(\operatorname{F}_{\beta}\)退化为\(\operatorname{F1}\);当\(\beta>1\)时,召回率具有更大的权重,适用于更关注漏检的任务;当\(0<\beta<1\)时,精确率具有更大的权重,适用于更关注误报的任务。因此,相比固定地给予精确率与召回率相同权重的\(\operatorname{F1}\),\(\operatorname{F}_{\beta}\)能够根据具体任务中两类错误的重要程度进行调整。

许多分类器并不直接输出类别,而是为每个样本输出一个连续得分或属于正类的概率,并在给定阈值后将其转化为分类结果。然而,分类阈值并不存在适用于所有任务的固定选择,即使模型输出的是概率,也不能机械地将\(0.5\)作为阈值。阈值的选择取决于正类的基础发生率、假正类与假负类的相对代价以及模型所服务的具体决策目标。

以罕见病筛查为例,假设某种疾病在人群中的患病率仅为\(0.1\%\)。若模型预测某位受检者的患病概率为\(10\%\),那么其患病风险已经达到普通人群的\(100\) 倍,显然应当被识别为高风险个体并接受进一步检查。然而,\(10\%\)仍然低于\(50\%\);若机械地将\(0.5\)作为分类阈值,该受检者就会被判定为阴性。类似地,在身份认证或银行人脸识别中,系统可能更重视防止冒用身份,因此会采用更加严格的判定阈值,以降低错误接受非本人的概率。

由此可见,同一个模型在不同应用场景中可能需要采用完全不同的阈值,而模型在某一个固定阈值下的表现不能完整反映其分类能力。为了减少评价结果对特定阈值选择的依赖,可以考察阈值连续变化时模型性能的变化,并将其表示为性能曲线。我们希望一个模型在较广的阈值范围内均能保持较好的正负类区分能力,因此可以进一步使用曲线下面积评价模型的综合表现。

Definition 16.7. 称:

\[\begin{equation*} \operatorname{TPR}=\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FN}},\quad\operatorname{FPR}=\frac{\operatorname{FP}}{\operatorname{FP}+\operatorname{TN}} \end{equation*}\]

分别为真阳率(true positive rate, TPR)与假阳率(false positive rate, FPR)。

Definition 16.8. 设分类器将得分不低于阈值\(t\)的样本预测为正类。受试者工作特征曲线(receiver operating characteristic curve, ROC)为在阈值取值范围内连续变化\(t\),对每个\(t\)计算对应的:

\[\begin{equation*} \operatorname{TPR}(t)=\frac{\operatorname{TP}(t)}{\operatorname{TP}(t)+\operatorname{FN}(t)},\quad\operatorname{FPR}(t)=\frac{\operatorname{FP}(t)}{\operatorname{FP}(t)+\operatorname{TN}(t)} \end{equation*}\]

然后以\(\mathrm{FPR}\)为横轴、\(\mathrm{TPR}\)为纵轴作图得到的曲线。受试者工作特征曲线下面积(area under ROC, AUROC)定义为ROC曲线与\(\mathrm{FPR}\)轴和\(\mathrm{FPR}=1\)围成区域的面积。

note 16.5. ROC曲线反映了阈值变化对\(\operatorname{TPR}\)与\(\operatorname{FPR}\)的权衡。

\(\operatorname{AUROC}\)独立于具体阈值,是比较模型总体区分能力的常用指标,取值通常在\([0.5,1]\)。当阈值取到最低时,所有样本均被判定为正类,此时\(\operatorname{TPR}=\operatorname{FPR}=1\);随着阈值逐渐升高,被判定为正类的样本不断减少,\(\operatorname{TPR}\)与\(\operatorname{FPR}\)均逐渐下降;当阈值高于所有样本的预测得分时,所有样本均被判定为负类,此时\(\operatorname{TPR}=\operatorname{FPR}=0\)。因此ROC曲线总是连接\((0,0)\)与\((1,1)\)。

当模型没有区分正负样本的能力时,对任意阈值\(t\)都有\(\operatorname{TPR}(t)=\operatorname{FPR}(t)\),此时ROC曲线为连接\((0,0)\)与\((1,1)\)的对角线,其下方面积为\(0.5\),对应随机猜测。若模型能够完美区分正负样本,则存在阈值使所有正样本都被判为正类,而所有负样本仍被判为负类,此时ROC曲线经过\((0,1)\)。继续降低阈值时,\(\operatorname{TPR}\)自然保持为\(1\),而\(\operatorname{FPR}\)会逐渐由\(0\)增加至\(1\),因此曲线沿\(\operatorname{TPR}=1\)从\((0,1)\)移动至\((1,1)\),曲线下面积达到最大值\(1\)。

TikZ 数学图示
图 16.1 ROC曲线示意

Definition 16.9. 设模型将得分不低于阈值 \(t\) 的样本预测为正类。精确率-召回率曲线(precision-recall curve, PRC)为在阈值取值范围内内连续变化\(t\),对每个\(t\)计算对应的\(\operatorname{Precision}(t)\)和\(\operatorname{Recall}(t)\),然后以\(\mathrm{Recall}\)为横轴、\(\mathrm{Precision}\)为纵轴作图得到的曲线。精确率-召回率曲线下面积(area under precision-recall curve, AUPRC)定义为PRC曲线与\(\operatorname{Recall}\)轴之间区域的面积。

note 16.6. PR曲线描述分类阈值变化时\(\operatorname{Recall}\)与\(\operatorname{Precision}\)之间的权衡,因此相比于\(\operatorname{AUROC}\),\(\operatorname{AUPRC}\)更关注正类的识别表现。当阈值取到最高时,所有样本都被判定为负类,此时\(\operatorname{Recall}=0\),\(\operatorname{Precision}\)在严格意义下未定义,作图时通常将该点记为\((0,1)\)。随着阈值逐渐降低,被判为正类的样本不断增多,\(\operatorname{Recall}\)单调上升,\(\operatorname{Precision}\)因为新纳入的预测正类中混入了负样本通常会逐渐下降或发生波动。当阈值取到最低时,所有样本均被判为正类,此时\(\operatorname{Recall}=1\),而\(\operatorname{Precision}\)等于数据中正类所占的比例。因此,PR曲线通常由左上方向右下方延伸。

当模型没有区分能力时,对任意阈值\(t\),预测为正类的样本中真正类所占的比例都应大致等于总体中的正类比例,PR曲线近似为一条水平直线,其纵坐标等于数据集中正样本所占的比例,\(\operatorname{AUPRC}\)也接近正类比例。若模型能够完美区分正负样本,则随着阈值逐渐降低、召回率由\(0\)增加到\(1\)的整个过程中,被判为正类的样本始终都是真正类,即\(\operatorname{Precision}\)始终保持为\(1\),于是理想的PR曲线就是\(\operatorname{Precision}=1\)这条水平直线,此时\(\operatorname{AUPRC}=1\)。

TikZ 数学图示
图 16.2 PR曲线示意

对于能够输出预测概率的分类模型,模型性能至少涉及两个不同方面:一方面,模型应具有良好的排序能力,即正类样本通常获得高于负类样本的预测分数;另一方面,模型给出的预测概率还应具有可靠的概率解释。良好的排序能力并不意味着预测概率本身准确。例如,假设某模型的预测概率已经具有良好的概率解释:对于预测概率约为\(0.2\)、\(0.5\)和\(0.8\)的样本,其中实际属于正类的比例也分别约为\(20\%\)、\(50\%\)和\(80\%\)。现在对所有预测概率作平方变换,则上述预测概率分别变为\(0.04\)、\(0.25\)和\(0.64\)。由于该变换不会改变任意两个样本预测分数的大小关系,模型对样本的排序完全不变,因此在合适的阈值选择下模型的预测能力不会改变,但是预测概率已经失去了正确的概率解释。因此,还需要进一步考察预测概率与实际事件发生频率之间的一致程度。

Definition 16.10. 设分类模型对样本\(x_i\)给出的正类预测概率为\(\hat{p}_i\)。将预测概率的取值范围划分为若干区间\(B_1, B_2, \dots, B_{K}\),对于每个区间\(B_k\),分别计算其中样本被预测为正类的概率的平均值以及真实正类比例:

\[\begin{equation*} \operatorname{Pred}(B_k)=\frac{1}{|B_k|}\sum_{i:\hat{p}_i\in B_k}\hat{p}_i,\quad\operatorname{Obs}(B_k)=\frac{1}{|B_k|}\sum_{i:\hat{p}_i\in B_k}y_i \end{equation*}\]

其中\(|B_k|\)表示落入第\(k\)个概率区间\(B_k\)中的样本数量。校准曲线(calibration curve)为以\(\operatorname{Pred}(B_k)\)为横轴、\(\operatorname{Obs}(B_k)\)为纵轴作图得到的曲线。

note 16.7. 理想情况下校准曲线应与直线\(y=x\)重合。若校准曲线位于\(y=x\)下方,则说明模型给出的预测概率整体偏高,反之模型给出的预测概率整体偏低。

TikZ 数学图示
图 16.3 校准曲线示意

16.1.1.2 多分类

与二分类问题不同,多分类问题中不存在唯一的正类与负类,因此通常需要先分别考察模型在每个类别上的表现,再通过适当的平均方式得到整体评价指标。

对于任意类别\(k\in\{1,2,\dots,K\}(K\geqslant3)\),可将类别\(k\)视为正类,将其余类别合并为负类,并按照二分类问题中的定义计算\(\operatorname{TP}_k\)、\(\operatorname{FP}_k\)、\(\operatorname{FN}_k\)和\(\operatorname{TN}_k\),进而得到该类别的\(\operatorname{Precision}_k\)、\(\operatorname{Recall}_k\)和\(\operatorname{F1}_k\)。这种处理方式称为one-versus-rest。

分别报告每个类别的评价指标能够完整反映模型在各类别上的表现,但当类别数量较多或需要使用单一数值比较模型时,需要进一步对各类别的指标进行汇总。常用的汇总方式包括Macro平均、Weighted平均和Micro平均。

Definition 16.11. 设\(m_k\)表示类别\(k\)的某个评价指标,该指标的Macro平均定义为:

\[\begin{equation*} m_{\mathrm{macro}}=\frac{1}{K}\sum_{k=1}^{K}m_k \end{equation*}\]

Definition 16.12. 设\(m_k\)表示类别\(k\)的某个评价指标,类别\(k\)的真实样本数为\(n_k\),该指标的Weighted平均定义为:

\[\begin{equation*} m_{\mathrm{weighted}}=\sum_{k=1}^{K}\frac{n_k}{n}m_k \end{equation*}\]

Definition 16.13. Micro平均定义为:

\[\begin{gather*} \operatorname{Precision}_{\mathrm{micro}}=\frac{\sum\limits_{k=1}^{K}\operatorname{TP}_k}{\sum\limits_{k=1}^{K}(\operatorname{TP}_k+\operatorname{FP}_k)} \\ \operatorname{Recall}_{\mathrm{micro}}=\frac{\sum\limits_{k=1}^{K}\operatorname{TP}_k}{\sum\limits_{k=1}^{K}(\operatorname{TP}_k+\operatorname{FN}_k)} \\ \operatorname{F1}_{\mathrm{micro}}=2\times\frac{\operatorname{Precision}_{\mathrm{micro}}\operatorname{Recall}_{\mathrm{micro}}}{\operatorname{Precision}_{\mathrm{micro}}+\operatorname{Recall}_{\mathrm{micro}}} \end{gather*}\]

note 16.8. 在每个样本只有一个真实标签且模型只预测一个类别的单标签多分类问题中,每个预测正确的样本产生一个真阳性,而每个预测错误的样本同时产生一个假阳性和一个假阴性。因此:

\[\begin{equation*} \sum_{k=1}^{K}\operatorname{FP}_k=\sum_{k=1}^{K}\operatorname{FN}_k=n-\sum_{k=1}^{K}\operatorname{TP}_k \end{equation*}\]

从而:

\[\begin{equation*} \operatorname{Precision}_{\mathrm{micro}}=\operatorname{Recall}_{\mathrm{micro}}=\operatorname{F1}_{\mathrm{micro}}=\operatorname{Accuracy} \end{equation*}\]

所以在多分类问题中,同时报告Accuracy和Micro通常不会提供额外信息。

若希望所有类别具有相同的重要性,应重点考察Macro平均;若希望评价结果反映实际类别分布,可以使用Weighted平均;若希望从所有样本层面汇总分类结果,则可以使用Micro平均。

16.1.2 回归任务

设真实值为\(\{y_i\}_{i=1}^{n}\),模型预测值为\(\{\hat{y}_i\}_{i=1}^{n}\)。

Definition 16.14. 称:

\[\begin{equation*} \operatorname{MAE}=\frac{1}{n}\sum_{i=1}^n|y_i-\hat{y}_i| \end{equation*}\]

为平均绝对误差(mean absolute error, MAE)。

note 16.9. MAE计算预测误差的绝对值并取平均,因而对所有误差赋予相同的线性权重。它具有易于解释、与原数据同量纲等优点,并对单个异常值不敏感。

Definition 16.15. 称:

\[\begin{equation*} \operatorname{MAPE}=\frac{1}{n}\sum_{i=1}^n\left|\frac{y_i-\hat{y}_i}{y_i}\right| \end{equation*}\]

为平均绝对百分比误差(mean absolute percentage error, MAPE)。

note 16.10. MAPE 将误差按照真实值比例归一化,适合对预测误差的相对大小进行衡量。

16.2 模型训练

16.2.1 训练目标与经验风险

模型训练的目的,是从给定的候选模型中选出一个能够在未知样本上取得良好预测表现的模型。为此,首先需要根据具体任务明确模型表现的评价标准。例如,在分类任务中,可以关注总体分类准确率,也可以更重视某一类别的召回率、精确率或者误分类成本。不同的评价标准体现了不同的任务需求,并决定了哪些预测结果应被认为更好、哪些预测错误应受到更大的惩罚。

评价指标主要用于衡量训练完成后的模型是否满足任务需求,但它未必适合直接作为训练时的优化目标。许多评价指标是不连续的、不可微的,或者不能分解为各个样本损失的平均。例如,分类准确率仅取决于最终预测类别是否正确。当模型的预测分数发生小幅变化但预测类别保持不变时,准确率也保持不变,因此它通常无法提供参数应当如何更新的信息。

为便于优化,训练时通常使用一个对模型输出变化更加敏感的损失函数作为代理目标。损失函数不必与评价指标具有相同的表达式,但应当与任务目标相容,即损失的减小通常能够促进所关心的预测性能改善。因此,评价指标决定模型最终应达到的目标,损失函数则将这一目标转化为可以进行参数优化的数学问题。

设观测数据集\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\)由分布\(P\)中的\(n\)个独立同分布样本构成,候选模型组成函数族:

\[\begin{equation*} \mathcal{F}=\{f_\theta\mid f_\theta:X\to\mathcal{A},\;\theta\in\Theta\} \end{equation*}\]

其中\(\mathcal{A}\)为模型输出空间,\(\Theta\)为参数空间。沿用点估计中的记号,记\(L:Y\times\mathcal{A}\to[0,+\infty]\)为损失函数,则\(L[y,f_\theta(x)]\)表示模型\(f_\theta\)在样本\((x,y)\)上产生的预测损失。若分布\(P\)已知,则模型\(f_\theta\)在来自同一分布的样本上的平均预测损失可以表示为:

\[\begin{equation*} R(\theta)\coloneq\operatorname{E}(L) \end{equation*}\]

称\(R(\theta)\)为模型\(f_\theta\)的总体风险。总体风险越小,表示模型在未知样本上产生的平均预测损失越小。因此,从预测角度看,理想的参数应满足:

\[\begin{equation*} \theta^\ast\in\underset{\theta\in\Theta}{\arg\min}\;R(\theta) \end{equation*}\]

然而,分布\(P\)通常未知,因此总体风险\(R(\theta)\)无法直接计算。训练时只能利用观测数据,以样本平均损失近似总体风险。

Definition 16.16. 称:

\[\begin{equation*} \hat{R}(\theta)\coloneq\frac{1}{n}\sum_{i=1}^{n}L[y_i,f_\theta(x_i)] \end{equation*}\]

为模型\(f_\theta\)在观测数据集\(\mathcal{D}\)上的经验风险(empirical risk)。

以经验风险作为训练目标时,模型训练可以表示为:

\[\begin{equation*} \hat{\theta}\in\underset{\theta\in\Theta}{\arg\min}\;\hat{R}(\theta) \end{equation*}\]

这种通过最小化经验风险选择模型参数的方法称为经验风险最小化。由于\(\hat{R}(\theta)\)依赖于随机抽取的观测数据,因此所得参数\(\hat{\theta}\)也是数据的函数。

16.2.1.1 例子

下面以分类任务为例,说明如何从模型希望达到的预测效果出发构造损失函数。设类别集合为\(Y=\{1,2,\dots,K\}\),分类模型对输入\(x\)输出一个概率向量:

\[\begin{equation*} q_\theta(x)=(q_{\theta1}(x),q_{\theta2}(x),\dots,q_{\theta K}(x))^{\top},\quad q_{\theta k}(x)>0,\quad\sum_{k=1}^{K}q_{\theta k}(x)=1 \end{equation*}\]

其中,\(q_{\theta k}(x)\)表示模型认为样本\(x\)属于第\(k\)类的概率。对于训练样本\((x_i,y_i)\),我们希望模型预测的准确率高,也即赋给真实类别\(y_i\)的概率\(q_{\theta y_i}(x_i)\)尽可能接近\(1\)。

由于训练样本相互独立,模型在所有样本上为真实类别给出的概率可以合并为乘积:

\[\begin{equation*} Q_{\mathcal{D}}(\theta)\coloneq\prod_{i=1}^{n}q_{\theta y_i}(x_i) \end{equation*}\]

这个乘积越大,说明模型为观测到的真实类别整体赋予的概率越大。最大化\(Q_{\mathcal{D}}(\theta)\)等价于最小化:

\[\begin{equation*} -\frac{1}{n}\ln Q_{\mathcal{D}}(\theta)=-\frac{1}{n}\sum_{i=1}^{n}\ln q_{\theta y_i}(x_i) \end{equation*}\]

由此可以把单个样本的损失定义为:

\[\begin{equation*} L[y_i,q_\theta(x_i)]\coloneq-\ln q_{\theta y_i}(x_i) \end{equation*}\]

称该损失为逐样本的交叉熵(cross entropy)损失。当\(q_{\theta y_i}(x_i)\)接近\(1\)时,该损失接近\(0\);当\(q_{\theta y_i}(x_i)\)接近\(0\)时,该损失趋于\(+\infty\)。因此,模型不仅会因预测类别错误而受到惩罚,还会因确信程度不足而产生较大的损失。相应的经验风险为:

\[\begin{equation*} \hat{R}(\theta)\coloneq\frac{1}{n}\sum_{i=1}^{n}L[y_i,q_\theta(x_i)]=-\frac{1}{n}\sum_{i=1}^{n}\ln q_{\theta y_i}(x_i) \end{equation*}\]

16.2.2 过拟合

经验风险\(\hat{R}(\theta)\)只度量模型在观测数据集上的平均损失,总体风险\(R(\theta)\)则度量模型对同一分布中新样本的平均损失,二者通常并不相等。

有时持续降低\(\hat{R}(\theta)\)未必会同时降低\(R(\theta)\):模型可能不是在提取能够推广到新样本的稳定规律,而是利用观测数据中的偶然噪声、异常点甚至样本的细微特征,对有限训练集作近似记忆。此时训练损失较低,而新样本上的损失较高,这种现象称为过拟合(overfitting)。

TikZ 数学图示
图 16.4 取自y = x的三个样本可由不同的模型进行解释。

从图 16.4可以看出,三条复杂程度不同的曲线都完美地拟合了由三个观测点构成的观测数据集,因此它们在该观测集上取得了相同且最小的经验风险\(\hat{R}(\theta)\)。然而,在训练样本之外,若新的观测仍来自真实关系\(y=x\),这些模型会产生较大的预测误差,即它们存在过拟合的现象。由此可见,仅凭观测数据上的经验风险无法判断模型的真实预测能力;即使多个模型具有相同的训练误差,它们的总体风险\(R(\theta)\)也可能存在显著差异。

综上,我们需要关注差值:

\[\begin{equation*} R(\theta)-\hat{R}(\theta) \end{equation*}\]

它反映了模型从观测数据推广到新数据时的性能差异。

16.2.2.1 训练集、验证集与测试集

计算上述差值需要计算\(R(\theta)\),但是由于分布\(P\)未知,\(R(\theta)\)通常无法直接计算,只能通过未参与训练的数据加以估计,所以我们通常将原始数据随机划分为三个互不相交的部分:

\[\begin{equation*} \mathcal{D}=\mathcal{D}_{\mathrm{train}}\cup\mathcal{D}_{\mathrm{val}}\cup\mathcal{D}_{\mathrm{test}} \end{equation*}\]

分别称为训练集(training set)、验证集(validation set)和测试集(test set)。三者应当尽可能来自相同的目标分布;对于类别不平衡的分类问题,通常采用分层抽样,使各部分的类别比例大致一致。

Method 16.1. 数据集划分后的模型训练与选择通常按下列顺序进行:

  1. 在训练集\(\mathcal{D}_{\mathrm{train}}\)上估计模型的可学习参数\(\theta\);

  2. 在验证集\(\mathcal{D}_{\mathrm{val}}\)上比较不同模型的性能,并据此选择模型;

  3. 确定全部建模方案后,只在测试集\(\mathcal{D}_{\mathrm{test}}\)上进行一次最终评价,报告模型对未见数据的性能估计。

模型训练是在一个已经给定的函数族中确定模型的具体参数。例如,若我们预先规定模型具有形式\(y=kx+b\),那么训练过程所做的事情是利用训练集在所有可能的\((k,b)\)中寻找一组使训练损失尽可能小的参数。然而在实际建模中,我们通常并不知道哪一种模型形式最合适,因此往往还需要同时考虑多个形式。例如,我们可能考虑以下三种形式:

\[\begin{equation*} y=kx+b,\quad y=kx^2+b,\quad y=kx^3+b \end{equation*}\]

对于每一个形式,我们首先分别利用训练集求出其中表现最好的模型:

\[\begin{equation*} y=k_1x+b_1,\quad y=k_2x^2+b_2,\quad y=k_3x^3+b_3 \end{equation*}\]

随后再利用验证集比较这三个已经训练好的模型,据此决定最终模型。

验证集虽然不参与参数的直接拟合,却参与模型选择;若反复根据测试集结果修改模型,测试集实际上也参与了训练流程,其评价结果便会过于乐观。因此,测试集必须与整个训练和选择过程隔离。数据预处理也应遵循相同原则:缺失值填补、标准化、特征选择等步骤只能在训练集上估计所需参数,再将已经确定的变换应用到验证集和测试集,不能提前利用后两者的信息。

当样本量较小时,如果再从训练数据中单独划分一个固定的验证集会进一步减少模型实际用于训练的样本。此时可以在训练数据内部使用\(K\)折交叉验证(cross validation, CV):将训练数据近似均匀地分为\(K\)份,依次将其中一份作为验证集,其余\(K-1\)份作为训练集,共进行\(K\)轮训练与验证,并对\(K\)次验证结果取平均,以评价候选模型。此时仍应保留独立测试集;交叉验证用于模型选择,测试集只用于最终评价。

16.2.2.2 正则化与模型空间约束

划分数据集能够帮助我们评估模型在未参与训练的数据上的表现,从而判断模型是否出现了过拟合,但这种做法本身只能用于识别过拟合,而不能从训练机制上加以预防。为了进一步理解如何控制过拟合,下面我们分析过拟合产生的原因,并据此介绍相应的预防方法。

对\(f=f_\theta\in\mathcal{F}\),记\(\hat{R}_{\mathrm{train}}(f)\coloneq\hat{R}_{\mathrm{train}}(\theta)\)及\(R(f)\coloneq R(\theta)\)。

若\(\mathcal{F}_1\subseteq\mathcal{F}_2\),则:

\[\begin{equation*} \inf_{f\in\mathcal{F}_2}\hat{R}_{\mathrm{train}}(f)\leqslant\inf_{f\in\mathcal{F}_1}\hat{R}_{\mathrm{train}}(f) \end{equation*}\]

也就是说,扩大模型空间不会使最优训练风险变大,因为新增的模型为拟合训练数据提供了更多选择;但总体风险并不具有相同的单调性。模型空间越大,训练过程越容易从大量候选模型中找到一个恰好迎合当前样本偶然性的模型,因而经验风险虽然可以很低,经验风险与总体风险之间的偏差却更难控制。与此同时,一个足够复杂的模型可以记住每个训练样本,使训练风险接近\(0\);但训练样本之外的函数取值仍可能几乎不受约束。

Definition 16.17. 设\(\Omega:\mathcal{F}\to[0,+\infty]\)为模型复杂度函数,\(\Omega(f)\)越大表示模型\(f\)越复杂。对\(c\geqslant0\),定义受限模型空间:

\[\begin{equation*} \mathcal{F}_c\coloneq\{f\in\mathcal{F}:\Omega(f)\leqslant c\} \end{equation*}\]

在\(\mathcal{F}_c\)中最小化经验风险:

\[\begin{equation*} \hat{f}_c\in\underset{f\in\mathcal{F}_c}{\arg\min}\;\hat{R}_{\mathrm{train}}(f) \end{equation*}\]

称为约束形式的正则化(regularization)。其中,\(c\)称为复杂度上界。

正则化通过缩小模型空间并限制模型复杂度来降低出现过拟合现象的可能性。实际计算中更常使用惩罚形式:

\[\begin{equation*} \hat{f}_\lambda\in\underset{f\in\mathcal{F}}{\arg\min}\left\{\hat{R}_{\mathrm{train}}(f)+\lambda\Omega(f)\right\},\;\lambda\geqslant0 \end{equation*}\]

其中,\(\hat{R}_{\mathrm{train}}(f)+\lambda\Omega(f)\)称为正则化目标函数,预先给定的常数\(\lambda\)称为正则化强度(regularization strength)。当\(\lambda=0\)时,该问题退化为原来的经验风险最小化;\(\lambda\)越大,模型复杂度在目标函数中付出的代价越大,训练过程便越倾向于选择简单模型。

note 16.11. 限制模型空间的本质是在拟合训练数据与控制模型复杂度避免过拟合之间取得平衡:模型空间过小会导致模型无法很好的拟合训练数据,也即欠拟合(underfitting),模型空间过大会导致过拟合。

当多个模型都能达到同样好的性能表现时,应优先选择假设更少、模型复杂度最低的那一个,这一原则被称之为奥卡姆剃刀原则(Occam's razor)。

16.3 树模型

16.3.1 信息度量与分裂准则

16.3.1.1 信息量

Definition 16.18. 设\((X,\mathscr{A},P)\)为概率空间,\(f\)是\(X\)上的离散型随机变量,\(f\)的取值集合为\(\{x_n\}\)。称\(-\log_2[P(f=x_n)]\)为事件\(\{f=x_n\}\)的自信息量(self-information),记作\(I(f=x_n)\),其中\(P(f=x_n)>0\)。

note 16.12. 为什么信息量这么定义呢?信息量应该具有如下两个性质:

  1. 一件事发生的概率越小,那么这件事发生后产生的信息量越大;

  2. 如果两件事情独立,那么这两件事情都发生所产生的信息量应该等于每件事情各自发生产生的信息量之和。

由(1),\(I(f=x_n)\)需要随\(P(f=x_n)\)单调下降;由(2),\(I(f=x_n)\)应具有对数的形式。

Definition 16.19. 设\((X,\mathscr{A},P)\)为概率空间,\(f\)是\(X\)上的离散型随机变量,\(f\)的取值集合为\(\{x_n\}\)。约定\(P(f=x_n)=0\)时\(P(f=x_n)I(f=x_n)=0\),称\(H(f)=\operatorname{E}[I(f)]\)为\(f\)的信息熵(information entropy),由样本得到的经验分布计算出的信息熵被称为经验熵(empirical entropy)。

Property 16.3.1. 设\((X,\mathscr{A},P)\)为概率空间,\(f\)是\(X\)上的离散型随机变量,\(f\)的取值集合为\(\{x_n\}\)。信息熵具有如下性质:

  1. \(H(f)\geqslant0\);

  2. 若\(f\)的取值集合为\(\{x_i\}_{i=1}^{n}\),则当\(P(f=x_1)=P(f=x_2)=\cdots=P(f=x_n)\)时,\(H(f)\)取得最大值\(\log_2(n)\)。

证明. (1)由信息熵的定义立即可得。

(2)由性质 8.1.10(3)可知\(-\log_2(x)\)在\((0,+\infty)\)上为凸函数,根据不等式 10可得:

\[\begin{align*} H(f)&=\operatorname{E}[I(f)]=-\sum_{i=1}^{n}P(f=x_i)\log_2[P(f=x_i)]=\sum_{i=1}^{n}P(f=x_i)\log_2\left[\frac{1}{P(f=x_i)}\right] \\ &\leqslant\log_2\left[\sum_{i=1}^{n}P(f=x_i)\frac{1}{P(f=x_i)}\right]=\log_2(n) \end{align*}\]

上式取等当且仅当\(\dfrac{1}{P(f=x_i)}\)为常数,即\(P(f=x_1)=P(f=x_2)=\cdots=P(f=x_n)\)。 ◻

对于二分类样本,记第一类样本比例为\(p\),第二类比例为\(1-p\),则信息熵为\(H(p)=-p\log_2p-(1-p)\log_2(1-p)\)。如下图所示,只含一个类别时熵为\(0\);两类各占一半时不确定性最大。

TikZ 数学图示
图 16.5 二分类样本的信息熵。p = 0.5时两类样本数量相同,信息熵达到最大值。

16.3.1.2 Gini系数

Definition 16.20. 设分类问题的类别空间为\(Y=\{1,2,\dots,K\}\),\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^n\)为观测样本集,其中\(y_i\in Y\)表示第\(i\)个样本的真实类别,记\(p_i\)为第\(i\)类样本在\(\mathcal{D}\)中所占比例。称:

\[\begin{equation*} \operatorname{Gini}(\mathcal{D})=1-\sum_{i=1}^Kp_i^2 \end{equation*}\]

为观测样本集\(\mathcal{D}\)的基尼指数(Gini index)。

Property 16.3.2. 设分类问题的类别空间为\(Y=\{1,2,\dots,K\}\),\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^n\)为观测样本集,其中\(y_i\in Y\)表示第\(i\)个样本的真实类别,记\(p_i\)为第\(i\)类样本在\(\mathcal{D}\)中所占比例。Gini指数具有如下性质:

  1. Gini指数是在样本集合中独立、有放回地抽取两个样本时,它们属于不同类别的概率;

  2. \(\operatorname{Gini}(\mathcal{D})\leqslant1-\dfrac{1}{K}\),上式取等当且仅当\(p_1=p_2=\cdots=p_K=\dfrac{1}{K}\)。

证明. (1)显然。

(2)由不等式 1可得:

\[\begin{equation*} \left(\sum_{i=1}^{K}1^2\right)\left(\sum_{i=1}^{K}p_i^2\right)\geqslant\left(\sum_{i=1}^{K}p_i\right)^2=1 \end{equation*}\]

上式取等当且仅当\(p_1=p_2=\cdots=p_K=\frac{1}{K}\),因此:

\[\begin{equation*} \operatorname{Gini}(\mathcal{D})=1-\sum_{i=1}^{K}p_i^2\leqslant1-\frac{1}{K} \end{equation*}\]

◻

note 16.13. 由性质 16.3.2(1)(2)可以看出Gini指数可以用来刻画样本类别分布的不纯度。当所有样本属于同一类别时\(\operatorname{Gini}(\mathcal{D})=0\),表示完全纯净;当类别分布均匀时\(\operatorname{Gini}(\mathcal{D})\)达到最大值,表示类别混杂程度最高。

对于二分类样本,Gini指数可写为\(G(p)=1-p^2-(1-p)^2=2p(1-p)\)。它与信息熵具有相同的对称性,但计算形式更简单。

TikZ 数学图示
图 16.6 二分类样本的Gini指数。节点越接近单一类别,Gini指数越接近0。

16.3.2 决策树

16.3.2.1 从判断规则到决策树

人们在进行分类或者回归时,往往不会同时综合所有信息后直接给出结论,而是依次提出若干问题,并根据每个问题的答案决定接下来应当考虑什么。例如,医生在进行初步问诊时,可能根据患者的体温、全身肌肉酸痛以及近期接触史,判断患者是否需要进一步接受流感检测。为了说明决策树的基本思想,考虑如下简化的判断规则:

首先判断患者的体温是否高于\(38\,^\circ\mathrm{C}\)。若体温高于\(38\,^\circ\mathrm{C}\),则继续判断患者是否存在明显的全身肌肉酸痛:若存在,则建议进行流感检测;若不存在,则暂不建议进行流感检测。

若患者的体温不高于\(38\,^\circ\mathrm{C}\),则继续询问其近期是否与流感患者有过密切接触:若有密切接触,则建议进行流感检测;若没有密切接触,则暂不建议进行流感检测。

这里的判断并不是一次完成的,而是由若干个简单问题依次组成。可以将这一系列判断规则表示为一棵树:

TikZ 数学图示
图 16.7 由一系列问诊规则构成的决策树示例

在图 16.7中,最上方的问题是整个判断过程的起点,称为根节点(root node);中间包含判断规则的节点称为内部节点;每个问题的不同答案对应从该节点出发的一条分支;不再提出新问题、而是直接给出预测结果的节点称为叶节点(leaf node)。从根节点出发,沿着每次判断所选择的分支,最终到达某个叶节点,这一系列连续的判断构成一条路径。每条路径都可以看作一组判断规则的组合,而每个叶节点则对应满足这些规则的一类样本。

决策树正是将这种基于连续提问的经验判断转化为一种可以从数据中学习的预测方法。给定训练数据集\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\),决策树需要完成两个任务:首先,从样本特征中选择适当的判断问题;其次,根据训练样本决定每条判断路径最终应当给出什么预测结果。

对于数值型特征,第\(j\)个特征和阈值\(\tau\)可以构成一个二元判断问题:

\[\begin{equation*} x_j\leqslant\tau\;? \end{equation*}\]

根据该问题的答案,可以将当前节点中的数据集\(\mathcal{D}_t\)划分为两个部分:

\[\begin{equation*} \mathcal{D}_{tL}(j,\tau)=\{(x,y)\in\mathcal{D}_t:x_j\leqslant\tau\},\quad\mathcal{D}_{tR}(j,\tau)=\{(x,y)\in\mathcal{D}_t:x_j>\tau\} \end{equation*}\]

其中\(\mathcal{D}_{tL}(j,\tau)\)和\(\mathcal{D}_{tR}(j,\tau)\)分别对应判断问题的两个答案,也就是当前节点产生的两个子节点。决策树随后在每个子节点中继续寻找新的判断问题,并重复这一划分过程。因此,决策树的构造过程具有递归结构:一个节点被划分为两个子节点,而每个子节点又可以继续被划分,直到满足某种停止条件。

从几何上看,每个判断规则都会对特征空间施加一个限制条件。例如,一条由如下判断组成的路径:

\[\begin{equation*} x_{j_1}\leqslant\tau_1,\quad x_{j_2}>\tau_2,\quad x_{j_3}\leqslant\tau_3 \end{equation*}\]

对应特征空间中的一个区域:

\[\begin{equation*} R_t=\left\{x:x_{j_1}\leqslant\tau_1,\;x_{j_2}>\tau_2,\;x_{j_3}\leqslant\tau_3\right\} \end{equation*}\]

所有落入同一个区域\(R_t\)的样本最终到达同一个叶节点,并共享相同的预测规则。因此,决策树本质上将特征空间划分为若干互不重叠的区域,并在每个区域内使用一个简单的局部预测。

在分类问题中,设叶节点\(t\)包含的数据集为\(\mathcal{D}_t\),其中第\(k\)类样本所占的比例为:

\[\begin{equation*} \hat{p}_{tk}=\frac{1}{|\mathcal{D}_t|}\sum_{(x,y)\in\mathcal{D}_t}I(y=k),\;k=1,2,\dots,K \end{equation*}\]

对于落入该叶节点的新样本\(x\),决策树将\(\hat{p}_{tk}\)作为类别\(k\)的预测概率,并将样本预测为该节点中的多数类别:

\[\begin{equation*} \hat{y}(x)=\underset{1\leqslant k\leqslant K}{\arg\max}\;\hat{p}_{tk} \end{equation*}\]

在回归问题中,同一个叶节点中的样本共享一个常数预测值\(\hat{c}_t\):

\[\begin{equation*} \hat{y}(x)=\hat{c}_t,\;x\in R_t \end{equation*}\]

根据所选择的损失函数,\(\hat{c}_t\)可以取节点中响应变量的均值、中位数或者其他适当的统计量,原则是使得损失函数值最小。

由此可见,一个判断规则是否合理,关键在于它是否将具有相似响应结果的样本分到一起。对于分类树,我们希望同一节点中的样本尽可能属于相同类别;对于回归树,我们希望同一节点中的响应值尽可能接近。换言之,决策树需要寻找能够使子节点中的响应变量更加同质的判断规则。

为了比较不同判断规则的优劣,首先需要度量一个节点中的响应变量究竟有多混杂。这个量称为节点不纯度(node impurity)。

16.3.2.2 节点不纯度

设\(\mathcal{D}\)是某个节点中的数据集。在该节点不再继续划分时,所有样本将共享同一个局部预测。对于给定损失函数\(L\),可以将节点不纯度统一定义为节点内最优常数预测所达到的最小经验风险:

\[\begin{equation*} I_{L}(\mathcal{D})=\underset{a\in\mathcal{A}}{\min}\;\frac{1}{|\mathcal{D}|}\sum_{(x,y)\in\mathcal{D}}L(y,a) \end{equation*}\]

其中\(\mathcal{A}\)表示允许的节点预测空间。节点不纯度越小,说明使用单一预测描述该节点中的所有样本所造成的损失越小,因而节点中的响应变量越同质。

在分类问题中,由性质 16.3.1(2)和性质 16.3.2(2)可知,信息熵与Gini不纯度都可以用于衡量节点中的类别分布是否集中。当节点中的样本全部属于同一类别时,二者均为\(0\);类别分布越分散,二者的取值越大。因此,它们都可以作为分类树中的节点不纯度指标。

在回归问题中,若使用平方误差损失:

\[\begin{equation*} L(y,c)=(y-c)^2 \end{equation*}\]

则最优节点预测为样本均值:

\[\begin{equation*} \hat{c}=\bar{y}=\frac{1}{|\mathcal{D}|}\sum_{(x,y)\in\mathcal{D}}y \end{equation*}\]

对应的节点不纯度为:

\[\begin{equation*} I_{\mathrm{SE}}(\mathcal{D})=\frac{1}{|\mathcal{D}|}\sum_{(x,y)\in\mathcal{D}}(y-\hat{c})^2 \end{equation*}\]

若使用绝对误差损失:

\[\begin{equation*} L(y,c)=|y-c| \end{equation*}\]

则最优节点预测为样本中位数:

\[\begin{equation*} \hat{c}=\operatorname{median}\{y:(x,y)\in\mathcal{D}\} \end{equation*}\]

对应的节点不纯度为:

\[\begin{equation*} I_{\mathrm{AE}}(\mathcal{D})=\frac{1}{|\mathcal{D}|}\sum_{(x,y)\in\mathcal{D}}|y-\hat{c}| \end{equation*}\]

在给定数据集和准则后,节点不纯度的计算过程可总结为算法 16.1。

Algorithm 16.1 Node Impurity

Input: Nonempty node dataset \(\mathcal{D}\) and splitting criterion

Output: Node impurity \(I(\mathcal{D})\)

if criterion \(\in \{\texttt{entropy},\texttt{gini}\}\) then

Let the class label set be \(\{1,2,\dots,K\}\)

\(n_i\gets|\{(x,y)\in\mathcal{D}:y=i\}|\), \(p_i\gets n_i/|\mathcal{D}|\), \(i=1,2,\dots,K\)

if criterion \(=\texttt{gini}\) then

\(I(\mathcal{D})\gets 1-\sum\limits_{i=1}^Kp_i^2\)

else if criterion \(=\texttt{entropy}\) then

\(I(\mathcal{D})\gets -\sum\limits_{\{i:p_i>0\}}p_i\log p_i\) (\(0\log0\) is interpreted as \(0\))

end if

else if criterion \(=\texttt{squared error}\) then

\(\bar{y}\gets |\mathcal{D}|^{-1}\sum\limits_{(x,y)\in\mathcal{D}}y\)

\(I(\mathcal{D})\gets |\mathcal{D}|^{-1}\sum\limits_{(x,y)\in\mathcal{D}}(y-\bar{y})^2\)

else if criterion \(=\texttt{absolute error}\) then

\(\widetilde{y}\gets \operatorname{median}\{y:(x,y)\in\mathcal{D}\}\)

\(I(\mathcal{D})\gets |\mathcal{D}|^{-1}\sum\limits_{(x,y)\in\mathcal{D}}|y-\widetilde{y}|\)

end if

return \(I(\mathcal{D})\)

16.3.2.3 寻找最优划分

节点不纯度衡量的是单个节点内部的同质程度。接下来需要判断某个划分规则能否使划分后的两个子节点比原节点更加同质。

对于候选划分\(s=(j,\tau)\),记划分后的数据集为:

\[\begin{equation*} \mathcal{D}_L(s)=\mathcal{D}_L(j,\tau),\quad\mathcal{D}_R(s)=\mathcal{D}_R(j,\tau) \end{equation*}\]

由于两个子节点包含的样本量可能不同,划分后的总体不纯度定义为两个子节点不纯度的样本量加权平均:

\[\begin{equation*} I_{\mathrm{split}}(s;\mathcal{D})=\frac{|\mathcal{D}_L(s)|}{|\mathcal{D}|}I[\mathcal{D}_L(s)]+\frac{|\mathcal{D}_R(s)|}{|\mathcal{D}|}I[\mathcal{D}_R(s)] \end{equation*}\]

该划分带来的不纯度下降为:

\[\begin{equation*} \Delta I(s;\mathcal{D})=I(\mathcal{D})-I_{\mathrm{split}}(s;\mathcal{D}) \end{equation*}\]

因此,最优划分可以等价地定义为使划分后不纯度最小的规则:

\[\begin{equation*} s^{\ast}\in\underset{s\in\mathcal{S}(\mathcal{D})}{\arg\min}\;I_{\mathrm{split}}(s;\mathcal{D}) \end{equation*}\]

或者使不纯度下降最大的规则:

\[\begin{equation*} s^{\ast}\in\underset{s\in\mathcal{S}(\mathcal{D})}{\arg\max}\;\Delta I(s;\mathcal{D}) \end{equation*}\]

其中\(\mathcal{S}(\mathcal{D})\)表示当前节点中允许使用的候选划分集合。

对于数值型特征,通常将相邻不同观测值的中点作为候选阈值。若特征\(j\)在当前节点中的不同取值为:

\[\begin{equation*} v_1<v_2<\cdots<v_q \end{equation*}\]

则候选阈值集合可以取为:

\[\begin{equation*} \left\{\frac{v_r+v_{r+1}}{2}:r=1,2,\dots,q-1\right\} \end{equation*}\]

为了避免产生仅包含极少量样本的子节点,还可以要求1:

\[\begin{equation*} |\mathcal{D}_L(s)|\geqslant N_{\mathrm{leaf}},\quad|\mathcal{D}_R(s)|\geqslant N_{\mathrm{leaf}} \end{equation*}\]

不满足该条件的候选划分不参与最优划分的比较。

遍历所有特征及其候选阈值并选择最优划分的过程可总结为算法 16.2。

Algorithm 16.2 Find the Best Binary Split

Input: Dataset \(\mathcal{D}\), feature set \(\mathcal{F}\), splitting criterion, minimum samples per leaf \(N_{\mathrm{leaf}}\)

Output: Best split \((F^\ast,\tau^\ast,I_{\mathrm{split}}^\ast)\) or None

\(I_{\mathrm{split}}^\ast\gets+\infty\), \((F^\ast,\tau^\ast)\gets\texttt{None}\)

for each feature \(F\in\mathcal{F}\) do

Let \(v_1<v_2<\cdots<v_q\) be the distinct observed values of feature \(F\) in \(\mathcal{D}\)

if \(q=1\) then

continue (Feature \(F\) is constant on the current node)

end if

\(\mathcal{T}_F\gets \{(v_j+v_{j+1})/2:j=1,2,\dots,q-1\}\)

for each candidate threshold \(\tau\in\mathcal{T}_F\) do

\(\mathcal{D}_L\gets \{(x,y)\in\mathcal{D}:x[F]\leqslant\tau\}\), \(\mathcal{D}_R\gets \{(x,y)\in\mathcal{D}:x[F]>\tau\}\)

if \(|\mathcal{D}_L|<N_{\mathrm{leaf}}\) or \(|\mathcal{D}_R|<N_{\mathrm{leaf}}\) then

continue (The split would create an undersized leaf)

end if

\(I_L\gets \operatorname{NodeImpurity}\!\left(\mathcal{D}_L,\text{criterion}\right)\), \(I_R\gets \operatorname{NodeImpurity}\!\left(\mathcal{D}_R,\text{criterion}\right)\)

\(I_{\mathrm{candidate}}\gets \dfrac{|\mathcal{D}_L|}{|\mathcal{D}|}I_L+ \dfrac{|\mathcal{D}_R|}{|\mathcal{D}|}I_R\)

if \(I_{\mathrm{candidate}}<I_{\mathrm{split}}^\ast\) then

\(I_{\mathrm{split}}^\ast\gets I_{\mathrm{candidate}}\), \((F^\ast,\tau^\ast)\gets(F,\tau)\)

end if

end for

end for

if \((F^\ast,\tau^\ast)=\texttt{None}\) then

return None

else

return \((F^\ast,\tau^\ast,I_{\mathrm{split}}^\ast)\)

end if

16.3.2.4 完整算法

完整的分类回归树(classification and regression tree, CART)对应的决策树生长过程可总结为算法 16.3。本文的算法不包含CART树生长完成后的代价复杂度后剪枝过程。

Algorithm 16.3 Classification and Regression Tree

Input: Dataset \(\mathcal{D}\), feature set \(\mathcal{F}\), maximum depth \(D_{\max}\), minimum samples to split \(N_{\mathrm{split}}\), minimum samples per leaf \(N_{\mathrm{leaf}}\), minimum impurity decrease \(\Delta_{\min}\), splitting criterion

Output: Root node \(T\) of the fitted decision tree

function CreateLeaf(\(\mathcal{D}\))

if criterion \(\in\{\texttt{gini},\texttt{entropy}\}\) then

Let the class label set be \(\{1,2,\dots,K\}\)

\(n_i\gets|\{(x,y)\in\mathcal{D}:y=i\}|\), \(p_i\gets n_i/|\mathcal{D}|\), \(i=1,2,\dots,K\)

\(\hat{y}\gets\underset{1\leqslant i\leqslant K}{\arg\max}\;p_i\)

return \(\{\text{type: \texttt{leaf}},\text{ prediction: }\hat{y}, \text{ class probabilities: }(p_1,p_2,\dots,p_K)\}\)

else if criterion \(=\texttt{squared error}\) then

\(\hat{y}\gets|\mathcal{D}|^{-1} \sum\limits_{(x,y)\in\mathcal{D}}y\)

return \(\{\text{type: \texttt{leaf}},\text{ prediction: }\hat{y}\}\)

else if criterion \(=\texttt{absolute error}\) then

\(\hat{y}\gets \operatorname{median}\{y:(x,y)\in\mathcal{D}\}\)

return \(\{\text{type: \texttt{leaf}},\text{ prediction: }\hat{y}\}\)

end if

end function

function BuildTree(\(\mathcal{D},d\))

if \(|\mathcal{D}|<N_{\mathrm{split}}\) or \(d\geqslant D_{\max}\) or all responses in \(\mathcal{D}\) are identical or no feature in \(\mathcal{F}\) varies on \(\mathcal{D}\) then

return CreateLeaf(\(\mathcal{D}\)) (Stopping rules)

end if

\(I_{\mathrm{parent}}\gets \operatorname{NodeImpurity}\!\left(\mathcal{D},\text{criterion}\right)\)

\(S\gets \operatorname{FindBestSplit}\!\left(\mathcal{D},\mathcal{F},\text{criterion},N_{\mathrm{leaf}}\right)\)

if \(S=\texttt{None}\) then

return CreateLeaf(\(\mathcal{D}\))

end if

\((F,\tau,I_{\mathrm{split}})\gets S\), \(\Delta\gets I_{\mathrm{parent}}-I_{\mathrm{split}}\)

if \(\Delta<\Delta_{\min}\) then

return CreateLeaf(\(\mathcal{D}\)) (Insufficient impurity decrease)

end if

\(\mathcal{D}_L\gets \{(x,y)\in\mathcal{D}:x[F]\leqslant\tau\}\), \(\mathcal{D}_R\gets \{(x,y)\in\mathcal{D}:x[F]>\tau\}\)

\(T\gets \{\text{type: \texttt{node}}, \text{ feature: }F, \text{ threshold: }\tau, \text{ impurity: }I_{\mathrm{parent}}, \text{ impurity decrease: }\Delta, \text{ sample size: }|\mathcal{D}|\}\)

\(T.\text{left}\gets \operatorname{BuildTree}\!\left(\mathcal{D}_L,d+1\right)\), \(T.\text{right}\gets \operatorname{BuildTree}\!\left(\mathcal{D}_R,d+1\right)\)

return \(T\)

end function

return BuildTree(\(\mathcal{D},0\))

前述算法框不仅规定了如何选择最优划分,还通过若干停止条件限制决策树的继续生长。这些条件属于决策树的预剪枝(pre-pruning)机制,其目的在于降低过拟合的风险。

如果不限制树的生长,决策树可以不断划分特征空间,甚至为每一个样本建立单独的叶节点。此时训练误差为\(0\),但模型并没有学习到总体中稳定的规律。于是决策树模型的复杂度函数可以定义为叶节点数。在本文给出的CART生长算法中,\(D_{\max}\)直接限制树的深度,单条决策路径深度每增加\(1\)就会使得叶节点数增加\(1\);\(N_{\mathrm{split}}\)阻止样本量过小的节点继续划分;\(N_{\mathrm{leaf}}\)防止划分产生仅包含少数样本的叶节点;\(\Delta_{\min}\)则要求一次划分必须带来足够明显的不纯度下降。上述参数从树的深度、节点样本量和划分收益等不同方面限制树的生长,共同控制决策树的复杂度。

因此,决策树算法的完整逻辑可以概括为:节点不纯度规定什么叫作响应同质;最优划分寻找能够最大程度提高同质性的局部规则;递归生长将这些局部规则组合为完整的预测路径;复杂度约束则防止树为了降低训练误差而不断拟合样本噪声。

16.3.2.5 特征重要性

决策树模型可以根据各特征在所有分裂节点上带来的纯度改进衡量该特征的重要性,某一特征若多次被用于关键节点划分,并且能够带来更明显的纯度提升,则说明该特征对样本区分和结果预测具有更重要的作用。由于不同节点包含的样本数可能相差很大,发生在树根附近的划分通常影响较多样本,而发生在树的深层节点中的划分只影响少量样本。因此,还需要根据当前节点所包含的样本比例对不纯度下降进行加权。设根节点中的完整训练数据集为\(\mathcal{D}_0\),节点\(t\)中的数据集为\(\mathcal{D}_t\),该节点采用的划分规则为\(s_t^{\ast}=(j_t,\tau_t)\),则特征\(j\)的未归一化重要性定义为:

\[\begin{equation*} \widetilde{\operatorname{FI}}_j=\sum_{\substack{t\text{为非叶节点} \\ j_t=j}}\frac{|\mathcal{D}_t|}{|\mathcal{D}_0|}\Delta I(s_t^{\ast};\mathcal{D}_t) \end{equation*}\]

也就是说,需要将所有使用特征\(j\)进行划分的节点找出,并将这些节点带来的加权不纯度下降累加起来。最后对所有特征的重要性进行归一化,得到:

\[\begin{equation*} \operatorname{FI}_j=\frac{\widetilde{\operatorname{FI}}_j}{\sum\limits_{k=1}^p\widetilde{\operatorname{FI}}_k} \end{equation*}\]

这一指标通常称为基于不纯度下降的特征重要性。该得分越高,表明该特征在决策树模型中的作用越显著,对最终预测结果的影响也越大。

16.3.3 随机森林

单棵决策树采用自上而下的贪心策略:在每个节点上,只根据当前到达该节点的样本选择局部最优的划分,而一旦划分确定,后续节点便只能在由该划分得到的子样本上继续生长。因此,靠近根节点的划分不仅决定样本最初被如何分组,还会改变之后各个节点所面对的数据,从而影响整棵子树的结构。

当训练样本发生轻微变化时,原本表现接近的几个候选划分可能交换优劣次序,使算法选择不同的划分变量或划分点。若这种变化发生在靠近根节点的位置,大量样本会被重新分配到不同的分支,后续所有划分都可能随之改变,最终形成结构和预测结果差异显著的决策树。因此,单棵决策树对训练数据中的随机扰动较为敏感,通常具有较大的方差。

为了减小这种不稳定性,可以利用不同的训练样本生成\(B\)棵决策树\(T_1, T_2, \dots, T_{B}\),再将它们的预测结果聚合起来。对于回归任务,通常取各棵树预测的平均值:

\[\begin{equation*} f_{\mathrm{bag}}(x)=\frac{1}{B}\sum_{b=1}^{B}T_b(x) \end{equation*}\]

对于类别集合\(Y=\{1,2,\dots,K\}\)上的分类任务,通常采用多数投票:

\[\begin{equation*} f_{\mathrm{bag}}(x)=\underset{1\leqslant k\leqslant K}{\arg\max}\; \sum_{b=1}^{B}I[T_b(x)=k] \end{equation*}\]

下面的性质说明了这种聚合能够有效地改善预测精度。

Property 16.3.3. 在固定样本点\(x\)处,树的集成模型具有如下性质2:

  1. 若任意一棵树的预测均满足\(\operatorname{Var}[T_b(x)]=\sigma^2\),且任意两棵不同的树均满足\(\operatorname{Corr}[T_b(x),T_{b'}(x)]=\rho\),则平均预测的方差为:

    \[\begin{equation*} \operatorname{Var}[f_{\mathrm{bag}}(x)]=\sigma^2\left(\rho+\frac{1-\rho}{B}\right) \end{equation*}\]

  2. 对于二分类任务,设\(B\)为奇数,每棵树在\(x\)处出错的概率均为\(p<1/2\)。若各棵树的错误事件相互独立,则多数投票出错的概率满足:

    \[\begin{equation*} P[f_{\mathrm{bag}}(x)\ne y]\leqslant\exp\left[-2B\left(\frac{1}{2}-p\right)^2\right] \end{equation*}\]

证明. (1)由性质 6.4.3(3)和性质 6.4.2(3)可知:

\[\begin{align*} \operatorname{Var}[f_{\mathrm{bag}}(x)] &=\frac{1}{B^2}\left\{\sum_{b=1}^{B}\operatorname{Var}[T_b(x)]+2\sum_{1\leqslant b<b'\leqslant B}\operatorname{Cov}[T_b(x),T_{b'}(x)]\right\} \\ &=\frac{B\sigma^2+B(B-1)\rho\sigma^2}{B^2} =\sigma^2\left(\rho+\frac{1-\rho}{B}\right) \end{align*}\]

(2)令\(I_b=I[T_b(x)\ne y]\),则多数投票出错时有\(B^{-1}\sum\limits_{b=1}^{B}I_b>1/2\)。由定理 17.20可得:

\[\begin{equation*} P[f_{\mathrm{bag}}(x)\ne y] \leqslant P\left(\frac{1}{B}\sum_{b=1}^{B}I_b-p\geqslant\frac{1}{2}-p\right) \leqslant\exp\left[-2B\left(\frac{1}{2}-p\right)^2\right] \end{equation*}\]

◻

第一条性质表明,增加树的数量可以逐渐消除各棵树中互不相关的波动,从而降低预测的方差,但是无法消除它们共同的波动:即使\(B\)不断增大,集成预测的方差仍受到\(\rho\sigma^2\)的限制。第二条性质则说明,当每棵树的判断优于随机猜测,并且各棵树的错误不易同时发生时,多数投票可以指数级降低分类错误率。因此,有效的树集成既要求单棵树具有一定的预测能力,也要求不同树之间存在足够的差异。

随机森林(random forest, RF)正是依据上述条件构造的。它仍以决策树作为基本模型,以保留决策树拟合复杂关系的能力;同时通过样本与特征两个层面的随机化,降低不同树之间的相关性。

设训练数据集为\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\),其中\(x_i\)具有\(p\)个特征。训练第\(b\)棵树时,先从\(\mathcal{D}\)中有放回地抽取\(n\)个样本,得到样本集\(\mathcal{D}^{(b)}\)。不同的树使用不同的样本集,因而会受到不同样本扰动的影响。

note 16.14. 重复采样还可以为模型提供内部验证。对于某棵树,没有被其样本集选中的训练样本称为该树的袋外样本(out-of-bag, OOB)。对每个训练样本,只聚合所有未使用该样本进行训练的树所给出的预测,便可得到相应的OOB预测;将OOB预测与真实响应比较,可以在不另外划分验证集的情况下估计模型的真实误差。

仅通过对训练样本进行随机抽样,虽然可以使各棵决策树接触不同的数据,但未必能够充分降低树与树之间的相关性。特别是当少数特征具有显著较强的预测能力时,这些特征可能在不同的训练样本上仍然反复成为靠近根节点的最优划分特征,使不同树形成相似的结构。为进一步增加树之间的差异,随机森林在划分每个节点时,会从全部\(p\)个特征中随机抽取\(q\)个候选特征,并且只在这些特征上寻找当前节点的最优划分。候选特征集合在每个节点处重新独立抽取,因此,即使某个强特征在全部特征中通常能够产生最优划分,它也并非在每个节点都能参与竞争。这样可以促使不同的树使用不同的特征形成划分,从而降低树之间的相关性,并通过集成平均获得更显著的方差缩减或错误率下降。

note 16.15. 随机森林使用决策树作为基本模型,但这种”分别训练多个模型,再对预测进行平均或投票”的思想并不局限于决策树。只要单个模型具有一定的预测能力,并且能够通过训练样本或模型结构的随机化产生足够不同的预测,就可以采用类似的集成方法。

Definition 16.21. 设集成模型由\(B\)个分别训练得到的预测模型\(f_1, f_2, \dots, f_{B}\)组成。称其中每个\(f_b\)为该集成模型的基学习器(base learner)。

Definition 16.22. 设训练数据集为\(\mathcal{D}\)。对\(b=1,2,\dots,B\),分别从\(\mathcal{D}\)中进行有放回的随机抽样得到\(\mathcal{D}^{(b)}\),并在\(\mathcal{D}^{(b)}\)上训练基模型\(f_b\)。将这些基模型的预测结果进行聚合所得的集成模型\(f_{\mathrm{bag}}\)称为袋装集成模型(bagging ensemble model) ,即:

\[\begin{equation*} f_{\mathrm{bag}}(x)\coloneq\operatorname{Agg}\left[f_1(x),f_2(x),\dots,f_B(x)\right] \end{equation*}\]

其中\(\operatorname{Agg}\)表示聚合算子:回归任务中通常取平均,分类任务中通常采用多数投票。

16.3.4 GBDT

16.3.4.1 逐步修正已有预测

梯度提升决策树(gradient boosting decision tree, GBDT)采用逐步构建加法模型的方式:先给出一个简单的初始预测,再依次加入若干棵回归树,使每棵新树针对当前模型预测不足的部分进行修正。因此,随着回归树逐棵加入,模型的预测结果也不断得到改进。

在加入任何回归树之前,模型尚未利用样本特征,因而无法对不同样本作出区分。此时,最自然的做法是令所有样本共享同一个常数预测,并选择使训练集经验损失最小的常数作为模型的初始值:

\[\begin{equation*} F_0(x)\equiv c_0,\quad c_0\in\underset{c\in\mathbb{R}}{\arg\min}\sum_{i=1}^{n}L(y_i,c) \end{equation*}\]

设第\(b\)轮开始时已有模型为\(F_{b-1}\),新树将特征空间划分为\(J_b\)个叶节点区域\(\{R_{jb}\}_{j=1}^{J_b}\)。若\(x_i\in R_{jb}\),则新树为样本\(x_i\)提供同一个新增值\(\gamma_{jb}\),使其预测由\(F_{b-1}(x_i)\)变为\(F_{b-1}(x_i)+\gamma_{jb}\)。由于该区域中的所有样本必须共享这个新增值,\(\gamma_{jb}\)应当使这些样本更新后的总损失最小,即:

\[\begin{equation*} \gamma_{jb}\in \underset{\gamma\in\mathbb{R}^{}}{\arg\min} \sum_{x_i\in R_{jb}} L[y_i,F_{b-1}(x_i)+\gamma] \end{equation*}\]

因此,第\(b\)棵树可以写为:

\[\begin{equation*} f_b(x)=\sum_{j=1}^{J_b}\gamma_{jb}I(x\in R_{jb}) \end{equation*}\]

16.3.4.2 学习率

如果将\(f_b\)完整地加入当前模型,就相当于完全接受新树在本轮给出的修正。GBDT通常只加入其中的一部分,即采用更新

\[\begin{equation*} F_b(x)=F_{b-1}(x)+\eta f_b(x),\quad0<\eta\leqslant1 \end{equation*}\]

其中\(\eta\)称为学习率。

随着迭代不断进行,模型会逐步拟合当前尚未解释的部分。前期的待修正部分主要包含较为稳定的结构,而在模型已经拟合得较好之后,剩余部分中噪声所占的比例会逐渐增大。若每一轮都完整加入新树,模型便会较快地继续追随这些细微波动,最终趋向于对训练数据的近乎完美拟合,从而产生过拟合。较小的学习率限制了每轮修正的幅度,因此可以减缓模型拟合噪声的速度。

此外,GBDT是一个前向构造的加法模型,已经加入的树不会在后续训练中重新调整。如果某一棵树给出了存在偏差的修正,较大的学习率会使这种偏差对整体模型产生较强影响;较小的学习率则可以限制单棵树造成的偏移,使模型的训练过程更加稳定,并有助于降低预测方差。

需要注意的是减小\(\eta\)意味着每棵树只能完成较小幅度的修正,因此通常需要相应增加回归树的数量。学习率与树的数量共同决定模型的拟合速度和复杂程度,需要配合选择。

16.3.4.3 伪残差与树的生长

叶节点新增值的计算建立在树的划分结构已经确定的基础上。为了真正生成第\(b\)棵树,还需要确定哪些样本应当进入同一个叶节点。GBDT中的新树拟合的不是响应变量本身,而是当前模型需要作出的修正,因此,应当将所需修正方向和修正幅度相近的样本划分到同一叶节点,使它们能够共享一个叶节点新增值。

设第\(b-1\)轮模型对样本\(x_i\)的预测为\(F_{b-1}(x_i)\),若在此基础上增加一个较小的修正\(\delta_i\),则由定理 4.6(1)可得:

\[\begin{equation*} L[y_i,F_{b-1}(x_i)+\delta_i]\approx L[y_i,F_{b-1}(x_i)]-r_{ib}\delta_i \end{equation*}\]

其中:

\[\begin{equation*} r_{ib}=-\left.\frac{\partial L(y_i,F(x_i))}{\partial F(x_i)}\right|_{F=F_{b-1}} \end{equation*}\]

称为第\(i\)个样本在第\(b\)轮的伪残差。

伪残差刻画了当前预测应当修正的局部方向及相对强度。若\(r_{ib}>0\),增大预测值可以使损失下降;若\(r_{ib}<0\),则应减小预测值;而\(|r_{ib}|\)越大,说明当前损失对相应方向的修正越敏感。因此,两个样本的伪残差相近,意味着它们在当前模型下需要沿相同方向、以相近的相对强度进行修正。GBDT正是利用这种相似性来组织样本:在第\(b\)轮回归树的生长中,\(\operatorname{BuildTree}(\mathcal{D},d)\)接收的数据集为\(\mathcal{D}=\{(x_i,r_{ib})\}_{i=1}^n\)。对于每个候选划分,算法根据划分后各子节点内伪残差的离散程度评价其优劣,并选择能够使节点内伪残差更加集中的划分。这与普通决策树依据节点不纯度选择划分的思想相同。需要注意的是,伪残差在这里仅用于确定树的划分结构,即决定哪些样本进入同一个叶节点,并不等同于叶节点最终输出想要拟合的新增值。待树结构确定后,各叶节点的新增值还需要通过最小化原损失函数另行计算。

对于分类任务,响应变量\(y_i\)虽然是离散类别,由损失函数导数得到的伪残差\(r_{ib}\)却是实数,因此每一轮拟合的基学习器仍然是回归树。此时GBDT实际上将分类训练转化成了回归问题:加法模型\(F_b(x)\)逐步学习连续的分类得分,再根据该得分与给定阈值确定类别。

16.3.4.4 完整算法

综上,GBDT的每一轮训练依次完成三件事:根据当前预测计算伪残差,使用伪残差拟合回归树的划分结构,再根据原损失函数计算各叶节点的最优新增值。最后将经过学习率收缩的新树加入当前模型。完整过程如下。

Algorithm 16.4 Gradient Boosting Decision Tree

Input: Training set \(\mathcal{D}_{\mathrm{train}}=\{(x_i,y_i)\}_{i=1}^n\), validation set \(\mathcal{D}_{\mathrm{val}}=\{(x_i^{(v)},y_i^{(v)})\}_{i=1}^{n_v}\), loss function \(L(y,F(x))\), number of boosting iterations \(B\), learning rate \(\eta\), early-stopping patience \(P\), tolerance \(\varepsilon\)

Output: Additive model \(F_{\hat{b}}(x)\)

Initialize the model prediction with an optimal constant \(F_0(x)\equiv c_0\) for all samples, which means that \(c_0\in\underset{c\in\mathbb{R}}{\arg\min}\sum\limits_{i=1}^nL(y_i,c)\)

\(\hat{b}\gets 0\), \(c_{\mathrm{stop}}\gets 0\), \(L_{\mathrm{best}}\gets +\infty\)

for \(b=1,2,\dots,B\) do

Compute pseudo-residuals for all training samples

\[\begin{equation*} r_{ib}\gets-\left.\frac{\partial L(y_i,z)}{\partial z}\right|_{z=F_{b-1}(x_i)} \quad i=1,2,\dots,n \end{equation*}\]

Fit a regression tree \(T_b\gets\operatorname{BuildTree}\!\left(\{(x_i,r_{ib})\}_{i=1}^n, 0\right)\)

Let the leaf regions of \(T_b\) be \(\{R_{jb}\}_{j=1}^{J_b}\)

for \(j=1,2,\dots,J_b\) do

Compute the optimal leaf output on region \(R_{jb}\)

\[\begin{equation*} \gamma_{jb}\gets \underset{\gamma\in\mathbb{R}}{\arg\min} \sum_{x_i\in R_{jb}} L[y_i,F_{b-1}(x_i)+\gamma] \end{equation*}\]

end for

Define the \(b\)-th base learner \(f_b(x)\gets\sum\limits_{j=1}^{J_b}\gamma_{jb}I(x\in R_{jb})\) and update the additive model \(F_b(x)\gets F_{b-1}(x)+\eta f_b(x)\)

Evaluate the validation loss \(L_b\gets \dfrac{1}{n_v}\sum\limits_{i=1}^{n_v}L[y_i^{(v)},F_b(x_i^{(v)})]\)

if \(L_b<L_{\mathrm{best}}-\varepsilon\) then

\(L_{\mathrm{best}}\gets L_b\), \(\hat b\gets b\), \(c_{\mathrm{stop}}\gets 0\)

else

\(c_{\mathrm{stop}}\gets c_{\mathrm{stop}}+1\)

end if

if \(c_{\mathrm{stop}}\geqslant P\) then

break

end if

end for

return \(F_{\hat{b}}(x)\)

上述算法引入了早停(early stopping)机制。树的数量\(B\)通常无法预先确定,\(B\)过小时模型可能尚未充分拟合,\(B\)过大时后续树又会逐渐拟合训练数据中的噪声,导致过拟合。因此,算法将\(B\)仅作为最大迭代次数,并根据验证损失决定实际拟合的树数。

16.3.5 XGBoost

XGBoost继承了Boosting模型和GBDT逐步加法优化的基本思想,但进一步对每一轮新增树的学习过程进行了更精细的刻画。

16.3.5.1 正则化

与传统GBDT主要关注经验损失的下降不同,XGBoost在目标函数中显式加入了对新增树复杂度的正则化惩罚,从而将第\(b\)轮的优化目标写为:

\[\begin{equation*} L^{(b)}=\sum_{i=1}^{n}L[y_i,F_{b-1}(x_i)+f_b(x_i)]+\Omega(f_b) \end{equation*}\]

其中,\(\Omega(f_b)\)表示对第\(b\)棵树复杂度的惩罚项,定义为:

\[\begin{equation*} \Omega(f_b)=\gamma|T_b|+\frac{1}{2}\lambda\sum_{j=1}^{J_b}w_{jb}^2 \end{equation*}\]

这里,\(|T_b|\)表示第\(b\)棵树的叶节点个数,\(w_{jb}\)表示第\(b\)棵树第\(j\)个叶节点的输出值,参数\(\gamma\)用于惩罚树结构过于复杂的情形,参数\(\lambda\)用于约束叶节点输出的大小,避免输出太激进。

16.3.5.2 叶节点输出

由于直接对\(f_b\)进行优化仍然较困难,XGBoost在\(F_{b-1}(x_i)\)附近对损失函数作二阶近似。令:

\[\begin{equation*} g_{ib}=\left.\frac{\partial L(y_i,F)}{\partial F}\right|_{F=F_{b-1}(x_i)}, \quad h_{ib}=\left.\frac{\partial^2 L(y_i,F)}{\partial F^2}\right|_{F=F_{b-1}(x_i)} \end{equation*}\]

其中,\(g_{ib}\)和\(h_{ib}\)分别表示第\(i\)个样本在第\(b\)轮迭代时损失函数关于当前预测的一阶导数和二阶导数。于是由定理 4.6(1)有近似展开3:

\[\begin{equation*} L[y_i,F_{b-1}(x_i)+f_b(x_i)]\approx L[y_i,F_{b-1}(x_i)]+g_{ib}f_b(x_i)+\frac{1}{2}h_{ib}f_b^2(x_i) \end{equation*}\]

将其代入\(L^{(b)}\),得到:

\[\begin{equation*} L^{(b)}\approx\sum_{i=1}^{n}\left\{L[y_i,F_{b-1}(x_i)]+g_{ib}f_b(x_i)+\frac{1}{2}h_{ib}f_b^2(x_i)\right\}+\Omega(f_b) \end{equation*}\]

其中,\(\sum\limits_{i=1}^{n}L[y_i,F_{b-1}(x_i)]\)与当前轮新增树\(f_b\)无关,于是第\(b\)轮真正需要优化的目标可写为:

\[\begin{equation*} \tilde{L}^{(b)}=\sum_{i=1}^{n}\left[g_{ib}f_b(x_i)+\frac{1}{2}h_{ib}f_b^2(x_i)\right]+\Omega(f_b) \end{equation*}\]

接下来利用树模型的结构来化简目标函数。设第\(b\)棵树\(f_b\)将样本空间划分为\(J_b\)个叶节点区域\(R_{1b},R_{2b},\dots,R_{J_bb}\),并且对落入第\(j\)个叶节点的样本统一输出\(w_{jb}\)。将样本按叶节点分组,记:

\[\begin{equation*} G_{jb}=\sum_{x_i\in R_{jb}}g_{ib}, \quad H_{jb}=\sum_{x_i\in R_{jb}}h_{ib} \end{equation*}\]

则有:

\[\begin{align*} \tilde{L}^{(b)}&=\sum_{j=1}^{J_b}\left(G_{jb}w_{jb}+\frac{1}{2}H_{jb}w_{jb}^2\right)+\gamma|T_b|+\frac{1}{2}\lambda\sum_{j=1}^{J_b}w_{jb}^2 \\ &=\sum_{j=1}^{J_b}\left[G_{jb}w_{jb}+\frac{1}{2}(H_{jb}+\lambda)w_{jb}^2\right]+\gamma|T_b| \end{align*}\]

于是最优叶节点输出和给定树结构下的最优目标值分别为:

\[\begin{equation*} w_{jb}^{\star}=-\frac{G_{jb}}{H_{jb}+\lambda},\quad\tilde{L}^{(b)\star}=-\frac{1}{2}\sum_{j=1}^{J_b}\frac{G_{jb}^2}{H_{jb}+\lambda}+\gamma|T_b| \end{equation*}\]

这个式子表明,一旦树的划分结构确定,叶节点权重可以显式求出,而且整棵树的优劣也可以直接由各叶节点上的\(G_{jb}\)与\(H_{jb}\)来衡量。进一步地,XGBoost在生成树时正是利用这一目标值来计算分裂增益,从而决定最优划分并判断一个结点是否值得继续分裂。

16.3.5.3 节点分裂

设在第\(b\)轮迭代中,当前有某个待分裂叶节点\(R\),其对应的一阶与二阶统计量分别为:

\[\begin{equation*} G=\sum_{x_i\in R}g_{ib}, \quad H=\sum_{x_i\in R}h_{ib} \end{equation*}\]

如果该结点暂时不分裂,而是作为一个叶节点保留,则根据前述最优目标值公式,这个叶节点对目标函数的贡献为:

\[\begin{equation*} -\frac{1}{2}\frac{G^2}{H+\lambda}+\gamma \end{equation*}\]

现在考虑将该结点分裂为左、右两个子结点\(R_L\)与\(R_R\)。记它们各自的一阶与二阶统计量为:

\[\begin{equation*} G_L=\sum_{x_i\in R_L}g_{ib}, \quad H_L=\sum_{x_i\in R_L}h_{ib}, \quad G_R=G-G_L, \quad H_R=H-H_L \end{equation*}\]

显然有\(G=G_L+G_R,\;H=H_L+H_R\)。分裂之后,原来的一个叶节点变成两个叶节点,因此这两个子结点对目标函数的贡献之和为:

\[\begin{equation*} -\frac{1}{2}\frac{G_L^2}{H_L+\lambda} -\frac{1}{2}\frac{G_R^2}{H_R+\lambda} +2\gamma \end{equation*}\]

因此,分裂前后目标函数的变化量可写为:

\[\begin{align*} \Delta &= \left( -\frac{1}{2}\frac{G_L^2}{H_L+\lambda} -\frac{1}{2}\frac{G_R^2}{H_R+\lambda} +2\gamma \right) - \left( -\frac{1}{2}\frac{G^2}{H+\lambda} +\gamma \right) \\ &= -\frac{1}{2}\frac{G_L^2}{H_L+\lambda} -\frac{1}{2}\frac{G_R^2}{H_R+\lambda} +\frac{1}{2}\frac{G^2}{H+\lambda} +\gamma \end{align*}\]

由于我们希望目标函数尽可能小,因此只有当\(\Delta<0\)时,分裂才会使目标函数下降,也才是有利的分裂。

这就是XGBoost中用于评估一个候选分裂是否值得采用的分裂增益公式。XGBoost将树结构的生长过程直接纳入了统一的优化框架之中(GBDT是分开的),使得每一次分裂都具有明确的目标函数解释。

16.3.5.4 完整算法

Algorithm 16.5 Extreme Gradient Boosting

Input: Training set \(\mathcal{D}_{\mathrm{train}}=\{(x_i,y_i)\}_{i=1}^n\), validation set \(\mathcal{D}_{\mathrm{val}}=\{(x_i^{(v)},y_i^{(v)})\}_{i=1}^{n_v}\), loss function \(L(y,\hat y)\), number of boosting iterations \(B\), learning rate \(\eta\), regularization parameters \(\lambda,\gamma\), early-stopping patience \(P\), tolerance \(\varepsilon\)

Output: Additive model \(\hat y_{\hat b}(x)\)

Initialize the model prediction with an optimal constant \(F_0(x)\equiv c_0\) for all samples, which means that \(c_0\in\underset{c\in\mathbb{R}}{\arg\min}\sum\limits_{i=1}^nL(y_i,c)\)

\(\hat b\gets 0\), \(c_{\mathrm{stop}}\gets 0\), \(L_{\mathrm{best}}\gets +\infty\)

for \(b=1,2,\dots,B\) do

Compute first- and second-order statistics for all training samples:

\(g_{ib}\gets \dfrac{\partial L(y_i,F)}{\partial F}\big|_{F=F_{b-1}(x_i)},\;h_{ib}\gets \dfrac{\partial^2L(y_i,F)}{\partial F^2}\big|_{F=F_{b-1}(x_i)},\;i=1,2,\dots,n\)

Fit a regression tree structure \(T_b\). Let the leaf regions of \(T_b\) be \(R_{1b},R_{2b},\dots,R_{J_b b}\)

for \(j=1,2,\dots,J_b\) do

Compute aggregated statistics on region \(R_{jb}\):

\[\begin{equation*} G_{jb}\gets\sum\limits_{x_i\in R_{jb}}g_{ib},\quad H_{jb}\gets\sum\limits_{x_i\in R_{jb}}h_{ib} \end{equation*}\]

Compute the optimal leaf output

\[\begin{equation*} w_{jb}\gets-\frac{G_{jb}}{H_{jb}+\lambda} \end{equation*}\]

end for

Define the \(b\)-th base learner \(f_b(x)\gets\sum\limits_{j=1}^{J_b}w_{jb}I(x\in R_{jb})\) and update the additive model \(F_b(x)\gets F_{b-1}(x)+\eta f_b(x)\)

Evaluate the validation loss \(L_b\gets\dfrac{1}{n_v}\sum\limits_{i=1}^{n_v}L[y_i^{(v)},F_b(x_i^{(v)})]\)

if \(L_b<L_{\mathrm{best}}-\varepsilon\) then

\(L_{\mathrm{best}}\gets L_b\), \(\hat b\gets b\), \(c_{\mathrm{stop}}\gets 0\)

else

\(c_{\mathrm{stop}}\gets c_{\mathrm{stop}}+1\)

end if

if \(c_{\mathrm{stop}}\geqslant P\) then

break

end if

end for

return \(F_{\hat b}(x)\)

16.3.5.5 工程优化

在结点分裂过程中,XGBoost需要依次考察所有候选特征,并在每个特征上寻找使目标函数增益最大的切分点。对于连续特征,精确贪心算法需要先按照特征值对样本排序,再沿排序结果依次扫描所有相邻取值之间的候选切分点,并在扫描过程中累计左右子结点的一阶与二阶梯度统计量。因此,树构造的主要计算开销来自两个方面:一是特征值的排序与重复访问,二是大量候选切分点的枚举。XGBoost分别通过Column Block与基于Histogram的近似切分搜索缓解这两个问题。

Column Block

精确切分搜索要求按照每个特征的取值顺序扫描样本。若在每个结点、每轮Boosting中都重新进行排序,计算代价将十分高昂。为避免重复排序,XGBoost在训练开始前将数据组织为按列存储的block,并使每一列按照对应特征值从小到大排列,同时记录每个特征值对应的样本索引。

这一排序结果只需要计算一次,随后可以在不同结点和不同轮次中重复使用。对于某个特征,算法只需沿预先排序的特征列做线性扫描,便可以依次累计梯度统计量并枚举候选切分点。不同特征列之间的切分搜索还可以并行执行,从而进一步提高树构造效率。

Histogram与近似切分搜索

Column Block避免了重复排序,但精确贪心算法仍需考察连续特征的所有可能切分位置。当特征包含大量不同取值时,候选切分点的数量可能接近样本数量。为减少候选点,XGBoost采用近似切分搜索:首先根据特征分布选取有限个候选边界,将连续特征划分为若干个bin;随后不再逐个扫描原始特征值,而是将落入同一bin的样本的一阶与二阶梯度统计量分别聚合,仅在相邻bin的边界处评估切分增益。因此,切分搜索的复杂度主要取决于bin的数量,而不再直接取决于特征不同取值的数量。

全局候选点与局部候选点

根据候选切分点的生成时机,近似切分搜索可以分为全局方法和局部方法。

全局方法在树开始构造时,基于根结点中的样本为每个特征生成一套候选切分点,并在该树的所有结点中重复使用相同的候选边界。由于候选点不需要随结点变化而重新计算,全局方法的计算开销较低,但为了在不同深度的结点中仍然保持足够的切分精度,通常需要设置较多的候选点。

在采用固定全局分箱的Histogram实现中,分箱边界还可以在训练开始前统一确定。此后,每个连续特征值都可以预先转换为有限范围内的bin编号。因而,后续树构造过程可以直接存储和访问整数形式的bin编号,而不必反复读取原始浮点特征值。这不仅减少了内存占用,也改善了缓存命中率和Histogram统计量的计算效率。

局部方法则在每个结点上,仅根据进入该结点的样本重新计算候选切分点。随着树不断生长,局部分位点可以逐渐适应不同结点内的局部特征分布,因此通常能够用较少的候选点获得较精细的切分;但每次结点分裂后都需要重新执行分位点近似,计算代价明显更高。

稀疏感知与缺失值处理

无论采用精确切分还是Histogram近似切分,算法都需要沿当前特征扫描样本并累计梯度统计量。在实际数据中,一个特征可能仅在部分样本上具有显式记录:一部分位置是真实的缺失值,另一部分则可能由于稀疏存储而被省略,例如One-hot Encoding中大量取值为零的未激活位置。

为此,XGBoost采用稀疏感知的切分搜索。对于每个特征,算法只扫描显式记录的非缺失取值,并据此完成排序、分箱以及梯度统计量的累计;未显式记录样本(包含缺失值与稀疏值)的统计量则可以由当前结点的总统计量减去显式观测部分的统计量得到,因而无须逐个访问这些样本。这样,切分搜索的计算量主要取决于非缺失元素的数量,而不是稠密矩阵的总元素数量。

对于未显式记录的样本,算法无法直接根据特征值与切分点的大小关系确定其所属分支。因此,在评估每个候选切分时,XGBoost分别计算将这些样本全部分配至左子结点和全部分配至右子结点时的切分增益,并将增益较大的方向记录为该切分的默认方向。预测时,若样本在当前切分特征上的取值缺失或未显式记录,便沿所学习的默认方向继续向下传播。

16.4 K近邻模型

下图展示了KNN分类的基本思想:以待预测点为中心,逐渐扩大邻域,直到包含\(k\)个训练样本,再由这些邻居的类别决定预测结果。图中取\(k=5\),邻域内有\(3\)个A类样本和\(2\)个B类样本,因此查询点被预测为A类。

TikZ 数学图示
图 16.8 KNN分类示意图。虚线圆仅用于表示包含第5个最近邻的最小邻域;实际算法只需比较距离,不必显式构造圆形边界。

Definition 16.23. 设\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\)为观测样本集,其中\(x_i\)为第\(i\)个样本的特征向量,\(y_i\)是第\(i\)个样本的标签或值。给定距离度量\(\rho\)以及整数\(1\leqslant k\leqslant n\),对任意样本\(x\),记其\(k\)个最近邻样本索引集合为:

\[\begin{equation*} \mathcal{N}_k(x)=\underset{S\subseteq\{1,\dots,n\},|S|=k}{\arg\min}\sum_{i\in S}\rho(x,x_i) \end{equation*}\]

若上述最小化问题存在多个解,则约定在距离相同的样本中优先选择索引较小者。 称如下规则为k近邻(k-nearest neighbours, KNN)模型:

  1. 分类情形:

    \[\begin{equation*} \hat y(x)=\underset{c\in\mathcal{Y}}{\arg\max}\sum_{i\in\mathcal{N}_k(x)}I(y_i=c) \end{equation*}\]

  2. 回归情形:

    \[\begin{equation*} \hat y(x)=\frac{1}{k}\sum_{i\in\mathcal{N}_k(x)}y_i \end{equation*}\]

若进一步引入权重函数\(w_i(x)\),则可得到加权KNN估计:

\[\begin{equation*} \hat y(x)=\sum_{i\in\mathcal{N}_k(x)}w_i(x)y_i,\qquad\sum_{i\in\mathcal{N}_k(x)}w_i(x)=1 \end{equation*}\]

其中权重通常取为距离的单调递减函数。

KNN属于一种非参数学习方法,其核心思想是在局部邻域中进行统计决策。由于KNN不需要通过训练估计一组模型参数,其主要计算开销通常发生在预测阶段:对于每个待预测样本,需要在训练样本中寻找距离最近的若干个样本。最直接的方法是计算待预测样本与所有训练样本之间的距离;当训练样本较多时,这一过程的计算成本较高。为此,可以预先根据训练样本在特征空间中的位置关系,以某种结构对样本进行组织和存储,使得近邻查询过程中能够直接排除部分不可能成为近邻的样本,从而减少实际需要计算的距离数量。KD树(k-dimensional tree)和球树(ball tree)正是基于这一思想构造的两类近邻搜索方法。

16.4.1 基于距离下界的精确搜索

下文设特征空间\(X\subseteq\mathbb{R}^{p}\),查询点为\(q\in X\),样本索引集为\(\mathcal{I}_0={1,2,\dots,n}\),并设\(1\leqslant k\leqslant n\)。直接搜索需要计算\(q\)与全部\(n\)个样本\(x_1,\dots,x_n\)之间的距离,再从中选取距离最小的\(k\)个样本。为了避免在搜索过程中反复对所有已考察样本进行排序,可以始终只保留当前距离\(q\)最近的\(k\)个候选样本。若当前候选集合记为\(\mathcal{N}_k(q)\),则只需记录其中距离\(q\)最远的样本;当考察到新的样本\(x_i\)时,若\(x_i\)比该样本更接近\(q\),便用\(x_i\)替换它。这样,在任意时刻都只需维护至多\(k\)个候选样本。

当多个样本与\(q\)的距离相同时,\(k\)个最近邻一般并不唯一。为避免这一不确定性,下文约定:若两个样本到\(q\)的距离相同,则认为样本索引较小者更接近\(q\)。因此,对任意\(i,j\in\mathcal{I}_0\),规定\(x_i\)优先于\(x_j\)当且仅当:

\[\begin{equation*} \rho(q,x_i)<\rho(q,x_j)\quad\text{或}\quad\rho(q,x_i)=\rho(q,x_j),\;i<j \end{equation*}\]

设\(\mathcal{I}\subseteq\mathcal{I}_0\)为一组尚待考察的样本索引。若能够根据这组样本在特征空间中的位置,得到查询点\(q\)到其中所有样本距离的一个共同下界\(b(q,\mathcal{I})\),即:

\[\begin{equation*} b(q,\mathcal{I})\leqslant\rho(q,x_i),\quad\forall i\in\mathcal{I} \end{equation*}\]

那么只需将这一距离下界与当前已经得到的第\(k\)近样本的距离进行比较。若当前已经找到至少\(k\)个候选样本,并且:

\[\begin{equation*} b(q,\mathcal{I})>\tau \end{equation*}\]

其中\(\tau\)表示当前第\(k\)近候选样本到\(q\)的距离,则对于任意\(i\in I\)都有:

\[\begin{equation*} \rho(q,x_i)>\tau \end{equation*}\]

因此,这组样本中不可能包含最终的\(k\)个最近邻,可以将其整体排除,而无须逐一计算其中每个样本与\(q\)之间的距离。

KD树与球树的基本作用,正是以不同方式将训练样本逐步划分为若干具有一定几何结构的样本集合,并为这些集合构造易于计算的距离下界。两者的主要区别在于如何划分和描述这些样本集合,以及由此如何计算查询点到该集合的距离下界。在此基础上,近邻搜索便可以通过不断比较距离下界与当前第k近距离,排除不可能包含近邻的样本集合,从而减少实际需要计算的样本距离。构建树后的精确搜索算法如下算法 16.6所示。

Algorithm 16.6 Exact \(k\)-Nearest Neighbour Search by Lower-Bound Pruning

Input: Root node \(T\), query point \(q\), number of neighbours \(k\), distance function \(\rho\), distance lower bound \(b\)

Output: Exact \(k\)-nearest neighbour index set \(\mathcal{N}_k(q)\)

\(\mathcal{N}_k(q)\gets\varnothing,\;\tau\gets+\infty\)

function Update(\(i\))

if \(|\mathcal{N}_k(q)|<k\) then

\(\mathcal{N}_k(q)\gets\mathcal{N}_k(q)\cup\{i\}\)

else

Choose \(j\in\mathcal{N}_k(q)\) such that \(\rho(q,x_j)=\displaystyle\max_{l\in\mathcal{N}_k(q)}\rho(q,x_l)\) and \(j\) is the largest index among samples attaining this distance

if \(\rho(q,x_i)<\rho(q,x_j)\) or \(\Big(\rho(q,x_i)=\rho(q,x_j) \textbf{ and } i<j\Big)\) then

\(\mathcal{N}_k(q)\gets \bigl(\mathcal{N}_k(q)\setminus\{j\}\bigr)\cup\{i\}\)

end if

end if

if \(|\mathcal{N}_k(q)|=k\) then

\(\tau\gets \displaystyle\max_{j\in\mathcal{N}_k(q)}\rho(q,x_j)\)

else

\(\tau\gets+\infty\)

end if

end function

function Search(\(V\))

Let \(\mathcal{I}_V\subseteq\mathcal{I}_0\) denote the sample index set contained in node \(V\)

if \(V=\texttt{None}\) or \(b(q,\mathcal{I}_V)>\tau\) then

return

end if

if \(V\) is a leaf node then

for \(i\in\mathcal{I}_V\) do

Update(\(i\))

end for

return

end if

Let \(V_L\) and \(V_R\) be the two child nodes of \(V\). Relabel \(V_L,V_R\) as \(V_1,V_2\) such that \(b(q,\mathcal{I}_{V_1})\leqslant b(q,\mathcal{I}_{V_2})\)

Search(\(V_1\))

if \(b(q,\mathcal{I}_{V_2})\leqslant\tau\) then

Search(\(V_2\))

end if

end function

Search(\(T\))

Sort \(\mathcal{N}_k(q)\) by increasing distance to \(q\); break ties by increasing sample index

return \(\mathcal{N}_k(q)\)

16.4.2 KD树

16.4.2.1 树的构建

KD树通过与坐标轴垂直的超平面反复划分特征空间。完整构建过程如算法 16.7所示。我们约定:当多个特征同时取得最大范围时选择索引最小的特征,当多个样本同时取得最远距离时选择索引最小的样本。

Algorithm 16.7 Build a KD Tree

Input: Feature vectors \(\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^{p}\), leaf capacity \(1\leqslant m_{\mathrm{leaf}}\leqslant n\)

Output: Root node \(T\) of the KD tree

\(\mathcal{I}_0\gets\{1,2,\dots,n\}\)

function BuildKD(\(\mathcal{I}\))

\(m\gets|\mathcal{I}|\)

\(l_j\gets\min\limits_{i\in\mathcal{I}}x_{ij}\), \(u_j\gets\max\limits_{i\in\mathcal{I}}x_{ij}\), \(j=1,2,\dots,p\)

\(B\gets\prod\limits_{j=1}^{p}[l_j,u_j]\)

if \(m\leqslant m_{\mathrm{leaf}}\) or \(\max\limits_{1\leqslant j\leqslant p}(u_j-l_j)=0\) then

return A leaf node storing \(\mathcal{I}\) and \(B\)

end if

\(s\gets\underset{1\leqslant j\leqslant p}{\arg\max}\;(u_j-l_j)\)

Arrange \(\mathcal{I}\) as \(i_{(1)},i_{(2)},\dots,i_{(m)}\) such that \(x_{i_{(1)}s}\leqslant x_{i_{(2)}s}\leqslant\cdots\leqslant x_{i_{(m)}s}\), with smaller sample indices preceding larger ones whenever the corresponding values are equal

\(h\gets\lfloor m/2\rfloor\)

\(\mathcal{I}_L\gets\{i_{(1)},\dots,i_{(h)}\}\), \(\mathcal{I}_R\gets\{i_{(h+1)},\dots,i_{(m)}\}\)

\(a\gets\dfrac{x_{i_{(h)}s}+x_{i_{(h+1)}s}}{2}\)

Create an internal node \(V\) storing bounding box \(B\)

\(V.\text{left}\gets\operatorname{BuildKD}\!\left(\mathcal{I}_L\right)\)

\(V.\text{right}\gets\operatorname{BuildKD}\!\left(\mathcal{I}_R\right)\)

return \(V\)

end function

return BuildKD(\(\mathcal{I}_0\))

note 16.16. 在算法 16.7中,内部节点选择满足:

\[\begin{equation*} s\in \underset{1\leqslant j\leqslant p}{\arg\max} \;(u_j-\ell_j) \end{equation*}\]

的坐标作为切分坐标。若包围盒在某一方向上具有很大的跨度,则即使节点中实际没有距离\(q\)很近的样本,其包围盒仍可能延伸到\(q\)附近,使得\(b(q,V)\)较小,从而无法剪枝。因此,优先沿跨度最大的方向切分,可以使不同子节点在空间上更加分离,并提高距离下界剪枝的有效性。

需要注意的是,若不同特征具有不同的尺度范围,则数值范围较大的特征会更容易被选为切分坐标。因此,通常应在构建KD树之前对特征进行标准化。

16.4.2.2 距离下界

KNN中所采用的距离不同,相应的节点距离下界计算方式也有所不同。下面以欧氏距离为例说明其具体构造。

对查询点\(q=(q_1,q_2,\dots,q_p)^{\top}\)与节点\(V\)的包围盒\(B_V\),定义第\(j\)个坐标上到区间\([l_{Vj},u_{Vj}]\)的距离为:

\[\begin{equation*} \delta_{Vj}(q)=\max\{l_{Vj}-q_j,0,q_j-u_{Vj}\} \end{equation*}\]

定义\(q\)到\(B_V\)的距离为:

\[\begin{equation*} b_{\mathrm{KD}}(q,V)=\left[\sum_{j=1}^{p}\delta_{Vj}^{2}(q)\right]^{1/2} \end{equation*}\]

对任意\(i\in\mathcal{I}_V\),由\(x_i\in B_V\)可知\(|q_j-x_{ij}|\geqslant\delta_{Vj}(q)\),所以:

\[\begin{equation*} \rho(q,x_i)=\left[\sum_{j=1}^{p}(q_j-x_{ij})^2\right]^{1/2}\geqslant\left[\sum_{j=1}^{p}\delta_{Vj}^{2}(q)\right]^{1/2}=b_{\mathrm{KD}}(q,V) \end{equation*}\]

将算法 16.6中的\(b(q,V)\)取为\(b_{\mathrm{KD}}(q,V)\),即可得到欧氏距离下KD树上的精确\(k\)近邻搜索。

note 16.17. 画数轴便可知\(|q_j-x_{ij}|\geqslant\delta_{Vj}(q)\)成立。图 16.9中为\(q_j<l_{Vj}\)时的情况,\(q_j>u_{Vj}\)时完全对称,\(q_j\in[l_{Vj},u_{Vj}]\)时\(\delta_{Vj}(q)=0\)。

TikZ 数学图示
图 16.9 坐标距离下界δVj(q)的几何意义

16.4.3 球树

16.4.3.1 树的构建

球树的子节点划分方法并不唯一。算法 16.8给出一种构建方法。首先选择距离当前中心\(c\)最远的样本\(x_{i_a}\),再选择距离\(x_{i_a}\)最远的样本\(x_{i_b}\),将二者作为近似的直径端点。对任意样本\(x_i\),定义相对距离得分:

\[\begin{equation*} g_i=\rho(x_i,x_{i_a})-\rho(x_i,x_{i_b}) \end{equation*}\]

\(g_i\)越小,表示\(x_i\)相对更接近\(x_{i_a}\);\(g_i\)越大,表示\(x_i\)相对更接近\(x_{i_b}\)。

Algorithm 16.8 Build a Ball Tree

Input: Feature vectors \(\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^{p}\), leaf capacity \(1\leqslant m_{\mathrm{leaf}}\leqslant n\)

Output: Root node \(T\) of the ball tree

\(\mathcal{I}_0\gets\{1,2,\dots,n\}\)

function BuildBall(\(\mathcal{I}\))

\(m\gets|\mathcal{I}|,\;c\gets \underset{c\in\mathbb{R}^{p}}{\arg\min}\sum\limits_{i\in\mathcal{I}}\rho(x_i,c),\;r\gets\max\limits_{i\in\mathcal{I}}\rho(c,x_i)\)

\(\mathcal{B}\gets\{x\in\mathbb{R}^{p}:\rho(c,x)\leqslant r\}\)

if \(m\leqslant m_{\mathrm{leaf}}\) or \(r=0\) then

return A leaf node storing \(\mathcal{I}\) and \(\mathcal{B}\)

end if

\(i_a\gets\underset{i\in\mathcal{I}}{\arg\max}\;\rho(c,x_i)\)

\(i_b\gets\underset{i\in\mathcal{I}}{\arg\max}\;\rho(x_{i_a},x_i)\)

\(g_i\gets\rho(x_i,x_{i_a})-\rho(x_i,x_{i_b})\), \(i\in\mathcal{I}\)

Arrange \(\mathcal{I}\) as \(i_{(1)},i_{(2)},\dots,i_{(m)}\) such that \(g_{i_{(1)}}\leqslant g_{i_{(2)}}\leqslant\cdots\leqslant g_{i_{(m)}}\), with smaller sample indices preceding larger ones whenever the corresponding values are equal

\(h\gets\lfloor m/2\rfloor\)

\(\mathcal{I}_L\gets\{i_{(1)},\dots,i_{(h)}\}\), \(\mathcal{I}_R\gets\{i_{(h+1)},\dots,i_{(m)}\}\)

Create an internal node \(V\) storing bounding ball \(\mathcal{B}\)

\(V.\text{left}\gets\operatorname{BuildBall}\!\left(\mathcal{I}_L\right)\)

\(V.\text{right}\gets\operatorname{BuildBall}\!\left(\mathcal{I}_R\right)\)

return \(V\)

end function

return BuildBall(\(\mathcal{I}_0\))

16.4.3.2 距离下界

设节点\(V\)所包含样本的指标集合为\(\mathcal{I}_V\),其包围球为:

\[\begin{equation*} B_V=\left\{x\in\mathbb{R}^{p}:\rho(c_V,x)\leqslant r_V\right\} \end{equation*}\]

其中\(c_V\)和\(r_V\)分别为包围球的中心与半径。由于\(x_i\in B_V\)对任意\(i\in\mathcal{I}_V\)均成立,由三角不等式可得:

\[\begin{equation*} \rho(q,x_i)\geqslant\rho(q,c_V)-\rho(c_V,x_i)\geqslant\rho(q,c_V)-r_V \end{equation*}\]

另一方面,由\(\rho(q,x_i)\geqslant0\)可知:

\[\begin{equation*} \rho(q,x_i)\geqslant\max\left\{0,\rho(q,c_V)-r_V\right\},\quad\forall\;i\in\mathcal{I}_V \end{equation*}\]

因此定义查询点\(q\)到节点\(V\)的距离下界为:

\[\begin{equation*} b_{\mathrm{Ball}}(q,V)=\max\left\{0,\rho(q,c_V)-r_V\right\} \end{equation*}\]

将算法 16.6中的节点距离下界\(b(q,V)\)取为\(b_{\mathrm{Ball}}(q,V)\),即可得到基于球树的精确\(k\)近邻搜索。

与KD树所不同的是,这里的距离下界自然地兼容了所有的距离度量,而无需根据KNN中距离度量的选择来设计相应的距离下界。

16.5 核方法

Definition 16.24. 设\(X\)是空间,\(k:X\times X\to\mathbb{R}\)被称为核函数(kernel function)。

  1. 若对任意的\(x_1,x_2\in X\)有\(k(x_1,x_2)=k(x_2,x_1)\),则称\(k\)是对称核;

  2. 若对任意的\(x_1, x_2, \dots, x_{n}\in X\)和任意的\(a_1, a_2, \dots, a_{n}\in\mathbb{R}\)有:

    \[\begin{equation*} \sum_{i=1}^{n}\sum_{j=1}^{n}a_ia_jk(x_i,x_j)\geqslant0 \end{equation*}\]

    则称\(k\)为半正定核。

Definition 16.25. 设\(X\)是空间,\(k:X\times X\to\mathbb{R}\)。对任意的\(x_1, x_2, \dots, x_{n}\in X\),称\(K=\Big(k(x_i,x_j)\Big)\in M_{n\times n}(\mathbb{R})\)为\(n\)维核矩阵或Gram矩阵。

Definition 16.26. 设\(X\)是空间,\(k:X\times X\to\mathbb{R}\),\(Y\)是Hilbert空间。若存在\(\varphi:X\to Y\)使得对任意的\(x_1,x_2\in X\)有:

\[\begin{equation*} k(x_1,x_2)=\Big(\varphi(x_1),\varphi(x_2)\Big) \end{equation*}\]

则称\(\varphi\)是\(k\)关于\(Y\)的一个特征映射。

Theorem 16.1. 设\(X\)是空间,\(k:X\times X\to\mathbb{R}\),\(Y\)是Hilbert空间。若\(k\)存在关于\(Y\)的特征映射,则\(k\)是半正定核。

证明. 对任意的\(x_1, x_2, \dots, x_{n}\in X\)和任意的\(a_1, a_2, \dots, a_{n}\in\mathbb{R}\)有:

\[\begin{align*} \sum_{i=1}^{n}\sum_{j=1}^{n}a_ia_jk(x_i,x_j)&=\sum_{i=1}^{n}\sum_{j=1}^{n}a_ia_j\Big(\varphi(x_i),\varphi(x_j)\Big)=\left(\sum_{i=1}^{n}a_i\varphi(x_i),\sum_{i=1}^{n}a_j\varphi(x_i)\right) \\ &=\left\|\sum_{i=1}^{n}a_i\varphi(x_i)\right\|^2\geqslant0 \end{align*}\]

◻

Definition 16.27. 若Hilbert空间\(Y\)是定义在\(X\)上的实值函数空间,则称\(Y\)为\(X\)上的Hilbert函数空间。

Definition 16.28. 设\(Y\)是\(X\)上的实Hilbert函数空间。若对任意的\(x\in X\),泛函:

\[\begin{equation*} L_x:Y\to\mathbb{R},\quad\forall\;f\in Y,\;L_x(f)=f(x) \end{equation*}\]

都是有界线性泛函,则称\(Y\)为\(X\)上的再生核Hilbert空间(reproducing kernel Hilbert space, RKHS)。

Theorem 16.2. 若\(Y\)是\(X\)上的RKHS,则对于任意的\(x\in X\),存在唯一的\(k_x\in Y\)使得对于任意的\(f\in Y\)有\(f(x)=(f,k_x)\)。

证明. 因为\(Y\)是\(X\)上的RKHS,所以对于任意的\(x\in X\),泛函:

\[\begin{equation*} L_x:Y\to\mathbb{R},\quad\forall\;f\in Y,\;L_x(f)=f(x) \end{equation*}\]

都是有界线性泛函。由定理 3.26(1)可知\(L_x\)是连续泛函,根据定理 3.34可知存在唯一的\(k_x\in Y\)使得对任意的\(f\in Y\)有\(L_x(f)=f(x)=(f,k_x)\)。 ◻

Definition 16.29. 设\(Y\)是\(X\)上的RKHS,\(x\in X\)。称满足对于任意的\(f\in Y\)有\(f(x)=(f,k_x)\)的\(k_x\in Y\)为\(x\)关于\(Y\)的再生核(reproducing kernel)。

note 16.18. 再生核族\(\{k_x:x\in X\}\)可导出核函数\(k(x_1,x_2)=k_{x_1}(x_2)\)。

Property 16.5.1. 设\(Y\)是\(X\)上的RKHS,\(k\)是\(Y\)上的再生核族\(\{k_x:x\in X\}\)导出的核函数,则:

  1. 对任意的\(x_1,x_2\in X\)有\(k(x_1,x_2)=(k_{x_1},k_{x_2})\);

  2. \(k\)是对称核;

  3. \(k\)是半正定核;

证明. (1)取任意的\(x_1,x_2\in X\),由定理 16.2可知\(k_{x_1},k_{x_2}\in Y\),所以\(k_{x_1}(x_2)=k(x_1,x_2)=(k_{x_1},k_{x_2})\)。

(2)在(1)中其实有\(k_{x_1}(x_2)=k(x_1,x_2)=(k_{x_1},k_{x_2})=(k_{x_2},k_{x_1})=k_{x_2}(x_1)=k(x_2,x_1)\)。

(3)由定理 16.1立即可得。 ◻

Theorem 16.3. (Aronszajn Theorem)

设\(X\)是空间,\(k:X\times X\to\mathbb{R}\)。\(k\)是半正定核当且仅当存在\(X\)上的再生核Hilbert空间\(Y\),对任意的\(x\in X\),\(k(x,\cdot)\)是\(x\)关于\(Y\)的再生核。

证明. (1)充分性:由性质 16.5.1(2)立即可得。 ◻

Theorem 16.4. (Representor Theorem)

设\(X\)是空间,\(k:X\times X\to\mathbb{R}\)是半正定核,由定理 16.3,记\(Y\)是由\(k\)导出的\(X\)上的RKHS,\(\{(x_i,y_i)\}_{i=1}^{n}\)为训练样本。对于优化问题:

\[\begin{equation*} \min_{f\in Y}\;\Omega[f(x_1),f(x_2),\dots,f(x_n)]+\lambda||f||^2,\quad\lambda>0 \end{equation*}\]

其任一最优解\(f^*\)都可以表示为:

\[\begin{equation*} f(x)=\sum_{i=1}^{n}a_ik(x,x_i) \end{equation*}\]

其中\(a_i\in\mathbb{R}\)。

证明. 由性质 1.1.5(1),取\(Y\)的子空间:

\[\begin{equation*} Y_n=\operatorname{span}\{k_{x_1},k_{x_2},\dots,k_{x_n}\} \end{equation*}\]

由性质 1.1.5(2)、性质 3.4.4(2)和性质 3.2.5(1)可知\(Y_n\)是闭的。根据定理 3.28可知对任意的\(f\in Y\),存在下述唯一的分解:

\[\begin{equation*} f=f_1+f_2,\quad f_1\in Y_n,\;f_2\in Y_n^{\perp} \end{equation*}\]

由性质 3.4.6(1)可知\(||f||^2=||f_1||^2+||f_2||^2\)。因为\(f_2\in Y_n^{\perp}\),所以对任意的\(i=1,2,\dots,n\)有\(f_2\perp k_{x_i}\),于是:

\[\begin{equation*} (f_2,k_{x_i})=f_2(x_i)=0 \end{equation*}\]

所以\(f(x_i)=f_1(x_i)\),优化问题可被改写为:

\[\begin{equation*} \min_{f\in Y}\Omega[f_1(x_1),f_1(x_2),\dots,f_1(x_n)]+\lambda[||f_1||^2+||f_2||^2],\quad\lambda>0 \end{equation*}\]

若\(f^*\)是最优解,则\(f^*\)必然在\(Y_n\)中,否则\(f^*\)在\(Y_n\)上的正交投影对应的目标函数值一定比\(f^*\)对应的目标函数值小。 ◻

16.6 支持向量机

考虑二分类问题。给定训练数据:

\[\begin{equation*} \mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n},\quad x_i\in\mathbb{R}^{p},\quad y_i\in\{-1,+1\} \end{equation*}\]

一个直观的分类思路是将每个样本\(x_i\)看作特征空间\(\mathbb{R}^{p}\)中的一个点,并尝试寻找一个超平面,使得属于不同类别的样本点尽可能分布在超平面的两侧。于是我们可以利用训练数据来确定这一超平面,并据此判断新的样本位于超平面的哪一侧,从而完成对新数据的分类。

根据解析几何中的知识我们知道一个超平面可以表示为:

\[\begin{equation*} \{x\in\mathbb{R}^{p}:w^{\top}x+b=0\} \end{equation*}\]

其中\(w\in\mathbb{R}^{p}\)为超平面的法向量,\(b\in\mathbb{R}\)为截距。该超平面将特征空间划分为两个半空间:

\[\begin{equation*} \{x:w^{\top}x+b>0\}\quad\{x:w^{\top}x+b<0\} \end{equation*}\]

因此,可以根据样本位于超平面的哪一侧来定义分类规则:

\[\begin{equation*} f(x)=\operatorname{sign}(w^{\top}x+b) \end{equation*}\]

若希望训练样本能够被该超平面正确分类,则对于每个\((x_i,y_i)\)都应满足:

\[\begin{equation*} y_i(w^{\top}x_i+b)>0 \end{equation*}\]

当存在某组\((w,b)\)使上述条件对所有训练样本同时成立时,称该训练集是线性可分的。

TikZ 数学图示
图 16.10 二维情形下的线性分类示意图。

当训练数据线性可分时,通常存在不止一个能够正确分类所有训练样本的超平面,于此同时仅仅要求训练样本被正确分类,并不足以说明某个分类超平面是合适的:如果某些样本距离分类边界非常近,那么样本发生轻微扰动时,其分类结果便可能发生改变。因此,我们希望分类超平面在正确分离训练数据的同时尽可能远离所有训练样本,这样的超平面具有最优的抗扰动性。这便是支持向量机(support vector machine, SVM)的思想。

对于任意被正确分类的样本\(x_i\),其到分类超平面\(w^{\top}x+b=0\)的欧氏距离为:

\[\begin{equation*} \frac{|w^{\top}x_i+b|}{||w||}=\frac{y_i(w^{\top}x_i+b)}{||w||} \end{equation*}\]

因此,SVM的优化问题为:

\[\begin{equation*} \begin{aligned} \max_{w\in\mathbb{R}^{p},b\in\mathbb{R}}\min_{i=1,\dots,n}\quad &\frac{y_i(w^{\top}x_i+b)}{||w||} \\ \operatorname{s.t.}\quad&y_i(w^{\top}x_i+b)\geqslant1,\quad i=1,\dots,n \end{aligned} \end{equation*}\]

对于任意的\(c>0\),\((w,b)\)与\((cw,cb)\)定义了完全相同的分类超平面,于是可以进行规范化:

\[\begin{equation*} \min_{i=1,\dots,n}y_i(w^{\top}x_i+b)=1 \end{equation*}\]

所以SVM的优化问题等价于:

\[\begin{equation*} \begin{aligned} \min_{w,b}\quad&\frac{1}{2}||w||^2\\ \operatorname{s.t.}\quad&y_i(w^{\top}x_i+b)\geqslant1,\quad i=1,\dots,n \end{aligned} \end{equation*}\]

16.7 主成分分析

设总体\(H\)中的每个个体由\(p\)个特征刻画。当\(p\)较大时,数据所处的空间维数随之升高,不仅难以直接把握其整体结构,其中还可能包含大量冗余的信息。因而我们希望寻找一个维数较低的空间,使原数据能够在其中得到较为简洁的表示,同时尽可能保留其主要的分布特征,减少由降维所造成的信息损失。

从几何上看,最直接的思路是寻找一个\(r\)维空间,其中\(r<p\),并用原数据在该空间中的表示代替原来的\(p\)维表示。若将这个\(r\)维空间限制为线性子空间,那么它必然经过原点,因此仅考虑线性子空间会对低维空间的位置施加不必要的限制。更自然的做法是考虑所有形如:

\[\begin{equation*} a+E \end{equation*}\]

的空间,其中\(a\in H\),\(E\)是\(H\)的\(r\)维线性子空间。

在给定这样一个\(r\)维空间之后,其中对某个数据点最自然的近似是该空间中与它距离最近的点。为了在线性空间中刻画距离,并进一步利用正交性描述最近点,我们在\(H\)上赋予实内积及其诱导的范数。由性质 3.4.4(2)可知此时\(H\)构成实Hilbert空间。由\(a+E\)的凸性、性质 3.2.6(1)和定理 3.27可知对于任意\(x\in H\),在\(a+E\)中存在唯一与\(x\)距离最近的点,该点即为\(x\)在\(a+E\)上的正交投影。这便自然给出了衡量降维信息损失的方式:以一个点到其正交投影的平方距离衡量该点在降维过程中产生的信息损失,并进一步以总体意义下的平均平方投影误差衡量整个低维表示所造成的信息损失。主成分分析(principal component analysis, PCA)的基本思想就是在所有\(r\)维空间中寻找使这一平均平方投影误差最小的空间。

记\(\mathcal{P}_E\)为从\(H\)到\(E\)上的正交投影算子。任取\(H\)中的点\(x\),则\(x\)在\(a+E\)上的正交投影\(w\)满足:

\[\begin{equation*} w=\underset{y\in a+E}{\arg\min}\;||x-y||^2 \end{equation*}\]

对任意的\(y\in a+E\),存在\(z\in E\)使得\(y=a+z\),所以:

\[\begin{equation*} w=a+\underset{z\in E}{\arg\min}\;||x-a-z||^2 \end{equation*}\]

因为:

\[\begin{equation*} x-a=\mathcal{P}_E(x-a)+(\mathcal{I}-\mathcal{P}_E)(x-a) \end{equation*}\]

于是:

\[\begin{align*} w&=a+\underset{z\in E}{\arg\min}\;||\mathcal{P}_E(x-a)+(\mathcal{I}-\mathcal{P}_E)(x-a)-z||^2 \\ &=a+\underset{z\in E}{\arg\min}\;||[\mathcal{P}_E(x-a)-z]+(\mathcal{I}-\mathcal{P}_E)(x-a)||^2 \end{align*}\]

由直和的定义、定理 3.33和性质 3.4.6(1)可知:

\[\begin{align*} w&=a+\underset{z\in E}{\arg\min}\;[||\mathcal{P}_E(x-a)-z||^2+||(\mathcal{I}-\mathcal{P}_E)(x-a)||^2] \\ &=a+\underset{z\in E}{\arg\min}\;||\mathcal{P}_E(x-a)-z||^2 \end{align*}\]

所以:

\[\begin{equation} \tag{16.1} w=a+\mathcal{P}_E(x-a) \end{equation}\]

于是:

\[\begin{equation*} x-w=x-a-\mathcal{P}_E(x-a)=(\mathcal{I}-\mathcal{P}_E)(x-a) \end{equation*}\]

平方投影误差为\(||(\mathcal{I}-\mathcal{P}_E)(x-a)||^2\)。

Definition 16.30. 设\(f\)为从概率空间\((X,\mathscr{F},P)\)到有限维实Hilbert空间\(H\)上的可测映射。对于给定的\(r<\dim H\),记\(\mathcal{P}_E\)为\(H\)到其上\(r\)维线性子空间\(E\)上的正交投影算子。PCA是如下优化问题:

\[\begin{equation*} \underset{\substack{a\in H\\E\subseteq H,\;\dim E=r}}{\arg\min}\;\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-a)||^2] \end{equation*}\]

note 16.19. 上述定义使用有限维实Hilbert空间的语言,是为了强调PCA所依赖的本质结构是内积所赋予的正交结构以及由此产生的正交投影。然而在一般的Hilbert空间中,进一步讨论需要借助Bochner积分和紧自伴算子的谱等理论。由于这些内容并非本节讨论的重点,以下均取\(H=\mathbb{R}^m\)并赋予标准内积。该情形已经涵盖通常统计学与数据科学中有限维数据的PCA问题,同时避免了引入不必要的泛函分析工具。需要注意,此时\(\mathcal{I}\)和\(\mathcal{P}_E\)可以分别表示为单位矩阵和正交投影阵。

16.7.1 总体主成分分析

16.7.1.1 问题化简

设\(\mu=\operatorname{E}(f)\),由内积导出范数的定义和性质 5.4.3(6)可知:

\[\begin{align*} &\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-a)||^2]=\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu+\mu-a)||^2] \\ =&\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)+(\mathcal{I}-\mathcal{P}_E)(\mu-a)||^2] \\ =&\operatorname{E}\left[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2+||(\mathcal{I}-\mathcal{P}_E)(\mu-a)||^2+2\Big((\mathcal{I}-\mathcal{P}_E)(f-\mu),(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big)\right] \\ =&\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2]+||(\mathcal{I}-\mathcal{P}_E)(\mu-a)||^2+2\operatorname{E}\left[\Big((\mathcal{I}-\mathcal{P}_E)(f-\mu),(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big)\right] \end{align*}\]

根据性质 5.4.3(6)和性质 3.4.5(1)可得:

\[\begin{align*} &\operatorname{E}\left[\Big((\mathcal{I}-\mathcal{P}_E)(f-\mu),(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big)\right]=\Big(\operatorname{E}[(\mathcal{I}-\mathcal{P}_E)(f-\mu)],(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big) \\ =&\Big((\mathcal{I}-\mathcal{P}_E)\operatorname{E}(f-\mu),(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big)=\Big(0,(\mathcal{I}-\mathcal{P}_E)(\mu-a)\Big)=0 \end{align*}\]

于是:

\[\begin{equation*} \operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-a)||^2]=\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2]+||(\mathcal{I}-\mathcal{P}_E)(\mu-a)||^2 \end{equation*}\]

对于任意给定的\(r\)维子空间\(E\),上式右侧第一项与\(a\)无关,而第二项非负,因此关于\(a\)的最小值在且仅在:

\[\begin{equation*} (\mathcal{I}-\mathcal{P}_E)(\mu-a)=\mathbf{0} \end{equation*}\]

时取得,即\(\mu-a=\mathcal{P}_E(\mu-a)\)。由正交投影阵的定义可知\(\mu-a\in E\),根据定理 1.9可知:

\[\begin{equation*} a+E=\mu+E \end{equation*}\]

所以PCA的优化问题可化为:

\[\begin{equation*} \underset{E\subseteq H,\;\dim E=r}{\arg\min}\;\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2] \end{equation*}\]

因为:

\[\begin{equation*} f-\mu=\mathcal{P}_E(f-\mu)+(\mathcal{I}-\mathcal{P}_E)(f-\mu) \end{equation*}\]

由直和的定义、定理 3.33和性质 3.4.6(1)可知:

\[\begin{equation*} ||f-\mu||^2=||\mathcal{P}_E(f-\mu)||^2+||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2 \end{equation*}\]

根据性质 5.4.3(6)可得:

\[\begin{equation*} \operatorname{E}(||f-\mu||^2)=\operatorname{E}[||\mathcal{P}_E(f-\mu)||^2]+\operatorname{E}[||(\mathcal{I}-\mathcal{P}_E)(f-\mu)||^2] \end{equation*}\]

因为\(\operatorname{E}(||f-\mu||^2)\)与\(E\)的选择无关,所以PCA优化问题可以化为:

\[\begin{equation*} \underset{E\subseteq H,\;\dim E=r}{\arg\max}\;\operatorname{E}[||\mathcal{P}_E(f-\mu)||^2] \end{equation*}\]

由于直接在所有\(r\)维子空间构成的集合上进行优化较为困难,我们转而通过子空间的一组基来参数化\(E\),从而将关于子空间的优化转化为关于基矩阵的优化。具体而言,对于任意\(r\)维子空间\(E\subseteq\mathbb{R}^m\),可选取\(E\)的一组基\(u_1, u_2, \dots, u_{r}\),并令:

\[\begin{equation*} U=(u_1, u_2, \dots, u_{r})\in M_{m\times r}(\mathbb{R}) \end{equation*}\]

则\(\mathcal{M}(U)=E\),因此对子空间\(E\)的选择可以等价地转化为对满列秩矩阵\(U\)的选择。根据性质 2.8.2(2)可知此时:

\[\begin{equation*} \mathcal{P}_E=U(U^{\top}U)^{-1}U^{\top} \end{equation*}\]

由上式可以发现,若使用任意一组基,则正交投影阵的表示中始终包含\((U^{\top}U)^{-1}\),不利于后续计算,于是我们可以进一步要求\(u_1, u_2, \dots, u_{r}\)为\(E\)的一组标准正交基。此时\(U^{\top}U=I_r\),从而正交投影阵简化为:

\[\begin{equation*} \mathcal{P}_E=UU^{\top} \end{equation*}\]

于是PCA优化问题就被化为了:

\[\begin{equation*} \underset{\substack{U^{\top}U=I_r\\U\in M_{m\times r}(\mathbb{R})}}{\arg\max}\;\operatorname{E}[||UU^{\top}(f-\mu)||^2] \end{equation*}\]

根据性质 2.1.2(4)可知:

\[\begin{align*} &||UU^{\top}(f-\mu)||^2=[UU^{\top}(f-\mu)]^{\top}UU^{\top}(f-\mu) \\ =&(f-\mu)^{\top}UU^{\top}UU^{\top}(f-\mu)=(f-\mu)^{\top}UU^{\top}(f-\mu) \end{align*}\]

由性质 2.1.5(3)可得:

\[\begin{equation*} ||UU^{\top}(f-\mu)||^2=\operatorname{tr}[(f-\mu)^{\top}UU^{\top}(f-\mu)]=\operatorname{tr}[U^{\top}(f-\mu)(f-\mu)^{\top}U] \end{equation*}\]

根据性质 5.4.3(6)可得:

\[\begin{align*} &\operatorname{E}[||UU^{\top}(f-\mu)||^2]=\operatorname{E}\{\operatorname{tr}[U^{\top}(f-\mu)(f-\mu)^{\top}U]\} \\ =&\operatorname{tr}\{\operatorname{E}[U^{\top}(f-\mu)(f-\mu)^{\top}U]\}=\operatorname{tr}[U^{\top}\operatorname{Cov}(f)U]=\sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i \end{align*}\]

所以PCA优化问题为:

\[\begin{equation*} \underset{\substack{U^{\top}U=I_r\\U\in M_{m\times r}(\mathbb{R})}}{\arg\max}\;\sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i \end{equation*}\]

16.7.1.2 最优子空间

Theorem 16.5. 设\(\operatorname{Cov}(f)\)的特征值从大到小为\(\lambda_1, \lambda_2, \dots, \lambda_{m}\),对应的一组标准正交化特征向量为\(\varphi_1, \varphi_2, \dots, \varphi_{m}\),则:

\[\begin{equation*} \underset{\substack{U^{\top}U=I_r\\U\in M_{m\times r}(\mathbb{R})}}{\max}\;\sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i=\sum_{i=1}^{r}\lambda_i \end{equation*}\]

且\(U=(\varphi_1, \varphi_2, \dots, \varphi_{r})\)为该优化问题的一个最优解,即PCA的最优\(r\)维子空间可以取\(E=\operatorname{span}\{\varphi_1, \varphi_2, \dots, \varphi_{r}\}\)。

证明. 由性质 2.6.3(3)和定理 3.32,记\(\operatorname{Cov}(f)\)的特征值从大到小为\(\lambda_1, \lambda_2, \dots, \lambda_{m}\),对应的一组标准正交化特征向量为\(\varphi_1, \varphi_2, \dots, \varphi_{m}\)。根据定理 2.16可知在单位向量中\(\varphi_1\)使得:

\[\begin{equation*} u^{\top}\operatorname{Cov}(f)u \end{equation*}\]

取得最大值\(\lambda_1\);在与\(\varphi_1\)正交的单位向量中,\(\varphi_2\)使其取得最大值\(\lambda_2\)。依此类推,在与\(\varphi_1, \varphi_2, \dots, \varphi_{k-1}\)均正交的单位向量中,\(\varphi_k\)使其取得最大值\(\lambda_k\)。若\(u_i=\varphi_i,\;i=1,2,\dots,r\),则:

\[\begin{equation*} \sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i=\sum_{i=1}^{r}\lambda_i \end{equation*}\]

下面证明:

\[\begin{equation*} \underset{\substack{U^{\top}U=I_r\\U\in M_{m\times r}(\mathbb{R})}}{\max}\;\sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i=\sum_{i=1}^{r}\lambda_i \end{equation*}\]

对于任意满足\(U^{\top}U=I_r\)的\(U=(u_1, u_2, \dots, u_{r})\),由于\(\varphi_1, \varphi_2, \dots, \varphi_{m}\)构成\(\mathbb{R}^m\)的一组标准正交基,所以存在\(\{c_{ij}\}\subseteq\mathbb{R}\)使得:

\[\begin{equation*} u_i=\sum_{j=1}^{m}c_{ij}\varphi_j \end{equation*}\]

于是:

\[\begin{equation*} u_i^{\top}\operatorname{Cov}(f)u_i=\sum_{j=1}^{m}\lambda_jc_{ij}^2 \end{equation*}\]

所以:

\[\begin{equation*} \sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i=\sum_{i=1}^{r}\sum_{j=1}^{m}\lambda_jc_{ij}^2=\sum_{j=1}^{m}\lambda_j\sum_{i=1}^{r}c_{ij}^2 \end{equation*}\]

记:

\[\begin{equation*} \alpha_j=\sum_{i=1}^{r}c_{ij}^2 \end{equation*}\]

因为\(\varphi_1, \varphi_2, \dots, \varphi_{m}\)为标准正交基,根据定理 3.29可得:

\[\begin{equation*} \alpha_j=\sum_{i=1}^{r}c_{ij}^2=\sum_{i=1}^{r}[(u_i,\varphi_j)]^2\leqslant||\varphi_j||^2=1 \end{equation*}\]

由性质 3.4.6可知:

\[\begin{equation*} ||u_i||^2=\sum_{j=1}^{m}c_{ij}^2=1 \end{equation*}\]

于是:

\[\begin{equation*} \sum_{j=1}^{m}\alpha_j=\sum_{j=1}^{m}\sum_{i=1}^{r}c_{ij}^2=\sum_{i=1}^{r}\sum_{j=1}^{m}c_{ij}^2=\sum_{i=1}^{r}||u_i||^2=r \end{equation*}\]

所以:

\[\begin{equation*} \sum_{i=1}^{r}u_i^{\top}\operatorname{Cov}(f)u_i\leqslant\sum_{j=1}^{r}\lambda_j \end{equation*}\]

◻

Definition 16.31. 称\(Z_i=\varphi_i^{\top}(f-\mu)\)为\(f\)的第\(i\)主成分(principal component),\(\varphi_i\)为\(f\)的第\(i\)个主成分方向。

note 16.20. 由公式 16.1可知对于任意的\(y\in\mathbb{R}^{m}\),其在\(\mu+E\)上的投影为:

\[\begin{equation*} \mu+\mathcal{P}_E(y-\mu)=\mu+UU^{\top}(y-\mu) \end{equation*}\]

从上式可以看出,\(Z_i\)对应于样本\(y\)的取值\(\varphi_i^{\top}(y-\mu)\)表示该投影点相对于基点\(\mu\),在第\(i\)个主成分方向\(\varphi_i\)上的坐标。

16.7.2 样本主成分分析

假设对\(f\)进行\(n\)次独立观测得到\(n\)个样本\(x_1, x_2, \dots, x_{n}\)。我们使用样本来估计\(\operatorname{Cov}(f)\),即:

\[\begin{equation*} \hat{\mu}=\frac{1}{n}\sum_{i=1}^{n}x_i,\quad S=\frac{1}{n-1}\sum_{i=1}^{n}(x_i-\hat{\mu})(x_i-\hat{\mu})^{\top} \end{equation*}\]

由性质 2.6.3(3)和定理 3.32,记\(S\)的特征值从大到小为\(\hat{\lambda}_1, \hat{\lambda}_2, \dots, \hat{\lambda}_{m}\),对应的一组标准正交化特征向量为\(\hat{\varphi}_1, \hat{\varphi}_2, \dots, \hat{\varphi}_{m}\)。

16.7.2.1 降维表示

根据定理 16.5,对于给定的\(r<m\),令:

\[\begin{equation*} U_r=(\hat{\varphi}_1, \hat{\varphi}_2, \dots, \hat{\varphi}_{r})\in M_{m\times r}(\mathbb{R}) \end{equation*}\]

则样本PCA所确定的\(r\)维主成分子空间可以取为:

\[\begin{equation*} E_r=\operatorname{span}\{\hat{\varphi}_1, \hat{\varphi}_2, \dots, \hat{\varphi}_{r}\} \end{equation*}\]

对于任意样本\(x_i\),其在\(\hat{\mu}+E_r\)上的正交投影为:

\[\begin{equation*} \hat{\mu}+U_rU_r^{\top}(x_i-\hat{\mu}) \end{equation*}\]

而该投影点相对于基点\(\hat{\mu}\)在标准正交基\(\hat{\varphi}_1, \hat{\varphi}_2, \dots, \hat{\varphi}_{r}\)下的坐标为:

\[\begin{equation*} z_i=U_r^{\top}(x_i-\hat{\mu})\in\mathbb{R}^r \end{equation*}\]

其中第\(j\)个坐标为:

\[\begin{equation*} z_{ij}=\hat{\varphi}_j^{\top}(x_i-\hat{\mu}) \end{equation*}\]

在PCA中,我们认为\(z_i\)为样本\(x_i\)经过降维后的\(r\)维表示。称\(z_{ij}\)为样本\(x_i\)在第\(j\)个主成分上的主成分得分。

16.7.2.2 主成分维数的选择

PCA中维数\(r\)决定了保留的信息量与降维程度之间的权衡。可以验证\(S\)为半正定矩阵,于是由定理 2.18(3.5)可知\(S\)的特征值满足:

\[\begin{equation*} \hat{\lambda}_1\geqslant\hat{\lambda}_2\geqslant\cdots\geqslant\hat{\lambda}_m\geqslant0 \end{equation*}\]

根据定理 16.5,第\(i\)个主成分所对应的样本方差为\(\hat{\lambda}_i\),而样本特征的总方差为:

\[\begin{equation*} \operatorname{tr}(S)=\sum_{i=1}^{m}\hat{\lambda}_i \end{equation*}\]

因此,前\(r\)个主成分所保留的方差占总方差的比例为:

\[\begin{equation*} R_r=\sum_{i=1}^{r}\hat{\lambda}_i\Big/\sum_{i=1}^{m}\hat{\lambda}_i \end{equation*}\]

称\(R_r\)为前\(r\)个主成分的累计方差解释比例。给定希望保留的方差比例\(\eta\in(0,1)\),可以选择满足\(R_r\geqslant\eta\)的最小\(r\),即:

\[\begin{equation*} r=\min\left\{k:\sum_{i=1}^{k}\hat{\lambda}_i\Big/\sum_{i=1}^{m}\hat{\lambda}_i\geqslant\eta\right\} \end{equation*}\]

16.7.2.3 标准化

上述主成分维数\(r\)的选择依赖于各个主成分所解释的样本方差,因此也会受到原始特征尺度的影响。若不同特征的数值尺度存在较大差异,则尺度较大的特征通常具有更大的样本方差,从而在总方差中占据较大的比例,此时累计方差解释比例也会相应地受到其主导。于是,根据\(R_r\)选择得到的\(r\)可能主要反映少数大尺度特征的变化,而不能公平地衡量不同特征所包含的信息。为了消除这种由尺度差异造成的影响,可以在执行PCA之前对各个特征进行标准化。

16.8 聚类分析

聚类分析(clustering analysis)是一类在不依赖任何先验标签信息的前提下,根据样本之间的相似性或距离关系,对数据进行划分的方法。形式上,聚类算法为每个样本分配一个簇标签,但从方法论角度看,聚类分析并不以标签本身为研究终点,而是试图通过这些标签揭示数据中潜在的结构性特征。

更准确地说,聚类分析的本质是一种结构发现(structure discovery)方法。它旨在回答如下问题:数据是否可以被视为由若干相对同质的子群体构成?这些子群体在统计特征、生成机制或行为模式上是否存在系统性差异?因此,聚类标签应被理解为对数据结构的一种抽象描述,而非具有直接语义的分类结果。

在统计学习与数据分析框架下,聚类的目的可以从以下几个相互关联的层面来理解。

  1. 结构发现与探索性分析:通过样本间相似性关系揭示数据中潜在的分组结构与异质性特征,例如多模态、分层或局部聚集等现象。从方法论上看,聚类属于典型的探索性数据分析手段,其结果主要用于辅助理解数据结构、生成研究假设并为后续建模提供依据,而非作为最终分析结论;

  2. 数据抽象与降低复杂度:在高维或大规模数据情形下,直接以单个样本为分析对象往往会带来计算和解释上的困难。聚类通过将大量样本映射为有限个簇,实现了从”个体层面”到”群体层面”的抽象。这种抽象使得研究者可以以簇为基本单元,对数据进行更高层次的描述、比较与总结。例如,可以利用簇的中心、分布特征或代表性样本来刻画原始数据,从而提高分析的可解释性与可操作性;

  3. 异质性刻画与分群建模:在许多应用场景中,整体数据往往并非来自单一同质总体,而是由多个具有不同统计特性或生成机制的子群体混合而成。如果忽略这种异质性而直接建立统一的全局模型,往往会导致模型拟合能力下降,甚至产生误导性的结论。此时聚类分析可以帮助识别潜在的异质子群体,并为后续的分群建模提供依据。例如,可以在不同簇内分别建立回归模型、风险模型或预测模型,比较模型参数在不同簇之间的差异,从而揭示不同子群体可能遵循的不同机制;

16.8.1 评价指标

聚类评价指标的核心目标是量化:

  • 簇内紧凑性(compactness):同一簇里的点是否彼此接近;

  • 簇间分离性(separation):不同簇之间是否彼此远离;

  • 与外部标签一致性(agreement with ground truth):当存在真实标签时,聚类是否复现了真实分组。

具体而言,聚类评价指标可以分为下述几类:

  1. 内部指标(Internal Validation):只依赖数据与聚类标签(不需要真实标签);

  2. 外部指标(External Validation):需要真实标签。

需要强调的是,外部评价指标的引入并不意味着聚类任务转化为监督学习问题。外部指标的核心作用在于:当研究者在特定观测样本集中事先掌握某种参考划分时,用以分析既有标签与数据内在结构之间的一致性关系。从而对聚类算法、距离度量或参数设置进行事后评估与比较。因此,外部指标并不参与聚类过程本身,也不作为聚类目标函数进行优化,而是作为一种方法学意义上的对照标准,为研究者理解聚类结果与现实语义之间的关系提供了一种量化参照,帮助研究者理解数据自身在多大程度上支持、修正或挑战已有的分类体系。

当外部一致性较高时,说明在当前特征表示与距离度量下,数据的几何或统计结构能够较好地支持既定的类别划分;例如,在文本主题分析或基因表达数据中,若聚类结果与人工标注的主题类别或生物亚型高度一致,则可认为该标签在数据空间中具有较清晰的结构基础。相反,当外部一致性较低时,这并不必然意味着聚类算法失效,而可能揭示出标签本身的局限性:例如,在心理测量或社会科学调查中,理论上区分的若干类型可能在实际数据中呈现出连续分布或显著重叠;又如,在医学诊断中,同一疾病标签下的样本可能包含多个具有不同分子机制的亚群。此时,聚类结果与外部标签之间的不一致,反而为研究者提供了重新审视标签定义、发现潜在异质结构或子群划分的线索。

设观测样本集\(\mathcal{D}=\{x_i\}_{i=1}^n,\;x_i\in\mathbb{R}^m\),聚类输出标签\(y_i\in\{1,2,\dots,K\}\)(或包含噪声\(-1\)),第\(k\)簇为\(C_k=\{i:y_i=k\}\),簇大小\(n_k=|C_k|\),簇中心和总体均值定义为:

\[\begin{equation*} \mu_k=\frac{1}{n_k}\sum_{i\in C_k}x_i,\quad\bar{x}=\frac{1}{n}\sum_{i=1}^nx_i \end{equation*}\]

16.8.1.1 内部指标

Definition 16.32. 称\(\operatorname{WCSS}=\sum\limits_{k=1}^{K}\sum\limits_{i\in C_k}\|x_i-\mu_k\|^2\)为簇内平方和(within-cluster sum of squares, WCSS)。

Property 16.8.1. (最优WCSS关于簇数的单调性)

定义:

\[\begin{equation*} \operatorname{WCSS}^\star(K)\coloneq\min_{\{C_1,\dots,C_K\}}\;\sum_{k=1}^K \min_{\mu_k\in\mathbb{R}^m}\sum_{i\in C_k}\|x_i-\mu_k\|^2 \end{equation*}\]

则对任意\(K\geqslant1\)有\(\operatorname{WCSS}^\star(K+1)\leqslant \operatorname{WCSS}^\star(K)\)。

证明. 取任意一个划分\(C=\{C_1,\dots,C_K\}\)和一个非空簇(如 \(C_1\ne\varnothing\)),将其拆成两个非空子集\(A,B\):

\[\begin{equation*} C_1=A\cup B,\;A\ne\varnothing,\;B\ne\varnothing \end{equation*}\]

并令新划分为\(C'=\{A,B,C_2,\dots,C_K\}\)。对新划分的目标值有:

\[\begin{equation*} \Phi(C') = \min_{\alpha}\sum_{i\in A}\|x_i-\alpha\|^2 +\min_{\beta}\sum_{i\in B}\|x_i-\beta\|^2 +\sum_{k=2}^K \min_{\mu_k\in\mathbb{R}^{m}}\sum_{i\in C_k}\|x_i-\mu_k\|^2 \end{equation*}\]

对任意给定向量\(\gamma\in\mathbb{R}^m\)有:

\[\begin{equation*} \min_{\alpha}\sum_{i\in A}\|x_i-\alpha\|^2 \leqslant\sum_{i\in A}\|x_i-\gamma\|^2,\quad \min_{\beta}\sum_{i\in B}\|x_i-\beta\|^2 \leqslant\sum_{i\in B}\|x_i-\gamma\|^2 \end{equation*}\]

取\(\gamma\)为原簇 \(C_1\) 的最优中心,记为 \(\mu_1^\star\),得到:

\[\begin{equation*} \min_{\alpha}\sum_{i\in A}\|x_i-\alpha\|^2 +\min_{\beta}\sum_{i\in B}\|x_i-\beta\|^2 \leqslant \sum_{i\in A}\|x_i-\mu_1^\star\|^2 + \sum_{i\in B}\|x_i-\mu_1^\star\|^2 = \sum_{i\in C_1}\|x_i-\mu_1^\star\|^2 \end{equation*}\]

因此:

\[\begin{equation*} \Phi(C')\leqslant \sum_{i\in C_1}\|x_i-\mu_1^\star\|^2 +\sum_{k=2}^K \min_{\mu_k\in\mathbb{R}^{m}}\sum_{i\in C_k}\|x_i-\mu_k\|^2 = \Phi(\mathcal{C}) \end{equation*}\]

由最小值的不等式性可得:

\[\begin{equation*} \operatorname{WCSS}^\star(K+1)\leqslant\operatorname{WCSS}^\star(K) \end{equation*}\]

◻

note 16.21. \(\operatorname{WCSS}\)是以欧氏距离为基础的簇内紧凑性度量,其数值大小直接反映了样本在各自簇内围绕质心的离散程度。一般而言,\(\operatorname{WCSS}\)越小,表示簇内样本越集中、聚类结果越紧凑,因而在同一簇数\(K\)下聚类质量越好。需要注意的是:

  1. 对固定观测样本集和以\(\operatorname{WCSS}\)为目标函数的算法,由性质 16.8.1可知\(\operatorname{WCSS}\)随簇数\(K\)的增大单调下降;极端情况下,当\(K=n\)时,每个样本单独成簇,\(\operatorname{WCSS}=0\),因此\(\operatorname{WCSS}\)本身不能直接用于比较不同\(K\)下的聚类优劣。尽管\(\operatorname{WCSS}\)随\(K\)单调下降,但其下降幅度通常在某一\(K\)值之后显著减缓。通过考察\(\operatorname{WCSS}\)随\(K\)变化的曲线,并选取曲线由”陡降”转为”平缓”的拐点,可以在模型复杂度与聚类紧凑性之间取得平衡,从而确定一个合理的簇数,这一方法被称之为肘部法则(elbow method);

  2. 由于\(\operatorname{WCSS}\)基于质心与距离定义,其隐含假设簇内样本呈近似球形或凸形分布,对非凸结构或密度差异较大的簇刻画能力有限;

  3. \(\operatorname{WCSS}\)对特征量纲高度敏感,不同尺度的特征会不均衡地影响距离计算,因此在实际应用中通常需要对特征进行标准化或归一化处理。

Definition 16.33. 称:

\[\begin{equation*} \operatorname{CH}=\frac{\operatorname{tr}(B)/(K-1)}{\operatorname{tr}(W)/(n-K)} \end{equation*}\]

为Calinski-Harabasz指数(Calinski-Harabasz index, CH),其中:

\[\begin{equation*} W=\sum_{k=1}^K\sum_{i\in C_k}(x_i-\mu_k)(x_i-\mu_k)^{\top},\quad B=\sum_{k=1}^K n_k(\mu_k-\bar{x})(\mu_k-\bar{x})^{\top} \end{equation*}\]

note 16.22. \(\operatorname{CH}\)指数本质上与单因素方差分析中的\(F\)统计量高度一致。在几何意义上,当簇中心彼此远离且簇内样本高度集中时,\(\operatorname{tr}(B)\) 相对较大而 \(\operatorname{tr}(W)\) 相对较小,从而\(\operatorname{CH}\)取得较大值,表明聚类结构清晰。

Definition 16.34. 设距离度量为\(\rho\)。对样本\(x_i\),定义:

\[\begin{gather*} a_i\coloneq\frac{1}{|C_{y_i}|-1}\sum_{\substack{j\in C_{y_i}\\ j\neq i}}\rho(x_i,x_j),\quad b_i\coloneq\min_{\ell\neq y_i}\frac{1}{|C_{\ell}|}\sum_{j\in C_{\ell}}\rho(x_i,x_j) \\ s_i\coloneq \begin{cases} \dfrac{b_i-a_i}{\max\{a_i,b_i\}},&\max\{a_i,b_i\}\ne0 \\ 0,&\max\{a_i,b_i\}=0 \end{cases},\quad\operatorname{Silhouette}\coloneq\frac{1}{n}\sum_{i=1}^ns_i \end{gather*}\]

Property 16.8.2. (轮廓系数的取值范围)

\(\operatorname{Silhouette}\)轮廓系数的取值范围为\([-1,1]\)。

证明. 若\(\max\{a_i,b_i\}=0\),则\(a_i=b_i=0\),此时\(s_i=0\)。下面假设\(\max\{a_i,b_i\}>0\)。分两种情形讨论:

  1. 若\(b_i\geqslant a_i\),则\(\max\{a_i,b_i\}=b_i\),从而:

    \[\begin{equation*} s_i=\frac{b_i-a_i}{b_i}=1-\frac{a_i}{b_i} \end{equation*}\]

    由于\(0\leqslant a_i\leqslant b_i\),可得\(0\leqslant \dfrac{a_i}{b_i}\leqslant 1\),因此\(0\leqslant s_i\leqslant 1\)。

  2. 若\(a_i>b_i\),则\(\max\{a_i,b_i\}=a_i\),从而:

    \[\begin{equation*} s_i=\frac{b_i-a_i}{a_i}=\frac{b_i}{a_i}-1 \end{equation*}\]

    由于\(0\leqslant b_i<a_i\),可得\(0\leqslant\dfrac{b_i}{a_i}<1\),因此\(-1\leqslant s_i<0.\)

综上,\(s_i\in[-1,1]\),即\(\operatorname{Silhouette}\in[-1,1]\)。 ◻

note 16.23. (Silhouette 的几何解释)

轮廓系数从几何角度刻画了单个样本在其所属簇中的相对位置。其中,\(a_i\)衡量样本\(x_i\)到其所在簇\(C_k\)内部的平均距离,反映簇内的局部紧凑性;而\(b_i\)刻画样本\(x_i\)到最近其他簇的平均距离,反映其与邻近簇之间的分离程度。由定义可以看出,\(s_i\)本质上是簇内距离与最近簇间距离之间的相对差异。当\(a_i\ll b_i\)时,样本\(x_i\)位于簇的内部区域,与其他簇之间存在明显间隔,此时\(s_i\)接近\(1\);当\(a_i\approx b_i\)时,样本\(x_i\)位于簇的边界附近,对不同簇的归属不具有明显偏好,此时\(s_i\)接近\(0\);当\(a_i>b_i\)时,样本\(x_i\)在几何意义上更接近其他簇而非当前簇,通常意味着该样本可能被错误分配,此时\(s_i<0\)。

因此,Silhouette 系数可被视为一种基于距离比值的局部几何判别准则,它同时综合了簇内紧凑性与簇间分离性,并能够在样本层面反映聚类结构的合理性。

16.8.1.2 外部指标

Definition 16.35. 考虑所有样本对\((i,j)\),定义:

  • \(a\):在真实标签同类,且聚类也同簇的样本对个数;

  • \(b\):在真实标签不同类,且聚类也不同簇的样本对个数;

称:

\[\begin{equation*} \operatorname{RI}=\frac{a+b}{\binom{n}{2}} \end{equation*}\]

为Rand 指数(Rand index, RI),称:

\[\begin{equation*} \operatorname{ARI}=\frac{\operatorname{RI}-\operatorname{E}(\operatorname{RI})}{1-\operatorname{E}(\operatorname{RI})} \end{equation*}\]

为校正 Rand 指数(adjusted Rand index, ARI)。

Theorem 16.6. 在固定边际(列联表个数统计值)的情况下,设:

表 16.3 真实类别与聚类簇的列联表
\(L_1\) \(L_2\) \(\cdots\) \(L_r\) 合计
\(C_1\) \(N_{11}\) \(N_{12}\) \(\cdots\) \(N_{1r}\) \(n_1\)
\(C_2\) \(N_{21}\) \(N_{22}\) \(\cdots\) \(N_{2r}\) \(n_2\)
\(\vdots\) \(\vdots\) \(\vdots\) \(\ddots\) \(\vdots\) \(\vdots\)
\(C_K\) \(N_{K1}\) \(N_{K2}\) \(\cdots\) \(N_{Kr}\) \(n_K\)
合计 \(m_1\) \(m_2\) \(\cdots\) \(m_r\) \(n\)

其中\(C_i\)表示第\(i\)个聚类簇,\(L_j\)表示第\(j\)个真实类别,\(N_{ij}=|C_i\cap L_j|\)为同时属于簇\(C_i\)且属于类别\(L_j\)的样本个数,并满足行边际与列边际约束:

\[\begin{equation*} \sum_{j=1}^rN_{ij}=n_i,\quad \sum_{i=1}^KN_{ij}=m_j,\quad \sum_{i=1}^K\sum_{j=1}^rN_{ij}=n \end{equation*}\]

则:

\[\begin{equation*} \operatorname{E}(\operatorname{RI})=1+\frac{2\sum\limits_{i=1}^{K}\binom{n_i}{2}\sum\limits_{j=1}^{r}\binom{m_j}{2}}{[\binom{n}{2}]^2}-\frac{\sum\limits_{i=1}^{K}\binom{n_i}{2}+\sum\limits_{j=1}^{r}\binom{m_j}{2}}{\binom{n}{2}} \end{equation*}\]

证明. 由性质 5.4.3(6)可知:

\[\begin{equation*} \operatorname{E}(\operatorname{RI})=\frac{\operatorname{E}(a)+\operatorname{E}(b)}{\binom{n}{2}} \end{equation*}\]

对于\(\operatorname{E}(a)\),根据性质 5.4.3(6)可得写非参数列联表的时候把这边综合起来:

\[\begin{equation*} \operatorname{E}(a)=\operatorname{E}\left[\sum_{i=1}^{K}\sum_{j=1}^{r}\binom{N_{ij}}{2}\right]=\sum_{i=1}^{K}\sum_{j=1}^{r}\frac{1}{2}\operatorname{E}[N_{ij}(N_{ij}-1)]=\sum_{i=1}^{K}\sum_{j=1}^{r}\frac{1}{2}[\operatorname{E}(N_{ij}^2)-\operatorname{E}(N_{ij})] \end{equation*}\]

而:

\[\begin{equation*} N_{ij}\sim\operatorname{Hyper}(n_i,m_j,n),\quad\operatorname{E}(N_{ij})=\frac{n_im_j}{n},\quad\operatorname{Var}(N_{ij})=\frac{n_im_j(n-n_i)(n-m_j)}{n^2(n-1)} \end{equation*}\]

由性质 6.4.2(1)可知:

\[\begin{align*} &\operatorname{E}(N_{ij}^2)=\operatorname{Var}(N_{ij})+[\operatorname{E}(N_{ij})]^2=\frac{n_im_j(n-n_i)(n-m_j)}{n^2(n-1)}+\frac{n_i^2m_j^2}{n^2} \\ =&\frac{n_im_j(n-n_i)(n-m_j)+n_i^2m_j^2(n-1)}{n^2(n-1)}=\frac{n_im_j[(n-n_i)(n-m_j)+n_im_j(n-1)]}{n^2(n-1)} \\ =&\frac{n_im_j(n^2-m_jn-n_in+n_im_j+n_im_jn-n_im_j)}{n^2(n-1)}=\frac{n_im_j(n^2-m_jn-n_in+n_im_jn)}{n^2(n-1)} \\ =&\frac{n_im_j(n-m_j-n_i+n_im_j)}{n(n-1)} \end{align*}\]

于是:

\[\begin{align*} &\operatorname{E}(N_{ij}^2)-\operatorname{E}(N_{ij})=\frac{n_im_j(n-m_j-n_i+n_im_j)}{n(n-1)}-\frac{n_im_j}{n} \\ =&\frac{n_im_j(n-m_j-n_i+n_im_j)-(n-1)n_im_j}{n(n-1)}=\frac{n_im_j(1-m_j-n_i+n_im_j)}{n(n-1)} \\ =&\frac{n_i(n_i-1)m_j(m_j-1)}{n(n-1)} \end{align*}\]

所以:

\[\begin{equation*} \operatorname{E}(a)=\sum_{i=1}^{K}\sum_{j=1}^{r}\frac{1}{2}\frac{n_i(n_i-1)m_j(m_j-1)}{n(n-1)}=\sum_{i=1}^{K}\sum_{j=1}^{r}\frac{\binom{n_i}{2}\binom{m_j}{2}}{\binom{n}{2}}=\frac{\sum\limits_{i=1}^{K}\binom{n_i}{2}\sum\limits_{j=1}^{r}\binom{m_j}{2}}{\binom{n}{2}} \end{equation*}\]

对于\(\operatorname{E}(b)\),注意到同簇样本个数\(A\)与同类样本个数\(B\)分别为:

\[\begin{equation*} A=\sum_{i=1}^{K}\binom{n_i}{2},\quad B=\sum_{j=1}^{r}\binom{m_j}{2} \end{equation*}\]

于是:

\[\begin{equation*} b=\binom{n}{2}-a-(B-a)-(A-a)=\binom{n}{2}-A-B+a \end{equation*}\]

根据性质 5.4.3(6)可得:

\[\begin{equation*} \operatorname{E}(b)=\binom{n}{2}-A-B+\operatorname{E}(a) \end{equation*}\]

所以:

\[\begin{align*} &\operatorname{E}(\operatorname{RI})=\frac{2\operatorname{E}(a)+\binom{n}{2}-A-B}{\binom{n}{2}}=\frac{2\operatorname{E}(a)}{\binom{n}{2}}+1-\frac{A+B}{\binom{n}{2}} \\ =&1+\frac{2\sum\limits_{i=1}^{K}\binom{n_i}{2}\sum\limits_{j=1}^{r}\binom{m_j}{2}}{[\binom{n}{2}]^2}-\frac{\sum\limits_{i=1}^{K}\binom{n_i}{2}+\sum\limits_{j=1}^{r}\binom{m_j}{2}}{\binom{n}{2}} \end{align*}\]

◻

Property 16.8.3. \(\operatorname{RI}\in[0,1],\;\operatorname{ARI}\leqslant1\)。

证明. \(\operatorname{RI}\in[0,1]\)是显然的。

若真实划分与聚类划分不是几乎必然完全一致,则随机生成的聚类结果不可能以概率\(1\)与真实标签完全一致,因此\(\operatorname{E}(\operatorname{RI})<1\)。

由\(\operatorname{RI}\leqslant1\)可知:

\[\begin{equation*} \operatorname{RI}-\operatorname{E}(\operatorname{RI})\leqslant1-\operatorname{E}(\operatorname{RI}) \end{equation*}\]

由于\(1-\operatorname{E}(\operatorname{RI})>0\),两边同除以\(1-\operatorname{E}(\operatorname{RI})\)可得:

\[\begin{equation*} \operatorname{ARI} =\frac{\operatorname{RI}-\operatorname{E}(\operatorname{RI})}{1-\operatorname{E}(\operatorname{RI})}\leqslant1 \end{equation*}\]

◻

note 16.24. \(\operatorname{RI}\)基于样本对一致性来评价聚类结果,\(\operatorname{RI}\) 越大表示聚类划分与真实标签的一致性越强;若 \(\operatorname{RI}\) 接近 \(1\),说明聚类结果与真实划分高度一致;若接近 \(0\),说明一致性极弱。

为了理解\(\operatorname{RI}\)的统计行为,需要引入随机聚类模型:在该模型中,真实类别个数与各簇规模均被视为固定,而聚类标签在所有满足给定簇规模约束的分配方式中等概率随机生成。该模型刻画了”聚类结果与真实标签在统计意义上相互独立”的基准情形。

在这一随机模型下,\(\operatorname{RI}\)往往具有非零且相对较大的期望值。其根本原因在于样本对中真实不同类的情形通常占据主导地位,而这类样本对在随机聚类中被分配到不同簇的概率本身较高。例如,设\(n=100\),真实标签包含\(10\)个类别、每类\(10\)个样本,则共有\(\binom{100}{2}=4950\)个样本对,其中真实不同类的样本对数为\(4500\)。即使聚类结果完全随机,只要簇规模与真实类别规模相近,大多数真实异类样本对仍会落入不同簇,从而使得 \(\operatorname{RI}\)取得看似较好的数值。这表明\(\operatorname{RI}\)对随机一致性并不敏感。

\(\operatorname{ARI}\)正是基于上述随机模型对\(\operatorname{RI}\)进行期望校正,该校正保证了随机聚类的期望\(\operatorname{ARI}\)为\(0\),完美一致时取值为\(1\),并允许在一致性劣于随机时取负值。因此,\(\operatorname{ARI}\)相较于\(\operatorname{RI}\)提供了一个以随机一致性为基准的、更为稳健的外部聚类评价指标。

Definition 16.36. 考虑所有样本对\((i,j)\),定义:

  • \(\operatorname{TP}\):真实标签同类,且聚类结果同簇的样本对数;

  • \(\operatorname{FP}\):真实标签不同类,但聚类结果同簇的样本对数;

  • \(\operatorname{FN}\):真实标签同类,但聚类结果不同簇的样本对数。

称:

\[\begin{equation*} \operatorname{FMI}=\sqrt{\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FP}}\cdot\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FN}}} \end{equation*}\]

为Fowlkes-Mallows 指数(Fowlkes-Mallows index, FMI)。

Property 16.8.4. \(\operatorname{FMI}\in[0,1]\)。

证明. 由定义立即可得。 ◻

note 16.25. Fowlkes-Mallows指数从样本对一致性的角度同时兼顾了聚类结果的精确性与完整性。在真实标签给定的条件下,\(\operatorname{FMI}\)越大表示聚类结果与真实划分在样本对层面上的一致性越强;当聚类结果与真实标签完全一致时,\(\operatorname{FMI}=1\);当不存在任何同类同簇的样本对时,\(\operatorname{FMI}=0\)。与\(\operatorname{RI}\)将异类异簇样本对也视为正确判断不同,\(\operatorname{FMI}\)仅关注真实同类样本是否被成功聚合,从而在类别数量较多或类别规模不平衡的情形下,往往比 RI 更为保守。

16.8.2 K-means

Algorithm 16.9 K-means Clustering (Lloyd’s Algorithm)

Input: Data points \(\mathcal{D}=\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^m\); number of clusters \(K\); maximum number of iterations \(T\); tolerance \(\varepsilon\)

Output: Cluster assignments \(\{C_1, C_2, \dots, C_{K}\}\); centroids \(\{\mu_1, \mu_2, \dots, \mu_{K}\}\)

Select the first centroid \(\mu_1\) uniformly at random from \(\mathcal{D}\) (Initialization step)

for \(k=2\) to \(K\) do

for each data point \(x_i \in\mathcal{D}\) do

\(D_i^2 \gets \min\limits_{1\leqslant j < k}\;||x_i - \mu_j||^2\)

end for

Select \(\mu_k\) from \(\mathcal{D}\) with probability

\[ P(x_i) = \frac{D_i^2}{\sum\limits_{j=1}^n D_j^2} \]

end for

for \(t=1\) to \(T\) do (Assignment step)

for each data point \(x_i\in\mathcal{D}\) do

\(y_i \gets \underset{k=1,2,\dots,K}{\arg\min}\;\|x_i-\mu_k\|^2\), assign \(x_i\) to cluster \(C_{y_i}\)

end for

for \(k=1\) to \(K\) do (Update step)

\(\mu_k^{\text{new}} \gets \dfrac{1}{|C_k|} \sum\limits_{x_i\in C_k} x_i\)

end for

if \(\max\limits_{k} \|\mu_k^{\text{new}}-\mu_k\| < \varepsilon\) then

break

end if

\(\mu_k \gets \mu_k^{\text{new}},\;\forall\;k=1,2,\dots,K\)

end for

算法 16.9给出了经典的K-means聚类算法(亦称Lloyd算法)的完整流程。该算法的目标是在给定样本集合的情况下,将样本划分为\(K\)个互不相交的簇,使得簇内样本在欧氏距离意义下尽可能紧密。从优化角度看,K-means试图最小化如下的类内平方和目标函数:

\[\begin{equation*} \Phi(\{\mu_k\},\{C_k\})=\sum_{k=1}^K\sum_{x_i\in C_k}\|x_i-\mu_k\|^2 \end{equation*}\]

由于每一次分配或更新步骤都会使目标函数\(\Phi\)的值非增平方分解均值最小,下面两个算法也需要链接,且\(\Phi\)在下界零处有界,根据性质 3.2.9(7)可知K-means算法在有限步内必然收敛到某一局部最优解。然而,由于目标函数是非凸的证明,算法的最终结果依赖于初始中心的选取,这也是在实际应用中通常采用k-means++初始化(即算法 16.9中的1-7步)而非完全随机初始化的原因。

16.8.2.1 K-modes

Algorithm 16.10 K-modes Clustering Algorithm with Cao Initialization

Input: Categorical data \(\mathcal{D}=\{x_i\}_{i=1}^n\), where \(x_i=(x_{i1},x_{i2},\dots,x_{ip})\); number of clusters \(K\); maximum number of iterations \(T\)

Output: Cluster assignments \(\{C_1, C_2, \dots, C_{K}\}\); modes \(\{m_1, m_2, \dots, m_{K}\}\)

For each categorical variable \(j=1,2,\dots,p\), let \(\mathcal C_j\) denote its set of possible categories. For each \(c\in\mathcal C_j\), compute

\[ f_j(c) = \frac{1}{n} \sum_{i=1}^n I(x_{ij}=c) \]

Define sample density: \(\mathrm{dens}(x_i)=\sum\limits_{j=1}^p f_j(x_{ij})\)

Select the first mode: \(m_1 \gets \underset{x_i\in\mathcal{D}}{\arg\max}\;\mathrm{dens}(x_i)\)

for \(k=2\) to \(K\) do

Select

\[ m_k \gets \underset{x_i\in\mathcal{D}}{\arg\max} \Bigl( \mathrm{dens}(x_i)\cdot \min_{1\leqslant r<k}\sum_{j=1}^p I(x_{ij}\neq m_{rj}) \Bigr) \]

end for

for \(t=1\) to \(T\) do (Assignment step)

for each data point \(x_i\in\mathcal{D}\) do

\(y_i \gets \underset{k=1,2,\dots,K}{\arg\min} \sum\limits_{j=1}^p I(x_{ij}\neq m_{kj})\), assign \(x_i\) to cluster \(C_{y_i}\)

end for

for \(k=1\) to \(K\) do (Update step)

for \(j=1\) to \(p\) do

\(m_{kj} \gets \operatorname{mode}\{x_{ij}:x_i\in C_k\}\)

end for

end for

if cluster assignments do not change then

break

end if

end for

算法 16.10 给出了K-modes聚类算法的标准形式。该算法专门用于处理分类型数据,其目标是在简单匹配不相似度下最小化类内不一致性。对应的优化目标函数为:

\[\begin{equation*} \Psi({m_k},{C_k})= \sum_{k=1}^K\sum_{x_i\in C_k}\sum_{j=1}^p I(x_{ij}\neq m_{kj}) \end{equation*}\]

该目标函数在簇分配或逐维众数更新时均单调不增,因此算法在有限步内收敛到某一局部最优解。与K-means类似,由于目标函数的非凸性,K-modes的结果依赖于初始化方式,实际应用中通常采用Cao初始化(即算法 16.10中的1-6步)。

Cao初始化的核心思想在于:初始聚类原型应当同时具备代表性和区分性。具体而言,算法首先通过各分类变量的经验频率刻画样本在全体数据中的密度,从而选取在每一维上均取高频类别的样本作为第一个初始中心,该样本可被视为总体经验分布下的一个典型点。随后,在选取其余初始中心时,Cao初始化在保证中心密度较高的同时,引入与已有中心之间的简单匹配不相似度,以避免多个初始原型集中于同一高频区域。

16.8.2.2 K-prototypes

Algorithm 16.11 K-prototypes Clustering Algorithm

Input: Mixed-type data \(\mathcal{D}=\{x_i=(x_i^{(n)},x_i^{(c)})\}_{i=1}^n\); number of clusters \(K\); trade-off parameter \(\gamma>0\); maximum number of iterations \(T\)

Output: Cluster assignments \(\{C_1, C_2, \dots, C_{K}\}\); prototypes \(\{(\mu_k,m_k)\}_{k=1}^K\)

Initialize numerical prototypes \(\mu_1, \mu_2, \dots, \mu_{K}\) (Initialization step) by applying K-means++ initialization

Initialize categorical prototypes \(m_1, m_2, \dots, m_{K}\) by applying Cao initialization

for \(t=1\) to \(T\) do (Assignment step)

for each data point \(x_i\in\mathcal{D}\) do

\(y_i \gets \underset{k=1,2,\dots,K}{\arg\min} \Big( \|x_i^{(n)}-\mu_k\|^2 + \gamma\sum\limits_{j=1}^q I(x_{ij}^{(c)}\neq m_{kj}) \Big)\), assign \(x_i\) to cluster \(C_{y_i}\)

end for

for \(k=1\) to \(K\) do (Update step)

\(\mu_k \gets \dfrac{1}{|C_k|} \sum\limits_{x_i\in C_k} x_i^{(n)}\)

for \(j=1\) to \(\dim(x_i^{(c)})\) do

\(m_{kj} \gets \operatorname{mode}\{x_{ij}^{(c)}:x_i\in C_k\}\)

end for

end for

if cluster assignments do not change then

break

end if

end for

算法 16.11 描述了K-prototypes聚类算法的完整流程。该算法同时处理数值型与分类型变量,其优化目标为:

\[\begin{equation*} \Omega({\mu_k,m_k},{C_k})= \sum_{k=1}^K\sum_{x_i\in C_k} \Big(\|x_i^{(n)}-\mu_k\|^2 + \gamma\sum_{j=1}^{\dim(x_i^{(c)})} I(x_{ij}^{(c)}\neq m_{kj})\Big) \end{equation*}\]

其中参数\(\gamma\)用于平衡数值误差与分类型不匹配误差的相对尺度,实践中,通常在对数值型变量进行标准化处理后,根据数值型与分类型变量的维数比例设置\(\gamma\approx p/q\),其中\(p\)与\(q\)分别表示数值型与分类型变量的维数。与 K-means和K-modes 类似,K-prototypes通过交替最小化策略保证目标函数单调下降,但最终仅能保证收敛到局部最优解。

16.8.3 凝聚型层次聚类

Algorithm 16.12 Agglomerative Hierarchical Clustering

Input: Data points \(\mathcal{D}=\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^m\); linkage criterion \(L\); distance metric \(\rho(\cdot,\cdot)\); target number of clusters \(K\) or distance threshold \(h_0\)

Output: Cluster assignments \(\{C_1, C_2, \dots, C_{K}\}\); merge history (dendrogram)

Initialize clusters \(C_i \gets \{x_i\}\) for \(i=1,2,\dots,n\)

Compute initial pairwise dissimilarities \(D_L(C_p,C_q)\)

while stopping criterion not satisfied do

\((p,q)\gets \underset{p\neq q}{\arg\min}\;D_L(C_p,C_q)\)

if \(D_L(C_p,C_q) > h_0\) then

break

end if

Merge clusters \(C_p\) and \(C_q\) into \(C_{\text{new}}\)

Record merge height \(D_L(C_p,C_q)\)

Remove \(C_p,C_q\) and add \(C_{\text{new}}\)

Update dissimilarities involving \(C_{\text{new}}\)

if number of clusters \(= K\) then

break

end if

end while

算法 16.12给出了凝聚型层次聚类(agglomerative hierarchical clustering)的基本流程。算法从最细粒度的划分开始,将每一个样本视为一个单独的簇。随后,在当前簇集合中,根据预先选定的簇间距离准则(linkage criterion),反复选择距离最近的两个簇并将其合并。该合并过程持续进行,直到簇的数量减少至预先指定的目标簇数\(K\),或最小簇间距离大于给定值\(h_0\)。

层次聚类的核心在于如何定义两个簇之间的距离。设\(A,B\subseteq X\)为两个簇,\(\rho\) 为样本空间中的距离度量,则簇间距离\(D_L(A,B)\)由 linkage 准则给出。不同的 linkage 定义对应着不同的几何偏好与聚类行为。

常见的 linkage 定义如下:

  1. 单链接(Single linkage):\(D_{\text{single}}(A,B)=\min\limits_{x\in A,\,y\in B} \rho(x,y)\);

  2. 全链接(Complete linkage):\(D_{\text{complete}}(A,B)=\max\limits_{x\in A,\,y\in B} \rho(x,y)\);

  3. 平均链接(Average linkage):\(D_{\text{average}}(A,B)=\dfrac{1}{|A||B|}\sum\limits_{x\in A}\sum\limits_{y\in B}\rho(x,y)\);

  4. Ward 方法(Ward linkage): \(\Delta(A,B)=\operatorname{SSE}(A\cup B)-\operatorname{SSE}(A)-\operatorname{SSE}(B)\),Ward 方法并不直接基于样本对距离,而是选择使合并后类内平方和增量最小的两个簇。

16.8.4 DBSCAN

前面介绍的K-means与凝聚型层次聚类都能够处理许多常见的聚类问题,但当簇具有复杂的几何结构时,它们可能无法得到符合直觉的划分。例如,对于两个同心圆形成的数据,内外两个圆环自然应当被视为两个不同的簇,但K-means默认不同簇具有不同的聚类中心,往往会将两个圆环沿某一方向切开;凝聚型层次聚类的结果则高度依赖于所采用的连接准则,也未必能够稳定地恢复这种结构。类似的问题还会出现在月牙形、环形等数据中。这说明,仅依靠簇中心或簇之间的距离并不足以描述所有可能的聚类结构,于是我们引入了下面两种基于密度的聚类方法。

Algorithm 16.13 DBSCAN (Density-Based Spatial Clustering of Applications with Noise)

Input: Data points \(\mathcal{D}=\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^m\); neighborhood radius \(\varepsilon\); minimum number of points \(\operatorname{MinPts}\)

Output: Cluster assignments \(\{C_1, C_2, \dots, C_{K}\}\); noise set \(\mathcal{N}\)

Mark all points as unvisited

\(K \gets 0\), \(\mathcal{N}\gets\varnothing\)

for each point \(x_i\in\mathcal{D}\) do

if \(x_i\) is visited then

continue

end if

Mark \(x_i\) as visited; \(U(x_i,\varepsilon)\gets\{x_j\in\mathcal{D}:\rho(x_j,x_i)\leqslant\varepsilon\}\)

if \(|U(x_i,\varepsilon)|<\operatorname{MinPts}\) then

Label \(x_i\) as noise; \(\mathcal{N}\gets\mathcal{N}\cup\{x_i\}\)

else

\(K\gets K+1\); Create new cluster \(C_K\) and add \(x_i\) to \(C_K\)

\(S\gets U(x_i,\varepsilon)\setminus\{x_i\}\)

while \(S\ne \varnothing\) do

Select \(x_p\in S\) and remove it from \(S\)

if \(x_p\) is not visited then

Mark \(x_p\) as visited; \(U(x_p,\varepsilon)\gets\{x_j\in\mathcal{D}:\rho(x_j,x_p)\leqslant\varepsilon\}\)

if \(|U(x_p,\varepsilon)|\geqslant\operatorname{MinPts}\) then

\(S\gets S\cup[U(x_p,\varepsilon)\setminus\{x_p\}]\)

end if

end if

if \(x_p\) has not been assigned to any cluster then

Add \(x_p\) to \(C_K\); \(\mathcal{N}\gets\mathcal{N}\setminus\{x_p\}\)

end if

end while

end if

end for

算法 16.13给出了DBSCAN算法的完整流程。与基于中心的K-means不同,DBSCAN不依赖簇中心或簇数量的预先设定,而是通过样本在空间中的密度分布来刻画簇结构。

给定半径参数\(\varepsilon\)与最小点数阈值\(\operatorname{MinPts}\),定义点\(x_i\)的\(\varepsilon\)-邻域为:

\[\begin{equation*} U(x_i,\varepsilon)=\{x_j\in X:\|x_j-x_i\|\leqslant\varepsilon\} \end{equation*}\]

若\(|U(x_i,\varepsilon)|\geqslant\operatorname{MinPts}\),则称\(x_i\)为核心点;若点本身不是核心点,但位于某一核心点的\(\varepsilon\)-邻域内,则称其为边界点;其余点被视为噪声。

TikZ 数学图示
图 16.11 DBSCAN中的点类型与邻域扩张。虚线圆表示以核心点c1, c2为中心的ε-邻域。

DBSCAN首先将所有样本标记为未访问,并依次选择尚未访问的样本\(x_i\)作为考察对象。算法访问\(x_i\)后,首先判断\(x_i\)是否为核心点,若\(x_i\)非核心点,此时算法暂时将\(x_i\)标记为噪声点。这里的噪声标记并不是最终结论:若后续从其他核心点扩张簇时发现\(x_i\)位于该核心点的\(\varepsilon\)-邻域内,则\(x_i\)仍会被吸收到相应簇中,并作为边界点从噪声集合中移除。

若\(x_i\)是核心点,算法以\(x_i\)为起点创建一个新簇,并将其邻域中的其他样本加入待处理集合\(S\)。随后,算法不断从\(S\)中取出一个样本\(x_p\)进行检查。若\(x_p\)尚未被访问,则先将其标记为已访问,并计算其邻域\(U(x_p,\varepsilon)\)。当\(x_p\)同样满足\(|U(x_p,\varepsilon)|\geqslant\operatorname{MinPts}\)时,说明\(x_p\)也是核心点,因此其邻域中的样本均可能通过\(x_p\)与当前簇邻域可达,算法便将这些样本继续加入\(S\),从而使当前簇沿着相邻核心点逐步向外扩张。若\(x_p\)不是核心点,则其邻域不会被继续展开,但只要\(x_p\)尚未被分配给其他簇,它仍会作为边界点加入当前簇。

上述过程持续进行,直到待处理集合\(S\)为空。此时,所有能够由起始核心点通过有限条直接密度可达关系到达的核心点,以及附着在这些核心点邻域中的边界点,均已被纳入当前簇,因此该簇不能再继续扩张。算法随后返回外层循环,继续选择下一个尚未访问的样本,并以相同方式判断其是否能够形成新的簇。全部样本处理完毕后,仍未被任何簇吸收的点最终构成噪声集合。

note 16.26. 在实际应用中,通常先根据数据维数选择\(\operatorname{MinPts}\)。若数据的有效维数为\(d_{\mathrm{eff}}\),一个常用的经验选择为:

\[\begin{equation*} \operatorname{MinPts}\approx 2d_{\mathrm{eff}} \end{equation*}\]

在给定\(\operatorname{MinPts}\)后,可以利用\(k\)-distance图选择\(\varepsilon\)。计算每个点到其第\(\operatorname{MinPts}\)个最近邻的距离\(d_k(x_i)\),该距离表示使\(x_i\)的邻域刚好包含足够多样本、从而满足核心点条件所需要的最小半径。将所有\(d_k(x_i)\)从小到大排序后,簇内部的高密度点通常对应较小且变化平缓的距离,而边界点和噪声点通常对应快速增大的距离。因此,可以将排序曲线由平缓转为快速上升的拐点所对应的距离作为\(\varepsilon\)的候选值。

需要特别注意的是,DBSCAN在整个数据集上使用同一个\(\varepsilon\)和同一个 \(\operatorname{MinPts}\),因而隐含地使用一个全局统一的密度阈值。若不同簇具有明显不同的密度水平,则通常不存在一个同时适合所有簇的\(\varepsilon\)。较小的\(\varepsilon\)能够识别高密度簇,但可能将低密度部分划为噪声;较大的 \(\varepsilon\) 虽然能够保留低密度簇,却可能通过一系列邻域可达点将原本不同的高密度簇错误连接。因此,当数据包含密度水平显著不同的簇时,参数选择困难并不一定只是调参问题,而可能反映了标准 DBSCAN 的模型假设不适合该数据。 同时,边界点的归属可能依赖于簇的扩张顺序。若某个非核心点同时位于两个不同核心簇的核心点的\(\varepsilon\)-邻域中,则它可以从两个簇中的核心点邻域可达,但自身不能继续扩张并连接这两个核心区域。按照算法 16.13,该边界点被归入第一个扩张到它的簇。

由于DBSCAN能够识别任意形状的簇并显式区分噪声点,其在处理非凸簇结构时具有显著优势。然而,算法对参数\(\varepsilon\)与\(\operatorname{MinPts}\)较为敏感,且在高维空间中邻域查询的效率可能显著下降,这在一定程度上限制了其适用范围。

16.8.5 OPTICS

Algorithm 16.14 OPTICS (Ordering Points To Identify the Clustering Structure)

Input: Data points \(\mathcal{D}=\{x_i\}_{i=1}^{n}\subseteq\mathbb{R}^m\); maximum neighborhood radius \(\varepsilon_{\max}\); minimum number of points \(\operatorname{MinPts}\)

Output: Cluster ordering \(\mathcal{O}\); reachability distances \(\{d_{\text{reach}}(x_i)\}\)

Mark all points as unprocessed

Initialize \(d_{\text{reach}}(x_i)\gets +\infty\) for all \(x_i\) and empty ordering list \(\mathcal{O}\)

for each point \(x_i\in\mathcal{D}\) do

if \(x_i\) is processed then

continue

end if

Compute neighborhood \(U(x_i,\varepsilon_{\max})\); Mark \(x_i\) as processed; Append \(x_i\) to \(\mathcal{O}\)

if \(|U(x_i,\varepsilon_{\max})|\geqslant\operatorname{MinPts}\) then

Compute \(d_{\text{core}}(x_i)=\text{distance to the $\operatorname{MinPts}$-th nearest neighbor of }x_i\)

Initialize empty priority queue \(S\)

for each unprocessed point \(x_j\in U(x_i,\varepsilon_{\max})\) do

\(d_{\text{new}} \gets \max\{d_{\text{core}}(x_i),\,\rho(x_i,x_j)\}\)

\(d_{\text{reach}}(x_j) \gets \min\{d_{\text{reach}}(x_j),\,d_{\text{new}}\}\)

Insert \(x_j\) in \(S\) with key \(d_{\text{reach}}(x_j)\)

end for

while \(S\ne \varnothing\) do

Extract \(x_p\in\operatorname{\arg\min}\limits_{x\in S}\;d_{\text{reach}}(x_p)\)

Compute neighborhood \(U(x_p,\varepsilon_{\max})\)

Mark \(x_p\) as processed; Append \(x_p\) to \(\mathcal{O}\)

if \(|U(x_p,\varepsilon_{\max})|\geqslant\operatorname{MinPts}\) then

Compute \(d_{\text{core}}(x_p)\)

for each unprocessed point \(x_q\in U(x_p,\varepsilon_{\max})\) do

\(d_{\text{new}} \gets \max\{d_{\text{core}}(x_p),\,\rho(x_p,x_q)\}\)

\(d_{\text{reach}}(x_q) \gets \min\{d_{\text{reach}}(x_q),\,d_{\text{new}}\}\)

Insert or update \(x_q\) in \(S\) with key \(d_{\text{reach}}(x_q)\)

end for

end if

end while

end if

end for

算法 16.14 给出了OPTICS的完整计算过程。OPTICS与DBSCAN使用相同的局部密度概念,但二者处理邻域半径的方式不同。DBSCAN预先固定半径\(\varepsilon\),并据此直接判断样本之间是否邻域可达;OPTICS则只给定一个足够大的搜索半径\(\varepsilon_{\max}\),在一次遍历中记录各样本能够以多大的距离尺度接入当前邻域区域,从而保留所有不超过\(\varepsilon_{\max}\)的潜在聚类结构。

OPTICS首先判断点\(x_i\)处的局部密度是否足够大(\(|U(x_i,\varepsilon_{\max})|\geqslant\operatorname{MinPts}\)),当局部密度足够大时,视\(x_i\)为核心点,可将\(x_i\)作为密度扩张的起点,否则不可将其作为密度扩张的起点。

当\(x_i\)为核心点时,利用核心距离(core distance)描述\(x_i\)附近达到\(\operatorname{MinPts}\)所要求的局部密度尺度。核心距离越小,说明在越小的邻域半径内便可以聚集至少\(\operatorname{MinPts}\)个样本,即\(x_i\)所处区域的局部密度越高。接着使用可达距离(reachability distance)衡量所有邻域样本\(x_j\)接入以\(x_i\)为中心的密度区域所需的距离尺度。一方面,邻域半径至少必须达到\(d_{\mathrm{core}}(x_i)\),否则\(x_i\)本身尚未形成满足\(\operatorname{MinPts}\)要求的稠密邻域;另一方面,邻域半径还必须覆盖\(x_i\)与\(x_j\)之间的实际距离。因此,\(d_{\mathrm{reach}}(x_j\mid x_i)\)可以理解为:若以\(x_i\)作为已经进入的核心点,至少需要将密度尺度扩大到多大,才能使\(x_j\)被该密度区域吸收。

完成对核心点\(x_i\)邻域样本的可达距离计算后,OPTICS从所有已经获得有限可达距离但尚未处理的样本中选择当前可达距离最小的样本\(x_p\),将\(x_p\)标记为已处理,并将其加入输出排序\(\mathcal{O}\)。随后,算法判断\(x_p\)是否为核心点。若\(x_p\)不是核心点,则它不能继续向外扩张,算法重新从当前候选集合中选择可达距离最小的未处理样本。若\(x_p\)是核心点,则利用\(x_p\)重新计算其邻域内尚未处理样本的候选可达距离。因此,第一次由起始核心点\(x_i\)展开邻域时,所有邻域样本都是首次获得有限可达距离;而在后续扩张过程中,同一样本可能被多个核心点触及,其可达距离会随着更紧密的密度连接被发现而不断减小。

OPTICS不断重复”选择当前可达距离最小的样本、判断其是否能够继续扩张、更新其邻域样本的可达距离”这一过程,直到不存在具有有限可达距离的未处理样本。此时,从当前起始点出发能够在\(\varepsilon_{\max}\)范围内连接到的密度区域已经处理完毕,算法再从剩余未处理样本中选择新的起点,开始下一轮扩张。

通过不断重复这一过程,最终所有样本均被处理,得到完整的样本排序:

\[\begin{equation*} \mathcal{O}=\left(x_{(1)},x_{(2)},\dots,x_{(n)}\right) \end{equation*}\]

以及与排序位置对应的可达距离序列:

\[\begin{equation*} d_{\mathrm{reach}}(x_{(1)}),d_{\mathrm{reach}}(x_{(2)}),\dots,d_{\mathrm{reach}}(x_{(n)}) \end{equation*}\]

按照排序\(\mathcal{O}\)绘制可达距离即可得到可达距离图。进入一个新的聚类区域时,最初的样本通常尚不能由先前区域到达,因此其可达距离较大或者为无穷大;当算法开始在该区域内部扩张后,后续样本可以在较小尺度下依次被吸收,于是形成连续的低可达距离区间。当算法从一个高密度区域跨越稀疏区域进入另一个聚类区域时,可达距离会显著升高。因此,可达距离图中的一个”谷地”通常对应一个潜在聚类:谷底越低,表示该区域的密度越高;谷地越宽,表示该密度区域包含的样本越多;谷地之间的高峰则反映不同密度区域之间的稀疏间隔。给定任意阈值\(\varepsilon'\leqslant\varepsilon_{\max}\),可以在可达距离图中将可达距离不超过\(\varepsilon'\)的连续区间提取为聚类。在相同\(\operatorname{MinPts}\)和邻域半径\(\varepsilon'\)下,这一提取过程可以恢复相应的DBSCAN聚类结果。因此,更准确地说,OPTICS的输出是一种能够支持提取多个不同\(\varepsilon'\)下DBSCAN聚类结果的密度排序表示。

OPTICS的优势由此变得很明确:DBSCAN只能在预先选定的单一\(\varepsilon\)下观察数据,而OPTICS通过记录样本的处理顺序和可达距离,将不同距离尺度下的密度连接关系压缩到同一个排序中。当不同簇的密度差异很大时,OPTICS可以根据可达距离图的局部谷地结构设置多个\(\varepsilon'\)来识别不同密度水平下的聚类。

note 16.27. 为了达成上述目的,我们需要控制\(\varepsilon_{\max}\)。若\(\varepsilon_{\max}\)过小,某些样本的邻域中可能无法包含至少\(\operatorname{MinPts}\)个样本,使其不能作为核心点继续扩张。即使样本本来属于同一个较低密度聚类,局部连接也可能由于超出\(\varepsilon_{\max}\)而被截断。此时,当前扩张会较早结束,算法需要频繁从剩余的未处理样本中选择新的起点。因此,同一个潜在聚类可能在排序中被拆分成多个扩张片段,并在可达距离序列中产生额外的\(+\infty\)或较大的跳跃。

若\(\varepsilon_{\max}\)较大,一次扩张可以覆盖更完整的密度连接结构,算法通常不需要频繁重新选择起点。不同聚类中的样本虽然可能同时位于搜索范围内,但OPTICS始终优先处理当前可达距离较小的样本,因此高密度聚类内部的样本仍会倾向于连续出现,而跨越不同聚类时会产生较大的可达距离。也就是说,较大的\(\varepsilon_{\max}\)一般不会像DBSCAN中过大的\(\varepsilon\)那样直接将所有聚类合并,而是会将不同聚类之间的分隔表现为可达距离图中的高峰。但是,\(\varepsilon_{\max}\)过大会显著增加计算开销,每次邻域搜索与可达距离更新都会涉及过多样本。

因此,\(\varepsilon_{\max}\)应当被设置为一个足够宽松但不过度扩张的上界:它应当大于所有感兴趣聚类内部维持密度连通所需的距离尺度,以避免聚类结构被人为截断;同时又不必大到使每个样本的邻域接近整个数据集。

Method 16.2. (\(\xi\)聚类提取)

记OPTICS排序为:

\[\begin{equation*} \mathcal{O}=\left(x_{(1)},x_{(2)},\ldots,x_{(n)}\right) \end{equation*}\]

并记对应的可达距离序列为:

\[\begin{equation*} r_k=d_{\mathrm{reach}}\left(x_{(k)}\right),\quad k=1,2,\dots,n \end{equation*}\]

给定参数\(\xi\in(0,1)\),若:

\[\begin{equation*} r_{k+1}\leqslant(1-\xi)r_k \end{equation*}\]

则称从位置\(k\)到位置\(k+1\)发生一次\(\xi\)-陡降,即可达距离至少相对下降了\(\xi\)。该位置可能对应聚类谷地的入口。

若:

\[\begin{equation*} r_k\leqslant(1-\xi)r_{k+1} \end{equation*}\]

则称从位置\(k\)到位置\(k+1\)发生一次\(\xi\)-陡升,即可达距离发生了足够明显的相对上升。该位置可能对应聚类谷地的出口。

连续的\(\xi\)-陡降区域与其后匹配的\(\xi\)-陡升区域之间形成一个候选谷地。若该区间包含的样本数不少于\(\operatorname{MinPts}\),并且区间内部的可达距离明显低于两侧边界,则将该区间对应的样本提取为一个簇。不属于任何簇的样本被标记为异常点。

参数\(\xi\)越小,算法对较缓的谷地边界越敏感;参数\(\xi\)越大,只有边界变化更加明显的谷地才会被识别。

实践中,可以先取\(\xi=0.05\)作为基准,这也是scikit-learn当前实现中的默认值。随后,可以考察:

\[\begin{equation*} \xi\in\{0.03,0.05,0.10,0.15\} \end{equation*}\]

时聚类提取结果的稳定性。若较小的\(\xi\)使结果被切分为大量规模很小的聚类,说明算法对可达距离图中的局部波动过于敏感,此时可以适当增大\(\xi\);若可达距离图中较为明显的浅谷未被提取,则可以适当减小\(\xi\)。

16.8.6 高斯混合模型

考虑如下两个一维正态分布:

\[\begin{equation*} X_1\sim N(0,0.5^2),\qquad X_2\sim N(0,1.5^2) \end{equation*}\]

假设它们分别对应两个不同的潜在簇。虽然两个分布具有相同的均值\(0\),但方差不同:第一个分布的样本更加集中于均值附近,而第二个分布的样本分布得更加分散。它们的密度函数图像如下。

TikZ 数学图示
图 16.12 两个均值相同但方差不同的正态分布

在这个例子中,如果只看距离中心\(0\)的远近,那么两个类别并没有不同的中心可以用来区分。因此,类似K-means这类基于欧氏距离和类中心的聚类方法并不能自然地刻画这种结构。对于基于密度的聚类方法,情况同样并不理想。两个总体的取值区域高度重叠,并共同形成以\(0\)附近为高密度区域的整体分布,因此仅根据样本之间的局部密度连通关系,也不存在一条自然的低密度边界将两个潜在类别分开。

然而,从概率分布的角度看,这两个类别具有明确的差异。当\(|x|\)较小时,方差较小的\(N(0,0.5^2)\)通常能够给出更高的密度;而当\(|x|\)较大时,方差较大的\(N(0,1.5^2)\)则相对更加可能产生这样的观测。也就是说,即使两个类别具有相同的中心,只要它们的概率分布不同,仍然可能根据各分布对样本的解释能力对潜在类别进行推断。

这提示我们可以不再直接将一个簇理解为围绕某个中心形成的点集,或者某个密度连通区域,而是假设每个簇对应一个概率分布。样本首先从某个潜在类别中产生,再由该类别对应的概率分布生成。

Definition 16.37. 设\(\mathbf{X}\)为\(p\)维随机向量,随机变量\(Z\in\{1,2,\dots,K\}\)满足:

\[\begin{equation*} P(Z=k)=\pi_k,\quad\pi_k>0,\quad\sum_{k=1}^{K}\pi_k=1 \end{equation*}\]

并且在给定\(Z=k\)时有:

\[\begin{equation*} \mathbf{X}\mid Z=k\sim N_p(\mu_k,\Sigma_k) \end{equation*}\]

其中\(\mu_k\in\mathbb{R}^p\),\(\Sigma_k\in M_p(\mathbb{R})\)为正定矩阵。由性质 6.3.6(2)可知:

\[\begin{equation*} p(x)=\sum_{k=1}^{K}\pi_k\phi_p(x;\mu_k,\Sigma_k) \end{equation*}\]

其中\(\phi_p(x;\mu_k,\Sigma_k)\)为\(p\)维正态分布\(N_p(\mu_k,\Sigma_k)\)的密度函数。称该模型为具有\(K\)个成分的高斯混合模型(Gaussian mixture model, GMM),称\(\pi_k\)为第\(k\)个混合权重,称\(N_p(\mu_k,\Sigma_k)\)为第\(k\)个混合成分。

对每个观测\(x_i\)引入表示其所属混合成分的隐变量\(Z_i\in\{1,2,\dots,K\}\),使用EM算法即可估计得到\(Z_i\)的条件分布与各混合成分的参数。\(Z_i\)的条件分布可作为\(x_i\)对各类别的软分类结果。若进一步将\(x_i\)归入后验概率最大的混合成分,则得到相应的硬分类结果。

16.9 神经网络

TikZ 数学图示

image

Definition 16.38. 若映射\(f_\theta:\mathbb{R}^p\rightarrow\mathbb{R}^n\)可以表示为若干仿射映射与逐元素非线性映射的复合:

\[\begin{equation*} f_\theta(x)=\phi^{(L)}\!\circ \phi^{(L-1)} \circ \cdots \circ \phi^{(1)}(x) \end{equation*}\]

其中第\(l\)层映射\(\phi^{(l)}:\mathbb{R}^{n_{l-1}}\to\mathbb{R}^{n_l}\) 定义为:

\[\begin{equation*} \phi^{(l)}(z)=\sigma^{(l)}\!\left(W^{(l)}z+b^{(l)}\right),\quad l=1,2,\dots,L \end{equation*}\]

这里:

  • \(W^{(l)}\in\mathbb{R}^{n_l\times n_{l-1}}\) 为第 \(l\) 层的权重矩阵,

  • \(b^{(l)}\in\mathbb{R}^{n_l}\) 为偏置向量,

  • \(\sigma^{(l)}\) 为逐元素作用的非线性函数,

  • \(\theta=\{W^{(l)},b^{(l)}\}_{l=1}^L\) 为网络的全部可学习参数;

则称\(f_\theta\)为神经网络(neural network, NN),\(\phi^{(l)}(z)\)的每一个分量为神经元(neuron),非线性映射\(\sigma^{(l)}\)被称之为激活函数(activation function)。当\(L>2\) 时,位于输入层与输出层之间的各层称为隐藏层(hidden layer)。

note 16.28. 引入激活函数的原因有两点:

  1. 如果网络中所有层都是线性函数,则整个网络表示的是线性模型,增加深度不会提升表达能力(无论增加多少层,总能以一个线性映射来等价表示,即多层神经网络等价于一层)。

  2. 引入非线性函数可以使得模型有能力拟合非线性函数。

下面给出一些常见的激活函数。

Definition 16.39. 称函数:

\[\begin{equation*} \sigma(x) = \frac{1}{1+\exp(-x)} \end{equation*}\]

为sigmoid函数(sigmoid function)。

TikZ 数学图示
图 16.13 Sigmoid 激活函数\(\sigma(x)=\dfrac{1}{1+e^{-x}}\)

note 16.29. Sigmoid函数可以将神经网络最终输出的实数值转换到区间\((0,1)\)内,因此可以将\(\sigma(x)\)解释为样本属于正类的预测概率,而\(1-\sigma(x)\)则表示样本属于负类的预测概率,从而可用于二分类任务。

Definition 16.40. 称函数:

\[\begin{equation*} \tanh(x)=\frac{e^x - e^{-x}}{e^x + e^{-x}} \end{equation*}\]

为双曲正切函数(tanh function)。

TikZ 数学图示
图 16.14 Tanh 激活函数 tanh (x)

Definition 16.41. 称函数:

\[\begin{equation*} \sigma(x)=\max(0,x) \end{equation*}\]

为整流线型单元(rectified linear unit, ReLU)。

TikZ 数学图示
图 16.15 ReLU 激活函数 σ(x) = max (0, x)

Definition 16.42. 称函数:

\[\begin{equation*} \sigma(x)= \begin{cases} x,& x>0 \\ \alpha x, & x\leqslant 0 \end{cases} \quad (\alpha>0 \text{为较小常数}) \end{equation*}\]

为泄漏整流线性单元(leaky ReLU)。

TikZ 数学图示
图 16.16 Leaky ReLU 激活函数(α = 0.1)

Definition 16.43. 称函数:

\[\begin{equation*} \sigma(x)= \begin{cases} x, & x>0 \\ \alpha(e^x-1), & x\leqslant 0 \end{cases} \end{equation*}\]

为指数线性单元(Exponential Linear Unit, ELU)。

TikZ 数学图示
图 16.17 ELU 激活函数(α = 1)

Definition 16.44. 设输入向量\(x=(x_1, x_2, \dots, x_{n})^{\top}\),称映射:

\[\begin{equation*} \sigma(x)_i=\frac{e^{x_i}}{\sum\limits_{j=1}^ne^{x_j}}, \quad i=1,\dots,n \end{equation*}\]

为softmax函数(softmax function)。

note 16.30. Softmax函数可以将神经网络最终输出的实数向量转换为概率向量\(\sigma(x)\),其中每个分量均非负且满足:

\[\begin{equation*} \sum_{i=1}^n\sigma(x)_i=1 \end{equation*}\]

因此,\(\sigma(x)_i\)可以解释为样本属于第\(i\)个类别的预测概率,从而可用于多分类任务。

note 16.31. 在实际计算中,直接按照Softmax函数的定义进行计算可能会遇到数值稳定性问题。当输入分量较大时,指数函数\(e^{x_i}\)的计算结果可能超过浮点数能够表示的最大值,从而发生上溢。当所有输入分量均为绝对值较大的负数时,各指数项\(e^{x_i}\)又可能因过于接近零而发生下溢,被舍入为零,使得分母\(\sum\limits_{j=1}^ne^{x_j}\)变为零,最终出现除零的情况。

为了解决这一问题,可以利用Softmax函数的平移不变性。对于任意的常数\(c\in\mathbb{R}\)有:

\[\begin{equation*} \sigma(x+c\mathbf{1})_i=\frac{\exp^{x_i+c}}{\sum\limits_{j=1}^n\exp^{x_j+c}}=\frac{e^ce^{x_i}}{e^c\sum\limits_{j=1}^ne^{x_j}}=\sigma(x)_i \end{equation*}\]

即对输入向量的所有分量加上相同的常数,并不会改变Softmax函数的输出。利用这一性质,可以得到数值稳定的计算形式:

\[\begin{equation*} \sigma(x)_i=\frac{\exp(x_i-m)}{\sum\limits_{j=1}^n\exp(x_j-m)},\quad m=\max_{1\leqslant j\leqslant n}x_j \end{equation*}\]

经过上述变换,所有指数函数的输入均不大于零,因此其输出位于\((0,1]\)之间,避免了指数运算的上溢。同时,由于至少存在一个分量满足\(x_j-m=0\),因此分母满足:

\[\begin{equation*} 1\leqslant\sum_{j=1}^n\exp(x_j-m)\leqslant n \end{equation*}\]

从而避免了所有指数项同时下溢为零而导致的除零问题。

此外,在分类任务中,Softmax函数通常需要与对数运算结合使用(交叉熵损失),此时即使使用前面介绍的数值稳定形式计算Softmax,直接对其输出取对数仍然可能产生数值问题。这是因为当某个类别的预测概率极小时,相应的指数运算可能发生下溢,使得计算得到的概率被舍入为零,而对零取对数将产生负无穷。

为了解决这一问题,可以不显式计算Softmax的输出,而是直接计算其对数形式。根据对数运算法则可知:

\[\begin{equation*} \log\sigma(x)_i=\log\left(\frac{e^{x_i}}{\sum\limits_{j=1}^ne^{x_j}}\right)=x_i-\log\left(\sum\limits_{j=1}^ne^{x_j}\right) \end{equation*}\]

这便解决了分子指数运算造成数值下溢的问题。然而分母中的指数求和仍然可能发生上溢或下溢,因此还需要结合前面介绍的平移不变性。令\(m=\max\limits_{1\leqslant j\leqslant n}x_j\),则:

\[\begin{equation*} \log\left(\sum_{j=1}^ne^{x_j}\right)=\log\left(e^m\sum_{j=1}^ne^{x_j-m}\right)=m+\log\left(\sum_{j=1}^ne^{x_j-m}\right) \end{equation*}\]

将其代入即可得到数值稳定的Log-Softmax计算公式:

\[\begin{equation*} \log\sigma(x)_i=x_i-m-\log\left(\sum_{j=1}^ne^{x_j-m}\right) \end{equation*}\]

16.9.1 模型学习

神经网络通常以经验风险\(\hat{R}(\theta)\)为训练目标,并利用Method 8.1反复更新参数。

16.9.1.1 批与轮

Definition 16.45. 设训练集为\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^n\)。在一次参数更新中用于计算梯度4的样本集合\(\mathcal{B}\subseteq\mathcal{D}\)称为一个批(batch),其样本数量\(|\mathcal{B}|\)称为批大小(batch size)。

Definition 16.46. 将训练集排列并依次划分为不交的样本集:

\[\begin{equation*} \mathcal{D}=\mathcal{B}_{e1}\cup\mathcal{B}_{e2}\cup\cdots\cup\mathcal{B}_{eM},\quad M=\left\lceil\frac{n}{B}\right\rceil \end{equation*}\]

其中\(|\mathcal{B}_{er}|=B,\;i=1,2,\dots,M-1\)。依次使用这些批完成参数更新,使每个训练样本恰好被使用一次的过程称为一个轮(epoch),下标\(e\)表示轮数。

因此,批大小\(B\)决定一次迭代使用多少样本,而轮数决定训练集被完整使用多少次。

16.9.1.2 批量梯度下降

若每次参数更新都使用整个训练集去计算梯度,这种方法称为批量梯度下降。在这种方法中,整个训练集\(\mathcal{D}\)本身构成一个批,因而批大小为\(B=n\),每一轮只进行一次参数更新。

批量梯度下降在给定参数处使用经验风险的准确梯度,目标函数的变化通常较为平稳。其缺点是必须完成全部\(n\)个样本的梯度计算后才能更新一次参数;当\(n\)很大时,单次更新的代价较高。由于更新方向中没有随机扰动,在非凸目标函数的平坦区域或鞍点附近也可能前进缓慢。

16.9.1.3 随机梯度下降

设第\(e\)轮开始时将训练样本随机排列为:

\[\begin{equation*} \pi_e(1),\pi_e(2),\dots,\pi_e(n) \end{equation*}\]

其中\(\pi_e\)是\(\{1,2,\dots,n\}\)的一个随机排列。随后依次使用单个样本的梯度更新参数。这种方法称为随机梯度下降。此时每个批只包含一个样本,即\(B=1\),所以每一轮产生\(n\)次参数更新。

随机梯度下降不必等待全部样本的梯度计算完成便可以更新参数,并且梯度中由抽样带来的随机扰动可能帮助迭代点离开部分平坦区域。其缺点是单个样本对总体梯度的代表性较弱,梯度方差较大,目标函数值和参数序列容易发生明显波动。

16.9.1.4 小批量梯度下降

批量梯度下降的梯度准确但更新次数较少,随机梯度下降能够频繁更新参数、帮助脱离平坦区域但梯度方差较大。介于二者之间,可以在每次迭代中使用包含多个样本、但又不等于整个训练集的批\(\mathcal{B}_{er}\)。使用这一种梯度估计更新参数的方法称为小批量梯度下降。此时\(1<B<n\),每一轮产生\(M=\lceil n/B\rceil\)次参数更新。

批大小能够直接控制梯度估计的随机性。批过小时参数波动较大,批过大时则逐渐失去频繁更新和随机扰动的作用。

16.9.1.5 梯度消失与梯度爆炸

神经网络参数的梯度需要根据链式法则由输出层向输入层逐层传递。由于这一过程包含许多导数的连续相乘,当其中大多数因子的绝对值小于\(1\)时,连乘结果会随着网络深度迅速缩小;当其中许多因子的绝对值大于\(1\)时,连乘结果则可能迅速增大。前一种现象称为梯度消失(vanishing gradient),后一种现象称为梯度爆炸(exploding gradient)。

发生梯度消失时,靠近输入端的网络层得到的梯度接近\(0\),相应参数在每次迭代中几乎不发生变化。于是这些层的学习速度远慢于靠近输出端的网络层,严重时即使继续增加训练轮数也难以得到有效更新。随着网络深度增加,更多网络层可能处于这种近似停止学习的状态,使增加深度无法带来预期的表达能力。

发生梯度爆炸时,部分参数的梯度范数会变得很大,使一次迭代产生过大的参数变化。此时经验风险可能在迭代过程中剧烈波动,参数序列可能偏离原有的下降区域,严重时甚至无法收敛。

因此,梯度消失主要表现为参数更新过慢,而梯度爆炸主要表现为参数更新幅度失去控制。

预防梯度消失或梯度爆炸,首先应当选择导数不易接近\(0\)且不会过大的激活函数。Sigmoid函数和双曲正切函数在当\(|x|\)较大时导数都接近\(0\),因此在深层网络的隐藏层中反复使用这类激活函数会增大梯度消失的可能性。对于梯度爆炸,还可以在参数更新前限制梯度的整体大小。

Definition 16.47. 将关于全部参数的梯度依次排列为向量\(g\)。给定阈值\(c>0\),定义按整体范数进行的梯度裁剪(gradient clipping)为:

\[\begin{equation*} \operatorname{clip}_c(g)= \begin{cases} g,&||g||\leqslant c \\ \dfrac{c}{||g||}g,&||g||>c \end{cases} \end{equation*}\]

经过裁剪后总有\(||\operatorname{clip}_c(g)||\leqslant c\),并且当\(\|g\|>c\)时只缩小梯度长度而不改变其方向。梯度裁剪为每次迭代中的参数变化提供了上界,可以防止个别过大的梯度直接造成参数更新失控。

16.9.2 序列网络

在许多预测问题中,一个样本所包含的信息并非集中在单个观测中,而是沿着某种顺序逐步展开。模型依次接收\(x_1,x_2,\dots,x_T\),并在处理当前观测\(x_t\)时结合此前已经获得的信息,从而不断更新对整个样本的理解。例如,文本的含义随着词语依次出现而逐渐形成,语音信号需要结合前后时刻的变化加以识别,患者的健康状态也常常需要根据一系列按时间记录的检查结果进行判断。因此,这类预测问题的输入不应被视为彼此独立的一组向量,而应被视为一个具有内部顺序的整体。

Definition 16.48. 设\(T\in\mathbb{N}^+\)。称有序向量组:

\[\begin{equation*} x_{1:T}=(x_1,x_2,\dots,x_T),\quad x_t\in\mathbb{R}^p,\quad t=1,2,\dots,T \end{equation*}\]

为长度为\(T\)的序列(sequence),其中\(t\)为时间步(time step)索引,\(x_t\)表示第\(t\)个时间步的观测向量。

本节考虑序列到单值输出的任务,即将整个输入序列\(x_{1:T}\)映射为单个输出\(y\),其中\(y\)可以是离散的类别标签,也可以是连续的数值。

为了在预测中体现输入序列的先后顺序,一种自然的建模方式是按照\(x_1,x_2,\dots,x_T\)的顺序依次处理各个观测。在处理序列的过程中,模型维护一个随时间步不断更新的内部状态:每读入一个新的观测,便结合当前观测与此前形成的状态更新这一内部状态,从而使历史信息能够沿序列逐步传递。

Definition 16.49. 设映射\(f_\theta:\mathbb{R}^p\times\mathbb{R}^d\to\mathbb{R}^d\),给定初始状态\(h_0\in\mathbb{R}^d\)。对于输入序列\(x_1,x_2,\dots,x_T\),递推定义:

\[\begin{equation*} h_t=f_\theta(x_t,h_{t-1}),\quad t=1,2,\dots,T \end{equation*}\]

称\(h_t\)为模型在第\(t\)个时间步的隐藏状态(hidden state)。

隐藏状态\(h_t\)可以看作模型在处理完\(x_1,x_2,\dots,x_t\)后形成的内部表示。它并不是对全部历史输入的逐项存储,而是通过递推映射\(f_\theta\)对历史信息进行压缩和整合,从中保留对当前任务有用的信息。因此,在处理完整个输入序列后,可以将最终隐藏状态\(h_T\)视为模型对整个序列所提取的信息表示,并在此基础上进行预测。例如将\(h_T\)输入到一个一般的神经网络\(g\)中去得到\(\hat{y}=g(h_T)\)。

初始状态\(h_0\)通常取为\(\mathbf{0}\),也可以作为模型参数进行学习。由于在所有时间步中使用的是同一个映射\(f_\theta\),模型的参数数量不会随序列长度\(T\)增加,并且同一模型可以递推处理不同长度的输入序列。

note 16.32. 在这一统一表示下,序列网络的核心在于如何构造状态更新映射\(f_\theta\)。不同的序列网络本质上对应于\(f_\theta\)的不同参数化方式。

16.9.2.1 循环神经网络

Definition 16.50. 设输入维数为\(p\),隐藏状态维数为\(d\),给定:

\[\begin{equation*} W_x\in\mathbb{R}^{d\times p},\quad W_h\in\mathbb{R}^{d\times d},\quad b\in\mathbb{R}^d \end{equation*}\]

则第\(t\)个时间步的隐藏状态定义为:

\[\begin{equation*} h_t=\phi(W_xx_t+W_hh_{t-1}+b),\quad t=1,2,\dots,T \end{equation*}\]

其中\(\phi\)为逐元素作用的激活函数,初始状态\(h_0\)为给定向量。由上式定义的序列映射称为循环神经网络(recurrent neural network, RNN)。

在这一状态更新式中,\(W_xx_t\)提取当前输入的信息,\(W_hh_{t-1}\)将上一时间步形成的历史状态传递到当前时间步。

为了逐层提取序列特征,可以将多个RNN沿网络深度方向堆叠。第\(l\)层第\(t\)个时间步的输入为第\(l-1\)层第\(t\)个时间步计算出的隐藏状态。

TikZ 数学图示
图 16.18 多层循环神经网络结构示意图

Algorithm 16.15 Forward Propagation of a Multi-layer RNN

Input: Input sequence \(\{x_t^{(1)}\}_{t=1}^T\), parameters \(\{W_h^{(l)}, W_x^{(l)}, b^{(l)}\}_{l=1}^{L}\), activation functions \(\phi(\cdot)\)

Output: Hidden states \(\{h_t^{(L)}\}_{t=1}^{T}\)

Initialize hidden states \(h_0^{(l)} = \mathbf{0}\) for all \(l = 1,\dots,L\)

for \(t = 1\) to \(T\) do (Iterate over time steps)

for \(l= 1\) to \(L\) do (Iterate over layers)

if \(l\ne1\) then

\(x_t^{(l)} \gets h_t^{(l-1)}\) (Output of previous layer as current input)

end if

\(h_t^{(l)}\gets\phi\left(W_h^{(l)}h_{t-1}^{(l)}+W_x^{(l)}x_t^{(l)}+b^{(l)}\right)\)

end for

end for

对于序列到单个输出的任务,需要进一步将隐藏状态序列\(\{h_t^{(L)}\}_{t=1}^{T}\)汇总为一个固定维数的序列表示。常见的做法是取最终隐藏状态,或者对所有隐藏状态求均值,即:

\[\begin{equation*} z=h_T^{(L)}\quad\text{或}\quad z=\frac{1}{T}\sum_{t=1}^Th_t^{(L)} \end{equation*}\]

得到序列表示\(z\)后,可将其输入一个一般的神经网络\(g\)从而得到输出值。

note 16.33. RNN在每个时间步都用当前输入和上一隐藏状态共同生成新的隐藏状态,但没有单独的机制判断哪些历史信息应当保留、哪些信息应当遗忘。随着时间步不断推进,较早的信息可能在反复更新中逐渐被后来的输入覆盖,导致模型难以区分当前状态中的短期信息和需要长期保留的信息。

RNN的另一个重要问题出现在长序列训练过程中。梯度沿时间方向反向传播时,需要连续经过许多次状态更新;链式法则中的导数连乘可能使早期时间步的梯度迅速缩小,形成梯度消失,也可能使梯度迅速增大,形成梯度爆炸。

16.9.2.2 长短期记忆网络

长短期记忆网络(long short-term memory, LSTM)是为了解决普通RNN在长序列训练中出现的梯度消失与梯度爆炸问题而提出的结构,它在隐藏状态\(h_t\)之外引入细胞状态(cell state)\(\;c_t\in\mathbb{R}^d\)。隐藏状态主要用于产生当前输出并传递给下一时间步,细胞状态则为历史信息提供一条单独的传递路径。模型利用若干取值位于\((0,1)\)的向量控制信息在各个分量上的保留、写入和输出,这些向量统称为门(gate)。

将上一时间步的隐藏状态与当前输入拼接为:

\[\begin{equation*} q_t=[h_{t-1};x_t]\in\mathbb{R}^{d+p} \end{equation*}\]

其中分号表示按分量纵向拼接。给定权重矩阵:

\[\begin{equation*} W_f,W_i,W_c,W_o\in\mathbb{R}^{d\times(d+p)} \end{equation*}\]

和偏置\(b_f,b_i,b_c,b_o\in\mathbb{R}^d\),LSTM按照下列顺序更新状态。递推开始前还需给定初始隐藏状态\(h_0\)和初始细胞状态\(c_0\)。

首先计算遗忘门:

\[\begin{equation*} f_t=\sigma(W_fq_t+b_f) \end{equation*}\]

Sigmoid函数逐分量将结果映射到\((0,1)\)。\(f_t\)的某一分量越接近\(1\),相应的旧细胞状态越多地被保留;越接近\(0\),相应信息越多地被遗忘。

随后计算输入门和候选细胞状态:

\[\begin{align*} i_t&=\sigma(W_iq_t+b_i),\\ \widetilde{c}_t&=\tanh(W_cq_t+b_c) \end{align*}\]

输入门\(i_t\)决定候选信息中有多少可以写入细胞状态,候选状态\(\widetilde{c}_t\)则给出根据当前输入和上一隐藏状态形成的新信息。由遗忘门和输入门共同更新细胞状态:

\[\begin{equation*} c_t=f_t\odot c_{t-1}+i_t\odot\widetilde{c}_t \end{equation*}\]

其中\(\odot\)表示Hadamard乘积。第一项表示从旧细胞状态中保留的信息,第二项表示写入的新信息。

最后计算输出门与隐藏状态:

\[\begin{align*} o_t&=\sigma(W_oq_t+b_o),\\ h_t&=o_t\odot\tanh(c_t) \end{align*}\]

输出门\(o_t\)决定细胞状态的哪些分量可以形成当前隐藏状态。预测结果仍然由\(h_t\)经过任务所需的输出映射得到。

TikZ 数学图示
图 16.19 单个时间步的LSTM状态更新

构造多层LSTM时,第\(l\)层在时间\(t\)产生的\(h_t^{(l)}\)作为第\(l+1\)层在同一时间步的输入,而每一层分别维护自己的隐藏状态和细胞状态。

TikZ 数学图示
图 16.20 多层LSTM在时间维度上的展开结构

Algorithm 16.16 Forward Propagation of a Multi-layer LSTM

Input: Input sequence \(\{x_t^{(1)}\}_{t=1}^T\), parameters \(\{W_f^{(l)},W_i^{(l)},W_c^{(l)},W_o^{(l)},b_f^{(l)},b_i^{(l)},b_c^{(l)},b_o^{(l)}\}_{l=1}^{L}\)

Output: Hidden states \(\{h_t^{(l)}\}_{l=1}^{L}\) and cell states \(\{c_t^{(l)}\}_{l=1}^{L}\)

Initialize \(h_0^{(l)}=\mathbf{0},\; c_0^{(l)}=\mathbf{0}\) for all \(l=1,\dots,L\)

for \(t=1\) to \(T\) do (Iterate over time steps)

for \(l=1\) to \(L\) do (Iterate over layers)

if \(l\ne 1\) then

\(x_t^{(l)} \gets h_t^{(l-1)}\) (Input from the previous layer)

end if

Forget gate: controls how much of \(c_{t-1}^{(l)}\) is retained

\[\begin{equation*} f_t^{(l)} \gets \sigma\!\left(W_f^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_f^{(l)}\right) \end{equation*}\]

Input gate: controls how much new information is written

\[\begin{equation*} i_t^{(l)} \gets \sigma\!\left(W_i^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_i^{(l)}\right) \end{equation*}\]

Candidate cell state: new content to be added

\[\begin{equation*} \tilde c_t^{(l)} \gets \tanh\!\left(W_c^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_c^{(l)}\right) \end{equation*}\]

Cell state update: forget old + write new

\[\begin{equation*} c_t^{(l)} \gets f_t^{(l)}\odot c_{t-1}^{(l)} + i_t^{(l)}\odot \tilde c_t^{(l)} \end{equation*}\]

Output gate: controls exposure of cell state

\[\begin{equation*} o_t^{(l)} \gets \sigma\!\left(W_o^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_o^{(l)}\right) \end{equation*}\]

Hidden state: gated and activated cell state

\[\begin{equation*} h_t^{(l)} \gets o_t^{(l)}\odot \tanh(c_t^{(l)}) \end{equation*}\]

end for

end for

对于序列到单个输出的任务,LSTM同样可以将最后一层的隐藏状态序列\(\{h_t^{(L)}\}_{t=1}^{T}\)汇总为固定维数的序列表示,例如:

\[\begin{equation*} z=h_T^{(L)}\quad\text{或}\quad z=\frac{1}{T}\sum_{t=1}^Th_t^{(L)} \end{equation*}\]

随后将\(z\)输入一个一般的神经网络\(g\),得到最终输出值。

16.9.2.3 门控循环单元

门控循环单元(gated recurrent unit, GRU)的设计目标是对LSTM的门控记忆机制进行简化。它将细胞状态与隐藏状态合并为单一的隐藏状态,并只保留重置门和更新门,从而减少需要维护的状态和门控变量,在保留门控信息传递能力的同时降低模型结构的复杂程度。

将上一隐藏状态与当前输入拼接为:

\[\begin{equation*} q_t=[h_{t-1};x_t]\in\mathbb{R}^{d+p} \end{equation*}\]

给定权重矩阵\(W_r,W_z\in\mathbb{R}^{d\times(d+p)}\)和偏置\(b_r,b_z\in\mathbb{R}^d\),分别定义重置门和更新门:

\[\begin{align*} r_t&=\sigma(W_rq_t+b_r),\\ z_t&=\sigma(W_zq_t+b_z) \end{align*}\]

两个门的每个分量都位于\((0,1)\)。重置门用于控制旧隐藏状态在形成候选状态时所占的比例,更新门则用于控制最终状态中旧信息和候选信息的组合比例。

将重置门作用在上一隐藏状态上,再与当前输入拼接,得到候选隐藏状态:

\[\begin{equation*} \widetilde{h}_t=\tanh\left(W_h[r_t\odot h_{t-1};x_t]+b_h\right) \end{equation*}\]

其中\(W_h\in\mathbb{R}^{d\times(d+p)}\),\(b_h\in\mathbb{R}^d\)。当\(r_t\)的某一分量接近\(0\)时,上一隐藏状态的相应信息在形成候选状态时受到抑制;当其接近\(1\)时,相应历史信息被较完整地用于计算候选状态。

最终隐藏状态由旧隐藏状态和候选隐藏状态逐分量组合:

\[\begin{equation*} h_t=z_t\odot h_{t-1}+(1-z_t)\odot\widetilde{h}_t \end{equation*}\]

在这一约定下,\(z_t\)越接近\(1\),旧隐藏状态保留得越多;\(z_t\)越接近\(0\),当前状态越多地采用候选隐藏状态。由于这一更新包含旧状态与候选状态的加法组合,模型可以在不同时间步上选择保持已有信息或者写入新信息。

TikZ 数学图示
图 16.21 单个时间步的GRU状态更新

构造多层GRU时,第\(l\)层在时间\(t\)产生的\(h_t^{(l)}\)作为第\(l+1\)层同一时间步的输入,每一层分别维护自己的隐藏状态。

Algorithm 16.17 Forward Propagation of a Multi-layer GRU

Input: Input sequence \(\{x_t^{(1)}\}_{t=1}^T\), parameters \(\{W_z^{(l)},W_r^{(l)},W_h^{(l)},b_z^{(l)},b_r^{(l)},b_h^{(l)}\}_{l=1}^{L}\)

Output: Hidden states \(\{h_t^{(l)}\}_{l=1}^{L}\)

Initialize \(h_0^{(l)}=\mathbf{0}\) for all \(l=1,\dots,L\)

for \(t=1\) to \(T\) do (Iterate over time steps)

for \(l=1\) to \(L\) do (Iterate over layers)

if \(l\ne 1\) then

\(x_t^{(l)} \gets h_t^{(l-1)}\) (Input from the previous layer)

end if

Update gate: controls how much of the past state is retained

\[\begin{equation*} z_t^{(l)} \gets \sigma\!\left(W_z^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_z^{(l)}\right) \end{equation*}\]

Reset gate: controls how much past information is ignored

\[\begin{equation*} r_t^{(l)} \gets \sigma\!\left(W_r^{(l)}[h_{t-1}^{(l)},x_t^{(l)}]+b_r^{(l)}\right) \end{equation*}\]

Candidate hidden state: new content to be written

\[\begin{equation*} \tilde h_t^{(l)} \gets \tanh\!\left(W_h^{(l)}[r_t^{(l)}\odot h_{t-1}^{(l)},x_t^{(l)}]+b_h^{(l)}\right) \end{equation*}\]

Hidden state update: interpolate between old and new

\[\begin{equation*} h_t^{(l)} \gets z_t^{(l)}\odot h_{t-1}^{(l)} + (1-z_t^{(l)})\odot \tilde h_t^{(l)} \end{equation*}\]

end for

end for

GRU得到最终输出值的方法与RNN和LSTM一样。

16.9.2.4 注意力机制

在前述序列网络中,模型通过隐藏状态沿时间步递推,逐步整合序列中的历史信息。在这一过程中,序列中不同时间步的信息对最终输出可能具有不同的重要性,而这种重要性并非仅由时间步本身决定,还与模型当前需要提取的信息有关。例如,在阅读一段较长的文本后,如果需要回答主人公的姓名,模型可能需要重点关注文本开头的人物介绍;而如果需要回答主人公最终去了哪里,则可能需要更多地关注文本结尾的情节。面对同一段文本,模型所需的信息不同,需要重点关注的时间步也可能随之改变。

对于序列到单个输出的任务,前述序列网络通常取最终隐藏状态\(h_T\)或对所有隐藏状态求均值来构造序列表示\(z\)。这些方法虽然能够通过学习模型参数,使不同时间步的信息对最终表示产生不同的影响,但其汇总方式本身并不具备根据当前信息需求对各个时间步的隐藏状态显式分配不同权重的机制。我们希望在已有隐藏状态的基础上,引入一种更加灵活的信息提取方式,使模型能够根据当前需要的信息,有选择地关注序列中的不同时间步,并据此构造相应的序列表示。这便引出了注意力机制(attention mechanism)。

注意力机制通过隐藏状态的加权求和来构造序列表示:

\[\begin{equation*} z=\sum_{t=1}^{T}\alpha_th_t \end{equation*}\]

其中\(\alpha_t\)表示第\(t\)个时间步的隐藏状态\(h_t\)在序列表示中的相对权重,满足\(\alpha_t\geqslant0\)且\(\sum\limits_{t=1}^{T}\alpha_t=1\)。当某个时间步包含与当前输出需求高度相关的信息时,模型可以为其分配较大的权重;反之则可以降低其贡献。接下来的核心问题在于:如何使模型根据当前的信息需求自动计算各个时间步的权重\(\alpha_t\)。

一种自然的建模方式是引入查询向量\(q\in\mathbb{R}^{d_k}\)来表示模型当前的信息需求,并通过可学习的评分函数\(a_\theta\)衡量查询向量与各个隐藏状态的相关程度:

\[\begin{equation*} e_t=a_\theta(q,h_t) \end{equation*}\]

称\(e_t\in\mathbb{R}\)为注意力分数。接着使用Softmax函数将注意力分数转化为满足非负性与归一化条件的权重:

\[\begin{equation*} \alpha_t=\frac{\exp(e_t)}{\sum\limits_{i=1}^{T}\exp(e_i)} \end{equation*}\]

\(q\)与\(a_{\theta}\)一般的具体形式为:

\[\begin{equation*} q=W_Qh_T,\quad a_\theta(q,h_t)=\frac{(W_Qh_T)^\top(W_Kh_t)}{\sqrt{d_k}} \end{equation*}\]

其中\(W_Q,W_K\in M_{d_k\times d}(\mathbb{R})\)为权重矩阵。称\(k_t=W_Kh_t\)为键向量。这里使用最后一个隐藏状态\(h_T\)构造查询向量,是因为查询向量\(q\)表示模型当前的信息需求,其构造应当基于当前可获得的全部信息,而\(h_T\)恰好整合了整个输入序列的信息。评分函数\(a_\theta\)采用内积形式,是因为向量内积可以用于衡量向量之间的相似程度。由于内积运算要求两个向量具有相同的维度,于是引入键向量\(k_t\)。

note 16.34. 评分函数中的缩放因子\(\sqrt{d_k}\)用于控制内积随向量维度增大而产生的数值波动。

假设查询向量\(q\)与键向量\(k_t\)的各分量是\(L_2(X,\mathscr{F},P)\)上相互独立的随机变量且均具有零均值和单位方差,\((q_i,k_{ti})\)相互独立。由性质 6.2.1(4)、性质 6.4.3(7)、性质 6.4.2(3)(1)和性质 6.3.1(2)可知:

\[\begin{align*} &\operatorname{Var}(q^\top k_t)=\operatorname{Var}\left(\sum_{i=1}^{d_k}q_ik_{ti}\right)=\sum_{i=1}^{d_k}\operatorname{Var}(q_ik_{ti})=\sum_{i=1}^{d_k}\{\operatorname{E}[(q_ik_{ti})^2]-[\operatorname{E}(q_ik_{ti})]^2\} \\ =&\sum_{i=1}^{d_k}\{\operatorname{E}(q_i^2)\operatorname{E}(k_{ti}^2)-[\operatorname{E}(q_i)\operatorname{E}(k_{ti})]^2\}=\sum_{i=1}^{d_k}\{\operatorname{Var}(q_i)+[\operatorname{E}(q_i)]^2\}\{\operatorname{Var}(k_{ti})+[\operatorname{E}(k_{ti})]^2\}=d_k \end{align*}\]

因此随着向量维度\(d_k\)的增加,注意力分数的数值尺度也会增大。当不同时间步的内积分数并非高度相关时,这种尺度增长也会使它们之间的绝对差异倾向于增大。

这一现象之所以需要关注,是因为Softmax对注意力分数之间的绝对差异十分敏感。由定义可知两个时间步的注意力权重之比满足:

\[\begin{equation*} \frac{\alpha_i}{\alpha_j}=\exp(e_i-e_j) \end{equation*}\]

这意味着,即使不同时间步之间的相对大小关系没有发生变化,仅仅放大注意力分数的数值尺度,也会显著改变最终的权重分布。例如,考虑两个时间步的注意力分数分别为\(10\)和\(11\),则较高分数对应的权重为:

\[\begin{equation*} \frac{\exp(11)}{\exp(10)+\exp(11)}=\frac{1}{1+\exp(-1)}\approx0.731 \end{equation*}\]

如果仅将两个分数同时放大\(100\)倍,变为\(1000\)和\(1100\),则相应的权重变为:

\[\begin{equation*} \frac{\exp(1100)}{\exp(1000)+\exp(1100)} =\frac{1}{1+\exp(-100)}\approx1 \end{equation*}\]

这说明Softmax不仅取决于不同时间步的相对分数差异,还依赖于分数差异的数值尺度。

因此,当向量维度较大时,未经缩放的内积可能仅仅因为维度带来的数值尺度增长,就使Softmax产生过度集中的注意力分配。更严重的是,如果在模型训练初期就产生这种权重过于集中的现象,它还会影响梯度传播,使模型难以在后续训练中纠正错误的注意力分配。Softmax函数的偏导数为:

\[\begin{equation*} \frac{\partial\alpha_i}{\partial e_j}=\alpha_i[I(i=j)-\alpha_j] \end{equation*}\]

当时间步\(i\)的注意力分数显著高于其他时间步时,\(\alpha_i\)趋近于\(1\),其余时间步的权重则趋近于\(0\)。此时,无论\(i=j\)还是\(i\neq j\),上述偏导数都将趋近于\(0\)。这意味着即使当前的注意力分配并不合理,模型的预测误差仍然较大,损失函数也可能难以通过注意力分数向评分函数的参数传播有效梯度。

由性质 5.4.3(6)可知:

\[\begin{equation*} \operatorname{Var}\left(\frac{q^\top k_t}{\sqrt{d_k}}\right)=1 \end{equation*}\]

所以缩放后的注意力分数具有不随维度增长的方差,从而可以使得注意力权重的分配更多地由模型学习到的相关性决定,而非单纯受到向量维度的影响。

16.9.2.5 自注意力

前面介绍的注意力机制虽然能够根据不同时间步的重要程度对隐藏状态进行加权求和,但仍然依赖于隐藏状态的递归生成。由于每个时间步的计算都依赖于前一个时间步的结果,因此无法在序列维度上实现完全并行计算。当序列较长时,这种递归依赖会限制模型的计算效率。另一方面,序列中某个时间步的信息往往需要结合整个序列的上下文才能得到充分理解。然而,在传统的单向递归结构中,第\(t\)个时间步的隐藏状态\(h_t\)只能整合当前时间步及其之前的信息,无法利用后续时间步的信息。因此,我们希望摆脱隐藏状态逐步传递的限制,使序列中每个时间步都能够直接与其他时间步建立联系,从而获得融合全局上下文信息的表示。

自注意力(self-attention)解决了上述问题。设输入序列中第\(i\)个时间步的初始表示为\(x_i\in\mathbb{R}^d\)。对于每个时间步,计算:

\[\begin{equation*} q_i=W_Qx_i,\quad k_i=W_Kx_i,\quad v_i=W_Vx_i \end{equation*}\]

其中\(W_Q,W_K\in M_{d_k\times d}(\mathbb{R}),\;W_V\in M_{d_v\times d}(\mathbb{R})\)为权重矩阵,\(q_i\)和\(k_i\)是前述的查询向量与键向量,称\(v_i\)为值向量。通过计算\(q_i\)与所有键向量的内积,可以得到第\(i\)个时间步对其他时间步的注意力权重:

\[\begin{equation*} \alpha_{ij}=\frac{\exp(q_i^\top k_j/\sqrt{d_k})}{\sum\limits_{l=1}^{T}\exp(q_i^\top k_l/\sqrt{d_k})} \end{equation*}\]

随后根据这些权重对所有时间步的值向量进行加权求和得到第\(i\)个时间步的新表示:

\[\begin{equation*} z_i=\sum_{j=1}^{T}\alpha_{ij}v_j \end{equation*}\]

于是每个时间步的新表示\(z_i\)都能够综合整个序列的信息,而且不同时间步可以根据各自的查询向量选择性地关注序列中的不同部分。与此同时,各个时间步之间不存在递归计算依赖,因此可以将全部时间步的注意力计算统一表示为矩阵运算。设输入序列的表示矩阵为\(X\in\mathbb{R}^{T\times d}\),其中第\(i\)行为\(x_i^\top\)。将所有时间步的查询向量、键向量和值向量分别按行排列,得到:

\[\begin{equation*} Q=XW_Q^\top,\quad K=XW_K^\top,\quad V=XW_V^\top \end{equation*}\]

其中\(Q,K\in\mathbb{R}^{T\times d_k}\),\(V\in\mathbb{R}^{T\times d_v}\)。注意到矩阵\(QK^\top\in\mathbb{R}^{T\times T}\)的第\((i,j)\)个元素恰好为\(q_i^\top k_j\),因此可以通过一次矩阵乘法计算所有时间步之间的注意力分数。对该矩阵的每一行应用Softmax函数,即可得到所有时间步的注意力权重。进一步将上述结果与值矩阵\(V\)相乘,便得到整个序列的新表示:

\[\begin{equation*} Z=\operatorname{Softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \end{equation*}\]

其中\(Z\in\mathbb{R}^{T\times d_v}\),其第\(i\)行对应第\(i\)个时间步的输出向量\(z_i^\top\)。

对于需要预测整个序列属性的任务,可以将各个时间步的表示进一步汇总为一个序列表示。例如,可以直接对所有时间步的表示取平均,也可以通过可学习的权重衡量不同时间步对最终预测的重要程度,并进行加权求和,随后将得到的序列表示输入一般的网络,即可完成分类或回归任务。而对于需要逐时间步预测的任务,则无需将所有表示汇总为一个向量,而是分别将每个时间步的表示输入一般的网络,从而得到对应时间步的预测结果。

note 16.35. 并非所有任务都允许每个时间步访问整个序列的信息。例如,在需要根据当前及历史信息预测后续结果的任务中,第\(i\)个时间步不能利用之后的信息,否则便会造成信息泄漏。为此,可以引入因果注意力掩码(causal attention mask),在Softmax计算之前对注意力分数进行限制:

\[\begin{equation*} Z=\operatorname{Softmax}\left(\frac{QK^\top}{\sqrt{d_k}}+B\right)V \end{equation*}\]

其中掩码矩阵\(B\in\mathbb{R}^{T\times T}\)定义为:

\[\begin{equation*} B_{ij}= \begin{cases} 0,&j\leq i\\ -\infty,&j>i \end{cases} \end{equation*}\]

由于\(\exp(-\infty)=0\),经过Softmax后,第\(i\)个时间步对所有后续时间步\(j>i\)的注意力权重均为\(0\),从而保证当前时间步只能利用自身及此前的信息。

因果注意力掩码仅限制不同时间步之间的信息传递,不会引入时间步之间的递归计算依赖。因此,在已知完整训练序列的情况下,模型仍然可以通过矩阵运算并行计算所有时间步的表示与预测结果,从而在避免未来信息泄漏的同时保留自注意力机制的并行计算优势。

note 16.36. 除了因果注意力掩码之外,另一种常见的掩码是填充注意力掩码(padding attention mask)。由于不同序列的长度往往并不相同,为了按批进行模型训练,我们会在较短序列的末尾添加填充时间步,使同一批次中的所有序列具有相同的长度。由于这些填充时间步并不包含实际信息,如果直接参与注意力计算,就可能影响有效位置的注意力权重及其最终表示。

为此,可以通过填充注意力掩码排除这些无效位置。设\(\mathcal{I}_{\mathrm{valid}}\)表示序列中有效位置的索引集合,定义掩码矩阵\(P\in\mathbb{R}^{T\times T}\)为:

\[\begin{equation*} P_{ij}= \begin{cases} 0,&j\in\mathcal{I}_{\mathrm{valid}}\\ -\infty,&j\notin\mathcal{I}_{\mathrm{valid}} \end{cases} \end{equation*}\]

与因果注意力掩码类似,将\(P\)加到注意力分数矩阵后,填充位置对应的注意力权重便为\(0\),从而保证有效位置的表示仅由真实的序列信息构成。

对于填充时间步对应的\(z_t\),通常还需要在后续损失计算中予以忽略。

16.9.2.6 多头注意力

在自注意力机制中,每个时间步虽然能够根据自身的信息需求从整个序列中提取相关信息,但对于固定的时间步\(i\),模型只能生成一组注意力权重,然而序列中不同时间步之间可能同时存在多种关联关系,单一的注意力权重未必能够充分刻画这些关系。因此,可以考虑同时构造多个独立的注意力机制,学习序列中各个时间步之间的多种关联,最后将这些信息综合起来。这种方法称为多头注意力(multi-head attention)。

假设使用\(M\)个注意力头(attention head),对于第\(m\)个注意力头,引入与其它注意力头独立的权重矩阵\(W_Q^{(m)},W_K^{(m)}\in M_{d_k\times d}(\mathbb{R})\)和\(W_V^{(m)}\in M_{d_v\times d}(\mathbb{R})\),构造:

\[\begin{gather*} Q^{(m)}=X(W_Q^{(m)})^\top,\quad K^{(m)}=X(W_K^{(m)})^\top,\quad V^{(m)}=X(W_V^{(m)})^\top \\ Z^{(m)}=\operatorname{Softmax}\left(\frac{Q^{(m)}(K^{(m)})^\top}{\sqrt{d_k}}\right)V^{(m)} \end{gather*}\]

在得到所有注意力头的输出后,将它们进行拼接并通过一个可学习的线性映射进行融合,得到最终输出:

\[\begin{equation*} Z=\operatorname{Concat}\left(Z^{(1)},Z^{(2)},\dots,Z^{(M)}\right)W_O^\top \end{equation*}\]

其中\(W_O\in M_{d\times Md_v}(\mathbb{R})\)。

16.9.3 图像网络

先说明计算机如何表示图像,再讨论如何利用图像自身的结构设计网络。

具体地,一幅图像可以看作规则空间网格上的数值函数。将连续的平面区域离散化为由\(H\times W\)个小方格组成的网格,每一个小方格称为一个像素(pixel),有序对\((H,W)\)称为图像的空间分辨率(spatial resolution)。像素的空间位置通常用整数坐标\((p,q)\)表示,其中\(p=1,2,\dots,H,\;q=1,2,\dots,W\)。因此,一幅分辨率为\(H\times W\)的图像可以看作在离散网格\(\{1,2,\dots,H\}\times\{1,2,\dots,W\}\)上定义的数值函数。

对于灰度图像而言,每个像素仅对应一个实数,用来描述该位置的亮度。灰度强度刻画了该像素有多亮或有多暗,通常取值在某个区间内,例如:

\[\begin{equation*} I_{p,q}\in[0,255]\quad\text{或}\quad I_{p,q}\in[0,1] \end{equation*}\]

其中数值越大表示亮度越高,数值越小表示亮度越低。这样,一幅灰度图像可以用一个矩阵\(I\in M_{H\times W}(\mathbb{R})\)来表示。

对于彩色图像,每个像素不仅具有亮度信息,还包含颜色信息。计算机中最常用的颜色表示方式是RGB模型,即用红色、绿色与蓝色三种基色的强度线性组合来表示任意颜色。对应地,每一个像素不再是一个标量,而是一个三维向量,这三个分量分别称为三个通道(channel),表示该像素在R、G、B三种基色(即红绿蓝)上的强度大小。

设图像的空间分辨率为\(H\times W\),则一幅RGB彩色图像可以表示为一个三维张量:

\[\begin{equation*} I=(I_{p,q,c})\in\mathbb{R}^{H\times W\times3} \end{equation*}\]

其中\((p,q)\)表示像素的空间位置,\(c\in\{1,2,3\}\)分别对应红、绿、蓝三个颜色通道,\(I_{p,q,c}\)表示位于位置\((p,q)\)的像素在第\(c\)个通道上的强度,即该颜色分量的亮度大小。

从矩阵的角度看,一幅彩色图像等价于三张大小为\(H\times W\)的灰度图像在通道维度上的叠加:

\[\begin{equation*} I=\big(I^{(R)},I^{(G)},I^{(B)}\big) \end{equation*}\]

其中:

\[\begin{equation*} I^{(R)},I^{(G)},I^{(B)}\in\mathbb{R}^{H\times W} \end{equation*}\]

分别表示红色通道、绿色通道与蓝色通道对应的灰度矩阵。

换言之,像素坐标刻画空间位置,通道数值刻画颜色分量的强度。对于图像而言,空间位置不是可以任意交换的标签:交换两个像素的位置通常会改变图像的含义。正因为如此,图像模型需要同时尊重局部邻域关系和空间排列关系。

16.9.3.1 卷积

Definition 16.51. 设输入图像为\(X\in M_{H\times W}(\mathbb{R})\)。在图像的每一个空间位置\((i,j)\),取其邻域窗口:

\[\begin{equation*} \{(i+u-1,j+v-1):u=1,2,\dots,h,\; v=1,2,\dots,w\} \end{equation*}\]

并对该邻域内的像素值施加一组固定权重\(K=(K_{uv})\in M_{h\times w}(\mathbb{R})\),定义局部加权求和:

\[\begin{equation*} s_{ij}=\sum_{u=1}^h\sum_{v=1}^wK_{uv}X(i+u-1;j+v-1) \end{equation*}\]

由此在整个空间网格上得到的二维数组\(S=(s_{ij})\)称为由该局部加权算子产生的特征图(feature map)。对于包含多个颜色通道的输入,每个通道都有一组对应的权重。

Definition 16.52. 称矩阵\(K=(K_{uv})\in M_{h\times w}(\mathbb{R})\)为卷积核(convolution kernel)。

note 16.37. 卷积是针对空间网格设计的局部算子。它包含以下几方面的建模假设。

  1. 局部连接:输出位置\((i,j)\)只使用输入位置\((i,j)\)附近的窗口,而不直接连接整幅图像。这个假设利用了图像的局部相关性:相邻像素往往共同构成边缘、纹理等局部结构。

  2. 权值共享:同一个卷积核\(K\)被用于所有空间位置,因此模型学习的是某种局部模式,而不是某个固定位置上的特定像素组合。这样既显著减少了参数数量,也使同一种模式出现在不同位置时能够被同一组参数检测出来。

  3. 平移等变性:在忽略边界处理的影响时,如果输入图像中的局部模式发生平移,由该卷积核产生的特征图也会相应平移,而不会因为模式改变了绝对位置就完全失效。

TikZ 数学图示
图 16.22 二维离散卷积的滑动窗口与输出位置对应关系示意图

16.9.3.2 池化

Definition 16.53. 设输入特征图为\(X\in M_{H\times W}(\mathbb{R})\)。给定窗口尺寸\((h,w)\)以及步幅\((s_h, s_w)\)。在输出特征图位置\((i,j)\)处,取\(X\)中的元素集合:

\[\begin{equation*} W_{ij}=\bigl\{\,(\,(i-1)s_h+u,\;(j-1)s_w+v\,):u=1,2,\dots,h,\;v=1,2,\dots,w\,\bigr\} \end{equation*}\]

并在该集合内对像素值施加一个映射\(P:\mathbb{R}^{h\times w}\to\mathbb{R}\),称\(P\)为池化算子(pooling operator),该操作称为池化(pooling)。

TikZ 数学图示
图 16.23 窗口尺寸为(2, 2)、步幅为(2, 2)的池化计算示意图

note 16.38. 卷积得到的特征图在相邻空间位置上通常具有较强的冗余性,因此没有必要在网络的所有层中始终保留同样精细的空间分辨率。为了降低后续计算量,可以对特征图进行降采样。

但是,若直接从相邻位置中间隔取值,得到的结果会较强地依赖于具体的采样位置:输入发生很小的平移,就可能使被保留的响应发生明显变化。因此,可以对一个局部邻域内的响应进行聚合,再用聚合结果代表这一邻域。池化正是基于这一思想:它在舍弃部分空间分辨率进行空间降采样的同时,使表示对局部范围内的位置变化不那么敏感。

常见池化函数有:

  1. 最大池化:在窗口内取最大值。

  2. 平均池化:在窗口内取均值。

16.9.3.3 填充

在上述卷积与池化定义中,邻域窗口\(W_{ij}\)在靠近边界时可能超出输入特征图\(X\)的定义域。为使边界位置仍能使用同样的局部算子,通常在输入特征图四周引入填充(padding),即补充一圈预先规定的数值,得到扩展特征图\(\tilde{X}\),然后对\(\tilde{X}\)进行卷积或池化操作。

note 16.39. 卷积中的padding除了规定边界之外的取值方式外,还可以控制卷积后特征图的空间尺寸。若卷积核大小为\(h\times w\),步长为\(1\),并分别在上下和左右各补充\(p_h\)行与\(p_w\)列,则卷积后的空间尺寸为:

\[\begin{equation*} (H+2p_h-h+1)\times(W+2p_w-w+1) \end{equation*}\]

因此,不使用padding时,卷积会使特征图的空间尺寸逐渐缩小;而当\(h,w\)均为奇数并取:

\[\begin{equation*} p_h=\frac{h-1}{2},\quad p_w=\frac{w-1}{2} \end{equation*}\]

时,可以使卷积前后的空间尺寸保持为\(H\times W\)。这种处理使边界附近的像素也能够参与与内部位置相同形式的局部卷积,同时避免特征图在连续卷积过程中因边界舍弃而过快缩小。

16.9.3.4 卷积神经网络

卷积神经网络(convolutional neural network, CNN)由特征提取阶段和预测阶段组成。特征提取阶段由若干卷积、激活和降采样操作交替堆叠而成,最前面的卷积层在小邻域内寻找边缘、方向和简单纹理等低级模式;后续卷积层再把相邻位置的低级模式组合成角点、局部形状等更复杂的结构;随着层数增加,单个位置能够利用的输入区域逐步扩大,最终形成对物体部件乃至整体形状的表示。池化进一步降低特征图的空间分辨率,使模型对小范围的位置变化更稳定。非线性激活函数使这些局部组合不再只是线性叠加,从而能够表达更复杂的判别关系。预测阶段将高层特征进行整合,并根据任务输出类别概率或其他预测量,也即将特征图展平为向量(多通道则每个通道展平为向量后再拼接为一个最终的特征向量)输入到分类器或回归器中。

TikZ 数学图示
图 16.24 卷积神经网络结构示意图

note 16.40. CNN是将图像的空间性质写入模型结构的神经网络。它适用于像素排列在规则网格上、邻近位置通常相关、同一种局部模式可能出现在不同位置的数据,例如自然图像和部分医学影像。对于行列只表示变量编号的矩阵,或者局部邻域没有稳定含义的表格数据,卷积所加入的空间假设未必成立,直接使用CNN反而可能限制模型的表达能力。

16.9.4 深层神经网络的常用改进方法

增加神经网络的层数通常能够扩大模型可以表示的函数范围,使网络有能力逐层构造更加复杂的特征表示。然而,网络变深并不意味着模型一定更容易获得更好的性能,因为深度增加的同时也会显著提高模型的优化难度。本节介绍几种针对这一问题的常用改进方法。

16.9.4.1 批归一化

设第\(l\)层的输入为\(h^{(l-1)}\),权重矩阵和偏置向量分别为\(W^{(l)}\)和\(b^{(l)}\),其线性变换为:

\[\begin{equation*} z^{(l)}=W^{(l)}h^{(l-1)}+b^{(l)} \end{equation*}\]

则在反向传播过程中,损失函数\(L\)关于权重矩阵\(W^{(l)}\)的梯度满足:

\[\begin{equation*} \nabla_{W^{(l)}}L=\frac{\partial L}{\partial z^{(l)}}\left(h^{(l-1)}\right)^{\top} \end{equation*}\]

因此,该层输入特征\(h^{(l-1)}\)的数值尺度会直接影响参数梯度的数值尺度。当不同网络层产生的特征在均值、方差或整体数值尺度上存在较大差异时,损失函数\(L\)对不同层参数的敏感程度也可能存在较大差异,从而使不同网络层参数的梯度具有不同的数值尺度。若采用统一的学习率\(\eta\)进行梯度下降,第\(l\)层的权重矩阵按照:

\[\begin{equation*} W^{(l)}\leftarrow W^{(l)}-\eta\nabla_{W^{(l)}}L \end{equation*}\]

进行更新。此时,实际的参数更新幅度不仅取决于学习率\(\eta\),还取决于梯度\(\nabla_{W^{(l)}}L\)的尺度。当不同网络层的梯度尺度相差较大时,同一个学习率便难以同时适应所有网络层:较大的学习率可能使梯度较大的网络层参数更新过于剧烈,导致损失函数发生震荡甚至使训练过程不稳定;而为了避免这种情况选取较小的学习率,又可能使梯度较小的网络层参数更新过于缓慢,从而降低整体的优化速度。

因此,使不同网络层产生的中间特征保持较为稳定且相近的数值尺度,有助于减小不同网络层之间梯度尺度的差异,使统一的学习率能够更加有效地作用于不同网络层,从而提高网络训练过程的稳定性。

Definition 16.54. 设某一网络层在一个批上得到的输入为\(\mathcal{B}=(x_{ij})\in M_{B\times p}(\mathbb{R})\),其中第\(i\)行表示第\(i\)个样本的输入向量,第\(j\)列表示第\(j\)个特征。对每一个特征\(j=1,2,\dots,p\),计算该批样本上的均值与方差:

\[\begin{equation*} \mu_{\mathcal{B}j}=\frac{1}{B}\sum_{i=1}^{B}x_{ij},\quad\sigma_{\mathcal{B}j}^2=\frac{1}{B}\sum_{i=1}^{B}(x_{ij}-\mu_{\mathcal{B}j})^2 \end{equation*}\]

给定常数\(\varepsilon>0\),将\(x_{ij}\)变换为:

\[\begin{equation*} \hat{x}_{ij}=\frac{x_{ij}-\mu_{\mathcal{B}j}}{\sqrt{\sigma_{\mathcal{B}j}^2+\varepsilon}} \end{equation*}\]

定义这一层的输出为:

\[\begin{equation*} \tilde{x}_{ij}=\gamma_j\hat{x}_{ij}+\beta_j \end{equation*}\]

其中\(\gamma_j\)和\(\beta_j\)是随网络其他参数一起学习的伸缩参数和平移参数。称上述变换为批归一化(batch normalization, BN)。

定义中的\(\varepsilon\)用于避免分母为\(0\)。

归一化使同一批次中各个特征的取值具有较为一致的数值尺度,从而减弱参数更新对参数初始值和学习率选择的敏感性,使网络的优化过程更加稳定。与此同时,引入可学习的伸缩参数\(\gamma_j\)和平移参数\(\beta_j\),使网络能够根据任务需要重新调整标准化后特征的尺度和位置。因此,BN在提供稳定数值尺度的基础上,保留网络学习适当特征分布的能力。

note 16.41. 上述给出的BN的计算过程是在训练阶段时进行的操作,每个样本的归一化结果会受到同一批中其他样本的影响,而在推断的阶段,我们希望训练好的神经网络表示一个确定的函数,即给定同一个输入\(x\),网络应该给出确定的输出,而不应该取决于这个样本恰好和哪些其他样本一起送进网络。为了在推断阶段获得不依赖于当前批次的统计量,通常在训练过程中为每一个特征维护均值和方差的滑动估计。

不同深度学习框架对均值与方差估计值的具体实现略有不同,但训练阶段使用当前批次统计量、推断阶段使用训练期间维护的固定统计量这一原则是一致的。

16.9.4.2 层归一化

批归一化沿样本方向计算同一特征的均值和方差。另一种思路是对每个样本分别归一化其内部特征。仍设\(X=(x_{ij})\in M_{B\times p}(\mathbb{R})\),对第\(i\)个样本计算:

\[\begin{align*} \mu_i&=\frac{1}{p}\sum_{j=1}^{p}x_{ij} \\ \sigma_i^2&=\frac{1}{p}\sum_{j=1}^{p}(x_{ij}-\mu_i)^2 \end{align*}\]

并定义:

\[\begin{align*} \hat{x}_{ij}&=\frac{x_{ij}-\mu_i}{\sqrt{\sigma_i^2+\varepsilon}} \\ y_{ij}&=\gamma_j\hat{x}_{ij}+\beta_j \end{align*}\]

称上述变换为层归一化(layer normalization, LN)。层归一化的均值和方差只由当前样本的特征决定,不依赖同一批中的其他样本,因此训练和预测可以使用相同的计算方式。对于序列网络,可以在每个时间步对隐藏状态向量进行层归一化,此时不同序列是否被分到同一批不会改变某个样本的归一化结果。

层归一化适用于批较小、样本长度不同或者不希望样本之间相互影响的情形。其局限在于,它把当前样本的一组特征共同用于估计均值和方差;如果这些特征的绝对尺度本身包含重要信息,或者不同特征不适合被放在同一归一化范围内,归一化就可能削弱有用信号。因此,使用层归一化时需要明确哪些维度共同描述一个特征向量,而不能仅因为输入是矩阵就沿所有维度进行归一化。

16.9.4.3 残差连接

设某一组网络层的输入为\(x\in\mathbb{R}^d\),希望得到的输出表示为\(y\in\mathbb{R}^d\)。在普通的层叠结构中,这组网络层需要利用参数\(\theta\)直接学习一个完整映射:

\[\begin{equation*} y=H_\theta(x) \end{equation*}\]

若输入\(x\)已经是较好的特征表示,新增网络层可能只需要对它进行很小的调整,甚至应当原样保留它。此时,普通的层叠结构仍然需要依靠若干带参数的线性变换和非线性激活共同近似恒等映射。随着网络层数增加,多个网络层需要相互配合才能保持已有表示,参数优化的难度也会随之增加。因此,较深的模型即使具有更强的表示能力,其训练误差也可能比相应的浅层模型更高。

Definition 16.55. 设\(F_\theta:\mathbb{R}^d\to\mathbb{R}^d\)为一组网络层构成的映射,将其输出与输入\(x\)相加,得到:

\[\begin{equation*} y=x+F_\theta(x) \end{equation*}\]

称从输入\(x\)直接到达相加位置的路径为残差连接(residual connection),称\(F_\theta\)为残差映射。

上述结构将需要学习的完整映射写成恒等映射与残差映射之和,其基本结构如下图所示。

TikZ 数学图示
图 16.25 残差连接的基本结构

在这种参数化方式下,完整映射\(H_\theta\)可以表示为:

\[\begin{equation*} H_\theta(x)=x+F_\theta(x) \end{equation*}\]

上述结构将完整映射\(H_\theta\)表示为恒等映射与残差映射之和,即:

\[\begin{equation*} H_\theta(x)=x+F_\theta(x) \end{equation*}\]

因此,\(F_\theta\)所学习的是目标表示相对于输入\(x\)的变化:

\[\begin{equation*} F_\theta(x)=H_\theta(x)-x \end{equation*}\]

当理想映射接近恒等映射时,\(F_\theta\)只需要学习较小的修正;特别地,当已有表示无需改变时,只需使:

\[\begin{equation*} F_\theta(x)\approx\mathbf{0} \end{equation*}\]

便有:

\[\begin{equation*} y=x+F_\theta(x)\approx x \end{equation*}\]

因此,恒等映射已经直接包含在残差结构中,而不需要再由若干带参数的网络层通过训练近似得到。

这一性质降低了增加网络深度所带来的风险。若新增的残差模块不能进一步改善已有表示,它可以通过使残差映射接近于零而近似退化为恒等映射,从而保留原有网络已经学习到的表示。换言之,加深后的残差网络能够较容易地包含较浅网络所实现的映射作为一种特殊情形,因此增加网络层数不再必然破坏已有表示,这有助于缓解深层网络中的退化问题。

note 16.42. 残差相加要求两条路径的输出具有相同的形状。若残差映射将输入从\(\mathbb{R}^d\)变换到\(\mathbb{R}^m\),就需要先利用映射\(P:\mathbb{R}^d\to\mathbb{R}^m\)调整输入路径,随后再进行相加:

\[\begin{equation*} y=P(x)+F_\theta(x) \end{equation*}\]

残差连接不仅改变了网络对映射的参数化方式,也改变了梯度在网络中的传播方式。若残差映射\(F_\theta\)在\(x\)处可微,记其梯度为\(J_{F_\theta}(x)\)。对于:

\[\begin{equation*} y=x+F_\theta(x) \end{equation*}\]

有:

\[\begin{equation*} \frac{\partial y}{\partial x}=I_d+J_{F_\theta}(x) \end{equation*}\]

因此,对于依赖于输出\(y\)的损失函数\(L\),根据链式法则可得:

\[\begin{equation*} \nabla_xL=\left[I_d+J_{F_\theta}(x)\right]^{\top}\nabla_yL=\nabla_yL+J_{F_\theta}(x)^{\top}\nabla_yL \end{equation*}\]

其中,第一项\(\nabla_yL\)来自残差连接所提供的恒等路径,第二项\(J_{F_\theta}(x)^{\top}\nabla_yL\)则来自残差映射。对于普通的层叠网络,梯度向前传播时需要连续经过各网络层的梯度;当这些变换在相应方向上反复缩小梯度时,梯度的大小会随着传播距离的增加而不断衰减,从而产生梯度消失。残差连接引入恒等路径后,传回输入端的梯度中显式包含不经过残差映射内部各网络层的分量\(\nabla_yL\),因此梯度不再完全依赖残差分支中的连续变换进行传播。随着网络不断加深,这种直接的梯度传播路径能够减弱连续梯度乘积对梯度大小的影响,从而缓解梯度在向较早网络层传播过程中逐渐衰减的问题,并改善深层网络的优化。

16.9.4.4 随机失活

普通神经网络在每次训练时通常使用同一组神经元,因此一个神经元在学习特征时可以始终依赖其他神经元所提供的信息。随着训练进行,不同神经元可能逐渐形成固定的协作方式:单个神经元不必独立提取具有较强预测能力的特征,而只需要在其他若干神经元同时存在时提供其中一部分信息。这种现象称为神经元之间的共适应。当模型具有较多参数时,网络可能进一步利用多个神经元之间复杂而高度特化的组合来拟合训练数据中的偶然关联。这样的组合虽然能够降低训练误差,却未必反映总体数据中稳定存在的规律,因此可能导致模型在新样本上的预测性能下降。为了减弱模型对固定特征组合的依赖,可以在训练过程中随机屏蔽部分神经元,使模型不能假定某个特征在每次参数更新时都一定存在。

Definition 16.56. 设某一隐藏层对一个样本产生的输出为:

\[\begin{equation*} h=(h_1,h_2,\dots,h_d)^{\top}\in\mathbb{R}^d \end{equation*}\]

给定保留概率\(q\in(0,1]\),每次在训练阶段计算该层输出时,生成相互独立且与\(h\)无关的随机变量:

\[\begin{equation*} m_j\sim\operatorname{Bernoulli}(q),\quad j=1,2,\dots,d \end{equation*}\]

并将传递给下一层的输出定义为:

\[\begin{equation*} \tilde{h}_j=\frac{m_j}{q}h_j,\quad j=1,2,\dots,d \end{equation*}\]

称上述变换为随机失活(dropout)。

当\(m_j=0\)时,第\(j\)个神经元的输出被置为\(0\),该神经元在当前计算中不再向后续网络层传递信息;当\(m_j=1\)时,该神经元被保留,其输出被放大为\(h_j/q\)。因此,每次计算所保留的神经元组合都可能不同,同一个输入在训练阶段也可能产生不同的中间表示。

保留神经元时除以\(q\),是为了补偿随机失活造成的输出尺度下降。由于\(m_j\)与\(h_j\)相互独立且\(\operatorname{E}(m_j)=q\),所以在给定\(h_j\)时有:

\[\begin{equation*} \operatorname{E}(\tilde{h}_j\mid h_j)=\frac{h_j}{q}\operatorname{E}(m_j)=h_j \end{equation*}\]

这表明随机失活虽然会改变某一次计算得到的具体输出,但不会改变单个神经元输出的条件期望。因子\(1/q\)的作用不是增强被保留神经元的重要性,而是使训练阶段输出的平均数值尺度与未进行随机失活时保持一致。与此同时,该随机输出的条件方差为:

\[\begin{equation*} \operatorname{Var}(\tilde{h}_j\mid h_j)=\frac{1-q}{q}h_j^2 \end{equation*}\]

因此,\(q\)越小,随机失活向该神经元输出中引入的随机波动通常越大。

随机失活使网络在每次训练时只能使用部分中间特征。若模型的预测过度依赖某几个神经元构成的固定组合,一旦其中某个神经元被屏蔽,预测误差就会增大。为了在不同随机屏蔽结果下都能给出较好的预测,模型需要把有用信息分散到更多特征中,并学习在部分特征暂时缺失时仍然有效的表示。随机失活由此向训练过程引入扰动,限制模型对训练数据中特定特征组合的精细拟合,从而起到正则化作用。它并没有删除网络参数,预测阶段模型可以表示的函数形式也没有改变,而是通过改变训练时使用这些参数的方式来降低过拟合风险。

保留概率\(q\)控制随机失活的强度。当\(q\)接近\(1\)时,大部分神经元始终参与训练,正则化作用较弱;当\(q\)过小时,模型在每次训练中能够使用的信息过少,梯度的随机波动也会增大,从而导致训练速度下降甚至产生欠拟合。随机失活通常用于容易产生过拟合的隐藏层;若模型本身已经欠拟合,继续增加失活概率只会进一步削弱模型的学习能力。

note 16.43. 随机失活在训练阶段和预测阶段采用不同的计算方式。训练阶段需要反复生成新的随机变量\(m_j\)并使用\(\tilde{h}_j\);预测阶段则关闭随机屏蔽,直接使用原输出\(h_j\)。由于训练阶段已经使用因子\(1/q\)保持输出的条件期望,预测阶段无需再对\(h_j\)进行额外缩放。

16.10 不平衡样本集问题

Definition 16.57. 在分类问题中,若观测数据集中不同类别的样本数量相差悬殊,则称该数据集存在不平衡样本集(class-imbalanced dataset)问题。此时,样本数量较多的类别称为多数类(majority class),样本数量较少的类别称为少数类(minority class)。

不平衡样本集问题的根源在于,模型训练通常以最小化经验风险为目标,而经验风险是各观测样本损失的平均。当不同类别的样本数量相差悬殊时,多数类样本对经验风险的贡献会占据主导地位,因此,即使模型在少数类上的预测表现很差,只要能够较好地拟合多数类,仍可能获得较小的总体经验风险。换言之,经验风险最小化本身并不会保证模型对各类别具有相近的识别能力,而可能使训练过程系统性地偏向多数类。

例如,在一个二分类任务中,观测数据集包含\(100\)个样本,其中\(99\)个属于多数类\(0\),仅有\(1\)个属于少数类\(1\)。若模型对任意样本的输出都为\(0\),则在\(0\)-\(1\)损失下(预测正确损失函数值为\(0\),预测错误损失函数值为\(1\)),其经验风险仅为\(0.01\)。因此,从总体经验风险的角度看,该模型已经取得了看似良好的预测结果。然而,它完全无法识别任何少数类样本。

类别不平衡现象广泛存在于实际分类任务中,并且在许多应用场景下,样本数量较少的类别恰恰对应我们真正关心的事件。例如,疾病患者、欺诈交易和设备故障通常只占全部观测中的很小比例,但识别这些少数类样本往往正是建模任务的核心目标。因此,一个仅通过良好预测多数类而获得较低经验风险的模型,即使在整体意义上表现良好,也可能完全失去实际应用价值,因为它没有学习到任务中最重要的少数类模式。

在接下来的讨论中,我们将主要涉及二分类任务,并且默认将少数类记为正类。

16.10.1 评价指标

类别不平衡不仅会影响模型的训练过程,也会影响模型性能的评价。当不同类别的样本数量相差悬殊时,一些在一般分类任务中常用的评价指标可能无法准确反映模型对少数类的识别能力。因此,在讨论不平衡样本集的评价问题时,一个自然的思路是重新考察此前介绍的各类评价指标,分析它们在类别不平衡情形下是否仍然具有合理的解释。对于容易受到类别比例影响的指标,则需要考虑相应的修正形式或采用新的评价指标。

在类别不平衡的数据集中,准确率显然容易受到多数类样本的主导。为避免不同类别的样本数量直接决定评价结果,可以分别计算模型在各类别上的识别准确率,再对其进行等权平均,从而减弱类别数量不平衡对评价结果的影响。。

Definition 16.58. 对于二分类任务,称:

\[\begin{equation*} \operatorname{BalancedAccuracy}=\frac{1}{2}\left(\frac{\operatorname{TP}}{\operatorname{TP}+\operatorname{FN}}+\frac{\operatorname{TN}}{\operatorname{TN}+\operatorname{FP}}\right) \end{equation*}\]

为模型的平衡准确率(balanced accuracy)。

由于默认将少数类视为正类,因此精确率、召回率、\(\operatorname{F1}\)分数、\(\operatorname{F}_{\beta}\)分数以及\(\operatorname{AUPRC}\)都能够直接反映模型在少数类上的预测表现。精确率刻画模型预测为少数类的样本中有多少真正属于少数类,召回率刻画实际少数类样本中有多少被模型成功识别,\(\operatorname{F1}\)分数和\(\operatorname{F}_{\beta}\)分数则综合衡量精确率与召回率,而\(\operatorname{AUPRC}\)进一步描述不同分类阈值下精确率与召回率之间的整体权衡。这些指标更加关注正类的识别效果,因此通常适用于类别不平衡问题。

16.10.2 代价敏感学习

对于不平衡分类问题,一种直接的处理思路是重新调整不同类别样本在损失函数中的权重,以抵消类别样本数量差异对经验风险的影响,使各类别的预测误差能够在训练目标中得到更为均衡的体现。这类通过赋予不同类型样本的预测错误以不同代价,从而改变模型优化目标的方法称为代价敏感学习(cost-sensitive learning)。

具体而言,设分类问题共有\(K\)个类别,第\(k\)类样本数量为\(n_k\),为该类别赋予权重\(w_k>0\),则代价敏感学习一般的经验风险被定义为:

\[\begin{equation*} \hat{R}_w(\theta)\coloneq\sum_{i=1}^{n}w_{y_i}L[y_i,f_\theta(x_i)] \end{equation*}\]

一种常用的权重设定方式是令类别权重与该类别的样本数量成反比,即:

\[\begin{equation*} w_k\propto\frac{1}{n_k} \end{equation*}\]

此时第\(k\)类样本在加权经验风险中的总权重满足:

\[\begin{equation*} n_kw_k\propto1 \end{equation*}\]

因此,不同类别虽然具有不同的样本数量和单样本权重,但其在总体损失中具有相同的总权重,从而消除了类别频率本身对经验风险贡献所造成的失衡。

16.10.3 样本平衡类方法

与代价敏感学习直接调整不同类别样本在损失函数中的贡献不同,处理不平衡样本集的另一类基本思路是从训练数据本身出发,通过改变不同类别样本在训练过程中的出现频率,使模型所观察到的经验类别分布更加均衡。这类方法统称为样本平衡类方法。其核心思想是,在不改变原始学习任务和损失函数形式的前提下,通过对多数类样本进行删减、对少数类样本进行重复或合成,重新构造用于模型训练的数据集,从而减弱多数类样本因数量占优而对经验风险及参数更新产生的主导作用。

16.10.3.1 欠采样与过采样

设分类问题共有\(K\)个类别,第\(k\)类样本数量为\(n_k\)。样本平衡最直接的做法是通过重新采样改变不同类别样本参与模型训练的频率,从而调整训练数据中的经验类别分布。根据调整方式的不同,可以分为欠采样(under-sampling)与过采样(over-sampling)。

欠采样通过减少多数类样本的数量来缓解类别之间的样本规模差异。最简单的随机欠采样方法从多数类样本中随机选取部分观测用于训练。对于二分类问题,设多数类和少数类的样本数量分别为\(n_{\mathrm{maj}}\)和\(n_{\mathrm{min}}\),可以从多数类中抽取\(n_{\mathrm{maj}}'<n_{\mathrm{maj}}\)个样本,使\(n_{\mathrm{maj}}'\)与\(n_{\mathrm{min}}\)之间的差异缩小。

欠采样能够减少多数类对训练目标的主导作用,同时直接降低训练数据规模,因此在多数类样本数量极大时还具有降低存储与计算成本的优势。其主要缺点在于部分已经观测到的样本会被舍弃。如果多数类具有复杂的内部结构,或者被删除的样本包含重要的决策边界信息,则过强的欠采样可能造成有效信息损失,并降低模型的泛化能力。

与之相对,过采样不删除原有观测,而是通过提高少数类样本在训练过程中的出现频率来改善类别不平衡。最简单的随机过采样方法从少数类样本中进行有放回抽样,使其有效样本数量增加。例如,可以将少数类从\(n_{\mathrm{min}}\)个样本重复采样至\(n_{\mathrm{min}}'\)个,使其与多数类的样本数量更加接近。

随机过采样避免了欠采样所带来的信息丢失,但简单复制已有样本并不会产生新的观测信息。少数类中的同一观测可能被模型反复使用,从而增加对有限少数类样本过拟合的风险;与此同时,重复采样还会增大训练数据规模和计算开销。

16.10.3.2 SMOTE

随机过采样通过重复已有少数类样本提高其在训练过程中的出现频率,但重复观测本身并不会引入新的特征信息。为缓解这一问题,合成少数类过采样技术(synthetic minority over-sampling technique, SMOTE)不再直接复制少数类样本,而是利用少数类样本在特征空间中的局部邻域结构生成新的合成样本。

设\(x_i\)为某个少数类样本,并在少数类样本中寻找其\(k\)个近邻。从这些近邻中选取一个样本\(x_j\),再生成随机变量:

\[\begin{equation*} \lambda\sim\operatorname{Uniform}(0,1) \end{equation*}\]

则SMOTE生成的新样本为:

\[\begin{equation*} x_{\mathrm{new}}=x_i+\lambda(x_j-x_i) \end{equation*}\]

因此,\(x_{\mathrm{new}}\)位于\(x_i\)与\(x_j\)之间的线段上,并被赋予与\(x_i\)相同的类别标签。通过对不同的少数类样本及其近邻重复这一过程,可以逐步扩充少数类训练样本。

SMOTE隐含的基本假设是:相邻少数类样本之间的区域仍然属于少数类,因此可以通过局部线性插值生成新的少数类样本。相较于随机过采样直接复制已有观测,这种方式能够扩展少数类样本在特征空间中的覆盖范围。这一假设也是SMOTE的主要局限。当少数类靠近决策边界或少数类本身具有复杂的非线性结构时,两个少数类样本之间的插值区域未必仍然属于少数类,从而可能生成不合理的合成样本并干扰决策边界。

16.10.4 Balanced Bagging

Bagging通过对训练数据进行重复采样并训练多个基学习器,再将各基学习器的预测结果进行集成,利用基学习器之间的异质性去提高模型的预测精度(性质 16.3.3(2))。对于不平衡分类问题,可以进一步将类别平衡操作嵌入Bagging的采样过程中,使每个基学习器均在相对平衡的数据集上训练。这类方法通常称为Balanced Bagging。

设二分类训练集为:

\[\begin{equation*} \mathcal{D}=\mathcal{D}_{+}\cup\mathcal{D}_{-} \end{equation*}\]

其中\(n_{+}\ll n_{-}\)。对于第\(b\)个基学习器,Balanced Bagging保留少数类样本,并从多数类中随机抽取与少数类数量相近的样本,构造平衡训练集\(\mathcal{D}^{(b)}\)并训练\(f^{(b)}\),其最终模型为\(f^{(1)},f^{(2)},\dots,f^{(B)}\)经过多数投票得到的结果。

这种方法通常采用多数类欠采样,而不是少数类过采样。原因在于单次欠采样虽然会丢失多数类信息,但Bagging中的不同基学习器可以使用不同的多数类子集,从而在整个集成模型中逐步利用更多多数类观测,因此Bagging恰好能够缓解欠采样最主要的信息损失问题。相比之下,随机过采样主要是在不同训练集中重复有限的少数类样本,并不会提供新的观测信息,还会增大每个基学习器的训练规模,并可能使不同基学习器反复依赖相同的少数类样本,提高了基学习器之间的相关性,从而无法达到理想的预测精度。

16.10.5 困难负样本挖掘

Definition 16.59. 设二分类训练集为\(\mathcal{D}=\mathcal{D}_{+}\cup\mathcal{D}_{-}\),其中\(\mathcal{D}_{+}\)与\(\mathcal{D}_{-}\)分别为正类和负类样本集。对于给定模型\(f_{\theta}\),定义负类样本\((x_i,0)\in\mathcal{D}_{-}\)的分类困难程度为:

\[\begin{equation*} d_i(\theta)\coloneq L[0,f_{\theta}(x_i)] \end{equation*}\]

给定阈值\(\tau\),若\(d_i(\theta)>\tau\),则称\((x_i,0)\)为模型\(f_{\theta}\)的困难负样本。从多数类中选择困难负样本并用于后续训练的方法称为困难负样本挖掘(hard negative mining)。

困难负样本挖掘的核心思想是:多数类样本虽然数量庞大,但并非所有样本对模型训练都具有同等价值。大量能够被当前模型轻易正确分类的负类样本对决策边界的进一步修正作用有限,而那些容易被误判、损失较大的负类样本更能反映模型当前尚未解决的分类困难。因此,与随机欠采样等概率地丢弃多数类样本不同,困难负样本挖掘利用当前模型主动筛选更有训练价值的负类样本,使模型将有限的训练能力集中于难以区分的区域。

由于样本的困难程度由当前模型决定,困难负样本并不是固定不变的。随着模型参数更新,原有困难负样本的损失可能降低,而其他负类样本可能成为新的困难样本。因此,困难负样本挖掘通常与模型训练交替进行,即利用当前模型筛选困难负样本,再使用这些样本更新模型,并重复这一过程,直至满足停止准则。

16.10.5.1 机器学习中的困难负样本挖掘

Method 16.3. 设第\(t\)轮训练所使用的负类样本集为\(\mathcal{S}_{-}^{(t)}\subseteq\mathcal{D}_{-}\)。对于非神经网络的传统机器学习模型,困难负样本挖掘的一般过程为:

  1. 从\(\mathcal{D}_{-}\)中选取部分负类样本,构造初始负类训练集\(\mathcal{S}_{-}^{(0)}\);

  2. 对\(t=0,1,2,\ldots\),使用\(\mathcal{D}_{+}\cup\mathcal{S}_{-}^{(t)}\)训练模型\(f_{\theta^{(t)}}\);

  3. 使用\(f_{\theta^{(t)}}\)评估负类样本的困难程度\(d_i(\theta^{(t)})\),并据此确定当前模型下需要重点用于后续训练的困难负样本;

  4. 根据当前负类训练集以及新识别出的困难负样本构造下一轮负类训练集\(\mathcal{S}_{-}^{(t+1)}\),随后进入下一轮训练。

当验证集上的评价指标不再明显改善,或达到预设的最大迭代轮数时停止。

设\(n_{+}=|\mathcal{D}_{+}|\)且\(s_t=|\mathcal{S}_{-}^{(t)}|\)。若还希望正类与选中负类对训练目标具有相同的总贡献,可以在第\(t\)轮使用如下经验风险:

\[\begin{equation*} \hat{R}_{\mathrm{HNM}}^{(t)}(\theta)\coloneq\frac{1}{n_{+}}\sum_{(x_i,1)\in\mathcal{D}_{+}}L[1,f_{\theta}(x_i)] +\frac{1}{s_t}\sum_{(x_i,0)\in\mathcal{S}_{-}^{(t)}}L[0,f_{\theta}(x_i)] \end{equation*}\]

负类训练集\(\mathcal{S}_{-}^{(t)}\)的更新方式并不唯一。最直接的做法是在已有负类训练集上不断加入新识别出的困难负样本,使训练集随迭代逐渐扩增;也可以在每一轮根据当前模型重新选择负类样本,从而完全重构\(\mathcal{S}_{-}^{(t+1)}\)。此外,还可以采用介于两者之间的策略,例如保留部分已有困难负样本,同时移除已经能够被模型轻易识别的样本并补充新的困难负样本。具体采用何种更新方式取决于具体场景。

16.10.5.2 深度学习中的困难负样本挖掘

设模型给出正类概率\(q_{\theta}(x_i)\in(0,1)\),记:

\[\begin{equation*} p_{ti}= \begin{cases} q_{\theta}(x_i), &y_i=1 \\ 1-q_{\theta}(x_i), &y_i=0 \end{cases} \quad \alpha_{ti}= \begin{cases} \alpha,& y_i=1, \\ 1-\alpha,& y_i=0 \end{cases} \end{equation*}\]

其中\(\alpha\in(0,1)\)为类别权重参数。

Definition 16.60. 设聚焦参数\(\gamma\geqslant0\)。称:

\[\begin{equation*} L_{\mathrm{FL}}[y_i,q_{\theta}(x_i)] \coloneq-\alpha_{ti}(1-p_{ti})^{\gamma}\ln p_{ti} \end{equation*}\]

为样本\((x_i,y_i)\)的焦点损失(focal loss)。

Focal loss在交叉熵前加入调节因子\((1-p_{ti})^{\gamma}\)。当样本容易分类时\(p_{ti}\)接近\(1\),其损失被显著抑制;当样本难以分类时\(p_{ti}\)较小,其损失得到保留。因此,Focal loss无需显式筛除容易样本,即可使训练更加关注困难样本。

16.10.5.3 困难负样本挖掘的缺陷

困难负样本挖掘依赖当前模型对样本困难程度的判断,因此其效果容易受到初始模型和迭代过程的影响。如果模型早期的决策边界存在较大偏差,所选出的困难负样本也可能缺乏代表性,并进一步影响后续训练。此外,高损失样本并不一定是真正有信息的困难样本。错误标签、异常观测以及类别重叠区域中的样本同样可能产生较大损失,从而被反复选中并获得过高的训练关注。与此同时,过度强调困难负样本会削弱模型对多数类整体分布的利用,若负样本选择过于集中,还可能降低模型对其他多数类区域的泛化能力。

16.11 超参数优化

在一次具体的模型训练开始之前,往往需要预先确定一组控制模型结构或训练过程的参数,例如决策树的最大深度、\(k\)近邻算法中的近邻个数以及神经网络的学习率。在这些参数固定后,训练算法才利用训练数据估计模型中的可学习参数\(\theta\)。这样的需要预先设定的参数被称之为超参数(hyperparameter)。

因为不同的超参数取值对应不同的候选模型结构或训练方案,所以超参数的选择本身也是模型选择的一部分。实际建模时一般会预先规定允许考察的超参数配置集合,并在其中寻找使模型表现最优的配置。记一组超参数配置为\(u\in\mathcal{U}\),其中\(\mathcal{U}\)被称为超参数搜索空间。对于每个\(u\in\mathcal{U}\),记在该配置下通过训练得到相应的模型\(f_{\hat{\theta}(u)}\),因此整个候选模型集合可以表示为:

\[\begin{equation*} \mathcal{F}(\mathcal{U})=\{f_{\hat{\theta}(u)}:u\in\mathcal{U}\} \end{equation*}\]

超参数优化就是依据独立于参数拟合过程的评价结果,从这一候选集合中选择表现最优的模型。

固定训练集\(\mathcal{D}_{\mathrm{train}}\)、验证集\(\mathcal{D}_{\mathrm{val}}\)与训练过程中的随机数种子。若以验证集上的经验风险作为评价指标,则超参数优化的目标函数可以写为:

\[\begin{equation*} F(u)=\frac{1}{|\mathcal{D}_{\mathrm{val}}|}\sum_{(x,y)\in\mathcal{D}_{\mathrm{val}}}L[y,f_{\hat{\theta}(u)}(x)] \end{equation*}\]

相应地,超参数优化问题为:

\[\begin{equation*} u^*\in\underset{u\in\mathcal{U}}{\arg\min}\;F(u) \end{equation*}\]

最直接的优化方式包括网格搜索(grid search)与随机搜索(random search):前者在预先离散化的超参数网格上逐一评价所有候选配置,直至整个网格被遍历;后者则按照给定的采样分布从搜索空间中随机抽取配置,并在达到预先设定的搜索预算后停止,例如完成固定次数的目标函数评价,或达到规定的时间与计算资源上限。

因为每次计算\(F(u)\)通常都需要重新完成一次模型训练与验证,所以随着超参数维数和候选取值数量的增加,可考察的超参数配置数会迅速增长,使穷举式搜索的计算成本变得难以接受。因此,超参数优化的核心问题不仅是找到性能较好的配置,还在于如何以尽可能少的目标函数评价次数高效地探索搜索空间,这就是后续各种超参数搜索方法的主要出发点。

16.11.1 树结构Parzen估计

设\(f,g\)分别是从概率空间\((X,\mathscr{F},P)\)到\((\mathbb{R},\mathcal{B}(\mathbb{R}))\)和\((\mathcal{U},\mathscr{A})\)上的可测映射,其中\(f\)用于刻画每次试验所对应的目标函数取值,\(g\)用于刻画该试验所采用的超参数配置。

设\(\nu\)是\((\mathbb{R},\mathcal{B}(\mathbb{R}))\)上的\(\sigma\)有限测度,\(\mu\)是\((\mathcal{U},\mathscr{A})\)上的\(\sigma\)有限测度,\(P(f,g)^{-1}\ll\nu\times\mu\)。根据定理 5.33和定理 5.38,记有限且非负的:

\[\begin{gather*} p_{f,g}=\frac{\mathop{}\!\mathrm{d}P(f,g)^{-1}}{\mathop{}\!\mathrm{d}(\nu\times\mu)},\quad p_f=\frac{\mathop{}\!\mathrm{d}Pf^{-1}}{\mathop{}\!\mathrm{d}\nu},\quad p_g=\frac{\mathop{}\!\mathrm{d}Pg^{-1}}{\mathop{}\!\mathrm{d}\mu} \\ p_f(y)=\int_{\mathcal{U}}p_{f,g}(y,u)\mathop{}\!\mathrm{d}\mu\;\text{a.e.于$(\mathbb{R},\mathcal{B}(\mathbb{R}),\nu)$} \\ p_g(u)=\int_{\mathbb{R}}p_{f,g}(y,u)\mathop{}\!\mathrm{d}\nu\;\text{a.e.于$(\mathcal{U},\mathscr{A},\mu)$} \end{gather*}\]

由性质 6.3.6(1)及性质 6.3.5(2),\(g\)关于\(f\)的条件概率核\(K_{g\mid f}\)和相对于\(\mu\)的条件概率密度\(p_{g\mid f}\)存在,且:

\[\begin{gather*} p_{g\mid f}(u\mid y)=\frac{p_{f,g}(y,u)}{p_f(y)},\quad 0<p_f(y)<+\infty \\ K_{g\mid f}(y,A)=\int_Ap_{g\mid f}(u\mid y)\mathop{}\!\mathrm{d}\mu,\quad\int_{\mathcal{U}}p_{g\mid f}(u\mid y)\mathop{}\!\mathrm{d}\mu=1 \end{gather*}\]

由于目标函数取值越小表示模型表现越好,我们选取阈值\(y^*\in\mathbb{R}\),记:

\[\begin{equation*} \gamma=P(f<y^*)=\int_{(-\infty,y^*)}p_f(y)\mathop{}\!\mathrm{d}\nu(y),\quad 0<\gamma<1 \end{equation*}\]

其中积分表示来自引理 5.10。由性质 5.2.1(2)可得\(P(f\geqslant y^*)=1-\gamma\)。

TPE进一步对条件概率核作如下简化:假设存在\((\mathcal{U},\mathscr{A})\)上的两个概率测度\(Q_l,Q_h\),使得在同一组内,配置的条件分布不再随具体评价值变化,即在一个共同的\(Pf^{-1}\)零测集之外,对任意\(A\in\mathscr{A}\)有:

\[\begin{equation*} K_{g\mid f}(y,A)= \begin{cases} Q_l(A),&y<y^* \\ Q_h(A),&y\geqslant y^* \end{cases} \end{equation*}\]

这是TPE对代理模型施加的分段条件分布假设。仅仅按照阈值将试验划分为两组,并不能推出同一组内所有评价值对应的条件分布都相同。

下面说明\(Q_l,Q_h\)具有关于\(\mu\)的概率密度。由于两组都具有正的\(Pf^{-1}\)概率,可以分别在两组中选取不属于上述例外集的\(y_l,y_h\)。于是对任意\(A\in\mathscr{A}\)有:

\[\begin{equation*} Q_l(A)=\int_Ap_{g\mid f}(u\mid y_l)\mathop{}\!\mathrm{d}\mu(u),\quad Q_h(A)=\int_Ap_{g\mid f}(u\mid y_h)\mathop{}\!\mathrm{d}\mu(u) \end{equation*}\]

若\(\mu(A)=0\),由性质 5.4.2(3)可得\(Q_l(A)=Q_h(A)=0\),所以\(Q_l\ll\mu\)且\(Q_h\ll\mu\)。由定理 5.33,记:

\[\begin{equation*} l=\frac{\mathop{}\!\mathrm{d}Q_l}{\mathop{}\!\mathrm{d}\mu},\quad h=\frac{\mathop{}\!\mathrm{d}Q_h}{\mathop{}\!\mathrm{d}\mu} \end{equation*}\]

其中\(l\)取自lower,\(h\)取自higher。由于\(Q_l,Q_h\)是有限测度,可以取\(l,h\)为有限的非负可测版本;在可测零测集上修改版本不改变积分,依据为性质 5.4.3(8)。取\(A=\mathcal{U}\)可得:

\[\begin{equation*} \int_{\mathcal{U}}l(u)\mathop{}\!\mathrm{d}\mu(u)=Q_l(\mathcal{U})=1,\quad \int_{\mathcal{U}}h(u)\mathop{}\!\mathrm{d}\mu(u)=Q_h(\mathcal{U})=1 \end{equation*}\]

由Radon-Nikodym导数的唯一性,在每个满足分段假设的\(y\)处,\(p_{g\mid f}(\cdot\mid y)\)分别与\(l\)或者\(h\)在\(\mu\)几乎处处的意义下相等。因此,可以将条件密度及其对应的核统一选为如下版本:

\[\begin{gather*} p_{g\mid f}(u\mid y)= \begin{cases} l(u),&y<y^* \\ h(u),&y\geqslant y^* \end{cases} \\ K_{g\mid f}(y,A)= \begin{cases} \displaystyle\int_A l(u)\mathop{}\!\mathrm{d}\mu(u),&y<y^* \\ \displaystyle\int_A h(u)\mathop{}\!\mathrm{d}\mu(u),&y\geqslant y^* \end{cases} \end{gather*}\]

这里的核对每个\(y\)都是概率测度;对每个\(A\),它是关于两个Borel集合\((-\infty,y^*)\)和\([y^*,+\infty)\)的非负简单函数,因而可测。新版本只在一个\(Pf^{-1}\)零测集上改变原核,根据性质 5.4.3(8),条件概率核定义中的积分等式保持不变,所以它仍然是\(g\)关于\(f\)的条件概率核。

为了与两组试验的含义联系起来,任取\(A\in\mathscr{A}\)。由性质 6.3.3(1)、条件概率核的定义以及性质 5.4.3(6),有:

\[\begin{align*} P(g\in A\mid f<y^*) &=\frac{1}{\gamma}\int_{(-\infty,y^*)}K_{g\mid f}(y,A)\mathop{}\!\mathrm{d}Pf^{-1}(y) \\ &=\frac{Q_l(A)}{\gamma}Pf^{-1}((-\infty,y^*)) =Q_l(A)=\int_A l(u)\mathop{}\!\mathrm{d}\mu(u) \\ P(g\in A\mid f\geqslant y^*) &=\frac{1}{1-\gamma}\int_{[y^*,+\infty)}K_{g\mid f}(y,A)\mathop{}\!\mathrm{d}Pf^{-1}(y) \\ &=\frac{Q_h(A)}{1-\gamma}Pf^{-1}([y^*,+\infty)) =Q_h(A)=\int_A h(u)\mathop{}\!\mathrm{d}\mu(u) \end{align*}\]

因此,分段条件分布模型下的\(l,h\)也分别是两组试验中的配置密度,但现在还明确规定了给定每个评价值时配置的条件分布。

由性质 6.3.6(2),再利用性质 5.4.3(6)和定理 5.27,配置的边际密度满足:

\[\begin{align*} p_g(u)&=\int_{\mathbb{R}}p_{g\mid f}(u\mid y)p_f(y)\mathop{}\!\mathrm{d}\nu(y) \\ &=l(u)\int_{(-\infty,y^*)}p_f(y)\mathop{}\!\mathrm{d}\nu(y) +h(u)\int_{[y^*,+\infty)}p_f(y)\mathop{}\!\mathrm{d}\nu(y) \\ &=\gamma l(u)+(1-\gamma)h(u) \;\text{a.e.于$(\mathcal{U},\mathscr{A},\mu)$} \end{align*}\]

以下取右端作为\(p_g\)的版本。由性质 6.3.6(1),联合密度可以相应地取为:

\[\begin{equation*} p_{f,g}(y,u)=p_{g\mid f}(u\mid y)p_f(y) \end{equation*}\]

现在可以反过来得到给定配置时评价值的条件分布。由性质 6.3.6(3),在\(p_g(u)>0\)处选取:

\[\begin{equation*} p_{f\mid g}(y\mid u)=\frac{p_{g\mid f}(u\mid y)p_f(y)}{p_g(u)} =\begin{cases} \displaystyle\frac{l(u)p_f(y)}{\gamma l(u)+(1-\gamma)h(u)},&y<y^* \\ \displaystyle\frac{h(u)p_f(y)}{\gamma l(u)+(1-\gamma)h(u)},&y\geqslant y^* \end{cases} \end{equation*}\]

其归一化由下式保证:

\[\begin{equation*} \int_{\mathbb{R}}p_{f\mid g}(y\mid u)\mathop{}\!\mathrm{d}\nu(y) =\frac{\gamma l(u)+(1-\gamma)h(u)}{p_g(u)}=1 \end{equation*}\]

在\(p_g(u)=0\)处,令\(p_{f\mid g}(y\mid u)=p_f(y)\)以补全概率密度。根据性质 6.3.5(2),相应条件概率核为:

\[\begin{equation*} K_{f\mid g}(u,C)=\int_Cp_{f\mid g}(y\mid u)\mathop{}\!\mathrm{d}\nu(y),\quad C\in\mathcal{B}(\mathbb{R}) \end{equation*}\]

这里\(p_g(u)=0\)的集合是\(Pg^{-1}\)零测集;并且因为\(0<\gamma<1\)且\(l,h\)非负,在该集合上有\(l(u)=h(u)=0\)。由定理 5.37(2)、性质 5.4.2(2)和定理 5.27,上述\(K_{f\mid g}\)对固定的\(u\)是概率测度,对固定的\(C\)是可测函数。对任意\(A\in\mathscr{A}\)和\(C\in\mathcal{B}(\mathbb{R})\),由引理 5.10和定理 5.37(2)可得:

\[\begin{align*} \int_AK_{f\mid g}(u,C)\mathop{}\!\mathrm{d}Pg^{-1}(u) &=\int_A\left[\int_Cp_{f\mid g}(y\mid u)\mathop{}\!\mathrm{d}\nu(y)\right]p_g(u)\mathop{}\!\mathrm{d}\mu(u) \\ &=\int_{C\times A}p_{f,g}(y,u)\mathop{}\!\mathrm{d}(\nu\times\mu)(y,u) \\ &=P(\{f\in C\}\cap\{g\in A\}) \end{align*}\]

因此,该核满足条件概率核定义中的联合积分等式。由性质 6.3.4还有:

\[\begin{equation*} K_{f\mid g}[g(\omega),C]=P(f\in C\mid g)(\omega) \;\text{a.e.于$(X,\sigma(g),P)$} \end{equation*}\]

在\(p_g(u)>0\)处,利用这一条件概率核可得:

\[\begin{equation*} K_{f\mid g}(u,(-\infty,y^*)) =\frac{l(u)}{\gamma l(u)+(1-\gamma)h(u)}\int_{(-\infty,y^*)}p_f(y)\mathop{}\!\mathrm{d}\nu(y) =\frac{\gamma l(u)}{\gamma l(u)+(1-\gamma)h(u)} \end{equation*}\]

它描述代理模型中给定配置\(u\)后,评价值低于阈值的条件概率;\(K_{f\mid g}(u,\cdot)\)则给出了整个评价值的条件分布,可用于后续计算给定配置时的期望改进。

16.11.2 逐次减半算法

前面介绍的超参数优化方法主要关注如何利用已有实验结果,选择下一组更有希望的超参数,从而更有效地探索超参数空间。然而这些方法通常需要对每组候选超参数完整地优化一次模型,才能获得相应的评价指标。实际上,不同超参数组合的表现可能在训练早期就呈现出明显差异,此时对所有候选参数都进行完整训练可能造成大量计算资源的浪费。这便引出了超参数优化中的另一个重要问题:如何利用训练过程中的中间评价结果,合理分配计算资源,提前终止表现不佳的参数组合?解决这一问题的方法通常依赖于一个共同的基本假设:候选超参数在训练早期的表现能够在一定程度上预测其充分训练后的最终表现。

16.11.2.1 SHA

逐次减半算法(successive halving algorithm, SHA)主要考虑如何在给定候选超参数集合的情况下,将有限的计算资源集中于更有希望的参数组合。其核心思想是交替进行候选参数筛选与计算资源增加,具体流程如算法16.18所示。

Algorithm 16.18 Successive Halving Algorithm (SHA)

Input: Initial configurations \(S_0\), base budget \(r\), maximum budget \(R\), reduction factor \(\eta>1\), minimum early-stopping rate \(s\geqslant0\)

Output: Selected configuration \(\hat{\theta}\)

\(S\gets S_0\), \(b\gets r\eta^s\) (Skip pruning in the first \(s\) resource levels)

while True do

Fit the model with configuration \(\theta\) using budget \(b\) and compute \(\hat{R}(\theta;b)\)

end for

if \(b=R\) then

return \(\hat{\theta}\gets\underset{\theta\in S}{\arg\min}\;\hat{R}(\theta;R)\)

end if

\(k\gets\max\{1,\lfloor|S|/\eta\rfloor\}\)

\(S\gets\operatorname{TopK}\!\left(S,\hat{R}(\cdot;b),k\right)\)

\(b\gets\min\{\eta b,R\}\)

end while

16.11.2.2 ASHA

尽管SHA能够通过逐步淘汰表现较差的候选参数来减少计算开销,但每一轮必须等待所有候选参数完成当前阶段的模型训练,才能决定哪些参数进入下一轮。当不同参数组合的训练时间存在差异时,较早完成训练的计算任务需要等待其他任务,从而造成计算资源的闲置,降低并行计算效率。针对这一问题,异步逐次减半算法(asynchronous successive halving algorithm, ASHA)不再要求所有候选参数在同一阶段完成训练后统一筛选。

Algorithm 16.19 Asynchronous Successive Halving Algorithm (ASHA)

Input: Minimum budget \(r\), maximum budget \(R\), reduction factor \(\eta>1\), minimum early-stopping rate \(s\geqslant0\)

Output: Selected configuration \(\hat{\theta}\)

\(K\gets\lceil\log_\eta(R/r)\rceil-s\) (The highest rung index after skipping the first \(s\) resource levels)

Initialize rung records \(H_1, H_2, \dots, H_{K}\gets\varnothing\)

Initialize promoted sets \(P_1, P_2, \dots, P_{K-1}\gets\varnothing\)

while stopping criterion is not met do

\((\theta,k)\gets\operatorname{GetJob}\!\left(\right)\)

Asynchronously fit the model with configuration \(\theta\) using budget \(b_k=\min\{r\eta^{s+k},R\}\) and compute \(\hat{R}(\theta;b_k)\)

end for

\(H_k\gets H_k\cup\{(\theta,\hat{R}(\theta;b_k))\}\)

end for

end while

return \(\hat{\theta}\gets\underset{\theta\in H_K}{\arg\min}\;\hat{R}(\theta;R)\)

function GetJob()

for \(k=K-1,K-2,\dots,0\) do (Check higher rungs first for eligible promotions)

\(m\gets\lfloor|H_k|/\eta\rfloor\)

\(Q\gets\operatorname{TopK}\!\left(H_k,m\right)\setminus P_k\)

if \(Q\neq\varnothing\) then

Select \(\theta\in Q\)

\(P_k\gets P_k\cup\{\theta\}\)

return \((\theta,k+1)\)

end if

end for

return \((\operatorname{Sample}\!\left(\right),0)\) (Sample a new configuration if no promotion is available)

end function

16.11.2.3 Hyperband

SHA在给定初始候选集合与起始预算后,按照固定的比例淘汰候选配置,但初始候选数与起始预算之间仍然需要权衡:起始预算较小时,可以考察更多配置,却可能过早淘汰训练初期表现不佳的配置;起始预算较大时,早期评价更充分,却会减少能够考察的配置数。Hyperband通过运行多组具有不同初始候选数与起始预算的SHA,同时考察这两种资源分配策略之间的多种折中。每一组这样的SHA被称为一个分支(bracket)。

候选模型数最多可以向下缩减的次数为:

\[\begin{equation*} q_{\max}=\left\lfloor\log_\eta\frac{R}{r}\right\rfloor \end{equation*}\]

Hyperband据此构建\(q_{\max}+1\)个Bracket,分别记为\(q=0,1,\dots,q_{\max}\),\(q\)也约定为对应Bracket中预算递增的次数。我们希望每个Bracket最终都能够将保留下来的配置训练至最大预算\(R\),因此其起始预算应为:

\[\begin{equation*} r_q=R\eta^{-q} \end{equation*}\]

\(q\)越大,起始预算就越小,能够进行的筛选轮数也越多。

接下来需要确定每个Bracket的初始候选配置数量\(n_q\),我们希望不同Bracket的总资源消耗大致相同,为此Hyperband引入资源预算\(B\),通常取为:

\[\begin{equation*} B=(q_{\max}+1)R \end{equation*}\]

现在考虑第\(q\)个Bracket。假设初始有\(n_q\)个候选配置,由于候选数量减少的比例与预算增加的比例相同,各轮的资源消耗近似相等。因为第\(q\)个Bracket一共有\(q+1\)轮,其总资源消耗约为\((q+1)n_qr_q\)。我们希望满足:

\[\begin{equation*} (q+1)n_qr_q\approx B \end{equation*}\]

将\(r_q=R\eta^{-q}\)代入,便可以解出所需的初始候选数量:

\[\begin{equation*} n_q\approx\frac{B}{(q+1)R\eta^{-q}}=\frac{B}{R}\frac{\eta^q}{q+1} \end{equation*}\]

最终取:

\[\begin{equation*} n_q=\left\lceil\frac{B}{R}\frac{\eta^q}{q+1}\right\rceil \end{equation*}\]

在确定\(r_q\)与\(n_q\)后,每个Bracket内部执行SHA。对于第\(q\)个Bracket,第\(i\)轮的候选数量与单个配置的累计训练预算分别为:

\[\begin{equation*} \begin{aligned} n_{q,i}&=\left\lfloor n_q\eta^{-i}\right\rfloor\\ b_{q,i}&=r_q\eta^i=R\eta^{i-q} \end{aligned} \qquad i=0,1,\dots,q \end{equation*}\]

每轮根据相同训练预算下的验证集经验风险\(\hat{R}(\theta;b_{q,i})\),保留表现较好的配置进入下一轮,直至最后一轮达到最大预算\(R\)。完成所有Bracket训练的资源消耗约为:

\[\begin{equation*} (q_{\max}+1)B=(q_{\max}+1)^2R \end{equation*}\]

Algorithm 16.20 Hyperband

Input: Minimum budget \(r>0\), maximum budget \(R\), reduction factor \(\eta>1\)

Output: Selected configuration \(\hat{\theta}\)

\(q_{\max}\gets\lfloor\log_\eta(R/r)\rfloor\), \(B\gets(q_{\max}+1)R\), \(C\gets\varnothing\)

for \(q=q_{\max},q_{\max}-1,\dots,0\) do

\(n_q\gets\lceil(B/R)\eta^q/(q+1)\rceil\)

\(S\gets\operatorname{Sample}\!\left(n_q\right)\) (Sample a fresh configuration set for this bracket)

for \(i=0,1,\dots,q\) do

\(b\gets R\eta^{i-q}\)

Fit the model with configuration \(\theta\) up to budget \(b\) and compute \(\hat{R}(\theta;b)\)

end for

if \(i<q\) then

\(k\gets\lfloor n_q\eta^{-(i+1)}\rfloor\)

\(S\gets\operatorname{TopK}\!\left(S,\hat{R}(\cdot;b),k\right)\)

else

\(C\gets C\cup S\) (Record full-budget configurations)

end if

end for

end for

return \(\hat{\theta}\gets\underset{\theta\in C}{\arg\min}\;\hat{R}(\theta;R)\)

Hyperband与ASHA可以结合:以ASHA替换各Bracket内部的SHA可以减少并行任务之间的等待。

16.12 表示学习

16.13 强化学习


  1. 这其实是树模型的一个正则化手段,在下一小节我们会提到,此处可以暂时跳过回头再看。↩︎

  2. 从证明可以看出下面两条性质没有用到任何有关树模型的知识,因此它们适用于所有类似的集成方法。↩︎

  3. 从这里也可以看出约束叶节点输出值的一个原因:使得泰勒展开更加精确。↩︎

  4. 这里计算梯度指的是:分别计算本次参数更新所使用的各个样本的损失函数关于参数的梯度,并取这些梯度的算术平均。↩︎

COMPUTATIONAL NOTES

计算实验

实验01

分类树

Python · Jupyter

分类树

本实验使用 scikit-learn 的乳腺癌二分类数据考察决策树的复杂度控制。实验依次完成:

  1. 将恶性样本定义为正类,并划分训练集与独立测试集;
  2. 拟合不限制复杂度的分类树,观察训练集与测试集表现的差距;
  3. 利用训练集上的五折分层交叉验证选择 max_depth 与 min_samples_leaf;
  4. 在测试集上报告 Accuracy、Precision、Recall、F1 与 AUROC,并查看混淆矩阵和特征重要性。
import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib

configure_matplotlib()

import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import (
    ConfusionMatrixDisplay,
    RocCurveDisplay,
    accuracy_score,
    f1_score,
    precision_score,
    recall_score,
    roc_auc_score,
)
from sklearn.model_selection import (
    GridSearchCV,
    StratifiedKFold,
    cross_validate,
    train_test_split,
)
from sklearn.tree import DecisionTreeClassifier

plt.style.use("seaborn-v0_8-white")
RANDOM_STATE = 42
COLORS = {"unrestricted": "#E45756", "tuned": "#4C78A8"}

1. 预处理

原始数据将良性记为 1、恶性记为 0。为使疾病筛查中的 Recall 具有直接含义,这里重新编码为恶性  = 1、良性  = 0。先按类别比例划出 20% 的独立测试集,其余 80% 作为训练集。

cancer = load_breast_cancer()
X = pd.DataFrame(cancer.data, columns=cancer.feature_names)
y = pd.Series((cancer.target == 0).astype(int), name="malignant")

X_dev, X_test, y_dev, y_test = train_test_split(
    X,
    y,
    test_size=0.20,
    stratify=y,
    random_state=RANDOM_STATE,
)

data_summary = pd.Series(
    {
        "total samples": len(X),
        "features": X.shape[1],
        "train samples": len(X_dev),
        "test samples": len(X_test),
        "malignant rate": y.mean(),
    },
    name="value",
).to_frame()
class_counts = pd.DataFrame(
    {
        "train": y_dev.value_counts().sort_index(),
        "test": y_test.value_counts().sort_index(),
    }
).rename(index={0: "benign (0)", 1: "malignant (1)"})
display(data_summary.round(3))
display(class_counts)
value
total samples 569.000
features 30.000
train samples 455.000
test samples 114.000
malignant rate 0.373
train test
malignant
benign (0) 285 72
malignant (1) 170 42

2. 不限制树生长

令 max_depth=None、min_samples_leaf=1,使树一直生长到叶节点纯净或无法继续分裂,查看模型的拟合情况。

def classification_metrics(model, features, labels):
    prediction = model.predict(features)
    probability = model.predict_proba(features)[:, 1]
    return {
        "Accuracy": accuracy_score(labels, prediction),
        "Precision": precision_score(labels, prediction),
        "Recall": recall_score(labels, prediction),
        "F1": f1_score(labels, prediction),
        "AUROC": roc_auc_score(labels, probability),
    }

unrestricted = DecisionTreeClassifier(random_state=RANDOM_STATE).fit(X_dev, y_dev)
unrestricted_metrics = pd.DataFrame(
    {
        "train": classification_metrics(unrestricted, X_dev, y_dev),
        "test": classification_metrics(unrestricted, X_test, y_test),
    }
).T
unrestricted_structure = pd.Series(
    {
        "depth": unrestricted.get_depth(),
        "leaves": unrestricted.get_n_leaves(),
    },
    name="unrestricted tree",
).to_frame()
display(unrestricted_structure)
display(unrestricted_metrics.round(3))
unrestricted tree
depth 8
leaves 24
Accuracy Precision Recall F1 AUROC
train 1.00 1.000 1.000 1.000 1.000
test 0.93 0.905 0.905 0.905 0.925

3. 观察树深的影响

固定 min_samples_leaf=1,令 max_depth 从 1 增加到 12。每个候选深度都在训练集上进行相同的五折分层交叉验证。训练 AUROC 与交叉验证 AUROC 的分离可以显示模型复杂度增加后方差逐渐增大的过程。

classification_cv = StratifiedKFold(
    n_splits=5, shuffle=True, random_state=RANDOM_STATE
)
depth_rows = []
for depth in range(1, 13):
    scores = cross_validate(
        DecisionTreeClassifier(max_depth=depth, random_state=RANDOM_STATE),
        X_dev,
        y_dev,
        scoring="roc_auc",
        cv=classification_cv,
        return_train_score=True,
        n_jobs=1,
    )
    depth_rows.append(
        {
            "max_depth": depth,
            "training AUROC": scores["train_score"].mean(),
            "validation AUROC": scores["test_score"].mean(),
            "validation standard deviation": scores["test_score"].std(),
        }
    )
depth_results = pd.DataFrame(depth_rows).set_index("max_depth")

fig, ax = plt.subplots(figsize=(8.6, 5.2), constrained_layout=True)
ax.plot(depth_results.index, depth_results["training AUROC"], marker="o", label="training AUROC")
ax.plot(depth_results.index, depth_results["validation AUROC"], marker="o", label="cross-validation AUROC")
ax.fill_between(
    depth_results.index,
    depth_results["validation AUROC"] - depth_results["validation standard deviation"],
    depth_results["validation AUROC"] + depth_results["validation standard deviation"],
    alpha=0.16,
)
ax.set(
    title="Tree depth and classification performance",
    xlabel="Maximum depth",
    ylabel="AUROC",
    xticks=list(depth_results.index),
)
ax.legend()
plt.show()

实验 01「分类树」结果 1(图 1)

4. 调参

联合搜索上述两个参数的最优值,并以平均交叉验证 AUROC 最大作为选择准则,若平均交叉验证 AUROC 相同,则取复杂度最低的模型。

5. 在测试集上比较模型

调参结束后才使用测试集。分类指标采用阈值 0.5,AUROC 则直接使用叶节点中的恶性样本比例。混淆矩阵的行表示真实类别,列表示预测类别。

tuned_classifier = classification_grid.best_estimator_
classification_models = {
    "unrestricted": unrestricted,
    "tuned": tuned_classifier,
}
comparison_rows = []
for model_name, model in classification_models.items():
    for data_name, features, labels in [
        ("train", X_dev, y_dev),
        ("test", X_test, y_test),
    ]:
        comparison_rows.append(
            {
                "model": model_name,
                "data": data_name,
                "depth": model.get_depth(),
                "leaves": model.get_n_leaves(),
                **classification_metrics(model, features, labels),
            }
        )
classification_comparison = pd.DataFrame(comparison_rows).set_index(["model", "data"])
display(classification_comparison.round(3))

fig, axes = plt.subplots(1, 2, figsize=(10.5, 4.3), constrained_layout=True)
for ax, (model_name, model) in zip(axes, classification_models.items()):
    ConfusionMatrixDisplay.from_predictions(
        y_test,
        model.predict(X_test),
        display_labels=["Benign", "Malignant"],
        colorbar=False,
        cmap="Blues",
        ax=ax,
    )
    ax.set_title(model_name.capitalize())
plt.show()

fig, ax = plt.subplots(figsize=(7.2, 5.2), constrained_layout=True)
for model_name, model in classification_models.items():
    RocCurveDisplay.from_predictions(
        y_test,
        model.predict_proba(X_test)[:, 1],
        name=model_name,
        curve_kwargs={"color": COLORS[model_name]},
        ax=ax,
    )
ax.plot([0, 1], [0, 1], linestyle="--", color="#6B7280", linewidth=1)
ax.set_title("ROC curves on the independent test set")
plt.show()
depth leaves Accuracy Precision Recall F1 AUROC
model data
unrestricted train 8 24 1.000 1.000 1.000 1.000 1.000
test 8 24 0.930 0.905 0.905 0.905 0.925
tuned train 5 7 0.941 0.899 0.947 0.923 0.988
test 5 7 0.939 0.927 0.905 0.916 0.979

实验 01「分类树」结果 1(图 3)

实验 01「分类树」结果 1(图 4)

6. 基于不纯度下降的特征重要性

下面给出调参后分类树中得分最高的十个特征。

feature_importance = (
    pd.Series(tuned_classifier.feature_importances_, index=X.columns, name="importance")
    .sort_values(ascending=False)
    .head(10)
)
display(feature_importance.to_frame().round(3))

fig, ax = plt.subplots(figsize=(8.4, 5.2), constrained_layout=True)
feature_importance.sort_values().plot.barh(ax=ax, color="#4C78A8")
ax.set(
    title="Impurity-based feature importance of the tuned tree",
    xlabel="Normalized importance",
    ylabel="Feature",
)
plt.show()
importance
worst perimeter 0.872
worst concave points 0.111
area error 0.012
worst texture 0.004
worst radius 0.001
mean radius 0.000
concavity error 0.000
worst symmetry 0.000
worst concavity 0.000
worst compactness 0.000

实验 01「分类树」结果 1(图 5)

实验02

回归树

Python · Jupyter

回归树

本实验使用 scikit-learn 自带的糖尿病回归数据完成一次决策树回归分析。实验将样本划分为训练集、验证集与测试集:候选模型只在训练集上拟合,利用验证集选择 max_depth 与 min_samples_leaf,最后仅在测试集上评价选定模型。

import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib

configure_matplotlib()

import numpy as np
import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor

plt.style.use("seaborn-v0_8-whitegrid")
RANDOM_STATE = 42

1. 读取数据并划分样本

数据包含 442 位受试者的 10 个基线特征,连续响应是一年后的疾病进展指标。scikit-learn 已对特征作中心化与尺度处理。首先留出 40% 的样本,再将其等分为验证集与测试集,最终训练集、验证集和测试集分别约占 60%、20% 和 20%。

三份数据的职责彼此分离:训练集用于估计每棵候选树,验证集用于选择超参数,测试集在选定模型后才使用。

diabetes = load_diabetes(as_frame=True)
X = diabetes.data
y = diabetes.target.rename("disease progression")

X_train, X_remaining, y_train, y_remaining = train_test_split(
    X,
    y,
    test_size=0.40,
    random_state=RANDOM_STATE,
)
X_validation, X_test, y_validation, y_test = train_test_split(
    X_remaining,
    y_remaining,
    test_size=0.50,
    random_state=RANDOM_STATE,
)

split_summary = pd.DataFrame(
    {
        "samples": [len(X_train), len(X_validation), len(X_test)],
        "proportion": [len(X_train) / len(X), len(X_validation) / len(X), len(X_test) / len(X)],
        "purpose": ["fit candidate trees", "select hyperparameters", "final evaluation"],
    },
    index=["training", "validation", "test"],
)
data_summary = pd.Series(
    {
        "total samples": len(X),
        "features": X.shape[1],
        "minimum response": y.min(),
        "maximum response": y.max(),
    },
    name="value",
).to_frame()
display(data_summary)
display(split_summary.round({"proportion": 3}))
value
total samples 442.0
features 10.0
minimum response 25.0
maximum response 346.0
samples proportion purpose
training 265 0.600 fit candidate trees
validation 88 0.199 select hyperparameters
test 89 0.201 final evaluation

2. 利用验证集调参

考虑 max_depth 与 min_samples_leaf 的不同组合。对于每组参数,重新在训练集上拟合一棵回归树,然后在验证集上计算 MAE、RMSE 与 R2。本实验以验证 RMSE 最小作为选模准则。

max_depth=None 表示不直接限制树深;即使如此,min_samples_leaf 仍可能阻止树继续生长。

3. 在测试集上评价选定模型

选定参数后,保留此前只用训练集拟合的模型,不再利用验证集或测试集重新训练。现在首次在测试集上计算 MAE、RMSE 与 R2。预测值—真实值图中的点越接近对角线,说明预测误差越小;残差图用于查看误差是否存在明显的系统结构。

test_prediction = selected_model.predict(X_test)
test_residual = y_test.to_numpy() - test_prediction
test_results = pd.Series(
    {
        "selected max_depth": best_max_depth,
        "selected min_samples_leaf": best_min_samples_leaf,
        "actual depth": selected_model.get_depth(),
        "leaves": selected_model.get_n_leaves(),
        **regression_metrics(y_test, test_prediction),
    },
    name="test result",
).to_frame()
display(test_results.round(3))

plot_min = min(y_test.min(), test_prediction.min()) - 5
plot_max = max(y_test.max(), test_prediction.max()) + 5
fig, axes = plt.subplots(1, 2, figsize=(11.5, 4.7), constrained_layout=True)
axes[0].scatter(y_test, test_prediction, s=30, alpha=0.72, color="#4C78A8")
axes[0].plot(
    [plot_min, plot_max],
    [plot_min, plot_max],
    linestyle="--",
    linewidth=1.6,
    color="#111827",
)
axes[0].set(
    title="Observed and predicted responses on the test set",
    xlabel="Observed response",
    ylabel="Predicted response",
    xlim=(plot_min, plot_max),
    ylim=(plot_min, plot_max),
)
axes[1].scatter(test_prediction, test_residual, s=30, alpha=0.72, color="#F28E2B")
axes[1].axhline(0, linestyle="--", linewidth=1.6, color="#111827")
axes[1].set(
    title="Residuals on the test set",
    xlabel="Predicted response",
    ylabel="Observed minus predicted",
)
plt.show()
test result
selected max_depth 3.000
selected min_samples_leaf 30.000
actual depth 3.000
leaves 6.000
MAE 49.292
RMSE 62.859
R2 0.317

实验 02「回归树」结果 1(图 2)

观察

验证集选择 max_depth=3、min_samples_leaf=30,对应验证 RMSE 约为 53.218。选定模型的实际深度为 3,共有 6 个叶节点。

该模型在独立测试集上的 MAE 约为 49.292,RMSE 约为 62.859,R2 约为 0.317。测试集没有参与模型训练或参数选择,因此这些数值是本次随机划分下对最终预测表现的独立评价;更换随机划分后,具体数值可能发生变化。

实验03

决策树与集成树模型的性能比较

Python · Jupyter

决策树与集成树模型的性能比较

本实验在乳腺癌分类数据与糖尿病回归数据上比较决策树、随机森林、GBDT 与 XGBoost。两个任务都按照 6:2:2 划分训练集、验证集与测试集:每个候选模型只在训练集上拟合,分类任务以验证集 AUROC 选择参数,回归任务以验证集 RMSE 选择参数。

import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib

configure_matplotlib()

import numpy as np
import pandas as pd
from IPython.display import display
from sklearn.base import clone
from sklearn.datasets import load_breast_cancer, load_diabetes
from sklearn.ensemble import (
    GradientBoostingClassifier,
    GradientBoostingRegressor,
    RandomForestClassifier,
    RandomForestRegressor,
)
from sklearn.metrics import (
    ConfusionMatrixDisplay,
    RocCurveDisplay,
    accuracy_score,
    f1_score,
    mean_absolute_error,
    mean_squared_error,
    precision_score,
    r2_score,
    recall_score,
    roc_auc_score,
)
from sklearn.model_selection import (
    ParameterGrid,
    train_test_split,
)
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from xgboost import XGBClassifier, XGBRegressor

plt.style.use("seaborn-v0_8-whitegrid")
RANDOM_STATE = 42
MODEL_COLORS = {
    "Decision tree": "#4C78A8",
    "Random forest": "#59A14F",
    "GBDT": "#F28E2B",
    "XGBoost": "#E45756",
}


def parameter_text(parameters):
    return ", ".join(
        f"{name}={value!r}" for name, value in sorted(parameters.items())
    )

1. 调参规则

四个模型分别搜索 48、36、36 与 32 组参数。每个模型使用完全相同的数据划分和评价指标。

classification_parameter_grids = {
    "Decision tree": {
        "max_depth": [1, 2, 3, 4, 5, 6, 8, None],
        "min_samples_leaf": [1, 2, 5, 10, 20, 30],
    },
    "Random forest": {
        "n_estimators": [200, 500],
        "max_depth": [6, 12, None],
        "min_samples_leaf": [1, 3, 6],
        "max_features": ["sqrt", 0.5],
    },
    "GBDT": {
        "n_estimators": [100, 300],
        "learning_rate": [0.03, 0.10],
        "max_depth": [1, 2, 3],
        "min_samples_leaf": [5, 10, 20],
    },
    "XGBoost": {
        "n_estimators": [100, 300],
        "learning_rate": [0.03, 0.10],
        "max_depth": [2, 3],
        "min_child_weight": [1, 5],
        "reg_lambda": [1.0, 5.0],
    },
}

regression_parameter_grids = {
    "Decision tree": {
        "max_depth": [1, 2, 3, 4, 5, 6, 8, None],
        "min_samples_leaf": [1, 2, 5, 10, 20, 30],
    },
    "Random forest": {
        "n_estimators": [200, 500],
        "max_depth": [6, 12, None],
        "min_samples_leaf": [1, 3, 6],
        "max_features": [0.5, 1.0],
    },
    "GBDT": {
        "n_estimators": [100, 300],
        "learning_rate": [0.03, 0.10],
        "max_depth": [1, 2, 3],
        "min_samples_leaf": [5, 10, 20],
    },
    "XGBoost": {
        "n_estimators": [100, 300],
        "learning_rate": [0.03, 0.10],
        "max_depth": [2, 3],
        "min_child_weight": [1, 5],
        "reg_lambda": [1.0, 5.0],
    },
}

2. 乳腺癌分类

2.1 数据划分

仍将恶性样本记为正类 1,良性样本记为 0。先按类别比例划出 60% 的训练集,再将剩余样本等分为验证集与测试集;两次划分都使用同一个随机种子并保持类别比例。

cancer = load_breast_cancer()
X_classification = pd.DataFrame(cancer.data, columns=cancer.feature_names)
y_classification = pd.Series(
    (cancer.target == 0).astype(int),
    name="malignant",
)

(
    X_classification_train,
    X_classification_remaining,
    y_classification_train,
    y_classification_remaining,
) = train_test_split(
    X_classification,
    y_classification,
    test_size=0.40,
    stratify=y_classification,
    random_state=RANDOM_STATE,
)
(
    X_classification_validation,
    X_classification_test,
    y_classification_validation,
    y_classification_test,
) = train_test_split(
    X_classification_remaining,
    y_classification_remaining,
    test_size=0.50,
    stratify=y_classification_remaining,
    random_state=RANDOM_STATE,
)
def classification_metrics(model, features, labels):
    prediction = model.predict(features)
    probability = model.predict_proba(features)[:, 1]
    return {
        "Accuracy": accuracy_score(labels, prediction),
        "Precision": precision_score(labels, prediction, zero_division=0),
        "Recall": recall_score(labels, prediction),
        "F1": f1_score(labels, prediction),
        "AUROC": roc_auc_score(labels, probability),
    }
2.2 四种模型调参

每个候选模型只在训练集上拟合,并计算训练 AUROC 与验证 AUROC。选择验证 AUROC 最大的候选。

def tune_classifier(estimator, parameter_grid):
    rows = []
    fitted_candidates = {}
    for candidate_id, parameters in enumerate(ParameterGrid(parameter_grid), start=1):
        candidate = clone(estimator).set_params(**parameters)
        candidate.fit(X_classification_train, y_classification_train)
        training_metrics = classification_metrics(
            candidate,
            X_classification_train,
            y_classification_train,
        )
        validation_metrics = classification_metrics(
            candidate,
            X_classification_validation,
            y_classification_validation,
        )
        rows.append(
            {
                "candidate": candidate_id,
                "parameters": parameters,
                "parameters text": parameter_text(parameters),
                "training AUROC": training_metrics["AUROC"],
                "validation Accuracy": validation_metrics["Accuracy"],
                "validation Precision": validation_metrics["Precision"],
                "validation Recall": validation_metrics["Recall"],
                "validation F1": validation_metrics["F1"],
                "validation AUROC": validation_metrics["AUROC"],
            }
        )
        fitted_candidates[candidate_id] = candidate

    results = pd.DataFrame(rows)
    selected = results.loc[results["validation AUROC"].idxmax()]
    return fitted_candidates[int(selected["candidate"])], selected, results


classification_estimators = {
    "Decision tree": DecisionTreeClassifier(
        random_state=RANDOM_STATE,
    ),
    "Random forest": RandomForestClassifier(
        random_state=RANDOM_STATE,
        n_jobs=1,
    ),
    "GBDT": GradientBoostingClassifier(
        random_state=RANDOM_STATE,
        subsample=0.8,
    ),
    "XGBoost": XGBClassifier(
        objective="binary:logistic",
        eval_metric="logloss",
        tree_method="hist",
        subsample=0.8,
        colsample_bytree=0.8,
        random_state=RANDOM_STATE,
        n_jobs=1,
        verbosity=0,
    ),
}

classification_tuned_models = {}
classification_selected_rows = {}
classification_search_results = {}

for model_name, estimator in classification_estimators.items():
    fitted_model, selected, results = tune_classifier(
        estimator,
        classification_parameter_grids[model_name],
    )
    classification_tuned_models[model_name] = fitted_model
    classification_selected_rows[model_name] = selected
    classification_search_results[model_name] = results

先给出各模型验证 AUROC 排名最高的五组候选,再汇总最终选定的参数。

for model_name, results in classification_search_results.items():
    print(f"{model_name}: top five candidates")
    top_candidates = (
        results.sort_values(
            "validation AUROC",
            ascending=False,
        )
        .head(5)[
            [
                "parameters text",
                "training AUROC",
                "validation AUROC",
                "validation Accuracy",
                "validation Precision",
                "validation Recall",
                "validation F1",
            ]
        ]
    )
    display(top_candidates.round(3))

classification_tuning_summary = pd.DataFrame(
    [
        {
            "model": model_name,
            "training AUROC": selected["training AUROC"],
            "validation AUROC": selected["validation AUROC"],
            "validation Accuracy": selected["validation Accuracy"],
            "validation Precision": selected["validation Precision"],
            "validation Recall": selected["validation Recall"],
            "validation F1": selected["validation F1"],
        }
        for model_name, selected in classification_selected_rows.items()
    ]
).set_index("model")
display(classification_tuning_summary.round(3))
for model_name, selected in classification_selected_rows.items():
    print(f"{model_name}: {parameter_text(selected['parameters'])}")
Decision tree: top five candidates
parameters text training AUROC validation AUROC validation Accuracy validation Precision validation Recall validation F1
34 max_depth=6, min_samples_leaf=20 0.988 0.985 0.939 0.929 0.907 0.918
40 max_depth=8, min_samples_leaf=20 0.988 0.985 0.939 0.929 0.907 0.918
46 max_depth=None, min_samples_leaf=20 0.988 0.985 0.939 0.929 0.907 0.918
28 max_depth=5, min_samples_leaf=20 0.988 0.985 0.939 0.929 0.907 0.918
17 max_depth=3, min_samples_leaf=30 0.977 0.978 0.947 0.911 0.953 0.932
Random forest: top five candidates
parameters text training AUROC validation AUROC validation Accuracy validation Precision validation Recall validation F1
0 max_depth=6, max_features='sqrt', min_samples_... 1.000 0.995 0.956 0.975 0.907 0.940
12 max_depth=12, max_features='sqrt', min_samples... 1.000 0.995 0.956 0.975 0.907 0.940
24 max_depth=None, max_features='sqrt', min_sampl... 1.000 0.995 0.956 0.975 0.907 0.940
27 max_depth=None, max_features='sqrt', min_sampl... 0.999 0.994 0.956 0.975 0.907 0.940
9 max_depth=6, max_features=0.5, min_samples_lea... 0.999 0.994 0.965 0.976 0.930 0.952
GBDT: top five candidates
parameters text training AUROC validation AUROC validation Accuracy validation Precision validation Recall validation F1
19 learning_rate=0.1, max_depth=1, min_samples_le... 1.0 1.0 0.982 1.0 0.953 0.976
23 learning_rate=0.1, max_depth=1, min_samples_le... 1.0 1.0 0.982 1.0 0.953 0.976
21 learning_rate=0.1, max_depth=1, min_samples_le... 1.0 1.0 0.982 1.0 0.953 0.976
29 learning_rate=0.1, max_depth=2, min_samples_le... 1.0 1.0 0.974 1.0 0.930 0.964
35 learning_rate=0.1, max_depth=3, min_samples_le... 1.0 1.0 0.974 1.0 0.930 0.964
XGBoost: top five candidates
parameters text training AUROC validation AUROC validation Accuracy validation Precision validation Recall validation F1
26 learning_rate=0.1, max_depth=3, min_child_weig... 1.000 1.000 0.982 1.0 0.953 0.976
18 learning_rate=0.1, max_depth=2, min_child_weig... 1.000 1.000 0.982 1.0 0.953 0.976
16 learning_rate=0.1, max_depth=2, min_child_weig... 1.000 0.999 0.982 1.0 0.953 0.976
14 learning_rate=0.03, max_depth=3, min_child_wei... 0.997 0.999 0.982 1.0 0.953 0.976
30 learning_rate=0.1, max_depth=3, min_child_weig... 0.998 0.999 0.974 1.0 0.930 0.964
training AUROC validation AUROC validation Accuracy validation Precision validation Recall validation F1
model
Decision tree 0.988 0.985 0.939 0.929 0.907 0.918
Random forest 1.000 0.995 0.956 0.975 0.907 0.940
GBDT 1.000 1.000 0.982 1.000 0.953 0.976
XGBoost 1.000 1.000 0.982 1.000 0.953 0.976
Decision tree: max_depth=5, min_samples_leaf=20
Random forest: max_depth=6, max_features='sqrt', min_samples_leaf=1, n_estimators=200
GBDT: learning_rate=0.1, max_depth=1, min_samples_leaf=5, n_estimators=300
XGBoost: learning_rate=0.1, max_depth=2, min_child_weight=1, n_estimators=300, reg_lambda=1.0
2.3 独立测试集性能

全部参数确定后,统一在独立测试集上计算 Accuracy、Precision、Recall、F1 与 AUROC。分类阈值为 0.5,AUROC 使用预测为恶性的概率。

classification_models = classification_tuned_models

classification_rows = []
for model_name, model in classification_models.items():
    for data_name, features, labels in [
        (
            "training",
            X_classification_train,
            y_classification_train,
        ),
        (
            "validation",
            X_classification_validation,
            y_classification_validation,
        ),
        ("test", X_classification_test, y_classification_test),
    ]:
        classification_rows.append(
            {
                "model": model_name,
                "data": data_name,
                **classification_metrics(model, features, labels),
            }
        )

classification_comparison = pd.DataFrame(classification_rows).set_index(
    ["model", "data"]
)
classification_test_comparison = classification_comparison.xs(
    "test",
    level="data",
).copy()
classification_test_comparison["AUROC difference from tree"] = (
    classification_test_comparison["AUROC"]
    - classification_test_comparison.loc["Decision tree", "AUROC"]
)
display(classification_comparison.round(3))
display(classification_test_comparison.round(3))
Accuracy Precision Recall F1 AUROC
model data
Decision tree training 0.947 0.936 0.921 0.929 0.988
validation 0.939 0.929 0.907 0.918 0.985
test 0.939 0.973 0.857 0.911 0.950
Random forest training 0.994 1.000 0.984 0.992 1.000
validation 0.956 0.975 0.907 0.940 0.995
test 0.974 1.000 0.929 0.963 0.995
GBDT training 1.000 1.000 1.000 1.000 1.000
validation 0.982 1.000 0.953 0.976 1.000
test 0.982 1.000 0.952 0.976 0.993
XGBoost training 1.000 1.000 1.000 1.000 1.000
validation 0.982 1.000 0.953 0.976 1.000
test 0.974 1.000 0.929 0.963 0.990
Accuracy Precision Recall F1 AUROC AUROC difference from tree
model
Decision tree 0.939 0.973 0.857 0.911 0.950 0.000
Random forest 0.974 1.000 0.929 0.963 0.995 0.045
GBDT 0.982 1.000 0.952 0.976 0.993 0.043
XGBoost 0.974 1.000 0.929 0.963 0.990 0.041
fig, axes = plt.subplots(2, 2, figsize=(10.2, 8.4), constrained_layout=True)
for ax, (model_name, model) in zip(axes.flat, classification_models.items()):
    ConfusionMatrixDisplay.from_predictions(
        y_classification_test,
        model.predict(X_classification_test),
        display_labels=["Benign", "Malignant"],
        colorbar=False,
        cmap="Blues",
        ax=ax,
    )
    ax.set_title(model_name)
plt.show()

fig, ax = plt.subplots(figsize=(7.5, 5.4), constrained_layout=True)
for model_name, model in classification_models.items():
    RocCurveDisplay.from_predictions(
        y_classification_test,
        model.predict_proba(X_classification_test)[:, 1],
        name=model_name,
        curve_kwargs={"color": MODEL_COLORS[model_name]},
        ax=ax,
    )
ax.plot(
    [0, 1],
    [0, 1],
    linestyle="--",
    color="#6B7280",
    linewidth=1,
)
ax.set_title("ROC curves on the classification test set")
plt.show()

实验 03「决策树与集成树模型的性能比较」结果 1(图 1)

实验 03「决策树与集成树模型的性能比较」结果 1(图 2)

3. 糖尿病回归

3.1 数据划分

使用两次随机划分得到 60% 的训练集、20% 的验证集和 20% 的测试集。所有候选模型只在训练集上拟合,验证集用于选参。

diabetes = load_diabetes(as_frame=True)
X_regression = diabetes.data
y_regression = diabetes.target.rename("disease progression")

X_train, X_remaining, y_train, y_remaining = train_test_split(
    X_regression,
    y_regression,
    test_size=0.40,
    random_state=RANDOM_STATE,
)
X_validation, X_regression_test, y_validation, y_regression_test = (
    train_test_split(
        X_remaining,
        y_remaining,
        test_size=0.50,
        random_state=RANDOM_STATE,
    )
)

regression_split = pd.DataFrame(
    {
        "samples": [
            len(X_train),
            len(X_validation),
            len(X_regression_test),
        ],
        "proportion": [
            len(X_train) / len(X_regression),
            len(X_validation) / len(X_regression),
            len(X_regression_test) / len(X_regression),
        ],
        "purpose": [
            "fit candidate models",
            "select hyperparameters",
            "final evaluation",
        ],
    },
    index=["training", "validation", "test"],
)
display(regression_split.round({"proportion": 3}))
samples proportion purpose
training 265 0.600 fit candidate models
validation 88 0.199 select hyperparameters
test 89 0.201 final evaluation
def regression_metrics(response, prediction):
    return {
        "MAE": mean_absolute_error(response, prediction),
        "RMSE": np.sqrt(mean_squared_error(response, prediction)),
        "R2": r2_score(response, prediction),
    }
3.2 四种模型调参

每组参数都只在训练集上拟合,然后在同一个验证集上计算 MAE、RMSE 与 R2。选择验证 RMSE 最小的候选。

def tune_regressor(estimator, parameter_grid):
    rows = []
    fitted_candidates = {}
    for candidate_id, parameters in enumerate(ParameterGrid(parameter_grid), start=1):
        candidate = clone(estimator).set_params(**parameters)
        candidate.fit(X_train, y_train)
        prediction = candidate.predict(X_validation)
        rows.append(
            {
                "candidate": candidate_id,
                "parameters": parameters,
                "parameters text": parameter_text(parameters),
                **regression_metrics(y_validation, prediction),
            }
        )
        fitted_candidates[candidate_id] = candidate

    results = pd.DataFrame(rows)
    selected = results.loc[results["RMSE"].idxmin()]
    return fitted_candidates[int(selected["candidate"])], selected, results


regression_estimators = {
    "Decision tree": DecisionTreeRegressor(
        random_state=RANDOM_STATE,
    ),
    "Random forest": RandomForestRegressor(
        random_state=RANDOM_STATE,
        n_jobs=1,
    ),
    "GBDT": GradientBoostingRegressor(
        random_state=RANDOM_STATE,
        subsample=0.8,
        loss="squared_error",
    ),
    "XGBoost": XGBRegressor(
        objective="reg:squarederror",
        eval_metric="rmse",
        tree_method="hist",
        subsample=0.8,
        colsample_bytree=0.8,
        random_state=RANDOM_STATE,
        n_jobs=1,
        verbosity=0,
    ),
}

regression_tuned_models = {}
regression_selected_rows = {}
regression_search_results = {}

for model_name, estimator in regression_estimators.items():
    fitted_model, selected, results = tune_regressor(
        estimator,
        regression_parameter_grids[model_name],
    )
    regression_tuned_models[model_name] = fitted_model
    regression_selected_rows[model_name] = selected
    regression_search_results[model_name] = results
for model_name, results in regression_search_results.items():
    print(f"{model_name}: top five candidates")
    top_candidates = (
        results.sort_values(
            "RMSE",
            ascending=True,
        )
        .head(5)[["parameters text", "MAE", "RMSE", "R2"]]
    )
    display(top_candidates.round(3))

regression_tuning_summary = pd.DataFrame(
    [
        {
            "model": model_name,
            "validation MAE": selected["MAE"],
            "validation RMSE": selected["RMSE"],
            "validation R2": selected["R2"],
            "selected parameters": parameter_text(selected["parameters"]),
        }
        for model_name, selected in regression_selected_rows.items()
    ]
).set_index("model")
display(regression_tuning_summary.round(3))
for model_name, selected in regression_selected_rows.items():
    print(f"{model_name}: {parameter_text(selected['parameters'])}")
Decision tree: top five candidates
parameters text MAE RMSE R2
17 max_depth=3, min_samples_leaf=30 41.586 53.218 0.509
23 max_depth=4, min_samples_leaf=30 41.990 53.934 0.495
41 max_depth=8, min_samples_leaf=30 41.990 53.934 0.495
35 max_depth=6, min_samples_leaf=30 41.990 53.934 0.495
29 max_depth=5, min_samples_leaf=30 41.990 53.934 0.495
Random forest: top five candidates
parameters text MAE RMSE R2
2 max_depth=6, max_features=0.5, min_samples_lea... 41.677 51.664 0.537
14 max_depth=12, max_features=0.5, min_samples_le... 41.802 51.882 0.533
3 max_depth=6, max_features=0.5, min_samples_lea... 41.752 51.922 0.532
26 max_depth=None, max_features=0.5, min_samples_... 41.828 51.940 0.532
15 max_depth=12, max_features=0.5, min_samples_le... 41.986 52.033 0.530
GBDT: top five candidates
parameters text MAE RMSE R2
22 learning_rate=0.1, max_depth=1, min_samples_le... 39.344 50.285 0.561
3 learning_rate=0.03, max_depth=1, min_samples_l... 39.512 50.519 0.557
1 learning_rate=0.03, max_depth=1, min_samples_l... 39.548 50.526 0.557
18 learning_rate=0.1, max_depth=1, min_samples_le... 39.948 50.548 0.557
5 learning_rate=0.03, max_depth=1, min_samples_l... 39.549 50.555 0.557
XGBoost: top five candidates
parameters text MAE RMSE R2
20 learning_rate=0.1, max_depth=2, min_child_weig... 40.526 50.519 0.557
21 learning_rate=0.1, max_depth=2, min_child_weig... 40.325 50.526 0.557
17 learning_rate=0.1, max_depth=2, min_child_weig... 40.092 50.616 0.556
7 learning_rate=0.03, max_depth=2, min_child_wei... 39.965 50.621 0.556
3 learning_rate=0.03, max_depth=2, min_child_wei... 40.177 50.786 0.553
validation MAE validation RMSE validation R2 selected parameters
model
Decision tree 41.586 53.218 0.509 max_depth=3, min_samples_leaf=30
Random forest 41.677 51.664 0.537 max_depth=6, max_features=0.5, min_samples_lea...
GBDT 39.344 50.285 0.561 learning_rate=0.1, max_depth=1, min_samples_le...
XGBoost 40.526 50.519 0.557 learning_rate=0.1, max_depth=2, min_child_weig...
Decision tree: max_depth=3, min_samples_leaf=30
Random forest: max_depth=6, max_features=0.5, min_samples_leaf=3, n_estimators=200
GBDT: learning_rate=0.1, max_depth=1, min_samples_leaf=20, n_estimators=100
XGBoost: learning_rate=0.1, max_depth=2, min_child_weight=5, n_estimators=100, reg_lambda=1.0
3.3 独立测试集性能

全部参数确定后,统一在独立测试集上报告 MAE、RMSE 与 R2。RMSE 相对决策树的改善比例定义为:

$$ \frac{\operatorname{RMSE}_{\mathrm{tree}}-\operatorname{RMSE}_{\mathrm{model}}} {\operatorname{RMSE}_{\mathrm{tree}}}. $$

正值表示测试 RMSE 低于决策树。

regression_models = regression_tuned_models

regression_rows = []
regression_predictions = {}
for model_name, model in regression_models.items():
    prediction = model.predict(X_regression_test)
    regression_predictions[model_name] = prediction
    regression_rows.append(
        {
            "model": model_name,
            **regression_metrics(y_regression_test, prediction),
        }
    )

regression_test_comparison = pd.DataFrame(regression_rows).set_index("model")
tree_rmse = regression_test_comparison.loc["Decision tree", "RMSE"]
regression_test_comparison["RMSE improvement over tree"] = (
    tree_rmse - regression_test_comparison["RMSE"]
) / tree_rmse
display(regression_test_comparison.round(3))
MAE RMSE R2 RMSE improvement over tree
model
Decision tree 49.292 62.859 0.317 0.000
Random forest 45.040 55.459 0.468 0.118
GBDT 45.088 54.993 0.477 0.125
XGBoost 45.080 55.483 0.468 0.117
plot_min = min(
    y_regression_test.min(),
    *(prediction.min() for prediction in regression_predictions.values()),
) - 5
plot_max = max(
    y_regression_test.max(),
    *(prediction.max() for prediction in regression_predictions.values()),
) + 5

fig, axes = plt.subplots(2, 2, figsize=(10.2, 8.4), constrained_layout=True)
for ax, (model_name, prediction) in zip(
    axes.flat,
    regression_predictions.items(),
):
    ax.scatter(
        y_regression_test,
        prediction,
        s=28,
        alpha=0.72,
        color=MODEL_COLORS[model_name],
    )
    ax.plot(
        [plot_min, plot_max],
        [plot_min, plot_max],
        linestyle="--",
        linewidth=1.4,
        color="#111827",
    )
    ax.set(
        title=model_name,
        xlabel="Observed response",
        ylabel="Predicted response",
        xlim=(plot_min, plot_max),
        ylim=(plot_min, plot_max),
    )
plt.show()

fig, axes = plt.subplots(1, 3, figsize=(12.2, 4.2), constrained_layout=True)
for ax, metric in zip(axes, ["MAE", "RMSE", "R2"]):
    values = regression_test_comparison[metric]
    ax.bar(
        values.index,
        values,
        color=[MODEL_COLORS[name] for name in values.index],
    )
    ax.set_title(f"Test {metric}")
    ax.tick_params(axis="x", rotation=30)
plt.show()

实验 03「决策树与集成树模型的性能比较」结果 1(图 3)

实验 03「决策树与集成树模型的性能比较」结果 1(图 4)

实验04

主成分分析

R · Quarto

本实验使用R自带的USArrests数据完成一次主成分分析(PCA),重点回答四个问题:

  1. 原始数据的第一主成分是否被大尺度变量主导;
  2. 标准化后各变量如何共同构成主要变异方向;
  3. 如何根据累计方差贡献率选择主成分个数;
  4. 哪些州对前两个主成分的形成影响最大,地区之间呈现怎样的差异。

USArrests记录了美国50个州在1973年的四项指标。Murder、Assault和Rape分别是每10万居民中的案件数,UrbanPop是城市人口所占百分比。州所属地区只用于给得分图着色,不参与PCA拟合。

数据概况与变量尺度

先查看样本量以及各变量的均值、标准差和取值范围。

arrests <- datasets::USArrests

dim(arrests)
[1] 50  4
data.frame(
  Variable = names(arrests),
  Mean = vapply(arrests, mean, numeric(1)),
  StandardDeviation = vapply(arrests, sd, numeric(1)),
  Minimum = vapply(arrests, min, numeric(1)),
  Maximum = vapply(arrests, max, numeric(1)),
  row.names = NULL
)
  Variable    Mean StandardDeviation Minimum Maximum
1   Murder   7.788            4.3555     0.8    17.4
2  Assault 170.760           83.3377    45.0   337.0
3 UrbanPop  65.540           14.4748    32.0    91.0
4     Rape  21.232            9.3664     7.3    46.0

Assault的标准差远大于其他变量。若直接对原始数据的协方差矩阵进行特征值分解,大尺度变量会对总方差产生更大的影响。

PCA还依赖变量之间的相关结构。先计算样本相关矩阵:

arrests_correlation <- cor(arrests)
round(arrests_correlation, 3)
         Murder Assault UrbanPop  Rape
Murder    1.000   0.802    0.070 0.564
Assault   0.802   1.000    0.259 0.665
UrbanPop  0.070   0.259    1.000 0.411
Rape      0.564   0.665    0.411 1.000

Murder与Assault的相关性最强,Rape也与两者呈明显正相关;UrbanPop与Murder的线性相关性则很弱。因此,标准化后的PCA既要概括三项犯罪指标的共同变化,也要保留城市化程度提供的不同信息。

比较标准化前后的第一主成分

分别对原始数据和标准化数据拟合PCA。标准化将每个变量变换为均值为0、样本方差为1的变量,此时PCA等价于对原变量的样本相关矩阵进行特征值分解。

为了判断第一主成分是否被某个变量主导,不能只比较方差贡献率,还应检查因子负荷。第\(j\)个变量在第一主成分上的因子负荷为

\[ \rho(X_j,Y_1), \]

即该变量与第一主成分得分的相关系数。其绝对值越接近1,第一主成分与该变量的变化越接近。

variance_ratio <- function(model) {
  model$sdev^2 / sum(model$sdev^2)
}

components_for_threshold <- function(model, threshold = 0.85) {
  which(cumsum(variance_ratio(model)) >= threshold)[1]
}

pca_raw <- prcomp(arrests, center = TRUE, scale. = FALSE)
pca_standardized <- prcomp(arrests, center = TRUE, scale. = TRUE)

raw_ratio <- variance_ratio(pca_raw)
standardized_ratio <- variance_ratio(pca_standardized)

standardized_arrests <- scale(arrests, center = TRUE, scale = TRUE)

# 调整PC1的正负号,使Assault的因子负荷为正,便于比较和解释。
raw_pc1_direction <- if (cor(arrests$Assault, pca_raw$x[, 1]) < 0) -1 else 1
standardized_pc1_direction <- if (
  cor(standardized_arrests[, "Assault"], pca_standardized$x[, 1]) < 0
) {
  -1
} else {
  1
}

raw_pc1_scores <- raw_pc1_direction * pca_raw$x[, 1]
standardized_pc1_scores <- standardized_pc1_direction *
  pca_standardized$x[, 1]

raw_pc1_loadings <- cor(as.matrix(arrests), raw_pc1_scores)[, 1]
standardized_pc1_loadings <- cor(
  standardized_arrests,
  standardized_pc1_scores
)[, 1]

scale_effect_summary <- data.frame(
  Data = c("Raw", "Standardized"),
  PC1VarianceRatio = c(raw_ratio[1], standardized_ratio[1]),
  FirstTwoPCs = c(sum(raw_ratio[1:2]), sum(standardized_ratio[1:2])),
  PCsNeededFor85Percent = c(
    components_for_threshold(pca_raw),
    components_for_threshold(pca_standardized)
  ),
  row.names = NULL
)

pc1_loading_comparison <- data.frame(
  Variable = names(arrests),
  StandardDeviation = vapply(arrests, sd, numeric(1)),
  RawPC1Loading = raw_pc1_loadings,
  StandardizedPC1Loading = standardized_pc1_loadings,
  RawPC1Rotation = raw_pc1_direction * pca_raw$rotation[, 1],
  StandardizedPC1Rotation = standardized_pc1_direction *
    pca_standardized$rotation[, 1],
  row.names = NULL
)

scale_effect_summary
          Data PC1VarianceRatio FirstTwoPCs PCsNeededFor85Percent
1          Raw          0.96553     0.99335                     1
2 Standardized          0.62006     0.86750                     2
pc1_loading_comparison
  Variable StandardDeviation RawPC1Loading StandardizedPC1Loading RawPC1Rotation
1   Murder            4.3555       0.80174                0.84398       0.041704
2  Assault           83.3377       0.99994                0.91844       0.995221
3 UrbanPop           14.4748       0.26804                0.43812       0.046336
4     Rape            9.3664       0.67187                0.85584       0.075156
  StandardizedPC1Rotation
1                 0.53590
2                 0.58318
3                 0.27819
4                 0.54343
pc1_loading_matrix <- rbind(
  Raw = abs(raw_pc1_loadings),
  Standardized = abs(standardized_pc1_loadings)
)

barplot(
  pc1_loading_matrix,
  beside = TRUE,
  names.arg = names(arrests),
  col = c(colour_smooth, colour_main),
  border = NA,
  ylim = c(0, 1.08),
  main = "Absolute PC1 Loadings before and after Standardization",
  xlab = "Variable",
  ylab = "Absolute Loading"
)
abline(h = 1, lty = 3, col = "grey55")
legend(
  "topright",
  legend = rownames(pc1_loading_matrix),
  fill = c(colour_smooth, colour_main),
  border = NA,
  bty = "n"
)

实验 04「主成分分析」结果 1(图 1)

old_par <- par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))

barplot(
  raw_ratio,
  names.arg = paste0("PC", seq_along(raw_ratio)),
  col = colour_smooth,
  border = NA,
  ylim = c(0, 1),
  main = "Raw Data",
  xlab = "Principal Component",
  ylab = "Variance Ratio"
)
barplot(
  standardized_ratio,
  names.arg = paste0("PC", seq_along(standardized_ratio)),
  col = colour_main,
  border = NA,
  ylim = c(0, 1),
  main = "Standardized Data",
  xlab = "Principal Component",
  ylab = "Variance Ratio"
)

实验 04「主成分分析」结果 1(图 2)

par(old_par)

原始数据PC1与Assault的因子负荷为0.9999,几乎达到1;同时,Assault在PC1方向向量中的系数为0.9952。这两项证据共同说明:原始数据的PC1几乎沿着Assault的变化方向,而Assault远大的标准差使它主导了协方差矩阵的最大变异方向。

Murder和Rape在原始PC1上的因子负荷也不低,并不意味着它们在PC1公式中的权重同样大;主要原因是它们本身与Assault正相关。这里,rotation描述主成分由各原变量如何线性组合,loading描述原变量与主成分的相关程度,二者含义不能混用。

标准化后,PC1在Assault、Rape和Murder上的因子负荷分别为0.918、0.856和0.844。三项犯罪指标共同决定第一主成分,不再由Assault的量纲单独控制。与此同时,PC1的方差贡献率从96.6%降为62.0%,说明原始数据中看似极高的解释比例主要来自尺度效应。

根据累计方差贡献率选择维数

下面列出各主成分的方差贡献率,并选择使累计方差贡献率首次达到85%的最小主成分个数。

standardized_cumulative_ratio <- cumsum(standardized_ratio)
selected_k <- components_for_threshold(pca_standardized, threshold = 0.85)

variance_table <- data.frame(
  Component = paste0("PC", seq_along(standardized_ratio)),
  Eigenvalue = pca_standardized$sdev^2,
  VarianceRatio = standardized_ratio,
  CumulativeRatio = standardized_cumulative_ratio,
  row.names = NULL
)

variance_table
  Component Eigenvalue VarianceRatio CumulativeRatio
1       PC1    2.48024      0.620060         0.62006
2       PC2    0.98977      0.247441         0.86750
3       PC3    0.35656      0.089141         0.95664
4       PC4    0.17343      0.043358         1.00000
bar_positions <- barplot(
  standardized_ratio,
  names.arg = variance_table$Component,
  col = colour_main,
  border = NA,
  ylim = c(0, 1),
  main = "Scree Plot and Cumulative Variance Ratio",
  xlab = "Principal Component",
  ylab = "Variance Ratio"
)
lines(
  bar_positions,
  standardized_cumulative_ratio,
  type = "b",
  pch = 19,
  lwd = 2,
  col = colour_smooth
)
abline(h = 0.85, lty = 2, col = "grey25")
points(
  bar_positions[selected_k],
  standardized_cumulative_ratio[selected_k],
  pch = 21,
  cex = 1.5,
  bg = "grey15",
  col = "white"
)
text(
  bar_positions[selected_k],
  standardized_cumulative_ratio[selected_k],
  labels = paste0("K = ", selected_k),
  pos = 4,
  offset = 0.7
)
legend(
  "bottomright",
  legend = c("Individual Ratio", "Cumulative Ratio", "85% Threshold"),
  fill = c(colour_main, NA, NA),
  border = c(NA, NA, NA),
  lty = c(NA, 1, 2),
  lwd = c(NA, 2, 1),
  pch = c(15, 19, NA),
  col = c(colour_main, colour_smooth, "grey25"),
  bty = "n"
)

实验 04「主成分分析」结果 1(图 3)

cat(
  sprintf(
    "\nThe first %d principal components explain %.1f%% of the standardized variance.\n",
    selected_k,
    100 * standardized_cumulative_ratio[selected_k]
  )
)

The first 2 principal components explain 86.8% of the standardized variance.

本例中,前2个主成分的累计方差贡献率为86.8%,首次超过85%的阈值。

解释前两个主成分

主成分得分是标准化样本在主成分方向上的坐标。因子负荷定义为主成分与原变量的相关系数;其绝对值越大,表示该变量与相应主成分的线性关系越强。

pca_directions <- c(
  standardized_pc1_direction,
  rep(1, ncol(pca_standardized$x) - 1)
)
pca_scores <- sweep(pca_standardized$x, 2, pca_directions, `*`)
factor_loadings <- cor(standardized_arrests, pca_scores)

state_regions <- as.character(
  state.region[match(rownames(arrests), state.name)]
)
score_contributions <- sweep(
  pca_scores[, 1:2, drop = FALSE]^2,
  2,
  colSums(pca_scores[, 1:2, drop = FALSE]^2),
  `/`
) *
  100
states_to_label <- unique(unlist(lapply(
  1:2,
  function(component) {
    names(sort(score_contributions[, component], decreasing = TRUE))[1:3]
  }
)))

displayed_loadings <- factor_loadings[
  nrow(factor_loadings):1,
  1:2,
  drop = FALSE
]
loading_palette <- colorRampPalette(c("#b2182b", "#f7f7f7", "#2166ac"))(201)

old_par <- par(mfrow = c(1, 2), mar = c(5, 5, 3, 1))

plot(
  pca_scores[, 1],
  pca_scores[, 2],
  pch = 19,
  col = unname(region_colours[state_regions]),
  xlab = sprintf("PC1 (%.1f%%)", 100 * standardized_ratio[1]),
  ylab = sprintf("PC2 (%.1f%%)", 100 * standardized_ratio[2]),
  main = "Scores on the First Two Components"
)
abline(h = 0, v = 0, lty = 3, col = "grey55")
text(
  pca_scores[states_to_label, 1],
  pca_scores[states_to_label, 2],
  labels = states_to_label,
  pos = 3,
  cex = 0.66,
  col = "grey20"
)
legend(
  "topright",
  legend = names(region_colours),
  col = region_colours,
  pch = 19,
  bty = "n",
  cex = 0.8
)

image(
  x = 1:2,
  y = seq_len(nrow(displayed_loadings)),
  z = t(displayed_loadings),
  col = loading_palette,
  zlim = c(-1, 1),
  axes = FALSE,
  xlab = "",
  ylab = "",
  main = "Factor Loadings"
)
axis(1, at = 1:2, labels = c("PC1", "PC2"), tick = FALSE)
axis(
  2,
  at = seq_len(nrow(displayed_loadings)),
  labels = rownames(displayed_loadings),
  las = 2,
  tick = FALSE
)
for (row in seq_len(nrow(displayed_loadings))) {
  for (column in 1:2) {
    value <- displayed_loadings[row, column]
    text(
      column,
      row,
      labels = sprintf("%.2f", value),
      col = ifelse(abs(value) >= 0.55, "white", "grey15"),
      cex = 0.9
    )
  }
}
box()

实验 04「主成分分析」结果 1(图 4)

par(old_par)

round(factor_loadings[, 1:2, drop = FALSE], 3)
           PC1    PC2
Murder   0.844 -0.416
Assault  0.918 -0.187
UrbanPop 0.438  0.868
Rape     0.856  0.166

第一主成分在三项犯罪指标上的因子负荷都较高,可以解释为“暴力犯罪总体水平”:PC1得分越高,Murder、Assault和Rape通常越高。第二主成分在UrbanPop上的因子负荷为0.868,远大于其他变量,主要刻画城市人口比例方向。由于PC2在Murder上的负荷为负,它还包含“城市化程度较高但谋杀率相对较低”与相反情形之间的对比。

识别对主成分影响较大的州

一个州在第\(k\)个主成分上的贡献定义为其得分平方占该主成分全部得分平方和的比例:

\[ \operatorname{Ctr}_{ik} =\frac{y_{ik}^2}{\sum_{j=1}^{50}y_{jk}^2}. \]

贡献较大的州位于相应主成分轴的两端,对该方向的样本差异具有较强代表性。

top_contributing_states <- do.call(
  rbind,
  lapply(
    1:2,
    function(component) {
      state_names <- names(sort(
        score_contributions[, component],
        decreasing = TRUE
      ))[1:5]

      data.frame(
        Component = paste0("PC", component),
        State = state_names,
        Region = state_regions[match(state_names, rownames(arrests))],
        Score = pca_scores[state_names, component],
        ContributionPercent = score_contributions[state_names, component],
        arrests[state_names, , drop = FALSE],
        row.names = NULL
      )
    }
  )
)

top_contributing_states
   Component          State        Region   Score ContributionPercent Murder Assault UrbanPop Rape
1        PC1        Florida         South  2.9828              7.3206   15.4     335       80 31.9
2        PC1   North Dakota North Central -2.9622              7.2198    0.8      45       44  7.3
3        PC1         Nevada          West  2.8455              6.6624   12.2     252       81 46.0
4        PC1        Vermont     Northeast -2.7733              6.3283    2.2      48       32 11.2
5        PC1     California          West  2.4986              5.1370    9.0     276       91 40.6
6        PC2    Mississippi         South -2.3697             11.5790   16.1     259       44 17.1
7        PC2 North Carolina         South -2.2056             10.0307   13.0     337       45 16.1
8        PC2 South Carolina         South -1.9140              7.5534   14.4     279       48 22.5
9        PC2         Hawaii          West  1.5547              4.9837    5.3      46       83 20.2
10       PC2     California          West  1.5274              4.8105    9.0     276       91 40.6

PC1贡献最大的州同时出现在暴力犯罪指标的高端或低端。例如,Florida、Nevada和California具有较高的PC1得分,而North Dakota、Vermont等州位于相反方向。PC2贡献较大的州主要由UrbanPop与犯罪指标之间的相对关系决定,而不只是由某一个指标的绝对大小决定。

最后汇总各地区在前两个主成分上的平均位置与内部离散程度。地区信息仍然没有参与PCA拟合,只用于事后解释。

region_score_summary <- do.call(
  rbind,
  lapply(
    names(region_colours),
    function(region) {
      region_rows <- state_regions == region
      data.frame(
        Region = region,
        States = sum(region_rows),
        MeanPC1 = mean(pca_scores[region_rows, 1]),
        SDPC1 = sd(pca_scores[region_rows, 1]),
        MeanPC2 = mean(pca_scores[region_rows, 2]),
        SDPC2 = sd(pca_scores[region_rows, 2]),
        row.names = NULL
      )
    }
  )
)

region_score_summary
         Region States  MeanPC1  SDPC1  MeanPC2   SDPC2
1     Northeast      9 -1.02460 1.4051  0.56522 0.98158
2         South     16  0.70536 1.2029 -0.85648 0.87283
3 North Central     12 -0.79327 1.5465  0.19664 0.48041
4          West     13  0.57345 1.5409  0.48132 0.85325

南部和西部地区的平均PC1得分较高,说明它们在这份1973年数据中的三项暴力犯罪指标整体偏高;南部地区的平均PC2得分较低,而东北部和西部较高,主要反映城市化程度及其与犯罪指标的相对关系。各地区内部的标准差仍然较大,因此这些结果只能描述该历史数据集中的总体结构,不能据此作因果解释,也不能代表当前情况。

实验05

K-means

Python · Jupyter

K-means 聚类:用多种指标选择簇数

本实验使用 scikit-learn 完成 K-means 聚类。主要观察两个问题:

  1. 不同评价指标如何帮助选择簇数 K;
  2. 聚类结果如何随 K 的增加而变化。
import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib
configure_matplotlib()
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import (
    adjusted_rand_score,
    calinski_harabasz_score,
    fowlkes_mallows_score,
    rand_score,
    silhouette_score,
)
from sklearn.preprocessing import StandardScaler

plt.style.use("seaborn-v0_8-white")
SINGLE_FIGSIZE = (8, 5.5)

1. 生成并标准化数据

生成三个近似球形的二维簇。变量 y_true 只用于演示外部评价指标,不参与 K-means 拟合。

X, y_true = make_blobs(
    n_samples=360,
    centers=[(-4, -1), (0, 4), (4, -2)],
    cluster_std=[0.85, 1.00, 0.90],
    random_state=42,
)
X_scaled = StandardScaler().fit_transform(X)

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
ax.scatter(X_scaled[:, 0], X_scaled[:, 1], s=24, alpha=0.75, color="#4C78A8")
ax.set(title="Standardized samples", xlabel="Feature 1", ylabel="Feature 2")
plt.show()

实验 05「K-means」结果 1(图 1)

2. 计算全部评价指标

对 K = 1, 2, …, 8 分别拟合 K-means。WCSS 使用肘部位置判断;CH、Silhouette、RI、ARI 和 FMI 均取较大值。

CH 与 Silhouette 是内部指标,只依赖样本和聚类标签。RI、ARI 与 FMI 是外部指标,需要已知真实标签,因此在实际无监督任务中通常不能用于选择 K。

all_k = range(1, 9)
candidate_k = range(2, 9)
models = {}
rows = []

for k in all_k:
    model = KMeans(n_clusters=k, init="k-means++", n_init=20, random_state=42)
    labels = model.fit_predict(X_scaled)
    models[k] = model

    if k >= 2:
        rows.append(
            {
                "K": k,
                "WCSS": model.inertia_,
                "CH": calinski_harabasz_score(X_scaled, labels),
                "Silhouette": silhouette_score(X_scaled, labels),
                "RI": rand_score(y_true, labels),
                "ARI": adjusted_rand_score(y_true, labels),
                "FMI": fowlkes_mallows_score(y_true, labels),
            }
        )

metrics = pd.DataFrame(rows).set_index("K")
metrics.round(3)
WCSS CH Silhouette RI ARI FMI
K
2 346.953 384.926 0.551 0.777 0.570 0.773
3 64.141 1825.206 0.755 1.000 1.000 1.000
4 55.314 1426.270 0.622 0.947 0.874 0.916
5 46.982 1271.359 0.481 0.889 0.728 0.816
6 39.308 1226.049 0.364 0.838 0.582 0.714
7 33.877 1191.580 0.368 0.818 0.525 0.672
8 30.578 1133.772 0.345 0.796 0.456 0.620

3. 比较指标曲线

WCSS 在 K = 3 附近由陡降转为平缓。其余指标的黑点表示各自的最大值。

chosen_k = 3
wcss = [models[k].inertia_ for k in all_k]
fig, axes = plt.subplots(2, 3, figsize=(14, 8), constrained_layout=True)

axes[0, 0].plot(list(all_k), wcss, marker="o", color="#E45756")
axes[0, 0].scatter(chosen_k, wcss[chosen_k - 1], s=90, color="#111827", zorder=3)
axes[0, 0].annotate("elbow", (chosen_k, wcss[chosen_k - 1]), xytext=(8, 8), textcoords="offset points")
axes[0, 0].set(title="WCSS (elbow)", xlabel="K", ylabel="WCSS", xticks=list(all_k))

for ax, metric, color in zip(
    axes.flat[1:],
    ["CH", "Silhouette", "RI", "ARI", "FMI"],
    ["#4C78A8", "#59A14F", "#F28E2B", "#B279A2", "#76B7B2"],
):
    values = metrics[metric]
    best_k = int(values.idxmax())
    ax.plot(values.index, values, marker="o", color=color)
    ax.scatter(best_k, values.loc[best_k], s=90, color="#111827", zorder=3)
    ax.annotate(f"max at K = {best_k}", (best_k, values.loc[best_k]), xytext=(8, 8), textcoords="offset points")
    ax.set(title=metric, xlabel="K", ylabel=metric, xticks=list(candidate_k))

plt.show()

实验 05「K-means」结果 1(图 2)

4. 查看每一个 K 的聚类结果

下图依次展示 K = 1, 2, …, 8 的划分。星号表示簇中心。随着 K 增大,原有样本群会被进一步拆分。

fig, axes = plt.subplots(2, 4, figsize=(15, 7.5), sharex=True, sharey=True, constrained_layout=True)
colors = plt.get_cmap("tab10")

for ax, k in zip(axes.flat, all_k):
    model = models[k]
    labels = model.labels_
    for label in range(k):
        points = X_scaled[labels == label]
        ax.scatter(points[:, 0], points[:, 1], s=14, alpha=0.72, color=colors(label))
    ax.scatter(
        model.cluster_centers_[:, 0],
        model.cluster_centers_[:, 1],
        marker="*",
        s=150,
        color="#111827",
        edgecolor="white",
        linewidth=0.8,
    )
    title = f"K = {k}" + ("  selected" if k == chosen_k else "")
    ax.set_title(title, fontweight="bold" if k == chosen_k else "normal")
    ax.set_xlabel("Feature 1")
    ax.set_ylabel("Feature 2")

plt.show()

实验 05「K-means」结果 1(图 3)

观察

本例中,各项指标都支持 K = 3。当 K < 3 时,彼此分离的样本群被合并;当 K > 3 时,已有样本群被人为拆分。实际数据没有真实标签时,应主要结合 WCSS、CH、Silhouette 和聚类图作出判断。

实验06

凝聚型层次聚类

Python · Jupyter

凝聚型层次聚类

本实验使用与 K-means 实验相同的数据。主要观察Ward 凝聚法如何从单个样本出发,逐步合并为完整的层次树。

import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib
configure_matplotlib()
import numpy as np
from scipy.cluster.hierarchy import dendrogram
from sklearn.cluster import AgglomerativeClustering
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler

plt.style.use("seaborn-v0_8-white")
SINGLE_FIGSIZE = (8, 5.5)

1. 生成并标准化数据

数据的样本量、中心、离散程度和随机种子均与 K-means 实验相同。

X, _ = make_blobs(
    n_samples=360,
    centers=[(-4, -1), (0, 4), (4, -2)],
    cluster_std=[0.85, 1.00, 0.90],
    random_state=42,
)
X_scaled = StandardScaler().fit_transform(X)

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
ax.scatter(X_scaled[:, 0], X_scaled[:, 1], s=24, alpha=0.75, color="#4C78A8")
ax.set(title="Standardized samples", xlabel="Feature 1", ylabel="Feature 2")
plt.show()

实验 06「凝聚型层次聚类」结果 1(图 1)

2. 展示逐步合并过程

树状图底部每个叶节点表示一个样本;每个分叉连接表示两个簇被合并;最上方只剩一个包含全部样本的簇。虚线是产生 3 个簇的切割位置,虚线下方的不同颜色对应最终簇。

tree = AgglomerativeClustering(
    n_clusters=None,
    distance_threshold=0,
    linkage="ward",
    compute_distances=True,
).fit(X_scaled)

n_samples = X_scaled.shape[0]
cluster_sizes = np.zeros(len(tree.children_), dtype=int)
for step, (left, right) in enumerate(tree.children_):
    left_size = 1 if left < n_samples else cluster_sizes[left - n_samples]
    right_size = 1 if right < n_samples else cluster_sizes[right - n_samples]
    cluster_sizes[step] = left_size + right_size

linkage_matrix = np.column_stack(
    [tree.children_, tree.distances_, cluster_sizes]
).astype(float)

lower_height = tree.distances_[-3]
upper_height = tree.distances_[-3 + 1]
cut_height = (lower_height + upper_height) / 2

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
dendrogram(
    linkage_matrix,
    color_threshold=cut_height,
    above_threshold_color="#6B7280",
    no_labels=True,
    ax=ax,
)
ax.axhline(cut_height, color="#E45756", linestyle="--", linewidth=2, label="cut for K = 3")
ax.set(title="Ward agglomerative hierarchy", xlabel="Individual samples", ylabel="Merge level")
ax.legend()
plt.show()

实验 06「凝聚型层次聚类」结果 1(图 2)

实验07

DBSCAN

Python · Jupyter

DBSCAN:非凸簇识别、参数选择与噪声诊断

本实验用带噪声的双弯月数据展示完整的 DBSCAN 分析流程:

  1. 检查数据并进行尺度标准化;
  2. 选择 ε 与 MinPts;
  3. 区分核心点、边界点和噪声点,并汇总最终簇;
  4. 用内部指标、外部指标和噪声识别指标评价结果;

最后将结果与 K-means 对照,说明密度聚类对非凸结构的优势。

import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib
configure_matplotlib()
from matplotlib.lines import Line2D
from matplotlib.patches import Circle
import numpy as np
import pandas as pd
from sklearn.cluster import DBSCAN, KMeans
from sklearn.datasets import make_moons
from sklearn.metrics import adjusted_rand_score, silhouette_score
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

plt.style.use("seaborn-v0_8-white")
SINGLE_FIGSIZE = (8, 5.5)
COLORS = ["#4C78A8", "#F28E2B", "#59A14F", "#B279A2"]
RANDOM_STATE = 42

1. 构造含离群点的非凸数据

主体样本由两个相互嵌套的弯月形簇组成,再加入均匀分布的人工噪声。这样的数据不满足近似球形簇假设,适合检验 DBSCAN 的密度连通能力。

X_signal, y_signal = make_moons(
    n_samples=520, noise=0.075, random_state=RANDOM_STATE
)
rng = np.random.default_rng(RANDOM_STATE)
X_noise = rng.uniform(low=[-1.7, -1.2], high=[2.7, 1.7], size=(45, 2))
X = np.vstack([X_signal, X_noise])
y_true = np.concatenate([y_signal, np.full(len(X_noise), -1)])

data_summary = pd.DataFrame(
    {
        "quantity": [len(X), len(X_signal), len(X_noise), X.shape[1]],
        "value": [len(X), len(X_signal), len(X_noise), X.shape[1]],
    },
    index=["total samples", "signal samples", "injected noise", "features"],
)[["value"]]
display(data_summary)

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
ax.scatter(X_signal[:, 0], X_signal[:, 1], s=22, alpha=0.72, color="#4C78A8", label="signal")
ax.scatter(X_noise[:, 0], X_noise[:, 1], s=34, marker="x", linewidth=1.4, color="#6B7280", label="injected noise")
ax.set(title="Raw non-convex data with injected noise", xlabel="Feature 1", ylabel="Feature 2")
ax.legend()
plt.show()
value
total samples 565
signal samples 520
injected noise 45
features 2

实验 07「DBSCAN」结果 1(图 1)

2. 标准化与 k-distance 诊断

先将每个特征标准化。取 MinPts = 4,再计算每个样本到第 4 个近邻的距离。绘制 k-distance 曲线来选择 ε。

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
min_samples = 4
neighbors = NearestNeighbors(n_neighbors=min_samples).fit(X_scaled)
distances, _ = neighbors.kneighbors(X_scaled)
k_distances = np.sort(distances[:, -1])
chosen_eps = 0.20

fig, axes = plt.subplots(1, 2, figsize=(13, 4.8), constrained_layout=True)
axes[0].scatter(X_scaled[:, 0], X_scaled[:, 1], s=20, alpha=0.7, color="#4C78A8")
axes[0].set(title="Standardized samples", xlabel="Standardized feature 1", ylabel="Standardized feature 2")
axes[1].plot(np.arange(1, len(k_distances) + 1), k_distances, color="#4C78A8", linewidth=2)
axes[1].axhline(chosen_eps, color="#E45756", linestyle="--", linewidth=2, label=f"candidate eps = {chosen_eps:.2f}")
axes[1].set(title=f"Sorted {min_samples}-distance curve", xlabel="Samples sorted by distance", ylabel=f"Distance to neighbor {min_samples}")
axes[1].legend()
plt.show()

实验 07「DBSCAN」结果 1(图 2)

3. 拟合最终模型并区分点类型

core_sample_indices_ 给出核心点。获得簇标签但不在该集合中的样本是边界点;标签为 −1 的样本是噪声点。下图同时呈现最终簇、三类点以及一个核心点的 ε-邻域。

dbscan = DBSCAN(eps=chosen_eps, min_samples=min_samples)
labels = dbscan.fit_predict(X_scaled)
core_mask = np.zeros(len(X_scaled), dtype=bool)
core_mask[dbscan.core_sample_indices_] = True
noise_mask = labels == -1
border_mask = (~core_mask) & (~noise_mask)
cluster_ids = sorted(set(labels) - {-1})

type_summary = pd.Series(
    np.select(
        [core_mask, border_mask, noise_mask],
        ["core", "border", "noise"],
        default="unknown",
    ),
    name="point_type",
).value_counts().rename_axis("point type").to_frame("count")
cluster_summary = pd.Series(labels[~noise_mask]).value_counts().sort_index().rename_axis("cluster").to_frame("samples")
display(type_summary)
display(cluster_summary)

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE, constrained_layout=True)
for cluster_id in cluster_ids:
    cluster_color = COLORS[cluster_id % len(COLORS)]
    cluster_core = (labels == cluster_id) & core_mask
    cluster_border = (labels == cluster_id) & border_mask
    ax.scatter(X_scaled[cluster_core, 0], X_scaled[cluster_core, 1], s=30, color=cluster_color, alpha=0.82)
    ax.scatter(X_scaled[cluster_border, 0], X_scaled[cluster_border, 1], s=48, facecolors="none", edgecolors=cluster_color, linewidth=1.5)
ax.scatter(X_scaled[noise_mask, 0], X_scaled[noise_mask, 1], s=42, marker="x", color="#4B5563", linewidth=1.4)
reference_index = dbscan.core_sample_indices_[len(dbscan.core_sample_indices_) // 3]
reference_point = X_scaled[reference_index]
ax.add_patch(Circle(reference_point, chosen_eps, fill=False, linestyle="--", linewidth=2, edgecolor="#ed3a3a"))
ax.annotate("one eps-neighborhood", reference_point, xytext=(18, 22), textcoords="offset points", color="#ed3a3a", arrowprops={"arrowstyle": "->", "color": "#ed3a3a"})
legend_items = [
    Line2D([0], [0], marker="o", color="none", markerfacecolor="#4C78A8", markeredgecolor="#4C78A8", label="core point"),
    Line2D([0], [0], marker="o", color="none", markerfacecolor="none", markeredgecolor="#4C78A8", label="border point"),
    Line2D([0], [0], marker="x", color="#4B5563", linestyle="none", label="noise point"),
]
ax.legend(handles=legend_items, loc="upper right")
ax.set(title="DBSCAN result: clusters and point types", xlabel="Standardized feature 1", ylabel="Standardized feature 2")
plt.show()
count
point type
core 526
noise 34
border 5
samples
cluster
0 264
1 267

实验 07「DBSCAN」结果 1(图 3)

4. 评价聚类与噪声识别

使用 Silhouette 和 ARI 进行模型评价。

true_noise = y_true == -1
true_positive_noise = np.sum(noise_mask & true_noise)
noise_precision = true_positive_noise / max(noise_mask.sum(), 1)
noise_recall = true_positive_noise / true_noise.sum()
non_noise = ~noise_mask
evaluation = pd.Series(
    {
        "clusters": len(cluster_ids),
        "noise samples": int(noise_mask.sum()),
        "noise rate": noise_mask.mean(),
        "silhouette (non-noise)": silhouette_score(X_scaled[non_noise], labels[non_noise]),
        "ARI (all samples)": adjusted_rand_score(y_true, labels),
        "noise precision": noise_precision,
        "noise recall": noise_recall,
    },
    name="value",
).to_frame()
evaluation.round(3)
value
clusters 2.000
noise samples 34.000
noise rate 0.060
silhouette (non-noise) 0.376
ARI (all samples) 0.944
noise precision 0.941
noise recall 0.711

5. 与 K-means 对照

kmeans_labels = KMeans(n_clusters=2, n_init=20, random_state=RANDOM_STATE).fit_predict(X_scaled)
comparison = pd.DataFrame(
    {
        "DBSCAN": [adjusted_rand_score(y_true, labels), silhouette_score(X_scaled[non_noise], labels[non_noise]), int(noise_mask.sum())],
        "K-means": [adjusted_rand_score(y_true, kmeans_labels), silhouette_score(X_scaled, kmeans_labels), 0],
    },
    index=["ARI", "Silhouette on assigned samples", "noise samples"],
)
display(comparison.round(3))

fig, axes = plt.subplots(1, 2, figsize=(13, 4.8), sharex=True, sharey=True, constrained_layout=True)
for cluster_id in cluster_ids:
    mask = labels == cluster_id
    axes[0].scatter(X_scaled[mask, 0], X_scaled[mask, 1], s=22, alpha=0.78, color=COLORS[cluster_id])
axes[0].scatter(X_scaled[noise_mask, 0], X_scaled[noise_mask, 1], s=36, marker="x", color="#4B5563")
axes[0].set_title("DBSCAN")
for cluster_id in np.unique(kmeans_labels):
    mask = kmeans_labels == cluster_id
    axes[1].scatter(X_scaled[mask, 0], X_scaled[mask, 1], s=22, alpha=0.78, color=COLORS[cluster_id])
axes[1].set_title("K-means")
for ax in axes:
    ax.set(xlabel="Standardized feature 1", ylabel="Standardized feature 2")
plt.show()
DBSCAN K-means
ARI 0.944 0.400
Silhouette on assigned samples 0.376 0.472
noise samples 34.000 0.000

实验 07「DBSCAN」结果 1(图 4)

观察

DBSCAN 在这个问题下能更好的恢复类别。

注意 K-menas 的 Silhouette 值比 DBSCAN 高,这提示我们不止得看聚类指标的具体值,分析时也需要结合具体的聚类图形。

实验08

OPTICS

Python · Jupyter

OPTICS:多密度结构、可达距离图与多尺度簇提取

本实验用三个密度水平明显不同的二维簇和人工噪声,展示一套完整的 OPTICS 分析流程:

  1. 标准化数据并诊断局部密度差异;
  2. 拟合 OPTICS;
  3. 用 reachability plot 识别谷地,并以 ξ 方法提取簇;
  4. 检查 MinPts、ξ 和最小簇规模的敏感性;
  5. 汇总簇规模、噪声比例、内部指标和外部验证指标。
import matplotlib.pyplot as plt
from figure_settings import configure_matplotlib
configure_matplotlib()
import numpy as np
import pandas as pd
from sklearn.cluster import OPTICS
from sklearn.datasets import make_blobs
from sklearn.metrics import adjusted_rand_score, silhouette_score
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import StandardScaler

plt.style.use("seaborn-v0_8-white")
SINGLE_FIGSIZE = (8, 5.5)
COLORS = ["#4C78A8", "#F28E2B", "#59A14F", "#B279A2", "#76B7B2"]
RANDOM_STATE = 42

1. 构造不同密度的簇

三个簇的样本量和标准差分别不同。最左侧簇最紧密,中间簇密度居中,右侧簇最稀疏;此外加入均匀噪声。

X_signal, y_signal = make_blobs(
    n_samples=[160, 220, 280],
    centers=[(-6, -1), (0, 0), (6, 2)],
    cluster_std=[0.25, 0.60, 1.10],
    random_state=RANDOM_STATE,
)
rng = np.random.default_rng(RANDOM_STATE)
X_noise = rng.uniform(low=[-9, -5], high=[9, 5], size=(45, 2))
X = np.vstack([X_signal, X_noise])
y_true = np.concatenate([y_signal, np.full(len(X_noise), -1)])
X_scaled = StandardScaler().fit_transform(X)

data_summary = pd.DataFrame(
    {
        "samples": [160, 220, 280, 45],
        "generating std": [0.25, 0.60, 1.10, np.nan],
    },
    index=["dense cluster", "medium cluster", "sparse cluster", "injected noise"],
)
display(data_summary)

fig, axes = plt.subplots(1, 2, figsize=(13, 4.9), constrained_layout=True)
for cluster_id in range(3):
    mask = y_signal == cluster_id
    axes[0].scatter(X_signal[mask, 0], X_signal[mask, 1], s=22, alpha=0.75, color=COLORS[cluster_id], label=f"signal group {cluster_id + 1}")
axes[0].scatter(X_noise[:, 0], X_noise[:, 1], s=34, marker="x", color="#6B7280", linewidth=1.3, label="injected noise")
axes[0].set(title="Raw variable-density data", xlabel="Feature 1", ylabel="Feature 2")
axes[0].legend(fontsize=8)
axes[1].scatter(X_scaled[:, 0], X_scaled[:, 1], s=22, alpha=0.72, color="#4C78A8")
axes[1].set(title="Standardized samples used by OPTICS", xlabel="Standardized feature 1", ylabel="Standardized feature 2")
plt.show()
samples generating std
dense cluster 160 0.25
medium cluster 220 0.60
sparse cluster 280 1.10
injected noise 45 NaN

实验 08「OPTICS」结果 1(图 1)

2. 诊断局部密度差异

取 MinPts = 10,计算每个样本到第 10 个近邻的距离。距离越小表示达到相同邻居数量所需的半径越小,即局部密度越高。

min_samples = 10
neighbor_model = NearestNeighbors(n_neighbors=min_samples).fit(X_scaled)
neighbor_distances, _ = neighbor_model.kneighbors(X_scaled)
k_distance = neighbor_distances[:, -1]
density_diagnostic = pd.DataFrame(
    {
        "median 10-distance": [np.median(k_distance[y_true == label]) for label in [0, 1, 2, -1]],
        "90% 10-distance": [np.quantile(k_distance[y_true == label], 0.90) for label in [0, 1, 2, -1]],
    },
    index=["dense cluster", "medium cluster", "sparse cluster", "injected noise"],
)
display(density_diagnostic.round(3))

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
distance_groups = [k_distance[y_true == label] for label in [0, 1, 2, -1]]
boxes = ax.boxplot(distance_groups, patch_artist=True, showfliers=False, medianprops={"color": "#111827", "linewidth": 1.5})
for patch, color in zip(boxes["boxes"], COLORS[:3] + ["#9CA3AF"]):
    patch.set_facecolor(color)
    patch.set_alpha(0.65)
ax.set_xticklabels(["dense", "medium", "sparse", "noise"])
ax.set(title="Local-density contrast via 10-distance", xlabel="Generating group (diagnostic only)", ylabel="Distance to neighbor 10")
plt.show()
median 10-distance 90% 10-distance
dense cluster 0.040 0.078
medium cluster 0.083 0.176
sparse cluster 0.144 0.266
injected noise 0.624 1.484

实验 08「OPTICS」结果 1(图 2)

3. 拟合 OPTICS 并读取可达距离图

采用 MinPts = 10、ξ = 0.07 和最小簇规模 5%。max_eps = np.inf 表示不预先截断邻域尺度。OPTICS 先产生样本排序,再用 ξ 方法识别 reachability plot 中相对陡峭的下降与上升边界。

chosen_xi = 0.07
chosen_min_cluster_size = 0.05
optics = OPTICS(
    min_samples=min_samples,
    max_eps=np.inf,
    xi=chosen_xi,
    min_cluster_size=chosen_min_cluster_size,
    cluster_method="xi",
).fit(X_scaled)
labels = optics.labels_
ordering = optics.ordering_
ordered_reachability = optics.reachability_[ordering]
ordered_labels = labels[ordering]
finite_reachability = ordered_reachability[np.isfinite(ordered_reachability)]
reachability_cap = np.quantile(finite_reachability, 0.99)
plot_reachability = np.minimum(ordered_reachability, reachability_cap)
cluster_ids = sorted(set(labels) - {-1})

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
positions = np.arange(len(X_scaled))
for cluster_id in cluster_ids:
    mask = ordered_labels == cluster_id
    ax.bar(positions[mask], plot_reachability[mask], width=1.0, color=COLORS[cluster_id % len(COLORS)], label=f"cluster {cluster_id + 1}")
noise_ordered = ordered_labels == -1
ax.bar(positions[noise_ordered], plot_reachability[noise_ordered], width=1.0, color="#9CA3AF", label="noise")
ax.set(title="OPTICS reachability plot with xi extraction", xlabel="OPTICS ordering", ylabel="Reachability distance (99% capped)")
ax.legend(ncol=4, fontsize=8)
plt.show()

实验 08「OPTICS」结果 1(图 3)

4. 检查参数敏感性

下面联合改变 MinPts 与 ξ,固定最小簇规模为 5%。调参时主要查看簇数、噪声比例和非噪声样本的 Silhouette;ARI 依赖真实标签,只作为模拟实验的事后验证。ξ 越小,算法越容易把较小的局部下降识别成独立簇,也更容易产生碎片化。

min_samples_values = [8, 10, 15, 20]
xi_values = [0.03, 0.05, 0.07, 0.09]
rows = []
for current_min_samples in min_samples_values:
    for current_xi in xi_values:
        current_labels = OPTICS(
            min_samples=current_min_samples,
            max_eps=np.inf,
            xi=current_xi,
            min_cluster_size=chosen_min_cluster_size,
            cluster_method="xi",
        ).fit_predict(X_scaled)
        current_non_noise = current_labels != -1
        current_clusters = len(set(current_labels) - {-1})
        current_silhouette = np.nan
        if current_clusters >= 2 and current_non_noise.sum() > current_clusters:
            current_silhouette = silhouette_score(X_scaled[current_non_noise], current_labels[current_non_noise])
        rows.append(
            {
                "MinPts": current_min_samples,
                "xi": current_xi,
                "clusters": current_clusters,
                "noise_rate": (~current_non_noise).mean(),
                "silhouette_non_noise": current_silhouette,
                "ARI_all": adjusted_rand_score(y_true, current_labels),
            }
        )
sensitivity = pd.DataFrame(rows)
display(sensitivity.query("MinPts == 10").set_index("xi").round(3))

fig, axes = plt.subplots(1, 3, figsize=(15, 4.5), constrained_layout=True)
for current_min_samples in min_samples_values:
    part = sensitivity[sensitivity["MinPts"] == current_min_samples]
    axes[0].plot(part["xi"], part["clusters"], marker="o", label=f"MinPts={current_min_samples}")
    axes[1].plot(part["xi"], part["noise_rate"], marker="o")
    axes[2].plot(part["xi"], part["silhouette_non_noise"], marker="o")
axes[0].legend(fontsize=8)
for ax, title, ylabel in zip(axes, ["Extracted clusters", "Noise rate", "Silhouette on assigned samples"], ["Clusters", "Noise fraction", "Silhouette"]):
    ax.axvline(chosen_xi, color="#111827", linestyle="--", linewidth=1.2)
    ax.set(title=title, xlabel="xi", ylabel=ylabel, xticks=xi_values)
plt.show()
MinPts clusters noise_rate silhouette_non_noise ARI_all
xi
0.03 10 4 0.658 0.734 0.132
0.05 10 3 0.340 0.677 0.603
0.07 10 3 0.035 0.640 0.945
0.09 10 3 0.037 0.641 0.947

实验 08「OPTICS」结果 1(图 4)

5. 最终聚类结果与评价

最终图同时给出 ξ 提取的簇和噪声点。Silhouette 只在已分配样本上计算;整体 ARI 将真实噪声和预测噪声都视为标签的一部分;signal-only ARI 只在真实非噪声样本上计算,并仍会惩罚被错误标为噪声的主体样本。噪声 precision 与 recall 则单独评价异常点识别。

noise_mask = labels == -1
non_noise = ~noise_mask
true_noise = y_true == -1
signal_mask = ~true_noise
true_positive_noise = np.sum(noise_mask & true_noise)
cluster_summary = pd.Series(labels[non_noise]).value_counts().sort_index().rename_axis("cluster").to_frame("samples")
cluster_summary.index = [f"cluster {index + 1}" for index in cluster_summary.index]
display(cluster_summary)
evaluation = pd.Series(
    {
        "clusters": len(cluster_ids),
        "noise samples": int(noise_mask.sum()),
        "noise rate": noise_mask.mean(),
        "silhouette (non-noise)": silhouette_score(X_scaled[non_noise], labels[non_noise]),
        "ARI (all samples)": adjusted_rand_score(y_true, labels),
        "ARI (true signal only)": adjusted_rand_score(y_true[signal_mask], labels[signal_mask]),
        "noise precision": true_positive_noise / max(noise_mask.sum(), 1),
        "noise recall": true_positive_noise / true_noise.sum(),
    },
    name="value",
).to_frame()
display(evaluation.round(3))

fig, ax = plt.subplots(figsize=SINGLE_FIGSIZE)
for cluster_id in cluster_ids:
    mask = labels == cluster_id
    ax.scatter(X_scaled[mask, 0], X_scaled[mask, 1], s=25, alpha=0.78, color=COLORS[cluster_id % len(COLORS)], label=f"cluster {cluster_id + 1}")
ax.scatter(X_scaled[noise_mask, 0], X_scaled[noise_mask, 1], s=40, marker="x", color="#4B5563", linewidth=1.3, label="noise")
ax.set(title="OPTICS xi extraction on variable-density data", xlabel="Standardized feature 1", ylabel="Standardized feature 2")
ax.legend(fontsize=8)
plt.show()
samples
cluster 1 166
cluster 2 223
cluster 3 291
value
clusters 3.000
noise samples 25.000
noise rate 0.035
silhouette (non-noise) 0.640
ARI (all samples) 0.945
ARI (true signal only) 0.997
noise precision 0.960
noise recall 0.533

实验 08「OPTICS」结果 1(图 5)