回归 (Regression) 是一种建模任务,涉及在给定输入的情况下预测数值。
线性回归是回归的标准算法,它假定输入和目标变量之间存在线性关系。线性回归的扩展包括在训练期间向损失函数添加惩罚项,以鼓励具有较小系数值的更简单的模型。这些扩展被称为正则化线性回归或惩罚线性回归。
弹性网络是一种流行的正则化线性回归类型,它结合了两个流行的惩罚项,即 L1 和 L2 惩罚函数。
在本教程中,您将学习如何在 Python 中开发弹性网络正则化回归。
完成本教程后,您将了解:
- 弹性网络是线性回归的扩展,它在训练期间向损失函数添加正则化惩罚项。
- 如何评估弹性网络模型,并使用最终模型对新数据进行预测。
- 如何通过网格搜索和自动方式为新数据集配置弹性网络模型。
让我们开始吧。

如何在Python中开发弹性网络回归模型
照片作者:Phil Dolby,部分权利保留。
教程概述
本教程分为三个部分;它们是:
- 弹性网络回归
- 弹性网络回归示例
- 调整弹性网络超参数
弹性网络回归
线性回归是指假定输入变量和目标变量之间存在线性关系的回归模型。
当只有一个输入变量时,这种关系是一条直线;当维度更高时,这种关系可以被认为是一个连接输入变量和目标变量的超平面。模型系数是通过一个优化过程找到的,该过程旨在最小化预测值(yhat)和期望目标值(y)之间的均方误差之和。
- loss = sum i=0 to n (y_i – yhat_i)^2
线性回归的一个问题是模型估计的系数可能很大,这使得模型对输入敏感,并且可能不稳定。对于观测值很少(样本)或样本数(n)多于输入预测变量(p)或变量(所谓的p >> n 问题)的问题,尤其如此。
解决回归模型稳定性的一种方法是修改损失函数,为具有大系数的模型增加额外的成本。在训练期间使用这些修改后的损失函数的线性回归模型统称为惩罚线性回归。
一个流行的惩罚是根据平方系数值的总和来惩罚模型。这称为 L2 惩罚。L2 惩罚最小化了所有系数的大小,尽管它阻止任何系数从模型中移除。
- l2_penalty = sum j=0 to p beta_j^2
另一个流行的惩罚是根据绝对系数值的总和来惩罚模型。这称为 L1 惩罚。L1 惩罚最小化了所有系数的大小,并允许某些系数减小到零值,从而将预测变量从模型中移除。
- l1_penalty = sum j=0 to p abs(beta_j)
弹性网络是一种在训练期间同时包含 L1 和 L2 惩罚的惩罚线性回归模型。
根据《统计学习要素》的术语,提供了一个超参数“alpha”,用于分配 L1 和 L2 惩罚项的权重。Alpha 是一个介于 0 和 1 之间的值,用于加权 L1 惩罚的贡献,而 (1 - alpha) 值用于加权 L2 惩罚。
- elastic_net_penalty = (alpha * l1_penalty) + ((1 – alpha) * l2_penalty)
例如,alpha 值为 0.5 将使每个惩罚项对损失函数的贡献为 50%。alpha 值为 0 将所有权重都赋予 L2 惩罚,值为 1 将所有权重都赋予 L1 惩罚。
alpha 参数决定了惩罚的组合,通常是基于定性标准预先选择的。
— 第 663 页,统计学习要素,2016。
好处是弹性网络可以平衡两种惩罚,在某些问题上可以比单独使用其中一种惩罚的模型获得更好的性能。
提供了另一个名为“lambda”的超参数,它控制两种惩罚之和对损失函数的加权。默认值为 1.0,表示使用完全加权的惩罚;值为 0 则排除惩罚。lambda 的值非常小,例如 1e-3 或更小,是很常见的。
- elastic_net_loss = loss + (lambda * elastic_net_penalty)
现在我们熟悉了弹性网络惩罚回归,让我们来看一个实例。
弹性网络回归示例
在本节中,我们将演示如何使用弹性网络回归算法。
首先,让我们引入一个标准的回归数据集。我们将使用住房数据集。
住房数据集是一个标准的机器学习数据集,包含 506 行数据,其中有 13 个数值输入变量和一个数值目标变量。
使用重复的 10 折交叉验证和三次重复的测试框架,朴素模型可以达到约 6.6 的平均绝对误差 (MAE)。表现最佳的模型在此相同测试框架上可达到约 1.9 的 MAE。这提供了此数据集上预期性能的界限。
该数据集涉及根据美国波士顿郊区的房屋细节来预测房价。
无需下载数据集;我们将在工作示例中自动下载它。
下面的示例下载并加载数据集为 Pandas DataFrame,并总结了数据集的形状和前五行数据。
1 2 3 4 5 6 7 8 9 10 |
# 加载和汇总住房数据集 from pandas import read_csv from matplotlib import pyplot # 加载数据集 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv' dataframe = read_csv(url, header=None) # 总结形状 print(dataframe.shape) # 总结前几行 print(dataframe.head()) |
运行示例,确认了 506 行数据、13 个输入变量和 1 个数字目标变量(共 14 个)。
我们还可以看到所有输入变量都是数字的。
1 2 3 4 5 6 7 8 9 |
(506, 14) 0 1 2 3 4 5 ... 8 9 10 11 12 13 0 0.00632 18.0 2.31 0 0.538 6.575 ... 1 296.0 15.3 396.90 4.98 24.0 1 0.02731 0.0 7.07 0 0.469 6.421 ... 2 242.0 17.8 396.90 9.14 21.6 2 0.02729 0.0 7.07 0 0.469 7.185 ... 2 242.0 17.8 392.83 4.03 34.7 3 0.03237 0.0 2.18 0 0.458 6.998 ... 3 222.0 18.7 394.63 2.94 33.4 4 0.06905 0.0 2.18 0 0.458 7.147 ... 3 222.0 18.7 396.90 5.33 36.2 [5 行 x 14 列] |
scikit-learn Python 机器学习库通过 ElasticNet 类提供了弹性网络惩罚回归算法的实现。
令人困惑的是,alpha 超参数可以通过“l1_ratio”参数设置,该参数控制 L1 和 L2 惩罚项的贡献,而 lambda 超参数可以通过“alpha”参数设置,该参数控制两种惩罚之和对损失函数的贡献。
默认情况下,“l1_ratio”使用相等的平衡值 0.5,alpha 使用完全加权值 1.0。
1 2 3 |
... # 定义模型 model = ElasticNet(alpha=1.0, l1_ratio=0.5) |
我们可以使用重复的 10 折交叉验证在房屋数据集上评估弹性网络模型,并报告数据集上的平均平均绝对误差 (MAE)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 在数据集上评估弹性网络模型 from numpy import mean from numpy import std from numpy import absolute from pandas import read_csv from sklearn.model_selection import cross_val_score from sklearn.model_selection import RepeatedKFold from sklearn.linear_model import ElasticNet # 加载数据集 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv' dataframe = read_csv(url, header=None) data = dataframe.values X, y = data[:, :-1], data[:, -1] # 定义模型 model = ElasticNet(alpha=1.0, l1_ratio=0.5) # 定义模型评估方法 cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型 scores = cross_val_score(model, X, y, scoring='neg_mean_absolute_error', cv=cv, n_jobs=-1) # 将分数强制为正数 scores = absolute(scores) print('Mean MAE: %.3f (%.3f)' % (mean(scores), std(scores))) |
运行示例,在房屋数据集上评估弹性网络算法,并报告 10 折交叉验证三次重复的平均 MAE。
鉴于学习算法的随机性,您的具体结果可能会有所不同。可以尝试运行几次示例。
在这种情况下,我们可以看到该模型达到了约 3.682 的 MAE。
1 |
平均 MAE:3.682 (0.530) |
我们可以决定将弹性网络作为最终模型,并对新数据进行预测。
这可以通过在所有可用数据上拟合模型并调用 predict() 函数来实现,传入新的数据行。
我们可以用一个完整的示例来演示这一点,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 使用弹性网络模型对数据集进行预测 from pandas import read_csv from sklearn.linear_model import ElasticNet # 加载数据集 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv' dataframe = read_csv(url, header=None) data = dataframe.values X, y = data[:, :-1], data[:, -1] # 定义模型 model = ElasticNet(alpha=1.0, l1_ratio=0.5) # 拟合模型 model.fit(X, y) # 定义新数据 row = [0.00632,18.00,2.310,0,0.5380,6.5750,65.20,4.0900,1,296.0,15.30,396.90,4.98] # 进行预测 yhat = model.predict([row]) # 总结预测 print('Predicted: %.3f' % yhat) |
运行示例,拟合模型并对新数据行进行预测。
1 |
预测值:31.047 |
接下来,我们可以看看如何配置模型超参数。
调整弹性网络超参数
我们怎么知道默认超参数 alpha=1.0 和 l1_ratio=0.5 对我们的数据集有好处呢?
我们不这样做。
相反,测试一系列不同的配置并发现最适合的方法是很好的做法。
一种方法是进行网格搜索,在 0 到 1 之间以 0.1 或 0.01 为间隔搜索 l1_ratio 值,并在对数 10 指数尺度上搜索 alpha 值,范围可能从 1e-5 到 100,以发现最适合某个数据集的配置。
下面的示例使用 GridSearchCV 类和我们定义的网格值来演示这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 网格搜索弹性网络的超参数 from numpy import arange from pandas import read_csv from sklearn.model_selection import GridSearchCV from sklearn.model_selection import RepeatedKFold from sklearn.linear_model import ElasticNet # 加载数据集 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv' dataframe = read_csv(url, header=None) data = dataframe.values X, y = data[:, :-1], data[:, -1] # 定义模型 model = ElasticNet() # 定义模型评估方法 cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1) # 定义网格 grid = dict() grid['alpha'] = [1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 0.0, 1.0, 10.0, 100.0] grid['l1_ratio'] = arange(0, 1, 0.01) # 定义搜索 search = GridSearchCV(model, grid, scoring='neg_mean_absolute_error', cv=cv, n_jobs=-1) # 执行搜索 results = search.fit(X, y) # 总结 print('MAE: %.3f' % results.best_score_) print('Config: %s' % results.best_params_) |
运行示例将使用重复交叉验证评估每种配置组合。
您可能会看到一些可以安全忽略的警告,例如
1 |
目标未收敛。您可能需要增加迭代次数。 |
鉴于学习算法的随机性,您的具体结果可能会有所不同。尝试运行几次示例。
在这种情况下,我们可以看到我们取得了比默认值 3.682 略好的结果,为 3.378。忽略符号,因为库为了优化目的会将 MAE 设为负数。
我们可以看到模型为惩罚项分配了 0.01 的 alpha 权重,并完全侧重于 L2 惩罚。
1 2 |
MAE:-3.378 配置:{'alpha': 0.01, 'l1_ratio': 0.97} |
scikit-learn 库还提供了一个内置版本的算法,可以通过 ElasticNetCV 类自动查找好的超参数。
要使用此类,首先需要用数据集进行拟合,然后用于进行预测。它将自动查找合适的超参数。
默认情况下,模型将测试 100 个 alpha 值并使用默认的 ratio。我们可以通过“l1_ratio”和“alphas”参数指定我们自己的要测试的值列表,就像我们在手动网格搜索中所做的那样。
以下示例将演示这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 使用自动配置的弹性网络算法 from numpy import arange from pandas import read_csv from sklearn.linear_model import ElasticNetCV from sklearn.model_selection import RepeatedKFold # 加载数据集 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv' dataframe = read_csv(url, header=None) data = dataframe.values X, y = data[:, :-1], data[:, -1] # 定义模型评估方法 cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1) # 定义模型 ratios = arange(0, 1, 0.01) alphas = [1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 0.0, 1.0, 10.0, 100.0] model = ElasticNetCV(l1_ratio=ratios, alphas=alphas, cv=cv, n_jobs=-1) # 拟合模型 model.fit(X, y) # 总结选择的配置 print('alpha: %f' % model.alpha_) print('l1_ratio_: %f' % model.l1_ratio_) |
鉴于学习算法的随机性,您的具体结果可能会有所不同。尝试运行几次示例。
同样,您可能会看到一些可以安全忽略的警告,例如
1 |
目标未收敛。您可能需要增加迭代次数。 |
在这种情况下,我们可以看到选择了 alpha 值为 0.0,从损失函数中移除了两个惩罚项。
这与我们通过手动网格搜索找到的结果不同,可能是因为配置的搜索或选择方式不同。
1 2 |
alpha:0.000000 l1_ratio_:0.470000 |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
书籍
API
文章
总结
在本教程中,您学习了如何在 Python 中开发弹性网络正则化回归。
具体来说,你学到了:
- 弹性网络是线性回归的扩展,它在训练期间向损失函数添加正则化惩罚项。
- 如何评估弹性网络模型,并使用最终模型对新数据进行预测。
- 如何通过网格搜索和自动方式为新数据集配置弹性网络模型。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
尊敬的Jason博士,
再次感谢您的指导性教程。
我有一个关于 MAE 的问题,请耐心听我讲。
在相关性中,最小和最大相关性介于 -1 和 1 之间。我们知道相关性的边界。如果值为 0.85,则表示强正相关。同样,相关性为 -0.85 也表示强负相关。
在 MAE 中,我们看到一个数字。MAE 没有上限和下限。是否存在理想的 MAE?
谢谢你,
悉尼的Anthony
是的,理想的 MAE 是 0.0(零误差)。
好的 MAE 相对于朴素模型而言
https://machinelearning.org.cn/faq/single-faq/how-to-know-if-a-model-has-good-performance
尊敬的Jason博士,
感谢您对上述页面“如何知道模型是否表现良好”的关注。
从主题来看,“……当我们谈论模型技能是相对的而不是绝对的时候,它是相对于基线方法的技能……”
也就是说,您需要将 MAE 与基线模型进行比较:也就是说,您需要将其与两个模型进行比较。
问题:那么“基线”模型的定义是什么?
谢谢你,
悉尼的Anthony
每种问题类型的基线模型定义在此列出
https://machinelearning.org.cn/faq/single-faq/how-to-know-if-a-model-has-good-performance
对于回归,预测平均值,或使用这个
https://scikit-learn.cn/stable/modules/generated/sklearn.dummy.DummyRegressor.html
尊敬的Jason博士,
谢谢你
悉尼的Anthony
不客气。
尊敬的Jason博士,
再次感谢您的回复。
我做了一些小实验和研究,发现了一个线性回归的“层级”:
检查 1 到 4 的所有情况,寻找最高分数
然后,如果回归模型使用了特定参数,则执行以下操作之一:
我是否走在正确的轨道上?
谢谢你,
悉尼的Anthony
是的,尽管弹性网络可以模拟所有 4 种。
尊敬的Jason博士,
再次感谢您,
悉尼的Anthony
不客气。
非常感谢这篇精彩的文章。
可以使用 PCA 和 Standard Scaler 来使用 ElasticNet 吗?
提前感谢
是的。
嗨,杰森,
elasticnet 可以应用于分类问题。
不,这是一个回归技术。
如何获取系数的显著性值?我知道可以使用 model.coef_ 获取系数本身。
我在数据科学方面受过训练。我的理解是我需要系数本身的值、标准误差和自由度。我认为我可以获得系数的值和自由度,但我如何获得标准误差?
如果能给出完整的答案就太好了。
我实际上使用了 z 标准化后的 x 预测变量项。所以我的标准误差应该都一样……但我不知道如何提取它们。也许是类似 model.se_?
您可能需要使用不同的 API 来拟合模型并进行分析,也许是 scipy。
我正在尝试在管道中使用此来提取最佳 alpha 和 lambda,你能帮忙吗?
这是我正在处理的代码片段
estimators = []
estimators.append((‘standardize’, ZCA()))
#estimators.append((‘ElasticNetCV’, ElasticNetCV(cv=10, random_state=0)))
cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1)
ratios = arange(0, 1, 0.01)
alphas = [1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 0.0, 1.0, 10.0, 100.0]
estimators.append((‘ElasticNetCV’, ElasticNetCV(l1_ratio=ratios, alphas=alphas, cv=cv, n_jobs=-1)))
model = Pipeline(estimators)
model.fit(X, y)
#print(‘alpha: %f’ % model.alphas)
#print(‘l1_ratio_: %f’ % model.l1_ratio)
我解决了这个问题
https://github.com/thistleknot/python-ml/blob/master/code/ElasticNetCV.ipynb