☰
正则化、归一化、标准化:三者的区别与实战指南
2026/10/14 4:38:51 网站建设 项目流程

搞机器学习和深度学习的同学,应该都遇到过这样的场面:模型训练loss降不下来,或者验证集和训练集准确率像两条分开的路越走越远。群里第一反应永远是“调参”,然后马上有人提议“加个正则化试试”,也有人建议“把数据归一化一下”,还有人强调得先做标准化。问题是这三件事听起来差不多,很多人嘴上也混着说,最后调了半天也没解决实际问题。

说句实在话,正则化、归一化、标准化在训练流程里各管的完全是不同环节,硬放在一起比较,就像问“炒菜放盐和提前热锅哪个更重要”,没有可比性。但它们的名字接近、使用时机又经常叠加,所以值得掰开揉碎讲清楚。这篇文章就从一个训练项目的完整视角出发,把三大策略的定位、原理、落地方式和常见坑一次说清,也算是我自己这些年踩过不少坑之后的一份经验沉淀。开始之前先说明一件事:严格来说这三个都不是传统意义上的“调参”,但它们都需要人工决策和设置超参数,所以大家习惯性地把它归到调参范畴。下面我按实际使用顺序来拆。

1. 三大策略的定位:正则化管模型,归一化和标准化管数据

训练一个模型的链路大致是:原始数据进门,先做清洗和特征加工,再做数据缩放,然后喂给模型开始迭代,损失函数算完误差后反向更新权重。归一化和标准化发生在“喂给模型”之前,做的是数据层面的变换;正则化发生在“损失函数”环节,做的是对模型复杂度的约束。

这个定位差别是很多人从头就搞混的地方。你会听到“我对数据做了归一化,为什么还过拟合”这种话,原因很简单:归一化和标准化解决的是特征量纲和分布范围的问题,跟过拟合没有直接关系;想控制过拟合,要去正则化或者增强数据。反过来也一样,有人说“我加了L2正则,为什么梯度下降还是这么慢”,这问题也问错了,因为L2正则管的是权重幅度,不是特征尺度。先把各管什么搞清楚,后面所有操作才有得聊。

1.1 正则化管的是“模型的复杂度”

机器学习的基本矛盾是:模型能力太强会把训练集里的噪声也学进去,模型能力太弱又学不到真实规律。前者叫过拟合,表现为训练集指标漂亮、测试集一塌糊涂;后者叫欠拟合,两边都不行。正则化就是为了压制前者而生的。

正规说法里,正则化就是在原始损失函数后面加一个关于参数的惩罚项,整体变成:

L = Loss(y, ŷ) + λ·Ω(θ)

这里的Loss可以是交叉熵、均方误差或者别的;Ω(θ)就是惩罚项,最常见的两种形式是:

  • L1正则:Ω(θ) = Σ|wi|,惩罚的是权重绝对值之和
  • L2正则:Ω(θ) = Σ(wi²),惩罚的是权重平方和

这两个很像,效果差别却很大。L1会让一部分权重干脆变成0,所以它自带“特征选择”功能,适合特征很多、想筛一筛的场景;L2不会把权重变成0,而是把所有权重往小了压,让模型不要对某个特征过度敏感。λ则控制惩罚的力度:λ太小,惩罚像不存在一样,模型照样放飞自我;λ太大,所有权重都被压成接近0,模型就“呆”了,只会输出一个无趣的预测。我习惯用写作文来类比:不加正则化,学生可以写三千字花式炫技;加L2,是要求写八百字且不许跑题;加L1,是要求删掉所有废话只留核心观点。这个类比能帮你快速记住两者的差别。

1.2 归一化和标准化管的是“喂给模型的数据”

归一化通常指MinMax缩放,把特征从原始范围映射到[0,1]或者[-1,1]:

x' = (x - min) / (max - min)

标准化通常指Z-score,把特征变成均值0、方差1的分布:

x' = (x - μ) / σ

