2019-CCF-BDCI-Competition-Review

2019-CCF-BDCI-Competition-Review

比赛简介

我们选取的赛题是离散制造过程中典型工件的质量符合率预测,赛题背景为:

在高端制造领域,随着数字化转型的深入推进,越来越多的数据可以被用来分析和学习,进而实现制造过程中重要决策和控制环节的智能化,例如生产质量管理。从数据驱动的方法来看,生产质量管理通常需要完成质量影响因素挖掘及质量预测、质量控制优化等环节,本赛题将关注于第一个环节,基于对潜在的相关参数及历史生产数据的分析,完成质量相关因素的确认和最终质量符合率的预测。在实际生产中,该环节的结果将是后续控制优化的重要依据。

赛题的任务是:

由于在实际生产中,同一组工艺参数设定下生产的工件会出现多种质检结果,所以我们针对各组工艺参数定义其质检标准符合率,即为该组工艺参数生产的工件的质检结果分别符合优、良、合格与不合格四类指标的比率。相比预测各个工件的质检结果,预测该质检标准符合率会更具有实际意义。

本赛题要求参赛者对给定的工艺参数组合所生产工件的质检标准符合率进行预测。

简单来说,对于给定的生产工艺参数,生产出来的工件会有相应的质量数据,以及根据质量数据进行质检得到的质检结果。

数据集简介

给定的训练集有 6000 条训练数据,每条数据有:

  • Parameter 1 - 10:生产工艺参数;
  • Attribute 1 - 10:工件的质量数据;
  • Quality Label {Fail, Pass, Good, Excellent}:工件对应的质检指标。

测试集中只有 Parameter 1 - 10,需要预测对应的 Quality label。特别地,测试集包括 120 组数据,每组包含 50 条样本,最终提交时只需明确每组中各个类别数据所占的比例(比如:{Fail: 25%, Pass: 25%, Good: 25%, Excellent: 25%}),这一定程度上放松了对预测准确性的约束,允许更多的容错。

解题思路

观察数据集和测试集所给的数据特征,我们考虑了两种思路:

  1. 由于测试集中没有 Attribute,直接使用 Parameter 预测 Quality Label。这样的思路比较直接,调参过程会比较简单,同时也从某种意义上避免了误差传播的风险(具体会在后面提及),缺点是没有充分利用到数据集中给出的数据,可能出现过拟合等瓶颈;
  2. 建立两阶段模型:首先通过 Parameter 回归预测 Attribute,然后通过 Attribute 分类 Quaility Label。这种思路充分利用了数据集中的有效数据,从理论和实验上看,Attribute 到 Quaility Label 有非常明确的对应关系(用给定的 Attribute 预测 Quaility Label 准确率可以接近 97%),但这样有误差传播的风险,如果回归预测不准确,那么会很大程度上影响到之后分类的结果。同时,在后面我们也发现,其实我们的两阶段思路是大体上正确的,但同时也存在一个比较大的问题:按照工业界的实际意义,Parameter 到 Attribute 不应该被建模为一个回归问题。实际上在工业生产当中,给定工艺参数 $\textbf{c}$ 之后,得到的质量数据应该是从概率分布 $P(\textbf{x}|\textbf{c})$ 采样得到的 $\textbf{x}$,而不是一个一一对应的回归预测,所以这也是这种思路最后效果没有达到预期的重要原因。

数据预处理和特征工程

观察到原始 Parameter 数据有一些右偏的现象,我们选择对Parameter 取 log 以更好地观察数据的特征。

image-20200511222035589

Parameter 1 - 4 的数据分布近似呈正态连续分布,Parameter 7 - 9 呈现明显的离散分布,Parameter 5, 6, 10 有明显的单峰与多峰。

为了进一步分析 Parameter 和 Quality Label 之间的关系,我们画出按照 Quality Label 标记颜色的 Parameter 散点图:

