Python实现鲁棒回归(Robust Regression):M-估计量与Huber损失函数的优化

Python实现鲁棒回归:M-估计量与Huber损失函数的优化

大家好,今天我们来探讨一个在统计建模中非常重要,但又容易被忽略的话题:鲁棒回归。在实际应用中,我们经常会遇到数据中存在离群点(outliers)的情况。这些离群点会对传统的最小二乘回归产生很大的影响,导致模型拟合效果变差,甚至得出错误的结论。鲁棒回归,顾名思义,就是指对离群点不敏感的回归方法。

在本次讲座中,我们将重点介绍一种常用的鲁棒回归方法:M-估计量(M-estimators)及其常用的损失函数——Huber损失函数,并结合Python代码,详细讲解如何实现和优化鲁棒回归模型。

1. 最小二乘回归的局限性

首先,我们回顾一下最小二乘回归。最小二乘回归的目标是最小化残差平方和:

min  Σ (yi - f(xi))^2

其中,yi是观测值,f(xi)是模型的预测值。最小二乘回归对残差进行平方,这意味着较大的残差会被赋予更大的权重。因此,离群点(即残差很大的点)会对模型参数产生很大的影响,使得回归线向离群点靠近,从而降低了模型的准确性。

举个简单的例子,假设我们有以下数据:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

# 生成数据,包含一个离群点
np.random.seed(0)
X = np.linspace(0, 10, 50)
y = 2 * X + 1 + np.random.normal(0, 2, 50)
y[45] = 40  # 添加一个离群点

# 使用最小二乘回归
model = LinearRegression()
model.fit(X.reshape(-1, 1), y)
y_pred = model.predict(X.reshape(-1, 1))

# 绘制结果
plt.figure(figsize=(8, 6))
plt.scatter(X, y, label="Data")
plt.plot(X, y_pred, color="red", label="OLS Regression")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
plt.title("Ordinary Least Squares Regression with Outlier")
plt.show()

print(f"OLS Slope: {model.coef_[0]:.2f}, Intercept: {model.intercept_:.2f}")

这段代码生成了一组线性数据,并在其中加入了一个离群点。然后,使用sklearn.linear_model.LinearRegression进行了最小二乘回归。可以看到,由于离群点的存在,回归线被明显拉高,导致模型拟合效果变差。

2. M-估计量:一种更鲁棒的替代方案

为了解决最小二乘回归对离群点敏感的问题,我们可以使用M-估计量。M-估计量通过最小化残差的函数ρ(r)来估计模型参数,而不是直接最小化残差平方和:

min  Σ ρ(yi - f(xi))

其中,ρ(r)是一个对称的、非负的函数,用于衡量残差的贡献。选择不同的ρ(r)函数,就可以得到不同的M-估计量。关键在于选择一个对大残差惩罚较小的ρ(r)函数。

3. Huber损失函数:兼顾效率与鲁棒性

Huber损失函数是一种常用的M-估计量损失函数,它兼顾了最小二乘回归的效率和绝对值损失的鲁棒性。Huber损失函数的定义如下:

ρ(r) = {
    r^2 / 2        if |r| <= δ
    δ * (|r| - δ/2)  if |r| > δ
}

其中,r是残差,δ是一个参数,用于控制Huber损失函数的形状。当残差的绝对值小于δ时,Huber损失函数等同于最小二乘损失;当残差的绝对值大于δ时,Huber损失函数等同于绝对值损失。这样,Huber损失函数既可以对较小的残差进行精确估计,又可以对较大的残差进行抑制,从而提高模型的鲁棒性。

我们可以绘制Huber损失函数,以便更好地理解其特性:

import numpy as np
import matplotlib.pyplot as plt

def huber_loss(r, delta):
    """Huber损失函数"""
    return np.where(np.abs(r) <= delta, 0.5 * r**2, delta * (np.abs(r) - 0.5 * delta))

# 定义残差范围和delta值
r = np.linspace(-10, 10, 200)
delta = 2

# 计算Huber损失
huber = huber_loss(r, delta)

