找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1248

积分

0

好友

184

主题
发表于 前天 23:08 | 查看: 5| 回复: 0

数据归一化是一类将特征映射到统一尺度的关键预处理方法。它在几乎所有机器学习模型和训练流程中都扮演着核心角色:有助于优化器快速收敛、避免数值不稳定、使距离度量更为合理、确保正则化的公平性。对某些算法而言,缺乏归一化步骤甚至无法有效工作

尽管“归一化”一词在中文语境中有时泛指一切“缩放到统一尺度”的操作,但其内部包含了标准化、缩放、稳健缩放、分布变换、白化等多个具体且重要的技术类型。

统一仿射变换框架

设原始特征向量为 ( \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-learnStandardScaler 使用无偏估计);
  • ( \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 封装预处理与模型,以防止数据泄漏,并利用交叉验证来客观评估不同归一化策略的效果。




上一篇:Python用户登录功能实战:基于PyMe与SQLite数据库开发
下一篇:Forward Deployed Engineer:硅谷热捧的FDE模式是创新还是伪装?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 20:13 , Processed in 0.121849 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表