ARTICLE DETAIL

资讯详情

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

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

特征工程实战:从数据清洗到特征选择的完整指南 1. 特征工程为什么被低估先看清它到底在解决什么问题说到机器学习大多数人第一反应是模型逻辑回归、随机森林、XGBoost、深度学习……很多新手把大量时间花在调参和换模型上却忽略了一个更关键的事实——喂给模型的原料决定了模型能力的上限。业内流传一句话数据和特征决定了机器学习的上限而模型和算法只是逼近这个上限而已。这句话不是鸡汤是无数项目的血泪总结。特征工程简单说就是把原始数据转换成更能表达业务规律、更便于模型学习的形式。它解决的几个核心问题很具体原始数据里噪声太多需要清洗量纲不一致需要统一尺度类别字段模型看不懂需要编码单个字段信息量不够需要组合出新的特征特征太多太冗余需要筛选降维。每一步都是在回答同一个问题我们如何把业务知识翻译成模型能听懂的语言。我见过不少实际案例同一个数据集A同学随便跑了个逻辑回归准确率82%B同学花了两天做特征工程同样的逻辑回归准确率直接到91%。这就是差距的来源。特征工程不是锦上添花它往往是项目能否从能跑变成能用的分水岭。这篇文章不打算写成教科书式的大全而是从实战角度出发按照一个项目推进的自然顺序把特征工程的关键环节拆开揉碎先讲数据清洗和变换再讲类别特征与缺失值处理然后是特征构造的思路接着是特征选择和降维最后给出一套可以落地的特征工程工作流。无论你是刚入门机器学习、还在为课程作业头疼还是在真实业务里被特征搞得焦头烂额这篇文章都应该能帮到你。2. 数据清洗与变换先让数字说人话2.1 缺失值处理别一上来就填均值很多教材讲缺失值上来就是用均值/中位数填充这个说法坑了无数人。缺失值处理的本质是回答一个问题这个值为什么缺失如果某个字段的缺失是随机的比如用户调研问卷里最喜欢的功能这项有一部分人就是没填那用众数或中位数填充是合理的。但如果缺失本身带有信息比如年收入这项高收入人群可能更倾向于不填那直接填均值会把拒绝透露收入这个信号直接抹掉。这种情况下更好的做法是为缺失值增加一个是否缺失的指示特征再去填充数值部分。这样模型可以自己学习缺失这件事本身有没有预测力。我踩过的一个坑是当时处理医疗数据体重字段缺失率高达30%我图省事直接填了均值结果模型在重症患者上的表现明显变差。后来查文献才知道重症患者往往因为无法测量而缺失体重记录这个缺失本身就是病情严重的信号。所以现在凡遇到缺失值我的默认流程是先统计每个字段的缺失率再结合业务判断缺失机制最后决定是填充、删除还是单独建指示特征。2.2 量纲统一标准化与归一化不能混为一谈标准化Standardization和归一化Normalization经常被混用但它们在实战中的适用场景完全不同。标准化是把数据变成均值为0、标准差为1的分布公式是 $z (x - \mu) / \sigma$。它不改变数据的分布形状只改变尺度和位置。归一化则是把数据压缩到[0,1]区间公式是 $x (x - x_{min}) / (x_{max} - x_{min})$。两者区别在于标准化对异常值更稳健因为用的是均值和标准差个别极端值影响有限而归一化对异常值极其敏感——一个极端大值会把其他所有值挤压到接近0。选哪个取决于模型类型。对线性回归、逻辑回归、SVM、KNN这类基于距离或梯度的模型标准化是常规选择因为它能让梯度下降收敛更快、距离计算更公平。对神经网络标准化几乎是必须的否则某些大数值特征会主导loss。而对决策树、随机森林、XGBoost、LightGBM这类树模型量纲统一根本不需要做因为树模型是按特征值排序后切分的单调变换不影响分裂点。这个点在实际项目中经常引发争论。我的建议很直接先确认你的模型类型再决定要不要做变换。如果做了变换务必记住——训练集和测试集要用同一个scaler先在训练集上fit再transform测试集千万不能分别fit否则会造成数据泄露和分布不一致。2.3 长尾分布与对数变换把指数级拉回线性级很多真实特征呈长尾分布典型的如用户消费金额、网页访问时长、城市人口数——少数样本取值极大大部分样本挤在很小的区间里。这种分布对线性模型和神经网络都很不友好因为极端值会主导损失函数模型容易学偏。对数变换是处理这类问题最经典的手段。取 $log(x1)$ 之后原本相差100倍的数值变成只相差约4.6倍分布会明显拉近正态。实际操作里我几乎对所有的金额时长点击量这类正偏态特征都会尝试做一次对数变换然后观察变换前后的分布图。如果变换后更接近钟形分布就用变换后的特征如果没啥变化就保持原样。还有一种更灵活的思路是分箱把连续变量切成若干个区间如把年龄分成未成年、青年、中年、老年把收入分成低收入、中收入、高收入。分箱的好处是能捕捉非线性关系且对异常值天然免疫坏处是会丢失部分信息切分点也需要业务知识支撑。我一般在不清楚特征和目标函数是否线性相关时会同时保留原始数值特征和一个分箱后的类别特征让模型自己决定用哪个。3. 类别特征与缺失值最容易出闷亏的处理环节3.1 编码方式怎么选独热、标签还是目标编码类别特征的处理是特征工程里最容易被轻视、也最容易出问题的环节。最常见的错误是拿到一个字符串特征的分类就无脑独热One-Hot完全不管这个字段的基数Cardinality有多高。先理清三种编码的区别和取舍。**标签编码Label Encoding**就是给每个类别一个整数编号如红、绿、蓝变成0、1、2。这种编码的问题是它人为引入了顺序关系而很多类别并没有顺序含义模型可能会学到蓝色是红色的两倍这种无意义的关系所以只适合有序类别如学历小学初中高中大学。独热编码适用于无序、基数较低的类别特征。每个类别变成一个0/1向量互不干扰。但问题也明显如果一个字段有100个类别就多出100个稀疏列不仅内存吃紧还可能引入维度灾难。**目标编码Target Encoding**则是用类别对应的目标变量的均值在回归中或概率在分类中来替换原始类别比如城市这个字段用该城市用户的平均消费金额来编码。它的信息利用率极高但极易过拟合必须配合交叉验证或平滑处理加入全局均值的加权否则模型会在训练集上表现得异常好、在测试集上崩掉。实战中的选择逻辑我一般这么判断类别数不超过10个且无明显顺序 → 独热类别数超过10个、又带着明显顺序 → 标签编码类别非常多有些字段几百个类别→ 优先考虑目标编码或者先用频率编码frequency encoding3.2 高基数特征碰撞编码和哈希技巧高基数类别特征是业务数据里最难啃的骨头。比如电商场景里的商品ID动辄几十万个类别用户ID、设备ID也类似。这种特征直接独热会直接爆内存直接目标编码又会过拟合。我在真实项目里用过两种比较有效的方案。第一种是碰撞编码Count Encoding/Hashing Trick——把类别通过哈希函数映射到固定长度的整数空间。比如用Python的hashlib或者sklearn.feature_extraction.FeatureHasher设定输出维度为10000或50000所有类别hash到这个范围里。优点是内存可控、不需要维护映射表缺点是不同类别可能碰撞到同一个桶里造成信息损失但实践表明只要桶数够大损失一般可接受。第二种是频率编码用每个类别出现的次数或频率替换原值。这个做法简单粗暴但意外地好用尤其当类别分布极不均匀时稀有类别和高频类别本身就有业务含义。比如用户行为日志里某个操作发生频次高的用户可能更活跃而这个频次特征就能直接参与建模。我的经验是高基数类别处理没有银弹往往是哈希编码 频率编码 少量核心类别独热的组合拳。先看业务上有哪些类是核心类比如Top10品类单独拎出来独热保留语义其他长尾类别统一用哈希或频率兜底。3.3 时间特征与周期性别忘了星期几这类细节时间字段是个特殊的存在它不是数值型也不是纯类别型处理方式取决于你要解决什么问题。如果数据集是按时间排序的比如股票价格、销量序列通常要做滞后特征lag前1天、前7天、前30天的值。但如果是普通业务表时间戳往往能拆出更丰富的维度。日期字段至少可以拆成年、月、日、星期几、是否周末、是否节假日、一年中的第几周、一天中的哪个时段。这些看似琐碎的信息在某些场景里是强特征——比如外卖订单量明显受是否周末和饭点时段影响零售销量受月初/月末影响。周期性特征还要注意一个坑直接用数值表达月份1-12会让模型以为12月和1月距离很远但它们在周期上其实相邻。正确的做法是用正弦和余弦将周期展开$sin(2\pi \cdot month/12)$ 和 $cos(2\pi \cdot month/12)$这样就能同时保留周期性和连续性。这个方法对小时、星期、月份都适用是一个性价比极高的特征变换技巧。4. 特征构造从原始字段里挖出新信息4.1 交叉特征让模型看到组合拳的力量很多时候单个字段的预测力有限但两个字段组合起来信息量就大幅提升。最经典的例子是经典的性别×商品类别——男性用户购买电子产品比例明显高于女性这个信息在性别和商品类别各自独立存在时都无法体现一组合就出来了。交叉特征怎么做两个类别特征之间可以做独热编码的笛卡尔积得到新的0/1特征。但维度会急剧膨胀所以实际中更推荐两个思路一是对树模型直接用原始特征让树自己去学组合树模型天然擅长找交互二是对线性模型手动构造有限数量的、业务上有明确意义的交叉特征。我习惯先画几个透视表pivot table看一下候选特征组合对目标的分布差异。比如想看品类×城市有没有信息量就统计每个品类在每个城市的平均消费如果差异明显就值得做成交叉特征。连续特征之间也可以做交叉通常是比值或乘积比如消费金额/浏览时长代表单位时间的消费强度点击次数/曝光次数就是点击率。这类派生特征往往比原始特征更能直接对应业务指标模型也更容易学到规律。一个项目里我靠一个消费金额/距离的比值特征就把配送时长模型的误差降低了15%——这就是业务知识转化为特征的直接收益。4.2 聚合特征GroupBy 操作的妙用聚合特征是结构化数据里最常用的特征构造方式核心就一句话针对某个ID做分组统计。比如对用户ID分组计算其历史订单的平均金额、最大金额、订单数、最近一次消费距今的天数RFM模型就是典型的聚合特征。这种特征在风控、推荐、用户画像场景里几乎是必用的。原因很简单一个用户ID本身不代表任何数值信息但这个用户过去30天花多少钱、买几次、最近一次是什么时候这些聚合统计量才真正描述了用户的行为特征。聚合特征的构造通常用Pandas的groupbyagg组合一次可以算出一大堆统计量。但要控制数量不要一上来就算几十个聚合特征——每个聚合特征都需要时间窗口的选择近7天、近30天、近90天而窗口选择本身就需要业务判断。我一般先确定核心ID用户、商品、店铺再确定关键行为下单、浏览、退换货最后确定统计口径均值、总和、标准差、数量、最近时间差。每一步都用业务常识检验一下——这个特征如果让业务人员来看能不能解释得通。4.3 业务特征最容易被忽略的信息金矿特征工程的最高级形态不是各种技术花活而是把领域知识编码进特征。这个观点我在很多场合提过机器学习的上限很大程度取决于你能否把业务里人脑已知、模型未知的信息转化为特征。举个例子在信贷风控场景中用户手机号归属地和身份证归属地是否一致比单独用这两个字段做独热编码来得更有价值。在风控反欺诈场景中同一设备号绑定的账户数就是一个极其有效的业务特征直接捕捉了团伙作案的信号。在销量预测中商品是否参与促销是否为新品是否处于季节性热销期这些业务标签对模型的影响远大于一堆堆复杂的统计特征。这类特征的挖掘方法很简单坐下来和业务方聊天问一个问题你们平时是怎么判断一个用户/订单/商品好坏的把业务方的判断逻辑拆解成规则把规则转成特征。我在一个保险项目中就靠一个客户在投保前是否主动留言咨询的二值特征把客户流失预测模型的AUC提升了0.03。这个信息之前一直躺在数据库里没人想过它居然是个强特征。5. 特征选择与降维别让模型淹没在冗余特征里5.1 过滤式、包裹式、嵌入式三种思路的取舍特征构造做得越多越容易陷入特征爆炸的困境。几十个特征还好如果造出上百个特征模型训练变慢、过拟合风险增加、可解释性变差。这时候就必须做特征选择。特征选择有三条主流路线过滤式Filter先评估每个特征和目标的相关性挑相关性高的不依赖模型。常用的有皮尔逊相关系数、互信息、卡方检验。优点是快缺点是单变量评估忽略了组合效应可能把两个单独平庸、组合起来很厉害的特征一起丢掉。包裹式Wrapper以模型性能为评分标准迭代地加特征或减特征如递归特征消除RFE。效果好但计算开销很大特征多的时候跑起来让人着急。嵌入式Embedded把特征选择嵌进模型训练过程最典型的就是L1正则化Lasso和树模型的特征重要性。L1正则天然会把不重要的特征权重压到0做完训练直接看稀疏权重就知道哪些特征没用了树模型训练完也可以输出feature_importance。我一般用三级漏斗先用过滤式做一轮快速筛选把明显无关、方差几乎为零的特征删掉再用L1正则或树模型重要性做第二轮筛选挑出Top N特征最后如果有精力再对候选特征做一轮RFE精修。这套流程兼顾了效率和效果。5.2 相关性分析与多重共线性别让慢性毒药拖垮你的模型多重共线性multicollinearity是指两个或多个特征高度相关比如同时放了用户年龄和出生年份这两者携带的信息几乎完全一致。对线性模型来说共线性会导致回归系数极不稳定稍微换一个样本系数可能就正负翻转让模型的解释性完全失效。对树模型影响相对小但也会造成特征重要性的分裂让模型错误地认为某个特征重要性极低。检测多重共线性最直接的方法是计算相关性矩阵correlation matrix把相关系数超过0.8的特征对找出来然后结合业务决定保留哪一个。此外还有方差膨胀因子VIFVIF 10通常被认为共线性严重。我在实际项目里见过最隐蔽的共线性问题是独热编码后的虚拟变量陷阱一个3分类字段独热后生成3列三列加起来等于1存在完全共线性。这时候不仅系数解释不了某些模型甚至会出现数值不稳定。解决办法很简单独热时删掉一列也就是drop_firstTrue除非你明确需要全部保留用于其他用途。5.3 PCA与嵌入向量什么时候该用什么时候别碰PCA主成分分析是最经典的降维工具它通过线性变换将多个相关特征压缩成少数几个正交的主成分。优点是一步到位在特征上百个、模型训练很慢时非常有效缺点是降维后的主成分没有可解释性你没法说这个主成分代表收入水平。所以我对PCA的态度很明确当特征数量庞大比如文本TF-IDF向量化后几千维或者模型性能主要靠非线性变换时PCA是救命的。但如果你要做的模型需要向业务方解释哪些因素影响客户的流失千万别用PCA保留原始特征用树模型重要性即可。业务方不会接受主成分1每增加一个单位流失率上升5%这种说辞。深度学习时代还有一条新路用Embedding做降维。对类别特征特别是高基数的可以先训练一个浅层网络得到实体嵌入再把嵌入向量作为特征喂给主模型。这个做法在推荐系统里很常见能把几十万维的稀疏ID压缩成几十维的稠密向量而且向量在隐含语义层面已经编码了相似性信息。6. 一套能在实际项目中落地的特征工程工作流6.1 一个通用流程从原始表到建模数据的完整链路讲了这么多理论和方法最后给出一套我在多个项目中反复验证过的标准流程。这个流程适用于大部分结构化数据项目新手可以按流程执行有经验的朋友也可以当作自查清单。第一步业务理解和数据盘点。拿到数据后先不急着写代码先看每一列的字段说明、数据类型、含义统计缺失率、分布、唯一值数量。这一步通常花15到30分钟能避免后面走很多弯路。第二步数据清洗。处理缺失值、删除或处理异常值、统一格式。先用一个小函数扫描所有字段的缺失率和异常值分布输出一个数据质量报告再逐字段决定策略。第三步特征变换。连续特征做标准化/归一化视模型而定正偏态特征尝试对数变换时间字段拆解类别特征做编码。第四步特征构造。根据业务知识构造交叉特征、聚合特征、业务规则特征。这个环节是产出最多特征的地方也是拉开项目差距的关键。第五步特征选择。用过滤式嵌入式包裹式的组合拳筛选有效特征检查多重共线性必要时做PCA降维。第六步验证与迭代。先训练基线模型然后逐步添加各类特征比较模型效果保留让效果提升的特征移除无效甚至拖后腿的特征。这一步体现了特征工程的迭代属性——不是一次做完美而是多次循环逼近最优。这套流程的特点是用一条明确的主线串联起所有操作每一步经受住了实践检验在数据竞赛里拿过不错的名次在工业项目里也撑住过线上流量。6.2 实际项目里的特征命名与版本管理特征工程做到后面特征数量动辄上百个如果特征命名没有规范一个月后你自己都看不懂当时在干什么。我吃过这个亏所以现在强制自己遵守三条规则第一特征命名采用来源表_字段_处理方式_窗口的格式。比如order_damount_sum_30d表示订单表里消费金额字段、近30天求和看到名字就能秒懂这个特征怎么来的。第二每个特征保留生成脚本的版本号。我维护一个特征仓库目录按日期和版本存特征生成代码代码里注明了输入表、逻辑、作者。这样模型上线后如果发现某个特征有问题可以快速定位是哪一步生成的。第三特征数据和模型参数分开存。特征工程产出的特征表单独存储模型训练时从特征表读取模型参数存到另一个位置。两者解耦遇到数据回流或特征重新生成时不需要重新训练模型就能快速排查。6.3 关于验证数据泄露最容易翻车的暗礁特征工程里最致命且最隐蔽的错误是数据泄露Data Leakage——你在特征里无意中包含了未来信息或目标信息。一个典型翻车场景在时间序列任务里你用了全量数据的均值做填充而不是只用截至训练时间点之前的数据。另一个常见场景目标编码时你在整个数据集上计算了目标均值然后直接用这个均值编码训练集和测试集测试集信息就悄悄渗进了训练集模型评估结果极其乐观一上线就崩。避免数据泄露的标准做法是所有需要统计信息的处理标准化、归一化、填充均值、目标编码、聚合特征都必须在训练集上完成统计后再应用到验证集和测试集。在时间序列场景中要严格按时间划分数据保证训练集的时间都在测试集之前特征统计只使用训练集时间窗内的数据。这个原则我在每个项目里都反复强调因为一旦泄露前面再多的特征工程都是空中楼阁。6.4 从数据处理到模型训练别再让特征成为统计孤儿最后一个经验也是很多项目的通病特征工程做完模型训练做完了上线了特征就没人管了。这是不对的。特征需要持续的监控和更新——数据的分布会漂移用户的习惯会变去年有效的特征今年可能就失效了。所以我在项目上线后都会加一个特征监控模块定期计算特征分布的统计量均值、方差、分位数和训练时的分布做对比一旦发现偏差超过阈值就报警。这个习惯帮我提前发现了好几次数据质量问题包括一次上游数据源接口升级导致整个字段含义改变的问题——如果不是监控到了特征分布突变那个模型可能会在线上悄悄降智很久。用个人真实体会来收尾吧做了这么多年特征工程最大的感触是它没有标准答案每个数据集都有自己的脾气——但正因为如此它也是机器学习里最能看到付出就有回报的环节。每当我困在一个模型性能瓶颈里时回到数据里重新审视特征往往会有柳暗花明的发现。如果你正为模型效果发愁不妨先别急着换更复杂的模型回到特征工程这一步那里往往藏着打开下一扇门的钥匙。
返回列表