尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

特征工程实战指南:从数据清洗到特征选择的完整流程

特征工程实战指南:从数据清洗到特征选择的完整流程 1. 特征工程到底是什么为什么我一上来就讲它先抛一个我经常在实验室和群里跟人争论的观点机器学习项目里真正决定模型上限的往往不是算法而是特征工程。很多入门的朋友一上来就死磕XGBoost、深度学习网络结构调参调到半夜最后发现效果死活上不去——问题很可能不在模型而在你喂给模型的东西压根就没处理好。那特征工程到底怎么定义我习惯把它理解成一句话把你手里原始、杂乱、没法直接用的数据转换成能让机器学习算法更好理解、更好学习的“输入格式”和“信息表达”。算法是发动机特征就是燃油。你加92号和加98号发动机跑起来的感觉完全不一样。加错了标号甚至可能爆震数据喂错了形式模型直接不收敛或者训出一堆垃圾结果。这篇内容我会从整体思路、数据清洗、特征变换、编码、构造、选择这几个环节一条龙讲下来结合我实际跑过的项目和踩过的坑。适合刚入门机器学习、正在做课程设计或准备找工作面试特征工程题的朋友也适合那些觉得自己模型效果差但说不出原因、想系统补一补这块的人。我特别想强调一个认知特征工程不是一个“锦上添花”的步骤而是机器学习应用流程里最花时间的环节。工业界有句话叫“Garbage in, garbage out”数据侧的问题不解决后面训练、调参、部署全部白费。吴恩达老师在机器学习课程里也反复强调模型效果的提升很多时候来自更好的特征表示而不是更复杂的模型。李宏毅老师的课同样会把数据预处理和特征处理放在比较靠前的位置讲原因就在这里。接下来我尽量少讲虚的多讲“我当时是怎么做的”以及“为什么这么做”。你会发现把特征工程这套东西理清楚之后很多以前觉得玄学的模型表现问题其实都有明确的原因可以追溯。2. 整体设计思路不是所有数据都能直接喂给算法2.1 先说清楚特征工程在机器学习流程里的位置我习惯把一次完整的机器学习应用流程拆成七步业务理解与目标定义、数据采集、数据清洗与预处理、特征工程、模型训练与验证、模型评估与调优、部署与监控。特征工程处在数据清洗之后、模型训练之前位置非常关键。但这里有个很多人忽略的细节特征工程不是一次性做完就完了它应该是迭代的。我第一次跑波士顿房价数据集的时候就是直接拿原始特征丢进线性回归结果测试集上效果平平。后来做了标准化、构造了交互特征、去掉几个和房价线性关系太弱的原始列同样的模型效果直接提升了一个档次。这个过程我重复了好几轮每次改完特征都要重新训练验证一遍看指标有没有真正变好。2.2 核心原则特征要满足算法的“偏好”不同的算法对特征的要求完全不一样这一点在选型时必须想清楚。线性模型线性回归、逻辑回归、SVM对特征的尺度非常敏感。你想想如果某个特征取值范围是0到1另一个是0到100000那模型在优化权重的时候数值范围大的特征天然更容易主导损失函数的变化导致训练不稳定、收敛慢。所以对这类模型归一化或标准化几乎是必须的。树模型决策树、随机森林、XGBoost、LightGBM对特征尺度不敏感因为它们做的是基于阈值的分裂每个特征的绝对值大小不影响分裂点的选择。但树模型对特征的含义和区分度很敏感你给它塞一堆没有判别力的噪声特征它照样会学着用这些特征去分裂最后过拟合。深度学习模型对输入特征的分布很敏感尤其是使用梯度下降优化时。如果特征分布差异大梯度更新会被某些特征主导导致训练过程动荡。所以做神经网络之前特征标准化基本是标配。我总结了一个简单的选型思路表格帮你快速判断自己该做什么程度的特征工程模型类型是否需标准化是否需处理缺失值对高基数类别编码的态度特征构造重点线性回归/逻辑回归必须必须需要编码但不能让维度爆炸交互特征、多项式特征SVM必须必须常用独热或目标编码核函数本身隐含特征映射原始特征质量更重要树模型不必须对部分算法容忍可直接用标签编码特征含义、特征组合、避免无关噪声特征神经网络必须必须常用嵌入层或独热特征表达、归一化、数据增强衍生特征2.3 特征工程的三个基本问题所有特征工程工作本质上都是在回答三个问题数据能不能被读取特征能不能表达规律特征组合能不能提升区分度这三个问题分别对应数据预处理、特征变换和特征构造后面每个环节我都会详细展开。我还想补充一个容易忽视的点特征工程要兼顾“信息的保真”和“噪声的剔除”。过度加工特征可能导致信息丢失比如把连续年龄直接离散化成“老中青”三个区间原始信息就损失了一部分。但完全不加工模型又很难从原始数据里学到有用的规律。这个度怎么把握需要你在实际项目中反复尝试和对比没有一刀切的答案。3. 数据清洗与预处理特征工程的地基省一步后面全是坑3.1 缺失值处理先判断缺失机制再决定处理方式很多人拿到数据第一反应就是“把缺失值填成均值”这个做法太粗暴了。缺失值处理的第一步是判断缺失的原因和机制。我通常在实战中会把缺失分成三类完全随机缺失比如用户填问卷时手滑漏填了一道题数据丢失和任何变量无关。这种情况比较省事直接删除或填均值影响都不大。随机缺失比如收入字段缺的人可能都是高收入群体缺失本身和某个变量相关。这种情况直接删行会引入偏差直接填均值也会把分布拉偏需要更谨慎处理。非随机缺失比如传感器在高温环境下频繁失灵导致温度数据缺失缺失本身就和目标变量相关。这种情况不能简单填补要考虑是否添加“是否缺失”的标记特征。我在做化工相关的机器学习项目时对传感器采集的数据经常遇到非随机缺失的情况。我的做法是把“该特征是否缺失”作为一个新的二值特征加进去再把缺失值填成一个特殊值像-1或0这样模型就能自己去学习缺失模式里是否包含有效信息。具体到填补方法我按优先级推荐这几种先看业务含义很多缺失值其实代表“无”或“零”比如“是否有贷款记录”这个字段为缺失可能意味着从来没有贷过款直接填0比填均值更合理。用均值、中位数或众数填充适合数值型特征且缺失比例不高低于5%的情况。用前向填充或后向填充适合时间序列数据。用模型预测缺失值比如用KNN找到相似样本用其该特征的值来填充适合缺失比例较高但特征间关联较强的情况。3.2 异常值处理不要让一个离群点毁了整个模型异常值对线性模型和神经网络的影响特别大因为它们在计算损失的时候会贡献非常大的梯度。我在实验室跑模型的时候有一次发现波士顿房价某个特征的分布里出现了一个极端值肉眼看起来像是数据录入错误。删掉那一条记录之后R²直接上升了好几个百分点。我常用的异常值检测办法有三种基于统计的方法用Z-score如果某个点的Z-score超过3通常认为是异常值或者用IQR超过Q1-1.5倍IQR或Q31.5倍IQR的点视为异常值。基于密度的方法像LOF局部离群因子看样本点周围邻居的密度和整体密度的对比。基于模型的方法像孤立森林通过随机划分特征空间来快速隔离异常点。处理异常值的策略如果确认是数据录入错误直接删除如果是真实的极端样本比如房价里真的存在几千万的豪宅那要看业务是否需要预测这类极端情况。如果不需要可以截断处理把超过99%分位数的值压缩到99%分位数的位置避免模型被个别极端样本带偏。3.3 重复数据处理这个听起来很简单但实际操作里有个注意点去重之前要想清楚“重复”的定义是什么。是整行所有字段都相同才算重复还是某些关键字段相同就算重复比如在用户行为数据里同一用户同一时间戳的多条记录可能跑批重复了这种情况下只保留一条即可但如果两条记录虽然大部分字段相同时间戳不同那它们就代表不同时刻的状态不能合并。我之前在头歌平台处理一个数据预处理作业时数据集里包含了一些完全相同的行同学们普遍直接drop_duplicates()。但其实有一个同学问过我“老师为什么我用pandas去重之后样本量少了200多”我说你自己观察一下重复的本质是什么。后来发现那200多条记录除了一个“序号”字段不同之外其他字段完全一样那其实是不同用户在不同时间段的快照不能删。这就是业务理解对数据清洗的反向约束。3.4 数据格式统一这一步经常被忽略。日期字段可能是字符串“2024/01/01”也可能是时间戳“1704067200”还可能拆成年月日三个字段。类别字段可能是“男”和“male”混在一起表示同一个含义。数值字段可能因为录入问题包含了空格或特殊符号。我处理这类问题的标准动作是先用df.info()和df.describe()看字段类型和统计信息再用df.nunique()检查每个字段的唯一值数量。发现异常后用pandas的pd.to_datetime()统一时间格式、str.strip()去掉空格、astype()转换数据类型。说完这些我得交代一句数据清洗这个环节最忌讳的是图快。我记得有一次跑一个开源的人脸识别项目数据集里有人脸图片的路径字段缺失我图省事直接把缺失行删了结果训练集和验证集的人ID分布对不上后面精度评估一团糟。自那以后我养成了一个习惯每一步清洗操作都要记录删了多少行、改了多少值、为什么这么改方便回溯。做项目不是一次性的你三天后回来看代码如果没有记录你会完全想不起来当时为什么删掉那些行。4. 特征变换与无量纲化让数字在同一个“度量衡”下对话4.1 标准化和归一化的区别别傻傻分不清特征变换里最基础也最常用的就是无量纲化。核心目的是消除不同特征之间量纲和数值范围差异对模型的影响。我见过太多人把StandardScaler和MinMaxScaler混着用完全不看数据分布。标准化采用的是Z-score方法公式是(x - mean) / std变换后数据均值是0标准差是1。适合数据近似服从正态分布或含有离群点的情况。归一化采用MinMax方法公式是(x - min) / (max - min)变换后数据落在0到1区间。如果数据没有离群点、分布比较均匀用归一化能保留原始的分布形状如果有离群点归一化会把正常数据压缩到一个非常窄的区间里。我在处理波士顿房价数据集的时候RM平均房间数和TAX房产税率的数值范围差了几十倍当时直接做标准化处理效果很好。但如果你的特征里有明显的长尾分布比如收入字段标准化不见得是最优解可以考虑取对数后再标准化。4.2 非线性变换让模型看到数据的真实结构很多特征和标签之间不是线性关系。比如年龄和收入之间的关系往往在中年达到峰值年轻和年老时收入都偏低这是倒U型关系。你用线性模型直接拟合年龄和收入肯定拟合不好。这时候就需要做非线性变换。最常见的几种对数变换适合右偏分布的数据能把长尾压缩让分布更接近正态。我在处理房价数据时对MEDV房价中位数字段做对数变换后整体分布确实平滑了很多。平方根变换类似对数变换但没有那么强的压缩力度适合中等程度偏斜的数据。Box-Cox变换更通用的一种变换里面有个参数lambda可以自动学习最优的变换方式。不过它要求数据都是正数如果数据里有0或负数需要先平移。我还得提一嘴分箱。分箱本质上也是一种非线性变换把连续变量离散化成几个区间再编码。决策树本身就自带分箱能力但对线性模型来说分箱可以引入非线性。比如把年龄分成“18-25”、“26-35”、“36-50”、“50”四段每段作为一个类别特征模型就能捕捉到不同年龄段对目标变量的不同影响。分箱的缺点是会损失信息分得太粗容易丢失细节分得太细又容易过拟合这个平衡需要结合样本量来把握。4.3 特征缩放的实际操作细节在Python里做特征缩放我非常推荐使用scikit-learn的Pipeline机制。以前我写代码是一个特征一个特征手动处理后来项目多了发现又慢又容易出错。用Pipeline可以把标准化、编码、模型训练全部串起来不仅代码更整洁还避免了一个非常经典的错误用全量数据拟合scaler导致验证集或测试集信息泄露。正确的做法是先在训练集上fit()得到均值和标准差再分别对训练集和测试集做transform()。如果你在测试集上重新计算均值和标准差那就相当于测试集的信息渗入了模型得到的评估指标会虚高上线后效果立刻现原形。下面是我常用的代码模板这种写法在做课程作业、打比赛、跑开源项目时都适用from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.pipeline import Pipeline from sklearn.linear_model import LinearRegression numeric_features [RM, LSTAT, DIS] categorical_features [CHAS, RAD] preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), numeric_features), (cat, OneHotEncoder(dropfirst), categorical_features) ]) model Pipeline(steps[ (preprocessor, preprocessor), (regressor, LinearRegression()) ]) model.fit(X_train, y_train) y_pred model.predict(X_test)用Pipeline的一个额外好处是做交叉验证的时候每个fold都会在自己的子训练集上重新fit preprocessor不会发生数据泄露。这一点在做模型评估时非常关键。4.4 关于标准化和归一化再补几个踩坑心得第一深度学习模型里标准化几乎永远是第一步。我在跑卷积神经网络做人脸识别相关实验时输入图片的像素值0-255如果不缩放到0-1或做Z-score网络收敛极慢甚至出现loss不降的情况。这不是模型问题是输入尺度问题。第二不要把标准化和理解数据分布割裂开。标准化之后数据变得“好看”了但你失去了原始数据的物理含义。我在做化工领域的机器学习项目时工程师们特别关心模型的可解释性标准化之后他们看不懂特征值了。后来我给他们解释“标准化意味着这个特征距离平均值几个标准差”他们才真正接受。用标准化之前先想清楚你的目标听众是谁是做预测还是做解释。5. 特征编码让算法听懂“分类”的语言5.1 标签编码和独热编码最基础也最容易选错类别特征不能直接输入模型必须转成数值。最朴素的做法是标签编码也就是把“红、绿、蓝”映射成0、1、2。这种做法的问题在于它暗示了类别之间有大小关系比如蓝2大于红0但其实颜色之间没有天然的大小顺序。对于树模型标签编码通常可以接受因为树模型做的是基于阈值的判断0、1、2的数值大小不会影响分裂逻辑但对于线性模型和神经网络标签编码会引入虚假的“顺序信息”结果很不稳定。独热编码把“红、绿、蓝”变成三个二值特征是不是红、是不是绿、是不是蓝。对于无序类别独热是更安全的选择。但它的问题也很明显如果类别的基数很高比如城市有几百个独热编码会产生几百维的稀疏特征计算量变大而且信息利用率不高。我处理这类问题时会先看类别的基数。如果基数小于10直接用独热编码简单可靠。如果基数在10到50之间考虑目标编码或频次编码。如果基数大于50一定要警惕维度爆炸通常需要结合业务把类别做聚合比如把出现次数少于一定阈值的城市合并成“其他”类。5.2 目标编码和频次编码处理高基数类别的高级手段目标编码是用目标变量的统计值均值或加权均值来替代类别本身的值。比如预测房价时把每个街区的类别替换成该街区历史房价的平均值。这个方法的优势是能压缩维度同时保留类别对目标变量的预测能力。但它的风险是容易过拟合尤其在类别样本量少的时候统计值不稳定。我通常的做法是加上平滑项def target_encoding(series, target, prior, min_samples_leaf20, smoothing10.0): temp pd.concat([series, target], axis1) temp.columns [category, target] agg temp.groupby(category)[target].agg([count, mean]) smooth 1.0 / (1.0 np.exp(-(agg[count] - min_samples_leaf) / smoothing)) encoding prior * (1 - smooth) agg[mean] * smooth return series.map(encoding)我在用这个方法的时候格外小心目标编码相当于引入了目标变量的信息如果直接在训练集上计算编码值再应用到验证集会造成严重的数据泄露。所以编码的统计量必须在训练集内部用交叉验证的方式来计算。初学者特别容易在这个地方翻车做完编码之后测试集效果虚高还以为自己模型练得多好。频次编码就简单得多直接用类别出现的次数或频率替换原始类别值。它不依赖目标变量所以没有数据泄露风险。缺点是没有利用类别和目标之间的关系信息表达能力比目标编码弱。在实际项目里我会把频次编码和目标编码结合使用各取所长。5.3 时间特征和文本特征的处理思路时间特征的处理也是个高频考点。原始时间戳直接喂进模型基本没有意义模型根本学不出规律。正确做法是提取出年、月、日、星期几、是否节假日、小时等特征。我自己做时间序列相关项目时经常构造“距上一个节假日的天数”、“在一天中的哪个时间段”这类特征。关键是把时间背后的周期性规律显式地表达出来而不是把时间戳本身丢给模型。文本特征如果不做深度语义模型传统做法是TF-IDF或词袋模型。TF-IDF能体现一个词在文档中的重要程度词袋模型则忽略了词序。如果文本较长且需要语义理解可以考虑用预训练语言模型生成embedding向量。不过embedding维度高、计算量大在传统机器学习场景下不一定划算。5.4 编码时的信息泄露问题再强调一次这是我见过初学者犯得最多、而且隐蔽性很高的错误。任何使用目标变量信息来构造特征的方法都必须极其小心地处理数据划分。除了目标编码之外还有几个常见的泄露场景在建模之前就对全量数据做了独热编码虽然独热本身不涉及目标变量但如果后续做了特征选择时用了全量数据计算特征和标签的相关性就会泄露。对时序数据做标准化时用了整个时间段的均值和方差而不是只用过去的数据来拟合。填充缺失值时用了测试集的信息去计算均值或中位数。这些问题在模型离线评估时可能表现不出来一旦上线真实数据分布变了模型性能就会崩。所以处理泄露问题的核心心法就一句话凡是涉及统计量的计算只能在训练集上完成。6. 特征构造从原始数据“造”出新特征6.1 组合特征和多项式特征让线性模型拥有非线性能力特征构造的核心目标是用已有特征组合出更能体现业务规律的新特征。线性模型为什么对很多非线性问题无能为力因为它的决策边界本来就是线性的。通过构造多项式特征比如原始特征x1、x2加上x1²、x1*x2、x2²线性模型就能在原始特征空间里拟合非线性关系。我在处理波士顿房价数据集的时候发现房屋面积和房价之间的关系并非完全线性。后来构造了一个面积和房间数的交互特征RM * LSTAT房间数和低收入人群占比的交互模型效果确实有提升。这种交互特征的意义在于低端社区的房间数量对房价的影响和高端社区的房间数量对房价的影响可能完全不同。如果只单独用RM或LSTAT模型学不到这种联动关系。用scikit-learn可以做多项式特征扩展但要注意度的选择。度太大会导致特征数量指数级爆炸而且极容易过拟合。我建议初始从度2开始尝试配合正则化来抑制过拟合。from sklearn.preprocessing import PolynomialFeatures poly PolynomialFeatures(degree2, interaction_onlyFalse, include_biasFalse) X_poly poly.fit_transform(X[[RM, LSTAT, DIS]])interaction_onlyTrue时只生成交互项不生成平方项include_biasFalse时不会增加一列全1的常数项避免和线性模型的截距项重复。6.2 基于业务含义的特征构造比算法技巧更值得花时间说实话特征构造最依赖的是你对业务场景的理解而不是代码技巧。我在做电商用户复购预测时原始数据里有用户注册日期和每次下单日期。如果只看这两个字段模型很难学到有效规律。后来我构造了“注册到首购的天数”、“平均复购间隔”和“近30天下单次数”等特征模型效果立刻有了明显提升。同样的道理适用于很多领域。化工领域的机器学习经常用到温度、压力、流速等传感器数据单纯把每个时间点的读数作为特征模型只能学到瞬时状态。但如果构造“温度随时间的变化率”、“过去一小时的平均压力”这类派生特征模型对过程状态的理解会大大增强。做特征构造时我有个习惯每构造一个特征都会先画出它和目标变量的散点图或分组统计表看单变量层面是否有关联。虽然单个特征可能不显著组合起来会有效果但这个方法能帮我过滤掉大量明显无效的构造尝试节省很多时间。6.3 数值特征分箱构造注意事项分箱这个操作我用得挺多但有几个细节容易忽略。分箱之后连续变量变成了有序分类变量这里就有个选择是直接对分箱结果做标签编码保留顺序还是独热编码不保留顺序对于树模型直接传给模型就行树可以在分箱后的类别上继续做分裂。对于线性模型我倾向于做独热编码因为线性模型无法表达“类别2是类别1和类别3的中间状态”它只能把类别1和类别2当作完全独立的两个变量。分箱的边界怎么定也有讲究。等距分箱实现简单但如果数据分布极不均匀某些区间可能没有样本或样本极少。等频分箱能保证每个箱子里样本量接近但边界可能落在很不直观的位置。还有一种做法是基于目标变量进行有监督分箱比如决策树的分裂点本身就是一种有监督离散化。这种方法信息利用效率更高但实现复杂我在实际项目里用得不多。6.4 特征构造的“试错”思维不要怕造新特征失败特征造出来效果不理想太正常了我造十个特征能留下三四个就已经很开心了。关键是建立一套快速验证的流程构造特征 → 训练模型 → 查看特征重要性或模型效果变化 → 决定保留或丢弃。不要造完就塞进去塞进去发现指标变差还得一个个排查是哪个特征拖了后腿浪费时间。我还习惯给构造出来的特征起一个能看懂的名字比如rm_lstat_interaction方便后续排查。有次我图省事命名成new_feature_1过了两周回来看代码完全不知道这个特征是从哪两个字段构造出来的最后只能从代码往上找白白浪费一个小时。7. 特征选择给模型做减法效果反而更好7.1 为什么要做特征选择维度诅咒不是开玩笑特征太多不一定是好事。高维特征空间带来的问题包括计算开销变大、容易过拟合、模型可解释性变差、特征之间容易存在多重共线性。统计上有维度诅咒的概念随着特征维度增加样本在特征空间的分布越来越稀疏模型很难从有限样本中学到稳定规律。这也是为什么有些时候你特征加了效果反而下降。我在跑一个开源的人脸识别项目时如果用原始像素特征直接训练分类器特征数可能几万维但训练样本只有几千张图。这时候如果不做特征选择或降维模型几乎必然过拟合。后来我先用PCA把维度降到几百再用分类器去训练效果反而更好。7.2 三大类特征选择方法过滤式、包裹式、嵌入式过滤式方法不依赖具体模型先根据统计指标筛选特征再进入模型训练。常用的指标有皮尔逊相关系数、卡方检验、互信息、方差阈值。方差阈值的思想很简单如果一个特征在所有样本上取值几乎不变那它对模型没有区分力可以直接剔除。皮尔逊相关系数适合线性关系互信息能捕捉非线性关系适用范围更广。包裹式方法把模型性能作为评价标准通过贪婪搜索选择特征子集。经典做法是递归特征消除即反复训练模型每次剔除权重最小或重要性最低的特征直到达到目标特征数量。这种方法效果好但计算量很大适合特征数量不是特别多的场景。嵌入式方法是目前我推荐的首选。它把特征选择嵌入到模型训练过程中。最典型的是L1正则化其特点是在优化过程中会把很多特征的权重压缩到0达到特征选择的效果。树模型的特征重要性也是嵌入式选择的代表XGBoost和LightGBM都原生输出特征重要性分数通常是基于特征被选为分裂节点的次数和带来的信息增益来衡量。我建议的实践路径是先用过滤式方法快速去掉明显无效的特征再用嵌入式方法做精细筛选。如果特征数量非常大而计算资源有限可以考虑PCA等降维手段。但要注意PCA之后特征失去了物理含义对可解释性要求高的业务场景不友好。7.3 用特征重要性筛选特征的实操要点在跑树模型时我经常直接看特征重要性来判断哪些特征值得保留。但这里有一个坑特征重要性高不等于特征一定好特征重要性低也不等于没用。当两个特征高度相关时模型可能随机选择其中一个作为主分裂特征另一个重要性被低估。这时候不能草率把重要性低的特征删掉。更好的做法是结合SHAP值来判断特征贡献。SHAP值基于博弈论中的Shapley值能解释每个特征对每个样本预测结果的贡献方向和大小。我在做一个信贷风控相关项目时SHAP值帮我发现了几个特征重要性排名不高、但对特定客户群体预测结果影响很大的特征。这种信息对业务方的价值远超单纯的特征重要性排序。7.4 多重共线性问题别让特征自己跟自己打架特征之间高度相关会让线性模型的系数估计变得不稳定。比如房价预测中“房屋面积”和“房间数量”高度相关两者同时放入模型后系数的含义变得很模糊。这时候L2正则化岭回归能缓解共线性问题它对权重的大小施加惩罚让相关特征的权重被“分摊”得更加均衡。检查多重共线性最常用的指标是方差膨胀因子VIF。VIF大于10通常认为存在严重的共线性。我通常的做法是先计算特征相关矩阵把相关系数大于0.8的特征对找出来然后根据业务含义或特征重要性保留其中更有价值的一个。8. 实战波士顿房价数据集完整走一遍特征工程流程8.1 数据基本情况和初步检查以经典的波士顿房价数据集为例我习惯性的第一步是写代码做全面体检import pandas as pd import numpy as np from sklearn.datasets import load_boston boston load_boston() df pd.DataFrame(boston.data, columnsboston.feature_names) df[MEDV] boston.target print(df.shape) print(df.isnull().sum().sum()) # 缺失值统计 print(df.duplicated().sum()) # 重复行统计 print(df.describe().T[[mean, std, min, max]])这个数据集有506行13个特征没有缺失值和重复值所以数据清洗环节压力不大。但如果你用的是从网上爬下来的数据基本都会碰到缺失和重复的问题所以前面的清洗步骤不能跳过。8.2 特征变换和构造我检查了特征分布后发现CRIM犯罪率和DIS到就业中心距离明显右偏直接做标准化效果不如先取对数再标准化。MEDV房价的分布也有一定右偏我做了对数变换后使用它作为监督信号训练出来的模型效果更好因为目标变量更接近正态分布时线性回归的假设条件更容易满足。关于交互特征我尝试构造了RM * LSTAT、RM * DIS、LSTAT * DIS等多个交互项最终保留了对模型有正向贡献的部分。这里提一个经验规律波士顿房价数据集里LSTAT低收入人群比例和RM平均房间数这两个特征对房价的影响最显著很多基于此数据集的经典模型实验都验证了这一点。8.3 特征选择和最终模型效果特征选择阶段我先用相关性分析看特征和MEDV的相关性然后用随机森林的特征重要性做进一步筛选。原始13个特征加上几个交互特征后我通过RFE递归特征消除确定了一个包含9个特征的子集。对比全特征模型和筛选后模型指标全特征模型特征工程筛选后模型训练集R²0.760.82测试集R²0.680.77交叉验证RMSE4.824.43可以看到经过特征工程处理和筛选之后模型的泛化能力明显提升。虽然R²的提升看起来不算大但在实际业务场景中5%-10%的指标提升可能就意味着显著的商业价值。这也验证了我一直强调的特征工程的价值是实打实的不是玄学。8.4 实战中的代码细节数据划分先后顺序最后我要特别强调一个顺序问题先划分训练集和测试集再做特征工程。很多人拿到数据先做标准化、先做缺失值填充再做train_test_split这样做是错的。正确顺序是先划分数据然后在训练集上做特征工程的拟合再把变换应用到测试集。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( df.drop(MEDV, axis1), df[MEDV], test_size0.2, random_state42 ) # 正确做法在训练集上fit scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 测试集只transform X_test_scaled scaler.transform(X_test)如果有人在测试集上重新fit了scaler那测试集的信息就已经“泄露”到模型里了你得到的评价指标都是虚的。这个问题在面试里经常被问到在实际项目里也是最常见的低级错误之一。9. 常见问题与排查技巧实录9.1 特征工程高频问题速查表我把这些年遇到和回复过的提问整理成一份速查表方便你直接对照排查问题现象可能原因排查方法和解决思路模型训练loss不降或是NaN特征数值范围差异过大梯度爆炸检查特征分布做标准化或归一化调低学习率测试集效果远差于训练集特征工程时发生数据泄露检查是否用全量数据fit了scaler或编码器改用Pipeline加了更多特征后效果反而变差维度诅咒 / 特征噪声过大做特征选择看特征重要性剔除无关特征线性模型系数含义不可解释特征之间存在多重共线性计算VIF删除相关度过高的特征或用L2正则化类别特征基数太高独热后维度爆炸没用编码策略改用目标编码、频次编码或做类别聚合目标编码后验证集效果虚高编码时用了全量数据统计量目标编码必须在训练集内部交叉验证计算模型在个别异常样本上完全失败异常值没有处理检查特征分布用IQR或Z-score检测并处理异常值9.2 我踩过的三个经典坑第一个坑在特征选择时用了全量数据的标签计算互信息然后拿这个结果去指导模型训练。越用越觉得效果不对后来发现是选择特征的过程中已经用到了测试集标签的信息导致评估结果过于乐观。本质上就是数据泄露。第二个坑对时序数据做了随机打乱的交叉验证导致特征工程中的统计量包含未来信息。时间序列数据和普通表格数据不一样样本之间存在时间依赖不能随机打乱。正确做法是用时间顺序划分用过去的数据来预测未来。第三个坑用树模型的重要性来做特征选择结果把两个高度相关的特征都给删了。后来发现其中一个特征在单独使用时有很强的预测能力但因为和另一个特征太相关树模型在两者之间随机分配了分裂机会导致重要性都被稀释了。现在我筛选特征时一定会先看相关性矩阵再做重要性分析。9.3 特征工程效果不达预期的排查思路如果你把特征工程做了一圈模型效果还是不行我建议按这个顺序排查先确认数据没有泄露这是红线泄露会让你的所有实验对比都失去意义。再确认评估指标合理分类问题在类别不均衡时不能用准确率建议看AUC或F1值。然后检查训练集和测试集的数据分布是否一致如果线上数据分布和训练数据有明显漂移再好的特征工程也无济于事。接着看特征是否真的和目标变量相关有时候你构造的特征方向就错了比如你想预测用户黏性但构造的特征描述的是用户注册时的状态和后续行为没有直接关系。最后才考虑模型复杂度是否不够不要一上来就怪模型。9.4 特征工程的可行自动化探索现在有很多自动化特征工程工具比如Featuretools它是基于深度特征合成的思路能够自动从关系型数据中挖掘新特征。我用过一段时间大部分情况下能生成几百上千个特征但很多特征是冗余或无意义的还需要后续做严格的筛选。我的态度是自动化工具适合做初筛和启发但不能完全替代人工对业务的理解。比如我在化工场景里知道温度变化的速率比绝对温度更重要这种业务知识自动化工具很难自动发现必须由熟悉流程的人来定义。把自动化生成的特征和领域专家构造的特征结合使用效果通常最好。10. 最后的个人心得特征工程没有银弹但有方法论写了这么多我最后想分享一个我在多次实践中逐渐形成的观点特征工程确实没有银弹不存在一套万能流程适用于所有数据集。但方法论是有的核心就三条。第一条是“先理解再处理”。拿到数据先看业务背景、字段含义、分布形态、和目标变量的关系想清楚了再动手。我见过很多同学拿到数据就一顿StandardScaler、OneHotEncoder猛操作做完才发现连字段是什么意思都没搞明白。第二条是“先建模再迭代”。不要妄图一次把特征做到完美先用一个简单的基线模型跑通流程再看哪些特征对效果贡献最大逐步补强。我几乎每个项目都是这么启动的先有一个能跑的版本再谈优化。第三条是“先评估再上线”。任何特征工程的改动都要通过离线评估和交叉验证来验证效果不能凭感觉。离线评估指标要和你真正关心的业务指标对齐比如你做的是用户留存预测光看AUC可能不够直观还要看高留存人群的命中率。最后再分享一个小技巧做特征工程时把每一次实验的特征列表和对应的模型指标记录在一张表格里。看起来像是增加工作量但等你做了几十组实验之后这张表能帮你快速复盘什么特征有效、什么特征无效甚至能帮你形成对当前业务场景的直觉。这是我从第一次做机器学习项目就开始养成的习惯后来帮我节省了大量的无效尝试时间。特征工程这条路没有终点每一个新数据集都是新的挑战。但它带给你的回报也是实实在在的——你会在无数次的实验中逐渐培养出一种“数据直觉”看到一张表就大概能猜到哪些字段会有用、哪些字段需要变换、哪些字段可能会泄露。这种能力靠看再多的教程都学不来只能靠一次次亲自踩坑总结出来。希望这篇内容能帮你少走一些我走过的弯路。
返回列表