机器出题并不只是批量生成问题。一个问题是否有价值,取决于它对应的知识范围、推理难度和质量标准是否可控。美团开源 LoHoSearch 的思路,是先用知识图谱建立全局视野,再围绕建图、难度控制、质量把关和数据概览,形成一套可检查的机器出题流程。
这套方法对评测集、训练数据和问答系统都很有参考意义:模型不应只“知道答案”,还需要清楚自己在什么知识节点上具备能力,以及哪些相邻能力仍然薄弱。
先建图,再生成问题
知识图谱可以把零散的文档、实体、概念和关系组织成一个可遍历的结构。对机器出题来说,它至少解决了两个问题:
- 让出题系统知道当前覆盖了哪些主题,减少知识盲区。
- 让问题能够绑定到具体的知识节点和关系,便于追溯来源与分析错误。
可以把一道题抽象成一条或多条图上的路径。例如,题目可能要求模型从一个实体出发,沿着“属于”“位于”“依赖”等关系完成一步推理,也可以组合多个关系形成多跳问题。
下面是一个不依赖第三方库的最小示例。它用邻接表表示知识图谱,并生成简单的一跳问题。实际系统可以把 triples 替换成从文档抽取或数据库读取的数据。
from collections import defaultdict
triples = [
("LoHoSearch", "用于", "机器出题质量控制"),
("机器出题质量控制", "依赖", "知识图谱"),
("知识图谱", "支持", "全局视野"),
]
graph = defaultdict(list)
for subject, relation, object_ in triples:
graph[subject].append((relation, object_))
def make_questions(graph):
questions = []
for subject, edges in graph.items():
for relation, object_ in edges:
questions.append({
"question": f"{subject}{relation}什么?",
"answer": object_,
"anchor": subject,
"relation": relation,
"difficulty": 1,
})
return questions
for item in make_questions(graph):
print(item)
关键不在于问题模板本身,而在于每道题都保留了 anchor、relation 和 difficulty 等结构化信息。这样,后续质量检查可以定位到具体的知识节点,而不是只对一段自然语言做模糊判断。
用图上的路径控制难度
题目难度不应完全交给模型主观判断。一个可操作的办法,是把难度映射到图上的路径特征:
- 一跳关系适合基础事实识别。
- 两跳或多跳路径适合组合推理。
- 多个候选节点之间关系接近时,需要更强的区分能力。
- 节点信息不完整、存在别名或关系方向容易混淆时,题目风险更高。
可以这样实践:先限定允许的路径长度,再让生成器根据路径生成问题;对于超过最大长度、无法唯一确定答案或依赖低置信度节点的样本,直接进入待审核队列。
def find_paths(graph, start, max_hops=2):
paths = [(start, [])]
result = []
while paths:
node, edges = paths.pop(0)
if edges:
result.append((node, edges))
if len(edges) >= max_hops:
continue
for relation, next_node in graph.get(node, []):
paths.append((next_node, edges + [(relation, next_node)]))
return result
for answer, path in find_paths(graph, "LoHoSearch", max_hops=2):
print("路径:", " -> ".join(["LoHoSearch"] + [f"{r}:{n}" for r, n in path]))
print("候选答案:", answer)
这个示例只展示了路径遍历,不能替代完整的难度模型。生产环境还应加入答案唯一性、路径置信度、实体类型和问题语言质量等特征,并把这些特征记录到数据集元信息中。
质量把关要检查什么
机器生成的问题常见的缺陷包括:问题与知识节点不匹配、答案不唯一、关系方向写反、问题包含图谱中不存在的事实,以及多个题目只是换了措辞却考查同一件事。
质量把关可以分成几层:
- 结构校验:检查实体、关系和答案是否都能在图中找到。
- 逻辑校验:检查推理路径是否真实存在,答案是否能由路径推出。
- 语言校验:检查问题是否完整、是否有歧义、是否包含无法回答的条件。
- 去重与覆盖检查:识别近似问题,并统计知识节点与关系的覆盖情况。
- 抽样复核:对高难度、低置信度或异常分布样本进行人工检查。
其中,人工抽样并不是机器流程的失败,而是对自动规则边界的校准。抽样结果可以反过来调整路径规则、难度阈值和生成提示词。
数据概览让问题集可运营
如果只保存问题和答案,数据集很快会变成难以维护的文本文件。更实用的记录方式还应包括:
- 对应的知识节点和关系。
- 路径长度与难度等级。
- 生成模型、生成时间和版本号。
- 自动检查结果与人工审核状态。
- 主题、实体类型和错误类型。
这些字段能支持几个重要分析:哪些主题题量不足,哪些关系持续产生错误,难题是否集中在少数节点,训练集与评测集是否发生知识泄漏,以及模型错误是否来自事实缺失还是推理失败。
可以用下面的命令快速查看一个 JSON Lines 数据集的难度分布。假设文件名为 questions.jsonl,每行包含 difficulty 字段:
jq -r '.difficulty' questions.jsonl \
| sort -n \
| uniq -c \
| awk '{print "difficulty=" $2 ", count=" $1}'
如果数据中包含 topic 字段,还可以按主题统计:
jq -r '.topic' questions.jsonl \
| sort \
| uniq -c \
| sort -nr
落地时的取舍
知识图谱能提供全局视野,但它不会自动保证知识正确,也不会自动解决自然语言歧义。图谱构建本身需要处理实体对齐、关系抽取、版本变化和冲突事实。图谱越大,覆盖面越好,但维护成本、错误传播风险和质量评估成本也会同步增加。
比较稳妥的落地顺序是:
- 先选择一个边界清晰的领域,建立少量高质量节点和关系。
- 为每条题目保留可追溯的知识路径和版本信息。
- 先使用规则控制路径长度、答案唯一性和数据去重。
- 再引入模型评审,并用人工抽样校准评审结果。
- 最后通过数据概览持续修正覆盖率和难度分布。
机器出题的核心不是让模型一次生成更多问题,而是让每个问题都能回答三个问题:它考查了哪项能力,为什么属于这个难度,以及出现错误时应该回查哪条知识路径。LoHoSearch 所体现的建图、控难度、做质检和看全局,正好构成了这条可追溯的数据生产链。