两列相关 0.999 的特征,真值总共 2。 纯 ℓ1 把它们分成 0.649 与 1.429;加 ℓ2(λ2 等于 5)变成 0.991 与 1.014。 λ2 加到 20,变成 0.915 与 0.912——两个几乎完全平均。 这条性质叫分组效应,它是弹性网比纯 Lasso 更常用的原因之一。
5.1 到 5.4 都在 ℓ1 内部做文章:改指数、改算法、改选择标准。 这一节把 ℓ2 请回来,两个罚项一起用。 它解决的是 ℓ1 最让人头疼的那个毛病:特征相关时随机挑一个、结果不稳定。 代价是多一个超参,调参从一维变成二维。
一、目标函数:两项各管一件事
目标等于残差平方和的一半,加上 λ1 乘系数绝对值之和,加上 λ2 乘系数平方和。 λ1 管稀疏,λ2 管稳定。 两个都是零就退化成普通最小二乘;λ2 是零就是 5.1 的 Lasso;λ1 是零就是 4.2 的岭回归。 所以弹性网不是一个新东西,它是「把前面几节串起来的那条线」。 四个角落分别是:最小二乘、岭回归、Lasso、弹性网。 为什么加了 ℓ2 就稳定?因为 ℓ2 项让目标函数严格凸。 ℓ1 的罚项在系数为零时是「尖」的,多个解可能并列最优(相关特征上就会出现这种并列); 加上 ℓ2 之后,那个二次项把并列的解分开,只剩一个最优点。 这一点在特征数大于样本数时尤其重要:纯 ℓ1 最多只能选出 n 个非零,而弹性网因为 ℓ2 的存在, 可以保住全部 b 个系数(虽然大部分仍然被压小)。
二、分组效应
现象:一组高度相关的特征,纯 ℓ1 会挑一个、把其余的压成零; 弹性网会把它们一起留下,并且系数接近。 原因在 ℓ2 那一项:它对「两个方向分别多大」敏感,而对「它们的和多大」不敏感。 所以把权重平均分给两个方向,ℓ2 罚得最少。 这个性质在生物学里特别有用。 同一个通路的基因表达高度相关,你想要的答案是「这条通路进来了」, 而不是「这条通路里的第 37 号基因进来了」。 纯 ℓ1 给你的恰好是后者——而且每次跑还不一样。 Demo 里纯 ℓ1 分到 0.649 和 1.429,加起来是 2.078。 它没有「只留一个」,但它分得很不均匀。 这个分配取决于坐标下降的扫描顺序,换个种子就变——这就是「不稳定」的具体样子。 另外要说明:分组效应不等于「组选择」。 真正的组选择(一个组要么全进要么全不进)要用组 Lasso,那是另一个罚项。 弹性网只是「倾向于一起留」,不保证。
三、实测:三个 λ2
| 设置 | 第 1 列 | 第 2 列 | 第 3 列 | 第 4 列 |
|---|---|---|---|---|
| 只有 ℓ1 | 0.649 | 1.429 | 负 0.001 | 负 0.000 |
| λ2 等于 5 | 0.991 | 1.014 | 负 0.005 | 负 0.000 |
| λ2 等于 20 | 0.915 | 0.912 | 负 0.014 | 负 0.000 |
| 80 个样本、4 列,前两列是同一个信号加 0.05 的噪声(相关 0.999),后两列是纯噪声。 | ||||
| 真值是 y 等于 2 乘公共信号。 | ||||
| 所以理想解应该是两列各 1.0 左右,加起来 2。 | ||||
| 只有 ℓ1 那一行:0.649 和 1.429,加起来 2.078。 | ||||
| 总和对了,分配错了——而且这个分配是不可复现的。 | ||||
| λ2 等于 5 那一行:0.991 和 1.014,几乎完美。 | ||||
| λ2 等于 20 那一行:0.915 和 0.912,更平均,但总和掉到 1.827。 | ||||
| λ2 太大也在压幅度——这就是「收缩」在起作用。 | ||||
| 后两列三行都是负的极小值,量级 1e-3 到 1e-2。 | ||||
| 它们其实是零,只是坐标下降在零附近留了一点点残渣。 | ||||
| 判断「是不是零」不能靠「打印出来是不是 0.000」,要看量级。 |
四、怎么调两个 λ
实践做法:固定一个比例,只调一个强度。 常见的是令 λ2 等于 α 乘 λ1,然后只调 λ1。 另一个做法是二维网格,但那要多花一个数量级的计算量。 还有一个更省事的经验:先用交叉验证把 λ1 定下来,再看解稳不稳。 如果同一批相关特征的系数在多次重采样里来回跳,就加 λ2。 这是「按症状下药」,比盲目调参有效。 标准化在这里比在纯 ℓ1 里更要紧。 因为 λ2 罚的是平方和,量纲大的特征平方之后更吃亏,收缩更狠。 不标准化直接用,等于让不同特征面对不同的有效 λ——这完全是伪影。 最后一点:弹性网的坐标下降实现简单到只需改一行。 分母从「列平方和」变成「列平方和加 λ2」,其余一模一样。 Demo 的代码里就这一处差别,值得对着 5.1 的代码比一比。
五、动手 Demo:ℓ1 加 ℓ2:纯 ℓ1 分到 0.649 与 1.429,λ2 等于 5 后变成 0.991 与 1.014
80 个样本、4 列。前两列是同一个公共信号加 0.05 的噪声(相关 0.999),后两列是纯噪声; y 等于 2 乘公共信号加噪声。 固定 λ1 等于 1.0,只改 λ2:不设、5、20 三档,看两列相关特征上的系数怎么分配。
def enet(lam1, lam2, it=600):
b = X.shape[1]
w = np.zeros(b)
col = (X * X).sum(0) + lam2 + 1e-12 # 分母多了一个 λ2,其余不变
r = y - X @ w
for _ in range(it):
for j in range(b):
r += X[:, j] * w[j]
rho = float(X[:, j] @ r)
w[j] = soft(rho, lam1) / col[j]
r -= X[:, j] * w[j]
return w
实跑输出:
--------------------------------------------------------------------
l1 加 l2:只加 l1 时两列相关特征分到 0.649 与 1.429,加 l2 后一起留
--------------------------------------------------------------------
数据 80 个样本,前两列是同一个信号加微噪声(相关 0.999)
真值 y 等于 2 乘公共信号,后两列是纯噪声
l1 的罚项 固定 λ1 等于 1.0,只改 λ2
设置 第 1 列 第 2 列 第 3 列 第 4 列
-----------------------------------------------------------
只有 l1 0.649 1.429 -0.001 -0.000
l1 加 l2,λ2 等于 5 0.991 1.014 -0.005 -0.000
l1 加 l2,λ2 等于 20 0.915 0.912 -0.014 -0.000
**只有 l1 那一行:两列相关特征被分到了 0.649 和 1.429。**
**注意它们加起来约等于 2,也就是把系数「平均分给了两个几乎一样的列」。**
**这不是稳定行为:换一个随机种子、或者换一下坐标下降的顺序,这一对分配就会变,甚至可能只留一列、另一列归零。**
**这就是 l1 在相关特征上的老毛病:它倾向于「随机挑一个」,而不是「一起留」。**
**加 λ2 等于 5 之后,两列分别是 0.991 和 1.014,比之前更接近平均。**
**λ2 加到 20,变成 0.915 和 0.912;l2 项把两个方向一起缩,等于强迫它们共享同一份权重,于是倾向于平均分配。**
**这条性质叫「分组效应」,它是弹性网(l1 加 l2)在实践中比纯 Lasso 更常用的原因之一。**
**另一个原因是稳定性:纯 l1 在特征数超过样本数时最多只能选出 n 个变量,加了 l2 就没有这个限制。**
要说明:**相关列是我造的**(同一个信号加 0.05 的噪声)。
**真实数据里相关群的规模可能很大,「一整个基因通路一起进模型」这类需求,正好对应分组效应。**
对比 5.1 的 lasso_cd:只多了 + lam2 这一处。
整个弹性网的全部实现就是这四个字符。
这也是为什么弹性网在实践中这么受欢迎——它几乎没有额外的工程成本。
为什么加在分母上是对的?
因为对第 j 个坐标,目标函数里那一项是「列平方和加 λ2」乘 w 的平方的一半,
所以一维子问题的分母就是它。
写成 col 预计算一次,600 轮都不用重算。
col 里的 1e-12 在这里还有一层用处:
如果某列全是零、λ2 又设成零,分母会是 0;加了 λ2 之后通常不会,但仍留个底。
注意三行的总和:2.078、2.005、1.827。
λ2 等于 5 时总和最接近真值 2,λ2 等于 20 时掉到 1.827。
所以 λ2 不是越大越好,它也在收缩。
「加 λ2 让结果更准」这句话只在「纯 ℓ1 分配不均」这个意义上成立。
这个 Demo 的结论比 5.1 的强,因为它测的不是「误差」而是「同一份权重怎么分配」。
这类「结构性质」必须自己造数据才看得见——真实数据里你根本不知道真值该怎么分。
要说明:相关列是我造的(同一个信号加 0.05 的噪声,相关 0.999)。
真实数据里相关度可能在 0.7 到 0.95 之间,分组效应会比这里弱,但方向是一样的:越相关,
越倾向于平均分配。
30 秒实验
- 把前两列的噪声从 0.05 加到 0.5(相关降到 0.8 左右),再跑一遍。分组效应还在吗?
- 把 λ1 从 1.0 加到 3.0,λ2 保持 5。两列会不会被一起压成零?
- 把样本数从 80 降到 20,λ2 设成 0,再跑一遍。坐标下降还稳定吗?加回 λ2 等于 5 之后呢?
常见误区
- 「弹性网就是 Lasso 加一点岭回归,没什么新东西」。 数学上确实是两项相加,但性质上它多了一个 Lasso 没有的东西:严格凸。 这让解唯一、让相关特征平均分配、让 b 大于 n 时也能保住全部系数。
- 「分组效应等于组选择」。 不等于。弹性网只是「倾向于一起留」,不保证整组进或整组出。 真正的组选择要用组 Lasso,罚的是「每组系数的范数」而不是「每个系数」。
- 「λ2 越大越稳定,所以越大越好」。 λ2 大到一定程度就退化成岭回归,从此不再稀疏。 Demo 里 λ2 等于 20 时总和已经从 2.005 掉到 1.827。 稳定是有代价的,代价就是幅度被压小、稀疏性被削弱。
- 「两个 λ 分别调最优就行」。 不行,它们耦合。 固定 λ1 调 λ2 得到的最优,和固定 λ2 调 λ1 得到的最优往往不是同一组。 实践做法是固定比例(λ2 等于 α 乘 λ1)只调一个,或者直接上二维网格。
自测三问(点开对答案)
1. 为什么加了 ℓ2 之后,相关特征上的系数会趋于平均? —— **因为 ℓ2 罚的是平方和。给定总和不变,两个数越不平均,平方和越大。** **比如 0.649 和 1.429 的平方和是 2.46,而 0.991 和 1.014 的平方和只有 2.01。** **所以 ℓ2 项会主动把权重往平均的方向推。** 2. 弹性网为什么能突破「最多选 n 个变量」的限制? —— **因为 ℓ2 项让目标函数严格凸,解不再被「最多 n 个非零」这个线性代数约束卡住。** **纯 ℓ1 的解是「用最少的列拼出 y」,100 个方程最多定 100 个未知数;** **加了 ℓ2 之后,解在所有方向上都被唯一确定,可以全部非零。** 3. 如果两列特征相关度只有 0.3,还需要弹性网吗? —— **一般不需要。** **相关度低的时候 ℓ1 的分配本来就稳定,加 ℓ2 只会多一个要调的超参。** **判断标准是「重采样时同一批特征的系数跳不跳」,跳才加 ℓ2。** **盲目上弹性网,等于用复杂度换一个不存在的问题的解法。**第 5 章讲完了「罚系数」。下一章换一个位置罚:不罚系数,罚误差。 当数据里混进离群点时,平方误差会被那几个点彻底带偏——这就是鲁棒学习要解决的问题。
- ☐ (本章完,下一节 6.1 6.1)