特征选择方法:从冗余消除到信息保留的筛选策略

特征选择方法:从冗余消除到信息保留的筛选策略

一、高维特征空间的工程代价:维度灾难与冗余干扰

在机器学习项目中,特征数量膨胀是常见现象。业务方提供数百个候选特征,数据工程师将原始字段交叉组合后特征数可达数千。然而,更多特征并不意味着更好的模型——无关特征引入噪声、冗余特征增加计算开销、高维空间导致样本稀疏(维度灾难),这些都会损害模型性能与训练效率。

某信用评分项目中,原始特征集包含 1200 个变量,涵盖用户基本信息、交易行为、设备指纹等多个维度。直接将全部特征输入 XGBoost 模型,训练耗时 45 分钟,AUC 为 0.78。经过特征选择后保留 180 个核心特征,训练耗时降至 8 分钟,AUC 提升至 0.81。冗余特征不仅浪费计算资源,还通过引入噪声干扰了模型的有效学习。

特征选择的目标是从候选特征集中筛选出对目标变量最具信息量的子集,同时控制冗余度。不同的选择方法基于不同的信息度量标准,理解其原理差异是正确应用的前提。

二、特征选择方法的三类范式与信息度量

特征选择方法按是否依赖模型训练,分为过滤法(Filter)、包裹法(Wrapper)、嵌入法(Embedded)三类范式。每类范式基于不同的信息度量标准评估特征重要性。

graph TD
    FS[特征选择方法] --> F[过滤法 Filter]
    FS --> W[包裹法 Wrapper]
    FS --> E[嵌入法 Embedded]

    F --> F1[方差阈值: 移除低方差特征]
    F --> F2[相关系数: 移除高相关冗余]
    F --> F3[互信息: 非线性依赖度量]
    F --> F4[卡方检验: 分类特征关联]

    W --> W1[递归特征消除 RFE]
    W --> W2[前向/后向逐步选择]

    E --> E1[L1 正则化: 稀疏选择]
    E --> E2[树模型特征重要性]
    E --> E3[排列重要性]

    F -->|优点| A1[计算快, 不依赖模型]
    F -->|缺点| B1[忽略特征交互]

    W -->|优点| A2[考虑特征组合效应]
    W -->|缺点| B2[计算开销大, 过拟合风险]

    E -->|优点| A3[与训练目标一致]
    E -->|缺点| B3[依赖模型选择]

过滤法:独立评估每个特征与目标变量的统计关联,不依赖任何模型。计算效率高,适合作为初步筛选步骤。但无法捕捉特征间的交互效应——两个单独不显著的特征组合后可能高度预测目标。

包裹法:以特定模型的性能为评价标准,搜索最优特征子集。递归特征消除(RFE)逐步移除最不重要的特征;前向选择逐步添加最有贡献的特征。包裹法考虑了特征组合效应,但计算开销大,且容易对特定模型过拟合。

嵌入法:特征选择与模型训练同步进行。L1 正则化将不重要特征的权重压缩为零;树模型通过分裂增益衡量特征重要性。嵌入法在训练过程中完成选择,效率与效果兼顾。

三、生产级特征选择实现

3.1 过滤法:多指标联合筛选

import numpy as np
import pandas as pd
from sklearn.feature_selection import (
    VarianceThreshold,
    mutual_info_classif,
    mutual_info_regression,
    chi2,
    f_classif
)
from scipy.stats import spearmanr
from typing import List, Tuple, Optional


class FilterSelector:
    """过滤法特征选择器:多指标联合筛选"""

    def __init__(
        self,
        variance_threshold: float = 0.01,
        correlation_threshold: float = 0.95,
        mi_percentile: float = 50,
        task_type: str = "classification"
    ):
        self.variance_threshold = variance_threshold
        self.correlation_threshold = correlation_threshold
        self.mi_percentile = mi_percentile
        self.task_type = task_type

    def select(
        self,
        X: pd.DataFrame,
        y: pd.Series
    ) -> Tuple[pd.DataFrame, dict]:
        """执行过滤法特征选择,返回筛选后数据与报告"""
        report = {}
        selected_cols = set(X.columns)

        # 步骤1:移除低方差特征
        var_selector = VarianceThreshold(threshold=self.variance_threshold)
        var_selector.fit(X)
        low_var_cols = X.columns[~var_selector.get_support()].tolist()
        selected_cols -= set(low_var_cols)
        report["low_variance_removed"] = low_var_cols

        # 步骤2:移除高相关冗余特征
        X_filtered = X[list(selected_cols)]
        redundant_cols = self._find_redundant_features(
            X_filtered, self.correlation_threshold
        )
        selected_cols -= set(redundant_cols)
        report["redundant_removed"] = redundant_cols

        # 步骤3:基于互信息筛选
        X_filtered = X[list(selected_cols)]
        mi_scores = self._compute_mutual_info(X_filtered, y)
        mi_threshold = np.percentile(
            list(mi_scores.values()), self.mi_percentile
        )
        low_mi_cols = [
            col for col, score in mi_scores.items()
            if score < mi_threshold
        ]
        selected_cols -= set(low_mi_cols)
        report["low_mi_removed"] = low_mi_cols
        report["mi_scores"] = mi_scores

        report["final_features"] = list(selected_cols)
        report["reduction_ratio"] = 1 - len(selected_cols) / len(X.columns)

        return X[list(selected_cols)], report

    def _find_redundant_features(
        self,
        X: pd.DataFrame,
        threshold: float
    ) -> List[str]:
        """识别高相关特征组,保留组内与目标相关性最高的特征"""
        corr_matrix = X.corr(method="spearman").abs()
        upper = corr_matrix.where(
            np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
        )

        redundant = []
        for col in upper.columns:
            high_corr_cols = upper.index[
                upper[col] > threshold
            ].tolist()
            for hc in high_corr_cols:
                # 保留方差更大的特征
                if X[col].var() >= X[hc].var():
                    redundant.append(hc)
                else:
                    redundant.append(col)

        return list(set(redundant))

    def _compute_mutual_info(
        self,
        X: pd.DataFrame,
        y: pd.Series
    ) -> dict:
        """计算每个特征与目标的互信息"""
        mi_func = (
            mutual_info_classif
            if self.task_type == "classification"
            else mutual_info_regression
        )
        scores = mi_func(X, y, random_state=42)
        return dict(zip(X.columns, scores))

