4.2 数据准备与预处理


文档摘要

4.2 数据准备与预处理 第四章:LightGBM 实战应用领域 - 4.2 数据准备与预处理详解 4.2.1 数据准备的重要性 数据准备阶段的目标是将原始数据转化为适合 LightGBM 模型训练的格式。这通常包括以下几个关键方面: 数据理解: 深入了解数据的来源、含义、特征类型、数据分布等,这有助于我们选择合适的预处理方法。 数据质量评估: 识别数据中的缺失值、异常值、重复值等问题,并制定相应的处理策略。 数据转换: 将数据转换成 LightGBM 可以有效处理的格式,例如数值型编码、特征缩放等。 特征工程: 基于业务理解和数据分析,创造新的特征,提升模型的预测能力。 数据集划分: 合理划分训练集、验证集和测试集,保证模型评估的可靠性和泛化能力。

4.2 数据准备与预处理

第四章:LightGBM 实战应用领域 - 4.2 数据准备与预处理详解

4.2.1 数据准备的重要性

数据准备阶段的目标是将原始数据转化为适合 LightGBM 模型训练的格式。这通常包括以下几个关键方面:

  • 数据理解: 深入了解数据的来源、含义、特征类型、数据分布等,这有助于我们选择合适的预处理方法。

  • 数据质量评估: 识别数据中的缺失值、异常值、重复值等问题,并制定相应的处理策略。

  • 数据转换: 将数据转换成 LightGBM 可以有效处理的格式,例如数值型编码、特征缩放等。

  • 特征工程: 基于业务理解和数据分析,创造新的特征,提升模型的预测能力。

  • 数据集划分: 合理划分训练集、验证集和测试集,保证模型评估的可靠性和泛化能力。

一个完善的数据准备流程可以显著提升 LightGBM 模型的性能,具体体现在:

  • 提高模型精度: 清洗后的数据可以减少噪声干扰,特征工程可以挖掘数据中的深层信息,从而提升模型的预测准确率。

  • 加快模型训练速度: 预处理后的数据可以减少数据维度,降低模型复杂度,从而加快训练速度。

  • 增强模型鲁棒性: 处理异常值和缺失值可以提高模型的鲁棒性,使其在面对新数据时表现更稳定。

  • 提升模型可解释性: 清晰的数据和特征可以帮助我们更好地理解模型的决策过程,提升模型的可解释性。

4.2.2 数据预处理的关键步骤

数据预处理是一个迭代的过程,需要根据具体的数据情况和模型需求进行调整。以下是 LightGBM 实战应用中常用的数据预处理步骤,我们将逐一进行详细讲解并提供代码示例。

1. 数据清洗 (Data Cleaning)

数据清洗是预处理的第一步,旨在处理数据中的噪声和错误,提高数据质量。常见的清洗操作包括:

  • 缺失值处理 (Handling Missing Values)

缺失值是实际数据中常见的问题,可能由于数据采集错误、信息遗漏等原因造成。处理缺失值的方法有很多,常见的包括:

* **删除缺失值:** 如果缺失值比例较小,且缺失值是随机分布的,可以考虑直接删除包含缺失值的样本或特征列。 * **填充缺失值:** 使用统计量(如均值、中位数、众数)或模型预测值填充缺失值。对于类别型特征,可以使用众数或新类别填充。 * **不处理:** 对于树模型(如 LightGBM),在某些情况下,可以不显式处理缺失值,模型本身可以学习如何处理缺失值。

代码实践 (Python - Pandas, Scikit-learn):

