找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4245

积分

0

好友

582

主题
发表于 1 小时前 | 查看: 1| 回复: 0

数值特征工程是机器学习模型训练中不可跳过的预处理环节。面对原始数据时,我们常常需要解决两个核心问题:特征的量纲差异和无处不在的异常值。

举个例子,年龄和薪资这两个特征,数值范围可能差了好几个数量级。如果不做任何处理,模型很可能仅仅因为薪资的数字更大,就错误地为其分配更高的权重,而完全忽略了年龄所蕴含的信息。

另一个常见问题是偏斜分布。许多特征的值会集中在一个很小的范围内,但同时存在少量极端值。比如一个表示“兄弟姐妹数量”的特征,绝大多数样本的值在0-2之间,但偶尔出现的8或10,会严重拉偏整个数据分布。有时我们可以直接丢弃这些极端样本,但在多数情况下,它们可能携带了真实且重要的信息,不能粗暴删除。

针对这些问题,数据预处理中常用到四种缩放方法:标准化(Standardization)、Robust缩放(Robust Scaler)、幂变换(Power Transformer)和归一化(Normalization)。接下来,我们用 Python 的 scikit-learn 内置的 California 住房数据集来逐一演示。我们将选取“Median Income”和“Population”这两个量级差异明显的特征进行分析。

from sklearn.datasets import fetch_california_housing
import pandas as pd

dataset = fetch_california_housing()
X_full, y_full = dataset.data, dataset.target
feature_names = dataset.feature_names
df = pd.DataFrame({
    “MedInc”: X_full[:, 0],
    “Population”: X_full[:, 4],
})
df.describe()

以下是该数据集部分特征的统计摘要:

+---------+------------+-------------+
| Metric  |   MedInc   | Population  |
+---------+------------+-------------+
| count   |     20640  |       20640 |
| 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百分位)后的散点图。

import numpy as np
import matplotlib.pyplot as plt

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, cmap=‘viridis’)
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, cmap=‘viridis’)
ax2.set_xlabel(‘MedInc’)
ax2.set_ylabel(‘Population’)
plt.show()

原始数据与剔除异常值后的数据对比

从图中可以直观看到,Population 特征中存在大量远离主体的高值点,这就是典型的异常值。

下面,我们分别来看看这四种技术如何变换数据,以及它们各自的适用场景。

标准化(Standardization)

标准化的目标是把数值特征变换到零均值、单位方差的尺度上。试想一下,年龄相差10岁,收入相差5万,这对于模型来说,收入的“信号”远比年龄“响亮”得多。标准化通过公式 $z = (x — \mu) / \sigma$(其中 $\mu$ 是均值,$\sigma$ 是标准差)将所有特征映射到均值为0、标准差为1的分布中,使得不同特征的数值落到同一个可比较的区间内。

标准化后的特征,与那些对输入分布有正态假设的算法契合度较高,例如线性回归、逻辑回归、支持向量机(SVM)以及主成分分析(PCA)等降维方法。

在 scikit-learn 中,对应的实现是 StandardScaler

from sklearn.preprocessing import StandardScaler

standard_scaler = StandardScaler()
standardized_x = standard_scaler.fit_transform(X)

标准化数据前后对比

从结果看,原始数据中 Population 的范围在0到35k,MedInc 在0到14。标准化后,两个特征的主体数据落到了可比较的范围:MedInc 大约在 [-2, 4],Population 大约在 [-1, 4]。

然而,标准化有一个明显的弱点:它对异常值极其敏感。从上图可以看出,最大值虽然被缩放了,但异常值的存在拉高了整体均值,导致大部分数据被挤压到一个相对狭窄的区间内。标准化只改变数值的尺度,不改变分布的形状。数据原本是偏斜的,标准化之后依然偏斜。如果你正在处理涉及大量数据的项目,或者对模型效率有更高要求,可以关注我们社区 云栈社区 中关于计算力与数据处理的讨论。

Robust 缩放(Robust Scaler)

RobustScaler 可以看作是标准化的一个“鲁棒”变体。它的核心区别在于,它使用中位数代替均值,使用四分位距(IQR,即第75百分位数减去第25百分位数)代替标准差。标准化在面对极端异常值时会被“带偏”,而 IQR 只关注中间50%的数据,少数极端值对它几乎没有影响。注意,异常值本身并不会被移除,但它们对缩放中心与范围的影响被降到了最低。

from sklearn.preprocessing import RobustScaler

robust_scaler = RobustScaler(quantile_range=(25.0,75.0),
    with_scaling=True, with_centering=True, unit_variance=True)
robust_x = robust_scaler.fit_transform(X)

默认的分位数范围 (25, 75) 意味着我们忽略了两端各25%的极端数据,这正是“鲁棒”一词的来源。

Robust缩放效果图

从图中可以看到,两个特征的主体数据落在了更合理且相近的区间:MedInc 在 [-2, 5],Population 在 [-2, 6]。

StandardScalerRobustScaler 都能把特征拉到可比较的尺度上,但它们都无法根本性地消除异常值带来的分布偏斜。要解决形状问题,我们需要引入非线性变换。

幂变换(Power Transformer)