以房价预测为例,面积可能从几十到几千平方米,房龄从1年到50年,成交价从一百万到一千万。如果直接把这些原始数值丢进模型,面积这个特征因为数值大,会在距离计算和梯度更新里占据绝对主导地位,房龄再重要也很难起作用。归一化或标准化之后,每个特征都处在同一个对比尺度上,模型才有机会公平地学习每个维度。

两者最关键的区别在于对数据分布形态的影响。MinMax归一化是把数据硬拉到一个固定区间,会改变原来的分布形状;Z-score标准化则是先在横轴上平移,再整体缩放,近似保留了数据原有的相对分布形态,只是把轴的单位统一了。分布大体对称、没有太多离群点的时候,标准化通常更稳;数据有明确上下界、比如图像像素固定0到255,归一化更自然。这个选择并不复杂,后面第3节我会给具体的选型清单。

1.3 三者为什么总被放在一起说

因为它们在一个完整训练流程里几乎同时出现。数据处理阶段,你得先决定用归一化还是标准化;模型训练阶段,你得考虑该不该加L1或L2正则;到深度学习里,网络内部还有BatchNorm这类“隐藏的归一化层”。名字接近,出现在同一个项目里,又都需要人工设置,自然会被混为一谈。

我的建议是遇到问题时先做个定位:当前现象是数据进入模型之前的分布有问题,还是模型本身迭代出的权重失控了?前者去找归一化和标准化,后者去找正则化。定位准确了,再去选具体工具就简单很多。我一再强调这个区别,是因为见过太多人在这上面绕圈子。

2. 原理层面:为什么这些策略确实有效

这一节不写复杂推导,只把关键直觉讲透。理解了原理,将来换场景时你才知道怎么迁移,而不是死记硬背公式。

2.1 L1正则为什么能让一部分权重归零,L2却不能

很多人看文档只知道“L1产生稀疏解、L2不产生”,但不知道原因。用一个二维权重的例子就能说明白。假设模型只有两个权重w1和w2,损失函数在权重空间里的等高线是椭圆,不加正则时,最优解落在椭圆中心附近某个位置,权重往往都不是0。

加上L1正则后,约束项在二维空间里是一个菱形。优化问题变成了“在菱形内部找损失最低的点”。菱形的尖角正好在坐标轴上,而且尖角位置离椭圆的中心往往更近,所以最优解很容易落在w1=0或者w2=0的坐标轴上,产生稀疏解。L2正则对应的是圆形约束,圆的边界是平滑的,最优解落在坐标轴上的概率很低,所以权重只会被成比例缩小,不会变成0。

工程上的意义很直接:面对几百个特征时,L1等于帮我们自动挑了一批有效特征,剩下的权重直接被设成0,模型推理时更轻、解释性也更好;L2更像一个“均衡器”,让权重不会集中到某几个特征上,适合特征之间本身有关联、信息重叠很多的情况。

2.2 特征缩放为什么能让梯度下降更快

想象损失函数只依赖于两个特征x1和x2,其中x1的数值范围是0到1000,x2是0到10。这样形成的损失等高线是一个很扁的椭圆。梯度下降沿着椭圆长轴和短轴方向上,更新步长差异很大,结果就是优化路径走成一条歪歪扭扭的“之”字形,明明离最优解不远,却要花很多轮迭代才能蹭过去。

把特征做标准化之后,两个方向上的数值尺度接近,等高线会接近正圆,梯度下降走的基本是直线,收敛速度肉眼可见地变快。这还不只是训练快慢的问题,在有些任务里,不缩放直接跑SVM或者神经网络,甚至会出现loss卡住不动的情况,因为梯度的方向被大数值特征带歪了。

凡是基于距离度量的算法,比如K近邻、SVM、K均值聚类、主成分分析,特征缩放都属于必做项,否则量纲大的特征会完全主导距离计算。反之,树模型这类基于分裂阈值、不依赖特征绝对尺度的算法,缩放与否影响很小,做了反而可能增加理解成本。这也是经常被忽略的细节。