import pandas as pd from sklearn.impute import SimpleImputer # 示例数据 data = {'feature1': [1, 2, None, 4, 5], 'feature2': ['A', 'B', 'A', None, 'C'], 'target': [0, 1, 0, 1, 0]} df = pd.DataFrame(data) print("原始数据:\n", df) # 缺失值统计 print("\n缺失值统计:\n", df.isnull().sum()) # 1. 删除缺失值 (样本) df_dropna = df.dropna(axis=0) # 删除包含缺失值的行 print("\n删除缺失值后的数据 (样本):\n", df_dropna) # 2. 填充缺失值 (均值/中位数/众数) - 数值型特征 imputer_mean = SimpleImputer(strategy='mean') # 均值填充 df['feature1_mean_imputed'] = imputer_mean.fit_transform(df[['feature1']]) print("\n均值填充后的数据 (数值型特征):\n", df) imputer_mode = SimpleImputer(strategy='most_frequent') # 众数填充 df['feature2_mode_imputed'] = imputer_mode.fit_transform(df[['feature2']]) print("\n众数填充后的数据 (类别型特征):\n", df) # 3. 填充缺失值 (常量) df['feature2_constant_imputed'] = df['feature2'].fillna('Unknown') # 用 'Unknown' 填充类别型缺失值 print("\n常量填充后的数据 (类别型特征):\n", df)

代码详解:

  • df.isnull().sum(): 统计每列的缺失值数量。

  • df.dropna(axis=0): 删除包含缺失值的行 (样本)。axis=1 则删除包含缺失值的列 (特征)。

  • SimpleImputer(strategy='mean'): Scikit-learn 提供的缺失值填充器,strategy 参数可以设置为 'mean' (均值), 'median' (中位数), 'most_frequent' (众数), 'constant' (常量)。

  • imputer.fit_transform(df[['feature1']]): 先用 fit 方法学习填充策略(例如计算均值),然后用 transform 方法进行填充。fit_transform 将这两个步骤合并。

  • df['feature2'].fillna('Unknown'): Pandas 提供的 fillna 方法,可以直接用指定值填充缺失值。

  • 异常值处理 (Handling Outliers)

异常值是指与其他观测值明显不同的数据点,可能是数据采集错误、实验误差或真实存在的极端情况。异常值会影响模型的训练,降低模型的泛化能力。常见的异常值处理方法包括:

* **删除异常值:** 如果异常值是明显的错误数据,可以考虑直接删除。 * **替换异常值:** 用合理的数值(如均值、中位数、边界值)替换异常值。 * **缩尾处理 (Winsorization):** 将超出一定范围的异常值替换为边界值,例如将 99% 分位数以上的值替换为 99% 分位数。 * **不处理:** 对于树模型,在某些情况下,异常值对模型的影响较小,可以不处理。

代码实践 (Python - Pandas, NumPy):

import pandas as pd import numpy as np # 示例数据 data = {'feature': [10, 20, 15, 12, 100, 18, 22, -50, 16, 25]} df_outlier = pd.DataFrame(data) print("原始数据 (含异常值):\n", df_outlier) # 1. 基于 IQR (四分位距) 的异常值检测和删除 Q1 = df_outlier['feature'].quantile(0.25) Q3 = df_outlier['feature'].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR df_no_outlier_iqr = df_outlier[(df_outlier['feature'] >= lower_bound) & (df_outlier['feature'] <= upper_bound)] print("\n基于 IQR 删除异常值后的数据:\n", df_no_outlier_iqr) # 2. 缩尾处理 (Winsorization) lower_percentile = 0.05 upper_percentile = 0.95 lower_threshold = df_outlier['feature'].quantile(lower_percentile) upper_threshold = df_outlier['feature'].quantile(upper_percentile) df_winsorized = df_outlier['feature'].clip(lower=lower_threshold, upper=upper_threshold) print("\n缩尾处理后的数据:\n", df_winsorized) # 3. 替换异常值 (例如用均值) - 这里简单演示替换大于 50 的值 df_replaced_outlier = df_outlier['feature'].mask(df_outlier['feature'] > 50, df_outlier['feature'].mean()) print("\n替换异常值后的数据 (大于 50 的值替换为均值):\n", df_replaced_outlier)