分析以上5幅图片,我们发现, Parameter 1 - 4 的按照Quality Label 标记颜色的散点图基本上呈现随机分布, 同时通过对不同特征选取的尝试, 我们判断使用 Parameter 1 - 4 对最终 Quality Label 的预测没有太大贡献,因此舍去 Parameter 1 - 4。另外分析了剩下的参数的相关系数之后,由于 Parameter 5 和 Parameter 6 之间的相关系数接近 1(接近线性关系),为了简化模型,我们也去掉了 Parameter 5,剩下使用 Parameter 6 - 10 用作 Parameter 特征集。

对 Attribute 做相同的分析处理,最终选择比较具有区分性的 Attribute 4 - 10 用作 Attribute 特征集。

模型构建

我们决定使用第二个思路进行建模。根据实验以及提交成绩,我们最终选用 XGBoost 进行回归预测,CatBoost 进行分类预测。大致的预测模型结构及流程如下:

注意,分类时使用的是预测出来的 attribute 进行训练。虽然使用原始 attribute 预测 Label 的准确度非常高,但由于测试集并没有给我们 attribute,我们肯定会使用根据 parameter 预测出来的 attribute 来预测 label,所以为了保证数据分布一致,我们的分类器也必须使用训练集 parameter 预测出来的 attribute 进行训练。

共同确定了模型结构之后,我的队友使用 Grid Search 以及手动调参的方法确定了超参数,我们的分数比 baseline model 有明显提升。

Boosting 原理

目前的数据科学比赛中很大一部分算法都是使用 CatBoost 和 XGBoost 等基于 Boosting 开源库实现的。当训练数据和训练时间都有限的情况下,使用 Boosting 算法可能会收获比神经网络(训练需要的数据量较大)更好的效果。由于我们在比赛中也使用了 CatBoost 和 XGBoost,这里来简单探讨一下 Boosting 算法的原理。

集成学习的主要思路是使用多个学习器结合,希望获取能够比单一学习器更强的泛化性能,目前的方法大概分为两种,一种是 bagging,另一种是 boosting。Bagging 的个体学习器之间不存在很强的依赖关系,能够同时生成,可以并行化;而 boosting 的个体学习器之间存在比较强的依赖关系,必须串行生成。

Boosting 是一种可以将一组弱学习器(泛化性能略优于随机猜测的学习器)提升为一个强学习器的算法。大体思路是先从初始训练集中训练处一个基学习器,在根据基学习器的表现对训练样本分布进行调整,使得做错的训练样本受到更多的关注,然后基于调整后的训练样本来训练下一个基学习器,反复进行直到基学习器数目到达指定的值,最后将这些基学习器进行加权组合得到最后的强学习器。常用的算法为 AdaBoost 和 GradientBoost。

AdaBoost

AdaBoost 算法的推导过程有多种,这里采用加性模型的方法。希望找到最终的学习器 $H(x) = \sum\limits_{t=1}^{T}\alpha_th_t(x)$ 来最小化指数损失函数:

这里考虑二分类问题,$y = -1 / 1$,那么:

其实可以进一步推出 $P(y=1|x) = \frac{e^{2F(x)}}{1+e^{2F(x)}}$,这和逻辑回归的模型非常相似,只是系数多了一个 2.

迭代产生下一个学习器时,之前的学习器不变,引入新的学习器以希望纠正之前学习器的错误。设引入的学习器及其权重为 $h(x), c$,那么新的学习器为 $H(x) + ch(x)$,使用泰勒展开近似新学习器的误差:

所以:

希望找到 $h(x)$ 最小化加权期望,令 $c>0$,那么优化目标等价于:

最优解为:

我们需要进一步找到 $c$:

令 $\epsilon$ 为错分类的样本加权和,那么:

可以看出错误率越低,该分类器加权越高。下一轮迭代之前需要更新权重:

可以看出,对于预测正确的样本,权重会变小,预测错误的样本,权重增加。

综上,AdaBoost 的本质思想就是进行迭代,第 m 次迭代的目标为:

另外还有一些训练中常用的手段:

  • Shrinkage:每个基学习器乘一个系数(大于 0 小于 1),使得对最终模型的贡献减小,防止过拟合,也称为学习率;
  • Early Stopping:在验证集上的准确率下降到一定阈值以下时就停止训练,防止过拟合;
  • Weight Trimming:提高训练速度的一种手法,每一轮学习中可能只有小部分样本权重较大,其他大部分权重小的样本对训练的影响比较小。Weight Trimming 的思想是每一轮迭代中只使用高权重的样本,例如权重占比前 90% 的样本。每一轮计算之后所有样本的权重依然会被重新计算,所以之前被舍弃的样本仍然可能被重新用于训练。这样可以加速训练过程。

