使用三种稳健线性回归模型处理异常值
使用三种稳健线性回归模型处理异常值
在机器学习中,异常值可能会对模型的性能产生严重影响,特别是在线性回归模型中。本文将介绍四种不同的回归模型:线性回归、Huber Regression、RANSAC Regression和Theil-Sen Regression,并通过实验数据展示它们在处理异常值时的表现。
线性回归是最简单的机器学习模型之一。它通常不仅是学习数据科学的起点,也是构建快速简单的最小可行产品(MVP)的起点,然后作为更复杂算法的基准。
一般来说,线性回归拟合最能描述特征和目标值之间线性关系的直线(二维)或超平面(三维及三维以上)。该算法还假设特征的概率分布表现良好;例如,它们遵循高斯分布。
异常值是位于预期分布之外的值。它们导致特征的分布表现较差。因此,模型可能会向异常值倾斜,正如我已经建立的那样,这些异常值远离观测的中心质量。自然,这会导致线性回归发现更差和更有偏差的拟合,预测性能较差。
重要的是要记住,异常值可以在特征和目标变量中找到,所有场景都可能恶化模型的性能。
有许多可能的方法来处理异常值:从观察值中删除异常值,处理异常值(例如,将极端观察值限制在合理值),或使用非常适合自己处理此类值的算法。本文重点介绍了这些稳健的方法。
安装程序
我使用相当标准的库:numpy、pandas、scikit-learn。我在这里使用的所有模型都是从scikit-learn的linear_model模块导入的。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets
from sklearn.linear_model import (LinearRegression, HuberRegressor, RANSACRegressor, TheilSenRegressor)
数据
鉴于目标是展示不同的鲁棒算法如何处理异常值,第一步是创建定制的数据集,以清楚地显示行为中的差异。为此,请使用scikit-learn中提供的功能。
首先创建一个包含500个观察值的数据集,其中包含一个信息性特征。只有一个特征和目标,绘制数据以及模型的拟合。此外,指定噪声(应用于输出的标准差),并创建包含基础线性模型系数的列表;也就是说,如果线性回归模型适合生成的数据,系数会是多少。在本例中,系数的值为64.6。提取所有模型的系数,并使用它们来比较它们与数据的拟合程度。
接下来,用异常值替换前25个观察值(占观察值的5%),远远超出生成的观察值的质量。请记住,先前存储的系数来自没有异常值的数据。包括他们会有所不同。
N_SAMPLES = 500
N_OUTLIERS = 25
X, y, coef = datasets.make_regression(
n_samples=N_SAMPLES,
n_features=1,
n_informative=1,
noise=20,
coef=True,
random_state=42
)
coef_list = [["original_coef", float(coef)]]
# add outliers
np.random.seed(42)
X[:N_OUTLIERS] = 10 + 0.75 * np.random.normal(size=(N_OUTLIERS, 1))
y[:N_OUTLIERS] = -15 + 20 * np.random.normal(size=N_OUTLIERS)
plt.scatter(X, y);
图 1。生成的数据和手动添加的异常值
线性回归
从良好的旧线性回归模型开始,该模型可能受到异常值的高度影响。使用以下示例将模型与数据拟合:
lr = LinearRegression().fit(X, y)
coef_list.append(["linear_regression", lr.coef_[0]])
然后准备一个用于绘制模型拟合的对象。
plotline_X对象是一个2D数组,包含在生成的数据集指定的间隔内均匀分布的值。使用此对象获取模型的拟合值。它必须是2D数组,因为它是scikit-learn中模型的预期输入。然后创建一个fit_df数据框,在其中存储拟合值,通过将模型拟合到均匀分布的值来创建。
plotline_X = np.arange(X.min(), X.max()).reshape(-1, 1)
fit_df = pd.DataFrame(
index=plotline_X.flatten(),
data={"linear_regression": lr.predict(plotline_X)}
)
准备好数据框架后,绘制线性回归模型与具有异常值的数据的拟合图。
fix, ax = plt.subplots()
fit_df.plot(ax=ax)
plt.scatter(X, y, c="k")
plt.title("Linear regression on data with outliers");
图 2:线性回归模型对含有异常值的数据的拟合
使用线性回归获得了基准模型。现在是时候转向稳健回归算法了。
Huber Regression
Huber regression是稳健回归算法的一个示例,该算法为被识别为异常值的观察值分配较少的权重。为此,它在优化例程中使用Huber损耗。下面让我们更好地了解一下这个模型中实际发生了什么。
Huber回归最小化以下损失函数:
其中,表示标准差,表示特征集,是回归的目标变量,是估计系数的向量,是正则化参数。该公式还表明,根据Huber损失,对异常值的处理与常规观测不同:
Huber损失通过考虑残差来识别异常值,用表示。如果观察被认为是规则的(因为残差的绝对值小于某个阈值),然后应用平方损失函数。否则,将观察值视为异常值,并应用绝对损失。话虽如此,胡伯损失基本上是平方损失函数和绝对损失函数的组合。
好奇的读者可能会注意到,第一个方程类似于Ridge regression,即包括L2正则化。Huber回归和岭回归的区别在于异常值的处理。
通过分析两种常用回归评估指标:均方误差(MSE)和平均绝对误差(MAE)之间的差异,您可能会认识到这种损失函数的方法。与Huber损失的含义类似,我建议在处理异常值时使用MAE,因为它不会像平方损失那样严重地惩罚这些观察值。
与前一点相关的是,优化平方损失会导致均值周围的无偏估计,而绝对差会导致中值周围的无偏估计。中位数对异常值的鲁棒性要比平均值强得多,因此预计这将提供一个偏差较小的估计。
使用默认值1.35,这决定了回归对异常值的敏感性。Huber(2004)表明,当误差服从正态分布且=1和=1.35时,相对于OLS回归,效率达到95%。
对于您自己的用例,我建议使用网格搜索等方法调整超参数alpha和epsilon。
使用以下示例将Huber回归拟合到数据:
huber = HuberRegressor().fit(X, y)
fit_df["huber_regression"] = huber.predict(plotline_X)
coef_list.append(["huber_regression", huber.coef_[0]])
图 3:Huber回归模型对含异常值数据的拟合
RANSAC 回归
随机样本一致性(RANSAC)回归是一种非确定性算法,试图将训练数据分为内联(可能受到噪声影响)和异常值。然后,它仅使用内联线估计最终模型。
RANSAC是一种迭代算法,其中迭代包括以下步骤:
- 从初始数据集中选择一个随机子集。
- 将模型拟合到选定的随机子集。默认情况下,该模型是线性回归模型;但是,您可以将其更改为其他回归模型。
- 使用估计模型计算初始数据集中所有数据点的残差。绝对残差小于或等于所选阈值的所有观察值都被视为内联,并创建所谓的共识集。默认情况下,阈值定义为目标值的中值绝对偏差(MAD)。
- 如果足够多的点被分类为共识集的一部分,则拟合模型保存为最佳模型。如果当前估计模型与当前最佳模型具有相同的内联数,则只有当其得分更好时,才认为它更好。
迭代执行步骤的次数最多,或者直到满足特殊停止标准。可以使用三个专用超参数设置这些标准。如前所述,最终模型是使用所有内部样本估计的。
将RANSAC回归模型与数据拟合。
ransac = RANSACRegressor(random_state=42).fit(X, y)
fit_df["ransac_regression"] = ransac.predict(plotline_X)
ransac_coef = ransac.estimator_.coef_
coef_list.append(["ransac_regression", ransac.estimator_.coef_[0]])
如您所见,恢复系数的过程有点复杂,因为首先需要使用estimator_访问模型的最终估计器(使用所有已识别的内联线训练的估计器)。由于它是一个LinearRegression对象,请像前面一样继续恢复系数。然后,绘制RANSAC回归拟合图(图4)。
图 4:RANSAC回归模型对含有异常值的数据的拟合
使用RANSAC回归,您还可以检查模型认为是内联值和离群值的观察值。首先,检查模型总共识别了多少异常值,然后检查手动引入的异常值中有多少与模型的决策重叠。训练数据的前25个观察值都是引入的异常值。
inlier_mask = ransac.inlier_mask_
outlier_mask = ~inlier_mask
print(f"Total outliers: {sum(outlier_mask)}")
print(f"Outliers you added yourself: {sum(outlier_mask[:N_OUTLIERS])} / {N_OUTLIERS}")
运行该示例将打印以下摘要:
Total outliers: 51
Outliers you added yourself: 25 / 25
大约10%的数据被确定为异常值,所有引入的观察结果都被正确归类为异常值。然后可以快速将内联线与异常值进行比较,以查看标记为异常值的其余26个观察值。
plt.scatter(X[inlier_mask], y[inlier_mask], color="blue", label="Inliers")
plt.scatter(X[outlier_mask], y[outlier_mask], color="red", label="Outliers")
plt.title("RANSAC - outliers vs inliers");
图 5:与RANSAC算法识别的异常值进行比较的内联线
泰尔森回归
scikit-learn中可用的最后一种稳健回归算法是Theil-Sen regression。这是一种非参数回归方法,这意味着它不假设基础数据分布。简而言之,它涉及在训练数据子集上拟合多元回归模型,然后在最后一步聚合系数。
下面是算法的工作原理。首先,它计算从训练集X中所有观察值创建的大小为p(超参数n_subsamples)的子集上的最小二乘解(斜率和截距)。如果计算截距(可选),则必须满足以下条件p >= n_features + 1。直线的最终斜率(可能还有截距)定义为所有最小二乘解的(空间)中值。
该算法的一个可能缺点是计算复杂度,因为它可以考虑等于n_samples choose n_subsamples的最小二乘解总数,其中n_samples是X中的观测数。鉴于这一数字可能迅速扩大,可以做几件事:
- 在样本数量和特征方面,只对小问题使用该算法。然而,由于明显的原因,这可能并不总是可行的。
- 调整n_subsamples超参数。值越低,对异常值的鲁棒性越高,但效率越低,而值越高,鲁棒性越低,效率越高。
- 使用max_subpopulation超参数。如果n_samples choose n_subsamples的总值大于max_subpopulation,则该算法仅考虑给定最大大小的随机子种群。自然,仅使用所有可能组合的随机子集会导致算法失去一些数学特性。
此外,请注意,估计器的稳健性随着问题的维数迅速降低。要了解这在实践中的效果,请使用以下示例估计泰尔森回归:
theilsen = TheilSenRegressor(random_state=42).fit(X, y)
fit_df["theilsen_regression"] = theilsen.predict(plotline_X)
coef_list.append(["theilsen_regression", theilsen.coef_[0]])
图 6:泰尔森回归模型对含有异常值的数据的拟合
模型比较
到目前为止,已经对包含异常值的数据拟合了三种稳健回归算法,并确定了各个最佳拟合线。现在是进行比较的时候了。
从图7的目视检查开始。为了显示太多行,未打印原始数据的拟合行。然而,考虑到大多数数据点的方向,很容易想象它是什么样子。显然,RANSAC和泰尔森回归得到了最准确的最佳拟合线。
图 7:所有考虑的回归模型的比较
更准确地说,请查看估计系数。表1显示,RANSAC回归结果最接近原始数据之一。有趣的是,5%的异常值对正则线性回归拟合的影响有多大。
model | coefficient |
---|---|
0 | original_coef |
1 | linear_regression |
2 | huber_regression |
3 | ransac_regression |
4 | theilsen_regression |
表 1:不同模型的系数与Outle数据拟合的比较