Z-Score归一化

Z-Score标准化:详细数学与实现笔记

一、数学原理与推导

1.1 基本公式

Z-Score标准化(标准差标准化)将数据转换为均值为0、标准差为1的标准正态分布:

\[z = \frac{x - \mu}{\sigma} \]

其中:

  • \(\mu\) 为数据的均值:\(\mu = \frac{1}{n}\sum_{i=1}^{n} x_i\)
  • \(\sigma\) 为数据的标准差:\(\sigma = \sqrt{\frac{1}{n}\sum_{i=1}^{n} (x_i - \mu)^2}\)

1.2 样本标准差与总体标准差

在实际应用中,需要注意区分:

  • 总体标准差\(\sigma = \sqrt{\frac{1}{n}\sum_{i=1}^{n} (x_i - \mu)^2}\)
  • 样本标准差\(s = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n} (x_i - \bar{x})^2}\)

在Z-Score标准化中,通常使用总体标准差

1.3 矩阵形式表示

对于数据集 \(X \in \mathbb{R}^{m \times n}\)(m个样本,n个特征):

\[Z = \frac{X - \mu}{\sigma} \]

其中:

  • \(\mu\):每列的均值向量 \(\in \mathbb{R}^{1 \times n}\)
  • \(\sigma\):每列的标准差向量 \(\in \mathbb{R}^{1 \times n}\)
  • 运算为广播操作

二、数学性质分析

2.1 变换性质

\(T(x) = \frac{x - \mu}{\sigma}\),则:

  1. 线性变换\(T(ax + b) = a'T(x) + b'\),其中 \(a' = \frac{a}{\sigma}\)\(b' = \frac{b}{\sigma}\)
  2. 保持分布形状:若 \(X \sim N(\mu, \sigma^2)\),则 \(Z \sim N(0, 1)\)
  3. 无量纲化:消除不同特征间的量纲影响

2.2 统计特性证明

定理:对于任意数据集 \(X = \{x_1, x_2, ..., x_n\}\),经Z-Score标准化后得到 \(Z = \{z_1, z_2, ..., z_n\}\),则:

  1. \(\bar{z} = 0\)
  2. \(s_z = 1\)

证明

  1. 均值:

    \[\bar{z} = \frac{1}{n}\sum_{i=1}^{n} z_i = \frac{1}{n}\sum_{i=1}^{n} \frac{x_i - \mu}{\sigma} = \frac{1}{\sigma}\left(\frac{1}{n}\sum_{i=1}^{n} x_i - \mu\right) = 0 \]

  2. 标准差:

    \[s_z^2 = \frac{1}{n}\sum_{i=1}^{n} (z_i - \bar{z})^2 = \frac{1}{n}\sum_{i=1}^{n} z_i^2 = \frac{1}{n}\sum_{i=1}^{n} \left(\frac{x_i - \mu}{\sigma}\right)^2 = \frac{1}{\sigma^2} \cdot \frac{1}{n}\sum_{i=1}^{n} (x_i - \mu)^2 = 1 \]

三、代码实现与数学验证

3.1 Z-Score标准化实现

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from sklearn.preprocessing import StandardScaler

class ZScoreNormalization:
    """Z-Score标准化实现"""
    
    def __init__(self, ddof=0):
        """
        参数:
        ddof: 自由度调整,0表示总体标准差,1表示样本标准差
        """
        self.ddof = ddof
        self.mean = None
        self.std = None
        
    def fit(self, X):
        """
        计算数据的均值和标准差
        
        参数:
        X: 原始数据,形状为(n_samples, n_features)或(n_samples,)
        """
        X = np.array(X)
        if X.ndim == 1:
            X = X.reshape(-1, 1)
            
        self.mean = np.mean(X, axis=0)
        self.std = np.std(X, axis=0, ddof=self.ddof)
        
        # 防止除零
        self.std[self.std == 0] = 1
        
        return self
    
    def transform(self, X):
        """
        应用Z-Score标准化
        
        公式: z = (x - mean) / std
        """
        if self.mean is None or self.std is None:
            raise ValueError("必须先调用fit方法")
            
        X = np.array(X)
        if X.ndim == 1:
            X = X.reshape(-1, 1)
            
        # 应用公式
        Z = (X - self.mean) / self.std
            
        return Z.squeeze()
    
    def fit_transform(self, X):
        """fit和transform的组合"""
        return self.fit(X).transform(X)
    
    def inverse_transform(self, Z):
        """
        反标准化
        
        公式: x = z * std + mean
        """
        if self.mean is None or self.std is None:
            raise ValueError("必须先调用fit方法")
            
        Z = np.array(Z)
        if Z.ndim == 1:
            Z = Z.reshape(-1, 1)
            
        # 应用反变换公式
        X = Z * self.std + self.mean
        
        return X.squeeze()
    
    def get_params(self):
        """获取标准化参数"""
        return {
            'mean': self.mean,
            'std': self.std,
            'ddof': self.ddof
        }

