别再混淆了!一文搞懂机器学习中的Standardization和Normalization(附sklearn代码示例)
别再混淆了!一文搞懂机器学习中的Standardization和Normalization(附sklearn代码示例)
刚入门机器学习那会儿,我总在数据预处理这一步卡壳。面对一堆数值范围天差地别的特征,是该用 StandardScaler 还是 MinMaxScaler?网上教程说法不一,有的说标准化好,有的说归一化更优,看得人一头雾水。直到在几个真实项目里栽了跟头,用错了方法导致模型效果惨不忍睹,我才痛定思痛,决心把这事儿彻底掰扯清楚。今天,我们就抛开那些模棱两可的定义,直接从机器学习的实战视角出发,结合 sklearn 的代码,把 Standardization(标准化)和 Normalization(归一化)的本质、区别和应用场景讲透。无论你是正在啃《西瓜书》的初学者,还是工作中需要快速调优模型的从业者,这篇文章都能帮你建立起清晰、可操作的认知。
1. 核心概念:从统计本源到机器学习实践
很多人混淆这两个概念,根源在于它们在不同学科语境下的定义确实有重叠。我们先跳出代码,看看它们最根本的数学意图是什么。
标准化 (Standardization) 的核心思想是“中心化”和“缩放”。它假设你的数据(大致)服从正态分布,或者你希望它经过变换后能服从标准正态分布。其操作是将每个特征的值减去该特征的均值(中心化),再除以标准差(缩放)。公式非常简单:
z = (x - μ) / σ
其中,μ 是特征列的均值,σ 是标准差。经过这个变换,数据的新分布会具有均值为0,标准差为1的特性。注意,它并不改变数据的原始分布形状(如果原本是偏态的,变换后依然是偏态的),只是做了平移和缩放。
归一化 (Normalization),在机器学习最常用的语境下,特指 Min-Max 缩放。它的目标是将所有数据线性地映射到一个固定的区间,通常是 [0, 1]。公式如下:
x_scaled = (x - x_min) / (x_max - x_min)
这里,x_min 和 x_max 分别是该特征列的最小值和最大值。这个操作会改变数据的分布,将其强行压缩到0到1的范围内。
那么,为什么 sklearn 的命名会让人困惑呢?因为在 sklearn.preprocessing 模块里:
StandardScaler对应我们刚说的标准化(减均值除标准差)。MinMaxScaler对应我们刚说的归一化(缩放到[0,1])。- 而
sklearn里的Normalizer类,做的事情完全不同!它是对每个样本(行)进行“正则化”,使其范数(模长)等于1,常用于文本分类或聚类中。这和我们讨论的、针对每个特征(列)的缩放是完全两码事。
注意:在阅读其他资料时,务必确认其语境。在本文及后续的
sklearn实践中,我们统一口径:标准化=StandardScaler,归一化=MinMaxScaler。
为了更直观地对比,我们看下面这个表格:
| 特性 | 标准化 (StandardScaler) | 归一化 (MinMaxScaler) |
|---|---|---|
| 核心公式 | z = (x - μ) / σ | x' = (x - min)/(max - min) |
| 输出范围 | 理论上无界,大致在 [-3, 3] | 固定区间,如 [0, 1] |
| 对异常值 | 非常敏感。均值和标准差易受极端值影响。 | 极其敏感。一个极大/极小值会压缩其他数据的尺度。 |
| 数据分布 | 不改变原始分布形状。 | 改变原始分布形状,强制线性映射。 |
| 适用算法 | 假设数据服从正态分布或使用距离度量的算法(如SVM、线性回归、逻辑回归、KNN、K-Means、PCA等)。 | 不假设数据分布,且需要固定输入范围的算法(如神经网络、图像处理像素值)。 |
sklearn 类 | preprocessing.StandardScaler | preprocessing.MinMaxScaler |
理解了这个表格,你就掌握了区分的钥匙。接下来,我们深入到具体场景中看看如何选择。
2. 实战场景:如何根据数据和模型做选择?
理论懂了,但一到真实数据集面前可能又懵了。别急,我们可以通过一个决策流程来辅助判断。选择标准化还是归一化,主要取决于你的数据特性和你将要使用的机器学习算法。
2.1 审视你的数据:异常值与边界
首先,拿出你的数据,做描述性统计和可视化(用 pandas 的 .describe() 和 seaborn 的 boxplot 或 histplot 非常方便)。关键看两点:
-
是否存在显著异常值?
- 如果存在,慎用归一化。因为一个极端最大值会把所有其他正常值都“挤”到接近0的区域,导致有效信息丢失。标准化虽然也受影响,但程度相对较轻。
- 解决方案:可以考虑使用对异常值鲁棒的缩放器,如
RobustScaler(使用中位数和四分位数间距),或者在缩放前处理(剔除、缩尾)异常值。
-
数据是否有天然边界?
- 例如,图像像素值(0-255)、百分比(0-100%)、评分(1-5星)。这类数据有明确的上下限,使用归一化非常直观,可以直接映射到
[0,1]。 - 像“收入”、“城市人口”这类没有理论上限的数据,更适合标准化。
- 例如,图像像素值(0-255)、百分比(0-100%)、评分(1-5星)。这类数据有明确的上下限,使用归一化非常直观,可以直接映射到
2.2 匹配你的算法:距离、梯度与分布假设
不同的算法对数据尺度有着不同的“偏好”和内在要求。
-
基于距离/相似度的算法:如 K-最近邻 (KNN)、支持向量机 (SVM)、K-均值聚类 (K-Means)。这些算法计算样本点之间的距离(如欧氏距离)。如果一个特征的量纲是万元,另一个是百分比,那么万元特征将完全主导距离计算,导致模型失效。必须进行缩放! 通常,标准化是更通用的选择,因为它不会因为某个特征的最大值特别大而过度压缩其他特征。
-
基于梯度下降的算法:如 线性回归、逻辑回归、神经网络。虽然很多实现内部有优化,但进行缩放能显著加快梯度下降的收敛速度,使损失函数曲面更“圆”,更容易找到最优解。对于神经网络,输入层进行归一化到
[0,1]或[-1,1]是常见做法,尤其是使用Sigmoid、Tanh等激活函数时。 -
基于方差分析的算法:如 主成分分析 (PCA)、线性判别分析 (LDA)。PCA的目的是找到方差最大的投影方向。如果某个特征方差巨大(量纲大),它就会主导主成分的方向。必须先进行标准化,使所有特征具有可比性。
-
树模型:如 决策树、随机森林、XGBoost、LightGBM。这类模型基于特征阈值进行分裂,对数据尺度不敏感,通常不需要进行标准化或归一化。但有时为了加速训练(尤其是XGBoost的近似分桶算法)或与其他需要缩放的特征一起输入,也会进行缩放。
为了帮你快速决策,这里有一个简单的自查清单:
- 步骤一:数据有异常值吗?有 -> 考虑
RobustScaler或先处理异常值。 - 步骤二:数据有明确边界吗?有 ->
MinMaxScaler是候选。 - 步骤三:要用KNN、SVM、PCA、线性模型吗?用 -> 优先选择
StandardScaler。 - 步骤四:要训练神经网络吗?是 ->
MinMaxScaler或StandardScaler均可,常选MinMaxScaler。 - 步骤五:只用树模型吗?是 -> 可以跳过缩放,或尝试缩放看效果。
3. 手把手代码:sklearn预处理全流程详解
现在我们用 sklearn 和一个人造数据集来演示完整的流程,包括如何正确地在训练集和测试集上应用这些变换——这是实践中极易出错的一步。
首先,我们创建一些模拟数据:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 创建模拟数据:特征1(收入,单位:万,范围10-100),特征2(年龄,范围20-60),外加一个异常值
np.random.seed(42)
income = np.random.randint(10, 100, 100) # 100个样本
age = np.random.randint(20, 60, 100)
# 故意加入一个异常高收入
income[0] = 500
X = pd.DataFrame({'income': income, 'age': age})
y = np.random.randn(100) > 0 # 随机生成二分类标签
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print("训练集原始数据描述:")
print(X_train.describe())
print("\n注意income的max值(异常值)对统计量的影响。")
3.1 标准化 (StandardScaler) 的正确用法
关键原则:必须用训练集拟合出的参数(均值、标准差)去变换训练集和测试集,绝对不能用测试集重新拟合!
# 初始化标准化器
scaler_standard = StandardScaler()
# 只在训练集上拟合(计算均值和标准差)
scaler_standard.fit(X_train)
# 用训练集拟合出的参数,同时变换训练集和测试集
X_train_scaled_standard = scaler_standard.transform(X_train)
X_test_scaled_standard = scaler_standard.transform(X_test)
# 通常转换回DataFrame以便查看
X_train_standard_df = pd.DataFrame(X_train_scaled_standard, columns=X_train.columns, index=X_train.index)
X_test_standard_df = pd.DataFrame(X_test_scaled_standard, columns=X_test.columns, index=X_test.index)
print("\n标准化后的训练集统计(均~0, 标~1):")
print(X_train_standard_df.describe().round(2))
3.2 归一化 (MinMaxScaler) 的正确用法
流程完全一样,只是换了个转换器。注意观察异常值带来的影响。
# 初始化归一化器,默认范围[0,1]
scaler_minmax = MinMaxScaler()
# 在训练集上拟合(计算min和max)
scaler_minmax.fit(X_train)
# 变换数据集
X_train_scaled_minmax = scaler_minmax.transform(X_train)
X_test_scaled_minmax = scaler_minmax.transform(X_test)
X_train_minmax_df = pd.DataFrame(X_train_scaled_minmax, columns=X_train.columns, index=X_train.index)
print("\n归一化后的训练集统计(范围[0,1]):")
print(X_train_minmax_df.describe().round(2))
print("\n由于收入异常值500的存在,其他正常收入值都被压缩到了0.02以下,年龄特征则正常分布在0-1间。这展示了归一化对异常值的脆弱性。")
3.3 构建可复用的预处理管道
在实际项目中,将预处理步骤和模型训练封装成管道 (Pipeline) 是最佳实践,能避免数据泄露,使代码更简洁。
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 创建一个包含标准化和逻辑回归的管道
pipeline_std = Pipeline([
('scaler', StandardScaler()), # 步骤1:标准化
('classifier', LogisticRegression(random_state=42)) # 步骤2:分类器
])
# 训练和预测
pipeline_std.fit(X_train, y_train)
y_pred_std = pipeline_std.predict(X_test)
print(f"\n使用标准化管道的测试集准确率:{accuracy_score(y_test, y_pred_std):.3f}")
# 创建一个包含归一化和逻辑回归的管道
pipeline_mm = Pipeline([
('scaler', MinMaxScaler()),
('classifier', LogisticRegression(random_state=42))
])
pipeline_mm.fit(X_train, y_train)
y_pred_mm = pipeline_mm.predict(X_test)
print(f"使用归一化管道的测试集准确率:{accuracy_score(y_test, y_pred_mm):.3f}")
# 在这个有异常值的例子中,标准化通常表现更好
4. 进阶话题与常见陷阱
掌握了基础用法后,我们来看看一些更深入的问题和容易踩的坑。
4.1 其他缩放器:RobustScaler与MaxAbsScaler
sklearn 的武器库里不止这两样。当你的数据不“干净”时,它们能派上大用场。
-
RobustScaler:前面提到过,它使用中位数和四分位数间距(IQR)进行缩放。因为中位数和IQR对异常值不敏感,所以它非常适合处理包含异常值的数据。其变换公式为:x_scaled = (x - median) / IQR。 -
MaxAbsScaler:将每个特征除以该特征绝对值的最大值,从而将数据缩放到[-1, 1]的范围内。它不会移动/中心化数据,因此不会破坏数据的稀疏性(保持0值仍为0),适用于稀疏数据。
from sklearn.preprocessing import RobustScaler, MaxAbsScaler
import scipy.sparse as sp
# 使用RobustScaler处理我们的异常数据
robust_scaler = RobustScaler()
X_train_robust = robust_scaler.fit_transform(X_train)
print("RobustScaler处理后收入的缩放范围,受异常值影响较小。")
# 模拟稀疏数据
X_sparse = sp.random(100, 10, density=0.1, format='csr')
maxabs_scaler = MaxAbsScaler()
X_sparse_scaled = maxabs_scaler.fit_transform(X_sparse)
# 检查稀疏性和0值是否得以保持
4.2 分类/顺序特征与数值特征混合时怎么办?
这是一个非常实际的场景。例如,你的数据里有“年龄”(数值)、“城市”(分类)、“评级”(优、良、中,顺序)。你不能直接对整个数据集应用 StandardScaler。
标准做法是使用 ColumnTransformer:
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
# 假设我们有一个混合DataFrame
# df = pd.DataFrame({'age': [25,30,35], 'city': ['北京','上海','北京'], 'income': [50, 80, 120]})
# 定义不同的转换器给不同的列
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), ['age', 'income']), # 对数值列标准化
('cat', OneHotEncoder(), ['city']) # 对分类列独热编码
])
# 然后可以将这个preprocessor放入Pipeline中
# pipeline = Pipeline(steps=[('preprocessor', preprocessor), ('model', LogisticRegression())])
4.3 需要反复强调的陷阱
- 数据泄露:这是最大的坑。永远记住
fit方法只能在训练集上调用。无论是计算均值、标准差,还是最小值、最大值,都只能来源于训练集。然后用这些参数去transform训练集和测试集。 - 盲目默认选择:不要无脑选
StandardScaler。根据第二部分的分析,先看数据,再看模型。 - 忽略分布检查:如果你为基于距离的算法做标准化,最好检查一下特征变换后是否大致对称。如果某个特征严重偏斜(如长尾分布),标准化后可能效果仍不佳,此时可能需要先进行对数变换 (
np.log1p) 等处理,使其更接近正态分布,再进行标准化。 - 在交叉验证中错误使用:当使用
GridSearchCV或cross_val_score时,预处理步骤必须放在Pipeline内。否则,每一折交叉验证都会在包含验证集信息的“训练集”上重新拟合缩放器,导致轻微的数据泄露和过于乐观的验证分数。
我在处理一个金融风控项目时,就曾因为忽略了异常值,直接对包含极端交易额的数据使用 MinMaxScaler,导致模型完全忽略了金额这个重要特征。后来改用 RobustScaler 并结合业务逻辑进行截断处理,模型效果才大幅提升。另一个经验是,对于神经网络,尤其是CNN处理图像,MinMaxScaler 把像素值缩放到 [0,1] 或 [-1,1] 几乎是指定动作,配合批归一化 (BatchNorm) 层使用,效果非常稳定。多试错,多观察数据变换前后的分布,你的“手感”会越来越准。
更多推荐
所有评论(0)