数据说明
最重要的一点:本项目的全部训练数据和测试数据都是合成数据,不是真实用户反馈。 没有使用任何第三方数据集。下文说明它们是怎么生成的、有什么局限、怎么检查。
数据一览
| 文件 | 条数 | 来源 | 用途 |
|---|---|---|---|
data/processed/train.jsonl |
5146 | 合成语法 | 训练 |
data/processed/val.jsonl |
969 | 合成语法(核心表达与训练集不重叠) | 选检查点 |
data/processed/test.jsonl |
977 | 合成语法(核心表达与训练集不重叠,含未见领域) | 最终报告:同分布测试 |
data/processed/ood_dev.jsonl |
63 | 手写,不经过语法 | 错误分析,指导下一轮数据 |
data/processed/ood_test.jsonl |
117 | 手写,不经过语法 | 最终报告:分布外测试 |
规模、分布、去重和泄漏检查的全部数字见自动生成的 数据构建报告,样例见 数据样例预览。
来源与许可
| 数据 | 生成方式 | 作者 | 许可 |
|---|---|---|---|
| 合成语法词表 feedlens/data/lexicon.py | 人工设计问题类型和说法,由项目作者借助 AI 助手(Claude)撰写 | 本项目 | Apache-2.0(随本项目) |
| 合成语法样本(train / val / test) | 由 feedlens/data/generate.py 根据词表程序化组合,随机种子 42 | 本项目 | Apache-2.0(随本项目) |
| 手写分布外集 data/raw/ood_handwritten.txt | 由 AI 助手(Claude)逐条撰写并按标注规范标注,不经过生成器 | 本项目 | Apache-2.0(随本项目) |
未使用的数据:没有抓取任何应用商店、社交平台或客服系统的内容;没有使用 MiniMind 的预训练或对话微调语料(那些只体现在下载的官方基础权重里)。
数据格式
每行一个 JSON 对象:
{
"id": "train-00121",
"text": "点插入表格就直接退回桌面,真的受不了。",
"category": "功能故障",
"sentiment": "负面",
"urgency": "高",
"summary": "插入表格时闪退",
"source": "synthetic_grammar_v1",
"group": "bug_crash_act#6",
"issue_id": "bug_crash_act",
"domain": "笔记",
"variant": "problem",
"split": "train"
}
| 字段 | 含义 |
|---|---|
text |
模型输入 |
category sentiment urgency summary |
监督目标 |
source |
synthetic_grammar_v1 或 handwritten_ood_v1,每条都可追溯 |
group |
划分单位。合成样本为“问题类型#核心表达序号”,同组样本只会出现在一个划分里 |
issue_id domain variant |
生成时的元信息,用于分组分析(如未见领域、带紧急度修饰语的样本) |
训练时,每条样本被渲染成一段对话:用户消息是固定指令加反馈原文,助手消息是四行答案。只对答案部分计算损失。
<|im_start|>user
请把下面这条用户反馈整理成四行:类别、情感、紧急度、摘要。
反馈:点插入表格就直接退回桌面,真的受不了。<|im_end|>
<|im_start|>assistant
<think>
</think>
类别:功能故障
情感:负面
紧急度:高
摘要:插入表格时闪退<|im_end|>
标注规范
生成器按这套规范构造标签,手写集也按它标注。原文在 feedlens/data/lexicon.py 顶部。
| 字段 | 规则 |
|---|---|
| 类别 | 以用户最主要的诉求为准;并列时取影响更严重的那一项 |
| 情感 | 报告问题或表达不满 = 负面;建议、咨询且无明显不满 = 中性;认可、感谢 = 正面。先夸后提需求:类别仍是功能建议,情感记正面 |
| 紧急度 | 高:核心流程被阻断(无法启动、登录、支付,闪退)、资金损失、数据丢失、账号安全。中:功能或体验明显受损但仍可使用。低:不影响使用(建议、咨询、表扬、审美偏好、无效内容) |
| 摘要 | 不超过 20 字,写“发生了什么 / 想要什么”,不带情绪词 |
这套规范是本项目自定的,不是行业标准。尤其是紧急度,不同团队的定义会不一样,实际使用前应当按团队自己的口径重新标注或调整。
合成语法是怎么工作的
一条样本 = 问题类型 的一个 核心表达 + 可选的 外壳。
[开头语] [应用称呼] [时间/设备语境] 核心表达 [紧急度修饰语] [收尾语] [标点/表情]
反馈一下, 这个笔记软件 更新到最新版之后 一导出PDF就闪退 ,不过只是偶尔出现 ,求修复 。
| 组成 | 数量 | 说明 |
|---|---|---|
| 问题类型 | 72 个 | 每个固定了类别、基础情感、基础紧急度和摘要模板,如 bug_crash_act → 功能故障 / 负面 / 高 / “{act}时闪退” |
| 核心表达 | 386 个 | 每个问题类型 4–12 种不同说法 |
| 应用领域 | 10 个 | 通用、电商、视频、笔记、记账、游戏、出行、学习、社交、影音工具。各自带一组动作、页面、功能名 |
| 外壳片段 | 开头语、语境、收尾语各十余种 | 不影响标签 |
| 表面噪声 | 口语替换、标点变化、表情 | 不影响标签 |
标签不是事后标的,而是由构造决定的,所以每条样本的标签都能追溯到规则。其中两处让标签不只是“问题类型”的简单查表:
- 紧急度修饰语:对允许的问题类型,“现在完全没法用了”把 中 升为 高;“不过只是偶尔出现”把 高 降为 中。
- 功能建议的语气:平铺直叙是 中性;“一直很喜欢,希望能加…”是 正面;“这么基本的功能都没有…”是 负面。
生成时有一条硬性校验:摘要里出现的具体词(如“导出PDF”)必须出现在原文里,否则生成器直接报错。这是为了避免训练出“摘要里有原文没有的信息”的习惯。
流水线
命令:python -m feedlens.data.build(约 7 秒)。代码:feedlens/data/build.py。
| 步骤 | 做了什么 | 本次运行结果 |
|---|---|---|
| 1. 导入 | 生成器产出 8646 条,手写集 180 条 | train 6422 / val 1104 / test 1120 / ood 63+117 |
| 2. 清洗 | Unicode 规范化、去控制字符、折叠空白 | — |
| 3. 校验 | 标签合法、长度在范围内、无未替换槽位、目标输出能被解析器读回 | 0 条不合格 |
| 4. 精确去重 | 去掉标点、空格、表情后文本相同的只留一条 | 去掉 1530 条(train 1254 / val 135 / test 141) |
| 5. 近重复 | 同一核心表达内,字符三元组 Jaccard ≥ 0.90 的只留一条 | 去掉 24 条 |
| 6. 泄漏检查 | 每条评测样本与全部训练样本比相似度,≥ 0.80 的训练样本剔除 | 0 条需要剔除 |
| 7. 硬断言 | 任何核心表达不得跨划分;保留领域不得出现在训练集和验证集 | 通过 |
| 8. Token 检查 | 用真实分词器量“提示词 + 答案”的长度 | 68–113 token,远低于训练上限 256 |
第 4 步去掉了近两成样本,这是预期之内的:不带槽位的核心表达(如“一打开就闪退”)加上有限的外壳,本来就会撞车。生成数量是按去重后仍有足够样本来设定的。
划分与防泄漏
合成数据最容易出的问题是:训练集和测试集只差一个表情或一个开头语,测试分数高得毫无意义。本项目用三层措施应对。
第一层:按核心表达分组划分。 先把 386 个核心表达分成 train 247 / val 69 / test 70,再各自生成样本。测试集里的“每次{act}都会崩溃退出”这种说法,训练集里一条都没有——训练集里只有同一问题类型的其他说法。
第二层:保留一个领域。 “学习”领域的词(课程、题库、错题本等)只出现在测试集(100 条),训练集和验证集里完全没有。
第三层:实测相似度。 分组划分是结构上的保证,相似度检查是实测验证。
| 评测划分 | 与训练集的最大相似度:均值 | P95 | 最大 | ≥ 0.8 的条数 |
|---|---|---|---|---|
| val | 0.186 | 0.348 | 0.567 | 0 |
| test | 0.180 | 0.333 | 0.529 | 0 |
| ood_dev | 0.040 | 0.130 | 0.154 | 0 |
| ood_test | 0.046 | 0.120 | 0.192 | 0 |
相似度最高的几对是因为共用了同一段外壳(例如都以“这么基本的功能都没有,太不方便了”开头),核心表达并不相同。手写集与训练集的相似度很低,说明它确实在语法之外。
仍然存在的泄漏:外壳片段和紧急度修饰语在各划分之间是共享的;同一个问题类型(及其摘要)在训练集和测试集里都会出现。所以同分布测试集衡量的是“同一类问题换一种核心说法还能不能认出来”,而不是“全新的问题”。后者由手写集来衡量。
手写分布外集
180 条,9 个类别各 20 条,逐条自由撰写。和合成语法的区别:
- 句子更长、信息更具体(“充了648,游戏里一分钱没到,客服让我等,等了三天了”)
- 很多问题不在 72 个问题类型里(定位漂移、闹钟延迟、大数据杀熟、广告伪装成系统提示)
- 摘要是针对每条单独写的,不是模板
划分方式:每个类别内按顺序,序号能被 3 整除的进 ood_dev(63 条),其余进 ood_test(117 条)。ood_dev 的失败案例可以看、可以用来改进数据;ood_test 只看汇总指标。
局限:
- 它仍然是合成数据,而且和语法词表出自同一个作者,用词习惯难免相近。它比同分布测试更严格,但不能替代真实数据。
- 标注是单人完成的,没有第二人交叉检查,一致性未知。
- 只有 117 条,置信区间很宽。
抽样复核流程
标签由规则构造不等于标签正确——规则本身可能写错。复核流程如下:
# 1) 每个类别抽 10 条,生成待填写的表格
python -m feedlens.data.review sample --split train --n 90
# 2) 打开 data/reports/review_train.csv,逐条填写 ok 列(Y/N),N 的写明错在哪个字段
# 3) 汇总,得到通过率和错误分布
python -m feedlens.data.review summarize --file data/reports/review_train.csv
复核时看四件事:
- 这句话读起来像人写的吗?(不通顺的记“文本不通”)
- 类别、情感、紧急度是否符合上面的标注规范?
- 摘要是否忠实于原文,有没有多出原文没有的信息?
- 如果你来标,会给出不同答案吗?为什么?
当前状态:两份待复核表格(review_train.csv 90 条、review_ood_test.csv 117 条)已生成,尚未有人填写,因此没有复核通过率可报告。我在开发过程中看过随机样本,发现并记录了下面这些问题,但这不等于正式复核。
已知的数据问题
| 问题 | 例子 | 影响 | 处理 |
|---|---|---|---|
| 领域与问题搭配不自然 | “这个打车软件只有画面没有声音” | 少量样本语义别扭 | 未处理。不带槽位的核心表达会搭配任意领域的应用称呼 |
| 个别槽位组合语法生硬 | “加好友不了,一直报错” | 少量不通顺的句子 | 未处理。可给模板加词性约束 |
| 句式多样性有限 | 所有样本都是“外壳 + 核心 + 外壳”的结构 | 模型可能依赖位置和固定搭配 | 用手写集暴露这个问题 |
| 情感分布偏斜 | 训练集负面 71% | 反映了反馈的常态,但中性、正面样本偏少 | 未处理 |
| “其他”类过于简单 | 多为几个字的短句 | 稍长的无关内容可能被误判 | 未处理 |
第二轮数据:语法 v2
第一轮训练完成后,我看了全参数微调模型 ft-full-v1 在分布外开发集 ood_dev 上的全部失败案例(自动整理的清单),据此扩充了语法,得到 v2 数据,用于训练 v0.2。结果和结论在 评测报告 里,这里只说数据怎么改的。
失败类型 → 数据改动
| 编号 | 在 ood_dev 上看到的失败 | 例子(原文 → 模型给出的摘要或标签) | v2 的改动 |
|---|---|---|---|
| F1 | 摘要拼贴:把训练见过的摘要模板碎片和输入里的词拼在一起 | “首月1元,第二个月直接扣了30…” → 摘要“首月界面发烫” | 增加摘要里带开放槽位的问题类型(“{remind}不响”“咨询{acctopic}”“希望{adj}可调”),让“把原文的主体抄进摘要”成为常见模式 |
| F2 | 关键词捷径:某些词在 v1 里只出现在投诉中;性能、界面类说法稍变就归错类 | “没有开屏广告这一点就值得五星” → 广告骚扰 / 负面 | 增加对照样本:带“广告”“换”等词的表扬;不带“卡”“慢”字样的性能投诉;抱怨现有交互而非提需求的界面投诉 |
| F3 | 咨询识别不稳 | “买错了规格,想改一下订单,在哪里操作呀” → 其他 | 增加带开放主题的咨询类型 |
| F4 | v1 没有的紧急度修饰语不起作用 | “…导航完全不能用” → 紧急度 中 | 新增 6 种升级说法、3 种降级说法,并让 v1 的问题类型也带上它们再生成一批 |
代码:feedlens/data/lexicon_v2.py(只含增量)、feedlens/data/build_v2.py。命令:python -m feedlens.data.build_v2。
v2 数据规模
| 文件 | 条数 | 构成 |
|---|---|---|
train_v2.jsonl |
8408 | v1 的 5146 条原样保留 + 新增 3262 条(新问题类型 2859 条、新修饰语 403 条) |
val_v2.jsonl |
1444 | v1 的 969 条 + 新增 475 条 |
test_v2new.jsonl |
381 | v2 新增问题类型里留出的核心表达,训练中未出现 |
新增 44 个问题类型、190 个核心表达。全部数字见 v2 数据构建报告。
保持可比性的三条约束
- v1 的文件一个字节都不改。 改动生成器后重新构建 v1,各文件 SHA256 与改动前相同(已核对)。所以 v0.1 和 v0.2 可以在同一份
test、同一份ood_test上直接比较。 - 训练设置不变。 v0.2 用与 v0.1 相同的配置(方法、轮数、学习率、种子),只换数据,差别就只能来自数据。
- 只看
ood_dev,不看ood_test。 新增内容只依据ood_dev的失败类型设计。
一次真实的泄漏,以及怎么发现的
第 3 条约束我第一次没有守住。写完 v2 词表后,我用程序扫描了它与 ood_test 的字面重合(槽位取值整词、三字片段,核心表达四字片段),发现:
- 槽位取值里有一个 “拖动排序”,而
ood_test里正好有一条“希望播放列表可以拖动排序”。这不是有意为之——两份东西出自同一个作者,写的时候顺手用了同一个词。 - 另有两处核心表达与
ood_test的措辞相近(“点进去是空白”“没有乱七八糟的…”)。
处理:删掉这个取值,改写那两处,重新构建。重建后,新增训练样本与 ood_test 的最大相似度为 0.143,没有任何一条超过 0.4。
这件事说明两点:
- “同一作者既写语法又写测试集”的风险是真实存在的,不是理论上的担心。
- 扫描只能发现字面重合,发现不了概念上的相近(例如新增的“{act}速度慢”这个通用类型,对
ood_test里某些具体的“慢”显然有帮助)。所以 v0.2 在ood_test上的提升应当保守看待,它不能替代一份由别人撰写、或来自真实用户的测试集。
ood_dev 从此不再独立
v2 的新增语法就是照着 ood_dev 的失败案例写的,新增训练样本与 ood_dev 的最大相似度达到 0.833。因此 v0.2 在 ood_dev 上的成绩不能当作泛化能力的证据,它只说明“针对性的修改生效了”。下一轮迭代需要一份新的开发集。
复现
python -m feedlens.data.build --seed 42
同一种子会生成逐字节相同的文件。各文件的 SHA256 记录在 data/processed/manifest.json,模型说明卡里记录了训练所用训练集的校验和。
本页由 docs/07-data.md 生成。