ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

机器学习特征工程实战:从数据预处理到模型效果验证

机器学习特征工程实战:从数据预处理到模型效果验证 特征工程在机器学习项目里属于那种“没人单独夸、但少一步就崩”的环节。很多人刷完模型原理、调参技巧跑到真实数据集上一跑分数上不去回头查问题往往出在数据特征没处理好。距离“能用”还差很远。CampusX 的 100 天机器学习系列把“特征工程”单独放在第 23 天来讲说明它不是补充内容而是主线内容。这次我们把特征工程这件事拆开看它到底解决什么问题、有哪些常用方法、用 Python 怎么做、做完之后怎么判断有没有效果。同时会结合 CampusX 课程里常见的教学组织方式给出可复制的代码示例和一套能直接用在项目里的验证流程。如果你正在学机器学习或者已经开始用 sklearn 跑模型但总感觉效果不稳定这篇文章可以认真读一遍。1. 核心能力速览能力项说明项目类型机器学习基础知识与实战方法核心内容特征工程的定义、常用方法、Python 代码实现和效果验证技术栈Python、Pandas、NumPy、Scikit-learn是否需要 GPU不需要普通 CPU 即可运行全部示例适合人群机器学习初学者、数据分析师、准备做 Kaggle 比赛或课程作业的同学前置知识Python 基础、Pandas 基础、简单模型训练流程主要内容缺失值处理、类别编码、特征缩放、特征选择、特征构造、数据管道设计工程化程度可以封装成可复用的特征处理类或 sklearn Pipeline配套练习方式建议使用公开数据集如泰坦尼克号、房价预测、信贷违约等学习成本中等代码量不大但需要理解每种方法的适用前提特征工程不是某一个固定算法它是一套“从原始数据到模型可用特征”的处理流程。下面先从价值和使用边界说起。2. 特征工程到底解决什么问题先明确一个判断特征工程的核心目的不是“处理数据”而是“降低模型的学习难度”。原始数据里有大量不适合直接建模的信息模型拿到这些数据要么学不到规律要么学到错误的规律。特征工程要做的就是把信息转换成模型更容易理解的表达方式。典型问题包括数据里有缺失值模型无法直接计算距离或梯度。类别型特征用字符串表示模型只认识数字。数值特征量纲差异巨大例如“年龄 20”和“收入 80000”如果不缩放部分算法会把更大数值的维度当成更重要的维度。特征之间存在冗余导致训练时间变长模型还容易过拟合。单个特征本身信息量不足但把多个特征组合起来之后就可以表达更复杂的规律。CampusX 这类课程在讲特征工程时通常会反复强调一句话模型的上限由数据决定特征工程是逼近上限的手段。算法调参只是在同一个特征集里找更好的参数组合而特征工程是改变特征集本身。使用边界同样要讲清楚特征工程不能凭空创造数据中不存在的信息。它只是把已有信息转换成更合适的形式。特征工程不能解决数据质量问题。如果原始数据本身就是错的特征处理只会让错误变得更有迷惑性。特征工程需要和模型选择一起考虑。线性模型对特征缩放敏感树模型不敏感类别特征在树模型里可以直接做标签编码但在线性模型里更适合做独热编码。特征工程不能过度。加太多构造特征会让模型训练变慢也容易过拟合尤其是用小数据集的时候。适合的场景结构化表格数据、回归和分类任务、Kaggle 竞赛、课程作业、日常数据分析项目。 不适合的场景原始图像和原始音频数据需要专门的预处理管线、深度学习端到端任务模型自带特征提取能力。3. 环境准备与工具链特征工程在 CPU 上就能跑。环境准备按照最小可用方案来3.1 操作系统Windows、macOS、Linux 都可以。命令行操作建议统一熟悉一下方便装包和跑脚本。3.2 Python 环境建议使用 Python 3.9 以上版本并创建独立虚拟环境避免和系统其他项目冲突。创建虚拟环境的命令python -m venv feature_envWindows 激活feature_env\Scripts\activatemacOS / Linux 激活source feature_env/bin/activate3.3 安装依赖库pip install pandas numpy scikit-learn matplotlib seaborn额外推荐feature-engine它是一个专门做特征工程的库封装了缺失值填充、类别编码、离群值处理等常用方法接口风格和 sklearn 一致。如果暂时不想多装依赖纯 pandas 加 scikit-learn 也够用。pip install feature-engine3.4 学习工具建议使用 Jupyter Notebook 或 VS Code 的 Python 交互窗口。特征工程是探索性很强的工作需要反复查看数据的中间状态。每处理一步就输出一次数据的 shape、dtype 和前几行可以直观理解每一步做了什么。4. 数据准备与检查流程在实际动手做特征处理之前先建立一套标准的“数据体检”流程。这里使用一个公开的经典数据集作为演示对象例如泰坦尼克号乘客数据。如果你手头没有现成文件也可以用 sklearn 自带的数据集构造一个示例 DataFrame 来跑通流程。4.1 通用数据检查清单检查项方法判断标准数据形状df.shape确认行数和列数是否符合预期数据缺失df.isnull().sum()定位哪些列存在缺失值类别分布df[col].value_counts()确认类别型特征的类别数量是否过多数值分布df.describe()查看均值、标准差、最小值、最大值判断是否需要缩放重复行df.duplicated().sum()确认是否存在重复样本目标变量分布df[target].value_counts()判断是否存在严重的类别不平衡示例代码import pandas as pd # 示例加载一个 CSV 文件路径按实际文件替换 df pd.read_csv(./data/train.csv) print(数据集形状, df.shape) print(\n缺失值统计) print(df.isnull().sum()) print(\n数值列统计) print(df.describe())4.2 区分特征类型特征类型决定了后续处理方法。通常在特征工程之前要把特征分成三类数值型特征年龄、收入、距离、金额等。类别型特征性别、城市、职业、产品类别等。时间型特征日期、时间戳多数情况下需要拆出年、月、日、星期等成分。4.3 确定目标变量有监督任务必须先明确目标变量。分类任务看标签类别回归任务看目标变量分布。目标变量的处理效果会直接影响模型训练值得单独观察但本文重点放在特征侧。5. 常用特征处理方法与代码实现这一部分是全文的核心。下面的方法基本覆盖了结构化数据特征工程的常见操作。5.1 缺失值处理缺失值处理不是简单删掉就行。先要想清楚缺失的原因再决定策略。常用方法直接删除缺失行适用于缺失比例很低、样本量很大的情况。数值特征用均值或中位数填充有偏分布时中位数更稳定。类别特征用众数填充。时序数据用前向填充用前一个有效值填补后一个空位。构造“是否缺失”标记列有时缺失本身也是一种信息。示例代码from sklearn.impute import SimpleImputer import numpy as np # 示例数据年龄列存在缺失 df[Age] df[Age].astype(float) # 方法一pandas 中位数填充 median_age df[Age].median() df[Age_median_filled] df[Age].fillna(median_age) # 方法二sklearn SimpleImputer适合放进 Pipeline imputer SimpleImputer(strategymedian) age_imputed imputer.fit_transform(df[[Age]]) df[Age_sklearn_filled] age_imputed # 方法三构造缺失标记 df[Age_is_missing] df[Age].isnull().astype(int)缺失值的处理策略不是一成不变的。如果缺失比例超过 70%就要怀疑这个特征还有没有保留的必要如果缺失值分布和标签有明显相关性那“是否缺失”这个标记对模型是有价值的。5.2 类别特征编码类别特征编码是特征工程里最容易踩坑、影响也最大的部分。主要分三种情况5.2.1 标签编码适用于有序类别特征比如“低、中、高”可以编码成 0、1、2。注意无序类别用标签编码模型会错误地学习到数值大小的关系。# 有序类别手动编码 grade_mapping {low: 0, medium: 1, high: 2} df[grade_label] df[grade].map(grade_mapping)5.2.2 独热编码适用于无序类别特征类别数量不多的情况。类别太多时会生成大量稀疏列影响训练效率。# pandas get_dummies 方式 df_encoded pd.get_dummies(df, columns[sex, embarked], drop_firstTrue)# sklearn OneHotEncoder 方式方便和 Pipeline 组合 from sklearn.preprocessing import OneHotEncoder encoder OneHotEncoder(handle_unknownignore, sparse_outputFalse) encoded encoder.fit_transform(df[[sex, embarked]])5.2.3 目标编码类别很多、数据量也够时可以考虑目标编码。用该类别下目标变量均值作为编码值。这种方法信息量高但容易过拟合需要配合交叉验证使用。对于课程作业和初学者项目建议先掌握标签编码和独热编码目标编码用在有把握的场景。5.3 数值特征缩放是否要做缩放取决于模型类型。线性模型、逻辑回归、K 近邻、支持向量机、神经网络强烈建议缩放。决策树、随机森林、梯度提升树等树模型对缩放不敏感。常用方式标准化StandardScaler把数据变成均值为 0、标准差为 1 的分布。适合大多数模型。归一化MinMaxScaler把数据压缩到 0 到 1 之间。适合有明确上下界的特征。鲁棒缩放RobustScaler使用中位数和四分位数不受离群值影响。from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler # 标准化 scaler StandardScaler() df[age_standard] scaler.fit_transform(df[[Age]]) # 归一化 minmax MinMaxScaler() df[age_minmax] minmax.fit_transform(df[[Age]]) # 鲁棒缩放 robust RobustScaler() df[age_robust] robust.fit_transform(df[[Age]])注意fit_transform 只能用训练集测试集和预测阶段用 transform不能重新 fit。这是初学者最常见的错误会把测试集信息泄漏到训练过程里。5.4 特征选择特征选择的目标是在不损失太多信息的前提下减少特征数量降低过拟合风险加快训练速度。常用方法方差阈值过滤去掉方差近似为 0 的特征。相关系数分析去掉与目标变量相关性极低的特征。基于模型的特征重要性随机森林、梯度提升树训练后输出特征重要性排序。递归特征消除反复训练模型并删掉最不重要的特征。from sklearn.ensemble import RandomForestClassifier from sklearn.feature_selection import SelectFromModel # 先用随机森林训练 model RandomForestClassifier(n_estimators100, random_state42) model.fit(X_train, y_train) # 输出特征重要性 importance pd.DataFrame({ feature: X_train.columns, importance: model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(20))# 基于重要性做特征选择 selector SelectFromModel(model, thresholdmedian, prefitTrue) X_selected selector.transform(X_train) selected_features X_train.columns[selector.get_support()] print(保留特征数量, len(selected_features))特征选择不能只看单个特征和目标的相关系数。有些特征单独看和目标关系很弱但组合起来很有价值。这也是为什么最好结合模型的特征重要性来看。5.5 特征构造特征构造属于进阶内容也是特征工程里最能拉开差距的部分。简单说就是从现有特征中构造出新特征把模型需要费力的非线性关系提前暴露出来。常见思路数值特征组合比例、差值、乘积。分箱把连续值切成区间降低异常值影响。时间特征从日期中提取星期、月份、是否周末、节假日。文本特征TF-IDF、关键词计数。领域特征根据具体业务逻辑构造比如“消费金额除以消费次数”得到客单价。# 数值组合示例 df[amount_per_item] df[total_amount] / df[item_count] # 分箱示例 df[age_group] pd.cut(df[Age], bins[0, 18, 35, 55, 100], labels[child, young, middle, senior]) # 时间特征示例 df[transaction_date] pd.to_datetime(df[transaction_date]) df[is_weekend] df[transaction_date].dt.dayofweek 5构造特征要克制。每构造一个新特征都要有合理的业务或数据解释。一堆无意义的组合特征只会增加训练时间和过拟合风险。5.6 离群值处理离群值是否要处理取决于模型和业务。树模型对离群值不敏感线性模型和神经网络会受影响。两种处理角度统计角度用 IQR 或 Z-Score 识别离群值。业务角度结合真实场景判断异常值是否代表真正业务异常。# IQR 方法识别离群值 Q1 df[feature].quantile(0.25) Q3 df[feature].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outlier_mask (df[feature] lower_bound) | (df[feature] upper_bound) print(离群值数量, outlier_mask.sum())处理方式有删除、截尾把极端值封顶到分位数和单独标记。截尾比直接删除温和保留了一部分信息。6. 不同任务的特征工程思路6.1 分类任务分类任务的特征工程重点是类别特征编码和类别不平衡处理。如果标签类别严重不平衡特征工程阶段就要特别注意不要因为采样或裁剪丢失少数类信息。模型选择阶段再考虑 SMOTE 或 class_weight。6.2 回归任务回归任务的特征工程重点是数值特征分布。目标变量如果有明显长尾分布可以考虑先对目标变量做对数变换。特征侧同样可以观察偏度偏度较大的数值特征可以考虑对数变换或 Box-Cox 变换。6.3 时间序列任务时间序列的特征工程要额外构造滞后特征、滚动均值、滚动标准差、时间窗口统计量等。这部分比普通表格数据更复杂要考虑待预测时刻之前的信息不能把未来信息泄漏到训练集里。6.4 文本特征文本在进入模型之前通常先做 TF-IDF 或词向量。对初学者来说TF-IDF 配合线性模型是非常实用的一套组合。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer(max_features5000, stop_wordsenglish) X_text vectorizer.fit_transform(df[text_column])7. 效果验证怎么判断特征工程做得好不好特征工程做得对不对不能靠直觉要通过“对比实验”来验证。7.1 基线模型对比最直接的方法在原始数据上跑一个基线模型再在特征工程后的数据上跑相同的模型对比验证集分数。from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier # 原始特征 model_raw RandomForestClassifier(n_estimators100, random_state42) score_raw cross_val_score(model_raw, X_raw, y, cv5, scoringaccuracy) # 特征工程后 model_fe RandomForestClassifier(n_estimators100, random_state42) score_fe cross_val_score(model_fe, X_fe, y, cv5, scoringaccuracy) print(原始特征平均准确率, score_raw.mean()) print(特征工程后平均准确率, score_fe.mean())注意两个模型要用相同的随机种子和相同的交叉验证折数否则对比没有意义。7.2 观察特征重要性训练完树模型后输出特征重要性排序。如果前几位的特征都是我们构造的新特征说明特征构造方向是对的。如果原始特征霸榜新特征基本没用就要反思构造思路。7.3 观察训练时间和过拟合程度特征工程后的数据如果训练时间显著变长甚至出现过拟合信号说明特征数量太多或构造特征信息冗余。需要做特征选择保留最有用的部分。8. 数据管道与批量处理思路特征工程容易被写成一段乱糟糟的脚本尤其在数据处理步骤多、需要反复复用到新数据时。这里建议用 sklearn 的 Pipeline 把特征处理封装起来。from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer # 数值列处理管道 numeric_features [Age, Fare] numeric_transformer Pipeline(steps[ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()) ]) # 类别列处理管道 categorical_features [Sex, Embarked] categorical_transformer Pipeline(steps[ (imputer, SimpleImputer(strategymost_frequent)), (onehot, OneHotEncoder(handle_unknownignore)) ]) # 组合 preprocessor ColumnTransformer(transformers[ (num, numeric_transformer, numeric_features), (cat, categorical_transformer, categorical_features) ]) # 完整模型管道 pipeline Pipeline(steps[ (preprocessor, preprocessor), (classifier, RandomForestClassifier(n_estimators100, random_state42)) ]) pipeline.fit(X_train, y_train) test_score pipeline.score(X_test, y_test) print(管道测试集准确率, test_score)管道的好处是训练、验证、预测统一走同一套处理逻辑不容易出现训练时处理方式和预测时不一致的 bug。如果是批量处理大量文件可以写一个简单的处理函数循环读入文件依次做特征处理保存临时特征文件。批量任务的关键是每一步都输出日志方便定位到底哪个文件失败。import os import pandas as pd input_dir ./data/raw/ output_dir ./data/processed/ os.makedirs(output_dir, exist_okTrue) for filename in os.listdir(input_dir): if not filename.endswith(.csv): continue filepath os.path.join(input_dir, filename) df pd.read_csv(filepath) # 这里替换为实际特征处理逻辑 df[processed_flag] 1 output_path os.path.join(output_dir, filename) df.to_csv(output_path, indexFalse) print(f处理完成{filename})9. 常见问题与排查方法问题现象可能原因排查方式解决方案特征处理后模型分数反而下降处理方式破坏了数据语义或引入了错误信息对比处理前后的数据分布和模型分数回退到上一个处理步骤逐步排查哪一步导致的下降独热编码后特征数量爆炸类别特征类别数太多查看类别数量对高频类别单独编码低频类别合并为“其他”测试集处理时报错列数对不上独热编码时测试集缺少某些类别检查编码器配置OneHotEncoder 设置 handle_unknownignore数据泄漏验证分数很高但线上效果差fit 和 transform 混用预处理统计量用了全量数据检查是否对测试集单独 fit所有预处理必须在训练集上 fit测试集只 transform推荐使用 Pipeline缺失值填充后模型分数不变或下降缺失比例过高填充值反而引入噪声查看缺失比例和填充前后的分布考虑直接删除该特征或保留“是否缺失”标记构造特征太多训练变慢特征数量过大查看训练时间和模型特征重要性用 SelectFromModel 或递归特征消除做降维时间特征处理报错日期列未正确转换检查 dtype使用 pd.to_datetime 后再提取成分分类任务中低频类别被直接丢弃value_counts 过滤不当检查类别数量低频类别合并到“其他”而不是直接删除10. 最佳实践与学习建议特征工程的学习过程容易停在“知道概念”层面真正需要的是“反复练习直到变成处理问题时的条件反射”。结合 CampusX 100 天机器学习系列这种学习组织方式给出几个建议第一每学一个特征处理方法就找一份公开数据集实际跑一遍。泰坦尼克号、房价预测、银行营销数据集都适合练手。不要只看课程代码要自己从原始数据开始走完整流程。第二建立自己的特征工程模板。把缺失值处理、类别编码、数值缩放、特征选择、特征构造这些步骤做成模板函数之后做新项目时直接复用。模板不是死的每做一个项目就补充一点慢慢变成自己的处理体系。第三重视数据可视化。特征工程的每一步决策都应该有数据依据。比如类别特征要不要合并先看类别频率分布数值特征要不要缩放先看直方图。可视化工具用 matplotlib 和 seaborn 就够。import matplotlib.pyplot as plt import seaborn as sns # 示例查看数值特征分布 fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(df[Age].dropna(), bins30, axaxes[0]) axes[0].set_title(Age Distribution) sns.boxplot(xdf[Fare], axaxes[1]) axes[1].set_title(Fare Boxplot) plt.tight_layout() plt.show()第四读周志华《机器学习》相关章节时把注意力放在“为什么要这么做”上。比如书上讲特征选择和稀疏表示不是让读者记住名词而是理解特征冗余和维度灾难之间的关系。理解了原理代码层面的操作自然就通了。吴恩达的课程里同样强调“特征工程是反复迭代的过程”方向上都是相通的。第五做项目时一定要留出效果验证时间。最忌讳的是把所有特征处理做完直接训练然后只看一个准确率就结束了。要建立基线对比记录每一步对分数的影响这样才能真正理解哪些处理起到了效果。11. 总结与下一步特征工程虽然叫“工程”但本质是对数据的理解。数据长什么样、缺失代表什么、类别怎么分布、哪些组合有业务含义这些问题想清楚了再落到代码上就是顺手的事情。如果你想检验自己的掌握程度可以找一个数据集设定一个明确目标比如把预测准确率从 0.7 提升到 0.8然后用本文提到的方法逐步处理特征每次只改一个变量看看哪个步骤影响最大。下一步可以继续探索的方向包括目标编码的更高级用法、K 折交叉验证下的目标编码避免泄漏、时间序列特征的滞后构造、以及把特征工程封装成 sklearn 自定义 Transformer。这些内容在后续的机器学习进阶学习中都会用到。建议收藏备用。
返回列表