3.2 数学验证与测试

def verify_zscore_properties():
    """验证Z-Score标准化的数学性质"""
    
    # 1. 生成测试数据
    np.random.seed(42)
    X = np.random.normal(loc=50, scale=10, size=1000)  # 正态分布
    
    # 2. 初始化标准化器
    scaler = ZScoreNormalization()
    Z = scaler.fit_transform(X)
    
    # 3. 验证性质1: 均值为0,标准差为1
    print("性质1验证 - 统计特性:")
    print(f"  原始数据: μ={X.mean():.6f}, σ={X.std(ddof=0):.6f}")
    print(f"  标准化数据: μ={Z.mean():.6f}, σ={Z.std(ddof=0):.6f}")
    print(f"  是否满足μ=0: {abs(Z.mean()) < 1e-10}")
    print(f"  是否满足σ=1: {abs(Z.std(ddof=0) - 1) < 1e-10}")
    
    # 4. 验证性质2: 线性变换性质
    print("\n性质2验证 - 线性变换:")
    a, b = 2, 3
    X_linear = a * X + b
    
    # 方法1: 直接标准化X_linear
    scaler2 = ZScoreNormalization()
    Z_linear = scaler2.fit_transform(X_linear)
    
    # 方法2: 通过标准化Z计算
    # 理论推导: 如果 Y = aX + b, 则 Z_Y = (Y - μ_Y)/σ_Y = (aX + b - aμ_X - b)/(aσ_X) = (X - μ_X)/σ_X = Z_X
    Z_theory = Z  # 理论上应该相同
    
    # 计算误差
    error = np.max(np.abs(Z_linear - Z_theory))
    print(f"  线性变换标准化误差: {error:.10f}")
    print(f"  是否保持线性关系: {error < 1e-10}")
    
    # 5. 验证性质3: 可逆性
    print("\n性质3验证 - 可逆性:")
    X_reconstructed = scaler.inverse_transform(Z)
    reconstruction_error = np.max(np.abs(X - X_reconstructed))
    print(f"  最大重构误差: {reconstruction_error:.10f}")
    print(f"  是否可逆: {reconstruction_error < 1e-10}")
    
    # 6. 验证性质4: 保持分布形状
    print("\n性质4验证 - 分布形状保持:")
    
    # Kolmogorov-Smirnov检验
    from scipy.stats import kstest
    
    # 检验原始数据是否正态分布
    ks_stat_original, p_original = kstest(X, 'norm', args=(X.mean(), X.std()))
    print(f"  原始数据KS检验: 统计量={ks_stat_original:.6f}, p值={p_original:.6f}")
    
    # 检验标准化数据是否标准正态分布
    ks_stat_z, p_z = kstest(Z, 'norm', args=(0, 1))
    print(f"  标准化数据KS检验: 统计量={ks_stat_z:.6f}, p值={p_z:.6f}")
    
    # 如果p值大于0.05,不能拒绝正态分布假设
    print(f"  原始数据是否正态分布: {p_original > 0.05}")
    print(f"  标准化数据是否标准正态分布: {p_z > 0.05}")
    
    # 7. 验证样本标准差与总体标准差的区别
    print("\n样本vs总体标准差验证:")
    
    # 总体标准差
    scaler_pop = ZScoreNormalization(ddof=0)
    Z_pop = scaler_pop.fit_transform(X)
    
    # 样本标准差
    scaler_sample = ZScoreNormalization(ddof=1)
    Z_sample = scaler_sample.fit_transform(X)
    
    print(f"  总体标准差: {scaler_pop.std}")
    print(f"  样本标准差: {scaler_sample.std}")
    print(f"  总体标准差标准化后σ: {Z_pop.std(ddof=0):.6f}")
    print(f"  样本标准差标准化后σ: {Z_sample.std(ddof=0):.6f}")
    
    # 样本标准差标准化后,用总体标准差计算得到的σ
    sigma_sample_std = Z_sample.std(ddof=0)
    print(f"  使用样本标准差标准化,但用总体公式计算的σ: {sigma_sample_std:.6f}")