# 绘制Huber损失函数
plt.figure(figsize=(8, 6))
plt.plot(r, huber, label=f"Huber Loss (delta={delta})")
plt.xlabel("Residual (r)")
plt.ylabel("Loss")
plt.title("Huber Loss Function")
plt.grid(True)
plt.legend()
plt.show()

从图中可以看出,当残差的绝对值较小时,Huber损失函数类似于二次函数;当残差的绝对值较大时,Huber损失函数类似于线性函数。

4. 使用Python实现Huber回归

scikit-learn库并没有直接提供Huber回归的实现。但是,我们可以使用sklearn.linear_model.RANSACRegressor来近似实现Huber回归。RANSACRegressor是一种随机抽样一致性算法,它可以自动识别和剔除离群点,从而得到一个更鲁棒的回归模型。

下面是一个使用RANSACRegressor实现Huber回归的例子:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import RANSACRegressor, LinearRegression

# 生成数据,包含一个离群点
np.random.seed(0)
X = np.linspace(0, 10, 50)
y = 2 * X + 1 + np.random.normal(0, 2, 50)
y[45] = 40  # 添加一个离群点

# 使用RANSACRegressor进行Huber回归
ransac = RANSACRegressor(base_estimator=LinearRegression(),
                           min_samples=0.8,  # 至少80%的数据点
                           residual_threshold=3.0, # 残差阈值
                           max_trials=100)  # 最大迭代次数

ransac.fit(X.reshape(-1, 1), y)
y_pred_ransac = ransac.predict(X.reshape(-1, 1))

# 使用最小二乘回归进行比较
model = LinearRegression()
model.fit(X.reshape(-1, 1), y)
y_pred_ols = model.predict(X.reshape(-1, 1))

# 绘制结果
plt.figure(figsize=(8, 6))
plt.scatter(X, y, label="Data")
plt.plot(X, y_pred_ols, color="red", label="OLS Regression")
plt.plot(X, y_pred_ransac, color="green", label="RANSAC Regression (Huber)")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
plt.title("Comparison of OLS and RANSAC Regression with Outlier")
plt.show()

print(f"OLS Slope: {model.coef_[0]:.2f}, Intercept: {model.intercept_:.2f}")
print(f"RANSAC Slope: {ransac.estimator_.coef_[0]:.2f}, Intercept: {ransac.estimator_.intercept_:.2f}")

在这个例子中,我们使用了RANSACRegressor,并将base_estimator设置为LinearRegressionresidual_threshold参数用于设置残差阈值,超过该阈值的点将被视为离群点。可以看到,使用RANSACRegressor后,回归线不再受到离群点的干扰,能够更好地拟合大部分数据。

需要注意的是,RANSACRegressor并不是真正的Huber回归,而是一种近似的实现。它通过随机抽样和剔除离群点的方式,达到类似于Huber回归的效果。

5. 使用迭代加权最小二乘法(IRLS)实现Huber回归

另一种实现Huber回归的方法是使用迭代加权最小二乘法(Iteratively Reweighted Least Squares,IRLS)。IRLS是一种迭代算法,它通过不断调整每个数据点的权重,使得模型对离群点不敏感。

IRLS算法的步骤如下:

  1. 初始化模型参数。
  2. 计算每个数据点的残差。
  3. 根据残差计算每个数据点的权重。
  4. 使用加权最小二乘法重新估计模型参数。
  5. 重复步骤2-4,直到模型参数收敛。

对于Huber损失函数,权重可以这样计算:

w(r) = {
    1             if |r| <= δ
    δ / |r|      if |r| > δ
}

下面是一个使用IRLS实现Huber回归的例子:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

def huber_weight(r, delta):
    """Huber权重函数"""
    return np.where(np.abs(r) <= delta, 1, delta / np.abs(r))

def IRLS_huber(X, y, delta=1.0, max_iter=100, tol=1e-6):
    """使用IRLS实现Huber回归"""
    # 初始化模型参数
    model = LinearRegression()
    model.fit(X, y)
    beta = np.array([model.intercept_, model.coef_[0]])

    for i in range(max_iter):
        # 计算残差
        y_pred = X[:, 1] * beta[1] + beta[0]
        residuals = y - y_pred

        # 计算权重
        weights = huber_weight(residuals, delta)

        # 使用加权最小二乘法重新估计模型参数
        W = np.diag(weights)
        X_weighted = X * np.sqrt(W)
        y_weighted = y * np.sqrt(W)

        model.fit(X_weighted, y_weighted)
        beta_new = np.array([model.intercept_, model.coef_[0]])

        # 检查收敛性
        if np.sum((beta_new - beta)**2) < tol:
            break

        beta = beta_new

    return beta

