老齐教室

如何生成机器学习的预测区间

如何生成机器学习的预测区间

——使用梯度上升回归来显示机器学习模型评估的不确定性

https://towardsdatascience.com/how-to-generate-prediction-intervals-with-scikit-learn-and-python-ab3899f992ed

作者:Will Koehrsen

翻译:老齐


“所有的模型都是错误的,有的却很有用” —— George Box。

我们用机器学习进行预测时,要记住这句至理名言。所有的机器学习模型都有局限性:对结果有影响的特征并没有在数据中,或者模型所做的假设是与现实不相符的。当我们给出一个精确的预测数字——房子将价值450300.01美元——这给人的印象是我们确信模型源于现实。

要诚实地反映模型的预测性能,必须通过一系列的评估手段,对于预测结果,或许有一个最佳值,但实际上更可能是一个很宽的区间。在数据科学课程中,这并不是老生常谈,但至关重要的是,我们要在预测中把不确定性找出来,不要过度宣传机器学习的能力。虽然人们渴望确定性,但我认为,与其给出一个远离现实的精确值,不如给出一个包含真实值的宽泛预测区间。

在本文中,我们将介绍一种在Scikit-Learn中生成不确定区间的方法。生成预测区间是数据科学工具箱中的另一个工具,对于赢得非数据科学家的信任至关重要。

本文完整代码已经发布在本公众号的在线平台“机器学习案例”课程中,请关注公众号,并回复“姓名+手机号+‘案例’”,即可申请加入。详细请参阅本公众号的菜单:“课程-开放课程”的详细介绍。

问题

