Lasso回归
Lasso回归是在线性回归平方损失中加入系数绝对值和惩罚的方法。它会缩小系数,并可使一些系数精确等于零。零值来自目标在零点的尖角和最优性条件,而不只是计算后把较小数字四舍五入删除。
四个合成样本的两个特征列分别为 、,响应为 。两个特征均值为零、均方为一且互相正交;它们让读者能独立复算完整最优解,再看一般坐标更新如何实现。
目标与尺度约定
对 、,本文最小化 截距不受惩罚。若各特征列已中心化,则截距最优为 ;将y也减去均值后可只计算β。通常还将每列的均方调整为一,避免一个特征仅因单位较大而以更小系数承担相同预测,从而受到不同惩罚。本文λ的数值对应前面的 损失系数,不能与省去n或二的写法直接比较。[1]
在截距已单独确定的中心化问题中,目标是凸函数:平方项关于β的Hessian为 、半正定,绝对值和也凸。λ为正时惩罚随系数大小趋向无穷,保证最小值存在;若中心化后的X满列秩,平方项严格凸,β最优解唯一。λ为零回到最小二乘,解在列相关时可能不唯一。
一维最小化与软阈值
固定其他系数,定义去掉第j个特征贡献后的部分残差 。与 有关的目标为 b为正时导数为 ,只有z大于λ才产生正根;b为负时导数为 ,只有z小于负λ才产生负根。零点左右导数分别为 与 ,零为最优恰当 。所以统一更新是 这叫软阈值:先向零平移λ,未超过阈值的整段直接归零;它与只删除小系数、保留大系数原值的硬阈值不同。
对整体残差 ,最优的必要且充分条件为 这是平方梯度与绝对值次梯度之和含零的条件;凸性使它充分。它既解释变量何时归零,也给出停止算法的可检查标准,而不只观察相邻两次系数是否看似不变。
正交例子的最优解和目标值
主例 ,,目标逐坐标分离。取λ=1.2,软阈值给出 。预测为 ,残差为 ,故平方损失和惩罚分别为 总目标2.18,小于全部系数为零时的2.5,也小于无残差系数(2,1)的3.6。完整KKT核对为 、,因此是全局最优;主例满秩,解唯一。
变式。 λ改为0.5时,解为 ,平方损失0.25、惩罚一,总目标1.25;λ至少为二时,两个系数都为零。一般中心化问题中,只要 ,零向量满足全部KKT条件。主例系数随λ的路径分段线性,但不能把这两个独立阈值公式原样用于相关列,相关时部分残差会随其他系数变化。
坐标下降实现
程序先中心化并按均方根标准化X,同时中心化y;在标准化坐标上施加上述λ,最后还原原单位系数和截距。常数特征需先移除;输入为有限实数、正λ,函数不改写输入。有限输入仍可能在求和、平方或还原单位时超出浮点范围,程序对派生均值、尺度、残差及KKT量逐一检查,超出时抛出 ArithmeticError,不会把NaN当成零误差而成功返回。每轮更新保持 ,用部分残差计算精确一维最小值,因此在精确算术下目标不增。循环后检查所有KKT误差,达到指定容差才返回。
import math
def lasso_coordinate(X, y, penalty, tol=1e-9, max_sweeps=10000):
X, y = tuple(tuple(row) for row in X), tuple(y)
n = len(y)
if n < 2 or len(X) != n or not X or len(X[0]) == 0:
raise ValueError("nonempty matched matrix and response required")
p = len(X[0])
if (any(len(row) != p for row in X)
or any(not math.isfinite(v) for row in X for v in row)
or any(not math.isfinite(v) for v in y)):
raise ValueError("finite rectangular data required")
if not math.isfinite(penalty) or penalty <= 0:
raise ValueError("penalty must be positive")
if not math.isfinite(tol) or tol <= 0 or type(max_sweeps) is not int or max_sweeps < 1:
raise ValueError("invalid convergence controls")
def checked(values):
values = list(values)
if any(not math.isfinite(v) for v in values):
raise ArithmeticError("derived quantity exceeds finite floating range")
return values
def finite_sum(values):
try:
value = math.fsum(checked(values))
except (OverflowError, ValueError) as error:
raise ArithmeticError("sum exceeds floating range") from error
return checked([value])[0]
ymean = finite_sum(y) / n
means = [finite_sum(row[j] for row in X) / n for j in range(p)]
centered = [checked(row[j] - means[j] for row in X) for j in range(p)]
if any(all(v == 0 for v in column) for column in centered):
raise ValueError("remove constant predictor columns")
scales = checked(math.sqrt(finite_sum(v * v for v in column) / n)
for column in centered)
if any(scale == 0 for scale in scales):
raise ArithmeticError("predictor scale underflows to zero")
columns = [checked(v / scale for v in column)
for column, scale in zip(centered, scales)]
beta, residual = [0.0] * p, checked(v - ymean for v in y)
for _ in range(max_sweeps):
for j, column in enumerate(columns):
z = finite_sum(x * (r + x * beta[j]) for x, r in zip(column, residual)) / n
new = math.copysign(max(abs(z) - penalty, 0.0), z)
difference = new - beta[j]
checked([new, difference])
residual = checked(r - x * difference for x, r in zip(column, residual))
beta[j] = new
violation = 0.0
for b, column in zip(beta, columns):
score = finite_sum(x * r for x, r in zip(column, residual)) / n
gap = (abs(score - penalty * math.copysign(1, b)) if b != 0
else max(abs(score) - penalty, 0.0))
checked([gap])
violation = max(violation, gap)
if violation <= tol:
original = checked(b / s for b, s in zip(beta, scales))
intercept = checked([ymean - finite_sum(b * m for b, m in zip(original, means))])[0]
return intercept, original, beta, violation
raise RuntimeError("KKT tolerance not reached")
X = [[1, 1], [1, -1], [-1, 1], [-1, -1]]
y = [3, 1, -1, -3]
intercept, coefficients, standardized, gap = lasso_coordinate(X, y, 1.2)
assert abs(intercept) < 1e-12
assert abs(coefficients[0] - 0.8) < 1e-12 and coefficients[1] == 0
assert gap < 1e-12
assert all(abs(a - b) < 1e-12 for a, b in
zip(lasso_coordinate(X, y, 0.5)[1], [1.5, 0.5]))
print(intercept, coefficients, gap)
每轮所有坐标的残差更新及KKT核验为 ,预处理及保存标准化列需要 空间;总时间乘实际扫描轮数。λ为正、各列非零时,这类循环精确坐标最小化对该凸可分惩罚问题可求到最优目标;列强相关时可能收敛慢,代码以扫描上限和KKT容差报告实际结果。浮点容差只给数值近似的最优性检查,不能保证每个参数在病态设计下都有同样小的误差。[2]
Lasso的零系数也不等于某变量在现实中没有作用。若两列完全相同,目标只依赖系数和;当最佳和为正时,许多非负拆分具有相同绝对值和与相同拟合,系数可能不唯一。惩罚强度应结合预测目的和独立验证选取,做变量选择后也不能直接套用事先固定模型的普通回归置信区间。
参考文献
- ↑ Robert Tibshirani(1996),Regression Shrinkage and Selection via the Lasso,§2.1定义与标准化、§2.2正交情形;本篇统一采用平均平方损失和独立推导的坐标更新,访问日期:2026年10月25日。
- ↑ Jerome Friedman、Trevor Hastie、Rob Tibshirani(2010),Regularization Paths for Generalized Linear Models via Coordinate Descent,§2、§2.1,式(1)、(5)–(8),标准化坐标更新与残差维护。访问日期:2026年10月25日。