数据归一化是一类将特征映射到统一尺度的关键预处理方法。它在几乎所有机器学习模型和训练流程中都扮演着核心角色:有助于优化器快速收敛、避免数值不稳定、使距离度量更为合理、确保正则化的公平性。对某些算法而言,缺乏归一化步骤甚至无法有效工作。
尽管“归一化”一词在中文语境中有时泛指一切“缩放到统一尺度”的操作,但其内部包含了标准化、缩放、稳健缩放、分布变换、白化等多个具体且重要的技术类型。
统一仿射变换框架
设原始特征向量为 ( \mathbf{x} \in \mathbb{R}^d ),我们定义一个通用的仿射缩放变换:
[
\mathbf{z} = \mathbf{S}^{-1}(\mathbf{x} - \mathbf{t})
]
其中 ( \mathbf{t} ) 为平移参数(例如均值或中位数),( \mathbf{S} ) 为对角缩放矩阵(例如标准差、IQR、范围)。在第 ( j ) 维上:
[
z_j = \frac{x_j - t_j}{s_j}
]
- 当 ( t_j = \mu_j )(均值),( s_j = \sigma_j )(标准差),则是Z-score标准化;
- 当 ( t_j = \min_j ),( s_j = \max_j - \min_j ),则是Min-Max缩放到[0,1];
- 当 ( t_j = \text{median}_j ),( s_j = \text{IQR}_j ),则是稳健缩放;
- 当 ( t_j = 0 ),( s_j = |\mathbf{x}|_p )(按样本的p范数),则是向量归一化(注意此时 ( s_j ) 依赖样本而非特征)。
逆变换为:
[
\mathbf{x} = \mathbf{S}\mathbf{z} + \mathbf{t}
]
对于概率密度,若 ( \mathbf{x} ) 有密度 ( p{\mathbf{x}}(\mathbf{x}) ),则 ( \mathbf{z} ) 的密度是:
[
p{\mathbf{z}}(\mathbf{z}) = |\det \mathbf{S}| \, p{\mathbf{x}}(\mathbf{S}\mathbf{z} + \mathbf{t})
]
这里 ( |\det \mathbf{S}| = \prod{j=1}^d s_j )。这个公式用于理解变换对概率密度的影响。
常见归一化方法详解
Min-Max缩放
线性缩放到[0,1]或[a,b]。对每个特征 ( j ),设 ( m_j, M_j ) 分别为训练集最小/最大值,则
[
z_j = \frac{x_j - m_j}{M_j - m_j}
]
更一般地映射到 ( [a, b] ):
[
z_j = a + \frac{(x_j - m_j)(b - a)}{M_j - m_j}
]
性质:
- 单调、保持排序;
- 对极值敏感,离群点会挤压主数据密度;
- 用于输入层(尤其深度网络)常见,便于将激活范围控制在标准域。
逆变换:
[
x_j = m_j + (M_j - m_j) z_j
]
要注意,测试时必须使用训练集拟合得到的参数 ( m_j, M_j ),严禁使用测试集自身的极值,以避免数据泄漏。
标准化(Z-score)
对每个特征 ( j ),设训练集均值 ( \mu_j )、标准差 ( \sigma_j ):
[
z_j = \frac{x_j - \mu_j}{\sigma_j}
]
性质:
- 标准化后,训练集的均值为0、方差为1;
- 对梯度下降、线性模型系数解释、正则化公平性极为重要;
- 对极端离群值不稳健(受其影响较大)。
估计细节:
- 样本方差估计可采用有偏 ( (n) ) 或无偏 ( (n-1) )(
scikit-learn 中 StandardScaler 使用无偏估计);
- ( \sigmaj = \sqrt{\frac{1}{n-1} \sum{i=1}^n (x_{ij} - \mu_j)^2} )。
Max-Abs缩放
用于稀疏特征或中心化成本高的场景:
[
z_j = \frac{x_j}{\max(|x_j|)}
]
性质:不改变稀疏性,对0中心化不敏感,但对离群点仍敏感。
向量归一化(L1/L2范数归一化)
把每个样本当作一个向量进行归一化,例如L2归一化:
[
\mathbf{z} = \frac{\mathbf{x}}{|\mathbf{x}|_2}
]
常用于文本TF-IDF特征,使样本向量长度统一,关注方向;不改变特征间相对比例,但可能削弱强维度的绝对量级信息。
稳健缩放
对每个特征 ( j ),用中位数和平稳尺度(如四分位距IQR):
[
z_j = \frac{x_j - \text{median}_j}{\text{IQR}_j}
]
对离群点鲁棒;分布如果严重偏态可能仍需结合幂变换或分位数变换。
分位数变换
将数据的经验分布映射到指定目标分布(如均匀或高斯):
- 令 ( F_j ) 为第 ( j ) 维的经验CDF,则
[
u_j = F_j(x_j)
]
- 若目标为标准正态,则
[
z_j = \Phi^{-1}(u_j)
]
其中 ( \Phi^{-1} ) 是标准正态分布的分位点函数。
性质:非线性、可矫正偏态和重尾;会改变样本间的相对距离(相对尺度与排序保留但间距改变)。
幂变换
Box-Cox(要求数据为正):
[
z_j = \begin{cases}
\frac{x_j^\lambda - 1}{\lambda}, & \lambda \neq 0 \
\log(x_j), & \lambda = 0
\end{cases}
]
Yeo-Johnson(可处理任意实数):
[
z_j = \begin{cases}
\frac{(x_j+1)^\lambda -1}{\lambda}, & x_j \geq 0, \lambda \neq 0 \
\log(x_j+1), & x_j \geq 0, \lambda = 0 \
-\frac{(-x_j+1)^{2-\lambda}-1}{2-\lambda}, & x_j < 0, \lambda \neq 2 \
-\log(-x_j+1), & x_j < 0, \lambda = 2
\end{cases}
]
性质:通过选择 ( \lambda ) 使数据更接近正态、方差更稳定;多用于特征工程中处理偏态、提升线性模型适配性。
白化
使变换后特征协方差为单位阵,消除相关性并统一尺度。令 ( \mathbf{x} ) 的协方差矩阵为 ( \mathbf{\Sigma} ),则白化变换可写为:
[
\mathbf{z} = \mathbf{\Sigma}^{-\frac{1}{2}} (\mathbf{x} - \boldsymbol{\mu})
]
可通过特征分解或SVD实现:若 ( \mathbf{\Sigma} = \mathbf{U} \mathbf{\Lambda} \mathbf{U}^\top ),则
[
\mathbf{\Sigma}^{-\frac{1}{2}} = \mathbf{U} \mathbf{\Lambda}^{-\frac{1}{2}} \mathbf{U}^\top
]
性质:在PCA中常将数据标准化后再做白化;对某些算法(ICA等)尤为关键。
归一化对模型的核心影响:优化、距离、正则化与核函数
对梯度下降和条件数的影响:
以线性回归(平方损失)为例,损失函数:
[
L(\mathbf{w}) = \frac{1}{2n} |\mathbf{X}\mathbf{w} - \mathbf{y}|^2
]
梯度为:
[
\nabla L(\mathbf{w}) = \frac{1}{n} \mathbf{X}^\top (\mathbf{X}\mathbf{w} - \mathbf{y})
]
对梯度下降的收敛速度,上界由Lipschitz常数控制,收敛率与Hessian矩阵 ( \mathbf{H} = \frac{1}{n}\mathbf{X}^\top\mathbf{X} ) 的条件数 ( \kappa = \frac{\lambda{\max}}{\lambda{\min}} ) 相关。
当各特征尺度差异很大时,( \mathbf{H} ) 的特征值分布拉大,条件数恶化,梯度下降在狭长的碗形上振荡慢收敛。通过对每列特征进行标准化(或更强的白化)可以显著改善,使等高线更为圆形,加快收敛。
若采用仿射变换 ( \tilde{\mathbf{X}} = (\mathbf{X} - \mathbf{1}\mathbf{t}^\top)\mathbf{S}^{-1} ),则对应的样本矩阵 ( \tilde{\mathbf{X}} ),则:
[
\tilde{\mathbf{H}} = \frac{1}{n}\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}} = \mathbf{S}^{-1} \mathbf{H} \mathbf{S}^{-1}
]
因此,合适的 ( \mathbf{S} )(如方差的平方根)可将 ( \mathbf{H} ) 的谱半径缩小、降低条件数。
对距离度量与相似性的影响:
在基于距离/相似度的方法中,特征尺度决定距离贡献。例如欧氏距离:
[
d(\mathbf{x}, \mathbf{x}’) = \sqrt{\sum_{j=1}^d (x_j - x’j)^2}
]
如果某维的数值尺度远大于其他维,则该维主导整体距离,使kNN、KMeans、RBF核等偏置于该维。经过标准化:
[
d{\text{std}}(\mathbf{x}, \mathbf{x}’) = \sqrt{\sum_{j=1}^d \left(\frac{x_j - x’_j}{\sigmaj}\right)^2}
]
近似于对每维采用单位方差的马氏距离(对角协方差假设)。若进一步白化,则变为真正的马氏距离:
[
d{\text{mah}}(\mathbf{x}, \mathbf{x}’) = \sqrt{(\mathbf{x}-\mathbf{x}’)^\top \mathbf{\Sigma}^{-1} (\mathbf{x}-\mathbf{x}’)}
]
对正则化的影响(L1/L2):
考虑带L2正则的线性模型:
[
\min_{\mathbf{w}} |\mathbf{X}\mathbf{w} - \mathbf{y}|^2 + \lambda |\mathbf{w}|_2^2
]
进行 ( \tilde{\mathbf{X}} = \mathbf{X}\mathbf{S}^{-1} )(即先做缩放)的变换,令 ( \mathbf{v} = \mathbf{S}\mathbf{w} ),则:
[
|\mathbf{X}\mathbf{w} - \mathbf{y}|^2 = |\tilde{\mathbf{X}}\mathbf{v} - \mathbf{y}|^2
]
而正则项变为:
[
\lambda |\mathbf{w}|_2^2 = \lambda |\mathbf{S}^{-1}\mathbf{v}|2^2 = \lambda \sum{j=1}^d \frac{v_j^2}{s_j^2}
]
如果不对特征缩放一致,正则化对不同特征施加的惩罚强度不一致,造成偏置。标准化后让各维尺度 ( s_j ) 接近,有助于公平的正则化。对于L1正则(Lasso),同理更为敏感:未缩放时大尺度特征更容易被保留,小尺度特征更容易被压为0。
对核方法(RBF核)的影响:
RBF核:
[
K(\mathbf{x}, \mathbf{x}’) = \exp\left(-\gamma |\mathbf{x} - \mathbf{x}’|^2\right)
]
如果特征尺度不一致,某一维的距离主导,导致有效的 ( \gamma ) 空洞。归一化后,特征距离分布更加均衡,网格搜索/贝叶斯优化的超参数 ( \gamma ) 更易寻找。否则相同的 ( \gamma ) 在不同数据尺度下含义截然不同,调参效率低、决策边界不稳定。
完整案例:Python实现与可视化对比
我们构造一个包含多种尺度、偏态、重尾和离群点的二分类数据集,通过 scikit-learn 进行综合对比,直观展示:
- 归一化对分布形态(直方图/核密度)的影响;
- 归一化对SVM决策边界(RBF核)的改善;
- 多种归一化方法在不同模型(kNN/SVM/逻辑回归/KMeans)上的性能差异;
- 通过
Pipeline 确保无数据泄漏的最佳实践。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler, QuantileTransformer, PowerTransformer, Normalizer
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.cluster import KMeans
from sklearn.metrics import accuracy_score, f1_score, silhouette_score
from sklearn.compose import ColumnTransformer
from scipy.stats import gaussian_kde
# 1. 生成合成数据集
def make_synthetic(seed=42, n_samples=1200):
rng = np.random.RandomState(seed)
# 设计多尺度、多形态特征
# X1: 大方差高斯 + 5%远离群点
x1 = rng.normal(loc=0, scale=50, size=n_samples)
outlier_idx = rng.rand(n_samples) < 0.05
x1[outlier_idx] += rng.choice([300, -300], size=outlier_idx.sum())
# X2: 指数分布(偏态,正偏)
x2 = rng.exponential(scale=10.0, size=n_samples)
# X3: 小范围均匀分布
x3 = rng.uniform(low=-1.0, high=1.0, size=n_samples)
# X4: 重尾分布(Students t)
x4 = rng.standard_t(df=2, size=n_samples) * 10
# 目标:非线性决策边界(组合)
noise = rng.normal(0, 1.5, size=n_samples)
logit = 0.03 * x1 + np.sin(0.5 * x2) + 0.5 * x3 - 0.2 * x4 + noise
y = (logit > 0).astype(int)
X = np.c_[x1, x2, x3, x4]
return X, y
# 2. 可视化函数
def plot_complex_figure(X, y, scaler=None, title_suffix=""):
feature_colors = ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3']
cmap_bg = ListedColormap(['#ff7f00', '#1f78b4'])
cmap_pts = ListedColormap(['#f781bf', '#66c2a5'])
plt.figure(figsize=(14, 10))
# 子图1:原始特征直方+核密度叠加
ax1 = plt.subplot(2, 2, 1)
ax1.set_title("原始特征分布(直方图 + KDE)", fontweight='bold')
for j in range(X.shape[1]):
data = X[:, j]
ax1.hist(data, bins=40, alpha=0.25, color=feature_colors[j], density=True, edgecolor='white')
try:
kde = gaussian_kde(data)
grid = np.linspace(np.percentile(data, 0.5), np.percentile(data, 99.5), 300)
ax1.plot(grid, kde(grid), color=feature_colors[j], lw=2.5, label=f"X{j+1}")
except Exception:
pass
ax1.legend()
ax1.set_xlabel("数值")
ax1.set_ylabel("密度")
# 子图2:归一化后特征分布
ax2 = plt.subplot(2, 2, 2)
ax2.set_title("归一化后特征分布(直方图 + KDE)", fontweight='bold')
if scaler is None:
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
for j in range(X.shape[1]):
data = X_scaled[:, j]
ax2.hist(data, bins=40, alpha=0.25, color=feature_colors[j], density=True, edgecolor='white')
try:
kde = gaussian_kde(data)
grid = np.linspace(np.percentile(data, 0.5), np.percentile(data, 99.5), 300)
ax2.plot(grid, kde(grid), color=feature_colors[j], lw=2.5, label=f"X{j+1}'")
except Exception:
pass
ax2.legend()
ax2.set_xlabel("标准化后数值")
ax2.set_ylabel("密度")
ax2.axvline(0, color='k', linestyle='--', alpha=0.5)
# 子图3:SVM决策边界(未归一化)
ax3 = plt.subplot(2, 2, 3)
ax3.set_title("SVM(RBF) 决策边界(未归一化)", fontweight='bold')
X12 = X[:, :2]
X12_train, X12_test, y_train, y_test = train_test_split(X12, y, test_size=0.3, random_state=0, stratify=y)
svm_raw = SVC(kernel='rbf', gamma='scale', C=1.0, probability=False, random_state=0)
svm_raw.fit(X12_train, y_train)
x1_min, x1_max = np.percentile(X12[:, 0], 1), np.percentile(X12[:, 0], 99)
x2_min, x2_max = np.percentile(X12[:, 1], 1), np.percentile(X12[:, 1], 99)
xx, yy = np.meshgrid(np.linspace(x1_min, x1_max, 400), np.linspace(x2_min, x2_max, 400))
zz = svm_raw.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax3.contourf(xx, yy, zz, alpha=0.35, cmap=cmap_bg)
ax3.scatter(X12_train[:, 0], X12_train[:, 1], c=y_train, cmap=cmap_pts, edgecolor='k', s=20, alpha=0.8, label='Train')
ax3.scatter(X12_test[:, 0], X12_test[:, 1], c=y_test, cmap=cmap_pts, marker='^', edgecolor='k', s=35, alpha=0.9, label='Test')
ax3.set_xlabel("X1(大尺度+离群)")
ax3.set_ylabel("X2(正偏态)")
ax3.legend()
# 子图4:SVM决策边界(标准化后)
ax4 = plt.subplot(2, 2, 4)
ax4.set_title("SVM(RBF) 决策边界(标准化后)", fontweight='bold')
scaler_12 = StandardScaler().fit(X12_train)
X12_train_s = scaler_12.transform(X12_train)
X12_test_s = scaler_12.transform(X12_test)
svm_scaled = SVC(kernel='rbf', gamma='scale', C=1.0, probability=False, random_state=0)
svm_scaled.fit(X12_train_s, y_train)
zz_s = svm_scaled.predict(scaler_12.transform(np.c_[xx.ravel(), yy.ravel()])).reshape(xx.shape)
ax4.contourf(xx, yy, zz_s, alpha=0.35, cmap=cmap_bg)
ax4.scatter(X12_train[:, 0], X12_train[:, 1], c=y_train, cmap=cmap_pts, edgecolor='k', s=20, alpha=0.8, label='Train')
ax4.scatter(X12_test[:, 0], X12_test[:, 1], c=y_test, cmap=cmap_pts, marker='^', edgecolor='k', s=35, alpha=0.9, label='Test')
ax4.set_xlabel("X1(大尺度+离群)")
ax4.set_ylabel("X2(正偏态)")
ax4.legend()
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
return plt
# 3. 模型评估:归一化前后对比
def evaluate_models(X, y):
models = {
"SVM-RBF": SVC(kernel='rbf', gamma='scale', C=1.0, random_state=0),
"kNN-5": KNeighborsClassifier(n_neighbors=5),
"Logistic-L2": LogisticRegression(max_iter=500, C=1.0, solver='lbfgs', random_state=0)
}
scalers = {
"None": None,
"Standard": StandardScaler(),
"MinMax": MinMaxScaler(),
"Robust": RobustScaler(),
"Quantile-Normal": QuantileTransformer(output_distribution='normal', random_state=0),
"Power-YJ": PowerTransformer(method='yeo-johnson', standardize=True)
}
print("分类模型在不同缩放策略下的5折交叉验证准确率与F1")
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
for scaler_name, scaler in scalers.items():
for model_name, model in models.items():
if scaler is None:
pipe = Pipeline([('clf', model)])
else:
pipe = Pipeline([('scaler', scaler), ('clf', model)])
acc = cross_val_score(pipe, X, y, cv=cv, scoring='accuracy').mean()
f1 = cross_val_score(pipe, X, y, cv=cv, scoring='f1').mean()
print(f"[{scaler_name:>12s}] {model_name:>12s} | Acc={acc:.4f}, F1={f1:.4f}")
# KMeans聚类评估
print("\nKMeans在不同缩放策略下的轮廓系数(k=2)")
for scaler_name, scaler in scalers.items():
if scaler is None:
X_use = X
else:
X_use = scaler.fit_transform(X)
km = KMeans(n_clusters=2, n_init=10, random_state=0)
labels = km.fit_predict(X_use)
sil = silhouette_score(X_use, labels)
print(f"[{scaler_name:>12s}] KMeans | Silhouette={sil:.4f}")
# 4. 主程序
if __name__ == "__main__":
X, y = make_synthetic(seed=42, n_samples=1200)
plt = plot_complex_figure(X, y, scaler=StandardScaler(), title_suffix="(虚拟数据)")
plt.show()
evaluate_models(X, y)
可视化结果分析:
原始特征分布(直方图 + KDE):清晰展示了X1~X4特征的天然差异:X1尺度大且有离群点;X2呈现明显正偏态;X3范围集中;X4具有重尾特性。未经处理时,特征的尺度和分布形态差异巨大,这将严重影响依赖距离度量的算法(如kNN、RBF-SVM)以及基于梯度下降的优化过程。
标准化后特征分布(直方图 + KDE):每个特征被转换为以0为中心、方差约为1的分布。虽然离群点的影响依然存在,但整体尺度得到了统一。标准化在一般情况下能有效缓解尺度差异,为算法优化和公平的距离计算奠定基础。然而,对于X2(严重偏态)或X4(重尾)这类分布,仅使用Z-score可能不足以使数据接近正态,此时需要考虑分位数变换或幂变换等更强的特征工程手段。
SVM(RBF核)决策边界对比:
- 未归一化:决策边界明显受到大尺度特征X1的主导,边界扭曲,分割效果不理想。这是因为RBF核的宽度参数 ( \gamma ) 隐含了对距离的缩放,特征尺度不一致导致有效的 ( \gamma ) 难以寻找。
- 标准化后:相同的模型与超参数应用在标准化后的特征空间上,决策边界变得稳定、平滑,且与数据的潜在结构更为匹配。归一化帮助核函数公平地权衡各维度,使得超参数搜索更稳定,模型泛化能力更强。
通过综合对比可以直观理解,归一化并非仅仅是改变数值单位的简单操作,而是能宏观影响模型学习行为、微观保障数值稳定的决定性步骤。
总结
数据归一化是构建健壮机器学习流水线的基石。它直接关系到模型的训练效率、数值稳定性、泛化性能及评估结果的可信度。
在实践中,建议将标准化(Z-score)作为强基线方法。随后,根据数据的实际分布(如偏态、重尾、离群点、稀疏性)针对性选用稳健缩放或分布变换方法。务必通过 Pipeline 封装预处理与模型,以防止数据泄漏,并利用交叉验证来客观评估不同归一化策略的效果。