一字一诗
LESSON 23 / 26

比较候选并检查重复

这一课完成什么

理解为什么同一个模型可以先生成多个候选,再选择更合适的结果。本项目把模型预测与后续筛选分别记录,让你能追溯展示结果的来源,而不把所有效果都归给模型参数。

比较候选,只能改善部分问题

多写几首,再根据公开规则排序。流畅度线索、关键词命中和重复惩罚都有限,不是客观的文学质量评分。

比较候选,只能改善部分问题
用文字逐步读这张图
  1. 产生多首候选:同样条件下采样;内容可能不同
  2. 字面线索:关键词是否出现;只是辅助项
  3. 模型平均分数:自己觉得是否常见;不等于正确
  4. 惩罚重复:重复字词、句尾等;规则仍可能误判
  5. 精确查重:对照匹配版本语料;整首相同则排除
  6. 保留结果:同时展示检查字段;交给人继续判断
整首查重没命中,是否证明原创?

不能。它只检查配套材料中的整首精确重复,不能发现所有片段借用与外部作品。

候选是怎样得到的

默认一次并行生成 12 个候选,各自按概率逐字选择。给定开头和关键词相同,采样路径仍可能不同。生成后计算每个候选的平均选字对数概率、关键词线索得分以及重复惩罚,排序后展示要求的数量。

平均选字对数概率越高,说明这些字在模型自身看来相对更常见。但常见不等于好诗,模型也可能偏好平淡或重复的表达,所以它只作为一个因素。

公开当前筛选规则

text
综合分 = 模型平均选字对数概率
       + 2.0 × 关键词线索得分
       - 高频重复字、重复双字片段、重复句尾、相邻叠字的惩罚

这些系数是本项目的产品规则,没有经过文学专家校准。相邻叠字和重复句尾有时也是合理修辞,因此惩罚只用于排序,并不宣称所有重复都错误。真正与已整理语料整首相同的候选会排除,并明确说明是否具备本地语料检查条件。

bash
./poet lesson 23
./poet write --keywords 明月,故乡 --form seven --candidates 24 --count 3 --json

JSON 中的 keyword_literal_hits 是字面出现的关键词;keyword_rule_score 是自动线索得分;model_mean_log_probability 来自模型;repeated_bigramsrepeated_line_endings 表示重复情况。这个文件可以保留作人工比较。

比较单候选与多候选

bash
./poet write --start 春 --candidates 1 --seed 17 --json
./poet write --start 春 --candidates 24 --seed 17 --json

两条命令的采样批形状不同,即使种子相同,也不能假定多候选中的第一首一定等于单候选结果。这里比较的是两种使用配置的效果。想严格对照筛选本身,应该保存同一批候选后比较排序前后;不能把不同样本的变化全部归因于排序。

原诗重复检查的边界

程序将生成正文去掉标点,计算与整理阶段相同的指纹,再查询已整理材料。这个检查能发现整首完全相同,无法穷尽不同繁简规则、近似改写、句子拼接或外部未收录作品。若作品要公开使用,仍应人工检索和审阅。

若把模型单独搬走而没有配套材料,或材料缺失、内容摘要与模型不匹配,corpus_check_available 会为 false,corpus_exact_match 为 null,原因写在 corpus_check_note。生成能力仍可使用,但报告必须说明未执行原诗集合检查,不能把“没检查”写成“已证实没有重复”。

验收与排错

小练习与答案

一首诗包含所有关键词,却把“故乡故乡”重复多次,关键词出现率能说明质量好吗?

查看答案

不能。词面出现只反映一个很窄的事实。还要检查句法、重复、意象关系和整体意思。当前综合排序会惩罚一些重复,但自动规则仍不能替代人工文学评价。

动手补全一小段

先运行上面的完整实验,再复制本课起始文件为自己的练习。starter 有意留空 solve 函数;补全后运行文件,底部检查会告诉你是否符合本课要求。卡住时打开参考答案,比较每一步。

bash
cp lessons/23/starter.py lessons/23/my_exercise.py
# 编辑 my_exercise.py 中的 solve 函数,然后运行:
.venv/bin/python lessons/23/my_exercise.py
# 对照完整答案:
.venv/bin/python lessons/23/solution.py

下载起始代码 · 下载参考答案

展开本课补全练习的完整参考答案
python
"""第 23 课补全练习:检查重复的句尾。修改 solve,保持下方检查不变。"""
import math, io, argparse
import torch
from torch import nn
from torch.nn import functional as F
torch.set_num_threads(2)
torch.manual_seed(26)

def solve(lines):
    endings=[line.rstrip(',。')[-1] for line in lines]
    return len(endings)-len(set(endings))

assert solve(['春风归,','明月回。','江水流,','故人归。'])==1
print("本课补全练习通过。")
展开本课完整、可独立运行的实验代码
python
"""本课独立实验;在仓库根目录执行 .venv/bin/python lessons/23/experiment.py。"""
import sys, json, math
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
import torch
from torch import nn
from torch.nn import functional as F
from poetry_gpt.common import ROOT, DEFAULT_DATA, read_json, read_jsonl
from poetry_gpt.model import ModelConfig, PoetryGPT, CausalAttention, Block
from poetry_gpt.data import Tokenizer, SPECIAL, clean_record, keywords_for
from poetry_gpt.labs import show
torch.set_num_threads(2)
torch.manual_seed(26)

from poetry_gpt.generate import keyword_score
for text in ['明月照归舟', '故乡明月', '千山万水']:
    show(text, keyword_score(text, ['明月', '故乡']))
print('字面线索得分只能辅助筛选,不能判断诗的意境和自然程度。')
下载本课实验