对类别特征的高效处理是许多基于树的算法(包括梯度提升模型)面临的一项重要难题。传统方法通常涉及独热编码(OHE)等转换,这会显著增加特征维度,特别是对于高基数特征(即具有许多独特类别的特征)。这种维度爆炸会导致内存消耗增加和训练时间变长,与此类模型通常期望的效率性能相悖。标签编码虽然内存效率高,但会引入任意的数值顺序,这可能误导树的分裂算法。LightGBM 通过提供优化的内置支持来直接解决这个问题,在许多情况下消除了手动预处理(如 OHE)的需求。这是一个显著优势,特别是在处理包含大量或高基数类别列的数据集时。原生类别特征分裂LightGBM 无需用户预先将类别特征数值编码,它能够在树构建过程中直接识别并使用它们。核心思想是通过考量类别与训练目标的关系来寻找最佳划分(分裂)。当在特定树节点考虑对类别特征进行分裂时,LightGBM 采用一种专用算法,通常基于 Fisher (1958) 描述的方法,用于寻找最佳划分。以下是概述:收集统计量: 对于到达当前节点的每个类别数据,LightGBM 计算与目标函数相关的统计量。通常,这涉及对属于该类别的所有实例的梯度和海森量进行求和:$G_c = \sum_{i \in \text{类别 } c} g_i$ 和 $H_c = \sum_{i \in \text{类别 } c} h_i$,其中 $g_i$ 和 $h_i$ 分别是实例 $i$ 的梯度和海森量。类别排序: 类别随后根据从这些统计量计算出的相关值进行排序,通常是计算叶子输出时使用的比率:$G_c / H_c$ (或类似,可能经过正则化)。这种排序使得对目标函数有相似影响的类别彼此靠近。寻找最佳分裂点: LightGBM 在这个已排序的类别列表中寻找最佳分裂点。它评估潜在的分裂,将类别划分为两个子集(例如,{类别 A, 类别 C} 对比 {类别 B, 类别 D})。目标是找到能够最大化增益(损失减少)的划分,类似于使用直方图为数值特征寻找分裂点的方式。最佳分裂有效地将导致相似预测调整的类别分组。对于具有许多类别的特征,这种方法比 OHE 效率高得多,因为它在排序后只考虑 $k-1$ 个潜在分裂点(其中 $k$ 是唯一类别的数量),而不是创建 $k$ (或 $k-1$) 个新的二进制特征。LightGBM 中的实现为了运用此功能,您通常需要告知 LightGBM 哪些特征是类别型的。有两种主要方法可以实现这一点:Pandas category 数据类型: 如果您正在使用 Pandas DataFrame,请确保您的类别列具有 category 数据类型。LightGBM 的 Scikit-learn API 通常会自动检测并处理这些。import pandas as pd import lightgbm as lgb # 示例数据 data = {'numeric_feat': [1.2, 3.4, 0.5, 2.1], 'category_feat': ['A', 'B', 'A', 'C']} df = pd.DataFrame(data) # 转换为 category 数据类型 df['category_feat'] = df['category_feat'].astype('category') # LightGBM 现在可以潜在地使用其原生处理方式 # (取决于 API 使用情况 - Dataset 对象更明确)categorical_feature 参数: 在模型初始化或创建 LightGBM Dataset 对象时,通过 categorical_feature 参数明确提供类别特征的索引或名称。这是最可靠的方法。# 假设使用上一个示例中的 df X = df[['numeric_feat', 'category_feat']] y = [0, 1, 0, 1] # 明确告知 LightGBM 哪个特征是类别型的 # 使用特征名(Pandas 推荐) lgb_model = lgb.LGBMClassifier() lgb_model.fit(X, y, categorical_feature=['category_feat']) # 或使用列索引(如果使用 NumPy 数组) X_np = df.to_numpy() # 对于 NumPy 数组,可能需要先使用 OrdinalEncoder # 如果 'category_feat' 是第1列: # lgb_model.fit(X_np, y, categorical_feature=[1]) # 使用 Dataset 对象(通常为了性能而优先选择) # 对于 Dataset,需要先将类别编码为整数 from sklearn.preprocessing import OrdinalEncoder encoder = OrdinalEncoder() X_encoded = X.copy() X_encoded['category_feat'] = encoder.fit_transform(X[['category_feat']]) lgb_data = lgb.Dataset(X_encoded, label=y, feature_name=['numeric_feat', 'category_feat'], categorical_feature=['category_feat']) # 参数 = {...} # bst = lgb.train(参数, lgb_data)注意: 当使用 lgb.Dataset 对象时,类别特征必须编码为非负整数(0, 1, 2,...)。OrdinalEncoder 可以实现这一点。LightGBM 随后将这些整数解释为不同的类别,而不是具有序数关系,根据 categorical_feature 参数的指示。LightGBM 方法的优势效率: 避免了与高维 OHE 相关的内存和计算开销。训练速度可以显著加快,特别是对于高基数特征。有效性: 相比于 OHE 或朴素标签编码,可能带来更好的模型准确性。通过根据类别对目标的影响进行分组,它可以找到更有意义的关系,比独立处理每个类别 (OHE) 或施加任意顺序 (标签编码) 更好。简洁性: 减少了对复杂特征工程步骤的需求,特别是针对类别变量的。调优参数LightGBM 提供了参数来微调其类别处理:max_cat_to_onehot:(整数,默认值=4) 如果特征中唯一类别的数量小于或等于此值,LightGBM 可能会使用独热编码而非其原生分裂逻辑,因为对于基数非常低的特征,OHE 可能更快。cat_smooth:(浮点数,默认值=10.0) 对每个类别计算出的统计量 ($G_c / H_c$) 进行平滑处理。这有助于防止过拟合,特别是对于节点中样本较少的类别。它基于所有类别的平均值添加一个先验。cat_l2:(浮点数,默认值=10.0) 专门应用于类别分裂的 L2 正则化惩罚。与其他方法的比较尽管非常有效,LightGBM 的方法是处理提升模型中类别特征的几种高级方法之一。第6章将详细介绍 CatBoost,CatBoost 采用不同的、通常更精巧的技术,例如有序目标统计和自动特征组合生成,专门设计用于有效对抗目标泄漏和类别交互建模。传统上,XGBoost 需要手动编码(如 OHE 或目标编码)才能进行训练,尽管最新版本已添加了对类别数据的实验性支持。LightGBM 的原生处理方式对于许多涉及类别数据的问题提供了一种强大且计算高效的默认方案,与其追求速度和可扩展性的整体设计理念非常契合。