机器学习:套索回归

套索回归是一种缩减方法,和岭回归类似。它的不同之处在于选择的惩罚方式:套索在参数 \(\beta\) 上施加了一个 \(\ell_1\) 惩罚。也就是说,套索找到了一个最小化函数

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

\(\beta\) 的赋值方式,其中 \(\lambda\) 是超参数,通常 \(X\) 是训练数据,\(Y\) 是观测数据。\(\ell_1\) 惩罚鼓励学到的参数的稀疏性,并且,如我们所看到的,可以驱使许多系数为零。从这个意义上说,套索是一种连续的特征选择方法。

在这个笔记本中,我们展示了如何使用 CVXPY 拟合套索模型,如何评估模型,以及如何调整超参数 \(\lambda\)

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

编写目标函数

我们可以将 目标函数 分解为 最小二乘损失函数 和一个 \(\ell_1\) 正则化项 的和。

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

def regularizer(beta):
    return cp.norm1(beta)

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

生成数据

我们生成线性相关的训练样本和观测结果;我们让关系*稀疏*,然后看看lasso能够近似恢复它。

def generate_data(m=100, n=20, sigma=5, density=0.2):
    "生成数据矩阵 X 和观测结果 Y。"
    np.random.seed(1)
    beta_star = np.random.randn(n)
    idxs = np.random.choice(range(n), int((1-density)*n), replace=False)
    for idx in idxs:
        beta_star[idx] = 0
    X = np.random.randn(m,n)
    Y = X.dot(beta_star) + np.random.normal(0, sigma, size=m)
    return X, Y, beta_star

m = 100
n = 20
sigma = 5
density = 0.2

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)

评估模型

完成正如我们在岭回归中看到的那样,正则化可以提高泛化能力。

``` python

%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/lasso_regression_9_0.svg

正则化路径和特征选择

随着 \(\lambda\) 的增加,参数被驱使为 \(0\)。当 \(\lambda \approx 10\) 时,大约80% 的系数*确切地*为零。这与 \(\beta^*\) 的生成方式相符,其80% 的条目为零。与具有最慢衰减系数的特征对应的可以解释为最重要的特征。

从定性上看,lasso 与 ridge 的不同之处在于前者经常将参数驱使为零,而后者只是收缩参数而不通常将其设为零。也就是说,lasso 产生的是稀疏模型;ridge(通常)不是。

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("正则化路径")
    plt.show()

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