跳转至

Lecture 3

1. Feature Engineering

1.1. Feature Engineering

特征工程(Feature Engineering) 是从原始数据中构造、转换和选择特征的过程,是机器学习流程中对模型性能影响最大的环节之一。

特征工程的四大核心任务:

  • 特征创建(Feature Creation):基于领域知识或数据规律构造新特征
  • 特征转换(Feature Transformation):对特征进行数学变换以满足模型要求
  • 特征提取(Feature Extraction):从高维数据中提取关键信息(如 PCA)
  • 特征选择(Feature Selection):筛选出对预测最有用的特征子集

1.2. Data Preparation

数据准备阶段包括:数据清洗、数据组织、数据集成、数据变换。

从以下六个维度保证 数据质量

维度 含义 检查方法
Validity(有效性) 数据是否符合业务规则 范围检查、类型检查
Reliability(可靠性) 数据是否一致可重复 交叉验证
Completeness(完整性) 数据是否缺失 缺失值统计
Precision(精确性) 数据是否足够精确 小数位数、粒度检查
Timeliness(时效性) 数据是否及时更新 时间戳检查
Integrity(一致性) 数据之间是否一致 外键约束、唯一性检查

1.3. Data Quality

Python
import pandas as pd
import numpy as np

# 检测缺失值
df.isnull().sum()
df.isnull().mean()  # 缺失比例

# 方法 1:删除
df.dropna()                    # 删除含缺失值的行
df.dropna(thresh=3)            # 至少有 3 个非空值才保留

# 方法 2:填充
df['age'].fillna(df['age'].median(), inplace=True)
df['city'].fillna(df['city'].mode()[0], inplace=True)
df.fillna(method='ffill')      # 前向填充
df.fillna(method='bfill')      # 后向填充

# 方法 3:插值
df['temperature'].interpolate(method='linear')

选择填充策略

  • 数值型:均值(正态分布时)、中位数(有偏态/异常值时)
  • 分类型:众数,或新增 "Unknown" 类别
  • 时序数据:前向/后向填充或线性插值
Python
# 方法 1:IQR 方法
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
df_clean = df[(df['income'] >= lower) & (df['income'] <= upper)]

# 方法 2:Z-score
from scipy import stats
z_scores = np.abs(stats.zscore(df['income']))
df_clean = df[z_scores < 3]  # 保留 Z-score < 3 的数据

# 方法 3:Winsorization(缩尾处理)
from scipy.stats import mstats
df['income_winsorized'] = mstats.winsorize(df['income'], limits=[0.05, 0.05])

异常值处理的注意事项

异常值不一定是错误数据。在欺诈检测、疾病诊断等场景中,异常值可能恰恰是我们关注的重点。处理前应先确认异常值的来源。

Python
1
2
3
4
5
6
7
8
# 检测重复值
df.duplicated().sum()

# 删除完全重复的行
df.drop_duplicates(inplace=True)

# 按指定列去重,保留最后一条
df.drop_duplicates(subset=['id'], keep='last')

2. Feature Creation

2.1 特征创建方法

特征创建是根据领域知识或数据本身规律,从现有数据中构造新特征的过程。

  • Domain-specific(领域驱动):基于领域知识创建特征。例如在金融领域,用"负债/收入比"作为信用风险指标。
  • Data-driven(数据驱动):基于数据本身的统计特征创建。例如将日期拆分为"星期几""是否节假日"。
  • Synthetic(合成特征):基于已有特征的数学组合。例如将"身高"和"体重"组合为 BMI 指数。
Python
# 领域驱动:提取日期特征
df['date'] = pd.to_datetime(df['date'])
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day_of_week'] = df['date'].dt.dayofweek
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)

# 数据驱动:分箱(Binning)
df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 60, 100],
                         labels=['少年', '青年', '中年', '老年'])

# 合成特征
df['bmi'] = df['weight'] / (df['height'] / 100) ** 2
df['income_per_family'] = df['income'] / df['family_size']

3. Feature Scaling

3.1 缩放方法对比

特征缩放是将不同量纲的特征统一到相同尺度的过程,对基于距离的算法(如 KNN、SVM)和梯度下降优化尤为重要。

