数值特征缩放方法权威指南:原理、场景与局限
数值特征工程在机器学习模型训练中属于不可或缺的预处理步骤。处理数值数据时,特征尺度差异与异常值是两个核心难点。以年龄和薪资为例,两者的数值范围相差数个数量级——若不进行标准化处理,模型往往倾向于给数值较大的特征(如薪资)分配更高权重,从而忽略年龄这类关键信息。
偏斜分布则是另一项常见挑战。许多特征的值集中在狭窄区间内,却夹杂着少量极端值。例如“兄弟姐妹数量”这一特征,绝大多数样本落在0到2之间,偶尔出现的8或10会严重扭曲整体分布。直接剔除这些极端样本虽简单,但多数情况下这些异常值承载着真实业务信息,不应简单丢弃。
解决上述问题的常用方法包括四种:标准化(Standardization)、Robust缩放(Robust Scaler)、幂变换(Power Transformer)、归一化(Normalization)。接下来借助scikit-learn内置的California住房数据集进行实操演示,重点关注“Median Income”和“Population”这两个量级截然不同的特征:
dataset = fetch_california_housing()
X_full, y_full = dataset.data, dataset.target
feature_names = dataset.feature_names
df = pd.DataFrame({
"MedInc": X[:, 0],
"Population": X[:, 4],
})
df.describe()
+---------+------------+-------------+
| Metric | MedInc | Population |
+---------+------------+-------------+
| count | 20640 | 20649 |
| mean | 3.870671 | 1425.476744 |
| std | 1.899822 | 1132.462122 |
| min | 0.499900 | 3 |
| 25% | 2.5634 | 787 |
| 50% | 3.5348 | 1166 |
| 75% | 4.743250 | 1725 |
| max | 15.0001 | 35682 |
+---------+------------+-------------+
首先观察原始数据(未经任何缩放或变换)的散点图,分别展示包含异常值以及剔除异常值(仅保留第0至第99百分位)后两种情形:
X = X_full[:, [0,4]]
outlier_range = (0, 99)
cutoffs_median_inc = np.percentile(X[:, 0], outlier_range)
cutoffs_population = np.percentile(X[:, 1], outlier_range)
non_outliers = np.all(X > [cutoffs_median_inc[0], cutoffs_population[0]], axis=1) & np.all(
X < [cutoffs_median_inc[1], cutoffs_population[1]], axis=1
)
non_outlier_X = X[non_outliers]
non_outliers_Y = y_full[non_outliers]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
fig.suptitle('Original Data')
ax1.set_title('Full Data')
ax1.scatter(X[:, 0], X[:, 1], c=y_full)
ax1.set_xlabel('MedInc')
ax1.set_ylabel('Population')
ax2.set_title('Non-outlier Data')
ax2.scatter(non_outlier_X[:, 0], non_outlier_X[:, 1], c=non_outliers_Y)
ax2.set_xlabel('MedInc')
ax2.set_ylabel('Population')
plt.show()
接下来逐一演示上述四种技术如何改变数据形态。
标准化(Standardization)
标准化的核心目标是将数值特征调整到均值为0、方差为1的尺度。举例来说,年龄相差10岁,收入却可能相差50k——对模型而言,收入的信号强度远超年龄。通过标准化,所有特征被映射到统一的均值和方差上,从而具备可比性。
z分数的计算公式为:
z = ( x — mean ) / standard_deviation.
标准化后的特征更适合那些假设输入分布接近正态的算法,例如线性回归、逻辑回归、支持向量机、PCA等降维方法。Scikit-learn中对应的实现是StandardScaler:
standard_scaler = StandardScaler()
standardized_x = standard_scaler.fit_transform(X)
原始数据中Population的取值范围是0到35k,MedInc为0到14。标准化后两者分别缩放到[0,35]和[-2,6]区间,量级上已可比较。但标准化有一个显著缺陷:对异常值极其敏感。从图中可见,最大值虽从35k压缩到约30,但由于异常值拉高了均值,大部分数据被挤压到[-1,4]的狭窄区间内。换言之,标准化仅改变数值尺度,并不改变分布形状——数据原本偏斜,标准化后依然偏斜。不过两个特征的主体数据确实落入了可比较的范围:MedInc集中在[-2,4],Population集中在[-1,4]。
Robust缩放(Robust Scaler)
RobustScaler是标准化的变体,其核心差异在于使用中位数和四分位距(IQR,通常取第25至第75百分位)替代均值和标准差。标准化面对极端异常值时,均值会被拉高、方差被放大,导致缩放效果大打折扣。而IQR只关注中间50%的数据,少数极端值对其影响微乎其微。异常值本身不会被移除,特征中仍保留着极端样本,但主体数据会落在更合理的区间内。
roubust_scaler = RobustScaler(quantile_range=(25.0,75.0),
with_scaling=True, with_centering=True, unit_variance=True)
robust_x = roubust_scaler.fit_transform(X)
默认分位数范围(25,75)意味着两端各忽略25%的极端数据,这正是“鲁棒”二字的由来。
图中显示,两个特征的主体数据落在相近区间:MedInc在[-2,5],Population在[-2,6]。与标准化相比,Robust缩放对异常值的抵抗能力更强,但本质上仍是线性变换——无法根除异常值带来的分布偏斜。要解决这一问题,需要引入非线性变换,如对数变换、幂变换、分位数变换等。
幂变换(Power Transformer)
收入、房价等现实数据通常呈现一个共同模式:大量值集中在较低区间,同时存在少数极大的异常值。线性回归或逻辑回归试图找到一条使所有数据点距离最小的拟合线,一个极端异常值就像跷跷板一端的重物,足以将整条线拽偏,破坏对其余样本的拟合。神经网络虽对数据形态容忍度较高,但某个极端值在单次训练步中产生的梯度冲击,结合较大的学习率,同样可能引发损失曲面的剧烈震荡。
PowerTransformer通过压缩分布的长尾,将异常值拉近数据主体,将偏斜分布整形为接近钟形曲线。异常值的信息得以保留,但不再以极端数值扭曲模型。Scikit-learn中除了PowerTransformer,QuantileTransformer也可达到类似效果。
先用箱线图直观展现Population特征的长尾:
plt.figure(figsize=(12, 4))
sns.boxplot(x=df['Population'], color='skyblue')
plt.title('Box Plot of Population (Visualizing Outliers)')
plt.xlabel('Population Value')
plt.axvline(1425, color='orange', linestyle='--', label='Mean: 1425')
plt.legend()
plt.show()
箱体对应数据主体(四分位距范围),右侧那一长串散点正是可能“掀翻跷跷板”的极端Population值。
对Population应用PowerTransformer:
from sklearn.preprocessing import PowerTransformer
pt = PowerTransformer(method='yeo-johnson')
pt_transformed = pt.fit_transform(X[:,[1]])
绘制变换前后的直方图对比:
import matplotlib.pyplot as plt
import seaborn as sns
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
sns.histplot(standardized_x[:,1], ax=ax1)
ax1.set_title("Before: Standardized Population (Skewed)")
ax1.set_xlabel("Value")
sns.histplot(pt_transformed[:,0], ax=ax2)
ax2.set_title("After: PowerTransformed Population (Normal-like)")
ax2.set_xlabel("Transformed Value")
plt.tight_layout()
plt.show()
效果一目了然:PowerTransformer成功将原本右偏的分布变换为接近正态的形状。
再看变换前后的箱线图:标准化后的数据主体仍然偏左,长尾向右延伸;PowerTransformer处理后,箱体居中,两侧须线基本对称。
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
sns.boxplot(x=standardized_x[:,1], ax=ax1)
ax1.set_title('Standardized Populationn(Scale changed, outliers remain)', fontsize=13)
ax1.set_xlabel('Z-Score')
sns.boxplot(x=pt_transformed[:,0], ax=ax2)
ax2.set_title('PowerTransformed Populationn(Shape changed uniform tails)', fontsize=13)
ax2.set_xlabel('Transformed Value')
plt.tight_layout()
plt.show()
左图(标准化)中数值分布跨度很大。线性回归一旦在异常值上产生错误预测,平方误差会被放大到极端量级,拟合线被迫朝异常值方向偏移,整体拟合质量随之下降。右图(幂变换)中异常值与主体数据的距离大幅缩短,误差分布更加均匀,模型得以将注意力集中在数据主体上。
对比标准化后的MedianInc和经过PowerTransformer处理的Population:
与标准化不同,异常值被拉入数据主体的邻域。数据分布均匀,没有被压缩到狭窄区间。在MedianInc最高的第6档中,Population的取值分散在[-4, 2]之间,模型能够捕捉到这些细微差异并发现特征间的关联。
归一化(Normalization)
归一化将所有数据重新缩放到0-1的范围。KNN等基于距离的算法对数值绝对大小敏感,归一化对这类算法尤为重要。在神经网络中,归一化还能缓解梯度消失问题。较大的输入值(例如年龄为99)容易使激活函数进入饱和区,梯度趋近于零,权重不再更新,学习就此停滞。将输入缩放到0-1之间,恰好落在多数激活函数的敏感区域内。
最常用的归一化方法是Min-Max缩放,公式为:
x_norm = (x — x_min) / (x_max — x_min).
Min-Max缩放有一个致命弱点:一旦出现极端异常值——例如某人的收入为10亿美元——它会被映射为1,其余所有正常值全部被压缩到接近0的微小区间内,数据中的细微差异被彻底抹平。看看归一化对当前数据集的实际效果:
min_max_scaler = MinMaxScaler()
nomralize_x = min_max_scaler.fit_transform(X)
Min-Max缩放将Population的最大值映射为1.0,而几乎全部数据都被挤压到0-0.16的区间内。回顾前面的箱线图——Population最大值达到35k,数据主体集中在1000-2000之间。35k被映射为1后,1000-2000这个区间仅能占据1/35的宽度,有意义的分辨率荡然无存。归一化最适合的场景是输入特征的边界已知且固定,典型例子是RGB图像的像素值——取值范围恒定在0-255。
下表汇总了四种缩放器的适用场景:
.----------------------.---------------------------.-------------------------------------------------------.
| Issue | Best Tool | Why? |
:----------------------+---------------------------+-------------------------------------------------------:
| Different Scales | StandardScaler | Makes features comparable. |
:----------------------+---------------------------+-------------------------------------------------------:
| Hea vy Skew | Power/QuantileTransformer | Normalizes the distribution shape. |
:----------------------+---------------------------+-------------------------------------------------------:
| Extreme Outliers | RobustScaler | Uses Median and IQR, unaffected by marginal outliers. |
:----------------------+---------------------------+-------------------------------------------------------:
| Neural Network Input | Min-Max Scaler | Matches the "expected" range of neurons. |
'----------------------'---------------------------'-------------------------------------------------------'
使用这些缩放器时有一条铁律:fit() 只在训练数据上调用。fit():计算统计量(均值、标准差等),只能在训练集上执行。transform():用已有的统计量做变换,训练集、测试集、线上数据都要执行。如果在测试集上调用了fit,等于模型提前“看到”了测试数据的分布——这就是数据泄露。部署模型时,缩放器的参数也必须一起打包上线。





