机器学习:岭回归

岭回归是一种与简单的最小二乘线性回归非常相似的回归技术:将参数 \(\beta\)\(\ell_2\) **惩罚项**简单地添加到线性回归的目标函数中,得到岭回归的目标函数。

我们的目标是找到使函数最小化的 \(\beta\) 赋值:

\[f(\beta) = \|X\beta - Y\|_2^2 + \lambda \|\beta\|_2^2,\]

其中 \(\lambda\) 是一个超参数,如通常情况下,\(X\) 是训练数据,\(Y\) 是观察数据。在实践中,我们通过调节 \(\lambda\) 直到找到一个在测试数据上有很好泛化能力的模型。

岭回归是一种**缩减方法**的例子:与最小二乘法相比,它缩小了参数估计值的范围,以期望**减小方差、提高预测准确性并有助于解释**。

在本文中,我们将展示如何使用 CVXPY 拟合岭回归模型,如何评估模型以及如何调节超参数 \(\lambda\)

import cvxpy as cp
import numpy as np
import matplotlib.pyplot as plt

编写目标函数

我们可以将**目标函数**分解为一个**最小二乘损失函数**和一个 \(\ell_2\) 正则化器

def loss_fn(X, Y, beta):
    return cp.pnorm(X @ beta - Y, p=2)**2

def regularizer(beta):
    return cp.pnorm(beta, p=2)**2

def objective_fn(X, Y, beta, lambd):
    return loss_fn(X, Y, beta) + lambd * regularizer(beta)

def mse(X, Y, beta):
    return (1.0 / X.shape[0]) * loss_fn(X, Y, beta).value

生成数据

由于岭回归鼓励参数估计较小,因此往往导致与用普通线性回归拟合的模型相比**方差较小**。我们生成一个小的数据集来说明这一点。

def generate_data(m=100, n=20, sigma=5):
    "生成数据矩阵 X 和观测值 Y."
    np.random.seed(1)
    beta_star = np.random.randn(n)
    # 生成一个病态数据矩阵
    X = np.random.randn(m, n)
    # 用加性高斯噪声破坏观测值
    Y = X.dot(beta_star) + np.random.normal(0, sigma, size=m)
    return X, Y

m = 100
n = 20
sigma = 5

X, Y = generate_data(m, n, sigma)
X_train = X[:50, :]
Y_train = Y[:50]
X_test = X[50:, :]
Y_test = Y[50:]

拟合模型

为了拟合模型,我们只需创建一个CVXPY问题,其中目标是最小化上述定义的目标函数。我们将 \(\lambda\) 指定为CVXPY参数,以便可以使用单个CVXPY问题来获得多个 \(\lambda\) 值的估计值。

beta = cp.Variable(n)
lambd = cp.Parameter(nonneg=True)
problem = cp.Problem(cp.Minimize(objective_fn(X_train, Y_train, beta, lambd)))

lambd_values = np.logspace(-2, 3, 50)
train_errors = []
test_errors = []
beta_values = []
for v in lambd_values:
    lambd.value = v
    problem.solve()
    train_errors.append(mse(X_train, Y_train, beta))
    test_errors.append(mse(X_test, Y_test, beta))
    beta_values.append(beta.value)

评估模型

请注意,一定程度上,对参数大小进行惩罚可以降低测试误差,但代价是增加训练误差,从而在降低方差的同时,增加偏差;换句话说,这表明,对于我们的示例来说,经过适当调整的岭回归**推广性更好**,而不是最小二乘线性回归。

%matplotlib inline
%config InlineBackend.figure_format = 'svg'

def plot_train_test_errors(train_errors, test_errors, lambd_values):
    plt.plot(lambd_values, train_errors, label="训练误差")
    plt.plot(lambd_values, test_errors, label="测试误差")
    plt.xscale("log")
    plt.legend(loc="upper left")
    plt.xlabel(r"$\lambda$", fontsize=16)
    plt.title("均方误差 (MSE)")
    plt.show()

plot_train_test_errors(train_errors, test_errors, lambd_values)
../../_images/ridge_regression_9_0.svg

正则化路径

如预期所示,增大 \(\lambda\) 可以将参数推向 \(0\)。在实际应用中,那些比其他参数更慢地趋近于零的参数可能对应于更**具信息性**的特征。从这个意义上说,岭回归可以被认为是**模型选择**。

def plot_regularization_path(lambd_values, beta_values):
    num_coeffs = len(beta_values[0])
    for i in range(num_coeffs):
        plt.plot(lambd_values, [wi[i] for wi in beta_values])
    plt.xlabel(r"$\lambda$", fontsize=16)
    plt.xscale("log")
    plt.title("Regularization Path")
    plt.show()

plot_regularization_path(lambd_values, beta_values)
../../_images/ridge_regression_11_0.svg