2.3 深度学习里的“归一化”可不只在输入层

在深度学习里,归一化思想被进一步推广到了网络内部,典型代表就是BatchNorm(批归一化)。它的做法是:在每一层激活之后,不看整份数据,而是看当前batch在这一个维度上的均值和方差,先把这个batch的激活值拉回均值0、方差1,再做一次缩放和平移。

为什么要这么做?因为网络前面的层一旦参数更新,后面层拿到的输入分布就会跟着变,训练越深变化越剧烈。BatchNorm相当于把中间层的输入分布重新拉回来,让每一层都面对一个相对稳定的输入,梯度传播也会更顺畅。实操中你会发现,加了BatchNorm的模型对学习率不那么敏感,收敛也更快。

BatchNorm训练时用的统计量是当前batch的,但推理时要换成训练阶段累计的滑动平均值,这一点新手经常忽略。除了BatchNorm,还有针对序列数据的LayerNorm,按样本维度做归一化;针对图像风格迁移的InstanceNorm等。它们解决的问题各有侧重,但内核都是“把分布拉到合理范围”,跟输入层的标准化思想一脉相承,只是作用的层次不同。

3. 实操落地:给训练流程配上三大策略

讲完原理就该动手了。这一节按一个典型训练项目的顺序走一遍,包含数据预处理、缩放选型、正则化系数调节和完整训练配置。

3.1 数据预处理的纪律:训练集fit、测试集transform

这是数据缩放时最不该犯的错误:有些人图省事,直接拿全量数据fit一个标准化器,再切训练集和测试集。这会造成数据泄漏,因为测试集的统计量已经通过scaler传给了训练过程。离线测试分数会虚高,等模型上线面对真实新数据,统计量对不上,效果立刻现原形。

正确的做法是:先用训练集调用scaler.fit()算出参数,再用同一个scaler分别transform训练集和测试集,测试集的变换参数完全来自训练集,不掺入任何测试数据的信息。实际项目里我强烈建议用Pipeline把这些步骤串起来,省得切分、缩放、训练的顺序出错。一个标准的scikit-learn流程长这样:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Ridge from sklearn.model_selection import train_test_split, GridSearchCV X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) pipe = Pipeline([ ('scaler', StandardScaler()), ('estimator', Ridge()) ]) params = { 'estimator__alpha': [0.01, 0.1, 1.0, 10.0] } grid = GridSearchCV(pipe, params, cv=5, scoring='neg_mean_squared_error') grid.fit(X_train, y_train) y_pred = grid.predict(X_test)

这里Pipeline会在交叉验证的每个fold里单独做fit,也就是说scaler只接触当前fold的训练部分,不会偷看验证部分,非常稳。如果你已经吃过“离线分数很好、线上效果稀烂”的亏,九成是这一步出了问题。

3.2 特征缩放选型:StandardScaler、MinMaxScaler还是RobustScaler

很多人问是不是默认用StandardScaler就行,答案是大部分情况下可以,但有例外。选型主要看数据和模型:

  • 特征接近正态分布,没有特别夸张的离群点:优先StandardScaler,这是最通用的选择。
  • 数据有明确上下界,或者算法严格要求输入在固定区间里,比如图像像素值:优先MinMaxScaler,或者直接除以255。
  • 数据里离群点很多,StandardScaler会被μ和σ拉偏:优先RobustScaler,它用中位数和四分位距做缩放,对离群点更稳。
  • 数据本身是稀疏的(大量0),比如文本TF-IDF特征:尽量不要用StandardScaler,做完后稀疏结构会被破坏,考虑保留原始表示或做专门的缩放手动处理。

为了好记,我列了一个简单对照:

场景推荐方案原因
通用数值特征、大致正态分布StandardScaler保留分布形态,通用性强
像素、百分比、有明确上下边界MinMaxScaler强制落在固定区间
存在大量离群点的数值特征RobustScaler中位数和四分位距抗离群
稀疏特征矩阵不缩放或专门处理避免破坏稀疏结构

