支持向量机分类器与L1正则化

在这个例子中,我们使用CVXPY来训练一个带有L1正则化的支持向量机分类器。给定数据:(xi, yi), i = 1, …, m。其中xi ∈R^n是特征向量,而yi ∈{-1, 1}是相关的布尔结果。我们的目标是构建一个好的线性分类器: ŷ = sign(β^Tx - v)。我们通过最小化(凸)函数来找到参数β和v。

\[f(\beta,v) = (1/m) \sum_i \left(1 - y_i ( \beta^T x_i-v) \right)_+ + \lambda \| \beta\|_1\]

第一项是平均合页损失。第二项缩小了β中的系数并鼓励稀疏性。标量λ ≥ 0是(规则化)参数。同时最小化f(β, v)为选择特征和适应分类器。

示例

在以下代码中,我们通过随机选择xi和一个稀疏的β_真实来生成具有n=20个特征的数据。然后我们设置yi = sign(β_真实^Txi - v_真实 - zi),其中zi是独立同分布的正态随机变量。我们将数据分成训练集和测试集,每个集合包含m=1000个示例。

# 生成用于L1正则化的支持向量机分类器的数据。
from __future__ import division
import numpy as np
np.random.seed(1)
n = 20
m = 1000
TEST = m
DENSITY = 0.2
beta_true = np.random.randn(n,1)
idxs = np.random.choice(range(n), int((1-DENSITY)*n), replace=False)
for idx in idxs:
    beta_true[idx] = 0
offset = 0
sigma = 45
X = np.random.normal(0, 5, size=(m,n))
Y = np.sign(X.dot(beta_true) + offset + np.random.normal(0,sigma,size=(m,1)))
X_test = np.random.normal(0, 5, size=(TEST,n))
Y_test = np.sign(X_test.dot(beta_true) + offset + np.random.normal(0,sigma,size=(TEST,1)))

接下来我们使用CVXPY来制定优化问题。.. code:: python

# 使用L1正则化问题构建SVM。
import cvxpy as cp
beta = cp.Variable((n,1))
v = cp.Variable()
loss = cp.sum(cp.pos(1 - cp.multiply(Y, X @ beta - v)))
reg = cp.norm(beta, 1)
lambd = cp.Parameter(nonneg=True)
prob = cp.Problem(cp.Minimize(loss/m + lambd*reg))

我们对一系列 \(\lambda\) 的取值范围求解优化问题,以计算出一个权衡曲线。然后我们绘制出训练误差和测试误差随着权衡曲线的变化。一个合理的 \(\lambda\) 取值是能使测试误差最小化的值。

# 计算权衡曲线并记录训练误差和测试误差。
TRIALS = 100
train_error = np.zeros(TRIALS)
test_error = np.zeros(TRIALS)
lambda_vals = np.logspace(-2, 0, TRIALS)
beta_vals = []
for i in range(TRIALS):
    lambd.value = lambda_vals[i]
    prob.solve()
    train_error[i] = (np.sign(X.dot(beta_true) + offset) != np.sign(X.dot(beta.value) - v.value)).sum()/m
    test_error[i] = (np.sign(X_test.dot(beta_true) + offset) != np.sign(X_test.dot(beta.value) - v.value)).sum()/TEST
    beta_vals.append(beta.value)
# 在权衡曲线上绘制训练误差和测试误差。
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

plt.plot(lambda_vals, train_error, label="训练误差")
plt.plot(lambda_vals, test_error, label="测试误差")
plt.xscale('log')
plt.legend(loc='upper left')
plt.xlabel(r"$\lambda$", fontsize=16)
plt.show()
../../_images/svm_8_0.svg

我们还绘制了正则化路径,即 \(\beta_i\) 关于 \(\lambda\) 的变化情况。注意到 \(\beta_i\) 不一定随着 \(\lambda\) 的增大而单调递减。有4个特征在较大的 \(\lambda\) 值上保持非零,这表明这些特征是最重要的。事实上,\(\beta_{\mathrm{true}}\) 中有4个非零值。.. code:: python

# 绘制 beta 的正则化路径
for i in range(n):
    plt.plot(lambda_vals, [wi[i,0] for wi in beta_vals])
plt.xlabel(r"$\lambda$", fontsize=16)
plt.xscale("log")
../../_images/svm_10_0.svg