Python 现代开发学习笔记

(十六)用训练数据建立标准化尺度

1. 每一列分别减均值、除标准差

自拟三条训练记录的前两列为 [10,20,30] 与 [100,200,300],第三列恒为 7。前两列表达相同比例的变化,数值尺度却差十倍。标准化让每列先减自身均值,再除以自身标准差:

zij=xij−μjsj.z_{ij}=\frac{x_{ij}-\mu_j}{s_j}.

下标 i 表示记录,j 表示特征列。均值和尺度都沿记录轴求出,保留 shape (1,特征数),然后向所有记录广播。

# 项目文件:analysis_project/src/readingarray/scaling.py
# 训练集算出的均值与标准差保存下来,新数据只复用这份统计量。
from dataclasses import dataclass

import numpy as np


@dataclass
class Standardizer:
    mean: np.ndarray
    scale: np.ndarray

    @classmethod
    def fit(cls, values):
        values = np.asarray(values, dtype=np.float64)
        if values.ndim != 2 or values.shape[0] == 0 or values.shape[1] == 0:
            raise ValueError("训练数据应为有行有列的二维数组")
        if not np.isfinite(values).all():
            raise ValueError("先处理训练数据中的缺失值与无穷值")
        mean = values.mean(axis=0, keepdims=True)
        scale = values.std(axis=0, keepdims=True)
        # 常量列除以 1:训练集该列减去均值后为 0,不制造除零结果。
        scale = np.where(scale == 0, 1.0, scale)
        return cls(mean, scale)

    def transform(self, values):
        values = np.asarray(values, dtype=np.float64)
        if values.ndim != 2 or values.shape[1] != self.mean.shape[1]:
            raise ValueError("新数据的列数应与训练数据一致")
        if not np.isfinite(values).all():
            raise ValueError("先处理新数据中的缺失值与无穷值")
        return (values - self.mean) / self.scale

    def inverse_transform(self, values):
        return np.asarray(values, dtype=np.float64) * self.scale + self.mean

均值为 [20,200,7]。第一列减去均值后为 [−10,0,10],平方偏差平均为 (100+0+100)/3,开平方得到标准差约 8.164966;第二列的偏差放大十倍,标准差也变成约 81.649658。每列再除以自身标准差,得到相同的 [−1.224745,0,1.224745]。第三列标准差为 0,代码将除数设为 1,训练集该列减去均值后全为 0。

训练集按列得到均值和尺度,保留一乘三的形状,减均值除尺度后各行逐项得到标准化结果

2. 新数据沿用已经建立的尺度

# 项目文件:demos/standardize.py
# 每列由训练集建立尺度;新记录沿同一尺度变换,不重新计算自己的均值。
import numpy as np
from readingarray.scaling import Standardizer

train = np.array([[10, 100, 7], [20, 200, 7], [30, 300, 7]], dtype=np.float64)
scaler = Standardizer.fit(train)
new = np.array([[40, 400, 7]])
print("均值", scaler.mean.tolist())
print("尺度", scaler.scale.round(6).tolist())
print("训练结果", scaler.transform(train).round(6).tolist())
print("新记录", scaler.transform(new).round(6).tolist())
print("还原", scaler.inverse_transform(scaler.transform(new)).round(6).tolist())

新记录 [40,400,7] 变为约 [2.449490,2.449490,0],表示前两列都比训练均值高约 2.45 个训练标准差。inverse_transform 按相反次序乘回尺度、加回均值,恢复原记录。

若对这条新记录自己重新计算均值,它每列都只剩一个数,居中后全变为 0,已经失去“相对训练记录处于什么位置”的信息。因此先划分训练与后续评估记录,只让训练集参与 fit,再让两者调用同一份 transform;保存模型时也一起保存这份尺度。

笔记正文与全部题解 · Ctrl / ⌘ K