Open-Assistant StackExchange 指令数据集构建全流程解析:从 XML 归档到 Parquet 训练数据
【免费下载链接】Open-AssistantOpenAssistant is a chat-based assistant that understands tasks, can interact with third-party systems, and retrieve information dynamically to do so.项目地址: https://gitcode.com/gh_mirrors/op/Open-Assistant
本文以 Open-Assistant 仓库中 data/datasets/oa_stackexchange 目录为线索,完整讲解如何将 Stack Exchange 全站历史问答归档(Internet Archive 提供的 XML 转储)转换为可用于 SFT 指令微调的 Instruction 数据集。读者将掌握「下载 → XML 流式解析 → 过滤与格式转换 → 统计 → 合并 → 上传 Hugging Face」六阶段流水线的每步操作、关键参数(长度阈值、评分阈值、每站最大答案数)及对应的源码实现细节,并能直接复现出与仓库发布版本一致的数据集。
一、数据集概览:What & Why
Stack Exchange 是互联网上规模最大的高质量问答社区网络,覆盖编程、数学、物理、烹饪、棋类等数百个垂直主题站点。Open-Assistant 从 Internet Archive 的 stackexchange 归档(下称"归档")中提取帖子数据,构建了名为Open-Assistant StackExchange Instruction(pretty name)的指令数据集。
该数据集最终以单个合并后的 parquet 文件形式发布,并带有以下数据集卡片信息(见 data/datasets/oa_stackexchange/README.md 的 YAML frontmatter):
| 字段 | 值 |
|---|---|
| 数据列 | INSTRUCTION、RESPONSE、SOURCE、METADATA(answer_score、question_score、tags) |
| splits | train 单折,6,331,083 条样本 |
| 数据大小 | dataset_size ≈ 6,549,838,664 字节(约 6.1 GiB),download_size ≈ 3,755,782,987 字节 |
| license | cc-by-sa-4.0(遵循 Stack Exchange 数据许可) |
| language | en、uk、ru、de、fr、it、es 等 |
数据源于归档中各站点公开的Posts.xml文件,原始提取代码由 b-mc2 定义的 Instruction 数据集规范(四列:INSTRUCTION、RESPONSE、SOURCE、METADATA)。
数据集的核心过滤规则
归档中包含各站点的全部帖子(问题、回答、评论等),本数据集按如下规则过滤(README.md 与 process.py 双重印证):
- 仅保留有被采纳回答(accepted answer)的帖子线程;
- 问题与回答的字符长度均小于 1000 字符(源码常量
MAX_LENGTH = 1000); - 其他回答、无采纳答案的问题、过长条目一律丢弃。
也就是说,每一行代表一个「问题 → 被采纳回答」的高质量问答对,天然契合指令微调所需的 INSTRUCTION/RESPONSE 结构。
每行数据结构
每行包含四列:
- INSTRUCTION:由问题标题(Title)与问题正文(Body,HTML 转 Markdown 后)拼接而成,格式为
Title\nBody_markdown; - RESPONSE:被采纳回答正文(HTML 转 Markdown 后);
- SOURCE:固定格式
stackexchange-{site_name},例如stackexchange-ai,用于标注数据出处,便于训练时做去重与溯源; - METADATA:JSON 结构,包含
tags(站点标签,如python pandas)、question_score(问题评分)、answer_score(回答评分)。
二、数据集复现流水线(五步操作)
仓库在oa_stackexchange目录下提供了完整的可复现脚本链,依次执行即可从原始 XML 归档重建整个数据集:
第 1 步:下载全部 XML 文件
将归档中所有站点的Posts.xml下载到xml/目录:
./download.pydownload.py 的实现要点:
- 先请求归档目录页,解析 HTML 表格拿到全部
*.7z归档文件名,并对站点名做归一化处理(如meta.stackoverflow.com转为meta_stackoverflow),再拼出view_archive.php的直链; - 使用
ThreadPoolExecutor(NUM_PARALLEL = 20)并发下载,跳过_meta与stackoverflow.com-命名的文件(RE_IGNORE正则); - 已有缓存(
xml/{name}.xml存在)时直接复用,避免重复下载; - 注意:
stackoverflow.com-Posts.7z体积约 18GB,脚本特意提示手动下载或用 torrent(stackexchange_archive.torrent的 658 号文件),解压后重命名为xml/stackoverflow.xml放入目录即可。
第 2 步:解析 XML、过滤并转换为 OA 格式
./process.py不带参数时,process.py 会自动扫描xml/*.xml逐个处理;也可以显式传入站点名只处理特定站点。每个站点输出到parquet/{site}.parquet。
第 3 步:统计各站点样本数
./stats.pystats.py 遍历parquet/下所有 parquet 文件,用 pyarrow 的ParquetDataset统计每个站点的行数并打印- {topic}: {count:,}格式的清单(即 README 中统计列表的来源)。
第 4 步:合并为单一 parquet 文件
./combine.pycombine.py 将full/目录下的 parquet 全部合并为stackexchange.parquet,合并前会再做一次长度收紧(INSTRUCTION 与 RESPONSE 均 < 1000 字符)并清洗 METADATA 字段,打印每个文件的缩短比例;合并逻辑由 merge_parquets.py 实现——它通过 pyarrow 流式读写(ParquetWriter逐表写入、iter_batches逐批读取),并用coalesce按行数(默认max_size = 2**20)分批拼接,避免大文件一次性载入内存。
第 5 步:上传 Hugging Face Hub
./upload.py先执行huggingface-cli login完成认证,然后运行:
./upload.pyupload.py 使用datasets库加载本地stackexchange.parquet并push_to_hub(max_shard_size="500MB"自动分片),最终上传到donfu/oa-stackexchange。
完整流程也符合仓库总则 data/datasets/README.md 中「Convert → Login → Push → 更新 HF README」的规范步骤。
三、源码级深入:process.py 的解析与过滤原理
3.1 流式 SAX 解析,应对超大 XML
Stack Exchange 的 Posts.xml 动辄数 GB(如 stackoverflow 的 Posts.xml 超过 60GB),无法整体载入内存。parse_and_convert采用lxml.etree.iterparse的end 事件流式解析:
context = etree.iterparse(path, events=("end",)) for _, element in tqdm(context, total=total_rows): if element.tag == "row": if len(element.get("Body")) > MAX_LENGTH: continue rows.append(parse_row(element)) element.clear() while element.getprevious() is not None: del element.getparent()[0]关键点:
- 预先用
grep -c '<row'统计总行数用于 tqdm 进度条; MAX_LENGTH = 1000:正文超长直接跳过(这是 README 所述「长度 < 1000」的底层实现);element.clear()配合删除前序兄弟节点,及时释放已处理节点的内存;- 每处理
10**6行或全部完成时,将累积行构造 DataFrame、调用convert_to_oa转换,随后del df / del oa / gc.collect()强制回收内存——即分块批处理模式。
parse_row从 XML 属性中提取Id、PostTypeId、Body、Title、Tags、Score、AcceptedAnswerId、ParentId八个字段(缺失字段用 0 或空串兜底)。
3.2 问答配对:以内连接实现「有采纳答案」过滤
convert_to_oa是过滤逻辑的核心:
questions = all[all["AcceptedAnswerId"] != 0] merged = pd.merge( questions, all, how="inner", left_on="AcceptedAnswerId", right_on="Id", suffixes=("_q", "_a"), )即:先筛出所有带AcceptedAnswerId的问题,再与该问题表自身按AcceptedAnswerId == Id做 inner join,把问题行与被采纳回答行拼成一对。没有采纳答案的问题、以及未被采纳的其他回答都在这一步自然被丢弃。
之后构造四列:
merged["INSTRUCTION"] = merged["Title_q"] + "\n" + merged["Body_q"].apply(to_markdown) merged["RESPONSE"] = merged["Body_a"].apply(to_markdown) merged["SOURCE"] = source merged["METADATA"] = merged.apply(create_metadata, axis=1)注意脚本还定义了MAX_ANSWERS = 10、QUESTION_SCORE_TRESHOLD = 0、ANSWER_SCORE_TRESHOLD = 0等常量,其中后两者在 join 前可作为评分下限过滤的扩展点(当前版本未启用该过滤,仅保留常量)。
3.3 正文清洗:HTML → Markdown,去链接去 emoji
问答正文是 Stack Exchange 的 HTML 片段,直接喂给模型会引入大量噪声。to_markdown做了三级清洗:
- 用
html2text(text, bodywidth=0)将 HTML 转为 Markdown,转换失败时退化为正则剔除所有<...>标签; - 用正则
\[([^\]]+)\]\(([^\)]+)\)将 Markdown 链接text替换为纯文本text,再用https?:\/\/[^\s]+删除残余裸链接; remove_emojis按 Unicode 区块(emoticons、symbols & pictographs、transport & map、flags、dingbats 等)剔除 emoji。
3.4 标签清洗与元数据
convert_tags将 Stack Exchange 的><分隔的原始标签串(如<python><pandas>)转换为人类可读的逗号分隔文本(python, pandas),同时把-替换为空格(如machine-learning→machine learning)。create_metadata最终产出:
{ "tags": "python, pandas", "question_score": 42, "answer_score": 7, }3.5 落盘规范
save_parquet使用 pandas 的to_parquet并强制row_group_size=100, engine="pyarrow", index=False——这正是 data/datasets/README.md 规定的仓库级数据格式要求(parquet 必须带row_group_size=100与index=False),保证全仓库数据集格式统一。
四、统计结果:数据集覆盖面与规模
README 中列出的按站点统计的样本数(来源于stats.py输出)展示了该数据集的广度。以下节选若干代表站点:
| 站点 | 样本数 | 站点 | 样本数 |
|---|---|---|---|
| stackoverflow | 4,269,779 | ru_stackoverflow | 106,714 |
| math | 501,019 | serverfault | 81,229 |
| superuser | 128,488 | askubuntu | 78,472 |
| unix | 54,338 | gaming | 44,899 |
| english | 42,415 | electronics | 41,717 |
| tex | 42,808 | physics | 35,386 |
| apple | 32,603 | gis | 30,249 |
| ell | 30,428 | salesforce | 27,962 |
| pt_stackoverflow | 27,650 | blender | 25,527 |
| wordpress | 24,270 | sharepoint | 24,934 |
| ... | ... | 合计约 180 个站点 | 6,331,083 |
值得注意的几点:
- 长尾明显:stackoverflow 与 math 两个站点贡献了约 75% 的样本;而
bioacoustics(70)、cseducators(71)、conlang(101)、interpersonal(199)等小众站点样本极少,为数据集带来跨领域多样性; - 多语言覆盖:包含
es_stackoverflow、ja_stackoverflow、pt_stackoverflow、ru_stackoverflow、ukrainian、chinese、japanese、french、german等非英语站点,与数据集卡片标注的 7 种语言一致,可用于多语言 SFT 训练; - 含 meta 站点:
meta_stackoverflow、meta_askubuntu等元站点也被收录,需注意这类站点内容以社区治理讨论为主,使用时可结合 METADATA 的 tags 按需过滤。
五、数据集在训练链路中的使用方式
该数据集按仓库规范属于Instruction 数据集(data/datasets/README.md 中明确定义:必须含INSTRUCTION、RESPONSE、SOURCE、METADATA四列)。在模型训练侧,model/model_training的 SFT 训练器会读取这类指令样本并与对话式数据混合使用——以 model/model_training/custom_datasets/oasst_dataset.py 为例,其load_oasst_export会按DatasetEntrySft(conversation由Utterance(text, role, ...)序列组成)组织样本,role取prompter/assistant,对应到本数据集即为INSTRUCTION → prompter、RESPONSE → assistant。用户可仿照该模式,将本数据集的问答对映射为 SFT 对话样本参与微调;METADATA 中的question_score、answer_score还可作为质量加权或过滤的依据。
六、复现注意事项与边界
- 磁盘与内存:全部 XML 解压后体积庞大(仅 stackoverflow 一项约 60GB+),建议按站点分批处理,且
process.py的分块批处理与merge_parquets.py的流式合并都明确针对大文件场景设计; - 依赖:
process.py需要pandas、lxml、html2text、tqdm、pyarrow;download.py需要requests、beautifulsoup4;upload.py需要datasets与huggingface_hub; - 网络要求:归档托管于 Internet Archive,下载依赖其可用性;
stackoverflow.com-Posts.7z建议走官方提示的 torrent 方式; - 许可证:数据遵循 Stack Exchange 的 CC BY-SA 4.0 许可(归档详情页亦有说明),使用与再分发时须保留署名并遵守相同许可条款;
- 上游引用:原始提取与转换思路来自 b-mc2 的公开代码,本仓库脚本在此基础上适配了 Open-Assistant 的四列 Instruction 格式。
七、小结
oa_stackexchange数据集是 Open-Assistant 训练数据体系中最具规模的指令数据集之一:它以 Stack Exchange 的「问题 + 被采纳回答」结构为天然指令对,通过「仅保留有采纳答案、双方均 <1000 字符」的过滤策略控制质量与长度,借助iterparse流式解析 + pandas 批处理 + pyarrow 流式合并解决了超大数据集的内存瓶颈,最终以统一四列 Schema 的 parquet 文件交付。仓库内的 download.py、process.py、stats.py、combine.py、upload.py 五步脚本链即为完整可复现的参考答案,开发者可在此基础上调整长度阈值、评分阈值或站点白名单,构建属于自己的 Stack Exchange 指令数据集。
【免费下载链接】Open-AssistantOpenAssistant is a chat-based assistant that understands tasks, can interact with third-party systems, and retrieve information dynamically to do so.项目地址: https://gitcode.com/gh_mirrors/op/Open-Assistant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考