3.2 包裹法:递归特征消除

from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import warnings


def recursive_feature_elimination(
    X: pd.DataFrame,
    y: pd.Series,
    n_features_to_select: int = 50,
    step: float = 0.1,
    cv: int = 5,
    scoring: str = "roc_auc"
) -> Tuple[List[str], dict]:
    """递归特征消除,带交叉验证评估"""
    estimator = RandomForestClassifier(
        n_estimators=100,
        max_depth=10,
        random_state=42,
        n_jobs=-1
    )

    rfe = RFE(
        estimator=estimator,
        n_features_to_select=n_features_to_select,
        step=step
    )

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        rfe.fit(X, y)

    selected = X.columns[rfe.support_].tolist()
    ranking = dict(zip(X.columns, rfe.ranking_))

    # 交叉验证评估
    X_selected = X[selected]
    cv_scores = cross_val_score(
        estimator, X_selected, y, cv=cv, scoring=scoring
    )

    report = {
        "selected_features": selected,
        "ranking": ranking,
        "cv_score_mean": cv_scores.mean(),
        "cv_score_std": cv_scores.std()
    }

    return selected, report

3.3 嵌入法:树模型与排列重要性

from sklearn.inspection import permutation_importance


def embedded_feature_selection(
    X: pd.DataFrame,
    y: pd.Series,
    importance_threshold: float = 0.01,
    use_permutation: bool = True,
    cv: int = 3
) -> Tuple[List[str], dict]:
    """嵌入法特征选择:树模型重要性 + 排列重要性"""
    model = RandomForestClassifier(
        n_estimators=200,
        max_depth=15,
        random_state=42,
        n_jobs=-1
    )
    model.fit(X, y)

    # 基尼重要性
    gini_importance = dict(zip(X.columns, model.feature_importances_))

    # 排列重要性(更可靠,不受特征尺度影响)
    perm_importance = {}
    if use_permutation:
        perm_result = permutation_importance(
            model, X, y, n_repeats=10,
            random_state=42, cv=cv, scoring="roc_auc"
        )
        perm_importance = dict(zip(
            X.columns, perm_result.importances_mean
        ))

    # 综合筛选
    importance_source = perm_importance if use_permutation else gini_importance
    selected = [
        col for col, imp in importance_source.items()
        if imp >= importance_threshold
    ]

    report = {
        "selected_features": selected,
        "gini_importance": gini_importance,
        "permutation_importance": perm_importance,
        "reduction_ratio": 1 - len(selected) / len(X.columns)
    }

    return selected, report

四、特征选择方法的适用边界与组合策略

特征选择方法的选择需根据数据规模、特征类型与计算预算综合判断。

过滤法的局限:过滤法忽略特征交互,可能遗漏组合特征。例如,特征 A 和 B 单独与目标弱相关,但 A×B 交互项高度相关,过滤法无法识别。此外,方差阈值与相关系数阈值的设定缺乏理论依据,需根据经验调整。

包裹法的过拟合风险:RFE 在训练集上评估特征重要性,可能选择出对训练集过拟合的特征子集。建议使用交叉验证评估特征子集的泛化性能,但计算成本会进一步增加。对于特征数超过 500 的场景,RFE 的计算时间可能不可接受。

嵌入法的模型依赖:树模型特征重要性偏向高基数特征(取值多的特征更容易被选为分裂点),可能高估其重要性。排列重要性更可靠,但计算成本是基尼重要性的 10 倍以上。L1 正则化的稀疏选择依赖正则化强度 $\lambda$,不同 $\lambda$ 值产生不同的特征子集,需通过交叉验证选择。

组合策略:实践中常采用"过滤→嵌入"的两阶段策略。第一阶段用过滤法快速移除明显的低质量特征(低方差、高冗余),将特征数降至数百量级。第二阶段用嵌入法精细筛选,保留对模型贡献最大的特征子集。这种策略兼顾效率与效果,是工业项目的推荐方案。

特征稳定性:不同随机种子或数据划分下,特征选择结果可能不一致。建议多次重复选择过程,统计每个特征被选中的频率,优先保留高频特征。稳定性本身也是特征质量的信号——频繁被选中的特征更可能是真实信号的载体。

五、总结

特征选择的核心目标是在保留预测信息的同时消除冗余与噪声,缓解维度灾难并提升训练效率。过滤法、包裹法、嵌入法三类范式各有适用场景:过滤法适合初步快速筛选,包裹法适合精细搜索最优子集,嵌入法适合与模型训练同步选择。实践中推荐"过滤→嵌入"的两阶段策略,兼顾计算效率与选择质量。特征重要性评估应优先使用排列重要性而非基尼重要性,以避免高基数偏差。建议在特征选择后评估子集稳定性,确保选择结果在不同数据划分下的一致性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值