verify_zscore_properties()

四、几何解释与可视化

4.1 一维数据可视化

def visualize_zscore_1d():
    """可视化一维数据的Z-Score标准化"""
    
    # 生成不同分布的数据
    np.random.seed(42)
    
    # 正态分布
    X_normal = np.random.normal(loc=50, scale=10, size=1000)
    
    # 均匀分布
    X_uniform = np.random.uniform(20, 80, size=1000)
    
    # 指数分布
    X_exponential = np.random.exponential(scale=20, size=1000) + 30
    
    datasets = [
        ("正态分布", X_normal),
        ("均匀分布", X_uniform),
        ("指数分布", X_exponential)
    ]
    
    # 创建图形
    fig, axes = plt.subplots(3, 4, figsize=(16, 12))
    
    for idx, (name, X) in enumerate(datasets):
        # 标准化
        scaler = ZScoreNormalization()
        Z = scaler.fit_transform(X)
        
        # 1. 原始数据直方图
        axes[idx, 0].hist(X, bins=50, density=True, alpha=0.7, color='blue')
        axes[idx, 0].axvline(x=X.mean(), color='red', linestyle='-', 
                            linewidth=2, label=f'μ={X.mean():.2f}')
        axes[idx, 0].axvline(x=X.mean() + X.std(), color='green', 
                            linestyle='--', linewidth=1.5, 
                            label=f'μ±σ=[{X.mean()-X.std():.2f}, {X.mean()+X.std():.2f}]')
        axes[idx, 0].axvline(x=X.mean() - X.std(), color='green', 
                            linestyle='--', linewidth=1.5)
        axes[idx, 0].set_xlabel('X值')
        axes[idx, 0].set_ylabel('密度')
        axes[idx, 0].set_title(f'{name} - 原始数据')
        axes[idx, 0].legend()
        axes[idx, 0].grid(True, alpha=0.3)
        
        # 2. 标准化过程示意图
        x_range = np.linspace(X.min(), X.max(), 1000)
        z_range = (x_range - scaler.mean) / scaler.std
        
        axes[idx, 1].plot(x_range, z_range, 'b-', linewidth=2)
        
        # 标记几个关键点
        key_points = [X.mean(), X.mean() + X.std(), X.mean() - X.std()]
        for point in key_points:
            z_point = (point - scaler.mean) / scaler.std
            axes[idx, 1].plot([point, point], [0, z_point], 'r--', alpha=0.5)
            axes[idx, 1].plot(point, z_point, 'ro', markersize=8)
            axes[idx, 1].text(point, z_point+0.5, f'({point:.1f}, {z_point:.1f})', 
                             fontsize=9, ha='center')
        
        axes[idx, 1].set_xlabel('原始X值')
        axes[idx, 1].set_ylabel('标准化Z值')
        axes[idx, 1].set_title(f'{name} - 标准化映射函数')
        axes[idx, 1].grid(True, alpha=0.3)
        
        # 3. 标准化后数据直方图
        axes[idx, 2].hist(Z, bins=50, density=True, alpha=0.7, color='green')
        
        # 绘制标准正态分布曲线
        x_norm = np.linspace(-4, 4, 1000)
        y_norm = stats.norm.pdf(x_norm, 0, 1)
        axes[idx, 2].plot(x_norm, y_norm, 'r-', linewidth=2, label='标准正态分布')
        
        axes[idx, 2].axvline(x=0, color='red', linestyle='-', 
                           linewidth=2, label='μ=0')
        axes[idx, 2].axvline(x=1, color='green', linestyle='--', 
                           linewidth=1.5, label='σ=1')
        axes[idx, 2].axvline(x=-1, color='green', linestyle='--', linewidth=1.5)
        
        axes[idx, 2].set_xlabel('标准化Z值')
        axes[idx, 2].set_ylabel('密度')
        axes[idx, 2].set_title(f'{name} - 标准化后数据')
        axes[idx, 2].set_xlim(-4, 4)
        axes[idx, 2].legend()
        axes[idx, 2].grid(True, alpha=0.3)
        
        # 4. QQ图(分位数-分位数图)
        stats.probplot(Z, dist="norm", plot=axes[idx, 3])
        axes[idx, 3].set_title(f'{name} - QQ图')
        axes[idx, 3].grid(True, alpha=0.3)
        
        # 计算R²值
        from scipy.stats import pearsonr
        osr = stats.zscore(Z)
        r, _ = pearsonr(osr, Z)
        axes[idx, 3].text(0.05, 0.95, f'R² = {r**2:.4f}', 
                         transform=axes[idx, 3].transAxes,
                         bbox=dict(boxstyle="round,pad=0.3", 
                                 facecolor="yellow", alpha=0.5))
    
    plt.tight_layout()
    plt.show()
    
    # 数学分析
    print("Z-Score标准化数学分析:")
    print("公式: z = (x - μ) / σ")
    print()
    
    for name, X in datasets:
        μ, σ = X.mean(), X.std(ddof=0)
        print(f"{name}:")
        print(f"  原始统计: μ={μ:.4f}, σ={σ:.4f}")
        print(f"  标准化后: 理论μ=0, σ=1")
        print(f"  实际验证: μ={(X-μ)/σ}.mean():.6f}, σ={((X-μ)/σ).std():.6f}")
        print()

