ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DAIL-SQL源码深读:从sql2skeleton到Jaccard相似度,SQL骨架提取的完整代码实现

DAIL-SQL源码深读:从sql2skeleton到Jaccard相似度,SQL骨架提取的完整代码实现 DAIL-SQL源码深读从sql2skeleton到Jaccard相似度SQL骨架提取的完整代码实现【免费下载链接】DAIL-SQLA efficient and effective few-shot NL2SQL method on GPT-4.项目地址: https://gitcode.com/gh_mirrors/da/DAIL-SQLDAIL-SQL 是基于 GPT-4 的高效少样本 NL2SQL自然语言转 SQL方法在 Spider 基准上仅用每题约 1600 tokens 的成本取得 86.2% 的执行准确率一度登顶 Spider 榜单。本文带你完整读懂它的两大核心源码——sql2skeleton的 SQL 骨架提取与 Jaccard 相似度计算看看骨架相似度是如何驱动少样本示例选择的。 为什么 NL2SQL 需要SQL 骨架Few-shot 学习的关键在于示例选得好不好。DAIL-SQL 的核心假设是LLM 学习的不是问题→SQL的逐字映射而是问题→SQL 结构的映射。表名、列名、字面值都是跨领域知识对结构学习而言是噪声。论文在 zero-shot 阶段的消融实验发现把结构知识编码成 SQL 语句Code Representation是 GPT-4 下最稳的问题表示此外外键信息和不要输出解释的规则也被验证为有效组件于是少样本阶段的选什么样的示例就成为胜负手。DAIL-SQL 的答案是用 SQL 骨架把 SQL 抽象成纯结构模板再用 Jaccard 相似度快速筛出结构一致的示例。整个过程不依赖额外模型纯字符串操作成本极低。 骨架在哪里生成、在哪里被消费先看数据流全貌三个入口脚本依次执行data_preprocess.py生成表结构 JSON 等数据资产generate_question.py为每道测试题生成 few-shot 提示词骨架筛选发生在这里ask_llm.py调用 GPT-4 得到最终 SQL骨架的生产发生在 utils/data_builder.py 的data_pre_process中约 L179–L208对每一条训练样本用其所属库的 schema 调用sql2skeleton把结果存进query_skeleton字段与question、question_pattern问题模式一起构成示例的完整画像。骨架的消费有两个去处示例筛选prompt/ExampleSelectorTemplate.py 中 9 种选择器其中 4 种直接拿jaccard_similarity当硬门槛质量统计prompt/PromptICLTemplate.py 的record_example_qualityL14–L36为每个提示词记录所选示例骨架与目标骨架的平均 Jaccard 相似度运行结束后以Example quality打印方便横向对比不同选择策略。️ sql2skeleton 逐段拆解从原始 SQL 到结构模板sql2skeleton位于 utils/utils.py 的 L313–L387分三步走。第一步SQL 归一化函数入口先调用sql_normalizationL185–L310它是一个由 6 个纯函数用 lambda 串成的管线processing_func lambda x: remove_table_alias( add_asc(lower(white_space_fix( double2single(remove_semicolon(x))))))逐个职责white_space_fix用sql_metadata的 Parser 重排空白lower全小写单引号字符串内保持原样remove_semicolon去分号double2single双引号统一为单引号add_asc给order by补上隐式的ascremove_table_alias把t1、T1这类别名统一展开成真实表名——展开别名很关键否则t1.name和singer.name会被当成两个不同 token骨架就不稳定了。第二步分词与掩码归一化后交给Parser(sql)分词然后按 6 条规则把具体值替换成_掩码对象判定方式表名命中当前库 schema 的表名列表列名 /表.列/表.*命中由 schema 动态构建的列名列表字符串字面值单引号开头且结尾正整数 / 负整数 / 浮点数isdigit()、isNegativeInt、isFloat注意列名列表是按目标库 schema 现算的所以不同数据库的同一条 SQL 会先被规范化到统一命名再抽象成骨架。第三步后处理压缩最后几段是精心设计的结构等价化简L354–L385on _ _、on _ _ and _ _整段删除——多表关联的连接条件不参与结构判断连续 join 链_ join _ join _用正则压成一个_ join __ , _ , _列清单压成一个_where _ _等 6 种比较运算压成where _order by后非_的残留 token 强制改为_压缩多余空格。一次完整的变换长这样SELECT name FROM singer JOIN concert ON singer.id concert.id WHERE concert.city Paris ORDER BY name ↓ 归一化 掩码 select _ from _ join _ on _ _ where _ _ order by _ asc ↓ ON/WHERE 压缩 select _ from _ join _ where _ order by _ asc jaccard_similarity 拆解多重集版 Jaccard实现位于 utils/utils.py 的 L411–L429只有 20 行def jaccard_similarity(skeleton1, skeleton2): tokens1 skeleton1.strip().split( ) tokens2 skeleton2.strip().split( ) # 统计每个 token 的频次collections.defaultdict(int) intersection 0 for t in token_dict1: if t in token_dict2: intersection min(token_dict1[t], token_dict2[t]) union (len(tokens1) len(tokens2)) - intersection return float(intersection) / union这里的细节值得留意它不是经典集合 Jaccard而是多重集版本。骨架里_会大量重复select _ from _ where _中_出现 3 次如果先去重select _ from _和select _ from _ where _都会变成{select, from, _, where}之类的小集合区分度全无保留频次后_的个数差异被如实计入分母。手动算一笔账骨架 A骨架 B交集并集相似度select _ from _4 tokenselect _ from _ where _6 token460.67select _ from _select _ from _441.00可以看出骨架完全相同才接近 1.0差一个 where 子句就掉到 0.67 以下。所以代码里统一设置的 0.85 阈值self.threshold 0.85见 prompt/ExampleSelectorTemplate.py是一道非常严格的结构一致门槛。 DAIL-SQL 的示例选择嵌入距离 骨架阈值双重机制DAIL-SQL 最终采用的选择器是EuclideanDistanceQuestionMaskPreSkeletonSimilarThresholdSelector对应命令行参数EUCDISMASKPRESKLSIMTHR常量定义在 utils/enums.py 的SELECTOR_TYPE流程是语义粗筛 结构精筛把问题中的表名/列名/值用 schema linking 信息替换成mask/unk消除领域词汇干扰用all-mpnet-base-v2句向量编码按欧氏距离升序排好所有训练示例第一遍按相似度顺序遍历只收jaccard_similarity 0.85的示例——语义相近且 SQL 结构完全一致兜底第二遍若凑不满num_example个再遍历一遍收集阈值以下的示例把坑位填满保证 k-shot 数量稳定。这个先严后松的双层循环是全文最体现工程权衡的地方宁缺毋滥地优先喂结构一致的示例同时保证上下文长度不被浪费。另外选择器还支持cross_domain开关强制从不同数据库中挑示例避免跨领域评测时数据泄漏。若手里已有预生成 SQL如 results/ 目录下的预跑结果还可以换用EUCDISPRESKLSIMTHR它直接拿预生成 SQL 的骨架pre_skeleton做筛选省掉一次 LLM 调用。 端到端效果骨架筛选值不值few-shot 阶段的组织实验Spider-devGPT-4直观回答了这个问题——DAIL 的曲线始终压在 Full-Information全量信息示例和 SQL-Only只给 SQL之上且 3-shot 后就基本进入平台期本地复现只需三步代码获取git clone https://gitcode.com/gh_mirrors/da/DAIL-SQL # 数据预处理 python data_preprocess.py # 生成 few-shot 提示词骨架相似度筛选 python generate_question.py --data_type spider --split test \ --prompt_repr SQL --k_shot 9 --example_type QA \ --selector_type EUCDISMASKPRESKLSIMTHR --max_seq_len 4096 # 调用 GPT-4--n 5 --temperature 1.0 即开启自洽投票 python ask_llm.py --model gpt-4 --question [prompt_dir] --openai_api_key [key] 总结两个核心函数的文件地图模块位置职责sql_normalizationutils/utils.py L185–L310小写、别名展开、补asc等 6 步归一化sql2skeletonutils/utils.py L313–L387掩码表/列/字面值输出纯结构骨架jaccard_similarityutils/utils.py L411–L429骨架间多重集 Jaccard 相似度data_pre_processutils/data_builder.py L179–L208为训练集预计算query_skeleton示例选择器prompt/ExampleSelectorTemplate.py9 种策略0.85 阈值双遍筛选质量统计prompt/PromptICLTemplate.py L14–L36记录每批示例的平均骨架相似度DAIL-SQL 这套设计的精妙之处在于用几百行纯字符串代码把哪条训练示例和当前问题结构最像这件原本需要检索模型才能做的事变成了 O(n) 次词频统计——语义相似度负责选得近骨架相似度负责选得对这正是它 1600 tokens 就能打赢 86.2% 的秘密。【免费下载链接】DAIL-SQLA efficient and effective few-shot NL2SQL method on GPT-4.项目地址: https://gitcode.com/gh_mirrors/da/DAIL-SQL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进