现实世界中的数据,如收入、房价,常常呈现一种“重尾”分布:大量值集中在较低区间,同时存在少数极大的异常值。在线性模型中,一个极端异常值就像跷跷板一端的重物,足以把整条拟合线拽偏。即使在神经网络中,单个极端值带来的梯度冲击也可能引发训练的不稳定。

PowerTransformer 的做法是压缩分布的长尾,将异常值拉近数据主体,从而把偏斜分布整形为接近正态分布的形状。异常值的信息得以保留,但它们不再以极端的数值扭曲模型。在 scikit-learn 中,QuantileTransformer 也能达到类似效果。

我们先通过箱线图直观感受一下 Population 特征的“长尾”。

import seaborn as sns

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特征的箱线图,展示异常值

箱体对应的是数据主体(IQR范围),右侧那一长串散点就是可能“掀翻跷跷板”的极端 Population 值。现在,我们对 Population 应用 PowerTransformer

from sklearn.preprocessing import PowerTransformer

pt = PowerTransformer(method=‘yeo-johnson’)
pt_transformed = pt.fit_transform(X[:,[1]])

绘制变换前后的直方图对比:

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
sns.histplot(standardized_x[:,1], ax=ax1, kde=True)
ax1.set_title(“Before: Standardized Population (Skewed)”)
ax1.set_xlabel(“Value”)
sns.histplot(pt_transformed[:,0], ax=ax2, kde=True)
ax2.set_title(“After: PowerTransformed Population (Normal-like)”)
ax2.set_xlabel(“Transformed Value”)
plt.tight_layout()
plt.show()

幂变换前后直方图对比

效果非常明显!PowerTransformer 把原本严重右偏的分布变换成了接近对称的钟形曲线。我们再看变换前后的箱线图对比:

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
sns.boxplot(x=standardized_x[:,1], ax=ax1)
ax1.set_title(‘Standardized Population\n(Scale changed, outliers remain)’, fontsize=13)
ax1.set_xlabel(‘Z-Score’)
sns.boxplot(x=pt_transformed[:,0], ax=ax2)
ax2.set_title(‘PowerTransformed Population\n(Shape changed uniform tails)’, fontsize=13)
ax2.set_xlabel(‘Transformed Value’)
plt.tight_layout()
plt.show()

幂变换前后箱线图对比

左图(标准化后)中,数据分布跨度依然很大,异常值明显。右图(幂变换后),箱体基本居中,两侧须线变得对称。这意味着,在线性回归等模型中,单个极端异常值带来的巨大平方误差被显著平滑,模型可以更专注于拟合数据的主体部分。

归一化(Normalization)

归一化通常指将所有数据重新缩放到 [0, 1] 的固定范围内。这对于 K 近邻(KNN)、支持向量机(使用RBF核)等基于距离的算法至关重要,因为特征的绝对大小会直接影响距离计算。在神经网络中,归一化还能帮助缓解梯度消失问题,将输入值控制在多数激活函数的敏感区间内。

最常用的方法是 Min-Max 缩放,公式为:$x\_{norm} = (x — x\_{min}) / (x\_{max} — x\_{min})$。然而,它有一个致命弱点:对异常值极度敏感。想象一下,如果数据中有一个10亿美元的收入(异常值),它会被映射为1,而其余所有正常收入可能都被压缩到接近0的狭窄区间内,数据内部的细微差异被彻底抹平。

让我们看看它对当前数据集的效果:

from sklearn.preprocessing import MinMaxScaler

min_max_scaler = MinMaxScaler()
normalized_x = min_max_scaler.fit_transform(X)

归一化数据效果图

结果触目惊心:Min-Max 缩放把 Population 的最大值(35k)映射为1.0,而几乎全部数据都被挤压到了 0-0.16 的狭窄区间内。数据有意义的分辨率几乎丧失殆尽。

因此,归一化最适合的场景是输入特征的边界已知且固定,最典型的例子就是 RGB 图像的像素值,其取值范围恒定在 [0, 255]。

方法总结与选用指南

下表汇总了四种缩放器的核心适用场景:

.----------------------.---------------------------.-------------------------------------------------------.
|        Issue         |         Best Tool         |                         Why?                          |
:----------------------+---------------------------+-------------------------------------------------------:
| Different Scales     | StandardScaler           | Makes features comparable.                            |
:----------------------+---------------------------+-------------------------------------------------------:
| Heavy 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():使用已计算的统计量对数据进行变换。

无论是在测试集还是未来线上的新数据,都只能调用 .transform()。如果在测试集上再次调用 .fit(),就等于发生了数据泄露,模型性能评估将变得毫无意义。在部署模型时,训练好的缩放器(及其参数)也必须和模型一起打包上线。

希望这篇对比能帮助你在实际机器学习项目中,更准确地为数据选择“合身”的预处理方法。更多数据处理与模型优化的实践讨论,欢迎在技术社区 云栈社区 交流。




上一篇:背靠背Pmos理想二极管电路设计,实现电源防反接与低功耗管理
下一篇:Kubernetes证书过期怎么办?kubeadm部署集群的续签与长期有效设置指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-23 06:00 , Processed in 0.594627 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表