许多机器学习算法在输入数值变量处于标准尺度时表现更好。计算数据点之间距离的算法(如k近邻)或依赖梯度下降优化的算法(如线性回归、逻辑回归和神经网络)可能对范围差异很大的特征很敏感。范围较大的特征可能会过度影响距离计算或导致更陡峭的梯度,从而可能阻碍学习过程。特征缩放和标准化是将所有特征调整到相似尺度的技术,以确保每个特征的公平贡献。我们将介绍Scikit-learn中两种常见的缩放技术:标准化和最小-最大缩放。标准化 (Z-分数缩放)标准化将数据重新调整尺度,使其均值 ($\mu$) 为0,标准差 ($\sigma$) 为1。这种变换通常称为Z-分数标准化。标准化的公式如下:$$ z = \frac{x - \mu}{\sigma} $$其中,$x$ 是原始特征值,$\mu$ 是特征列的均值,$\sigma$ 是特征列的标准差。结果值 $z$ 表示原始值与均值相距的标准差数量。标准化不将值限制在特定范围,这对于某些算法可能是一个缺点,但与最小-最大缩放相比,它对离群值不那么敏感。它特别适用于假设数据围绕零中心或服从高斯分布的算法。Scikit-learn在其preprocessing模块中提供了StandardScaler类来执行此变换。import numpy as np from sklearn.preprocessing import StandardScaler import pandas as pd # 示例数据(例如,特征值) data = pd.DataFrame({'FeatureA': [10, 20, 30, 40, 50], 'FeatureB': [1000, 1500, 1200, 1800, 1300]}) print("原始数据:") print(data) # 初始化StandardScaler scaler = StandardScaler() # 将缩放器拟合到数据并进行变换 # 注意:fit_transform()结合了fit()和transform() scaled_data = scaler.fit_transform(data) # 结果是NumPy数组 scaled_df = pd.DataFrame(scaled_data, columns=data.columns) print("\n标准化数据(均值约0,标准差约1):") print(scaled_df) print(f"\n缩放后的均值:\n{scaled_df.mean()}") print(f"\n缩放后的标准差:\n{scaled_df.std()}")运行此代码将显示原始数据和标准化数据,其中每个特征的均值现在都非常接近0,标准差非常接近1。让我们可视化标准化对样本分布的影响。假设我们有一个表示收入的特征,它可能存在偏斜。{"data": [{"x": [30000, 35000, 40000, 42000, 45000, 46000, 47000, 48000, 50000, 51000, 52000, 53000, 55000, 58000, 60000, 65000, 70000, 75000, 80000, 90000, 120000], "type": "histogram", "name": "原始", "marker": {"color": "#4dabf7"}}, {"x": [-0.83, -0.59, -0.35, -0.25, -0.11, -0.06, -0.01, 0.04, 0.13, 0.18, 0.23, 0.28, 0.37, 0.51, 0.61, 0.85, 1.09, 1.33, 1.57, 1.94, 3.36], "type": "histogram", "name": "标准化", "marker": {"color": "#ff922b"}, "xaxis": "x2", "yaxis": "y2"}], "layout": {"title": "标准化对特征分布的影响", "grid": {"rows": 1, "columns": 2, "pattern": "independent"}, "xaxis": {"title": "原始收入"}, "yaxis": {"title": "频率"}, "xaxis2": {"title": "标准化收入 (Z-分数)"}, "yaxis2": {"anchor": "x2", "title": "频率"}, "legend": {"traceorder": "reversed"}, "autosize": true, "height": 350}}原始收入分布(蓝色)经过缩放,使得变换后的分布(橙色)均值为0,标准差为1。分布的形状保持相似。归一化 (最小-最大缩放)归一化,特指最小-最大缩放,将数据重新调整到固定范围,通常是 [0, 1]。最小-最大缩放的公式如下:$$ x_{scaled} = \frac{x - min(x)}{max(x) - min(x)} $$其中,$min(x)$ 和 $max(x)$ 分别是特征列的最小值和最大值。当算法期望特征在有界区间内时,这种缩放很有用,例如某些神经网络激活函数(如Sigmoid)或处理图像像素强度(通常缩放到 [0, 1])。然而,最小-最大缩放对离群值敏感。如果存在一个非常大或非常小的值,它可能会将其余数据压缩到一个非常窄的范围。Scikit-learn为此目的提供了MinMaxScaler类。import numpy as np from sklearn.preprocessing import MinMaxScaler import pandas as pd # 使用相同的示例数据 data = pd.DataFrame({'FeatureA': [10, 20, 30, 40, 50], 'FeatureB': [1000, 1500, 1200, 1800, 1300]}) print("原始数据:") print(data) # 初始化MinMaxScaler(默认范围是[0, 1]) min_max_scaler = MinMaxScaler() # 将缩放器拟合到数据并进行变换 normalized_data = min_max_scaler.fit_transform(data) # 结果是NumPy数组 normalized_df = pd.DataFrame(normalized_data, columns=data.columns) print("\n归一化数据(范围 [0, 1]):") print(normalized_df) print(f"\n缩放后的最小值:\n{normalized_df.min()}") print(f"\n缩放后的最大值:\n{normalized_df.max()}")此代码展示了MinMaxScaler如何变换特征,使其最小值变为0,最大值变为1。让我们使用相同的收入数据可视化最小-最大缩放的效果。{"data": [{"x": [30000, 35000, 40000, 42000, 45000, 46000, 47000, 48000, 50000, 51000, 52000, 53000, 55000, 58000, 60000, 65000, 70000, 75000, 80000, 90000, 120000], "type": "histogram", "name": "原始", "marker": {"color": "#4dabf7"}}, {"x": [0.00, 0.06, 0.11, 0.13, 0.17, 0.18, 0.19, 0.20, 0.22, 0.23, 0.24, 0.26, 0.28, 0.31, 0.33, 0.39, 0.44, 0.50, 0.56, 0.67, 1.00], "type": "histogram", "name": "最小-最大缩放", "marker": {"color": "#74b816"}, "xaxis": "x2", "yaxis": "y2"}], "layout": {"title": "最小-最大缩放对特征分布的影响", "grid": {"rows": 1, "columns": 2, "pattern": "independent"}, "xaxis": {"title": "原始收入"}, "yaxis": {"title": "频率"}, "xaxis2": {"title": "缩放收入 [0, 1]"}, "yaxis2": {"anchor": "x2", "title": "频率"}, "legend": {"traceorder": "reversed"}, "autosize": true, "height": 350}}原始收入分布(蓝色)被缩放以适应 [0, 1] 范围(绿色)。注意离群值(120000)的存在如何影响缩放,可能将大部分数据点压缩到 [0, 1] 范围的较小部分。标准化与归一化的选择标准化和归一化之间的选择取决于数据和你计划使用的算法:标准化 (StandardScaler):通常首选用于假设零中心分布的算法,或基于距离计算且相对距离比范围更重要的算法(例如PCA、K-Means、SVM、带正则化的线性回归)。它受离群值的影响较小。归一化 (MinMaxScaler):适用于要求特征在特定尺度上的算法,例如 [0, 1](例如使用sigmoid/tanh激活函数的神经网络,对绝对范围有要求的数据使用距离度量的算法,图像处理)。请注意它对离群值的敏感性。如果你不确定,标准化通常是一个好的默认选择。有时,尝试两者并评估模型性能可以指导决策。在训练/测试集划分中正确应用缩放器预处理中的一个重要一点是避免测试集数据泄露到训练过程中。缩放也不例外。你必须只在训练数据上拟合缩放器。然后,学习到的参数(StandardScaler的均值/标准差,MinMaxScaler的最小值/最大值)将用于变换训练数据和测试数据。在分割之前在整个数据集上拟合缩放器将允许关于测试集分布(或范围)的信息影响训练集的缩放,从而导致过于乐观的性能估计。以下是使用训练/测试集划分的正确模式:from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import pandas as pd import numpy as np # 示例数据 X = pd.DataFrame(np.random.rand(100, 3) * np.array([1, 100, 50]), columns=['F1', 'F2', 'F3']) y = np.random.randint(0, 2, 100) # 分割数据 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 初始化缩放器 scaler = StandardScaler() # 仅在训练数据上拟合缩放器 scaler.fit(X_train) # 使用已拟合的缩放器变换训练和测试数据 X_train_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test) # 转换回DataFrame以供检查(可选) X_train_scaled_df = pd.DataFrame(X_train_scaled, columns=X_train.columns, index=X_train.index) X_test_scaled_df = pd.DataFrame(X_test_scaled, columns=X_test.columns, index=X_test.index) print("原始训练数据头部:") print(X_train.head()) print("\n缩放后的训练数据头部:") print(X_train_scaled_df.head()) print("\n缩放后的测试数据头部:") print(X_test_scaled_df.head()) print(f"\n缩放后的训练数据均值:\n{X_train_scaled_df.mean()}") print(f"\n缩放后的测试数据均值:\n{X_test_scaled_df.mean()}") # 注意:测试集均值不会完全为0请注意,缩放后的测试数据均值可能不会完全为零,因为缩放参数完全来自训练数据的分布。这是预期且正确的行为,模拟了模型在实际应用中如何遇到未见数据。正确管理多个预处理变换的这些步骤可能会变得繁琐。在下一节中,我们将介绍Scikit-learn Pipelines,一个旨在将这些步骤干净地串联起来并防止在变换过程中出现数据泄露等常见错误的工具。