visualize_zscore_1d()

4.2 二维数据可视化

def visualize_zscore_2d():
    """可视化二维数据的Z-Score标准化"""
    
    # 生成二维数据
    np.random.seed(42)
    n_samples = 500
    
    # 创建两个相关的特征
    X1 = np.random.normal(loc=50, scale=10, size=n_samples)
    X2 = 0.7 * X1 + np.random.normal(loc=0, scale=5, size=n_samples)
    X = np.column_stack([X1, X2])
    
    # 添加一些异常值
    X[0, 0] = 150  # 异常值
    X[1, 1] = -20  # 异常值
    
    # 标准化
    scaler = ZScoreNormalization()
    Z = scaler.fit_transform(X)
    
    # 创建图形
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 1. 原始数据散点图
    scatter1 = axes[0, 0].scatter(X[:, 0], X[:, 1], alpha=0.6, 
                                  c=np.arange(n_samples), cmap='viridis')
    axes[0, 0].axhline(y=X[:, 1].mean(), color='red', linestyle='-', 
                      linewidth=2, label=f'μ2={X[:, 1].mean():.2f}')
    axes[0, 0].axvline(x=X[:, 0].mean(), color='blue', linestyle='-', 
                      linewidth=2, label=f'μ1={X[:, 0].mean():.2f}')
    
    # 添加一个标准差椭圆
    from matplotlib.patches import Ellipse
    cov_matrix = np.cov(X.T)
    eigvals, eigvecs = np.linalg.eig(cov_matrix)
    angle = np.degrees(np.arctan2(eigvecs[1, 0], eigvecs[0, 0]))
    width = 2 * np.sqrt(eigvals[0])
    height = 2 * np.sqrt(eigvals[1])
    
    ellipse = Ellipse(xy=(X[:, 0].mean(), X[:, 1].mean()), 
                     width=width, height=height, angle=angle,
                     edgecolor='red', facecolor='none', linewidth=2, 
                     linestyle='--', label='1σ椭圆')
    axes[0, 0].add_patch(ellipse)
    
    axes[0, 0].set_xlabel('特征1')
    axes[0, 0].set_ylabel('特征2')
    axes[0, 0].set_title('原始数据散点图(含异常值)')
    axes[0, 0].legend(loc='upper right')
    axes[0, 0].grid(True, alpha=0.3)
    
    # 2. 特征分布对比
    axes[0, 1].hist(X[:, 0], bins=30, alpha=0.5, label='特征1', density=True)
    axes[0, 1].hist(X[:, 1], bins=30, alpha=0.5, label='特征2', density=True)
    axes[0, 1].set_xlabel('特征值')
    axes[0, 1].set_ylabel('密度')
    axes[0, 1].set_title('原始特征分布对比')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)
    
    # 3. 标准化过程示意图
    # 绘制原始坐标系和标准化坐标系
    axes[0, 2].scatter(X[:, 0], X[:, 1], alpha=0.3, label='原始数据')
    
    # 绘制标准化后的位置
    axes[0, 2].scatter(Z[:, 0] * 20 + X[:, 0].mean(),  # 缩放以便可视化
                      Z[:, 1] * 20 + X[:, 1].mean(),
                      alpha=0.3, color='red', label='标准化后位置')
    
    # 绘制映射线
    for i in range(min(10, n_samples)):  # 只显示前10个
        axes[0, 2].plot([X[i, 0], Z[i, 0] * 20 + X[:, 0].mean()],
                       [X[i, 1], Z[i, 1] * 20 + X[:, 1].mean()],
                       'g--', alpha=0.3)
    
    axes[0, 2].set_xlabel('特征1')
    axes[0, 2].set_ylabel('特征2')
    axes[0, 2].set_title('Z-Score映射过程示意图')
    axes[0, 2].legend()
    axes[0, 2].grid(True, alpha=0.3)
    
    # 4. 标准化后数据散点图
    scatter4 = axes[1, 0].scatter(Z[:, 0], Z[:, 1], alpha=0.6, 
                                  c=np.arange(n_samples), cmap='viridis')
    axes[1, 0].axhline(y=0, color='red', linestyle='-', linewidth=2, 
                      label='μ2=0')
    axes[1, 0].axvline(x=0, color='blue', linestyle='-', linewidth=2, 
                      label='μ1=0')
    
    # 添加一个标准差圆
    circle = plt.Circle((0, 0), 1, edgecolor='red', facecolor='none', 
                       linewidth=2, linestyle='--', label='1σ圆')
    axes[1, 0].add_patch(circle)
    
    axes[1, 0].set_xlabel('标准化特征1')
    axes[1, 0].set_ylabel('标准化特征2')
    axes[1, 0].set_title('标准化后数据散点图')
    axes[1, 0].set_xlim(-4, 4)
    axes[1, 0].set_ylim(-4, 4)
    axes[1, 0].legend(loc='upper right')
    axes[1, 0].grid(True, alpha=0.3)
    
    # 5. 标准化后特征分布对比
    axes[1, 1].hist(Z[:, 0], bins=30, alpha=0.5, label='标准化特征1', 
                   density=True)
    axes[1, 1].hist(Z[:, 1], bins=30, alpha=0.5, label='标准化特征2', 
                   density=True)
    
    # 绘制标准正态分布曲线
    x_norm = np.linspace(-4, 4, 1000)
    y_norm = stats.norm.pdf(x_norm, 0, 1)
    axes[1, 1].plot(x_norm, y_norm, 'k-', linewidth=2, 
                   label='标准正态分布')
    
    axes[1, 1].set_xlabel('标准化特征值')
    axes[1, 1].set_ylabel('密度')
    axes[1, 1].set_title('标准化后特征分布对比')
    axes[1, 1].set_xlim(-4, 4)
    axes[1, 1].legend()
    axes[1, 1].grid(True, alpha=0.3)
    
    # 6. 相关性保持验证
    corr_original = np.corrcoef(X.T)[0, 1]
    corr_normalized = np.corrcoef(Z.T)[0, 1]
    
    axes[1, 2].scatter(X[:, 0], X[:, 1], alpha=0.5, label=f'原始数据(r={corr_original:.4f})')
    axes[1, 2].scatter(Z[:, 0], Z[:, 1], alpha=0.5, label=f'标准化数据(r={corr_normalized:.4f})')
    axes[1, 2].set_xlabel('特征1 / 标准化特征1')
    axes[1, 2].set_ylabel('特征2 / 标准化特征2')
    axes[1, 2].set_title('相关性保持验证')
    axes[1, 2].legend()
    axes[1, 2].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 统计信息
    print("原始数据统计:")
    print(f"  特征1: μ={X[:, 0].mean():.4f}, σ={X[:, 0].std():.4f}")
    print(f"  特征2: μ={X[:, 1].mean():.4f}, σ={X[:, 1].std():.4f}")
    print(f"  协方差矩阵:\n{np.cov(X.T)}")
    print(f"  相关系数: {corr_original:.6f}")
    
    print("\n标准化后数据统计:")
    print(f"  特征1: μ={Z[:, 0].mean():.6f}, σ={Z[:, 0].std():.6f}")
    print(f"  特征2: μ={Z[:, 1].mean():.6f}, σ={Z[:, 1].std():.6f}")
    print(f"  协方差矩阵:\n{np.cov(Z.T)}")
    print(f"  相关系数: {corr_normalized:.6f}")
    
    print(f"\n相关系数变化: {abs(corr_original - corr_normalized):.10f}")
    print("Z-Score标准化保持数据的相关性!")

