Z-Score归一化
Z-Score标准化:详细数学与实现笔记
一、数学原理与推导
1.1 基本公式
Z-Score标准化(标准差标准化)将数据转换为均值为0、标准差为1的标准正态分布:
其中:
- \(\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个特征):
其中:
- \(\mu\):每列的均值向量 \(\in \mathbb{R}^{1 \times n}\)
- \(\sigma\):每列的标准差向量 \(\in \mathbb{R}^{1 \times n}\)
- 运算为广播操作
二、数学性质分析
2.1 变换性质
设 \(T(x) = \frac{x - \mu}{\sigma}\),则:
- 线性变换:\(T(ax + b) = a'T(x) + b'\),其中 \(a' = \frac{a}{\sigma}\),\(b' = \frac{b}{\sigma}\)
- 保持分布形状:若 \(X \sim N(\mu, \sigma^2)\),则 \(Z \sim N(0, 1)\)
- 无量纲化:消除不同特征间的量纲影响
2.2 统计特性证明
定理:对于任意数据集 \(X = \{x_1, x_2, ..., x_n\}\),经Z-Score标准化后得到 \(Z = \{z_1, z_2, ..., z_n\}\),则:
- \(\bar{z} = 0\)
- \(s_z = 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 \] -
标准差:
\[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\),则相关系数保持不变:
证明:
相关系数定义为:
标准化后:
计算协方差:
而 \(\sigma_{Z_X} = \sigma_{Z_Y} = 1\),所以:
5.2 Z-Score标准化与线性变换
定理:Z-Score标准化是线性变换。
证明:
设 \(T(X) = \frac{X - \mu_X}{\sigma_X}\),对于任意 \(a, b \in \mathbb{R}\),有:
已知:
- \(\mu_{aX+b} = a\mu_X + b\)
- \(\sigma_{aX+b} = |a|\sigma_X\)
所以:
因此,Z-Score标准化是保持方向的线性变换。
5.3 Z-Score与Mahalanobis距离
对于多元数据 \(X \in \mathbb{R}^{m \times n}\),Mahalanobis距离定义为:
其中 \(\Sigma\) 是协方差矩阵。
当各特征不相关时,\(\Sigma\) 是对角矩阵,对角线元素为 \(\sigma_i^2\),则:
因此,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 优点
- 保持分布形状:不改变数据的分布形态
- 对异常值相对稳健:异常值对均值和标准差的影响有限
- 无边界限制:适用于可能无限增长的数据
- 保持相关性:不改变特征间的相关系数
- 适合正态分布数据:能将正态分布转化为标准正态分布
7.2 缺点
- 要求近似正态分布:对于非正态分布数据效果不佳
- 仍受异常值影响:异常值会影响均值和标准差
- 不保证有界输出:结果可能超出[-3, 3]范围
- 需要保存参数:预测时需要原始均值和标准差
7.3 数学形式分析异常值影响
假设数据集 \(X = \{x_1, x_2, ..., x_n\}\),均值为 \(\mu\),标准差为 \(\sigma\)。
添加一个异常值 \(x_{n+1} = k\)(\(k\) 很大),则新均值和标准差:
可见,异常值对标准差的影响比对均值的影响更大。
本文来自博客园,作者:ffff5,转载请注明原文链接:https://www.cnblogs.com/ffff5/p/19601539

浙公网安备 33010602011771号