一、特征工程概述
特征工程是机器学习流程中将原始数据转换为能够被机器学习模型有效利用的特征的过程。它是连接原始数据与机器学习算法的桥梁,被认为是决定模型性能的关键因素之一。
二、特征工程的核心目标
- 提升模型性能:创建更好的特征来提升预测准确率
- 降低计算成本:减少特征维度,提升训练效率
- 提高模型可解释性:使特征更符合业务逻辑和直观理解
- 增强模型稳定性:减少过拟合,提高泛化能力
三、特征工程的主要工作内容
1. 数据预处理
- 缺失值处理
- 异常值检测与处理
- 数据类型转换
- 数据格式标准化
2. 特征构建
- 从原始数据中创建新特征
- 组合现有特征
- 时间序列特征提取
- 文本特征提取
3. 特征变换
- 标准化/归一化
- 对数变换、指数变换
- 分箱/离散化
- 多项式特征生成
4. 特征编码
- 类别特征编码(独热编码、标签编码、目标编码等)
- 文本向量化
- 时间特征编码
5. 特征选择
- 过滤法(基于统计指标)
- 包装法(基于模型性能)
- 嵌入法(模型训练中自动选择)
6. 特征降维
- 主成分分析(PCA)
- 线性判别分析(LDA)
- t-SNE等非线性降维
四、完整的随机森林模型特征工程代码示例
以下是一个基于泰坦尼克数据集的完整特征工程和随机森林模型训练代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.feature_selection import SelectFromModel, RFE, chi2, f_classif
from sklearn.decomposition import PCA
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示和美化样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 8)
# 1. 数据加载与探索
print("=" * 60)
print("1. 数据加载与探索性分析")
print("=" * 60)
# 加载泰坦尼克数据集
from sklearn.datasets import fetch_openml
titanic = fetch_openml('titanic', version=1, as_frame=True)
df = titanic.data
df['Survived'] = titanic.target.astype(int)
print(f"数据集形状: {df.shape}")
print("\n数据前5行:")
print(df.head())
print("\n数据基本信息:")
print(df.info())
print("\n数据统计描述:")
print(df.describe())
# 2. 缺失值分析
print("\n" + "=" * 60)
print("2. 缺失值分析")
print("=" * 60)
missing_data = df.isnull().sum()
missing_percent = (missing_data[missing_data > 0] / len(df)) * 100
missing_df = pd.DataFrame({
'缺失数量': missing_data[missing_data > 0],
'缺失比例%': missing_percent
})
print(missing_df)
# 可视化缺失值
plt.figure(figsize=(12, 6))
sns.heatmap(df.isnull(), cbar=False, cmap='viridis', yticklabels=False)
plt.title('缺失值热力图', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 3. 特征工程流程
print("\n" + "=" * 60)
print("3. 特征工程开始")
print("=" * 60)
# 创建特征工程前后的数据副本
df_original = df.copy()
df_engineered = df.copy()
# 3.1 处理缺失值
print("\n3.1 处理缺失值...")
# 对于Age,使用KNN插补
print(" - 对Age使用KNN插补...")
knn_imputer = KNNImputer(n_neighbors=5)
df_engineered['Age'] = knn_imputer.fit_transform(df_engineered[['Age']])
# 对于Embarked,使用众数填充
print(" - 对Embarked使用众数填充...")
df_engineered['Embarked'].fillna(df_engineered['Embarked'].mode()[0], inplace=True)
# 对于Fare,使用中位数填充
print(" - 对Fare使用中位数填充...")
df_engineered['Fare'].fillna(df_engineered['Fare'].median(), inplace=True)
# 删除Cabin特征(缺失太多)
print(" - 删除Cabin特征(缺失率超过77%)...")
df_engineered.drop('Cabin', axis=1, inplace=True)
# 3.2 创建新特征
print("\n3.2 创建新特征...")
# 从姓名中提取头衔
print(" - 从姓名中提取头衔...")
df_engineered['Title'] = df_engineered['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
# 标准化头衔
title_mapping = {
'Mr': 'Mr', 'Miss': 'Miss', 'Mrs': 'Mrs', 'Master': 'Master',
'Dr': 'Rare', 'Rev': 'Rare', 'Col': 'Rare', 'Major': 'Rare',
'Mlle': 'Miss', 'Countess': 'Rare', 'Ms': 'Miss', 'Lady': 'Rare',
'Jonkheer': 'Rare', 'Don': 'Rare', 'Dona': 'Rare', 'Mme': 'Mrs',
'Capt': 'Rare', 'Sir': 'Rare'
}
df_engineered['Title'] = df_engineered['Title'].map(title_mapping)
# 创建家庭大小和是否独行特征
print(" - 创建家庭大小和是否独行特征...")
df_engineered['FamilySize'] = df_engineered['SibSp'] + df_engineered['Parch'] + 1
df_engineered['IsAlone'] = 0
df_engineered.loc[df_engineered['FamilySize'] == 1, 'IsAlone'] = 1
# 创建票价等级特征(分箱)
print(" - 创建票价等级特征...")
df_engineered['FareCategory'] = pd.qcut(df_engineered['Fare'], 4, labels=[1, 2, 3, 4])
# 创建年龄组特征
print(" - 创建年龄组特征...")
df_engineered['AgeGroup'] = pd.cut(df_engineered['Age'],
bins=[0, 12, 18, 35, 60, 100],
labels=['Child', 'Teen', 'Young Adult', 'Adult', 'Senior'])
# 3.3 特征编码
print("\n3.3 特征编码...")
# 对Sex进行标签编码
print(" - 对Sex进行标签编码...")
le_sex = LabelEncoder()
df_engineered['Sex_encoded'] = le_sex.fit_transform(df_engineered['Sex'])
# 对Embarked和Title进行独热编码
print(" - 对Embarked和Title进行独热编码...")
df_engineered = pd.get_dummies(df_engineered, columns=['Embarked', 'Title', 'AgeGroup'],
prefix=['Emb', 'Title', 'AgeGroup'], drop_first=True)
# 3.4 特征变换
print("\n3.4 特征变换...")
# 对Fare进行对数变换(处理偏态分布)
print(" - 对Fare进行对数变换...")
df_engineered['Fare_log'] = np.log1p(df_engineered['Fare'])
# 对Age进行标准化
print(" - 对Age进行标准化...")
scaler_age = StandardScaler()
df_engineered['Age_scaled'] = scaler_age.fit_transform(df_engineered[['Age']])
# 3.5 删除无用特征
print("\n3.5 删除无用特征...")
cols_to_drop = ['Name', 'Ticket', 'Sex', 'Age', 'Fare']
df_engineered.drop(cols_to_drop, axis=1, inplace=True)
print(f"\n特征工程后数据集形状: {df_engineered.shape}")
print(f"特征工程后特征数量: {len(df_engineered.columns)}")
print("\n特征工程后的特征列表:")
print(df_engineered.columns.tolist())
# 4. 特征选择
print("\n" + "=" * 60)
print("4. 特征选择")
print("=" * 60)
# 准备数据
X = df_engineered.drop('Survived', axis=1)
y = df_engineered['Survived']
# 4.1 基于随机森林的特征重要性
print("\n4.1 基于随机森林的特征重要性分析...")
rf_for_selection = RandomForestClassifier(n_estimators=100, random_state=42)
rf_for_selection.fit(X, y)
# 可视化特征重要性
feature_importance = pd.DataFrame({
'feature': X.columns,
'importance': rf_for_selection.feature_importances_
}).sort_values('importance', ascending=False)
plt.figure(figsize=(12, 8))
sns.barplot(x='importance', y='feature', data=feature_importance.head(15))
plt.title('Top 15 特征重要性', fontsize=16, fontweight='bold')
plt.xlabel('特征重要性', fontsize=14)
plt.ylabel('特征名称', fontsize=14)
plt.tight_layout()
plt.show()
# 4.2 使用SelectFromModel选择特征
print("\n4.2 使用SelectFromModel选择重要特征...")
selector = SelectFromModel(rf_for_selection, threshold='median', prefit=True)
X_selected = selector.transform(X)
selected_features = X.columns[selector.get_support()]
print(f"原始特征数量: {X.shape[1]}")
print(f"选择后特征数量: {X_selected.shape[1]}")
print(f"选择的特征: {selected_features.tolist()}")
# 使用选择的特征
X = X[selected_features]
# 5. 数据分割
print("\n" + "=" * 60)
print("5. 数据分割")
print("=" * 60)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集大小: {X_train.shape}")
print(f"测试集大小: {X_test.shape}")
print(f"正负样本比例 (训练集): {np.bincount(y_train)/len(y_train)}")
print(f"正负样本比例 (测试集): {np.bincount(y_test)/len(y_test)}")
# 6. 可选:特征降维(PCA)
print("\n" + "=" * 60)
print("6. 可选:特征降维(PCA)")
print("=" * 60)
# 标准化特征(PCA需要标准化)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 应用PCA
pca = PCA(n_components=0.95) # 保留95%的方差
X_train_pca = pca.fit_transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)
print(f"PCA降维后特征数量: {X_train_pca.shape[1]}")
print(f"解释方差比例: {np.sum(pca.explained_variance_ratio_):.4f}")
# 可视化PCA解释方差
plt.figure(figsize=(10, 6))
plt.plot(np.cumsum(pca.explained_variance_ratio_), 'bo-', linewidth=2)
plt.xlabel('主成分数量', fontsize=14)
plt.ylabel('累积解释方差比例', fontsize=14)
plt.title('PCA解释方差比例', fontsize=16, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 7. 随机森林模型训练与评估
print("\n" + "=" * 60)
print("7. 随机森林模型训练与评估")
print("=" * 60)
# 使用原始特征训练
print("\n7.1 使用原始特征训练随机森林...")
rf_original = RandomForestClassifier(
n_estimators=100,
max_depth=10,
min_samples_split=5,
min_samples_leaf=2,
random_state=42,
n_jobs=-1
)
rf_original.fit(X_train, y_train)
y_pred_original = rf_original.predict(X_test)
accuracy_original = accuracy_score(y_test, y_pred_original)
print(f"原始特征模型准确率: {accuracy_original:.4f}")
# 使用PCA降维后的特征训练
print("\n7.2 使用PCA降维特征训练随机森林...")
rf_pca = RandomForestClassifier(
n_estimators=100,
max_depth=10,
min_samples_split=5,
min_samples_leaf=2,
random_state=42,
n_jobs=-1
)
rf_pca.fit(X_train_pca, y_train)
y_pred_pca = rf_pca.predict(X_test_pca)
accuracy_pca = accuracy_score(y_test, y_pred_pca)
print(f"PCA降维后模型准确率: {accuracy_pca:.4f}")
# 7.3 模型超参数调优
print("\n7.3 随机森林超参数调优...")
param_grid = {
'n_estimators': [100, 200],
'max_depth': [5, 10, 15, None],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}
rf_tuned = RandomForestClassifier(random_state=42, n_jobs=-1)
grid_search = GridSearchCV(
rf_tuned,
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1,
verbose=0
)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳交叉验证准确率: {grid_search.best_score_:.4f}")
# 使用最佳模型
best_rf = grid_search.best_estimator_
y_pred_best = best_rf.predict(X_test)
accuracy_best = accuracy_score(y_test, y_pred_best)
print(f"调优后测试集准确率: {accuracy_best:.4f}")
# 7.4 模型评估
print("\n7.4 模型详细评估...")
print("\n最佳模型分类报告:")
print(classification_report(y_test, y_pred_best))
# 混淆矩阵可视化
cm = confusion_matrix(y_test, y_pred_best)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False,
xticklabels=['未存活', '存活'],
yticklabels=['未存活', '存活'])
plt.title('混淆矩阵', fontsize=16, fontweight='bold')
plt.ylabel('真实标签', fontsize=14)
plt.xlabel('预测标签', fontsize=14)
plt.tight_layout()
plt.show()
# 8. 特征工程效果总结
print("\n" + "=" * 60)
print("8. 特征工程效果总结")
print("=" * 60)
print("\n准确率对比:")
print(f" 原始特征模型: {accuracy_original:.4f}")
print(f" PCA降维模型: {accuracy_pca:.4f}")
print(f" 调优后模型: {accuracy_best:.4f}")
# 计算特征工程带来的提升
improvement = accuracy_best - accuracy_original
print(f"\n特征工程和调优带来的准确率提升: {improvement:.4f} ({improvement/accuracy_original*100:.2f}%)")
# 最终特征重要性
final_feature_importance = pd.DataFrame({
'特征': X.columns,
'重要性': best_rf.feature_importances_
}).sort_values('重要性', ascending=False)
print("\nTop 10 最重要特征:")
print(final_feature_importance.head(10).to_string(index=False))
# 可视化最终特征重要性
plt.figure(figsize=(12, 8))
top_features = final_feature_importance.head(10)
sns.barplot(x='重要性', y='特征', data=top_features, palette='viridis')
plt.title('Top 10 最重要特征(调优后模型)', fontsize=16, fontweight='bold')
plt.xlabel('特征重要性', fontsize=14)
plt.ylabel('特征名称', fontsize=14)
plt.tight_layout()
plt.show()
print("\n" + "=" * 60)
print("特征工程流程完成!")
print("=" * 60)
五、代码说明与总结
主要特点:
- 完整的特征工程流程:涵盖了数据预处理、特征构建、编码、变换、选择和降维
- 可视化分析:包含缺失值可视化、特征重要性、PCA分析等
- 对比实验:比较了原始特征、PCA降维特征和调优后的模型性能
- 模块化设计:每个步骤都有明确的输出和解释
关键步骤:
- 数据探索:了解数据分布和缺失情况
- 缺失值处理:根据缺失率采用不同策略
- 特征构建:从原始数据提取新特征(如头衔、家庭大小等)
- 特征编码:将分类变量转换为数值形式
- 特征变换:标准化、对数变换等
- 特征选择:基于模型重要性选择关键特征
- 模型训练与评估:使用随机森林进行训练和调优
实际应用建议:
- 特征工程应结合具体业务场景
- 不同问题需要不同的特征工程策略
- 特征工程是迭代过程,需要不断优化
- 注意避免数据泄露(确保特征工程只在训练集上进行)
这个完整的示例展示了如何在真实项目中实施特征工程,并通过随机森林模型验证其效果。