哈喽,大家好~
今儿群里,有一个问题,“数据里有几个特别大的值,直接删掉可以吗?”
实施上是可以的,但你得先确认,它们到底是异常,还是业务中真实存在的极端情况。
在机器学习建模时,异常值经常会影响模型的学习方向。尤其是线性回归这类对数值比较敏感的模型,少量异常点就可能把整条回归线拉偏。
今天我们就用一个简单案例,看看如何识别异常值、删除异常值,并观察删除前后模型发生了什么变化~
01什么是异常值?
异常值,简单来说,就是和大多数样本明显不一样的数据。
比如,我们统计房屋面积和房价。大部分房子面积在 平方米之间,房价也基本随着面积增加而增加。但某一条数据可能是:
面积:80 平方米
房价:2000 万
它不一定是错误数据,也可能是学区房、豪宅或者录入错误。
所以,异常值处理不能只看“它是不是很大”,而要结合业务背景判断:
-
如果是录入错误、传感器故障,可以删除或修正; -
如果是真实但极端的样本,不建议直接删除; -
如果异常样本很多,说明数据分布本身可能发生了变化。
就是说,删除异常值的前提,不是它看起来奇怪,而是它确实不代表我们想建模的正常规律。
02为什么异常值会影响线性回归?
线性回归希望找到一条直线,让预测值和真实值之间的误差尽可能小。
假设输入特征是 ,目标值是 ,线性回归公式为:
其中, 是斜率,表示 每增加一个单位, 大约变化多少; 是截距,表示 时的预测值。
模型通常通过最小化均方误差来训练:
这里的关键是平方。假设正常误差是 ,平方后是 ;但某个异常点误差是 ,平方后就变成了 。
也就是说,一个异常值产生的影响,可能相当于很多个正常样本。模型为了降低这个巨大的误差,就会主动把回归线往异常点方向拉。
03用 MAD 找出异常残差
我们先训练一个初始模型,然后计算每个样本的残差:
残差越大,说明真实值和模型预测值相差越远。
不过,直接使用均值和标准差容易再次受到异常值影响。因此这里使用 MAD,也就是中位数绝对偏差:
再计算稳健标准分数:
当 时,我们把它标记为候选异常值。
你可以这样理解:先用模型找到“偏离正常规律”的点,再把这些点从训练数据中移除,重新训练模型。
04完整案例
下面构造一组数据~
正常数据大致满足 ,同时人为加入几个明显偏离规律的异常点。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(42)
# 构造正常数据
x = np.linspace(0, 10, 100)
y = 2.8 * x + 5 + np.random.normal(0, 1.5, 100)
# 加入异常值
outlier_index = [8, 25, 57, 78, 91]
y[outlier_index] += [18, -22, 25, -18, 20]
X = x.reshape(-1, 1)
# 第一次训练
model_before = LinearRegression()
model_before.fit(X, y)
y_pred = model_before.predict(X)
residual = y - y_pred
# 使用 MAD 计算稳健标准分数
median_residual = np.median(residual)
mad = np.median(np.abs(residual - median_residual))
robust_z = 0.6745 * (residual - median_residual) / mad
detected_outliers = np.abs(robust_z) > 3.5
# 删除异常值后重新训练
model_after = LinearRegression()
model_after.fit(X[~detected_outliers], y[~detected_outliers])
y_pred_after = model_after.predict(X)
print("识别出的异常值数量:", detected_outliers.sum())
print("删除前:斜率=%.2f,截距=%.2f" %
(model_before.coef_[0], model_before.intercept_))
print("删除后:斜率=%.2f,截距=%.2f" %
(model_after.coef_[0], model_after.intercept_))
# 绘图
plt.figure(figsize=(14, 5))
# 图1:删除前后回归线
plt.subplot(1, 2, 1)
plt.scatter(x[~detected_outliers], y[~detected_outliers],
color="#00B4D8", s=45, label="正常样本")
plt.scatter(x[detected_outliers], y[detected_outliers],
color="#FF4D6D", edgecolors="#6A0572",
s=100, marker="X", label="异常样本")
plt.plot(x, y_pred, color="#FFB000", linewidth=3, label="删除前")
plt.plot(x, y_pred_after, color="#7B2CBF", linewidth=3, label="删除后")
plt.xlabel("特征 x")
plt.ylabel("目标值 y")
plt.title("异常值对回归线的影响")
plt.legend()
plt.grid(alpha=0.25)
# 图2:残差诊断
plt.subplot(1, 2, 2)
plt.scatter(y_pred, residual, color="#06D6A0", s=50)
plt.scatter(y_pred[detected_outliers], residual[detected_outliers],
color="#FF4D6D", edgecolors="#6A0572",
s=110, marker="X")
plt.axhline(0, color="#222222", linewidth=1.5)
plt.axhline(3.5 * mad / 0.6745, color="#FFB000",
linestyle="--", linewidth=2)
plt.axhline(-3.5 * mad / 0.6745, color="#FFB000",
linestyle="--", linewidth=2)
plt.xlabel("预测值")
plt.ylabel("残差")
plt.title("基于MAD的异常残差检测")
plt.grid(alpha=0.25)
plt.tight_layout()
plt.show()
第一张图中,蓝色点是正常样本,红色叉号是检测出的异常值~
黄色回归线表示删除异常值前的结果,紫色回归线表示删除后的结果。
如果异常值分布在回归线两侧,删除前后的变化可能不明显;但如果异常值集中在某一侧,删除前的回归线就会明显向异常点偏移。删除后,模型通常会更接近正常数据的真实趋势。
第二张图是残差诊断图。横轴是模型预测值,纵轴是残差。黄色虚线表示异常判断边界,落在边界外的点会被标记为异常。这个图能帮助我们判断:模型的误差是否随机分布,以及是否存在特别突出的样本。
05删除异常值时要注意什么?
第一,不要把异常值检测结果直接当成删除命令。模型识别出的只是“统计上的异常”,还需要结合业务规则确认。
第二,异常值检测要只使用训练集。不能先用完整数据删除异常值,再划分训练集和测试集,否则测试集信息会泄漏到训练过程中。
第三,删除异常值后要重新评估模型。重点观察验证集上的 、 或 是否真正改善,而不是只看训练集效果。
第四,如果异常值代表真实业务场景,比如欺诈交易、设备故障、极端天气,那么它们可能正是我们最关心的数据。这种情况下,与其删除,不如单独建模或增加异常样本特征。
总结
删除异常值的核心流程是:先训练初始模型,再通过残差识别明显偏离规律的样本,结合业务判断后删除,最后重新训练并评估模型。
在这个案例中,MAD比普通均值和标准差更不容易受到极端值影响,适合做稳健的异常检测。
后续大家还可以尝试使用 IsolationForest、LocalOutlierFactor 等方法处理多维异常值,也可以对比删除异常值、缩尾处理和使用 HuberRegressor 这类稳健模型之间的效果。