代码详解:

  • df_outlier['feature'].quantile(0.25): 计算特征列的 25% 分位数 (Q1)。

  • IQR = Q3 - Q1: 计算四分位距。

  • lower_bound, upper_bound: 基于 IQR 定义的异常值边界。

  • df_outlier[(df_outlier['feature'] >= lower_bound) & (df_outlier['feature'] <= upper_bound)]: 筛选出在正常范围内的样本,删除异常值。

  • df_outlier['feature'].clip(lower=lower_threshold, upper=upper_threshold): 使用 clip 方法进行缩尾处理,将值限制在指定范围内。

  • df_outlier['feature'].mask(df_outlier['feature'] > 50, df_outlier['feature'].mean()): 使用 mask 方法,将满足条件 (大于 50) 的值替换为均值。

  • 重复值处理 (Handling Duplicates)

重复值是指完全相同的样本记录,可能是数据采集或合并过程中产生的。重复值会影响模型的训练,导致模型过拟合。处理重复值的方法通常是:

* **删除重复值:** 保留第一个或最后一个重复样本,删除其余重复样本。

代码实践 (Python - Pandas):

import pandas as pd # 示例数据 data = {'feature1': ['A', 'B', 'A', 'A', 'C'], 'feature2': [1, 2, 1, 1, 3], 'target': [0, 1, 0, 0, 1]} df_duplicate = pd.DataFrame(data) print("原始数据 (含重复值):\n", df_duplicate) # 检测重复值 print("\n重复值检测 (所有列完全相同):", df_duplicate.duplicated()) print("\n重复值检测 (指定列 'feature1', 'feature2'):", df_duplicate.duplicated(subset=['feature1', 'feature2'])) # 删除重复值 df_no_duplicate = df_duplicate.drop_duplicates() # 删除所有列完全相同的重复行,保留第一个 print("\n删除重复值后的数据 (所有列):\n", df_no_duplicate) df_no_duplicate_subset = df_duplicate.drop_duplicates(subset=['feature1', 'feature2'], keep='last') # 基于指定列删除重复行,保留最后一个 print("\n基于指定列删除重复值后的数据:\n", df_no_duplicate_subset)

代码详解:

  • df_duplicate.duplicated(): 检测所有列完全相同的重复行,返回布尔 Series。

  • df_duplicate.duplicated(subset=['feature1', 'feature2']): 检测指定列 ('feature1', 'feature2') 组合相同的重复行。

  • df_duplicate.drop_duplicates(): 删除所有列完全相同的重复行,默认保留第一个出现的行。

  • df_duplicate.drop_duplicates(subset=['feature1', 'feature2'], keep='last'): 基于指定列删除重复行,keep='last' 表示保留最后一个出现的行,keep='first' (默认) 表示保留第一个。

2. 特征工程 (Feature Engineering)

特征工程是预处理的核心环节,旨在基于原始数据创建新的、更有意义的特征,提升模型的预测能力。特征工程的方法非常多样,需要结合业务理解和数据分析,常见的包括:

  • 特征构造 (Feature Creation): 基于现有特征进行组合、变换、聚合等操作,生成新的特征。例如:

    • 多项式特征: 将特征进行平方、立方等操作,捕捉特征之间的非线性关系。

    • 交叉特征: 将不同特征进行组合(例如相乘、相除),捕捉特征之间的交互作用。

    • 聚合特征: 对分组数据进行统计聚合(例如求和、均值、最大值、最小值),提取统计信息。

    • 时间特征: 从日期时间类型数据中提取年、月、日、小时、星期几等特征。

  • 特征选择 (Feature Selection): 从众多特征中选择出对模型预测最有用的特征,降低特征维度,提高模型效率和泛化能力。例如:

    • 过滤式 (Filter Methods): 基于特征的统计指标(如方差、相关系数)进行选择,与模型无关。

    • 包裹式 (Wrapper Methods): 将特征子集的选择看作搜索问题,使用模型性能作为评价指标,例如递归特征消除 (RFE)。

    • 嵌入式 (Embedded Methods): 将特征选择融入模型训练过程中,例如 L1 正则化 (Lasso)、树模型的特征重要性。