visualize_zscore_2d()

五、数学定理与证明

5.1 Z-Score标准化保持相关性

定理:对于任意两个变量 \(X\)\(Y\),经Z-Score标准化后得到 \(Z_X\)\(Z_Y\),则相关系数保持不变:

\[\rho_{X,Y} = \rho_{Z_X, Z_Y} \]

证明
相关系数定义为:

\[\rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} \]

标准化后:

\[Z_X = \frac{X - \mu_X}{\sigma_X}, \quad Z_Y = \frac{Y - \mu_Y}{\sigma_Y} \]

计算协方差:

\[\begin{aligned} \text{Cov}(Z_X, Z_Y) &= \mathbb{E}[(Z_X - \mathbb{E}[Z_X])(Z_Y - \mathbb{E}[Z_Y])] \\ &= \mathbb{E}[Z_X Z_Y] \quad (\text{因为 } \mathbb{E}[Z_X] = \mathbb{E}[Z_Y] = 0) \\ &= \mathbb{E}\left[\frac{X - \mu_X}{\sigma_X} \cdot \frac{Y - \mu_Y}{\sigma_Y}\right] \\ &= \frac{1}{\sigma_X \sigma_Y} \mathbb{E}[(X - \mu_X)(Y - \mu_Y)] \\ &= \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} \end{aligned} \]