方法 公式 输出范围 适用场景
Min-Max Scaling \(x' = \frac{x - x_{min}}{x_{max} - x_{min}}\) \([0, 1]\) 数据无明显异常值
Standard Scaling \(x' = \frac{x - \mu}{\sigma}\) 均值 0,标准差 1 数据近似正态分布
Robust Scaling \(x' = \frac{x - Q_2}{Q_3 - Q_1}\) 不固定 数据含较多异常值
Python
1
2
3
4
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()  # 默认 range=(0, 1)
X_scaled = scaler.fit_transform(X)

优点:输出有界,计算简单。 缺点:对异常值敏感,一个极端值会压缩其余数据的分布。

Python
1
2
3
4
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

优点:不受异常值影响太大,保留了数据的分布形状。 缺点:输出无界,不保证在固定范围内。

Python
1
2
3
4
from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)

优点:使用中位数和四分位距,对异常值有很强的鲁棒性。 缺点:输出范围不固定。

何时需要特征缩放?

  • 需要缩放:KNN、SVM、神经网络、PCA、梯度下降优化的模型(线性回归、逻辑回归)
  • 不需要缩放:决策树、随机森林、XGBoost 等基于树的模型(它们基于特征排序而非绝对值)

4. Feature Transformation

4.1 编码方法

机器学习模型只能处理数值型输入,因此需要将分类变量转换为数值表示。

独热编码 将每个类别转换为一个独立的二进制列。

Python
1
2
3
4
5
6
7
8
9
import pandas as pd

# Pandas 实现
df_encoded = pd.get_dummies(df, columns=['color'], prefix='color')

# Scikit-learn 实现
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False, drop='first')  # drop 避免多重共线性
encoded = encoder.fit_transform(df[['color']])
原始值 color_red color_green color_blue
red 1 0 0
green 0 1 0
blue 0 0 1

高基数问题

当类别数非常多时(如城市、用户 ID),One-Hot Encoding 会导致维度爆炸。此时可考虑 Target Encoding 或 Frequency Encoding。

标签编码 将每个类别映射为一个整数。

Python
1
2
3
4
5
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
df['size_encoded'] = le.fit_transform(df['size'])
# small=2, medium=1, large=0 (按字母排序)

适用场景:有序分类变量(Ordinal),如教育程度:小学 < 初中 < 高中 < 大学。 不适用场景:名义变量(Nominal),如颜色:红=0, 绿=1, 蓝=2 会引入不存在的大小关系。

目标编码 用目标变量的统计量(通常是均值)替代类别。

Python
1
2
3
4
5
6
7
8
# 手动实现
means = df.groupby('city')['target'].mean()
df['city_encoded'] = df['city'].map(means)

# 使用 category_encoders 库
import category_encoders as ce
encoder = ce.TargetEncoder(cols=['city'])
df['city_encoded'] = encoder.fit_transform(df['city'], df['target'])

防止数据泄露

Target Encoding 必须在交叉验证的 fold 内进行,否则会将目标变量信息泄露到训练集中。推荐使用 K-Fold Target Encoding。

4.2 其他变换方法

  • 对数变换 \(x' = \log(x)\):处理右偏分布,缩小数据范围
  • 平方根变换 \(x' = \sqrt{x}\):弱于对数变换,适用于轻度偏态
  • Box-Cox 变换:自动寻找最优幂变换参数 \(\lambda\),使数据最接近正态分布
Python
1
2
3
4
5
6
7
8
# 对数变换
import numpy as np
df['log_income'] = np.log1p(df['income'])  # log1p = log(x+1),处理零值

# Box-Cox 变换
from scipy.stats import boxcox
df['income_bc'], lambda_opt = boxcox(df['income'] + 1)
print(f"最优 lambda: {lambda_opt:.4f}")

5. Feature Selection

5.1 特征选择方法

特征选择是从原始特征中筛选出最有价值的子集,目的是降低过拟合、减少训练时间、提高模型可解释性。

graph TD
    A[特征选择方法] --> B[Filter 过滤法]
    A --> C[Wrapper 包装法]
    A --> D[Embedded 嵌入法]
    B --> B1[卡方检验]
    B --> B2[互信息]
    B --> B3[方差分析]
    C --> C1[递归特征消除 RFE]
    C --> C2[前向/后向选择]
    D --> D1[Lasso 回归]
    D --> D2[决策树重要性]

Filter 方法独立于模型,使用统计指标评估特征与目标变量的相关性。

Python
1
2
3
4
5
6
7
8
9
from sklearn.feature_selection import SelectKBest, chi2, mutual_info_classif

# 卡方检验(适用于分类特征 vs 分类目标)
selector = SelectKBest(chi2, k=10)
X_selected = selector.fit_transform(X, y)

# 互信息(适用于任意类型)
selector = SelectKBest(mutual_info_classif, k=10)
X_selected = selector.fit_transform(X, y)

优点:计算速度快,与模型解耦。 缺点:忽略特征之间的交互作用。

Wrapper 方法使用特定模型评估特征子集的性能。

Python
1
2
3
4
5
6
7
8
9
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(max_iter=1000)
rfe = RFE(model, n_features_to_select=10)
X_selected = rfe.fit_transform(X, y)

print("被选中的特征:", X.columns[rfe.support_])
print("特征排名:", rfe.ranking_)

优点:考虑了特征交互,通常效果最好。 缺点:计算成本高,每次评估都需训练模型。

Embedded 方法在模型训练过程中自动进行特征选择,结合了 Filter 和 Wrapper 的优点。

Python
from sklearn.linear_model import LassoCV
from sklearn.ensemble import RandomForestClassifier

# L1 正则化(Lasso)自动将不重要的特征系数压缩到 0
lasso = LassoCV(cv=5)
lasso.fit(X, y)
important_features = X.columns[lasso.coef_ != 0]

# 随机森林特征重要性
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
importances = pd.Series(rf.feature_importances_, index=X.columns)
importances.nlargest(10).plot(kind='barh')

特征选择的实践建议

  1. 先用 Filter 快速排除明显无关的特征
  2. 再用 Embedded 方法(如 Lasso 或树模型)得到初步重要特征
  3. 最后用 Wrapper 方法(如 RFE)精调特征子集
  4. 特征数量不是越多越好,少而精 的特征往往优于大量冗余特征