一字一诗
LESSON 02 / 26

认识这份诗词库

这一课完成什么

读懂一首诗在文件里的样子,分清“正文”和附属信息。模型只会学习我们实际交给它的内容:把作者生平、乱码或注释混进去,它也会尝试模仿。因此,读材料是模型开发的一部分。

一条记录,怎样成为一首诗

文件名、作者、标题和正文承担不同用途。用于接字学习的内容是经过筛选的正文;元信息用来追溯来源。

一条记录,怎样成为一首诗
用文字逐步读这张图
  1. JSON 文件:装着许多条记录;像一个列表
  2. 一条诗记录:title / author;paragraphs
  3. 取出正文:按标点拆为句子;不是按数组项数
  4. 检查外形:四句、五字或七字;本项目学习范围
  5. 保留来源:原文件与记录位置;便于回看原文
  6. 汇总材料:唐诗与宋诗正文;宋词另作扩展
paragraphs 有两项,就一定只有两句吗?

不一定。每项可能包含逗号和句号分开的多句正文。

数据库的实际范围

本次按正文主分片检查到唐诗 57,607 条、宋诗 254,248 条、宋词 21,050 条。全唐诗 目录同时包含唐诗和宋诗;御定全唐詩 是另一种整理版本,不能把它的 43,103 条记录直接当成新增独立作品。这里的数字是记录数,包含重复、残句和不符合本项目四句外形的作品。

唐诗和宋诗通常有 titleauthorparagraphs;宋词有 rhythmic 表示词牌。paragraphs 是字符串列表,但一项可能含两句,不能把列表长度直接当作诗句数。全唐诗/error/ 内是已发现问题的记录,不能递归搜到所有 JSON 就全部加入训练。

查看一首真实作品

bash
./poet lesson 2
python
from pathlib import Path
import json

path = Path("全唐诗/poet.tang.0.json")
records = json.loads(path.read_text(encoding="utf-8"))
poem = records[0]
print(poem["title"])
print(poem["author"])
print("".join(poem["paragraphs"]))

records 是许多记录组成的列表;每条记录是按名字取值的字典;join 把正文各段接起来。你可以只查看前五条,不必打印整份材料。若编码错误,明确指定 UTF-8,不要用忽略错误的方式悄悄丢字。

确定本项目的材料边界

主线只选唐宋诗正文中的四句五言、七言候选。诗经、楚辞、词和曲的句式不同,暂时不混入同一个练习任务。原始文件保留,只把筛选结果写入 artifacts/data/。每首保留来源路径、标题、作者、正文指纹和朝代,遇到异常时能追溯。

单凭字数只能说“外形符合”。押韵需要选择韵部体系,平仄需要处理多音字、古今音差异,本课程第一版没有把这些结论写入材料标签。后续若加严格格律,应另建有出处的校验模块。

动手步骤

bash
.venv/bin/python -c 'import json; print(json.load(open("全唐诗/authors.tang.json"))[0].keys())'

验收与排错

你应能指出原诗正文在哪里,解释为什么不递归导入整个仓库,以及为什么四行字符串不等于四句诗。若找不到文件,确认终端当前处于仓库根目录;若某条正文不是字符串列表,先记录异常,避免强行转成字符串后训练。

小练习与答案

一条记录有两段,每段都是“前句,后句。”,它有几句?如何得到句数?

查看答案

共有四句。先把段落连接起来,再按逗号、句号等标点切分,并去掉空片段。还要检查每句是否只含允许的汉字,以及长度是否一致。若正文中含注释,简单切分可能误判,因此第 3 课会隔离这类候选。

动手补全一小段

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

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

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

展开本课补全练习的完整参考答案
python
"""第 02 课补全练习:读取正文。修改 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(record):
    return ''.join(record['paragraphs'])

assert solve({'paragraphs':['春风,','明月。']})=='春风,明月。'
print("本课补全练习通过。")
展开本课完整、可独立运行的实验代码
python
"""本课独立实验;在仓库根目录执行 .venv/bin/python lessons/02/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 dynasty in ('tang', 'song'):
    p = next((ROOT / '全唐诗').glob(f'poet.{dynasty}.0.json'))
    rec = read_json(p)[0]
    show(dynasty, {k: rec[k] for k in ('title', 'author', 'paragraphs')})
下载本课实验