\(\sigma_{Z_X} = \sigma_{Z_Y} = 1\),所以:

\[\rho_{Z_X, Z_Y} = \frac{\text{Cov}(Z_X, Z_Y)}{\sigma_{Z_X} \sigma_{Z_Y}} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} = \rho_{X,Y} \]

5.2 Z-Score标准化与线性变换

定理:Z-Score标准化是线性变换。

证明
\(T(X) = \frac{X - \mu_X}{\sigma_X}\),对于任意 \(a, b \in \mathbb{R}\),有:

\[T(aX + b) = \frac{aX + b - \mu_{aX+b}}{\sigma_{aX+b}} \]

已知:

  • \(\mu_{aX+b} = a\mu_X + b\)
  • \(\sigma_{aX+b} = |a|\sigma_X\)

所以:

\[T(aX + b) = \frac{aX + b - (a\mu_X + b)}{|a|\sigma_X} = \frac{a(X - \mu_X)}{|a|\sigma_X} = \text{sign}(a) \cdot \frac{X - \mu_X}{\sigma_X} = \text{sign}(a) \cdot T(X) \]

因此,Z-Score标准化是保持方向的线性变换。

5.3 Z-Score与Mahalanobis距离

对于多元数据 \(X \in \mathbb{R}^{m \times n}\),Mahalanobis距离定义为:

\[D_M(x) = \sqrt{(x - \mu)^T \Sigma^{-1} (x - \mu)} \]

其中 \(\Sigma\) 是协方差矩阵。

当各特征不相关时,\(\Sigma\) 是对角矩阵,对角线元素为 \(\sigma_i^2\),则:

\[D_M(x) = \sqrt{\sum_{i=1}^n \frac{(x_i - \mu_i)^2}{\sigma_i^2}} = \sqrt{\sum_{i=1}^n z_i^2} = \|z\| \]

因此,Z-Score标准化后的欧氏距离等于原始数据的Mahalanobis距离。

六、Z-Score在机器学习中的应用

6.1 在神经网络中的应用

