Least Absolute Shrinkage and Selection Operator(LASSO,最小绝对收缩和选择算子)是什么

概述

把不重要影像特征的系数变力0,不进行后续的分析

详解

我们用一个“给厨师发工资”的故事来理解 LASSO 的原理。

第一步:背景(为什么需要 LASSO?)

想象你开了一家超大型餐厅,为了做出一道完美的菜(预测结果),你招了 1000 个厨师(1000 个影像特征)。

  • 有的厨师负责切菜,有的负责颠勺,有的负责摆盘,有的可能只是在旁边看着。
  • 如果你给所有人都发同样的工资,你会发现成本极高,而且很多人在浑水摸鱼,你根本分不清谁才是真正干活的人。

第二步:LASSO 的绝招——“生存压力”

为了精简团队,你制定了一个极其严苛的工资规则

  1. 基础扣费(惩罚项):每个人只要进厨房,每天必须先扣除 1000 块钱的“场地使用费”。
  2. 按贡献发钱:只有当你对这道菜的美味程度做出了巨大的贡献,你赚到的奖金才能抵消掉那 1000 块钱的扣费。
  3. 负分滚粗(归零):如果你的贡献很小,算下来工资是负的,对不起,你的工资直接计为 0,并且立刻卷铺盖走人。

第三步:结果(特征筛选)

这个规则实行几天后,厨房发生了惊人的变化:

  • 平庸的厨师(没用的特征):他们虽然有点小功劳,但抵不过那 1000 块钱的扣费。为了不倒贴钱,他们全都辞职了。这些人的“工资”(特征权重)变成了 0
  • 重复的厨师(相关的特征):如果有两个厨师干的活一模一样,规则会发现留两个人的扣费太贵了,于是会强迫其中一个走人,只留一个最厉害的。
  • 真正的核心:最后剩下的,就是实力极强的厨师,做出的贡献远超那 1000 块钱的惩罚。

既然你已经理解了“给厨师发工资”的逻辑,我们现在把数学公式请进来。其实 LASSO 的核心就是在一个普通的回归方程后面,加了一个“负重包”

普通的线性回归(OLS)公式长这样:

Y = \beta_1X_1 + \beta_2X_2 + \dots + \beta_nX_n + \epsilon

而 LASSO 的目标,是最小化下面这个总成本函数

Loss = \sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \lambda \sum_{j=1}^{p}|\beta_j|

我们可以把这个公式拆成两部分来理解:

第一部分“准确度惩罚”:\sum_{i=1}^{n}(y_i - \hat{y}_i)^2

  • 名字:残差平方和(RSS)。
  • 通俗解读:这是在衡量你的模型预估得准不准。预估值 \hat{y}_i 离真实值 y_i 越远,这个数值就越大,惩罚也就越重。
  • 目标:逼着模型为了求准,去给特征分配权重(系数 $\beta$)。

第二部分 “权力惩罚” (L1 正则项):\lambda \sum_{j=1}^{p}|\beta_j|

这就是 LASSO 的核心。

  • |\beta_j|:这是每个特征获得的“权重”(系数)的绝对值。
  • \lambda (Lambda):这是惩罚力度系数。它就像是一个“税率”。
  • 通俗解读:模型每想动用一个特征(给它分配权重 $\beta$),就必须交一笔“税”。你给的权重越大,交的税就越多。

为什么这个公式能让特征“变回零”?

这是最关键的地方。为什么加上绝对值之和(L1 范数),就能让没用的特征变成 0,而普通回归不会?

我们用“预算约束”的概念来解释:

  1. 菱形的边界:在数学空间里,L1 正则项限制下的特征权重范围是一个菱形(如果是二维空间)。
  2. 触碰顶点:当我们尝试寻找“最准的效果”与“最低的税收”之间的平衡点时,这条寻找平衡的线非常容易撞到菱形的顶点上。
  3. 坐标轴上的零点:在坐标系中,菱形的顶点都在轴上。撞到轴上,就意味着其中一个特征的权重 \beta 正好等于 0

给零基础同学的“公式版总结”

  • 如果 \lambda = 0:没有税收。模型会为了追求准,疯狂动用所有特征,导致“过拟合”。
  • 如果 \lambda 很大:税收极高。模型为了省钱,只能把绝大多数人的权重都砍成 0,只留最有用的那个,结果可能“欠拟合”。
  • 如果 \lambda 刚刚好:这就是你项目中做的事情。通过交叉验证找到那个完美的税率,把无关紧要的、重复的特征全部“加税”加到破产(权重归零),最后剩下的就是最具预测价值的特征

模拟代码

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Lasso, LassoCV
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 1. 模拟数据生成 (模拟影像组学场景)
# 假设我们有 100 个样本,每个样本提取了 100 个特征
np.random.seed(42)
n_samples, n_features = 100, 100
X = np.random.randn(n_samples, n_features)

# 模拟真实情况:在这 100 个特征中,只有 3 个是真正与结果相关的
# 这里的 3, -4, 2 就是那几个大厨的“真实贡献”
true_weights = np.zeros(n_features)
true_weights[0] = 3
true_weights[1] = -4
true_weights[2] = 2

# 生成标签 y = Xw + 噪声
y = np.dot(X, true_weights) + np.random.normal(0, 1, size=n_samples)

# 2. 数据预处理
# LASSO 对数值大小非常敏感,必须进行标准化(让所有特征都在同一个起跑线上交税)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# 3. 使用 LassoCV 自动寻找最佳的惩罚系数 Lambda (在 sklearn 中叫 alpha)
# LassoCV 使用了“交叉验证”,会自动帮我们试出那个最合适的税率
lasso_model = LassoCV(cv=5, random_state=42).fit(X_train, y_train)

print(f"最佳税率 (Alpha/Lambda): {lasso_model.alpha_:.4f}")

# 4. 观察筛选结果
# 获取所有特征的系数 (beta)
coefficients = lasso_model.coef_
# 统计有多少个系数不为 0
selected_features = np.sum(coefficients != 0)

print(f"原始特征总数: {n_features}")
print(f"LASSO 筛选后的有效特征数: {selected_features}")

# 5. 可视化:看看哪些特征被“砍”掉了
plt.figure(figsize=(10, 6))
plt.stem(np.where(coefficients != 0)[0], coefficients[coefficients != 0], markerfmt='go', label='Selected')
plt.stem(np.where(coefficients == 0)[0], coefficients[coefficients == 0], markerfmt='rx', label='Eliminated')
plt.title("LASSO Feature Selection Results")
plt.xlabel("Feature Index")
plt.ylabel("Coefficient Value (Weight)")
plt.legend()
plt.show()

# 6. 模型评估
train_score = lasso_model.score(X_train, y_train)
test_score = lasso_model.score(X_test, y_test)
print(f"训练集 R^2 分数: {train_score:.4f}")
print(f"测试集 R^2 分数: {test_score:.4f}")

参考