SQLFlow 中 XGBoost 模型的 Feature Column 支持:从 COLUMN 子句到 DMatrix 的完整设计与实现

📅 发布时间:2026/10/13 12:29:50
SQLFlow 中 XGBoost 模型的 Feature Column 支持:从 COLUMN 子句到 DMatrix 的完整设计与实现
人工智能机器学习后端编译器【免费下载链接】sqlflowBrings SQL and AI together.项目地址https://gitcode.com/gh_mirrors/sq/sqlflow点击查看免费下载SQLFlow 通过扩展 SQL 语法中的COLUMN子句声明式地完成数据预处理本文围绕设计文档 xgboost_feature_column.md 展开讲清楚 XGBoost 模型在 SQLFlow 中如何支持 feature column为什么 TensorFlow 的 feature column API 无法直接复用到 XGBoost、SQLFlow 最终选择了哪种预处理方案、COLUMN子句如何被编译成 Python 转换代码以及训练/预测/评估三个阶段如何保证同一份变换逻辑并附带模型导出 PMML/Treelite 的设计取舍。读完后你可以理解 SQLFlow XGBoost 训练流水线中“数据库行 → 变换 → LIBSVM 文件 → DMatrix”的完整链路并能定位到仓库中对应的源码实现。背景COLUMN 子句与 XGBoost 的缺口SQLFlow 扩展了 SQL 语法以支持声明式的数据预处理核心载体就是COLUMN子句。例如用CATEGORY_HASH把一个字符串列解析为整数列这是 NLP 类任务中非常常见的预处理操作SELECT string_column1, int_column2, class FROM xgboost.gbtree TO TRAIN XGBoostModel COLUMN INDICATOR(CATEGORY_HASH(string_column1, 10)), int_column2 LABEL class INTO sqlflow_xgboost_model.my_model;在 TensorFlow 模型中COLUMN子句会在 codegen 阶段被转换成 TensorFlow 的 feature column API 调用可用的关键词如下引自设计文档SQLFlow 关键词TensorFlow API说明DENSEtf.feature_column.numeric_column未经任何预处理的原始数值特征列BUCKETtf.feature_column.bucketized_column将输入整数按 boundaries 切分为 bucket idCATEGORY_IDtf.feature_column.categorical_column_with_identity整数特征列的恒等映射CATEGORY_HASHtf.feature_column.categorical_column_with_hash_bucket用哈希算法把字符串或整数映射为 category idSEQ_CATEGORY_IDtf.feature_column.sequence_categorical_column_with_identityCATEGORY_ID 的序列数据版本CROSStf.feature_column.crossed_column用哈希算法组合多个类别特征INDICATORtf.feature_column.indicator_column将 category id 转换为 multi-hot 表示EMBEDDINGtf.feature_column.embedding_column将 category id 转换为 embedding 表示但 XGBoost 没有类似 TensorFlow 的 feature column API。设计文档指出的原始问题是XGBoost 模型的COLUMN子句当时只支持c1, c2, c3这样的简单列名任何数据预处理都不被支持导致无法用 XGBoost 训练接受字符串列作为输入的模型。这正是本文主题要解决的缺口。SQLFlow 内 XGBoost 训练流程三步走设计文档给出的 SQLFlow 内 XGBoost 训练流程分为三步从数据库读数据调用db.db_generator()方法返回一个 Python 生成器generator逐行产出数据库中的记录落盘 SVM 文件调用dump_dmatrix()方法把原始数据写入一个 LIBSVM 格式文件该文件可以直接被加载为 XGBoost 的DMatrix训练把落盘文件加载为DMatrix调用xgboost.trainAPI 开始训练。当前仓库中这条链路的实现与设计文档一一对应位于 dataset.pyxgb_dataset()通过db.db_generator(conn, dataset_sql, label_meta)()拿到数据生成器然后循环调用dump_dmatrix()分块写出 LIBSVM 文件再逐块yield load_dmatrix(...)给训练代码消费dump_dmatrix()中先通过db.read_features_from_row(...)解析出每行特征若存在transform_fn则先执行features transform_fn(features)再逐特征序列化为index:value的 LIBSVM 行。transform_fn的插入点正是在“写 SVM 文件”这一步之前这是整个 feature column 设计的核心位置训练入口 train.py 中train()函数把transform_fn一路透传给xgb_dataset最终xgb.train(model_params, per_batch_dmatrix, evalswatchlist, ...)完成训练rank 0 节点上通过save_model_to_local_file和save_metadata落盘模型与元数据。三种候选方案与 Method 1 的胜出原因设计文档对比了三种支持 feature column 的候选方案值得完整保留其论证逻辑Method 1在 Step 1/Step 2读数据、写 SVM 文件期间做变换。数据预处理在写 SVM 文件之前完成。该方法适用于离线训练单机与分布式、预测和评估因为训练、预测、评估可以生成完全相同的变换 Python 代码。但它不适合在线服务因为在线服务通常使用 C/Java 等其他库或语言无法执行 SQLFlow 生成的 Python 变换代码。Method 2修改 XGBoost 的训练迭代在每个迭代中插入变换代码。但修改 XGBoost 训练迭代并不容易且与 Method 1 同样不适合在线服务。Method 3把数据预处理与模型训练组合成一个 sklearn pipeline。sklearn pipeline 可以通过 sklearn2pmml 或 Nyoka 保存为 PMML 文件因此该方案适用于单机训练、离线预测、离线评估和在线服务分布式训练可以用 Dask 实现。但 Dask 并非真正使用原生 sklearn API 构建 pipeline而是 mock 了这些 API因此分布式训练出来的 pipeline 无法直接存为 PMML此外 sklearn pipeline 支持的预处理 transformer 很少例如“把字符串哈希为单个整数”这类操作 sklearn 本身并不支持自行新增的 transformer 也无法导出 PMML。文档给出的结论是最关键的问题是数据预处理 transformer 如何被保存以供在线服务使用。作者调研了公司内部在线服务平台如 Arks后发现预处理步骤通常不会保存在 PMML 或 Treelite 文件中而是由平台提供一组插件供用户选择预处理步骤而不是从 PMML/Treelite 文件加载。因此最终选择Method 1实现 XGBoost 的 feature column。这个结论直接影响后面的实现形态——预处理必须写成可在训练/预测/评估中重复生成的纯 Python 代码而模型文件本身只保存树模型。Transformer 的类设计设计文档中给出的 transformer 类结构伪代码如下展示了组合式设计class BaseFeatureColumnTransformer(object): def __call__(self, inputs): raise NotImplementedError() def set_column_names(self, column_names): self.column_names column_names class NumericColumnTransformer(BaseFeatureColumnTransformer): # key 是 SELECT 语句中的列名 def __init__(self, key): self.key key def set_column_names(self, column_names): BaseFeatureColumnTransformer.set_column_names(column_names) self.index self.column_names.index(self.key) # inputs 是所有原始列数据 # NumericColumnTransformer 只取 index 指定的列 def __call__(self, inputs): return inputs[self.index] # CategoricalColumnTransformer 是所有类别列的基类 # 用于做类型检查例如 INDICATOR 只接受类别列作为输入 class CategoricalColumnTransformer(BaseFeatureColumnTransformer): pass class BucketizedColumnTransformer(CategoricalColumnTransformer): def __init__(self, key, boundaries, default_valueNone): self.key key self.boundaries boundaries self.default_value default_value # ... 按 boundaries 分桶 class CrossedColumnTransformer(BaseFeatureColumnTransformer): # ... 多列交叉哈希 pass class ComposedFeatureColumnTransformer(BaseFeatureColumnTransformer): def __init__(self, *transformers): self.transformers transformers def set_column_names(self, column_names): BaseFeatureColumnTransformer.set_column_names(column_names) for t in self.transformers: t.set_column_names(column_names) def __call__(self, inputs): return [t(inputs) for t in self.transformers]按照设计COLUMN INDICATOR(CATEGORY_HASH(string_column1, 10)), int_column2这样的列子句最终会被转换成transform_fn ComposedFeatureColumnTransformer( IndicatorColumnTransformer(CategoryColumnWithHashBucketTransformer( keystring_column1, hash_bucket_size10)), NumericColumnTransformer(keyint_column2))transform_fn随后被传入runtime.xgboost.train方法在训练过程中db.db_generator(...)产出的原始数据通过transform_fn.__call__完成变换。set_column_names会在运行时拿到表 schema 时被调用一次用于在 Python 运行时推断key对应的列下标。变换后的数据写入 SVM 文件再被后续训练步骤加载。另一个关键约束是预测/评估阶段必须执行与训练完全相同的预处理因此需要保存训练时的 feature column 定义让预测/评估阶段的 codegen 生成与训练阶段相同的变换代码。设计文档同时明确EMBEDDING在该设计中不被支持因为 embedding feature column 可能包含可训练参数而这些参数无法在 XGBoost 的训练过程中被更新。实际实现runtime/xgboost/feature_column.py仓库中的落地实现见 feature_column.py与文档设计高度一致且比文档伪代码更完整基类与组合BaseColumnTransformer定义了_set_feature_column_names(names)/__call__(inputs)/get_feature_column_names()接口CategoricalColumnTransformer额外要求实现num_classes()ComposedColumnTransformer(feature_column_names, *columns)负责把整组列按 SELECT 顺序对齐并依次变换——注意它与文档伪代码的一个差异实现里组合器需要显式传入feature_column_names在构造时就完成_set_feature_column_names列下标因此在生成代码时即可确定哈希实现CategoricalColumnWithHashBucketTransformer使用hashing(x) % hash_bucket_size其中hashing基于hashlib.sha1而不是 Python 内建hash——源码注释明确解释了原因内建hash在不同会话间结果不确定受字符串哈希随机化影响会导致训练与预测结果不一致分桶实现BucketizedColumnTransformer使用np.searchsorted(self.boundaries, x, sideright)完成分桶构造时断言boundaries必须升序num_classes()返回len(boundaries) 1identity 与词表CategoricalColumnWithIdentityTransformer支持default_value越界值要么映射到默认值要么抛ValueErrorCategoricalColumnWithVocabularyList直接按词表索引映射indicatorIndicatorColumnTransformer先断言输入必须是CategoricalColumnTransformer且不是稀疏特征再对每个标量值生成num_classes维的 one-hot 向量dense/sparse 统一处理apply_transform_on_value对长度为 1 的 dense 输入和(indices, values, dense_shape)的稀疏输入分别应用逐元素变换这是 XGBoost 支持稀疏特征如KEYVALUE列的基础。行为正确性由 feature_column_test.py 覆盖例如categorical_column_with_vocabulary_list对cat/dog/apple分别映射为0/1/2indicator_column得到[1,0,0]等 one-hot 向量bucketized_column在boundaries[-1, 2, 3, 4]下把-2 → 0、2 → 2、5 → 4等断言逐条验证。codegenCOLUMN 子句如何变成 Python 代码Go 侧的代码生成把 IR 中的 feature column 翻译为上面这套 Python 调用。核心函数是 codegen_feature_column.go 中的GenerateFeatureColumnCode(fc ir.FeatureColumn, module string)对 XGBoost 模块前缀为xgboost生成形如xgboost_extended.feature_column.numeric_column(col, shape...)、xgboost_extended.feature_column.bucketized_column(..., boundaries...)、xgboost_extended.feature_column.categorical_column_with_hash_bucket(key..., hash_bucket_size10, dtype...)、xgboost_extended.feature_column.indicator_column(...)的代码串对 XGBoost 模块显式拒绝三种列类型返回错误SEQ_CATEGORY_ID is not supported in XGBoost models、CROSS is not supported in XGBoost models、EMBEDDING is not supported in XGBoost models——这正是设计文档中“EMBEDDING 不受支持”的编译期兜底。在 XGBoost codegen 入口 codegen.go 中deriveFeatureColumnCodeAndFieldDescs遍历trainStmt.Features[feature_columns]逐列调用GenerateFeatureColumnCode并把生成的代码串拼接为featureColumnCode同时收集所有FieldDesc序列化进 JSON。该函数在Train/DistTrain、Pred、Evaluate三处被重复调用且输入来自同一份 TrainStmt——预测与评估语句携带训练语句的 feature column 信息从而保证“预测/评估生成与训练相同的变换代码”这一设计要求在 codegen 层被结构性保证。生成的训练脚本骨架见 template_train.go关键片段与设计文档描述的transform_fn用法完全一致feature_column_names [string_column1, int_column2] # NOTE: in the current implementation, we are generating a transform_fn from COLUMN clause. # The transform_fn is executed during the process of dumping the original data into DMatrix SVM file. feature_column_list [xgboost_extended.feature_column.indicator_column( xgboost_extended.feature_column.categorical_column_with_hash_bucket(keystring_column1, hash_bucket_size10, dtype...)), xgboost_extended.feature_column.numeric_column(int_column2, shape...)] transform_fn xgboost_extended.feature_column.ComposedColumnTransformer(feature_column_names, *feature_column_list) train(datasource..., selectSELECT ..., ... transform_fntransform_fn, feature_column_code..., ...)同样的transform_fn构造模式也出现在预测模板 template_pred.go、评估模板 template_evaluate.go 和解释模板 template_explain.go 中——四个阶段train/predict/evaluate/explain共享同一份由COLUMN子句派生的代码串这就是 Method 1 “相同变换代码用于训练、预测和评估”的具体落点。跨阶段一致性feature_column_code 的保存与反序列化设计文档强调预测/评估必须执行与训练相同的预处理因此需要保存训练时的 feature column 定义。仓库中这一需求的实现有两条路径codegen 内联feature_column_code以 Python 代码串形式直接嵌入生成脚本如上并作为字符串参数feature_column_code...传入train()模型元数据在 PAI 分布式场景下train.py 的save_model()会把feature_column_code连同模型参数、字段元数据一起写入模型描述文件xgboost_model_desc预测时由生成的脚本通过oss.load_metas(...)读回并eval重建 transformer见 pai/template_xgboost.go(model_params, train_params, feature_metas, feature_column_names, label_meta, feature_column_code) oss.load_metas({{.OSSModelDir}}, xgboost_model_desc) feature_column_transformers eval([{}].format(feature_column_code)) transform_fn xgboost_extended.feature_column.ComposedColumnTransformer(feature_column_names, *feature_column_transformers)为了让 transformer 对象本身可 JSON 序列化/反序列化PAI 分片下载数据的子进程间传参用feature/column.py 提供了JSONEncoderWithFeatureColumn/JSONDecoderWithFeatureColumn序列化时把列对象转为{__sqlflow_fc_class_name__: ..., __sqlflow_fc_data__: ...}结构反序列化时按类名重建例如 dataset.py 末尾的pai_download_table_data_worker就依赖该机制在每个 worker 子进程里还原transform_fn。支持矩阵XGBoost 能用哪些 feature column综合 compile.pycompile_ir_feature_columns按EstimatorType分发与 codegen_feature_column.go 中的断言XGBoost 模型下各类COLUMN关键词的支持情况为SQLFlow 关键词XGBoost 支持实现说明DENSE数值列支持numeric_column(key, shape..., dtype...)dtype 取 int64/float32/stringBUCKET支持bucketized_column底层np.searchsorted(boundaries, x, sideright)CATEGORY_ID支持有 vocabulary 时编译为categorical_column_with_vocabulary_list否则categorical_column_with_identity支持default_valueCATEGORY_HASH支持categorical_column_with_hash_bucketSHA-1 哈希取模结果稳定可复现INDICATOR支持indicator_column只接受类别列输出 one-hotSEQ_CATEGORY_ID不支持编译期直接断言SEQ_CATEGORY_ID is not supported in XGBoost modelsWEIGHTED_CATEGORY不支持同上断言WEIGHTED_CATEGORY is not supported in XGBoost modelsCROSS不支持codegen 阶段报错CROSS is not supported in XGBoost modelsEMBEDDING不支持可训练参数无法参与 XGBoost 训练过程文档明确排除此外从 codegen.go 的deriveFeatureColumnCodeAndFieldDescs看XGBoost 要求且仅要求一个 feature targetfeature_columnslabel 必须是数值列否则 codegen 报错。模型导出PMML 与 TreeliteXGBoost 提供两类训练 APIxgboost.train返回 Booster与xgboost.XGBClassifier/XGBRegressor/XGBRanker。设计文档的取舍是xgboost.train返回的 Booster 可以保存为 Treelite API 可加载的格式但不能直接被 PMML API 加载sklearn2pmml / Nyoka 只能导出由XGBClassifier/XGBRegressor/XGBRanker构建的模型而这组 API 在 SQLFlow 场景下不好用必须先区分分类/回归/排序构造函数里 Booster 参数与训练参数混在一起如booster是 Booster 参数n_estimators是训练参数都出现在构造函数中难以区分参数命名与xgboost.train不一致n_estimators对应num_boost_round。因此设计选择用xgboost.train训练导出走两条路PMMLBooster.load_model()加载训练结果 → 根据 objective 构造对应的XGBClassifier/XGBRegressor/XGBRanker→load_model()重新加载 → 包成 sklearn pipeline → 用 sklearn2pmml 或 Nyoka 存为 PMMLTreelite用Booster.save_model()保存的模型文件通过 Treelite 的Model.from_xgboost导入。仓库中这条导出链路的实现见 save.py 的save_model_to_local_file它从model_params[objective]前缀判断模型类型binary:/multi:走XGBClassifier并伪造_le、classes_等 sklearn 侧元数据reg:走XGBRegressorrank:走XGBRanker把scikit_learn元信息set_attr进 Booster 后save_model随后构建PMMLPipeline调用sklearn2pmml。从源码结构看当前版本的sklearn2pmml调用部分处于注释状态仅完成 Booster 序列化与元信息注入这与设计文档“Booster 可被 Treelite 加载但 PMML 导出是后续步骤”的表述相符而 train.py 中 PAI 的save_model()已按my_model、my_model.pmml、model_meta.json三个文件规划了产物结构可以推断 PMML 落盘是既定的完整形态。小结把设计文档的脉络落到当前仓库代码上SQLFlow 对 XGBoost feature column 的支持可以概括为一条清晰链路COLUMN子句解析为 IR →GenerateFeatureColumnCode生成xgboost_extended.feature_column.*调用串训练/预测/评估/解释四处 codegen 复用同一逻辑→ 运行时构造ComposedColumnTransformer作为transform_fn→ 在dump_dmatrix写 LIBSVM 文件前对原始行做变换 →xgb.train训练 → 模型与feature_column_code一并持久化PAI 场景经 OSS预测/评估阶段读回同一份变换代码保证一致性。方案上选择 Method 1预处理与训练解耦、代码可重复生成而非 sklearn pipeline正是权衡了在线服务生态与 PMML 可保存性的结果SEQ_CATEGORY_ID、WEIGHTED_CATEGORY、CROSS、EMBEDDING四类关键词在 XGBoost 下被编译期显式拒绝是阅读该功能源码时最容易踩到的边界。赞分享人工智能机器学习后端编译器【免费下载链接】sqlflowBrings SQL and AI together.项目地址https://gitcode.com/gh_mirrors/sq/sqlflow点击查看免费下载相关推荐nanoGPT OpenWebText 预处理走查从 800 万篇文档到 17GB train.binnanoGPT OpenWebText 预处理走查从 800 万篇文档到 17GB train.bin 要在 nanoGPT 里复现 GPT 2数据集预处理人工智能机器学习后端编译器SQLFlow特征工程终极指南COLUMN子句的10个高级用法揭秘SQLFlow特征工程终极指南COLUMN子句的10个高级用法揭秘 SQLFlow将SQL与AI完美融合为数据科学家提供了强大的特征工程能力。其中COLUM人工智能机器学习后端编译器TanStack Table Lit 列排序Column Ordering指南从 feature 注册到拖拽重排的完整实现TanStack Table Lit 列排序Column Ordering指南从 feature 注册到拖拽重排的完整实现 导读 本文基于 TanStac前端UI组件上一篇一次扫码把QQ空间历史说说全部导出为Excel和本地图片下一篇Hackintool终极指南黑苹果系统配置的瑞士军刀创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考