GradientBoost

指数损失对异常点比较敏感,对异常点会有特别大的惩罚,因此过拟合的风险比较大,鲁棒性比较差。而 AdaBoost 的结论几乎都是建立在损失函数为指数损失的基础上,所以有人提出了 Gradient Boost,使得可以使用任意连续可导的损失函数,这样可以使用一些更鲁棒的损失函数,增强泛化能力。

Boosting 的基本思想是通过某种方式使得每一轮基学习器在训练过程中更加关注上一轮学习错误的样本,区别在于是采用何种方式。AdaBoost 采用的是增加上一轮学习错误样本的权重的策略,而在 Gradient Boosting 中则将负梯度作为上一轮基学习器犯错的衡量指标,在下一轮学习中通过拟合负梯度来纠正上一轮犯的错误。

使用决策树作为基学习器的 Gradient Boost 方法称为 GBDT,和上面一样最终的模型可以看成是加性模型:

损失函数:

后面的一项是正则化项。

每次新加入的学习器,都是以减小之前学习器产生的误差为目标。考察第 t 步的拟合,这一步模型对第 i 个样本的预测为 $\hat{y}_i^t = \hat{y}_i^{t-1}+f_t(x_i)$,目标函数为:

使用泰勒展开至二阶:

其中,$g_i, h_i$ 分别为损失函数对 $\hat{y}_i^{t-1}$的一阶导数和二阶导数。

去掉对没有影响的表达式,优化目标变为:

上面提到,我们的弱学习器使用的是决策树。假设新加入的决策树有 $T$ 片叶子,每片叶子对应的值为 $w\in R^T$,在分类问题中,每片叶子预测值是某一类;在回归问题中,每片叶子预测的是某一个值,那么存在函数 $q$,可以将 $f_t(x)$ 的预测值映射到一个叶子节点上,即 $f_t(x) = w_{q(x)}$,$q(x)$ 代表样本在哪个节点上,$w$ 是其取值。

决策树的复杂度被定义为 $\Omega\left(f_{t}\right)=\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}$,即跟叶节点的数量和取值有关,设 $I_j=\{i|q(x_i) = j\}$,那么目标可以写为:

记 $G_j = \sum\limits_{i \in I_{j}}g_i, H_j=\sum\limits_{i \in I_{j}}h_i$ 那么目标函数可以变为 $O b j^{(t)}=\sum_{j=1}^{T}\left[G_j w_{j}+\frac{1}{2}\left(H_j+\lambda\right) w_{j}^{2}\right]+\gamma T$

对于一棵确定结构的决策树,即已知 $q$,那么 $G_j, H_j$ 是确定的,现在 $w$ 未知,也就是我们需要预测的值,那么令目标函数的一阶导为 0,得到:

这时目标函数值为:

如何选择树的结构以及获得最优化的目标函数?

那么对于单棵决策树,一种理想的优化状态就是枚举所有可能的树结构,因此过程如下:

a、首先枚举所有可能的树结构,即 $q$ ;

b、计算每种树结构下的目标函数值,即等式7的值;

c、取目标函数最小(大)值为最佳的数结构,根据等式6求得每个叶子节点的 $w$ 取值,即样本的预测值。

但上面的方法肯定是不可行的,因为树的结构千千万,所以一般用贪心策略来优化:

a、从深度为0的树开始,对每个叶节点枚举所有的可用特征

b、 针对每个特征,把属于该节点的训练样本根据该特征值升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的最大收益(采用最佳分裂点时的收益)

c、 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,把该节点生长出左右两个新的叶节点,并为每个新节点关联对应的样本集

d、回到第1步,递归执行到满足特定条件为止

