在这篇博客中,我将分享我和队友Aron、Ashish、Gabriel共同完成的第一个机器学习项目的经历。撰写这篇博客的初衷是记录我作为一名数据科学家的成长历程,并阐述逐步优化预测模型的过程。我的目标是创建一个可以快速应用的通用工作流程,因此尽量简化推理过程。最终,我希望在回顾这个数据集时,能够利用更好的预测模型,看到自己原本可以做出的改进,并见证自己的成长。
该数据集来自Kaggle,包含79个描述房屋特征的维度,涵盖了爱荷华州埃姆斯几乎所有房屋的信息。这个数据集旨在为有志于机器学习的人士提供一个实践案例。处理这个数据集的主要教训是:简单的线性模型也可以非常强大,并且在适当的情况下,它们的表现甚至优于复杂模型。接下来的文章将详细介绍我们处理数据集时遵循的工作流程,并论证为何线性模型应始终存在于工具箱中。
我们首先将目标变量(房价)转换为符合正态分布且不含异常值的变量。这样做是为了确保目标变量的残差符合正态分布(这是线性模型的基本假设),并且去除异常值可以避免模型结果因极端值而产生偏差。以下是通过对数转换(我们使用了手动Box-Cox变换)的例子:
在上面的图片中,我们可以看到未经处理的数据高度倾斜。而在下方的图片中,我们展示了经过对数转换后的数据。通过对比可以看出,数据在分布上的显著改善。
在这里,我们并未对各个特征进行转换,以使其变为正态分布。尽管这样做可能会让机器学习模型受益,但会损害结果模型的可解释性。因此,我们选择了去除异常值的方法。下面是删除某个变量异常值的效果:
左边是未处理的数据,右边是处理过的数据。去除异常值的效果很明显,因为拟合线明显偏移。
在第二步中,我们花了大量时间寻找缺失值,并进行插补。插补方法需要对每个特征有深入了解。无论是使用均值、中位数、众数、零、空值,还是简单地删除观测值或特征本身,都取决于我们认为可接受的标准。这些标准很多时候依靠直觉。以下是关于缺失值的总结:
顶部显示的是每个特征的缺失值数量,底部显示的是缺失值之间的相关性。
对于缺失率较高的变量(一般指缺失值超过95%的变量),可以直接丢弃。而对于缺失率较低的变量(如缺失值少于5%的观测数据),如果是连续变量,则选择使用均值插补;如果是分类变量,则使用众数插补。插补的目的是确保插补的数据不会改变拟合斜率,从而不影响模型结果。尽管这种方法可能有缺陷,但在缺失值较少的情况下,便捷性往往比准确性更重要。为了使插补过程更加准确,可以使用基于k近邻或其他机器学习模型的方法。另一种常见的插补方法是用一个非常边缘的数(如-999,前提是所有观测值都是正实数)。但这种方法不适用于拟合解析方程的推理模型。
我们经常听到维度诅咒。高维度可能导致共线变量,进而导致拟合系数不准确以及高方差。高维度也可能意味着稀疏数据或特征过多,从而导致过拟合。这两种情况都不理想,因为它们都会产生性能较差的模型。
第一次特征选择的目的是减少多重共线性。方法是进行相关性研究,同时合并或删除特征。以下是处理前后相关图的对比:
左侧是原始数据的相关图,右侧是处理后数据的相关图,其中部分特征被删除或合并。
通过对比可以看出,多重共线性(用深蓝色表示)大幅降低。这是通过删除或合并特征实现的。我们基于对特征R平方值的持续评估来做出决策:
左侧图显示,与居住面积相关的变量(最后五分之一到最后三分之一)的R平方值都大于0.8(大致等于VIF的5)。在右侧图中,适当合并特征后,与居住面积相关的R平方值有所降低。
类别型变量的子类别可以聚类在一起。下面是一个例子:
在这个图中,我们可以看到所有的不规则子类别(IR1到IR3)的均值都非常接近,但离规则的(Reg)很远。这是一个提示,在简化维度后,我们应该把所有不规则类别(IR)聚在一起。
在这个特定例子中,我们发现,将所有不规则类别(IR1到IR3)聚为一大类可能会对模型产生积极影响。这是因为哑变量处理后,特征空间会相对较小。聚类过程不是手动完成的,而是使用K-means聚类,根据与目标变量相关的变量对子类别进行聚类(在这个数据集中,我们使用变量Gr living area)。
特征工程可以通过交互完成,这些交互可以表现为任意两个或多个特征的某种算术运算。例如,乘法和加法在最终模型结果中会产生巨大差异。我们得出的一个结论是:每个值必须始终符合变量的自然物理单位。例如,如果要合并车库数量和车库面积,应该通过乘法而不是加法来合并。在这种情况下,加法是没有物理意义的。实际上,测试结果表明,对两个变量进行乘法会导致VIF显著下降,而加法则不会。
另一个值得注意的变量是每个社区的表现:
不同的社区拥有不同的销售价格。每个社区都值得拥有自己的模型。
通过观察社区图,我们可以看到每个社区的表现都是有区别的,并且每个社区都遵循明确的行为模式。这些社区都有自己的形式。为了实现这一点,我们创建了一个类似于开关的交互参数,方法是将哑变量处理过的社区类别乘以Gr living area。这样,每个社区都可以有自己的系数,而不是简单的截距偏移。使用这个特征处理方法,我们的Kaggle排名有所下降。
我们的流水线总结如下:
数据集被分割成训练集和测试集,然后训练集被发送到五个模型:三个线性模型(Lasso、岭回归、弹性网络)和两个非线性模型(随机森林、梯度提升)。每个模型进行了广泛的网格搜索,以选择最佳超参数。在最优超参数条件下,应用模型对测试集进行预测,并对测试结果进行比较。以下是初始特征工程的总结:
除了显示的特征工程外,我们还尝试了许多类型的特征工程和选择(从数据集A开始,到数据集C,这些特征工程被依次完成)。虽然我们自己得到的MSE测试分数并不总是与Kaggle排名一致,但所有这些尝试都导致了更差的Kaggle排名。以下是使用数据集A到D的结果:
从图中可以看出,弹性网络模型相对于其他模型有显著优势。所有线性模型都优于非线性树模型。这很好地验证了我们一开始提到的观点:线性模型总有一席之地。在这个特定数据集中,目标变量与其他特征主要表现出线性关系,这使得我们有充分理由使用线性模型而非非线性模型。综上所述,即使我们使用线性模型(较简单的模型),我们的Kaggle和MSE分数仍有望提高。我们之所以这样说的原因如下:
左侧是测试和训练数据集MSE,右侧是随机森林的数据集MSE。两者都显示出过拟合的迹象。
上图显示,测试和训练数据集MSE分数之间存在显著差异。对于树模型来说,这可能是有意义的,因为树模型倾向于过拟合(当然,使用随机森林的目的是为了避免这个问题);但是,对线性模型进行惩罚应该可以缓解这个问题……但实际情况并非如此。这意味着我们确实可以改进特征选择和处理。然而,我们尝试了大量的特征选择操作,同时也考虑了基于特征重要性的排名,但它们都给出了负面反馈。
这个图显示了Lasso(左)和随机森林(右)的特征重要性。需要注意的是,这两个模型的特征重要性是不同的。随机森林模型比Lasso模型更强调连续变量的重要性,因为高基数会导致更大的误差或熵降。我们通过测试比较了标签编码和One-Hot编码,发现它们产生了相似的结果(标签编码略优),这就是标签编码比One-Hot编码更重要的原因。
鉴于特征的无用性,我们选择通过递归去除特征来蛮力改进模型。这个想法如下:
左侧的图表显示了我们递归删除特征的过程。右侧显示了特征被逐渐删除时的MSE变化。突然的跳转会可能是由于一个重要特征被删除了。
特征的最佳数量由测试误差突然跳跃的位置来表示。通过这种递归方法,我们可以进一步提高我们的MSE分数:
我们可以看到,通过机器递归删除特征是十分有效的。在递归删除特征后,误差得分显著下降。
最后,我们选择通过集成所有不同的模型,把所有的东西放在一起。我们是这样做的:
这张图展示了我们使用的集成技术,它被称为堆叠。
集成技术只是不同模型预测值的线性组合。不同模型的权重是从最小化测试集错误分数的权重集中选取的。在将最终结果提交给Kaggle后,我们的最终分数是0.1214。
作为我们的第一个机器学习项目,我们学到了很多。首先,也是最重要的一点,我们亲眼见证了线性模型的力量。这是我们预期的理想结果。其次,也是更深层次的教训,我们看到了人类直觉的局限性。长时间的无效特征工程也给我们留下了深刻的印象。在处理这些问题时,我们应该在人类直觉和依赖机器之间找到平衡。我们花费了太多时间研究数据集,试图找出哪些数据在统计上是重要的或不重要的,并在删除特征时犹豫不决。如果我们果断地处理这些操作,它们会非常有益。但问题是,这些EDA和统计测试的结论从来不是非黑即白的——它们很少产生可操作的反馈。我们应该更快速地研究线性和非线性模型给出的特征重要性,并在与随机哑变量比较重要性时,更快地进行研究。同时,我们应该花更多的时间研究如何执行相关特征子集的PCA。尽管我们在人工特征处理方面做了很多努力,但最终我们还是遭遇了多重共线性的困扰。我们需要更明智地使用机器学习技术。因此,我们从中得到的教训是不言而喻的。当然,我们下次会做得更好。