def neural_network_with_zscore():
    """使用Z-Score标准化的神经网络示例"""
    
    # 1. 生成非线性数据
    np.random.seed(42)
    n_samples = 500
    
    # 生成数据: y = x² + 噪声
    X = np.random.uniform(-3, 3, n_samples)
    y = X**2 + np.random.normal(0, 1, n_samples)
    
    # 2. 划分训练集和测试集
    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )
    
    # 3. 分别对X和y进行Z-Score标准化
    scaler_X = ZScoreNormalization()
    scaler_y = ZScoreNormalization()
    
    X_train_norm = scaler_X.fit_transform(X_train.reshape(-1, 1))
    y_train_norm = scaler_y.fit_transform(y_train.reshape(-1, 1))
    
    X_test_norm = scaler_X.transform(X_test.reshape(-1, 1))
    
    # 4. 构建更复杂的神经网络
    class NeuralNetwork:
        def __init__(self, input_dim=1, hidden_dims=[10, 10], output_dim=1):
            # 初始化权重
            np.random.seed(42)
            self.weights = []
            self.biases = []
            
            # 输入层到第一个隐藏层
            prev_dim = input_dim
            for hidden_dim in hidden_dims:
                self.weights.append(np.random.randn(prev_dim, hidden_dim) * 0.1)
                self.biases.append(np.zeros((1, hidden_dim)))
                prev_dim = hidden_dim
            
            # 最后一个隐藏层到输出层
            self.weights.append(np.random.randn(prev_dim, output_dim) * 0.1)
            self.biases.append(np.zeros((1, output_dim)))
            
            self.activations = []
            
        def relu(self, x):
            return np.maximum(0, x)
        
        def relu_derivative(self, x):
            return (x > 0).astype(float)
        
        def forward(self, X):
            """前向传播"""
            self.activations = [X]
            a = X
            
            for i in range(len(self.weights) - 1):
                z = np.dot(a, self.weights[i]) + self.biases[i]
                a = self.relu(z)
                self.activations.append(a)
            
            # 输出层(线性激活)
            z = np.dot(a, self.weights[-1]) + self.biases[-1]
            self.activations.append(z)
            
            return z
        
        def backward(self, X, y, y_pred, lr=0.01):
            """反向传播"""
            m = X.shape[0]
            
            # 输出层误差
            d_loss = 2 * (y_pred - y) / m
            
            # 反向传播
            d_weights = []
            d_biases = []
            
            # 输出层梯度
            dZ = d_loss
            dW = np.dot(self.activations[-2].T, dZ)
            db = np.sum(dZ, axis=0, keepdims=True)
            d_weights.insert(0, dW)
            d_biases.insert(0, db)
            
            # 隐藏层梯度
            for i in range(len(self.weights) - 2, -1, -1):
                dA = np.dot(dZ, self.weights[i + 1].T)
                dZ = dA * self.relu_derivative(self.activations[i + 1])
                dW = np.dot(self.activations[i].T, dZ)
                db = np.sum(dZ, axis=0, keepdims=True)
                d_weights.insert(0, dW)
                d_biases.insert(0, db)
            
            # 更新权重
            for i in range(len(self.weights)):
                self.weights[i] -= lr * d_weights[i]
                self.biases[i] -= lr * d_biases[i]
        
        def predict(self, X):
            """预测"""
            return self.forward(X)
    
    # 5. 训练神经网络
    nn = NeuralNetwork(input_dim=1, hidden_dims=[20, 20], output_dim=1)
    epochs = 5000
    losses = []
    
    for epoch in range(epochs):
        # 前向传播
        y_pred_norm = nn.forward(X_train_norm.reshape(-1, 1))
        
        # 计算损失
        loss = np.mean((y_pred_norm - y_train_norm.reshape(-1, 1)) ** 2)
        losses.append(loss)
        
        # 反向传播
        nn.backward(X_train_norm.reshape(-1, 1), 
                   y_train_norm.reshape(-1, 1), 
                   y_pred_norm, 
                   lr=0.01)
        
        if epoch % 1000 == 0:
            print(f"Epoch {epoch}: Loss = {loss:.6f}")
    
    # 6. 预测并反标准化
    y_pred_test_norm = nn.predict(X_test_norm.reshape(-1, 1))
    y_pred_test = scaler_y.inverse_transform(y_pred_test_norm)
    
    # 7. 可视化结果
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 原始数据
    axes[0, 0].scatter(X_train, y_train, alpha=0.5, label='训练数据')
    axes[0, 0].scatter(X_test, y_test, alpha=0.5, color='red', label='测试数据')
    X_plot = np.linspace(-3, 3, 1000)
    y_true = X_plot**2
    axes[0, 0].plot(X_plot, y_true, 'g-', linewidth=2, label='真实函数: y=x²')
    axes[0, 0].set_xlabel('X')
    axes[0, 0].set_ylabel('y')
    axes[0, 0].set_title('原始数据分布')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    
    # 标准化后数据
    X_plot_norm = scaler_X.transform(X_plot.reshape(-1, 1))
    y_true_norm = scaler_y.transform(y_true.reshape(-1, 1))
    
    axes[0, 1].scatter(X_train_norm, y_train_norm, alpha=0.5, label='标准化训练数据')
    axes[0, 1].scatter(X_test_norm, scaler_y.transform(y_test.reshape(-1, 1)), 
                      alpha=0.5, color='red', label='标准化测试数据')
    axes[0, 1].plot(X_plot_norm, y_true_norm, 'g-', linewidth=2, 
                   label='标准化真实函数')
    axes[0, 1].set_xlabel('标准化X')
    axes[0, 1].set_ylabel('标准化y')
    axes[0, 1].set_title('标准化后数据分布')
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3)
    
    # 损失曲线
    axes[0, 2].plot(losses)
    axes[0, 2].set_xlabel('Epoch')
    axes[0, 2].set_ylabel('Loss (MSE)')
    axes[0, 2].set_title('训练损失曲线')
    axes[0, 2].set_yscale('log')
    axes[0, 2].grid(True, alpha=0.3)
    
    # 预测结果对比
    axes[1, 0].scatter(X_test, y_test, alpha=0.5, label='测试数据')
    axes[1, 0].scatter(X_test, y_pred_test, alpha=0.8, color='red', 
                      s=20, label='神经网络预测')
    axes[1, 0].plot(X_plot, y_true, 'g-', linewidth=2, label='真实函数')
    axes[1, 0].set_xlabel('X')
    axes[1, 0].set_ylabel('y')
    axes[1, 0].set_title('神经网络预测结果')
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)
    
    # 残差分析
    residuals = y_test - y_pred_test
    axes[1, 1].scatter(y_pred_test, residuals, alpha=0.5)
    axes[1, 1].axhline(y=0, color='red', linestyle='-', linewidth=2)
    axes[1, 1].set_xlabel('预测值')
    axes[1, 1].set_ylabel('残差')
    axes[1, 1].set_title('残差分析图')
    axes[1, 1].grid(True, alpha=0.3)
    
    # 预测误差分布
    axes[1, 2].hist(residuals, bins=30, density=True, alpha=0.7)
    axes[1, 2].set_xlabel('预测误差')
    axes[1, 2].set_ylabel('密度')
    axes[1, 2].set_title('预测误差分布')
    
    # 拟合正态分布
    mu, sigma = residuals.mean(), residuals.std()
    x = np.linspace(residuals.min(), residuals.max(), 100)
    y = stats.norm.pdf(x, mu, sigma)
    axes[1, 2].plot(x, y, 'r-', linewidth=2, 
                   label=f'N({mu:.2f}, {sigma:.2f}²)')
    axes[1, 2].legend()
    axes[1, 2].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 性能评估
    from sklearn.metrics import r2_score, mean_squared_error
    
    r2 = r2_score(y_test, y_pred_test)
    mse = mean_squared_error(y_test, y_pred_test)
    rmse = np.sqrt(mse)
    
    print("\n性能评估:")
    print(f"  R²分数: {r2:.6f}")
    print(f"  MSE: {mse:.6f}")
    print(f"  RMSE: {rmse:.6f}")
    
    # Z-Score参数
    print("\nZ-Score标准化参数:")
    print(f"  X: μ={scaler_X.mean[0]:.6f}, σ={scaler_X.std[0]:.6f}")
    print(f"  y: μ={scaler_y.mean[0]:.6f}, σ={scaler_y.std[0]:.6f}")
    
    # 数学验证
    print("\n数学验证:")
    print(f"  标准化后X的均值: {X_train_norm.mean():.10f} (应为0)")
    print(f"  标准化后X的标准差: {X_train_norm.std():.10f} (应为1)")
    print(f"  标准化后y的均值: {y_train_norm.mean():.10f} (应为0)")
    print(f"  标准化后y的标准差: {y_train_norm.std():.10f} (应为1)")