那么如何计算上面的收益呢,很简单,仍然紧扣目标函数就可以了。假设我们在某一节点上二分裂成两个节点,分别是左(L)右(R),则分列前的目标函数是 $-\frac{1}{2}\left[\frac{\left(G_{L}+G_{R}\right)^{2}}{H_{L}+H_{R}+\lambda}\right]+\gamma$ ,分裂后则是 $-\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda} + \frac{G_{R}^{2}}{H_{R}+\lambda}\right]+\gamma$ ,则对于目标函数来说,分裂后的收益是(这里假设是最小化目标函数,所以用分裂前-分裂后):

等式8计算出来的收益,也是作为变量重要度输出的重要依据。

综上,GBDT 算法的大概过程为:

  1. 每一步都生成一个新的决策树;
  2. 拟合决策树之前,计算出损失函数在每个样本上的一阶导和二阶导 $g_i, h_i$;
  3. 通过贪心策略生成一棵新的决策树,计算每个叶子节点的 $G_j, H_j$,并预测 $w$;
  4. 把新生成的决策树加入总的学习器,预测结果变为 $\hat{y}_i^t = \hat{y}_i^{t-1}+\epsilon f_t(x_i)$,$\epsilon$ 为学习率,抑制过拟合。

但其实上面所述的是 XGBoost 的原理,严格的 GBDT 只展开到一阶导数,不过其实大同小异。

Gradient Boosting 算法理论上可以选择多种不同的学习算法作为基学习器,但实际使用地最多的无疑是决策树,这并非偶然。决策树有很多优良的特性,比如能灵活处理各种类型的数据,包括连续值和离散值;对缺失值不敏感;不需要做特征标准化/归一化;可解释性好等等,但其致命缺点是不稳定,导致容易过拟合,因而很多时候准确率不如其他算法。

决策树是非参数模型,这并不意味着其没有参数,而是在训练之前参数数量是不确定的,因此完全生长的决策树有着较大的自由度,能最大化地拟合训练数据。然而单颗决策树是不稳定的,样本数相同的训练集产生微小变动就能导致最终模型的较大差异,即模型的方差大,泛化性能不好。集成学习的另一代表 Bagging 是对付这个问题的一大利器 (Bagging 降低方差,Boosting 降低偏差) 。而 Bagging 的拓展算法 —— 随机森林,通过在树内部结点的分裂过程中,随机选取固定数量的特征纳入分裂的候选项,这样就进一步降低了单模型之间的相关性,总体模型的方差也比 Bagging 更低。

由于 GBDT 采用的树都是复杂度低的树,所以方差很小(简单模型的方差较小,偏差可能较大;复杂模型的方差较大,偏差较小),通过梯度提升的方法集成多个决策树,最终能够很好的解决过拟合的问题。然而 Boosting 共有的缺点为训练是按顺序的,难以并行,这样在大规模数据上可能导致速度过慢,所幸近年来 XGBoost 和 LightGBM 的出现都极大缓解了这个问题。

各种实现的一些特点

在特征选择(决策树的节点分裂)部分,比较常用的方法是 pre-sorting splitting:

  • 对于每个节点,枚举所有特征;
  • 所有数据按照该特征排序;
  • 遍历所有可能的分裂值,选取 gain 最高作为分裂点;
  • 选择最佳分类点(所有 feature 中 gain 最高的)。

这样做的时间复杂度非常高,所以 XGBoost 使用的是基于直方图的分裂算法,大致思路是将连续的 feature 划分到若干个不同的桶中(离散化),每个桶中累加桶中样本对应的一阶和二阶梯度等信息(建立直方图)。由于离散化之后的桶的数量远远小于样本的数量,所以之后根据建立的直方图进行分裂的效率会有很大提升。

直方图算法可以减少内存的占用,计算效率有很大提升,但特征被离散化之后往往不能找到精确的分裂点,所以对结果会有一定影响。但因为决策树本身就是弱模型,分割点不准确对最终结果的影响有限,并且粗略的分割点有一定正则化的效果,可以防止过拟合。