代码实践 (Python - Pandas, Scikit-learn):

import pandas as pd from sklearn.preprocessing import PolynomialFeatures, StandardScaler from sklearn.feature_selection import SelectKBest, f_regression # 示例数据 data_fe = {'feature1': [1, 2, 3, 4, 5], 'feature2': [2, 4, 5, 4, 6], 'timestamp': ['2023-10-26 10:00:00', '2023-10-26 11:00:00', '2023-10-27 09:00:00', '2023-10-27 14:00:00', '2023-10-28 12:00:00'], 'target': [0, 1, 0, 1, 0]} df_fe = pd.DataFrame(data_fe) # 1. 特征构造 - 多项式特征 poly = PolynomialFeatures(degree=2, include_bias=False) # 生成 2 次多项式特征,不包含 bias 列 poly_features = poly.fit_transform(df_fe[['feature1', 'feature2']]) poly_feature_names = poly.get_feature_names_out(['feature1', 'feature2']) df_poly_features = pd.DataFrame(poly_features, columns=poly_feature_names) print("\n多项式特征:\n", df_poly_features) # 2. 特征构造 - 时间特征 df_fe['timestamp'] = pd.to_datetime(df_fe['timestamp']) df_fe['hour'] = df_fe['timestamp'].dt.hour df_fe['dayofweek'] = df_fe['timestamp'].dt.dayofweek # 0: Monday, 6: Sunday print("\n时间特征:\n", df_fe[['timestamp', 'hour', 'dayofweek']]) # 3. 特征选择 - 基于方差过滤 (示例,实际应用中需要结合业务和模型选择更合适的特征选择方法) from sklearn.feature_selection import VarianceThreshold selector = VarianceThreshold(threshold=0.5) # 方差阈值设置为 0.5 selector.fit(df_poly_features) selected_features_variance = df_poly_features.columns[selector.get_support()] print("\n基于方差过滤选择的特征:\n", selected_features_variance) # 4. 特征选择 - 基于单变量特征选择 (示例,使用 f_regression 评估回归任务的特征相关性) X = df_poly_features y = df_fe['target'] selector_f_regression = SelectKBest(score_func=f_regression, k=3) # 选择 top 3 特征 selector_f_regression.fit(X, y) selected_features_f_regression = X.columns[selector_f_regression.get_support()] print("\n基于单变量特征选择 (f_regression) 选择的特征:\n", selected_features_f_regression)

代码详解:

  • PolynomialFeatures(degree=2, include_bias=False): Scikit-learn 提供的多项式特征生成器,degree 参数指定多项式次数,include_bias=False 移除 bias 列 (常数项)。

  • poly.fit_transform(df_fe[['feature1', 'feature2']]): 生成多项式特征。

  • poly.get_feature_names_out(['feature1', 'feature2']): 获取生成的多项式特征的名称。

  • pd.to_datetime(df_fe['timestamp']): 将字符串时间戳转换为 Pandas datetime 类型。

  • df_fe['timestamp'].dt.hour, df_fe['timestamp'].dt.dayofweek: 从 datetime 类型数据中提取小时和星期几特征。

  • VarianceThreshold(threshold=0.5): Scikit-learn 提供的方差过滤特征选择器,移除方差低于阈值的特征。

  • selector.fit(df_poly_features): 训练方差过滤选择器。

  • selector.get_support(): 返回布尔数组,指示哪些特征被选择。

  • SelectKBest(score_func=f_regression, k=3): Scikit-learn 提供的单变量特征选择器,score_func 参数指定评分函数 (这里使用 f_regression 用于回归任务的特征选择),k 参数指定选择的特征数量。

  • selector_f_regression.fit(X, y): 训练单变量特征选择器。

3. 特征编码 (Feature Encoding)

