做特征选择的时候,我对“嵌入法”的第一印象是名字取得太欺负人。搜索栏里敲一个“Embedded”,出来的全是segger embedded studio、IAR Embedded Workbench这类嵌入式开发工具,乍一看跟机器学习完全不沾边。但嵌入法在特征选择里反而是一个特别实用、特别“省事”的方法:它把特征选择和模型训练揉在了一起,训练完模型,特征的重要性也就出来了,不需要像包裹法那样为了挑特征反复训练几十次模型,也比过滤法更贴近最终的预测效果。如果你正在处理一张几百上千列的表,不想在特征工程上投入太多算力,想快速定位一版可解释又不差的特征子集,这篇文章大概能帮你把嵌入法吃透。
1. 嵌入法到底在特征选择里扮演什么角色
1.1 过滤法、包裹法和嵌入法,本质区别在“模型”
为了讲清楚嵌入法,最好先把三种特征选择方法放在一张桌子上看。过滤法是最早接触的:方差过滤、相关系数过滤、互信息法,都是在建模之前,依据某个统计量给特征打分,然后按分数截断。它的优点是快,但缺点也很明显——打分过程和最终使用的模型没有任何关系。两个特征单独看起来和标签的相关性都接近零,可是放在一起组合后,对标签的判别能力非常强,过滤法一般发现不了这种情况。
包裹法则完全反过来,它把模型当作“评委”,用递归特征消除(RFE)或者贪心搜索的方式,一次次训练模型,根据模型性能决定去留。效果往往更好,因为每次评价都贴近真实需求,但代价是计算开销很大,特征一多就容易让人等到崩溃。
嵌入法正好在两者之间:它不额外开一个筛选循环,而是在模型训练的目标函数或训练规则里,直接加入“让不重要的特征权重变小甚至变零”的机制。模型训练收敛后,既得到了预测模型,也得到了一组特征权重或重要度分数。最典型的两个代表就是L1正则化模型和基于树模型的特征重要性。
1.2 为什么嵌入法的效率比包裹法高一个量级
嵌入法的“省事”不是感觉,而是来自训练过程本身。比如L1逻辑回归,它的目标函数里本来就带一个对所有特征权重的绝对值惩罚。优化器在训练过程中,会不断把一些特征的权重推向零。这意味着特征选择不是训练之后额外做的步骤,而是训练过程中自然发生的。你只需要花训练一个模型的时间,就完成了选择。
树模型更明显。随机森林、XGBoost、LightGBM训练时会计算每个特征的分裂次数、分裂增益和覆盖样本数,这些统计量本身就可以作为重要性分数。训练完,直接读feature_importances_属性就行,不需要额外的循环搜索。对一个1000维特征、几万样本的表格任务,这种开销比RFE小太多了。
所以,在很多实际项目里,我默认的第一道精筛工具就是嵌入法,尤其当特征是几十上百维的时候,性价比极高。
1.3 搜索“Embedded”容易跑偏,跟嵌入式开发是两码事
这是一个小提醒。因为嵌入法的英文是Embedded,和嵌入式系统是同一个词,很多人一搜索就跑到嵌入式开发工具链去了。这里说的Embedded是指“特征选择过程嵌入在模型训练内部”,不是“嵌入式硬件上的模型部署”。如果英文搜索想少走弯路,建议用“L1 regularization feature selection”、“feature importance based feature selection”这类关键词,能直接把有用的资料筛出来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 嵌入法能自动筛特征的底层逻辑
2.1 L1正则化的稀疏解是怎么来的
L1正则化做特征选择的原理,很多人知道“能让权重变成零”,但为什么会变成零,值得再说透一点。我们优化的目标函数一般是:
Loss(w) + λ * Σ|w_j|
这个Σ|w_j|就是L1惩罚项。如果把二维权重空间画出来,L1惩罚项的等值面是一个以原点为中心的菱形,菱形的四个角正好落在坐标轴上;而L2惩罚项的等值面是圆形,表面光滑没有角。
优化过程可以理解成在“训练集损失尽量小”和“权重不要太大”之间找平衡。当训练损失那一圈的等高线和L1菱形相交时,非常容易碰到菱形的角。菱形的角意味着某个维度的权重恰好是0,于是这个特征就被“开除”了。
从优化算法角度看也很直观。用坐标下降法求解L1问题时,每一步都会对单个权重做软阈值操作:如果当前权重减去学习率乘以梯度的结果,绝对值小于λ,就直接置为0。所以L1正则化在训练过程中做的,本身就是“选或不选”的硬决策。
2.2 树模型的特征重要性到底是从哪冒出来的
虽然外表看起来不像正则化,但树模型的特征重要性同样是训练过程“内嵌”出来的。比如XGBoost,训练时会记录每个特征在所有树分裂点上的累计收益。gain类型的重要性,就是把该特征参与分裂带来的目标函数增益累加起来;weight类型是分裂次数;cover类型是分裂点覆盖的样本数。不同importance_type得到的排名会有差异,使用时别混为一谈。
随机森林的feature_importances_则是基于基尼不纯度减少量。每次在某个特征上做分裂,都能降低节点的不纯度,把这种降低累加并归一化,就得到重要性分数。这些分数本质上也是模型对特征的“评价”,所以算作嵌入法。
不过要注意:这些重要性分数都是启发式的,它们衡量的是“在当前模型结构和训练数据下,特征对模型预测的贡献程度”,不等于特征的因果重要性。这一点在后面的坑里会重点展开。
2.3 标准化/归一化对系数型嵌入法的影响
L1正则化做特征选择时,特征尺度是一个特别容易被忽视的问题。线性模型训练时,权重w和特征x是相乘的关系,特征量纲越大,对应权重只要取很小的值就能产生同样的预测效果。如果不做标准化,L1惩罚会把大尺度特征的小权重当成“该被淘汰的项”直接压成零,结果真正重要的特征反而被误杀。
所以,用L1逻辑回归或Lasso做特征选择前,标准动作是先对数值特征做StandardScaler,让每个特征都处在差不多的量纲下。实际写代码时,把StandardScaler和线性模型塞进同一个Pipeline里,避免在训练集和测试集上分别计算均值和方差。
树模型不需要这一步,因为决策树做分裂时按阈值切分,不受单一特征尺度影响。这也是为什么混合使用L1和树模型时,有些人会疑惑结果排名差很多。
3. 用Python在一个示例数据集上把嵌入法跑通
3.1 构造一个带噪声的高维示例数据
先造数据,有20个有效特征,180个噪声特征。这样能直观看出嵌入法能否找回有效特征。
python复制from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(
n_samples=1000,
n_features=200,
n_informative=20,
n_redundant=0,
n_repeated=0,
n_clusters_per_class=1,
flip_y=0.05,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
3.2 用L1逻辑回归做特征选择
先用Pipeline串起标准化和L1逻辑回归,再用SelectFromModel把非零权重对应的特征挑出来。L1逻辑回归的稀疏解会让很多特征的权重精确为0,SelectFromModel默认会过滤掉这些特征。
python复制from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectFromModel
pipe = Pipeline([
("scaler", StandardScaler()),
(
"select",
SelectFromModel(
LogisticRegression(
penalty="l1",
C=0.05,
solver="liblinear",
max_iter=5000,
random_state=42,
)
),
),
("clf", LogisticRegression(max_iter=5000)),
])
pipe.fit(X_train, y_train)
selected_mask = pipe.named_steps["select"].get_support()
print(selected_mask.sum())
C值对L1的正则强度影响很大。实际调参时,可以遍历几个C,观察每个C下选出的特征数量:
python复制import numpy as np
for C in [1.0, 0.5, 0.1, 0.05, 0.01, 0.001]:
model = LogisticRegression(
penalty="l1", C=C, solver="liblinear", max_iter=5000, random_state=42
)
tmp_pipe = Pipeline([("scaler", StandardScaler()), ("clf", model)])
tmp_pipe.fit(X_train, y_train)
n_selected = int(
np.sum(np.abs(tmp_pipe.named_steps["clf"].coef_.ravel()) > 1e-5)
)
print(f"C={C}, selected={n_selected}")
在这个人造数据上,C越小,被选特征越少。具体数值会随随机种子变化,但趋势很稳定:正则越强,特征越稀疏。
要注意的是,liblinear求解器支持L1惩罚,默认的lbfgs求解器不支持,使用时会直接报错。这是L1逻辑回归最常见的一个坑。
3.3 用随机森林的特征重要性做嵌入法
树模型做嵌入法不需要标准化。直接把随机森林训练好,读feature_importances_,再交给SelectFromModel做阈值过滤。
python复制from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(
n_estimators=300,
random_state=42,
n_jobs=-1,
)
rf.fit(X_train, y_train)
selector = SelectFromModel(rf, threshold="median")
selector.fit(X_train, y_train)
print(selector.get_support().sum())
threshold="median"表示保留重要性大于中位数的特征。实际操作中,我一般不会直接套用mean或median,而是先把所有特征的重要性按降序排出来,画一条曲线看拐点,再定阈值。因为重要性分布经常是长尾的,少数特征贡献了绝大部分重要性,mean阈值会把一堆弱特征也放进来。
如果用的是XGBoost,记得指定importance_type="gain",这样拿到的才是基于分裂增益的重要性,而不是单纯的分裂次数。
3.4 用交叉验证挑选稳定特征
单次训练选出来的特征集可能会有偶然性。更稳的做法是多次重采样,记录每个特征被选中的频率,最后只保留出现频率高于某个阈值的特征。这其实就是简化版的特征稳定性选择。
python复制from sklearn.model_selection import RepeatedKFold
feature_count = np.zeros(X_train.shape[1])
n_repeats = 5
n_splits = 5
rkf = RepeatedKFold(
n_splits=n_splits,
n_repeats=n_repeats,
random_state=42,
)
for train_idx, val_idx in rkf.split(X_train):
fold_pipe = Pipeline(
[
("scaler", StandardScaler()),
(
"clf",
LogisticRegression(
penalty="l1",
C=0.05,
solver="liblinear",
max_iter=5000,
random_state=42,
),
),
]
)
fold_pipe.fit(X_train[train_idx], y_train[train_idx])
coef = fold_pipe.named_steps["clf"].coef_.ravel()
feature_count += (np.abs(coef) > 1e-5).astype(int)
stability = feature_count / (n_splits * n_repeats)
print("stability > 0.8:", np.sum(stability > 0.8))
用这个方法,能把那些被随机噪声“带出来”的特征过滤掉。尤其是在小样本高维场景下,稳定频率比单次选择结果可靠得多。
4. 嵌入法被神化之前,先看看这些坑
4.1 系数小不代表对预测不重要,单靠权重排名有盲区
嵌入法给出的权重或重要性,是基于特定模型假设的。L1逻辑回归只能捕捉线性关系,如果某个特征和标签的关系是“x大于0时标签概率明显上升,x小于0时基本无影响”,线性模型可能给出一个很小的系数,但树模型会把它当成重要特征。
这不是某个方法坏了,而是模型能力不同。选嵌入法之前,先想清楚你最终要用的模型是什么类型。如果是线性模型主导的风控评分卡,L1选择结果很合理;如果最终模型是GBDT,用树重要性更贴近实际决策规则。两种方法得出的排名不一致时,不一定是代码写错了。
4.2 共线性特征组会让L1“随机翻牌”
高度相关的特征组内,L1正则化倾向于只保留其中一个,但具体保留哪一个,往往是由噪声决定的。数据稍微变化,选出来的特征就换了。树模型也不太省心,它会把重要性分散到一组相关特征上,让每个特征的重要性都被平均稀释,看起来都不突出。
应对方法很朴素:先做相关性分析,把相关系数高于0.8的特征聚成簇,从每个簇里挑一两个代表特征,再进嵌入法。这样选出来的特征不仅稳定,后续给业务解释的时候也更好说:不是“系统随机选了一个”,而是“这几个特征高度相关,我们保留了最有代表性的一个”。
4.3 类别型特征和缺失值处理会直接影响嵌入法结果
嵌入法对数据预处理非常敏感。一个高基数类别特征如果做独热编码,会被拆成几十列,树模型和L1都容易倾向于它,因为拆分后的信息容量变大了。但高基数不代表高价值,可能只是枚举值多而已。
类别型特征建议优先考虑目标编码,或者用支持类别特征的库做内部编码。目标编码很容易泄漏,必须放在交叉验证内部去做,不能先在全量数据上算好均值再切分。缺失值填充同样会影响L1对特征权重的惩罚,不同填充规则可能让同一个特征从“被选中”变成“被淘汰”,所以预处理规则要固定,且跟着Pipeline走。
4.4 小样本高维场景下嵌入法选择结果的不稳定
当样本量只有几百、特征却有几万的时候,L1和树模型都不能保证每次都选同一组特征。一次训练选出来的特征集,换一批训练数据就变了。这不代表方法失效,而是数据能提供的信息不够。
解法就是稳定性选择:多次重采样,记录每个特征被选中的频次,用频次阈值替代单次选择。前面3.4的代码就是这种思路。高频被选中的特征,通常才是真正和标签有关联的稳定特征。
5. 嵌入法不是孤军奋战,联用才是日常
5.1 先用过滤法粗筛,再用嵌入法精筛
实操中,不要一上来就对几百上千列直接跑嵌入法。推荐先做三道过滤:删除缺失率超过50%的列;删除标准差接近0的常数列;计算相关性矩阵删除高相关列。这样先减少无信息特征和共线性干扰,嵌入法更容易收敛到稳定的特征集。
过滤法很快,几乎不费时间。它解决的是“显而易见的问题”,比如全空列、全等列、重复列。嵌入法解决的是“模棱两可的问题”,比如两个特征单独看都还行,但组合起来谁更值得留。先清场再精筛,逻辑上更顺,效果也更稳。
5.2 嵌入法输出作为包裹法搜索的起点
如果业务要求极致效果,可以先用L1或树重要性选出前50个特征,再在这50个特征上用RFECV做小规模包裹法搜索。因为候选集变小,RFE的循环次数和每次训练的时间都能接受。
我通常给这种“嵌入式粗筛+包裹式精炼”留一个步骤:先用SelectFromModel粗选,再在粗选结果上跑RFECV。这样既保留包裹法的效果,也避免它在全特征空间里裸奔。对于几百维特征的数据,这套组合拳能在合理时间内拿到一个接近最优的特征子集。
5.3 特征选择必须待在交叉验证内部
这是永远不要省的一条:如果你在全部数据上先做了一次嵌入法,把特征列表固定下来,再对这个新数据集跑交叉验证,那评估结果大概率虚高。因为特征选择已经偷看了测试折的信息。
正确做法是把特征选择器放进Pipeline里,让每一折训练时都在训练折上重新fit选择器,测试折只被transform。代码上只需要把SelectFromModel和模型一起包进Pipeline,交叉验证就会自动遵守这条规则。业务上也要提醒自己:任何用到标签信息做预处理的操作,都必须放进每一折里面,而不是先全量跑一遍。
6. 我从几个实际项目里总结出的嵌入法使用心得
6.1 随机森林重要性偏好数值型特征,需要额外校验
真实项目经验:在一个信贷数据集里,一个纯数值型高基数特征在随机森林的feature_importances_排名前五,但它本质上是某个业务系统的流水号,不可能是好特征。原因就是树模型喜欢连续、复杂分裂点,容易把有价值的方差分配给这种特征。
我后来用置换重要性(permutation importance)做对照:打乱这个特征后重新预测,模型性能几乎没有下降,于是把它删了。所以树重要性不是终点,必要时用置换重要性或模型无关指标交叉验证,特别是当你发现某个数值型特征的排名高得反常时,一定要多留个心眼。
6.2 C值的选择要配合业务需要,而不是只看CV分数
用L1做特征选择时,很多人直接调C让模型AUC最高。但在风控、医疗场景里,特征的稀疏性和可解释性同样重要。特征越少,模型越容易解释,线上也就越稳定。
我习惯的做法是画出C值与特征数的曲线,再在同一张图上叠上交叉验证AUC曲线,观察AUC下降不明显而特征数已经明显减少的拐点。选这个拐点附近的C值,既能压缩特征,又不牺牲太多效果,业务同事也更容易接受。纯靠CV分数选最优C,往往会保留一堆无关紧要的特征,给后续特征监控和上线评审增加负担。
6.3 上线后别把特征选择流程丢在训练脚本里
嵌入法的坑不只是训练阶段。模型上线时,如果只保存了最终训练好的模型,忘了保存特征选择器,线上服务可能会拿全量特征去做预测,维度对不上直接报错。
正确做法是把“标准化+特征选择+模型”整个Pipeline统一保存,线上和线下共用同一套逻辑。我遇到过好几次线上模型异常,最后查出来都是特征选择逻辑不一致导致,这种问题越早发现越省心。所以从第一天写训练代码时,就把Pipeline当做一个整体来维护,而不是把特征选择当成一个独立步骤放在外面。
如果你也正在高维表格数据里做特征选择,不妨先把嵌入法作为主力,再用稳定性分析和交叉验证把关。我自己的习惯是:先过滤法清场,再L1或树重要性嵌入精筛,最后在候选特征上做稳定性确认。这样一套下来,特征数量通常能压到原来的十分之一以下,并且效果不会明显缩水。以后有机会我再把稳定性选择背后的理论拆开聊聊,这篇文章先写到这儿。
