特征选择最快入门:方差阈值法|原理+公式+Python实战全攻略
方差阈值法是机器学习中最简单、最高效的特征筛选方法,核心逻辑是"去掉没波动的特征"------方差接近0的特征几乎没有区分度,相当于"无效信息",删除后能减少模型复杂度、加速训练。它无需复杂统计检验,仅通过方差大小就能完成筛选,是特征选择的"第一步必备工具"。
一、方差阈值法是什么?一句话看懂
方差阈值法 = 筛选"有波动"的特征,丢掉"没波动"的特征。
核心逻辑:
- 方差是衡量数据"波动大小"的指标(波动越大,方差越大)
- 设定一个"阈值",方差小于阈值的特征 → 几乎无波动 → 无信息量 → 删除
- 方差大于等于阈值的特征 → 有明显波动 → 含有效信息 → 保留
形象比喻:筛选有用的信号
把每个特征想象成一个"信号源":
- 方差大的特征:信号强弱变化明显(比如"不同学生的考试成绩":50~100分)
- 方差小的特征:信号几乎不变(比如"全班学生的性别(0=男/1=女)":大部分是0)
- 方差阈值法:只保留"信号变化明显"的源,关掉"信号无变化"的源
生活例子
假设数据集有两个特征:
- 特征1:"学生是否为中国人"(全班99%是中国人,编码为1)→ 方差极小
- 特征2:"学生的数学成绩"(50~100分)→ 方差较大
设定阈值0.1,特征1会被删除(方差<0.1),特征2会被保留(方差≥0.1)。
二、核心原理与公式(必学)
1. 方差的定义(基础回顾)
对于一个特征 (X),方差 (\sigma^2(X)) 衡量其数值的离散程度,公式如下:
σ2(X)=1n∑i=1n(Xi−μX)2 \sigma^2(X) = \frac{1}{n} \sum_{i=1}^n (X_i - \mu_X)^2 σ2(X)=n1i=1∑n(Xi−μX)2
- (n):样本数量
- (XiX_iXi):第 i 个样本的特征值
- (μX\mu_XμX):特征(X)的均值(μX=1n∑i=1nXi\mu_X = \frac{1}{n} \sum_{i=1}^n X_iμX=n1∑i=1nXi)
2. 方差与信息量的关系
- 方差越大 → 特征值波动越明显 → 能区分不同样本 → 信息量越多
- 方差越小 → 特征值几乎无变化 → 无法区分样本 → 信息量越少
- 方差=0 → 所有样本的特征值相同 → 完全无信息量 → 必须删除
3. 阈值设定与筛选规则
- 设定阈值(\theta)(如0.01、0.1、1等,需根据数据调整)
- 筛选规则:
- 若 (σ2(X)<θ\sigma^2(X) < \thetaσ2(X)<θ) → 删除特征(X)
- 若 (σ2(X)≥θ\sigma^2(X) \geq \thetaσ2(X)≥θ) → 保留特征(X)
4. 关键前提:数据标准化(重要!)
方差阈值法对特征尺度敏感(比如"收入(万元)"和"年龄(岁)"的方差单位不同),因此必须先标准化:
- 标准化后:每个特征的均值=0,方差=1(消除量纲影响)
- 未标准化:可能因尺度差异误删重要特征(如收入方差10000,年龄方差10,阈值0.1会误删年龄)
三、方差阈值法完整流程(必背)
- 数据预处理 :
- 区分特征和目标变量
- 处理缺失值(填充/删除)
- 标准化特征(消除量纲影响)
- 计算特征方差:对每个特征单独计算方差
- 设定阈值:根据数据特性或交叉验证选择阈值(\theta)
- 筛选特征:保留方差≥(\theta)的特征,删除方差<(\theta)的特征
- 模型训练与评估:用筛选后的特征训练模型,对比筛选前后性能
四、Python 完整实战:鸢尾花数据集特征选择
1. 导入库+加载数据
python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import VarianceThreshold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix
# 加载鸢尾花数据集(4个特征,3类目标)
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = pd.Series(iris.target, name='species')
print("样本数:", X.shape[0])
print("原始特征数:", X.shape[1])
print("特征名:", list(X.columns))
print("目标类别:", list(iris.target_names))
X.head()
2. 数据可视化(了解特征分布)
python
# 绘制特征散点图矩阵(查看特征区分度)
plt.figure(figsize=(10, 8))
sns.pairplot(pd.concat([X, y], axis=1), hue='species', palette='Set2')
plt.suptitle("鸢尾花数据集特征分布", y=1.02)
plt.show()
# 计算原始特征的方差(未标准化)
raw_variances = X.var()
print("\n未标准化特征方差:")
print(raw_variances.sort_values(ascending=False))
3. 数据预处理(标准化)
python
# 标准化特征(方差阈值法必需步骤)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled_df = pd.DataFrame(X_scaled, columns=iris.feature_names)
# 查看标准化后的方差(理论上接近1)
scaled_variances = X_scaled_df.var()
print("\n标准化后特征方差:")
print(scaled_variances.sort_values(ascending=False))
4. 方差阈值法特征选择(固定阈值)
python
# 设定方差阈值(示例:0.5)
threshold = 0.5
selector = VarianceThreshold(threshold=threshold)
X_selected = selector.fit_transform(X_scaled)
# 查看筛选结果
selected_mask = selector.get_support() # 保留特征的掩码(True=保留)
selected_features = X.columns[selected_mask].tolist()
deleted_features = X.columns[~selected_mask].tolist()
print(f"\n=== 方差阈值法筛选结果(阈值={threshold})===")
print(f"保留的特征: {selected_features}")
print(f"删除的特征: {deleted_features}")
print(f"筛选后特征数: {X_selected.shape[1]}")
5. 特征方差可视化(对比阈值)
python
plt.figure(figsize=(10, 6))
# 绘制标准化后的特征方差
bars = plt.bar(iris.feature_names, scaled_variances, color='skyblue')
# 标记删除的特征(红色)
for i, bar in enumerate(bars):
if not selected_mask[i]:
bar.set_color('red')
# 绘制阈值线
plt.axhline(y=threshold, color='black', linestyle='--', label=f'阈值={threshold}')
plt.title("标准化特征方差分布(红色=删除特征)", fontsize=12)
plt.xlabel("特征")
plt.ylabel("方差")
plt.xticks(rotation=45)
plt.legend()
plt.grid(alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
6. 模型训练与性能对比
python
# 定义训练评估函数
def train_eval(X_data, y_data, title):
# 拆分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(
X_data, y_data, test_size=0.3, random_state=42, stratify=y_data
)
# 训练逻辑回归模型
model = LogisticRegression(max_iter=200, random_state=42)
model.fit(X_train, y_train)
# 评估
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"\n=== {title} ===")
print(f"准确率: {acc:.4f}")
return acc, model, X_test, y_test, y_pred
# 1. 原始特征(4个)训练
acc_original, model_original, X_test_original, y_test_original, y_pred_original = train_eval(
X_scaled, y, "原始特征模型"
)
# 2. 筛选后特征(n个)训练
acc_selected, model_selected, X_test_selected, y_test_selected, y_pred_selected = train_eval(
X_selected, y, f"方差阈值筛选后模型(保留{len(selected_features)}个特征)"
)
# 性能对比
print("\n=== 性能对比 ===")
print(f"原始特征准确率: {acc_original:.4f}")
print(f"筛选后特征准确率: {acc_selected:.4f}")
print(f"特征数减少比例: {1 - len(selected_features)/len(X.columns):.1%}")
7. 混淆矩阵可视化(筛选后模型)
python
cm = confusion_matrix(y_test_selected, y_pred_selected)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=iris.target_names, yticklabels=iris.target_names)
plt.title(f"筛选后模型混淆矩阵(特征:{', '.join(selected_features)})", fontsize=12)
plt.xlabel("预测类别")
plt.ylabel("真实类别")
plt.tight_layout()
plt.show()
8. 优化:交叉验证选择最优阈值
python
# 定义阈值范围(0~1,取20个值)
thresholds = np.linspace(0, 1, 20)
cv_scores = []
for thresh in thresholds:
# 按阈值筛选特征
selector = VarianceThreshold(threshold=thresh)
X_temp = selector.fit_transform(X_scaled)
# 交叉验证(5折)
if X_temp.shape[1] == 0: # 避免特征被全部删除
cv_scores.append(0)
continue
scores = cross_val_score(
LogisticRegression(max_iter=200, random_state=42),
X_temp, y, cv=5, scoring='accuracy'
)
cv_scores.append(scores.mean())
# 找到最优阈值
best_idx = np.argmax(cv_scores)
best_threshold = thresholds[best_idx]
best_score = cv_scores[best_idx]
print(f"\n=== 交叉验证最优阈值 ===")
print(f"最优阈值: {best_threshold:.4f}")
print(f"最优交叉验证准确率: {best_score:.4f}")
# 可视化阈值与准确率关系
plt.figure(figsize=(10, 6))
plt.plot(thresholds, cv_scores, marker='o', color='green', linewidth=2)
plt.axvline(x=best_threshold, color='red', linestyle='--', label=f'最优阈值={best_threshold:.4f}')
plt.title("方差阈值与交叉验证准确率关系", fontsize=12)
plt.xlabel("方差阈值")
plt.ylabel("5折交叉验证准确率")
plt.grid(alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
9. 用最优阈值重新筛选并训练
python
# 最优阈值筛选
best_selector = VarianceThreshold(threshold=best_threshold)
X_best_selected = best_selector.fit_transform(X_scaled)
best_selected_features = X.columns[best_selector.get_support()].tolist()
print(f"\n=== 最优阈值筛选结果 ===")
print(f"最优阈值: {best_threshold:.4f}")
print(f"保留的特征: {best_selected_features}")
print(f"保留特征数: {X_best_selected.shape[1]}")
# 训练最优模型
acc_best, _, _, _, _ = train_eval(
X_best_selected, y, f"最优阈值筛选后模型(保留{len(best_selected_features)}个特征)"
)
五、方差阈值法的优缺点(必背)
优点
- 简单易懂:原理基于基础方差概念,本科生也能快速掌握
- 计算高效:仅需计算方差和阈值对比,时间复杂度低,适合高维数据(千/万级特征)
- 快速去冗余:能快速删除完全无波动的特征,减少模型训练时间
- 无模型依赖:不依赖复杂模型,仅基于数据本身特性筛选,结果稳定
- 适用于预处理:可作为特征选择的第一步,先粗筛再用复杂方法精筛
缺点
- 忽略特征与目标的关系:只看特征自身方差,不考虑特征对目标变量的预测能力(可能误删"低方差但与目标强相关"的特征)
- 依赖阈值选择:阈值设定凭经验或交叉验证,阈值不当会导致"删有用特征"或"留冗余特征"
- 无法处理多重共线性:若两个特征高度相关(如"身高"和"体重"),方差都较大,会同时保留(冗余)
- 仅适用于数值特征:对分类特征(如性别、职业)无效(需先编码为数值)
- 对尺度敏感:未标准化时,可能因量纲差异误判特征重要性
六、与其他特征选择方法对比
| 方法 | 核心逻辑 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 方差阈值法 | 基于特征自身方差 | 简单、快速、无模型依赖 | 忽略特征-目标关系、不处理共线性 | 高维数据粗筛、快速去冗余 |
| 单变量特征选择(卡方/F检验) | 基于特征-目标的统计相关性 | 考虑目标关系、适用广 | 忽略特征交互、依赖线性假设 | 中低维数据、线性关系为主 |
| L1正则化(Lasso) | 惩罚系数绝对值,使部分系数为0 | 嵌入建模、处理共线性 | 对参数敏感、可能随机删相关特征 | 高维数据、稀疏建模 |
| 树模型特征重要性(RF/XGBoost) | 基于节点分裂增益 | 捕捉非线性、抗共线性 | 计算量大、可能过拟合 | 复杂特征关系、非线性数据 |
七、适用场景与使用建议
优先用方差阈值法的情况
- 高维数据粗筛:特征数上千/万时,先删除低方差特征(如方差<0.01),减少后续计算量
- 数据预处理第一步:作为特征选择的"前置操作",先去冗余再用其他方法精筛
- 快速原型开发:需要快速验证模型可行性,无需复杂特征工程
- 特征尺度统一:已标准化的数据,方差能客观反映特征波动
使用建议
- 必须标准化:无论什么数据,先做标准化再用方差阈值法
- 阈值通过交叉验证选择:避免凭经验设定阈值导致的筛选失误
- 不单独使用:搭配单变量特征选择、L1正则化等方法,兼顾"去冗余"和"特征-目标相关性"
- 分类特征需编码:对分类特征(如性别),先做独热编码/标签编码,再计算方差
八、最简单总结(背诵版)
- 方差阈值法 = 保留高方差特征,删除低方差特征
- 核心步骤:标准化 → 算方差 → 设阈值 → 筛选
- 优点:简单、快速、适合高维数据粗筛
- 缺点:忽略特征-目标关系、不处理共线性
- 最佳用法:作为特征选择第一步,搭配其他方法使用