首页 / 资讯中心 / 文章详情

机器学习特征缩放全解析:量纲、方法与三大实操坑

机器学习特征缩放全解析:量纲、方法与三大实操坑 ★ FEATURED ARTICLE
1. 先从一次翻车的K近邻实验说起特征量纲如何毁掉模型1.1 那次预测偏差的完整经过承认一件事我入行第三年才真正搞懂机器学习里的特征缩放Feature Scaling而且是在一次让人脸红的模型翻车之后。那时候我做某平台的房价回归预测特征包括面积、房龄、卧室数量和到地铁站距离我图省事直接把原始特征丢进K近邻模型就跑结果验证集误差高得离谱调了半天k值和特征组合毫无起色。最后偶然做了一次标准化同样的数据和参数误差直接降了六成左右。这篇文章就围绕这次经历展开特征缩放到底在解决什么问题什么时候必须用什么时候用了也白用以及实操中最容易踩的坑。不管你是刚接触机器学习的新手还是跑过不少模型但始终没搞懂该不该缩放的开发者按这条思路过一遍基本不会再在缩放问题上犯迷糊。当时具体的数据大概是这样的面积从50平到400平房龄从1年到50年卧室数量1到10间到地铁站距离从200米到5000米。四个特征四种量纲四种尺度。我用K近邻做回归k取5结果平均绝对误差大得没法看。一开始我以为是特征没选好删了这个加那个又怀疑是数据噪声太多还试过调k值从3调到50最佳情况也就那样了。整整折腾了一个下午人都在崩溃边缘最后无意间看到一篇关于数据预处理的笔记才意识到问题可能出在特征量纲上。于是我用标准化把所有特征处理了一遍重新跑同样的K近邻误差直接降了一个量级。这次经历给我的教训不是K近邻需要缩放这么简单而是理解了一个更深层的原理在基于距离的算法里特征不缩放等于把模型的选择权拱手让给数值大的特征。1.2 欧氏距离里发生了什么数字大的人说了算K近邻的核心是距离。两个样本之间的欧氏距离长这样import numpy as np def euclidean_distance(a, b): return np.sqrt(np.sum((a - b) ** 2)) # 两个样本只在面积和房龄上有差异 a np.array([120, 2.5]) # 面积120平房龄2.5年 b np.array([125, 4.0]) # 面积125平房龄4年 print(euclidean_distance(a, b)) # 约5.22面积差了5它的平方贡献是25房龄差了1.5平方贡献是2.25。面积在距离计算里占了绝对优势。如果换成一个数值范围更大的特征比如到地铁站距离样本之间动辄差几百米甚至上千米平方之后就是几十万级别房龄、卧室数量这种小数值特征的贡献直接被淹没。所谓距离最近本质上变成了到地铁站距离最近模型退化成只看一两个特征跟你想拟合的目标半毛钱关系都没有。这不是K近邻独有的问题。凡是需要计算距离、相似度、内积的算法比如K均值聚类、支持向量机、基于距离的异常检测都会踩同一个坑。特征缩放要做的事情就是把不同量纲、不同单位的特征统一到一个可比较的尺度上让每个特征在模型里都有平等的表达机会。道理听起来朴素但什么时候该做、用哪种方法做、做完怎么验证这里面的门道真不少。2. 缩放的本质把不同尺度的信息摆到同一张桌上2.1 距离与相似度计算会被量纲绑架先扯一个生活化的类比。你去水果摊买西瓜老板说这个西瓜3斤重、30块钱你会用价格除以重量来判断性价比不会直接拿斤和元去比大小。但很多模型没有这个常识它只会机械地比较数值。拿两个特征来说一个是从0到1的标准化评分一个是从0到10000的年收入在模型眼里年收入特征携带的数值波动天然比评分大一万倍。这不是因为年收入更重要纯粹是单位在捣乱。把特征比作不同刻度的尺子厘米刻度上的长度和毫米刻度上的长度放在同一个算术式里相加得到的结果完全取决于你选了哪把尺子而不是物体的真实物理长度。特征缩放就是强制所有尺子统一刻度。对距离类模型来说统一刻度之后每个特征对距离的贡献才真正反映它数值上的差异而不是它在单位上的差异。这样K近邻选出来的邻居才是真正各方面都接近的样本而不是被某个大数值特征绑架的相似样本。2.2 梯度下降需要一条平坦的碗壁距离类模型好理解但很多人会疑惑线性回归、神经网络这些用梯度下降的模型为什么也强烈建议缩放这就要看损失函数的地形了。梯度下降的本质是沿着山坡往下走。如果两个特征尺度差异巨大损失等高线会变成一个非常狭长的椭圆形看起来像一条深而窄的峡谷。在峡谷地形里梯度方向往往跟最优解方向不垂直梯度下降就会走出之字形来回震荡收敛慢不说还容易在崎岖区域卡住。把特征缩放到相近范围后等高线趋近圆形梯度方向直指底部收敛速度提升一个数量级是很常见的事。这也是为什么神经网络训练前几乎都要做标准化或归一化。网络层数越深对输入尺度越敏感不缩放时你可能要反复调学习率才能勉强收敛缩放之后同样的学习率直接跑得很顺。用通俗的话讲缩放是给优化器修了一条平坦的碗壁让它沿着碗壁顺利滑到底而不是让它在歪歪扭扭的陡坡上挣扎。2.3 正则化惩罚在比较不同量级的系数第三个常被忽略的理由与正则化相关。岭回归的L2惩罚、Lasso的L1惩罚惩罚的对象是系数大小把系数的平方或绝对值加起来叠到损失函数里。可特征量纲不同系数天然就差了十万八千里。举个例子特征A取值范围0到1拟合出来的系数可能是5特征B取值范围0到10000拟合出来的系数可能只有0.005。两者对预测的实际贡献可能差不多但在惩罚项里5的平方是250.005的平方是0.000025惩罚几乎全部落在特征A头上。这等于你本来想对所有特征施加相同的正则强度实际效果却是对某些特征格外苛刻。缩放之后系数大小才真正反映特征贡献的权重正则化也才算公平执法。除此以外缩放还能让线性模型的系数具有可比性方便做特征重要性的初步判断。量纲统一之后你至少不会被收入系数是0.0001这种假象误导。3. 四种主流缩放方法怎么选从标准化到鲁棒缩放3.1 StandardScaler默认选项但别在偏态分布上盲目用最常用的特征缩放是标准化也叫Z分数归一化。公式很简单z (x - mean) / std做完之后特征的均值变成0标准差变成1。数据不再有单位变成了相对均值偏离了几个标准差的统计量。它不保证把数据限制在某个区间内如果数据接近正态分布标准化后大部分值会落在-3到3之间。标准化适合绝大多数场景尤其适合后续要接线性模型、神经网络、PCA这些方法。它有两点需要注意第一它对异常值比较敏感因为均值和标准差都会被极端值带偏这一点后面单独讲第二它治不了偏态分布。如果原始特征严重右偏比如收入、点击量这种长尾数据标准化之后依然是偏态的只是换了位置和尺度。正确做法是先做对数变换或Box-Cox变换再进行标准化。from sklearn.preprocessing import StandardScaler import numpy as np data np.array([[100], [200], [300], [400], [500]]) scaler StandardScaler() scaled scaler.fit_transform(data) print(scaled.ravel()) # [-1.41 -0.71 0. 0.71 1.41]很多人误以为标准化会改变特征分布的形状其实不会它只是平移和缩放。如果一个特征原始分布是右偏的标准化后还是右偏只是看坐标轴变了一个单位。3.2 MinMaxScaler有界需求的稳妥选择第二种主流方法是最大最小值缩放公式是x_scaled (x - min) / (max - min)输出严格落在0到1之间也可以用feature_range参数改成任意区间。它的优点是边界确定适合对输入范围有硬性要求的场景比如使用sigmoid或tanh激活函数的神经网络或者像素值这类天然有明确取值范围的输入。缺点也很直接它完全由最大值和最小值决定一个极端异常值就会把min或max拉得很远其余正常数据会被压缩到非常窄的区间里。比如某个特征99%的值在0到100之间突然冒出一个10000的异常值缩放之后正常数据几乎全部挤在0到0.01之间特征的信息被稀释得所剩无几。所以用MinMax之前务必先检查有没有异常值。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(data)3.3 RobustScaler异常值存在时的救星如果数据里异常值比较多或者你根本不知道有没有RobustScaler是更稳妥的选择。它用中位数替代均值用四分位距IQR替代标准差x_scaled (x - median) / IQR中位数和IQR对极端值不敏感哪怕数据里有几个离谱的异常值缩放结果也不会被带偏太多。它的输出没有固定边界中心在0附近尺度按四分位距来定。我在实际项目中处理收入、价格、点击量这些带长尾和异常值的数据时RobustScaler的出镜率最高。尤其在做聚类或异常检测之前先用RobustScaler打好底子比硬着头皮上StandardScaler稳得多。唯一要注意的是如果某个特征的IQR恰好为0比如大部分样本取值相同会出现除零问题需要先检查特征是否有足够的区分度。from sklearn.preprocessing import RobustScaler scaler RobustScaler() scaled scaler.fit_transform(data)3.4 MaxAbsScaler与稀疏数据场景还有一个使用频率稍低但非常好用的MaxAbsScalerx_scaled x / max(|x|)它的输出范围是-1到1关键特点是只做除法、不做平移。对于稀疏矩阵比如TF-IDF文本向量、One-Hot编码出来的特征如果使用StandardScaler减去均值原本大量为0的元素会全部变成非零数稀疏结构被破坏存储和计算成本直接爆炸。MaxAbsScaler不会动那些0从而保持稀疏性。选择缩放方法时我的习惯是先用训练集的分布图和统计量做个快速扫描看偏度、看异常值、看稀疏度再决定用哪个。下面这张表是我平时决策的速查场景推荐方法不考虑的原因近似正态、无明显异常值StandardScaler通用首选统计性质好范围有界、要做有界输出、无异常值MinMaxScaler对异常值过于敏感异常值多或长尾分布RobustScalerStandard/MinMax都会被带偏稀疏数据、需要保持0结构MaxAbsScaler平移类方法破坏稀疏性提示缩放器永远只用训练集去fit。测试集和线上数据只能transform绝不能参与计算均值、方差这些参数。这一点在第五部分会详细展开。4. 按算法对号入座哪些模型必须缩放哪些无所谓4.1 距离、梯度、方差三类算法的必须缩放清单特征缩放不是所有模型的标配但有一大类算法是不做不行。我习惯把需要缩放的方法分成三类。第一类是距离类。K近邻、K均值、层次聚类、基于距离的异常检测都依赖样本间的距离或相似度量纲不统一等于让大数值特征霸榜必须缩放。第二类是梯度类。线性回归、逻辑回归、支持向量机尤其是带核函数的版本、神经网络优化依赖梯度下降缩放能大幅加速收敛带正则化时还涉及惩罚公平性必须缩放。第三类是方差类。PCA、LDA这类降维方法靠方差找主方向一个量纲大的特征方差天然就大不缩放的话第一个主成分往往只是单位最大的那个特征的投影必须缩放。下面的表基本覆盖了日常工作里常见的主流模型模型是否必须缩放原因K近邻 / K均值 / 层次聚类必须距离计算受量纲支配SVM线性/核必须间隔与距离相关且依赖梯度优化线性回归/逻辑回归无正则建议不影响最终预测但系数解释困难线性模型 L1/L2正则必须惩罚项对量纲敏感神经网络/深度学习必须梯度收敛速度依赖尺度PCA/LDA降维必须方差被大数值特征主导决策树/随机森林/XGBoost/LightGBM通常不需要分裂阈值不受单调变换影响朴素贝叶斯可选高斯假设下不强制但缩放无害4.2 树模型为什么不关心缩放经常有人问我用的是随机森林或XGBoost到底要不要缩放答案是不需要你做了也不会让效果变好纯属白忙一场。原因是树模型的分裂逻辑跟距离、梯度都没有关系。决策树在一个特征上选一个阈值把样本分成左右两支比如房龄大于3年的进左子节点。如果把这个特征整体乘以10阈值也会自动相应调整最终的划分结果完全不变。任何单调变换都不会改变树的划分结构所以标准化、MinMax、Robust在随机种子一样的情况下树模型的预测结果几乎一模一样。这里要提一个例外如果同一个流程里既要跑树模型又要跑线性模型为了管线统一把缩放步骤加上去也没毛病对树模型没有伤害。但如果你问一个专门做树模型调参的老手他大概率会告诉你别费这个事。真正该花精力的是特征构造、分箱、编码这类树模型真正敏感的处理。4.3 被忽视的两个特例PCA和正则化线性模型两个特例容易被新手忽略。第一个是PCA。PCA的目标是找到方差最大的方向而方差受数值大小直接影响。一组特征里如果有一个是年收入单位是元数值上万它的方差天然比0到1的评分特征大得多PCA的第一个主成分会牢牢被年收入抓住完全不反映数据结构。标准化之后再做PCA每个特征才有平等的机会进入主成分主成分的方向才真正反映方差结构而不是单位结构。第二个是正则化线性模型。很多人觉得线性回归不需要缩放这话在无正则情况下基本成立因为梯度下降即使慢一点最终也能收敛到差不多的结果。但一旦加了L1或L2正则情况就变了。前面说过惩罚项是加在系数上的量纲不一致等于对特征施加了不均衡的惩罚可能导致模型把某些重要特征的系数压到接近0或者让不重要特征的系数保留得过多。所以只要你用了岭回归、Lasso、弹性网络缩放不是可选项而是必选项。5. 实操中绕不开的三个深坑数据泄露、异常值和目标变量5.1 先切分再缩放数据泄露是怎么悄悄发生的特征缩放实操中最大的坑不是选错方法而是顺序搞错。先看一段错误示范# 错误示范全量数据一起fit再切分 from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled_all scaler.fit_transform(X) X_train_scaled X_scaled_all[:80] X_test_scaled X_scaled_all[80:]这段代码的问题在于scaler在切分之前就看到了全部数据。测试集的均值、标准差被灌进了训练阶段的统计量里模型在训练时等于提前知道了测试集的分布信息。这不是玄学它的后果很具体交叉验证分数虚高你满心欢喜以为模型很强上线之后发现推理效果差得离谱模型在新数据上的泛化能力被高估因为新数据的分布和测试集分布往往存在差异而这个差异在预处理阶段就被抹平了。正确姿势是先用训练集fit再用同一个scaler去transform训练集和测试集from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 只transform不fit这条规则适用于所有需要从数据里学习参数的预处理步骤不只是特征缩放。缺失值填充里的均值、PCA里的主成分方向、降维里的投影矩阵通通只能从训练集学。我后来把这些逻辑固化到了Pipeline里不给自己留手动的机会from sklearn.pipeline import Pipeline from sklearn.neighbors import KNeighborsRegressor pipe Pipeline([ (scaler, StandardScaler()), (model, KNeighborsRegressor(n_neighbors5)) ]) pipe.fit(X_train, y_train) pipe.score(X_test, y_test)Pipeline的好处是fit和transform的顺序由内部组件保证测试时不会出现不小心拿全量数据重新fit这种低级错误。5.2 异常值让你选错缩放器第二个深坑是异常值。我见过不少开发者统计量都没看就直奔StandardScaler结果跑出来的特征分布依然畸形。原因在于均值和标准差都不是稳健统计量。假设收入特征大部分人月薪1万左右突然来了几个年入千万的样本均值被抬高标准差也被撑大标准化之后普通人的收入反而被压到负值区间信息的表达完全扭曲。MinMaxScaler对异常值更敏感因为min和max本身就是两个极端值只要有一个异常值整个特征的缩放区间就被它占据。我在某个图像处理Demo里遇到过这种案例数据集中有一张噪声极大的图片像素值分布极不均匀MinMax之后所有正常图片的特征都被压成一团模型直接学不到区分度。处理思路一般有三种。第一换RobustScaler用中位数和四分位距降低异常值影响。第二先对特征做异常值处理比如分位数截断把超过99分位数的值剪掉再上标准化或MinMax。第三对长尾数据先做对数变换把极端值拉回来再做标准化。我个人的习惯是先画分布图看到明显拖尾和离群点时优先考虑RobustScaler如果业务上允许再做一次分位数截断稳上加稳。5.3 目标变量要不要缩放第三个坑跟y有关。做分类任务时目标变量是离散标签缩放没有任何意义标签之间不存在距离概念你要做的是编码而不是缩放千万别顺手把标签也标准化了。但回归任务里y的处理经常被忽略。当目标值跨越几个数量级比如房价从几十万到上亿直接让模型拟合原始数值损失函数会被大目标样本主导训练不稳定。这时常见的做法是对y做对数变换或者用StandardScaler标准化。如果用scikit-learn的TransformedTargetRegressor预测值会自动反变换回原始尺度省去手动折腾的麻烦import numpy as np from sklearn.compose import TransformedTargetRegressor from sklearn.linear_model import LinearRegression reg TransformedTargetRegressor( regressorLinearRegression(), funcnp.log1p, inverse_funcnp.expm1 ) reg.fit(X_train, y_train) pred reg.predict(X_test) # 已经是原始房价尺度还有一个经验之谈如果只用均方误差或平均绝对误差评估模型标准化y不会改变最终误差指标但能让训练过程更稳定、收敛更快。真正要记住的是预测之后必须反变换回去否则后面所有业务指标都会对不上。6. 一套可以直接照抄的要不要缩放检查流程6.1 五分钟判断该不该缩放前面讲了那么多原理最后给一套可以直接套用的判断流程。拿到一个新项目按这个顺序走一遍五分钟内就能确定特征缩放策略。第一步看算法。树模型直接跳到第五步不需要缩放。距离类、梯度类、方差类模型都需要继续往下走。第二步看数据分布。画一下每个特征的直方图或箱线图重点看有没有明显的偏态和异常值。第三步选缩放器。分布近似正态、无明显异常值用StandardScaler范围有界、明确要求输出在0到1用MinMaxScaler异常值多、拖尾严重用RobustScaler数据稀疏用MaxAbsScaler。第四步决定要不要先做变换。严重偏态的特征先做对数或Box-Cox变换再缩放。第五步无论缩不缩放都必须保证先切分、后fit缩放器并把流程固化在Pipeline里。这套流程我在好几个项目里反复用过包括某跨平台用户行为分析和刚才提到的图像处理Demo基本都是走到第二步就能定下来极少进入反复试错的死循环。6.2 缩放前后如何验证是否有效最后聊聊怎么验证缩放到底有没有用。很多人缩放完直接看模型分数这没错但容易漏掉信息。我习惯做三件事。第一固定随机种子和超参数对比缩放前后的验证集指标。注意是只有缩放这一个变量的对比否则你根本不知道是缩放起了作用还是调参起了作用。第二看训练曲线。对于神经网络和线性模型画出损失随迭代次数的变化缩放之后曲线下降通常明显更快说明优化效率确实有了提升。第三看特征空间的分布。缩放后用箱线图对比各特征的数值范围理想状态下每个特征的范围应该处于同一个量级。还有一个非常有意思的反向验证如果树模型上跑了一次带缩放的流程发现指标和完全不缩放一模一样这不是流程有bug恰恰说明这个场景下缩放的确定多余。我见过有人遇到这种情况反而慌了以为哪写错了其实这是树模型的正常表现。6.3 我现在的默认配置说点个人习惯收尾。我现在做项目特征缩放默认走RobustScaler兜底除非明确看过分布图、确认数据干净异常值少才换StandardScaler每次跑完模型先看训练曲线再决定要不要调整缩放方式。特征缩放本身不会让一个模型从60分变成90分但没有它很多模型真的会在60分徘徊很久而你压根不知道瓶颈就在数据预处理这一步上。把这件事想明白之后我再看当年那次K近邻翻车反而觉得挺值——一次刻骨铭心的翻车比看十篇理论笔记都管用。
阅读完成 · 觉得有帮助?
咨询建站