在文中的案例,我们将使用来自DrivenData(https://drivendata.org/)的机器学习竞赛的数据,这个数据是关于能源问题的真实数据。此数据集有8个特征,其中的样本是每隔15分钟测量一次所得(**译者注:** 此数据集已经上传到在线平台,并且经过适当的数据清洗,便于读者学习使用)。

1
data.head()

本项目的目的是要预测能源消耗情况,这是我们在Cortex Building Intel每天都要做的一项实际任务。毫无疑问,从数据集中没有发现影响能源消耗的隐藏特征(潜在变量),因此,我们希望通过预测能源使用的上限和下限来显示模型评估中的不确定性。

1
2
3
4
# Use plotly + cufflinks for interactive plotting
import cufflinks as cf

data.resample('12 H').mean().iplot()

在本文中,作者使用的可视化工具是plotly,关于这个工具,译者有专门课程介绍,请参阅:《案例上手Python数据可视化》

实施

为了在Scikit-Learn中生成预测区间,我们将使用梯度上升回归算法,基本思路如下:

  1. 预测下限,使用GradientBoostingRegressor(loss= "quantile", alpha=lower_quantile)。其中,lower_quantile代表下限。比如说,第10个百分位数是0.1。
  2. 预测上限,使用GradientBoostingRegressor(loss= "quantile", alpha=upper_quantile)。其中,upper_quantile代表上限。比如,第90个百分位数是0.9。
  3. 预测中间值,使用GradientBoostingRegressor(loss="quantile", alpha=0.5),或者使用预测均值的默认loss="ls"(用于最小二乘法)。官方文档的示例使用后一种方法,我们也这么做。

GradientBoostingRegressor中的参数loss的值是优化模型的函数。如果loss="quantile",并将alpha的值也设置为百分位(类似上面预测上下限那样),就能够得到以百分位表示的预测区间。

在将数据分解成训练集和测试集之后,建立模型。实际上,我们必须使用3个单独的梯度上升回归模型,因为每个模型都有一个不同的优化函数,必须单独训练。

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.ensemble import GradientBoostingRegressor

# Set lower and upper quantile
LOWER_ALPHA = 0.1
UPPER_ALPHA = 0.9

# Each model has to be separate
lower_model = GradientBoostingRegressor(loss="quantile", alpha=LOWER_ALPHA)

# The mid model will use the default loss
mid_model = GradientBoostingRegressor(loss="ls")
upper_model = GradientBoostingRegressor(loss="quantile", alpha=UPPER_ALPHA)

Training and predicting uses the familiar Scikit-Learn syntax:

在训练和预测中使用熟悉的Scikit-Learn语法:

1
2
3
4
5
6
7
8
9
10
11
12
# Fit models
lower_model.fit(X_train, y_train)
mid_model.fit(X_train, y_train)
upper_model.fit(X_train, y_train)

# Record actual values on test set
predictions = pd.DataFrame(y_test)

# Predict
predictions['lower'] = lower_model.predict(X_test)
predictions['mid'] = mid_model.predict(X_test)
predictions['upper'] = upper_model.predict(X_test)

就是这样,我们得到了预测区间!

只要有一点Plotly技能,我们就可以创造出一个很好的互动图形。

译者注:Plotly擅长制作交互式的可视化图像,具体请参阅:《案例上手Python数据可视化》

计算预测误差

与任何机器学习模型一样,我们希望以量化的方式得到针对测试集的预测误差(我们有实际答案)。评价预测区间的误差要比评估某个具体预测值的误差复杂复杂一些。我们可以计算实际值占预测区间时间上的百分比,但如果让预测区间变大,就很容易被认为预测很好,因此,我们还需要一个度量标准来用于预测值与真实值之间的差距,例如绝对误差。

在源码中,我提供了一个函数,计算上限、下限和中间值的绝对误差,预测区间的上限为所有上限绝对误差的平均值,下限为所有下限绝对误差的平均值,再绘制下图所示箱线图。

有趣的是,对于这个模型,下限预测的中值绝对误差实际上小于预测的中间值的绝对误差。该模型精度不高,可以通过调整优化函数提高。真实值在上下限之间,刚好超过时间的一半。我们可以通过降低下四分位和提高上四分位数来增加度量范围,但这样做会让精度降低。

可能有更好的评估标准,但我选择这些标准是因为它们计算简单、易于解释。你使用的实际评估标准应该取决于你试图解决的问题和你的目标。

预测区间模型

用3个独立的模型进行训练和预测有些繁琐,因此我们可以编写一个模型,将梯度上升回归算法包装成一个类。它来自Scikit-Learn,因此我们使用相同的语法进行训练或预测,但现在只需要调用一次:

1
2
3
4
5
6
# Instantiate the class
model = GradientBoostingPredictionIntervals(lower_alpha=0.1, upper_alpha=0.9)

# Fit and make predictions
_ = model.fit(X_train, y_train)
predictions = model.predict(X_test, y_test)

该模型还带有一些绘图工具:

1
2
fig = model.plot_intervals(mid=True, start='2017-05-26', stop='2017-06-01')
iplot(fig)

请使用和调整模型直到你认为合适!这只是进行不确定性预测的一种方法,但我认为它很有用,因为它使用了Scikit-Learn(意味着一个平滑的学习曲线),我们可以根据需要对其进行扩展。一般来说,这是解决数据科学问题的好方法:从简单的解决方案开始,只根据需要增加复杂性!

背景:分位数损失与梯度上升回归

梯度上升回归算法是一个集成模型,由决策树回归树组成。对于该模型的最初解释,参见Friedman于1999年发表的论文:《Greedy Function Approximation: A Gradient Boosting Machine》(https://statweb.stanford.edu/~jhf/ftp/trebst.pdf)。与随机森林的并行训练不同,梯度提升算法按照次序进行训练,每棵树都从当前集合的错误(残差)中学习。树对于模型的贡献是通过最小化损失函数和训练集中的实际目标来确定的。

使用默认损失函数——最小二乘法——梯度上升回归模型预测平均值。要理解的关键点是,最小二乘法中对低误差和高误差的惩罚是一样的:

相比之下,分位数损失根据分位数和误差的正(实际>预测)负(实际<预测)惩罚误差,这使得梯度上升模型优化不是针对平均值,而是针对百分位数。分位数损失为:

其中α是分位数。让我们快速浏览一个示例,例子中使用实际值10以及分位数0.1和0.9:

  1. 如果α = 0.1,预测 = 15,那么损失 = (0.1–1)* (10–15)= 4.5
  2. 如果α = 0.1,预测 = 5,那么损失 = 0.1 *(10–5)= 0.5
  3. 如果α = 0.9,预测 = 15,那么损失 = (0.9–1)* (10–15)= 0.5
  4. 如果α = 0.9,预测 = 5,那么损失 = 0.9 *(10–5)= 4.5

对于< 0.5的分位数,如果预测值大于实际值(情况1),则损失将大于距实际值相同距离的预测值。对于> 0.5分位数,如果预测值小于实际值(情况4),则损失将大于实际值相同距离的预测值。分位数== 0.5时,高于和低于实际值的预测值将导致相同的误差,模型将对中值进行优化。(对于中间状态,我们可以使用loss=“quantile”alpha=0.5作为中值,或者使用loss=“ls”作为平均值)。

下图中说明了分位数损失和误差的关系:

分位数<0.5使预测低于中值,分位数>0.5使预测高于中值。这是一个很棒的提醒,机器学习中的损失函数决定了你要优化的内容!

根据我们想要的输出,可以优化平均值(最小二乘法)、中值(α == 0.5的分位数损失)或任何百分位数(α == 百分位数/ 100 的分位数损失)。这是分位数损失的一个相对简单的解释,但它足以让你开始使用模型训练生成预测区间。要获取更多信息,请阅读本文并查看源代码(译者注: 已经发布到本公众号“老齐教室”的在线实验平台)。

结论

用机器学习模型预测,如果只得到一个精确的数字,会给人一种错觉:我们对这个模型信心百倍。然而,一定要牢记,任何模型所得的都是一个近似值,所以,我们在评估时非常有必要表达不确定性,常用的一种方法就是用Scikit-Learn中的梯度上升回归生成预测区间。这只是预测范围(参见线性回归的置信区间)的一种方法,但它相对简单,可以根据需要进行调整。在本文中,我们看到了一个完整的实现,并学习了分位数损失函数背后的一些理论。

解决数据科学问题需要在工具箱中有许多可以按需使用的工具。生成预测区间是一种有用的技术,我建议你练习执行本文的代码,并用它处理你的问题。(学习任何技巧的最佳方法都是在实践中学!)我们知道机器学习可以做一些非常不可思议的事情,但它并不完美,我们也不应该把它描绘成完美的。为了获得决策者的信任,我们通常不需要给出一个数字作为评估结果,而是需要一个预测范围来表示所有模型固有的不确定性。

关注微信公众号:老齐教室。读深度文章,得精湛技艺,享绚丽人生。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能