选型之后还有一个细节:如果在分布式或在线学习场景里做推理,scaler参数要跟着模型一起保存和部署,否则上线时没有统计量可用。这一步属于工程化问题,但直接影响模型效果,务必纳入上线清单。

3.3 正则化系数怎么调:从λ=0出发做交叉验证

L1或L2的系数λ没有万能取值,但调节路径是有章可循的。我常用的套路分三段:

第一步,先把λ设成接近0,跑一版模型。这时模型基本等于没加正则化,主要观察它是不是过拟合:训练集分数很高、验证集分数明显低,就说明模型有容量余量需要约束。 第二步,把λ调大到一个明显让模型变呆的程度,比如训练损失都降不下去,说明正则化太狠了。 第三步,在0和这个“太狠”的值之间,按对数网格搜索。例如Ridge的alpha可以试0.0001、0.001、0.01、0.1、1、10,用交叉验证选验证集分数最高的那个。

代码用GridSearchCV已经非常方便,关键就是网格范围别拍脑袋。每次调参前先跑一版λ=0,明确过拟合边界,再跑一版很大λ,明确欠拟合边界,中间才是该搜索的区域。很多人一上来就设λ=0.1,大部分时候方向都不对。还有一条铁律:验证集和交叉验证用来选λ,测试集只能最后评估一次,拿测试集反复调参会把测试集污染成另一个验证集。

3.4 一个可复现的基准配置:特征缩放加L2正则的完整训练代码

下面给一个PyTorch的小例子,展示数据缩放加L2正则怎么协同工作。假设任务是回归预测房价,特征已经处理好,存在X中。完整逻辑分四块:缩放数据、定义模型、配置优化器、训练循环。