对于类别型特征,LightGBM 虽然可以直接处理类别特征,但在某些情况下,将其转换为数值型编码仍然可以提升模型性能或满足特定需求。常见的类别型特征编码方法包括:

  • 标签编码 (Label Encoding): 将类别标签转换为连续的整数,例如将 ['A', 'B', 'C', 'A'] 编码为 [0, 1, 2, 0]。适用于有序类别特征。

  • 独热编码 (One-Hot Encoding): 为每个类别创建一个新的二元特征,例如将 ['A', 'B', 'C', 'A'] 编码为:

    A: [1, 0, 0, 1] B: [0, 1, 0, 0] C: [0, 0, 1, 0]

    适用于无序类别特征,可以避免模型误解类别之间的顺序关系。

  • 序数编码 (Ordinal Encoding): 类似于标签编码,但可以自定义类别标签的顺序,适用于有序类别特征。

  • 频率编码 (Frequency Encoding): 用类别的频率或占比替换类别标签,适用于类别数量较多的特征。

  • 目标编码 (Target Encoding): 用类别的目标变量统计量(如均值、中位数)替换类别标签,适用于高基数类别特征,但容易导致过拟合,需要谨慎使用。

代码实践 (Python - Pandas, Scikit-learn):

import pandas as pd from sklearn.preprocessing import LabelEncoder, OneHotEncoder, OrdinalEncoder # 示例数据 data_encode = {'category_feature': ['A', 'B', 'C', 'A', 'B', 'D'], 'ordered_feature': ['Low', 'Medium', 'High', 'Medium', 'Low', 'High'], 'target': [0, 1, 0, 1, 0, 1]} df_encode = pd.DataFrame(data_encode) print("原始数据 (类别型特征):\n", df_encode) # 1. 标签编码 (Label Encoding) label_encoder = LabelEncoder() df_encode['category_encoded'] = label_encoder.fit_transform(df_encode['category_feature']) print("\n标签编码后的数据 (无序类别特征):\n", df_encode) # 2. 独热编码 (One-Hot Encoding) onehot_encoder = OneHotEncoder(sparse_output=False) # sparse=False 返回 dense array onehot_encoded_features = onehot_encoder.fit_transform(df_encode[['category_feature']]) onehot_feature_names = onehot_encoder.get_feature_names_out(['category_feature']) df_onehot_encoded = pd.DataFrame(onehot_encoded_features, columns=onehot_feature_names) print("\n独热编码后的数据 (无序类别特征):\n", df_onehot_encoded) # 3. 序数编码 (Ordinal Encoding) ordinal_encoder = OrdinalEncoder(categories=[['Low', 'Medium', 'High']]) # 指定类别顺序 df_encode['ordered_encoded'] = ordinal_encoder.fit_transform(df_encode[['ordered_feature']]) print("\n序数编码后的数据 (有序类别特征):\n", df_encode) # 4. Pandas get_dummies 实现独热编码 (更简洁常用) df_dummies = pd.get_dummies(df_encode, columns=['category_feature'], prefix='category') # prefix 指定前缀 print("\nPandas get_dummies 独热编码后的数据:\n", df_dummies)

代码详解:

  • LabelEncoder(): Scikit-learn 提供的标签编码器。

  • OneHotEncoder(sparse=False): Scikit-learn 提供的独热编码器,sparse=False 返回 NumPy dense array,而不是 sparse matrix。

  • onehot_encoder.get_feature_names_out(['category_feature']): 获取独热编码后的特征名称。

  • OrdinalEncoder(categories=[['Low', 'Medium', 'High']]): Scikit-learn 提供的序数编码器,categories 参数指定每个特征的类别顺序。

  • pd.get_dummies(df_encode, columns=['category_feature'], prefix='category'): Pandas 提供的 get_dummies 函数,可以快速进行独热编码,columns 参数指定要编码的列,prefix 参数指定编码后特征名称的前缀。

4. 特征缩放 (Feature Scaling)

