深入UltraData-Code-L2-Classifier实现原理:路径哈希技巧如何防止相关性模型走捷径
【免费下载链接】UltraData-Code-L2-Classifier项目地址: https://ai.gitcode.com/OpenBMB/UltraData-Code-L2-Classifier
UltraData-Code-L2-Classifier 是一组面向 11 种编程语言的文件级代码打分器,用于从海量代码语料中筛选出约 400B tokens 的算法导向数据(UltraData-Code-L2)。它由三个小模型组成:文件角色分类器 C、算法相关性模型A、代码质量模型 Q,三者共用同一个 1024 维代码 embedding。有意思的是,相关性模型还会把"文件在仓库中的路径"当作特征输入——而这一特征里藏着一套防止模型走捷径的精巧设计。本文带你逐层拆解 scripts/path_features.py 中的路径哈希技巧。
一、背景:为什么相关性打分要"看一眼"文件路径?
在 UltraData-Code 的分层数据管线中,L2 层负责"算法导向精筛":把 L1 的标准化自然代码,筛成集中了可解释计算与问题求解逻辑的高质量数据。整个 UltraData-Code L0-L3 构建流程如下图所示,L2 正是其中承上启下的一步:
相关性模型判断"这个文件是否包含算法逻辑"时,代码内容是最主要的证据,但文件路径本身也携带先验信号:algorithms/目录下更可能是算法题解,tests/、utils/下的文件角色相对明确。于是 A 模型的输入特征被拼接为:
features = [ embedding (1024维, 代码内容) | path_hash (256维, 路径哈希) × 0.4 ]注意0.4这个权重:路径向量经过 L2 归一化后再被压低,保证它只是"辅助提示",内容 embedding 始终占主导地位(拼接逻辑见 scripts/path_features.py)。
二、捷径风险:模型可能学会"抄答案" 🔎
数据筛选模型有一个经典陷阱:捷径(shortcut)。
设想训练数据里混着大量基准测试目录——leetcode/、codeforces/、humaneval/……模型很快会发现:只要路径里含这些词,文件就几乎总是"算法相关"。这条规则在训练集上命中率极高,模型会优先记住它,而不去理解代码内容。这样的筛选器换一批真实仓库就会"翻车":它识别的是目录名,而不是算法本身。
对于要直接用于数据生产的打分器来说,这种捷径必须从特征层面掐灭,而不能指望模型自己"想通"。
三、防捷径设计①:先剥离基准测试名称 🛡️
path_features.py 中的sanitize_benchmark_path会在任何特征化之前做"消毒":把路径统一转小写后,用正则批量剔除一整组基准名——leetcode、codeforces、atcoder、humaneval(含 plus/x 变体)、mbpp、ds_1000、cruxeval、bigcodebench、livecodebench、acm等,并且兼容-、_、.等分隔写法。
效果是:一个真实位于solutions/leetcode/322-coin-change.py的文件,进入模型的路径特征里已经没有 "leetcode" 这个标签,只剩下solutions、coin、change、322这类中性 token。模型再也无法靠"看到基准名就加分"的捷径刷准确率,只能回到内容本身 + 剩余路径语义去判断相关性。
四、防捷径设计②:哈希技巧 + 去位置化
剥离基准名之后,路径要变成一个固定宽度的向量才能与 embedding 拼接。scripts/path_features.py 使用的是经典hashing trick,算法名在 checkpoint 中记为crc32_signed_tokens_legacy:
- 分词:按
/ \ . _ -把路径切成小写 token; - 加长前缀:对超过 3 个字符的 token 额外加入它的 3 字符前缀,让长单词的开头也保留区分度;
- 哈希到桶:每个 token 用
crc32哈希定位到 256 维向量中的一个桶,再用第二次crc32的奇偶决定符号(+1 / -1),缓解哈希碰撞带来的正负偏差; - 归一化:向量做 L2 归一化后乘以 0.4,与 1024 维 embedding 拼接。
这套设计还有两个细节在"限权"路径的贡献:
- 丢弃位置:路径被当作 token 袋(bag-of-tokens),
a/b/...与b/a/...在模型眼中几乎等价。这让打分器对不同仓库的目录布局差异保持鲁棒,避免学到"某层级下必为算法代码"这类死规则; - 低维 + 低权重:256 维的哈希向量配上 0.4 的权重,无论目录名多么"像答案",都不足以推翻内容判断。
值得一提的是,推理端与训练端逐字复用同一份实现(文件头部注释写明了这一点),保证发布版打分器的路径特征化行为与训练时完全一致,不会因版本漂移引入偏差。
五、打分链路:路径如何进入模型?
在 scripts/scorers.py 中,三个模型各司其职:
| 模型 | 输入 | 输出 |
|---|---|---|
| C 文件角色 | 1024 维 embedding | category(ALGO/WEB/TOOL/DATA/TEST/CONFIG/EXCLUDE)+ 置信度 |
| A 相关性 | embedding +256 维路径哈希 | algo_rel_score(0~1) |
| Q 质量 | 1024 维 embedding(标准化后) | quality_score(0~10) |
也就是说,只有 A 需要路径。加载打分器时会校验 A 的 checkpoint 声明:如果检查点没声明路径特征,或输入维度与1024 + 256对不上,会直接报错而非静默降级。实际使用时:
- Python 调用
scorers.score(embeddings, paths)时,paths必须传与 embedding 顺序一致的仓库相对路径; - 用 scripts/score_parquet.py 批量处理 Parquet 时,输入需带
relative_path列(或meta中的file_path);路径缺失时脚本会明确拒绝运行(见 scripts/score_parquet.py)。
整个 L2 筛选流程如下:文件编码 → 角色预测 → 相关性/质量打分 → 按校准阈值筛选,路径哈希正是在"相关性打分"这一步介入:
筛选策略的默认值集中在 config.json:algo_rel_score ≥ 0.8且quality_score ≥ 3.0且角色不是EXCLUDE的文件才会被保留。
六、效果:筛出了"更懂算法"的数据 📈
这套带防捷径设计的打分器最终产出覆盖 11 种语言的 UltraData-Code-L2(约 400B tokens,总体保留 L1 约 12.23% 的文件)。在 10B-token、同一 1B 基座模型的受控对比中,仅用 L2 替代 L1 训练,EvalPlus 与 MultiPL-E 的 pass@1 分别提升7.80和5.13个百分点,训练动态如下图所示:
七、关键文件速查
| 文件 | 作用 |
|---|---|
| scripts/path_features.py | 基准名剥离、路径哈希向量构造 |
| scripts/scorers.py | C / A / Q 模型加载与批量打分 |
| scripts/score_parquet.py | Parquet 批量打分入口,支持--apply-policy输出selected |
| config.json | 相关性/质量阈值等筛选策略默认值 |
| classifiers/ | 11 种语言的打分器权重(A/C/Q + 校准参数) |
| README_ZH.md | 中文完整文档与使用示例 |
总结
UltraData-Code-L2-Classifier 的相关性模型给路径"开了一道小窗":哈希技巧把变长的仓库路径压缩成 256 维、低权重的辅助特征,让模型能利用目录语义这一先验;而基准名剥离 + 去位置化 + 低维低权重三重设计,则确保模型无法靠记住leetcode这类目录名走捷径。正是这种"给信号、不给答案"的特征工程,让 L2 筛选结果在不同仓库、不同语言上都稳健可用,也支撑了后续预训练中显著的算法能力增益。
【免费下载链接】UltraData-Code-L2-Classifier项目地址: https://ai.gitcode.com/OpenBMB/UltraData-Code-L2-Classifier
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考