一字一诗
LESSON 21 / 26

指定开头与五七言外形

这一课完成什么

让 CLI 保留指定开头,输出稳定的四句五言或七言。这里需要把模型的文字选择与程序的格式约束明确分开,才能知道到底验收了哪种能力。

外形由程序安排,用字交给模型

四句五言或七言在这个项目中是明确的程序约束。把句尾标点与汉字位置分开处理,便于验收;押韵、平仄还没有因此自动保证。

外形由程序安排,用字交给模型
用文字逐步读这张图
  1. 选择行长:五言 = 5 个汉字;七言 = 7 个汉字
  2. 保留指定开头:用户先写“春”;不让模型改掉
  3. 汉字位置:GPT 提供候选分数;采样得到一个字
  4. 标点位置:程序写逗号或句号;不让模型自由猜
  5. 四句齐全:共 4 × 行长 个汉字;外加四个标点
  6. 再读内容:主题、语义、重复;另作检查
字数正确是否证明学会格律?

没有。字数是程序限定的,严格押韵、平仄和对仗还需要另外实现和验证。

指定开头怎样实现

程序把你输入的开头原样放入正文前缀,再从它后面继续生成。输入“明月”,五言第一句还要补三个汉字。开头最长不能超过一句的字数,必须是字表支持的汉字;繁体输入会按同一套规则转为简体。

bash
./poet write --start 春 --form five
./poet write --start 明月 --form seven
./poet write --start 春江花月夜 --form five

第三条已给满第一句的五个字,程序接上逗号后继续后面三句。指定开头不等于藏头诗:藏头诗要求每一行的开头分别受控,属于后续扩展。

字数和标点由规则决定

五言每句五字加一个标点,四句共 24 个位置。七言每句七字加标点,四句共 32 个位置。在应该放汉字的地方,程序只在字表中的允许汉字范围采样;在标点位置,程序放入指定的逗号或句号。

text
五言:汉 汉 汉 汉 汉 ,
      汉 汉 汉 汉 汉 。
      汉 汉 汉 汉 汉 ,
      汉 汉 汉 汉 汉 。
bash
./poet lesson 21
./poet write --start 春 --form five --json

输出 JSON 包含正文、四行、格式、关键词线索得分和重复检查状态。你可以用 lines 检查每行长度是 6 或 8,最后一个字符是标点。字数校验不能据此证明平仄、押韵或词语搭配正确。

检查失败输入

bash
./poet write --start 春江花月夜山 --form five
./poet write --start abc --form five

两条命令都应给出中文提示并以非零退出码结束。第一条开头过长;第二条不符合汉字要求。程序不会悄悄截断用户的开头,也不会把输入失败当作随机写一首。

为什么不能在最后随意截字

如果先自由生成一大段,再按五字切开,词组可能被截断,句子之间也失去关系。当前程序在生成的每一步就知道标点与条件,它的下一个字会利用已经形成的句子。即使如此,语义质量仍取决于训练与采样,需要人工阅读。

验收与排错

先检查外形,再读懂诗意

下面使用交付模型重新执行首字写诗,固定 seed=2026、device=cpu。图里的每个字保留原样,便于看清“格式正确”和“表达自然”是两项不同的验收。

图 15 / 浏览器截图本次复现命令 · 正式模型
交付效果:指定“春”字开头

交付效果:指定“春”字开头

先看哪里
核对第一字为“春”,四行各有五个汉字,末尾交替使用逗号、句号。
这说明什么
开头与外形由程序保证,其余字来自模型。本例仍有“春来一日来”等重复、不自然表达;外形正确只是第一项检查。
你接着做
换一个常见开头,检查字数之后再用自己的话复述诗意;如果难以复述,就记为语义问题。

打开原图,放大阅读 · 可复制的文字版

查看来源

reports/illustrated/write-prefix.txt

小练习与答案

输入“明月”后,输出以“明月”开头,能说明模型自己学会遵守首字指令吗?

查看答案

不能这样归因。程序直接保留了指定开头,模型负责续写剩余部分。产品行为是符合需求的,但要研究模型是否理解自然语言指令,需要另造带指令的训练材料并单独评估。本课程训练的是条件接字模型。

动手补全一小段

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

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

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

展开本课补全练习的完整参考答案
python
"""第 21 课补全练习:为四句放入标点。修改 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(text, width):
    if width not in (5,7) or len(text)!=4*width: raise ValueError('长度不符')
    return [text[i*width:(i+1)*width]+(',' if i%2==0 else '。') for i in range(4)]

lines=solve('春江花月夜'*4,5)
assert len(lines)==4 and all(len(x)==6 for x in lines)
assert [x[-1] for x in lines]==list(',。,。')
print("本课补全练习通过。")
展开本课完整、可独立运行的实验代码
python
"""本课独立实验;在仓库根目录执行 .venv/bin/python lessons/21/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)

for form in [5, 7]:
    punctuation = [i for i in range(4 * (form + 1)) if i % (form + 1) == form]
    show(f'{form}言标点位置(从0起算)', punctuation)
print('字数和标点由程序约束,选哪个汉字仍来自模型。')
下载本课实验