第一篇咱们聊了"机器学习是什么",今天聊点实在的——为什么你的模型训出来一团糟,但别人用同样的算法就能跑出好结果?
答案十有八九是数据预处理和特征工程。这俩活儿在教科书里通常只占一两章,但在真实项目里,它们吃掉的时间至少是整个项目周期的80%。你调模型花一周,调数据得花一个月——这话听起来夸张,干过的都知道我说得保守了。
咱们一项一项拆。
缺失值处理——你永远不知道数据能有多脏
真实数据里缺失值无处不在。传感器某天坏了、人工录入漏了几行、系统迁移丢了一部分、用户没填那个字段——原因千奇百怪,结果就一个:你拿到的DataFrame里一堆NaN。
处理缺失值没有"标准答案",只有"场景适配":
最简单的做法是直接删除。如果缺失的行占比很低(比如小于5%),删掉就行了,省事。但如果某个字段缺失率超过30%,你直接删列可能会丢掉重要信息;删行的话数据量损失又太大。
均值/中位数/众数填充是最常见的补救措施。连续型数据填均值或中位数(中位数对异常值更鲁棒),分类型数据填众数。但这么做有个隐患——人为地给数据引入了"峰值",相当于你在原本没有数据的地方强行塞了一个"最可能的值",这个值在模型看来就是事实,会扭曲模型对真实分布的认知。
稍微高级一点的是模型预测填充——用其他字段做特征,把缺失的那个字段当作预测目标,用随机森林或者KNN去预测缺失值。这个方法的逻辑是"用数据本身的信息来补全数据",比填均值更贴近真实分布。代价是计算量大,如果数据量小还可能引入过拟合。
还有一种更极端的方式,在某些场景下反而效果不错——把缺失当特征。不填充缺失值,而是加一个二值标志列"该字段是否缺失",然后把缺失的那个字段本身填充成一个固定值(比如-1或者0)。这样模型可以学习到"缺失本身是一个信号"——在某些业务场景里,用户不填某个字段本身就包含了信息(比如用户不填收入,可能意味着收入不稳定)。
异常值检测——那几个"离谱"的数据点,可能毁了你的整个模型
异常值这事儿吧,有时候是数据录错了(比如年龄填了250岁),有时候是真的存在但极其罕见(比如某天的流量暴增了100倍)。你得区分这两种情况。
统计方法上最常用的是3σ原则——超过均值±3个标准差的数据点视为异常。这个方法的假设是数据服从正态分布,如果你的数据是长尾分布,那3σ会误杀很多正常值。
更稳健的是IQR(四分位距)法——Q1-1.5IQR和Q3+1.5IQR作为上下界。这个不依赖正态分布假设,对大部分数据分布都适用。
还有一个极易被忽略的事儿——异常值的判断要结合业务逻辑。某个产品的转化率在促销日从2%涨到了8%,在统计模型看来这是异常值(远超3σ),但在业务层面这就是促销正常效果。你如果机械地把这个样本删了,模型就学不到"促销能带来转化率飙升"这个关键规律。
所以在工业界,异常值处理一般分两步:先用统计方法粗筛出一批"疑似异常",然后让业务专家逐个确认这些值到底该不该留。这不是算法问题,是领域知识问题。
数据归一化/标准化——不做的后果就是模型学偏了
如果你的特征里既有"年龄"(范围0-100)又有"年收入"(范围3万-200万),两者的数值尺度差了好几个数量级。对于依赖距离度量的算法(KNN、SVM、K-means)来说,尺度大的特征会主导距离计算,年龄那点差异完全被忽略。
解决方式就是归一化或者标准化。
Min-Max归一化把数据压缩到[0,1]区间,公式是(x-min)/(max-min)。好处是保留原始数据的分布形状,坏处是对异常值极其敏感——你的某个值如果远远大于其他值,其他所有值都会被挤压到接近0的位置。
Z-Score标准化把数据变成均值为0、标准差为1的标准正态分布。公式是(x-均值)/标准差。这个不怕异常值(因为用的是均值和标准差,单个点影响有限),是工业界用得最多的方式。
对于树模型(随机森林、GBDT),归一化基本不需要做,因为树模型的决策边界跟特征尺度无关。但深度学习和基于距离的模型,归一化是必做且要做在前面的。
编码分类变量——把"男""女"变成数字,没你想的那么简单
机器学习模型只认数字,不认字符串。你的"性别"列里写着"男""女",必须变成1和0才能喂进模型。
最直接的是Label Encoding,直接把"男"映射成0、"女"映射成1。这个简单粗暴,但有个隐含问题——你给类别赋予了"顺序",模型可能误以为0<1、男<女,在某些线性模型里会产生误导。
所以大部分场景下推荐One-Hot Encoding——"性别"变成两列"性别_男"和"性别_女",一个人如果是男,则"性别_男"=1、"性别_女"=0。这样彻底消除了顺序关系。
但One-Hot有个致命缺陷——如果某个分类字段有上百个取值(比如"城市"有200个城市),One-Hot出来就是200列,特征维度直接爆炸。这时候可以考虑Target Encoding——用"该类别下目标变量的均值"来编码。比如"城市"这个类别特征,用"该城市用户的平均购买率"作为编码值。这种方法信息量丰富、维度低,但容易过拟合,通常需要用交叉验证来做平滑处理。
特征工程——让你跟普通玩家拉开差距的真正壁垒
前面说的都是"数据清洗",现在到了"特征创造"——这是真正区分"会用工具的人"和"真正懂的人"的分水岭。
特征交互是特征工程里最经典的操做。比如"年龄"和"收入"单独看可能都一般,但"年龄×收入"这个交叉特征可能跟购买力高度相关。人为创造这种"乘法特征""加法特征"或者"比值特征",能让线性模型学到非线性关系。
统计聚合在处理用户行为数据时特别有效。比如原始数据是用户的每次点击记录(几十万行),你要预测"这个用户会不会购买"。你需要把点击记录按用户聚合起来——"这个用户过去7天的点击总次数""过去30天的平均停留时长""最近一次点击距离今天的天数"。这些聚合统计量才是真正跟"购买意愿"相关的特征。
时间窗口特征非常容易被忽略,但在很多场景下极其关键。"今天周几""是否节假日""距离上一次购买过去了多少天"——这些基于时间的特征在电商、金融、交通预测里往往比复杂的模型架构更管用。
领域知识的注入——这个没法教,只能靠积累。做信贷风控的人知道"负债收入比"比单独的收入和负债都有用;做推荐系统的人知道"用户最近浏览的品类"比"用户历史总购买品类"更有预测力。这些特征不可能从原始数据里自动发现,必须靠对这个行业的理解。
特征选择——少即是多
你创造了100个特征,不是所有都有用。有些特征是噪声、有些特征互相高度相关(比如"房屋面积"和"房屋面积+10"几乎是同一回事)。保留过多的无效特征,不但增加训练时间,还会造成过拟合。
常用的特征选择方法:
过滤法:计算每个特征和目标变量的相关性(皮尔逊相关系数、互信息),只选排名靠前的K个
包裹法:用模型本身来评估特征的重要性(比如随机森林的feature_importance、L1正则化自动压缩不重要特征的系数到0)
嵌入法:在训练过程中自动做特征选择,比如XGBoost的增益重要性会自动告诉每个特征的贡献度
我个人最常用的是"先用随机森林跑一遍看feature_importance,砍掉贡献度最低的30%特征,再用XGBoost做精细调优"。这个流程不复杂,但效果极其稳定。
说了这么多,你可能已经发现了——数据预处理和特征工程没有"一键完成"的傻瓜式操作。每一份数据都有自己的脾气,你必须跟它相处一段时间,摸透它的缺失模式、分布形态、异常来源、业务含义,然后针对性地做处理。这个"跟数据相处"的过程,任何AutoML工具都替代不了。
很多新人把大量精力花在调模型上,觉得"换一个更先进的架构就解决问题了"。但真实情况是——一个清洗得干干净净、特征设计得恰到好处的数据集,配上最简单的逻辑回归,效果往往优于一团乱麻的数据配上最牛逼的Transformer。
别再问"哪个模型最厉害"了。先问问自己:"我的数据准备好了吗?"