系列:《从小白到 AI 大模型开发工程师的进阶之路》 技术点:AI-0303 特征缩放 主人公:小蓝伞 前置:AI-0302 训练集、验证集、测试集 本期产出:四种输入尺度的逻辑回归对照、离群值统计实验、训练集拟合与防泄漏实现
小蓝伞把两个量纲差异很大的特征送进逻辑回归。第一列标准差约为 0.92,第二列约为 111.67;固定数据划分和训练参数后,新版教学实现的未缩放输入准确率为 0.9306,标准化、MinMax 和 Robust 输入也为 0.9306,但四者的对数损失分别为 0.2161、0.1520、0.4014 和 0.1621。测试集参与缩放参数拟合会让评估失去独立性,而离群值又可能把均值、标准差、最小值和最大值拉偏。本文用两个可复现实验回答三个问题:哪些模型值得缩放、缩放器怎样选择、统计量应在哪个数据集上拟合。
一、小蓝伞遇到的问题
小蓝伞先看训练日志,发现优化器没有报错,损失也在下降,于是以为量纲差异不是问题。这个判断漏了两件事。第一,梯度下降能运行不代表收敛效率和概率质量一样;第二,只比较一份合成数据的准确率,不能证明缩放器对所有模型都有效。她需要固定随机划分、损失函数和迭代次数,隔离"尺度变化"这一个变量,再用单独的极端值样本解释不同统计量为何会偏移。
更容易被忽视的是预处理泄漏。若先对全量数据执行 fit_transform 再切分,测试集的均值、标准差、最小值或最大值已经进入训练流程。模型没有直接读取测试标签,仍然不代表评估独立,因为变换参数本身包含了测试分布信息。
二、先给结论
-
先判断模型机制。 距离、点积、梯度或正则化会受特征尺度影响时,比较缩放前后效果;基于阈值分裂的树模型通常不把缩放作为首要步骤。
-
无明显极端值时,以标准化作基线。
StandardScaler用训练均值和标准差转换特征,但不把数据变成正态分布。 -
需要指定数值范围时,比较 MinMax。 它把训练范围线性映射到目标区间,默认
[0, 1];测试值超出训练范围时,转换结果可能越界。 -
离群值明显时,比较 Robust。 它使用中位数和四分位距,降低极端值对中心和尺度估计的影响,但不判断离群值是否为错误数据。
-
先划分,再拟合。 缩放参数只由训练集学习;验证、测试和线上输入复用同一参数。交叉验证时把缩放器与模型放在同一个
Pipeline。
三、本篇任务卡
| 项目 | 本期设计 |
|---|---|
| 核心问题 | 特征尺度差异会怎样影响优化,缩放器该如何选择? |
| 目标读者 | 正在训练传统机器学习模型、容易把缩放当作固定模板的开发者 |
| 实验变量 | 原始尺度、Standard、MinMax、Robust;训练拟合与全量拟合统计量 |
| 固定条件 | 同一合成数据、样本划分、初始化、学习率、迭代次数和指标 |
| 成功判据 | 输出可复现;训练统计量可追溯;测试集不参与 fit |
| 不在范围 | 不把手写逻辑回归性能推广到 scikit-learn 全部算法或生产业务 |
四、缩放究竟改变了什么
4.1 三种变换的参数从哪里来
对特征第 j 列,标准化可写为:
z_j = (x_j - mean_train_j) / std_train_j
这里的均值和标准差只由训练样本计算。标准化让训练列的均值接近 0、标准差接近 1;它不保证正态分布,也不限制取值范围。若某列在训练数据中为常数,标准差为 0,库实现需要定义保护行为,应用时应检查常量列。
MinMax 变换为:
x'_j = (x_j - min_train_j) / (max_train_j - min_train_j)
结果只保证训练集的最小值和最大值映射到目标区间。新样本比训练最大值更大时,变换值可以超过 1;是否裁剪是部署策略,不是 MinMax 自动解决的分布漂移。
Robust 变换通常按以下形式计算:
x'_j = (x_j - median_train_j) / IQR_train_j
IQR = Q3 - Q1
中位数和四分位距比均值、标准差更不容易被少数极端值支配。它仍会保留极端样本的相对差异,也不会替代异常原因调查。
4.2 距离模型为什么敏感
KNN 的欧氏距离为 sqrt(sum((x_j - y_j)^2))。若某一列范围在数百,另一列范围在个位数,平方和通常由大尺度列主导。缩放后距离权重改变,所以近邻集合可能改变。
RBF 核 SVM 使用 exp(-gamma * ||x-y||^2);距离尺度变化会改变核值分布。如果继续使用同一个 gamma,模型实际感受到的相似度范围也会改变。缩放和 gamma 调参应在训练/验证流程内共同完成,不能先看测试分数再反复挑选。
4.3 梯度下降为什么敏感
逻辑回归的线性部分是 w0 + w1*x1 + w2*x2。若 x2 的尺度远大于 x1,损失对 w2 的梯度可能大很多。固定学习率时,优化器可能沿陡峭方向来回震荡,同时沿平缓方向前进很慢。缩放把坐标尺度拉近后,等高线通常更接近圆形,梯度下降更容易使用统一步长。
缩放不会凭空增加信息。若问题本来由数据噪声、标签错误或错误切分造成,缩放不能修复根因。
4.4 正则化的相对意义会变化
L1/L2 正则化直接惩罚权重大小。特征单位变化时,同一个预测函数所需的权重数值会变;固定正则化系数于是对不同特征施加不同的有效约束。缩放后再调正则化超参数,模型比较才有清楚的解释。不要把"缩放提高分数"单独归因于优化变快,正则化和超参数搜索也可能同时影响结果。
树模型通常按单个特征的排序位置选择切分阈值。严格单调的线性缩放一般保留该列的排序,所以许多树模型不需要标准化。极端精度、量化、稀疏表示或特定实现仍可能带来差异,最终以当前算法和库版本验证为准。
五、实验一:四种输入尺度的逻辑回归对照
实验固定种子 81,构造 240 个样本,其中 168 个训练、72 个测试。第一列服从标准差约为 1 的正态分布,第二列标准差设置为 100;标签由两列的线性组合加噪声生成。每种变换都只从训练样本拟合统计量,再应用到测试数据。优化器、学习率、训练轮数、初始化和数据划分完全相同。
将下列代码保存为 main.py。环境为 Windows 11、Python 3.12.14、NumPy 2.3.5;本实验未调用 scikit-learn,不把手写逻辑回归结果写成该库实测。
import numpy as np
rng = np.random.default_rng(81)
n = 240
x1 = rng.normal(size=n)
x2 = rng.normal(scale=100, size=n)
y = (x1 + 0.015 * x2 + rng.normal(scale=0.35, size=n) > 0).astype(float)
indices = np.arange(n)
rng.shuffle(indices)
train_indices, test_indices = indices[:168], indices[168:]
X = np.c_[x1, x2]
def scale_from_train(X_train, X_all, method):
"""仅用训练行拟合参数,再转换全部行。"""
if method == "standard":
center = X_train.mean(axis=0)
spread = X_train.std(axis=0)
elif method == "minmax":
center = X_train.min(axis=0)
spread = X_train.max(axis=0) - center
elif method == "robust":
center = np.median(X_train, axis=0)
quartiles = np.percentile(X_train, [25, 75], axis=0)
spread = quartiles[1] - quartiles[0]
elif method == "raw":
return X_all.copy(), None, None
else:
raise ValueError(f"unknown method: {method}")
spread = np.where(spread == 0, 1.0, spread)
return (X_all - center) / spread, center, spread
def evaluate(X_train, X_test, y_train, y_test):
"""训练同一逻辑回归教学实现,返回准确率和对数损失。"""
train_matrix = np.c_[np.ones(len(X_train)), X_train]
test_matrix = np.c_[np.ones(len(X_test)), X_test]
weights = np.zeros(train_matrix.shape[1])
for _ in range(3000):
probability = 1 / (1 + np.exp(-np.clip(train_matrix @ weights, -40, 40)))
gradient = train_matrix.T @ (probability - y_train) / len(y_train)
weights -= 0.08 * gradient
probability = 1 / (1 + np.exp(-np.clip(test_matrix @ weights, -40, 40)))
accuracy = float(((probability >= 0.5) == y_test).mean())
loss = float(-(
y_test * np.log(probability + 1e-9)
+ (1 - y_test) * np.log(1 - probability + 1e-9)
).mean())
return accuracy, loss
X_train = X[train_indices]
y_train = y[train_indices]
y_test = y[test_indices]
for method in ("raw", "standard", "minmax", "robust"):
X_scaled, center, spread = scale_from_train(X_train, X, method)
result = evaluate(
X_scaled[train_indices], X_scaled[test_indices], y_train, y_test
)
print(method, result)
if method == "standard":
print("train_mean", center.tolist())
print("train_std", spread.tolist())
print("all_data_mean_do_not_fit", X.mean(axis=0).tolist())
实际输出:
raw (0.9305555555555556, 0.2160760373373203)
standard (0.9305555555555556, 0.15198105386196034)
minmax (0.9305555555555556, 0.40137888604626726)
robust (0.9305555555555556, 0.1620680646603428)
train_mean [0.08294656121764418, -5.624243745092431]
train_std [0.9248892548263401, 111.66800925009704]
all_data_mean_do_not_fit [0.12575450637482288, -8.497416928721448]
这组结果里,四种输入的准确率相同,但损失不同。准确率只统计阈值 0.5 下预测对错,损失还会惩罚置信度差的错误,因此"分类边界给出同样数量的正确样本"不代表模型概率输出一样。当前手写优化器下,Standard 和 Robust 的损失较低;这是特定种子、数据、学习率和损失函数的实验结果,不是缩放方法排名。
六、实验二:离群值怎样扭曲统计量
使用一列数据 [1, 2, 3, 4, 5, 100]。前五个数集中在 1 到 5,最后一个数代表极端观测。全部六个数的均值为 19.1667、标准差为 36.1728;中位数为 3.5、四分位距为 2.5。若去掉 100,均值变为 3、标准差变为 1.4142。这个小例子说明均值和标准差对极端值敏感,不能证明 100 一定是错误数据。
MinMax 的范围会被 100 拉到 1 到 100,原本 1 到 5 的样本都挤在窄区间。Robust 用中位数和四分位距,不会让中心与尺度被这个点大幅拉走,但转换后的极端值仍然很大。处理前应先区分真实稀有事件、测量故障和数据录入错误;三者对应保留、修复或业务规则处理,不能统一删除。
七、故障复盘:全量拟合会偷看测试分布
假设训练特征只有 [0, 1, 2],测试特征是 [100]。训练均值为 1;若先在全量数据拟合,均值变为 25.75。测试样本没有提供标签,仍然影响了训练阶段使用的变换参数。这样评估回答的就不是"只根据训练数据学到的流程能否泛化"。
错误写法:
X_scaled = scaler.fit_transform(X) # 测试统计量进入拟合
X_train, X_test = split(X_scaled)
正确顺序:
X_train, X_test = split(X)
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
在交叉验证里,如果缩放器在交叉验证前拟合,验证折的信息也会泄漏到该折训练过程。scikit-learn 官方推荐把预处理放进 Pipeline,使每个折独立拟合训练部分,再转换验证部分。本文没有在环境中安装并运行 scikit-learn;此 API 建议依据官方文档核对。
八、缩放方法选择表
| 数据或模型条件 | 候选做法 | 重点检查 |
|---|---|---|
| 线性模型、SVM、KNN、PCA,分布无明显极端值 | 先试 Standard | 标准差为零的常量列、正则化参数 |
| 网络输入或业务约定要求固定范围 | 试 MinMax | 训练外新值是否越界、是否裁剪 |
| 离群值存在且有业务意义 | 比较 Robust | 中位数/IQR 是否稳定,极端值是否需监控 |
| 稀疏矩阵 | 避免默认中心化 | 例如 Standard 的 with_mean=False,否则可能破坏稀疏性 |
| 决策树、随机森林等阈值模型 | 先建立不缩放基线 | 以当前实现和数据实测,不套用距离模型结论 |
不平衡类别、缺失值填充、特征选择和缩放的顺序也应放进同一训练流水线。只有训练阶段需要估计参数的变换才存在拟合边界;固定单位换算通常不估计数据分布,但若换算规则是在观察测试数据后挑选,仍可能产生选择泄漏。
九、常见问题与避坑
标准化会让数据变成正态分布吗?
不会。它只线性平移、缩放训练列的均值和标准差。偏态分布经过标准化仍可能偏态,异常值也仍存在。若模型依赖分布形态,应使用适合的变换并在训练折内部拟合。
MinMax 后测试数据为什么大于 1?
映射边界来自训练集。新样本超过训练最大值时自然会越界。这可能意味着正常扩展,也可能意味着漂移或单位错误。先监控并排查原因,再决定裁剪、重训或拒绝输入。
为什么树模型通常不需要缩放?
严格单调变换会保留单列样本次序,许多树分裂候选由排序决定,因此结构常不变。浮点精度、直方图分箱和实现细节仍可能改变结果,所以"通常不需要"不是绝对保证。
稀疏数据能否减均值?
均值中心化会把大量零变成非零,可能令稀疏矩阵变成稠密矩阵,导致内存骤增。稀疏输入应选择保留稀疏性的变换或关闭中心化,并在目标库当前版本确认参数行为。
十、工程验收清单
-
数据划分在任何有统计拟合的预处理之前完成。
-
缩放器只在训练数据拟合;测试和生产输入只转换。
-
预处理、特征顺序、单位、缺失值策略与模型共同版本化。
-
通过同一数据划分、随机种子和指标公平比较缩放策略。
-
检查常量列、离群值、稀疏格式、测试越界和线上分布漂移。
-
交叉验证时,把缩放器和模型绑定在同一
Pipeline。
十一、面试题与追问
-
标准化是否会改变样本顺序? 单列正标准差缩放是严格单调变换,不改变该列排序;但距离和点积会变。
-
缩放是否改变逻辑回归函数族? 对无约束线性模型,线性可逆缩放可通过重新参数化表达等价预测函数;固定正则化和有限步优化会让实际训练结果不同。
-
准确率相同为什么损失不同? 阈值决策相同不代表预测概率相同,对数损失衡量置信度质量。
-
RobustScaler 是否能修复脏数据? 不能,它只用稳健统计量设定中心和尺度,不识别错误记录。
-
固定随机种子为什么挡不住泄漏? 种子控制随机抽样,不控制预处理拟合时看到了哪些行。
十二、工程金句与下一期
工程金句:缩放器学到的不是格式,而是训练数据的统计边界;边界一旦偷看测试集,评估就失去独立性。
这句话来自本期训练均值与全量均值的实测对照。它提醒我们,预处理步骤也有信息边界,不能因为"没用标签"就忽略测试数据。
下一期是 AI-0304「类别特征编码」,继续处理训练阶段学到的参数如何应对线上未知类别。
官方资料
-
Preprocessing data:Standard、MinMax、Robust 等转换及注意事项,访问日期 2026-10-29。
-
StandardScaler:均值、标准差和异常值敏感性,访问日期 2026-10-29。
-
MinMaxScaler:范围映射和训练范围外输入,访问日期 2026-10-29。
-
Common pitfalls:预处理泄漏和 Pipeline 建议,访问日期 2026-10-29。
-
Pipelines and composite estimators:交叉验证中组合变换和估计器,访问日期 2026-10-29。
适用边界
数值实验只覆盖合成二分类数据、固定迭代次数的 NumPy 教学实现和已记录环境。本文不声称三种缩放在其他数据集上效果相同,也未执行 scikit-learn 代码兼容性测试。生产使用还需验证漂移、稀疏内存、训练工件、输入 schema、模型回滚和业务指标。