LightGBM 在此基础上又做了改进。AdaBoost 中,样本的权重向量很好地反映了样本的重要性,而 GBDT 中,没有直接的权重反应样本的重要程度。但梯度可以间接反应样本的误差(梯度越大,误差越大)。要减少样本数来加速,一个直接的想法是抛弃那些梯度很小的样本,但是这样训练集的分布会被改变,可能会使得模型准确率下降。LightGBM 提出 Gradient-based One-Side Sampling (GOSS) 来解决这个问题。GOSS 按照样本的梯度降序排序,选择前 $a\times100\%$ 的样本,剩下的数据 $(1-a)\times100\%$ 中选取 $b\times100\%$ 的样本(计算增益时梯度放大 $\frac{1-a}{b}$ 倍),用这些样本可以在尽量保持原先分布的基础上减少训练样本,更加关注误差较大的样本。

LightGBM 等实现都使用了 Exclusive Feature Bunding 算法。一个高维的特征空间的数据往往是稀疏的,而稀疏的特征空间中,很多特征是互斥的(不会同时具有非零值,例如 One-hot 编码),LightGBM 提出使用 EFB 算法进行互斥特征合并减少特征数量。CatBoost 在这方面也是大同小异,它可以处理类别特征,也自动对输入特征进行组合。

XGBoost 中,树是按层生长(Level-wise tree growth)的生长策略,即同一层的所有节点都做分裂。这样容易进行多线程优化,但不加区分地对待同一层的叶子,带来了很多没有必要的开销(很多叶子分裂收益低,没有分裂的必要)。而 LightGBM 使用 Leaf-wise tree growth,每次只分裂增益最大的一个叶子,分裂次数相同的情况下 Leaf-wise 可以降低更多的误差,得到更好的精度。但缺点是容易生长出比较深的决策树,造成过拟合,因此 LightGBM, 增加了一些最大深度的限制,保证高效率的同时防止过拟合。

通过实验,我们选择在回归问题使用 XGBoost,分类问题使用 CatBoost。

数据后处理

使用 CatBoost 预测分类问题时,得到的是属于每个样本属于各个类别的概率,所以我们考虑有以下两种后处理方法:

  1. 对每个样本得到的概率使用 argmax,得到预测的类别,统计每组中不同类别的数目,除以 50 得到每组中各个类别的概率;
  2. 对每一组,直接对我们所得的每个样本属于不同类别的概率按类别求和,之后除以 50,得到该组中四个类别出现的概率(例如:将一组中所有样本属于 Good 的概率加起来除以 50 近似得到一组中 Good 产品的比例)。这样做从某种意义上可以减轻我们预测不准带来的误差(实际上预测应该说非常不准),并且我的队友提出了进一步修正结果的取整方案,这也很大程度上帮助我们提高了比赛成绩。

取整技巧

由于每一组都是 50 个样本,那么最后结果的比例应该都是 0.02 的倍数,而使用第二种思路进行预测得到的比例是一个小数点之后有很多位的浮点数,不符合实际意义,所以需要进行“取整”操作,把我们预测的结果变为 0.02 的倍数。

简单来说,可以进行如下操作,假设 $x$ 是我们某个预测结果,那么我们需要:

这样我们就得到了取整之后的结果。最后我们尝试取整方法之后正确率有明显提升。

最后的一点思考

数据科学比赛对数据的理解和认识还是非常重要的,最终我们的预测效果不好的一个非常重要的原因是把第一阶段的模型建模成了回归模型,事实上这是一个很严重的错误,导致我们预测出来的 attribute 误差很大,最终的效果不好。考虑到实际意义,给定工艺参数之后,产品的质量数据是对分布的一个采样,而不是固定的一个点(生产过程虽然很精确,但还是存在误差),所以使用生成对抗网络可能会有更好的效果。另外训练集的数据偏少,如果使用生成对抗网络还可以考虑使用生成网络造一些数据出来帮助学习,这也是我们没有考虑到的点。

总之通过这次比赛了解了数据科学比赛的一个整体流程,学到了不少模型有关的知识,还是很有收获的。

参考文献

[1] boosting-algorithm-adaboost

[2] 集成学习之Boosting —— AdaBoost原理

[3] 机器学习-一文理解GBDT的原理-20171001

[4] 『我爱机器学习』集成学习(四)LightGBM

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020 Bowen
  • Powered by Hexo Theme Ayer

请我喝杯咖啡吧~

支付宝
微信