特征缩放是将不同特征的值缩放到相似的范围,避免某些特征值过大或过小对模型训练产生不利影响。对于 LightGBM 这种树模型,特征缩放并非绝对必要,因为树模型对特征的尺度不敏感。然而,在以下情况下,特征缩放仍然可能是有益的:

  • 使用基于距离的算法: 如果模型中包含基于距离的算法(例如 KNN),特征缩放是必要的,否则尺度大的特征会主导距离计算。

  • 加速梯度下降: 对于某些梯度下降优化算法,特征缩放可以加速收敛。

  • 提升模型稳定性: 特征缩放可以提高模型的数值稳定性,避免因特征尺度差异过大而导致梯度爆炸或消失。

  • 特征重要性解释: 特征缩放后,特征重要性排序可能更合理。

常见的特征缩放方法包括:

  • 标准化 (Standardization): 将特征值转换为均值为 0,标准差为 1 的分布,也称为 Z-score 标准化。

  • 归一化 (Normalization): 将特征值缩放到 [0, 1] 或 [-1, 1] 区间,也称为 Min-Max 缩放。

  • RobustScaler: 基于四分位距进行缩放,对异常值更鲁棒。

代码实践 (Python - Scikit-learn):

import pandas as pd from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler # 示例数据 data_scale = {'feature1': [10, 20, 30, 40, 50], 'feature2': [100, 200, 150, 250, 300], 'target': [0, 1, 0, 1, 0]} df_scale = pd.DataFrame(data_scale) print("原始数据 (特征尺度差异大):\n", df_scale) # 1. 标准化 (StandardScaler) scaler_standard = StandardScaler() scaled_features_standard = scaler_standard.fit_transform(df_scale[['feature1', 'feature2']]) df_scaled_standard = pd.DataFrame(scaled_features_standard, columns=['feature1_scaled_standard', 'feature2_scaled_standard']) print("\n标准化后的数据:\n", df_scaled_standard) # 2. 归一化 (MinMaxScaler) scaler_minmax = MinMaxScaler() scaled_features_minmax = scaler_minmax.fit_transform(df_scale[['feature1', 'feature2']]) df_scaled_minmax = pd.DataFrame(scaled_features_minmax, columns=['feature1_scaled_minmax', 'feature2_scaled_minmax']) print("\n归一化后的数据:\n", df_scaled_minmax) # 3. RobustScaler scaler_robust = RobustScaler() scaled_features_robust = scaler_robust.fit_transform(df_scale[['feature1', 'feature2']]) df_scaled_robust = pd.DataFrame(scaled_features_robust, columns=['feature1_scaled_robust', 'feature2_scaled_robust']) print("\nRobustScaler 缩放后的数据:\n", df_scaled_robust)

代码详解:

  • StandardScaler(): Scikit-learn 提供的标准化缩放器。

  • MinMaxScaler(): Scikit-learn 提供的归一化缩放器。

  • RobustScaler(): Scikit-learn 提供的 RobustScaler 缩放器。

  • scaler.fit_transform(df_scale[['feature1', 'feature2']]): 先用 fit 方法学习缩放参数(例如均值和标准差),然后用 transform 方法进行缩放。

5. 数据集划分 (Data Splitting)

在模型训练之前,需要将数据集划分为训练集、验证集和测试集。

  • 训练集 (Training Set): 用于模型训练,模型从训练数据中学习参数。

  • 验证集 (Validation Set): 用于模型调参和选择,在训练过程中评估模型性能,调整超参数,防止过拟合。

  • 测试集 (Test Set): 用于最终评估模型性能,在模型训练完成后,用测试集评估模型的泛化能力。

常见的数据集划分方法包括:

  • 简单划分 (Train-Test Split): 将数据集随机划分为训练集和测试集,或者训练集、验证集和测试集。

  • 交叉验证 (Cross-Validation): 将训练集划分为 K 个 folds,轮流将其中一个 fold 作为验证集,其余 folds 作为训练集,进行 K 次训练和验证,得到 K 个验证结果的平均值,更可靠地评估模型性能。常见的交叉验证方法包括 K-Fold 交叉验证、Stratified K-Fold 交叉验证 (适用于类别不平衡数据)。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U