neural_network_with_zscore()

七、Z-Score标准化的优缺点

7.1 优点

  1. 保持分布形状:不改变数据的分布形态
  2. 对异常值相对稳健:异常值对均值和标准差的影响有限
  3. 无边界限制:适用于可能无限增长的数据
  4. 保持相关性:不改变特征间的相关系数
  5. 适合正态分布数据:能将正态分布转化为标准正态分布

7.2 缺点

  1. 要求近似正态分布:对于非正态分布数据效果不佳
  2. 仍受异常值影响:异常值会影响均值和标准差
  3. 不保证有界输出:结果可能超出[-3, 3]范围
  4. 需要保存参数:预测时需要原始均值和标准差

7.3 数学形式分析异常值影响

假设数据集 \(X = \{x_1, x_2, ..., x_n\}\),均值为 \(\mu\),标准差为 \(\sigma\)

添加一个异常值 \(x_{n+1} = k\)\(k\) 很大),则新均值和标准差:

\[\mu' = \frac{n\mu + k}{n+1} \approx \mu + \frac{k}{n} \quad (\text{当 } n \text{ 很大时}) \]

\[\sigma' = \sqrt{\frac{n}{n+1}\sigma^2 + \frac{(k - \mu')^2}{n+1}} \approx \sqrt{\sigma^2 + \frac{k^2}{n}} \quad (\text{当 } k \text{ 很大时}) \]

可见,异常值对标准差的影响比对均值的影响更大。

posted @ 2026-02-10 21:23  ffff5  阅读(50)  评论(0)    收藏  举报