ITPub博客

首页 > 人工智能 > 人工智能 > 梯度提升二三事:怎么来自定义损失函数?

梯度提升二三事:怎么来自定义损失函数?

原创 人工智能 作者:李佳惠 时间:2018-10-09 09:49:41 0 删除 编辑

介绍

梯度提升技术在工业中得到了广泛的应用,并赢得了许多Kaggle比赛。(https://github.com/Microsoft/LightGBM/blob/master/examples/README.md#machine-learning-challenge-winning-solutions)互联网已经有很多关于梯度提升的很好的解释,但我们注意到关于自定义损失函数的信息缺乏:比如原因,时间和方式。这篇文章是我们尝试总结自定义损失函数在许多实际问题中的重要性,以及如何使用LightGBM梯度提升包实现它们。

梯度提升二三事:怎么来自定义损失函数?


常见的ML库中有许多常用的损失函数。如果你想进一步了解这方面的知识,请阅读这篇文章,这是Prince在攻读数据科学硕士时写的。(https://heartbeat.fritz.ai/5-regression-loss-functions-all-machine-learners-should-know-4fb140e9d4b0)在现实世界中,这些"现成的"损失函数通常无法很好地适应我们试图解决的业务问题。输入自定义损失函数。

自定义损失函数

梯度提升二三事:怎么来自定义损失函数?


使用自定义损失函数的一个例子是机场准时的风险不对称。问题是决定何时离开家,以便在合适的时间到达机场。我们不想太早离开,在机场等几个小时。与此同时,我们更不想错过我们的航班。任何一方的损失都是非常不同的:如果我们提前到达,情况真的不是那么糟糕;如果我们来得太晚而错过航班,那真的很糟糕。如果我们使用机器学习来决定何时离开房子,我们可能希望直接在我们的模型中处理这种风险不对称,通过使用自定义损失函数来"惩罚"晚期错误而不是早期错误。

另一个常见的例子是分类问题。例如,对于疾病检测,我们可能认为假阴性比假阳性要严重得多,因为给予健康人的药物通常比未治疗病人的危害小。在这种情况下,我们可能希望优化F-beta评分,其中β取决于我们想要给予误报的权重大小。这有时被称为Neyman-Pearson标准。

在Manifold,我们最近遇到了一个需要自定义损失函数的问题。我们的客户之一Cortex Building Intelligence提供的应用程序可帮助工程师更精确地操作建筑物供暖,通风和空调(HVAC)系统。大多数商业建筑物具有"租赁义务",以在工作日期间的工作时间内将建筑物室内温度调节在"舒适"温度范围内,例如在上午9点至下午6点期间在华氏70和74度之间。与此同时,HVAC是建筑物的最大运营成本。高效HVAC运行的关键是在不需要时关闭系统,如夜间,并在清晨再次开启以满足"租赁义务"。为此,Manifold帮助Cortex建立了一个预测模型,以建议在建筑物中打开HVAC系统的确切时间。

然而,错误预测的惩罚是不对称的。如果我们预测的启动时间早于实际所需的启动时间,那么建筑物将过早地达到舒适的温度并且会浪费一些能量。但是如果预测的时间晚于实际所需的开始时间,那么建筑物的温度就会升温较晚,租户也不会感到高兴——没有人想在冰冷的建筑物中工作,购物或学习。因此,晚开比早开更糟糕,因为我们不希望租户不开心。我们通过创建自定义非对称Huber损失函数将业务知识编码到我们的模型中,当残差为正与负时,该函数具有更高的误差。有关此问题的更多详细信息,请参阅此文章。(https://www.manifold.ai/blog/data-science-at-cortex)

梯度提升二三事:怎么来自定义损失函数?


结论:找到一个与你的业务目标紧密匹配的损失函数。通常,这些损失函数在流行的机器学习库中没有默认的实现。没关系:定义自己的损失函数并用它来解决你的问题并不难。

自定义训练损失和验证损失

在进一步讨论之前,让我们在定义中明确一点。ML文献中使用了许多术语来指代不同的东西。我们将选择一组我们认为最清晰的定义:

·训练损失。这是在训练数据上进行优化的函数。例如,在神经网络二进制分类器中,这通常是二进制交叉熵。对于随机森林分类器,这是基尼系数。训练损失通常也称为"目标函数"。

·验证损失。这是我们用来评估我们训练模型在看不见的数据上的性能的函数。这通常与训练损失不同。例如,在分类器的情况下,这通常是接收器工作特性(ROC)曲线下的区域——尽管这从未直接优化,因为它是不可微分。这通常称为"性能或评估指标"。

在许多情况下,自定义这些损失对于构建更好的模型非常有效。这对于梯度提升特别简单,如下所示。

训练损失

在训练期间优化训练损失。很难为某些算法自定义,比如随机森林(见这里:https://github.com/scikit-learn/scikit-learn/issues/3071),但对其他算法来说比较容易,比如梯度提升和神经网络。因为梯度下降的一些变体通常是优化方法,所以训练损失通常需要一个具有凸梯度(一阶导数)和海森(二阶导数)的函数。它最好是连续的,有限的和非零的。最后一个很重要,因为函数为零的部分可以冻结梯度下降。

在梯度提升的背景下,训练损失是使用梯度下降优化的函数,例如梯度提升模型的"梯度"部分。具体地,训练损失的梯度用于改变每个连续树的目标变量。(如果你对更多细节感兴趣,请参阅此文章。https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d)请注意,即使训练损失定义了"梯度",每个树仍然使用与此自定义损失函数无关的贪婪分割算法生长。

定义自定义训练损失通常需要我们做一些微积分来找到梯度和海森。正如我们接下来将要看到的,首先更改验证损失更容易一些,因为它不需要那么多的开销。

验证损失

验证损失用于调整超参数。它通常更容易自定义,因为它没有像训练损失那样多的功能要求。验证损失可以是非凸的,不可微分的和不连续的。因此,从自定义开始通常是一个更容易的地方。

例如,在LightGBM中,一个重要的超参数是boosting的数量。验证损失可用于找到最佳数量的boosting次数。 LightGBM中的验证损失称为eval_metric。我们可以使用库中可用的验证损失之一,也可以定义我们自己的自定义函数。(https://github.com/Microsoft/LightGBM/blob/master/docs/Parameters.rst/#metric-parameters)由于它非常简单,如果它对你的业务问题很重要,那么你一定要自定义。

具体而言,我们通常使用early_stopping_rounds变量,而不是直接优化num boosting的轮数。当给定数量的早期停止轮次的验证损失开始增加时,它会停止提升。实际上,它通过监视样本外验证集的验证损失来防止过度拟合。如下图所示,设置更高的停止轮次会导致模型运行更多boosting 轮次。

梯度提升二三事:怎么来自定义损失函数?


蓝色:训练损失。橙色:验证损失。训练和验证都使用相同的自定义损失功能

梯度提升二三事:怎么来自定义损失函数?


k折交叉验证。每个测试折叠都有验证损失

请记住,验证策略也非常重要。上面列出的训练/验证是许多可能的验证策略之一。它可能不适合你的问题。其他包括k-fold交叉验证和嵌套交叉验证,我们在HVAC启动时建模问题中使用了这些验证。

如果适用于业务问题,我们希望使用自定义函数来进行训练和验证损失。在某些情况下,由于自定义损失的函数形式,可能无法将其用作训练损失。在这种情况下,仅更新验证损失并使用像MSE这样的默认训练损失可能是有意义的。你仍然可以获益,因为超参数将使用所需的自定义损失进行调整。

在LightGBM中实现自定义损失功能

让我们来看看它在实践中的样子,并对模拟数据进行一些实验。首先,让我们假设过高估计比低估更糟糕。另外,假设平方损失是我们在任一方向上的误差的良好模型。为了对其进行编码,我们定义了一个自定义MSE函数,它对正残差的惩罚比负残差多10倍。下图说明了我们的自定义损失函数与标准MSE损失函数的对比情况。

梯度提升二三事:怎么来自定义损失函数?


根据定义,非对称MSE很好,因为它具有易于计算的梯度和海森度,如下图所示。请注意,海森在两个不同的值上是常量,左边是2,右边是20,尽管在下面的图中很难看到。

梯度提升二三事:怎么来自定义损失函数?


LightGBM提供了一种直接的方式来实现自定义训练和验证损失。其他梯度提升包,包括XGBoost和Catboost,也提供此选项。这是一个Jupyter笔记本(https://github.com/manifoldai/mf-eng-public/blob/master/notebooks/custom_loss_lightgbm.ipynb),展示了如何实现自定义训练和验证损失功能。细节在笔记本中,但在较高的层次上,实现略有不同:

  • 训练损失:在LightGBM中自定义训练损失需要定义一个函数,该函数包含两个数组,即目标及其预测。反过来,该函数应该返回每个观察的两个梯度和海森的数组。如上所述,我们需要使用微积分来导出梯度和海森,然后在Python中实现它。

  • 验证损失:自定义LightGBM中的验证损失需要定义一个函数,该函数接受相同的两个数组,但返回三个值:一个字符串,其名称为metric的字符串,损失本身,以及关于更高是否更好的布尔值。

用于在LightGBM中实现自定义损失的代码

梯度提升二三事:怎么来自定义损失函数?


定义自定义验证和训练损失功能

梯度提升二三事:怎么来自定义损失函数?


梯度提升二三事:怎么来自定义损失函数?


梯度提升二三事:怎么来自定义损失函数?


在LightGBM中结合训练和验证损失(包括Python和scikit-learn API示例)

自定义损失函数的实验

Jupyter笔记本还对默认随机森林,默认LightGBM和MSE以及LightGBM与自定义训练和验损失函数进行了深入比较。(https://github.com/manifoldai/mf-eng-public/blob/master/notebooks/custom_loss_lightgbm.ipynb)我们使用Friedman 1合成数据集,进行了8,000次训练观察,2,000次验证观察和5,000次测试观察。验证集用于查找优化验证损失的最佳超参数集。下面报告的分数在测试观察结果上进行评估,以评估我们模型的普遍性。

我们已经完成了下表中总结的一系列实验。请注意,我们关心的最重要的分数是非对称MSE,因为它明确定义了我们的不对称惩罚问题。

梯度提升二三事:怎么来自定义损失函数?


我们的实验和结果

让我们详细看一些比较。

随机森林→LightGBM

使用默认设置,LightGBM在此数据集上的性能优于Random Forest。随着更多树和超参数的更好组合,随机森林也可能会给出好的结果,但这不是重点。

LightGBM→LightGBM,具有自定义的训练损失

这表明我们可以使我们的模型优化我们关心的内容。默认的LightGBM正在优化MSE,因此它可以降低MSE损失(0.24 vs 0.33)。具有自定义训练损失的LightGBM优化了非对称MSE,因此对于非对称MSE(1.31 vs. 0.81)表现更好。

LightGBM→LightGBM使用MSE调整早期停止轮次

两种LightGBM模型都在优化MSE。我们看到默认的MSE分数有了很大改善,只需稍微调整一下使用早期停止轮次(MSE:0.24 vs 0.14)。因此,我们应该让模型使用早期停止超参数来确定最佳提升次数,而不是将提升次数限制为默认值(即100)。超参数优化很重要!

LightGBM使用MSE→LightGBM调整早期停止轮次,并使用自定义MSE进行早期停止调整

这两个模型的得分非常接近,没有实质性差异。这是因为验证损失仅用于决定何时停止提升。梯度是在两种情况下优化默认MSE。每个后续树为两个模型生成相同的输出。唯一的区别是具有自定义验证损失的模型在742次增强迭代时停止,而其他的则运行多次。

LightGBM使用自定义MSE→LightGBM通过自定义损失进行调整,并使用MSE进行早期停止调整

仅在不改变验证损失的情况下自定义训练损失会损害模型性能。只有自定义训练损失的模型比其他情况增加了更多轮次(1848)。如果我们仔细观察,这个模型的训练损失非常低(0.013)并且在训练集上高度过度拟合。每个梯度提升迭代都是使用训练误差作为目标变量来创建新树,但仅当验证数据的损失开始增加时,提升才会停止。当模型开始过度拟合时,验证损失通常开始增加,这是停止构建更多树的信号。在这种情况下,由于验证和训练损失彼此不一致,因此模型似乎没有"得到消息"而导致过度拟合。这个配置只是为了完整而包含在内,并不是人们在实践中应该使用的。

LightGBM具有经过调整的早期停止轮次,MSE→LightGBM训练自定义训练损失,并通过自定义验证损失调整早期停止轮次最终模型使用自定义训练和验证损失。它通过相对较少的提升迭代次数给出最佳的非对称MSE分数。损失与我们关心的一致!

让我们仔细看看残差直方图以获得更多细节。

梯度提升二三事:怎么来自定义损失函数?

不同模型预测残差的直方图。

请注意,使用LightGBM(即使使用默认的超参数),与随机森林模型相比,预测性能也有所提高。具有自定义验证损失的最终模型似乎在直方图的右侧进行更多预测,即实际值大于预测值。 这是由于不对称的自定义损失函数的缘故。使用残差的核密度图可以更好地可视化残差的右侧偏移。

梯度提升二三事:怎么来自定义损失函数?


LightGBM模型的预测与对称和非对称评估的比较

结论

没有哪个模型是完美无缺的,但许多商业问题并没有不平等的对待低估和过高估。有时,我们有意地希望我们的模型将错误偏向某个方向,但这具体取决于哪些错误的成本更高。因此,我们不应该限制自己使用普通ML库中的"现成"对称损失函数。

LightGBM提供了一个简单的界面,可以合并自定义训练和验证损失函数。在适当的时候,我们应该利用这个功能来做出更好的预测。但是,你不应立即跳转到使用自定义损失函数。最好采用精益的、迭代的方法,首先从像随机森林这样的简单基线模型开始。在下一次迭代中,你可以移动更复杂的模型,如LightGBM,并进行超参数优化。只有在这些基线稳定后,才有必要进行自定义验证和训练损失函数。

更多推荐读物

如果你不清楚一般梯度提升的工作原理,我建议你阅读如何解释Terence Parr的梯度提升(http://explained.ai/gradient-boosting/index.html),以及Prince从头开始的梯度提升(https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d)。

关于如何在不同的GBM框架中调整超参数,有很多文章。如果你想使用其中一个软件包,可以花些时间了解要搜索的超参数范围。 LightGBM GitHub问题可以大致了解要使用的值范围。(https://github.com/Microsoft/LightGBM/issues/695) Aarshay Jainhas写了一篇关于调整XGBoost和sklearn梯度提升的博客。(https://www.analyticsvidhya.com/blog/2016/03/complete-guide-parameter-tuning-xgboost-with-codes-python/)

要了解哪种梯度提升包适合你的情况,请阅读Alvira Swalin的CatBoost vs. Light GBM vs. XGBoost(https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db),以及Pranjan Khandelwal的哪种算法取得冠军:Light GBM vs XGBOOST?。(https://www.analyticsvidhya.com/blog/2017/06/which-algorithm-takes-the-crown-light-gbm-vs-xgboost/)


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31545819/viewspace-2215639/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-09-19

  • 博文量
    81
  • 访问量
    90395