- 人工智能
- 大模型
- 预训练
- 微调
- LoRA
- RLHF
- 强化学习
- 分布式训练
【免费下载链接】PaddleNLP
Easy-to-use and powerful LLM and SLM library with awesome model zoo.
导读
本文围绕 PaddleNLP 中paddlenlp.metrics.dureader评测模块展开,系统讲解中文阅读理解(MRC)评测的完整链路:如何把模型的起始/结束位置 logits 后处理为可读答案(compute_predictions/get_final_text)、如何对中文答案做归一化(normalize),以及如何用 ROUGE-L 与 BLEU-4 计算 DuReader 式开放答案的评测分数(dureader_evaluate)。读完本文,你将掌握该模块全部公开函数的参数语义、底层算法流程,以及它在 DuReader-yesno、DuReader-robust 等数据集与示例代码中的实际定位,可直接迁移到自己的抽取式问答评测流程中。
一、模块定位:DuReader 评测在 PaddleNLP 中的角色
1.1 文档页与实际源码
docs/zh/source/paddlenlp.metrics.dureader.rst是 Sphinx 自动文档(autodoc)的入口,它通过automodule指令将paddlenlp.metrics.dureader模块的所有公开成员渲染为 API 文档:
dureader ================================= .. automodule:: paddlenlp.metrics.dureader :members: :no-undoc-members: :show-inheritance::members:表示渲染全部公开函数,:no-undoc-members:表示跳过没有 docstring 的成员,:show-inheritance:显示继承关系。因此这份文档的“灵魂”其实是其背后的实现文件 paddlenlp/metrics/dureader.py,其中包含 4 个公开函数:compute_predictions、get_final_text、normalize、dureader_evaluate,以及两个私有辅助函数_compute_softmax和_get_best_indexes。
1.2 DuReader 评测要解决什么问题
DuReader(百度阅读理解数据集)与 SQuAD 的一个关键差异在于答案形态:除了可从原文中抽取连续片段(Entity 型)之外,还包含需要模型自行生成的“描述型”答案(Description 型)和“是否型”答案(YesNo 型)。因此无法只用一个 F1/EM 类精确匹配指标,而需要:
- 答案后处理:将模型输出的 start/end logits 转成最终文本;
- 文本归一化:统一中英文标点(如全角逗号转半角),降低评测误差;
- 模糊匹配指标:用 ROUGE-L(基于最长公共子序列)和 BLEU-4(n-gram 精确率)衡量生成答案与参考答案的相似度。
从源码结构看,paddlenlp/metrics/dureader.py的模块 docstring 也明确说明其源自 “Official evaluation script for SQuAD version 2.0”,并在其基础上加入了 DuReader 的中文评测特性。
二、答案后处理:compute_predictions 全流程剖析
2.1 函数签名与输入输出
def compute_predictions( all_examples, all_features, all_results, n_best_size, max_answer_length, do_lower_case, verbose, tokenizer ): """Write final predictions to the json file and log-odds of null if needed."""见 paddlenlp/metrics/dureader.py#L29-L161。参数含义如下:
| 参数 | 类型/说明 |
|---|---|
all_examples | 原始样例列表,每条含qas_id、question_text、question_type、doc_tokens、orig_answer_text等字段 |
all_features | 特征列表,每条含example_index、unique_id、tokens、token_to_orig_map、token_is_max_context |
all_results | 模型推理结果列表,每条含unique_id、start_logits、end_logits |
n_best_size | 每个问题保留的候选答案数量(默认常用 20) |
max_answer_length | 允许的最大答案长度(token 数),超长候选被过滤 |
do_lower_case | 是否小写化(传给get_final_text的 tokenizer 使用) |
verbose | 是否打印对齐失败等调试信息 |
tokenizer | 用于把预测文本投影回原文的 tokenizer 实例 |
函数返回一个二元组(preds_for_eval, preds_for_test):
preds_for_eval:OrderedDict,qas_id -> 最终答案文本,直接喂给dureader_evaluate打分;preds_for_test:列表,每条包含question_id、question、question_type、answers、yesno_answers字段,用于按 DuReader 官方格式写出测试预测文件。
2.2 核心流程:五步得到最终答案
第一步:建立索引映射。用collections.defaultdict构建example_index -> features与unique_id -> result两个映射,实现从样例到特征再到模型输出的三级关联。
第二步:生成候选 span。对每个特征,用私有函数_get_best_indexes(dureader.py#L279-L288)分别取出 start/end logits 中得分最高的n_best_size个位置索引,然后双重遍历所有(start_index, end_index)组合。源码中有一组严格的合法性过滤条件:
if start_index >= len(feature.tokens): # 越界 continue if start_index not in feature.token_to_orig_map: # 无法映射回原文 continue if not feature.token_is_max_context.get(start_index, False): # 非最大上下文片段 continue if end_index < start_index: # 起始位置晚于结束位置 continue length = end_index - start_index + 1 if length > max_answer_length: # 超出最大答案长度 continue候选按start_logit + end_logit之和降序排列(dureader.py#L91),这一步实际上等价于对位置联合概率做近似打分。
第三步:去 token 化(de-tokenize)。对每个候选 span,先把 WordPiece token 拼接回词级别文本,并去掉##前缀拼接符:
tok_text = tok_text.replace(" ##", "") tok_text = tok_text.replace("##", "")然后调用get_final_text(tok_text, orig_text, tokenizer, verbose)把预测文本精确投影回原始文档文本(细节见下一节)。seen_predictions字典保证同一文本只保留一次,避免重复候选。
第四步:兜底与空答案处理。若因过滤过严导致一个候选都没有,会填充一个text="empty"、logits 为 0 的占位预测(dureader.py#L134-L135)。best_non_null_entry记录分数最高的非空候选,最终preds_for_eval[example.qas_id]即取这个非空最优答案。
第五步:输出组织。preds_for_test中每条记录都显式携带question_type与yesno_answers字段(默认空列表),这表明该输出格式是面向 DuReader 官方评测脚本的。
三、文本对齐:get_final_text 与 token 级到字符级的投影
3.1 为什么需要对齐
模型的预测是基于 token 化后的文本,而评测需要的是原始文档中的子串。源码注释给出了典型反例:预测文本是steve smith,而原文是Steve Smith's——直接返回orig_text会多出's,直接返回pred_text又丢失了大小写等信息。因此需要一个“半复杂”的对齐启发式。
3.2 对齐算法三步
get_final_text(dureader.py#L164-L253)的实现思路:
- 空格剥离与映射:私有函数
_strip_spaces对文本去空格,同时记录“无空格字符下标 -> 原文本下标”的映射ns_to_s_map。 - 长度一致性检查:分别对
pred_text和orig_text剥离空格后比较长度,若不等则说明启发式失败,直接返回orig_text(避免产生错误文本)。 - 字符级反投影:先在
tok_text中用find定位pred_text的起止位置,再通过tok_ns_to_s_map与orig_ns_to_s_map两级映射,把 token 层位置换算回原始文本的字符区间,最终切出orig_text[orig_start_position : orig_end_position + 1]。
verbose=True时,对齐失败(找不到文本、长度不一致、无法映射起止位置)都会打印诊断信息,便于调试。
四、中文答案归一化:normalize
normalize(dureader.py#L291-L314)负责把答案字符串规整为“空格连接字符”的规范形式,其要点是全角标点转半角:
norm_s = norm_s.replace(",", ",") norm_s = norm_s.replace("。", ".") norm_s = norm_s.replace("!", "!") norm_s = norm_s.replace("?", "?") norm_s = norm_s.replace(";", ";") norm_s = norm_s.replace("(", "(").replace(")", ")") norm_s = norm_s.replace("【", "[").replace("】", "]")此外还会先剔除所有空白字符(len(c.strip()) != 0的过滤)再拼接。输入为空列表时原样返回。这一步对中文评测非常关键:同一答案在不同标注中可能使用全角或半角标点,归一化后可避免标点形态差异导致的分数波动。
五、评分核心:dureader_evaluate 与 ROUGE-L / BLEU-4
5.1 调用方式与输出
def dureader_evaluate(examples, preds): bleu_eval = BLEU(4) rouge_eval = RougeL() ... metrics = {"ROUGE-L": round(rouge_l * 100, 2), "BLEU-4": round(bleu4 * 100, 2)} print(json.dumps(metrics).encode("utf8"))见 dureader.py#L317-L340。它遍历examples,按qas_id查表取preds中的预测答案;若某个 qid 缺失会打印Missing prediction for %s提示;参考答案取example.orig_answer_text(为空则跳过该样本)。预测与参考答案都经过normalize后,分别喂给 BLEU 和 RougeL 指标实例累加,最终输出形如{"ROUGE-L": 88.23, "BLEU-4": 76.41}的 JSON。
5.2 BLEU(4) 的计算原理
BLEU类位于 paddlenlp/metrics/bleu.py,继承自paddle.metric.Metric。其数学定义为:
BP = 1 (c > r) 或 e^(1-r/c) (c <= r) BLEU = BP * exp(Σ w_n * log p_n)其中c为候选长度、r为参考长度。实现上,count_ngram用get_match_size统计候选 n-gram 与参考 n-gram 的匹配数(多参考取每个 n-gram 的最大计数,见 bleu.py#L26-L41),accumulate中每个 gram 的精确率若为 0 会被替换为sys.float_info.min以避免log(0),最后乘上 brevity penalty(长度惩罚)得到 BLEU 分数。
在dureader_evaluate中使用的是BLEU(4),即n_size=4、权重均匀分配(每个 1/4),对应评测指标 BLEU-4。
5.3 RougeL 的计算原理
RougeL类位于 paddlenlp/metrics/rouge.py,基于**最长公共子序列(LCS)**衡量句子级结构相似度:
- 对每个参考答案计算
prec = LCS(cand, ref) / len(cand)、rec = LCS(cand, ref) / len(ref); - 取多参考中的
prec_max与rec_max,用gamma(默认 1.2)加权调和:
F = ((1 + gamma^2) * prec_max * rec_max) / (rec_max + gamma^2 * prec_max)lcs方法用动态规划矩阵(numpy.zeros)求最长公共子序列长度(rouge.py#L154-L176)。dureader_evaluate最后把rouge_l乘 100 并保留两位小数,与 DuReader 官方评测报告口径一致。
六、面向 DuReader 赛制加分的 Bonus 变体
paddlenlp.metrics.dureader模块本身不直接包含 Bonus 逻辑,但它所依赖的 BLEU / RougeL 指标在 bleu.py 与 rouge.py 中提供了 DuReader 赛制专用子类,可与本文模块配套使用:
BLEUForDuReader(bleu.py#L233-L276):在标准 BLEU 基础上增加add_yn_bonus(YesNo 型问题,n-gram 带上yn_label标签匹配)与add_entity_bonus(Entity 型问题,n-gram 带上"ENTITY"标签),通过alpha(YesNo 权重,默认 1.0)和beta(Entity 权重,默认 1.0)控制加分强度。RougeLForDuReader(rouge.py#L233-L284):LCS 长度加上alpha * yn_bonus + beta * entity_bonus后再计算精确率与召回率;其中add_yn_bonus在模型判断与参考答案的 YesNo 标签一致时奖励 LCS 长度,add_entity_bonus统计参考答案实体在候选文本中的出现长度。
这两个变体与dureader_evaluate的“归一化 + ROUGE-L + BLEU-4”思路互补,共同构成 DuReader 评测的两套口径:一套面向通用开放答案评分,一套面向含 YesNo/Entity 标签的赛制评分。
七、在仓库中的实际落地:数据集与示例
7.1 关联数据集
PaddleNLP 仓库内置了多个 DuReader 系数据集加载器,与评测模块配套使用:
- paddlenlp/datasets/dureader_yesno.py:YesNo 型,提供 train/dev/test 三个 split,答案形式为 yes/no 标签(源码中
labels直接取自source["yesno_answer"]); - paddlenlp/datasets/dureader_robust.py:鲁棒性评测集;
- paddlenlp/datasets/dureader_checklist.py:基于 CheckList 方法构建的能力测试集;
- paddlenlp/datasets/dureader_qg.py:面向问题生成任务。
7.2 示例代码中的评测调用
在 slm/examples/machine_reading_comprehension/DuReader-robust/run_du.py 中可以看到完整的评测流程:模型在evaluate函数中收集所有 batch 的start_logits与end_logits,然后调用compute_prediction(raw_dataset, data_loader.dataset, (all_start_logits, all_end_logits), False, args.n_best_size, args.max_answer_length)得到预测答案并写入prediction.json,最后调用squad_evaluate输出评测分数(run_du.py#L56-L92)。
值得注意的是,paddlenlp/metrics/squad.py 提供了针对 SQuAD 与 DuReader-robust 的compute_prediction/squad_evaluate,而paddlenlp.metrics.dureader模块面向的是原版 DuReader 风格(携带question_type、yesno_answers字段、使用 ROUGE-L/BLEU-4 打分)的评测。二者一脉相承:先做 start/end logits 的后处理抽取,再做指标计算,只是指标口径与输出格式不同。docs/zh/metrics.md的指标速查表中也将两者并列列出。
八、实战:把 dureader 模块接入你的问答评测
结合上文源码分析,可以给出一个最小可用的评测接入范式(以该模块 API 为准):
from paddlenlp.metrics.dureader import compute_predictions, dureader_evaluate # 1. 模型推理得到 logits,按 unique_id 组织为 all_results # all_results 中的每条需包含 unique_id / start_logits / end_logits # 2. 后处理:从 logits 还原答案文本 preds_for_eval, preds_for_test = compute_predictions( all_examples=examples, # 原始样例 all_features=features, # token 化特征 all_results=all_results, # 模型输出 n_best_size=20, # 候选答案数 max_answer_length=30, # 最大答案长度 do_lower_case=False, # 中文场景一般不做小写化 verbose=False, # 需要排查对齐问题时置 True tokenizer=tokenizer, # 用于 get_final_text 反投影 ) # 3. 评测:输出 {"ROUGE-L": xx.xx, "BLEU-4": xx.xx} dureader_evaluate(examples=examples, preds=preds_for_eval)使用要点:
n_best_size与max_answer_length是精度-召回权衡的关键旋钮:候选越多召回越高但噪音越大;答案过长会被length > max_answer_length直接过滤;tokenizer必须与训练时一致,因为get_final_text依赖 tokenizer 的basic_tokenizer.tokenize行为来做字符对齐;- 中文答案务必经过
normalize,模块内部已自动完成全角转半角; - 若数据带 YesNo/Entity 标签且按赛制评分,可改用
BLEUForDuReader/RougeLForDuReader,并调节alpha、beta权重。
九、小结
paddlenlp.metrics.dureader虽然是一份由 docs/zh/source/paddlenlp.metrics.dureader.rst 自动渲染生成的 API 文档,但其背后的源码提供了完整的 DuReader 中文阅读理解评测链路:compute_predictions负责把 logits 后处理为答案,get_final_text负责 token 级到字符级的精确对齐,normalize负责中英文标点归一化,dureader_evaluate最终以 ROUGE-L 与 BLEU-4 双指标打分;配合BLEUForDuReader/RougeLForDuReader的赛制加分变体,可覆盖 YesNo、Entity、Description 三种问题形态。无论是复现 DuReader 评测,还是构建自定义的抽取式问答评测流水线,这个模块都是值得直接复用与研读的参考实现。
- 人工智能
- 大模型
- 预训练
- 微调
- LoRA
- RLHF
- 强化学习
- 分布式训练
【免费下载链接】PaddleNLP
Easy-to-use and powerful LLM and SLM library with awesome model zoo.
相关推荐
MM-CoT评估指标详解:准确率、ROUGE-L、BLEU等7种评测方法
MM CoT评估指标详解:准确率、ROUGE L、BLEU等7种评测方法 在多模态人工智能领域,MM CoT(多模态思维链推理)模型正成为解决复杂视觉语言任务的
GLM-4.5评估指标:BLEU与ROUGE深度解析
GLM 4.5评估指标:BLEU与ROUGE深度解析 引言:为什么评估指标对大语言模型至关重要 在大语言模型(Large Language Model, LLM
基础模型大模型人工智能模型评估指标实战:ROUGE与BLEU全面解析
模型评估指标实战:ROUGE与BLEU全面解析 你是否在模型评测时困惑于指标选择?是否面对ROUGE和BLEU数值不知如何解读?本文将用10分钟带你掌握两大主流
人工智能大模型AI 技能/插件分布式训练微调深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考