2.2 转换器 (Transformer) Scikit-learn 核心概念:转换器 (Transformer) 详解 在机器学习的实践旅程中,数据预处理往往占据着至关重要的地位。高质量的数据是构建有效模型的基石,而 Scikit-learn (sklearn) 库作为 Python 中最受欢迎的机器学习库之一,提供了强大的工具来帮助我们进行数据预处理。其中,转换器 (Transformer) 是 sklearn 库的核心概念之一,它在数据预处理流程中扮演着不可或缺的角色。 本文将深入探讨 Scikit-learn 中的转换器 (Transformer),从核心概念到代码实践,再到详细的内容解析,帮助您全面理解和掌握这一关键工具,从而在机器学习项目中更加游刃有余。
在机器学习的实践旅程中,数据预处理往往占据着至关重要的地位。高质量的数据是构建有效模型的基石,而 Scikit-learn (sklearn) 库作为 Python 中最受欢迎的机器学习库之一,提供了强大的工具来帮助我们进行数据预处理。其中,转换器 (Transformer) 是 sklearn 库的核心概念之一,它在数据预处理流程中扮演着不可或缺的角色。
本文将深入探讨 Scikit-learn 中的转换器 (Transformer),从核心概念到代码实践,再到详细的内容解析,帮助您全面理解和掌握这一关键工具,从而在机器学习项目中更加游刃有余。
在真实世界的机器学习任务中,原始数据往往是 “脏乱差” 的:
数据类型不统一: 特征可能包含数值型、类别型、文本型等多种类型,模型难以直接处理。
量纲不一致: 不同特征的取值范围可能差异巨大,影响模型的收敛速度和性能。
存在缺失值: 数据采集过程中可能出现数据丢失,导致数据不完整。
存在异常值: 数据中可能包含噪声或错误数据,影响模型的鲁棒性。
特征冗余或无关: 部分特征可能与目标变量无关或高度相关,增加模型复杂度和降低泛化能力。
为了解决这些问题,数据预处理至关重要。数据预处理的目标是将原始数据转化为更适合机器学习模型学习的形式,从而提升模型的性能、稳定性和泛化能力。
转换器 (Transformer) 正是 Scikit-learn 中用于数据预处理的核心组件。 它可以被看作是一个数据转换的 “管道”,接收原始数据作为输入,经过一系列预定义的转换操作,输出处理后的数据。 这些转换操作可以包括:
数据标准化/归一化 (Scaling/Normalization): 将数值型特征缩放到特定的范围,消除量纲影响。
特征编码 (Encoding): 将类别型特征转换为数值型特征,以便模型处理。
缺失值处理 (Imputation): 填充数据中的缺失值。
特征选择 (Feature Selection): 选择最相关的特征,降低特征维度。
降维 (Dimensionality Reduction): 减少特征维度,提取主要特征信息。
非线性变换 (Non-linear Transformation): 对特征进行非线性变换,例如多项式特征扩展、对数变换等。
转换器的设计遵循了 Scikit-learn 的统一接口规范,使得数据预处理流程更加模块化、可复用和易于集成到机器学习 Pipeline 中。
Scikit-learn 中的转换器 (Transformer) 遵循统一的设计模式,主要包含两个核心方法:
fit(X, y=None): 学习 数据转换的参数。 fit 方法接收输入数据 X (通常是特征矩阵) 和可选的目标变量 y (某些转换器可能需要 y,例如 TargetEncoder)。 在 fit 方法中,转换器会分析输入数据,学习数据转换所需的统计信息或参数,例如:
StandardScaler: 计算特征的均值和标准差。
MinMaxScaler: 计算特征的最大值和最小值。
OneHotEncoder: 识别类别型特征的所有唯一值。
SimpleImputer: 学习缺失值填充策略 (例如均值、中位数等)。
PCA: 计算主成分方向。
需要注意的是,fit 方法 不会改变输入数据 X,而是将学习到的参数 存储在转换器对象内部,供后续的 transform 方法使用。
transform(X): 应用 学习到的转换。 transform 方法接收输入数据 X,并使用 fit 方法学习到的参数 对数据进行转换,然后返回转换后的数据。 例如:
StandardScaler: 使用 fit 方法计算的均值和标准差,对 X 进行标准化。
OneHotEncoder: 使用 fit 方法识别的唯一值,对 X 进行独热编码。
SimpleImputer: 使用 fit 方法学习的填充策略,填充 X 中的缺失值。
PCA: 使用 fit 方法计算的主成分方向,将 X 投影到低维空间。
fit_transform(X, y=None): 学习并应用 数据转换。 fit_transform 方法是 fit 和 transform 的组合,先调用 fit(X, y) 学习转换参数,然后立即使用学习到的参数对 X 进行转换,并返回转换后的数据。 在需要同时学习和转换数据的情况下,使用 fit_transform 可以更简洁高效。
核心思想: 转换器将数据转换过程分为 学习 (fit) 和 应用 (transform) 两个阶段。 fit 阶段在训练数据上进行,学习数据转换的规则,而 transform 阶段则将学习到的规则应用到训练数据、验证数据和测试数据上。 这种分离的设计模式至关重要,可以防止数据泄露 (data leakage),保证模型评估的可靠性。 只能在训练数据上进行 fit 操作,然后在所有数据集 (训练集、验证集、测试集) 上使用同一个 fit 后的转换器进行 transform 操作。
Scikit-learn 提供了丰富的转换器,涵盖了数据预处理的各个方面。 下面我们将详细介绍一些常用的转换器,并结合代码示例进行说明。
数据标准化/归一化旨在将数值型特征缩放到特定的范围,消除不同特征量纲的影响,使得不同特征具有可比性,并加速模型收敛。
StandardScaler (标准化)StandardScaler 通过移除均值并缩放到单位方差来标准化特征。 公式如下:
x_scaled = (x - mean) / std
其中,mean 是特征的均值,std 是特征的标准差。 标准化后的数据均值为 0,标准差为 1,近似服从标准正态分布。
代码示例:
from sklearn.preprocessing import StandardScaler import numpy as np # 示例数据 data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 创建 StandardScaler 对象 scaler = StandardScaler() # 学习数据转换参数 (计算均值和标准差) scaler.fit(data) # 查看学习到的参数 print("均值:", scaler.mean_) print("标准差:", scaler.scale_) # 注意这里是 scale_ 而不是 std_ # 应用标准化转换 scaled_data = scaler.transform(data) print("\n标准化后的数据:\n", scaled_data) # 使用 fit_transform 一步完成学习和转换 scaled_data_fit_transform = scaler.fit_transform(data) print("\nfit_transform 标准化后的数据:\n", scaled_data_fit_transform) # 逆转换 (将标准化后的数据还原) original_data = scaler.inverse_transform(scaled_data) print("\n逆转换后的数据:\n", original_data)
代码详解:
scaler = StandardScaler(): 创建 StandardScaler 对象。
scaler.fit(data): 在 data 上调用 fit 方法,StandardScaler 会计算 data 中每个特征的均值 (scaler.mean_) 和标准差 (scaler.scale_),并存储在 scaler 对象中。
scaler.transform(data): 使用 fit 方法学习到的均值和标准差,对 data 进行标准化转换,返回标准化后的数据 scaled_data。
scaler.fit_transform(data): 等价于先调用 fit(data) 再调用 transform(data),一步完成学习和转换。
scaler.inverse_transform(scaled_data): 使用 fit 方法学习到的均值和标准差,对 scaled_data 进行逆转换,还原回原始数据范围。 逆转换可以用于检查标准化过程是否正确。
适用场景: 当数据分布近似正态分布,或者模型对特征量纲敏感 (例如基于距离的模型,如 KNN、K-Means、SVM 等) 时,StandardScaler 是一个常用的选择。
MinMaxScaler (归一化)MinMaxScaler 通过将特征缩放到给定的最小值和最大值之间 (默认为 0 和 1) 来归一化特征。 公式如下:
x_scaled = (x - min) / (max - min) * (feature_range[1] - feature_range[0]) + feature_range[0]
其中,min 和 max 是特征的最小值和最大值,feature_range 是缩放范围,默认为 (0, 1)。 归一化后的数据值域通常在 [0, 1] 或 [-1, 1] 之间。
代码示例:
from sklearn.preprocessing import MinMaxScaler import numpy as np # 示例数据 (同上) data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 创建 MinMaxScaler 对象 scaler = MinMaxScaler() # 默认缩放到 [0, 1] # 学习数据转换参数 (计算最小值和最大值) scaler.fit(data) # 查看学习到的参数 print("最小值:", scaler.data_min_) print("最大值:", scaler.data_max_) # 应用归一化转换 scaled_data = scaler.transform(data) print("\n归一化后的数据:\n", scaled_data) # 逆转换 original_data = scaler.inverse_transform(scaled_data) print("\n逆转换后的数据:\n", original_data) # 缩放到 [-1, 1] 范围 scaler_range = MinMaxScaler(feature_range=(-1, 1)) scaled_data_range = scaler_range.fit_transform(data) print("\n缩放到 [-1, 1] 范围的数据:\n", scaled_data_range)
代码详解: 与 StandardScaler 类似,MinMaxScaler 也包含 fit、transform、fit_transform 和 inverse_transform 方法,使用方式类似。 可以通过 feature_range 参数指定缩放范围。
适用场景: 当数据分布不是正态分布,或者需要将特征值限定在特定范围内时,MinMaxScaler 比较适用。 例如,在图像处理中,像素值通常需要归一化到 [0, 1] 或 [0, 255] 范围内。 对于神经网络模型,归一化到 [0, 1] 或 [-1, 1] 范围通常有助于模型训练。
RobustScaler (鲁棒标准化)RobustScaler 使用中位数和四分位数范围来标准化特征,对异常值更具鲁棒性。 公式如下:
x_scaled = (x - median) / IQR
其中,median 是特征的中位数,IQR 是特征的四分位数范围 (Interquartile Range),即上四分位数 (75% 分位数) 减去下四分位数 (25% 分位数)。
代码示例:
from sklearn.preprocessing import RobustScaler import numpy as np # 示例数据 (包含异常值) data_with_outliers = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [100, 200, 300]]) # 添加异常值 # 创建 RobustScaler 对象 scaler_robust = RobustScaler() # 学习和应用鲁棒标准化 scaled_data_robust = scaler_robust.fit_transform(data_with_outliers) print("\nRobustScaler 标准化后的数据:\n", scaled_data_robust) # 对比 StandardScaler 的效果 scaler_standard = StandardScaler() scaled_data_standard = scaler_standard.fit_transform(data_with_outliers) print("\nStandardScaler 标准化后的数据 (对比):\n", scaled_data_standard)
代码详解: 代码示例中,我们添加了异常值 (100, 200, 300)。 可以看到,RobustScaler 标准化后的数据受异常值的影响较小,而 StandardScaler 标准化后的数据受异常值影响较大。
适用场景: 当数据中存在较多异常值时,RobustScaler 比 StandardScaler 更为稳健,可以更好地保留数据的分布形状。
Normalizer (归一化到单位范数)Normalizer 将每个样本 (行) 归一化到单位范数 (默认为 L2 范数)。 这意味着每个样本的特征向量的范数 (长度) 都变为 1。 常用于文本分类或聚类任务,将文本向量归一化到单位长度,消除文本长度差异的影响。
代码示例:
from sklearn.preprocessing import Normalizer import numpy as np # 示例数据 data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 创建 Normalizer 对象 normalizer = Normalizer() # 默认使用 L2 范数 # 学习和应用归一化 normalized_data = normalizer.fit_transform(data) print("\nNormalizer 归一化后的数据:\n", normalized_data) # 查看每个样本的 L2 范数 (应近似为 1) norms = np.linalg.norm(normalized_data, axis=1) print("\n每个样本的 L2 范数:\n", norms) # 使用 L1 范数归一化 normalizer_l1 = Normalizer(norm='l1') normalized_data_l1 = normalizer_l1.fit_transform(data) print("\nL1 范数归一化后的数据:\n", normalized_data_l1)
代码详解:
normalizer = Normalizer(): 创建 Normalizer 对象,默认使用 L2 范数 (欧几里得范数)。
normalizer.fit_transform(data): 对 data 进行归一化,返回归一化后的数据 normalized_data。 Normalizer 的 fit 方法实际上并不学习任何参数,只是为了保持接口一致性。
np.linalg.norm(normalized_data, axis=1): 计算 normalized_data 中每个样本 (行) 的 L2 范数。 结果应近似为 1。
Normalizer(norm='l1'): 创建 Normalizer 对象,使用 L1 范数 (曼哈顿范数) 进行归一化。
适用场景: 当需要消除样本之间特征向量长度差异时,Normalizer 比较适用。 例如,在文本分类中,文档长度不同会导致词频向量的范数差异很大,使用 Normalizer 可以消除这种差异,更关注词语的相对频率。
特征编码用于将类别型特征转换为数值型特征,以便机器学习模型能够处理。
OneHotEncoder (独热编码)OneHotEncoder 将每个类别型特征 转换为一个 one-hot 向量。 例如,如果一个类别型特征有 3 个类别: "red", "green", "blue",那么 OneHotEncoder 会将其转换为 3 个新的二元特征: "feature_red", "feature_green", "feature_blue"。 对于每个样本,只有一个新特征的值为 1,其余为 0,表示该样本属于哪个类别。
代码示例:
from sklearn.preprocessing import OneHotEncoder import numpy as np # 示例数据 categorical_data = np.array([['red'], ['green'], ['blue'], ['green']]) # 创建 OneHotEncoder 对象 encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore') # sparse=False 返回 NumPy 数组, handle_unknown='ignore' 处理未知类别 # 学习类别信息 encoder.fit(categorical_data) # 查看学习到的类别 print("类别:", encoder.categories_) # 应用独热编码 encoded_data = encoder.transform(categorical_data) print("\n独热编码后的数据:\n", encoded_data) # 逆转换 original_data = encoder.inverse_transform(encoded_data) print("\n逆转换后的数据:\n", original_data) # 处理未知类别 unknown_data = np.array([['yellow'], ['red']]) encoded_unknown_data = encoder.transform(unknown_data) print("\n处理未知类别后的数据:\n", encoded_unknown_data) # 'yellow' 类别被忽略,编码为全 0 向量
代码详解:
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore'): 创建 OneHotEncoder 对象。
sparse_output=False: 指定输出类型为 NumPy 数组,而不是稀疏矩阵 (默认输出稀疏矩阵)。
handle_unknown='ignore': 指定如何处理在 transform 阶段遇到的训练集中未出现过的类别。 'ignore' 表示忽略未知类别,将其编码为全 0 向量。 其他选项包括 'error' (报错)。
encoder.fit(categorical_data): 学习类别信息,OneHotEncoder 会识别 categorical_data 中每个类别型特征的所有唯一值,并存储在 encoder.categories_ 属性中。
encoder.transform(categorical_data): 对 categorical_data 进行独热编码,返回编码后的数据 encoded_data。
encoder.inverse_transform(encoded_data): 将独热编码后的数据还原回原始类别数据。
handle_unknown='ignore' 示例: 当 transform 的数据中包含训练集中未出现过的类别 'yellow' 时,由于 handle_unknown='ignore','yellow' 类别被忽略,编码为全 0 向量。
适用场景: 当类别型特征是名义型 (nominal) 的,即类别之间没有顺序关系时,OneHotEncoder 是一个常用的选择。 独热编码可以避免模型错误地学习到类别之间的顺序关系。 但需要注意的是,如果类别数量过多,独热编码会导致特征维度爆炸,可能会降低模型性能。
OrdinalEncoder (序数编码)OrdinalEncoder 将每个类别型特征 转换为序数数值。 它会为每个类别分配一个整数,通常按照类别出现的顺序或字母顺序进行分配。
代码示例:
from sklearn.preprocessing import OrdinalEncoder import numpy as np # 示例数据 (同上) categorical_data = np.array([['red'], ['green'], ['blue'], ['green']]) # 创建 OrdinalEncoder 对象 encoder_ordinal = OrdinalEncoder(categories='auto') # categories='auto' 自动识别类别 # 学习类别顺序 encoder_ordinal.fit(categorical_data) # 查看学习到的类别顺序 print("类别顺序:", encoder_ordinal.categories_) # 应用序数编码 encoded_ordinal_data = encoder_ordinal.transform(categorical_data) print("\n序数编码后的数据:\n", encoded_ordinal_data) # 逆转换 original_ordinal_data = encoder_ordinal.inverse_transform(encoded_ordinal_data) print("\n逆转换后的数据:\n", original_ordinal_data) # 自定义类别顺序 encoder_custom_order = OrdinalEncoder(categories=[['red', 'green', 'blue']]) # 自定义类别顺序 encoded_custom_order_data = encoder_custom_order.fit_transform(categorical_data) print("\n自定义类别顺序编码后的数据:\n", encoded_custom_order_data)
代码详解:
encoder_ordinal = OrdinalEncoder(categories='auto'): 创建 OrdinalEncoder 对象。 categories='auto' 表示自动识别类别并按照出现顺序或字母顺序分配整数。
encoder_ordinal.fit(categorical_data): 学习类别顺序,OrdinalEncoder 会识别 categorical_data 中每个类别型特征的所有唯一值,并确定类别顺序,存储在 encoder_ordinal.categories_ 属性中。
encoder_ordinal.transform(categorical_data): 对 categorical_data 进行序数编码,返回编码后的数据 encoded_ordinal_data。
encoder_ordinal.inverse_transform(encoded_ordinal_data): 将序数编码后的数据还原回原始类别数据。
OrdinalEncoder(categories=[['red', 'green', 'blue']]): 创建 OrdinalEncoder 对象,并自定义类别顺序为 ['red', 'green', 'blue']。
适用场景: 当类别型特征是有序型 (ordinal) 的,即类别之间存在顺序关系时,OrdinalEncoder 可以保留这种顺序信息。 例如,学历 (小学 < 初中 < 高中 < 大学)、等级 (低 < 中 < 高) 等。 但需要注意的是,序数编码会引入类别之间的数值距离,可能会被模型误解为类别之间的数值差异是等距的,因此需要谨慎使用。
LabelEncoder (标签编码)LabelEncoder 用于编码目标变量 (通常是 y),将类别型标签转换为数值型标签。 它与 OrdinalEncoder 类似,但通常只用于编码一维的目标变量,而不是特征矩阵。
代码示例:
from sklearn.preprocessing import LabelEncoder import numpy as np # 示例目标变量 labels = np.array(['red', 'green', 'blue', 'green']) # 创建 LabelEncoder 对象 label_encoder = LabelEncoder() # 学习标签类别 label_encoder.fit(labels) # 查看学习到的标签类别 print("标签类别:", label_encoder.classes_) # 应用标签编码 encoded_labels = label_encoder.transform(labels) print("\n标签编码后的数据:\n", encoded_labels) # 逆转换 original_labels = label_encoder.inverse_transform(encoded_labels) print("\n逆转换后的数据:\n", original_labels)
代码详解: LabelEncoder 的使用方式与 OrdinalEncoder 类似,但它通常用于编码目标变量,且只接受一维数组作为输入。
适用场景: 当目标变量是类别型时,需要使用 LabelEncoder 将其转换为数值型,以便模型能够处理。 例如,在分类任务中,目标变量通常是类别标签,需要进行标签编码。
TargetEncoder (目标编码)TargetEncoder 使用目标变量的均值 来编码类别型特征。 对于每个类别,TargetEncoder 会计算该类别下目标变量的均值 (对于分类任务,通常是正例的比例),并用该均值来代替类别。 目标编码是一种有监督的编码方法,因为它使用了目标变量的信息。
代码示例:
from sklearn.preprocessing import TargetEncoder import pandas as pd # 示例数据 data = pd.DataFrame({'category': ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C'], 'target': [0, 1, 0, 1, 0, 1, 1, 0, 0]}) # 创建 TargetEncoder 对象 encoder_target = TargetEncoder() # 学习目标编码 encoder_target.fit(data['category'], data['target']) # 查看学习到的目标编码 print("目标编码:", encoder_target.encodings_) # 应用目标编码 encoded_target_data = encoder_target.transform(data['category']) print("\n目标编码后的数据:\n", encoded_target_data)
代码详解:
encoder_target = TargetEncoder(): 创建 TargetEncoder 对象。
encoder_target.fit(data['category'], data['target']): 学习目标编码,TargetEncoder 会计算每个类别 ('A', 'B', 'C') 下目标变量 target 的均值,并存储在 encoder_target.encodings_ 属性中。
encoder_target.transform(data['category']): 对 data['category'] 进行目标编码,返回编码后的数据 encoded_target_data。
适用场景: 目标编码在高基数类别型特征 (类别数量非常多) 的情况下表现良好,可以有效地将类别信息融入到数值特征中,并减少特征维度。 但需要注意的是,目标编码容易导致过拟合 (overfitting),尤其是在类别数量较少的情况下。 为了缓解过拟合,TargetEncoder 通常会结合交叉验证 (cross-validation) 和 平滑 (smoothing) 技术。 Scikit-learn 的 TargetEncoder 默认使用了交叉验证和平滑。
缺失值处理旨在填充数据中的缺失值,使得数据完整可用。
SimpleImputer (简单填充)SimpleImputer 使用简单的策略 (例如均值、中位数、众数、常数) 来填充缺失值。