import torch import torch.nn as nn from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_std = scaler.fit_transform(X_train) X_test_std = scaler.transform(X_test) X_train_t = torch.tensor(X_train_std, dtype=torch.float32) y_train_t = torch.tensor(y_train.values, dtype=torch.float32).view(-1, 1) X_test_t = torch.tensor(X_test_std, dtype=torch.float32) class MLP(nn.Module): def __init__(self, in_dim): super().__init__() self.net = nn.Sequential( nn.Linear(in_dim, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): return self.net(x) model = MLP(in_dim=X_train_t.shape[1]) criterion = nn.MSELoss() optimizer = torch.optim.AdamW( model.parameters(), lr=1e-3, weight_decay=1e-4 ) for epoch in range(100): model.train() optimizer.zero_grad() loss = criterion(model(X_train_t), y_train_t) loss.backward() optimizer.step() if epoch % 20 == 0: print(f"epoch {epoch}, loss={loss.item():.4f}") model.eval() with torch.no_grad(): y_pred = model(X_test_t).numpy()

这段代码里weight_decay=1e-4就是L2正则,它会直接穿透到优化器内部,等价于在损失函数上加了权重平方惩罚。用AdamW而不是Adam加L2,是因为AdamW把权重衰减和Adam的梯度更新解耦了,实践中更稳定。常用的weight_decay范围一般在1e-4到1e-2之间,跟模型大小、batch size、学习率都有关系,没有绝对标准。训练结束别忘了把scaler的参数和模型一起保存,推理时先对输入做同样的标准化。

4. 常见坑位与排查心得

这一节整理几个最常见的问题,都是我实际带项目时反复遇见的,每个都配了排查思路。

4.1 MinMax归一化后模型反而变差,问题出在哪

有次某个数据里有一个极端离群特征,正常值在0到100之间,离群点跑到50000。直接做MinMax归一化,最后所有正常样本都被压到0.002这个狭窄区间里,模型等于看不到特征细节了,效果不差才怪。改成StandardScaler或者RobustScaler之后,收敛速度和最终精度都正常了。

排查这个问题的标准动作是三步:先画缩放前后特征分布直方图,看有没有被压扁;再对比训练集和验证集的loss曲线,判断是欠拟合还是优化出了问题;最后确认是不是把目标变量也缩放了,如果缩放了目标,预测之后记得还原到原始量纲再算RMSE。回归任务里,很多人把y也MinMax归一化,训练是方便了,评估时却忘了反变换,得到的结果看起来完全不合理,这类乌龙我见过不止一次。

4.2 同时调正则化和归一化时,顺序影响大吗

严格说不存在绝对的先后顺序,因为几何上它们作用在不同环节。但实际操作时有固定的推荐顺序:先固定数据缩放方案,再调正则化强度。原因是缩放会直接改变权重的绝对值大小,而L2正则惩罚的就是权重平方和,数据缩放一变,同一套λ对应的惩罚力度就完全变了。你如果先在一个没缩放的模型上调好了λ,后面补了标准化,之前的λ基本要重新调。

深度学习中还有一个与此相关的经验:BatchNorm和Dropout同时用时要格外小心。Dropout随机屏蔽神经元会让训练和推理行为不一致,而BatchNorm又依赖batch统计量,两种随机性叠在一起,有时反而会拖慢收敛。现在的实践更倾向于用weight_decay加EarlyStopping来控制过拟合,Dropout留到确实需要的时候再用。

4.3 给深度学习加正则化:weight_decay和AdamW的关系

深度学习社区说的“L2正则”跟传统线性模型里的L2不完全一样,实操中一般是通过优化器的weight_decay参数实现。传统实现是往loss里加权重平方项,Adam把梯度的一阶矩和二阶矩做了归一化,L2梯度也被缩放,效果会打折扣。AdamW把权重衰减这一步单独拿出来,在Adam更新之后直接做一次衰减,相当于恢复了解耦的权重衰减,这也是它现在成为默认优化器的原因。

入门的建议是:优先用AdamW,weight_decay先设1e-4,观察训练loss和验证loss的差距。如果验证loss在后期明显反弹,就把weight_decay调大到5e-4或者1e-3;如果训练loss都掉不下去,就先调小到1e-5。另外early stopping也是非常好用的“正则化”手段,它通过限制训练轮数防止模型过度学习训练集,虽然没有显式惩罚项,实际效果甚至经常比硬调正则系数更直接。

4.4 三个我亲手踩过的坑,值得你提前避开

第一个坑是切分顺序的错误。早期做某跨平台系统时,我把整个数据集合在一起fit了StandardScaler,再切验证集,离线AUC漂亮得不像话。等试运行的时候,输入数据分布稍有变化,所有统计量全不对劲,线上效果立刻崩。从那以后所有缩放都放进Pipeline,配合交叉验证,再也没出过同类问题。

第二个坑是L2系数设置太大。我有一版模型用weight_decay=0.1,训了几十轮loss还是降不下去,一度以为网络结构和数据有问题。后来把权重打印出来才发现全部趋近于0,模型输出对所有样本都差不多,明显是正则化直接把模型压死了。快速验证方法很简单:把weight_decay设成0,如果loss马上能降,那大概率是正则项太强而不是网络结构的问题。

第三个坑是图像任务里的归一化统计量用错。做某图像处理Demo时,我图省事直接拿全数据集的像素均值方差做归一化,而且没按通道分开算。结果训练时梯度反复震荡,几轮之后干脆梯度爆炸。排查半天才意识到,像素统计量要按通道严格从训练集的部分计算,并且加载新数据时要重复用完全相同的数值,差一点分布就对不齐。这类错误在视觉任务里特别容易发生,因为图像预处理看起来太简单,大家都不会想到问题出在均值方差上。

这几个坑的共同教训是:三大策略看起来都是预处理或约束的小环节,但每一步的统计量来源、参数配置和代码顺序都会影响最终模型。尤其是“谁计算统计量”和“谁参与计算统计量”这两个问题,几乎贯穿了所有数据相关项目的排查过程。我在实际项目中养成的习惯是,每加一个预处理步骤,就顺手记录它的统计量来源、参数和模型版本,时间一长才知道模型效果变化到底是谁引起的。这个习惯,比任何一套固定参数都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询