机器学习:岭回归
岭回归是一种与简单的最小二乘线性回归非常相似的回归技术:将参数 \(\beta\) 的 \(\ell_2\) **惩罚项**简单地添加到线性回归的目标函数中,得到岭回归的目标函数。
我们的目标是找到使函数最小化的 \(\beta\) 赋值:
其中 \(\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)
正则化路径
如预期所示,增大 \(\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)