# 生成数据,包含一个离群点
np.random.seed(0)
X_data = np.linspace(0, 10, 50)
y_data = 2 * X_data + 1 + np.random.normal(0, 2, 50)
y_data[45] = 40  # 添加一个离群点

# 构造设计矩阵
X = np.vstack([np.ones(len(X_data)), X_data]).T

# 使用IRLS进行Huber回归
beta_huber = IRLS_huber(X, y_data)
y_pred_huber = X[:, 1] * beta_huber[1] + beta_huber[0]

# 使用最小二乘回归进行比较
model = LinearRegression()
model.fit(X[:, 1].reshape(-1, 1), y_data)
y_pred_ols = model.predict(X[:, 1].reshape(-1, 1))

# 绘制结果
plt.figure(figsize=(8, 6))
plt.scatter(X_data, y_data, label="Data")
plt.plot(X_data, y_pred_ols, color="red", label="OLS Regression")
plt.plot(X_data, y_pred_huber, color="green", label="IRLS Huber Regression")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
plt.title("Comparison of OLS and IRLS Huber Regression with Outlier")
plt.show()

print(f"OLS Slope: {model.coef_[0]:.2f}, Intercept: {model.intercept_:.2f}")
print(f"IRLS Huber Slope: {beta_huber[1]:.2f}, Intercept: {beta_huber[0]:.2f}")

在这个例子中,我们首先定义了huber_weight函数,用于计算Huber权重。然后,我们实现了IRLS_huber函数,该函数使用IRLS算法进行Huber回归。可以看到,使用IRLS Huber回归后,回归线也能够更好地拟合大部分数据,而不会受到离群点的干扰。

6. Huber损失函数的参数 δ 的选择

Huber损失函数中参数 δ 的选择至关重要。δ 决定了损失函数对残差的敏感程度。

  • δ 较小: 模型对离群点更加敏感,更接近于最小二乘回归。
  • δ 较大: 模型对离群点更加不敏感,更接近于绝对值损失回归。

常用的选择 δ 的方法包括:

  • 经验法则: 例如,将 δ 设置为残差标准差的 1.5 倍。
  • 交叉验证: 尝试不同的 δ 值,并通过交叉验证选择性能最好的值。
  • 自适应估计: 某些方法可以自动估计 δ 的值。

在实际应用中,可以尝试不同的 δ 值,并根据模型的性能和对离群点的容忍度进行选择。

7. 鲁棒回归的适用场景

鲁棒回归适用于以下场景:

  • 数据中存在离群点。
  • 数据分布不是严格的正态分布。
  • 需要对模型参数进行更稳健的估计。

例如,在金融数据分析、图像处理、生物统计等领域,鲁棒回归都有广泛的应用。

8. 其他鲁棒回归方法

除了M-估计量和Huber损失函数之外,还有其他的鲁棒回归方法,例如:

  • RANSAC (RANdom SAmple Consensus) 回归: 如前所述,一种通过随机采样来识别和剔除离群点的算法。
  • Theil-Sen 回归: 一种基于斜率中位数的非参数回归方法。
  • S-估计量: 一种具有很高崩溃点 (breakdown point) 的鲁棒估计量。

每种方法都有其优缺点,需要根据具体情况进行选择。

9. 总结:选择合适的鲁棒回归方法

本次讲座我们深入探讨了鲁棒回归,特别是M-估计量和Huber损失函数。我们了解到传统最小二乘回归容易受到离群点的影响,而鲁棒回归通过使用不同的损失函数(如Huber损失函数)或算法(如RANSAC、IRLS),可以有效地降低离群点的影响,提高模型的稳健性。选择合适的鲁棒回归方法取决于数据的特点和具体的需求,需要根据实际情况进行选择和调整。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注