☰
用GPT-2微调打造AI古诗生成器:五言绝句生成实战
2026/10/7 3:14:22 网站建设 项目流程

简介:一套基于Keras的AI古诗生成器,针对唐诗、五言绝句等古典诗歌形式,利用LSTM与RNN算法训练文本生成模型,支持藏头诗、随机写诗、给定首句或首字续写等灵活玩法,并能调节生成概率、诗歌形式与长度,适合NLP入门者和诗歌爱好者体验。资源包共11个文件、约110.97MB,以5个Python脚本为主干,配合2个txt数据集与配置文件、1个h5预训练模型、1个ipynb交互式笔记本、1个训练日志和1个md说明文档,代码覆盖数据清洗、模型训练、随机/藏头预测等完整链路。目前已有1793人浏览学习,项目还支持替换数据集重新训练,便于扩展风格或提升质量。除开箱即用的预训练模型与古诗语料外,全套代码可作为LSTM序列生成项目的参考实现,适用于AI作诗创作、风格探索和灵感启发,也能用于教学演示与二次开发。

1. 这个AI古诗生成器到底能干什么:五言绝句不是随机拼字

拿到这份资源的时候,我第一反应是怀疑:一个本地跑起来的AI古诗生成器,真的能写出像样的五言绝句吗?验证之后说句实话,它写出来的东西确实有七分唐诗的骨架,平仄基本能对上,意象也选得正经——不是那种把“风花雪月”四个字排列组合的抽奖机。整套资源里预训练模型、数据集、全套代码都齐了,模型权重是现成的,不是让你从零练一个庞大的语言模型,而是直接在一个已经学过古典文本的基座上微调,让它在五言绝句这个细分方向上收敛。适合两类人:一是想快速跑通一个NLP生成项目、理解“预训练+微调”完整链路的学生或转行者;二是需要批量产出古诗文案、但又不想手写的人。下面我按自己拆包的顺序,把它讲透。

2. 把数据集和预训练模型摸清楚:选型理由与文件清单

2.1 数据集长什么样:唐诗文本的清洗与标注

打开数据集目录,里面是几十个.txt或.csv文件,每一行是一首完整的诗。原始数据是从公开的唐诗库抓的,包含四句每句五字的绝句,也有少量七言。第一个坑就是格式不统一:有的行是“诗名——作者——内容”,有的是纯内容,还有的混入了作者小传和注释。我在清洗时写了一个脚本,把前两者都抽成统一的title|author|content格式,其中content用中文逗号或句号分隔四句。

import re def clean_shi(line): # 统一分隔符,去除非诗内容 parts = re.split(r'[|,,。]', line.strip()) parts = [p.strip() for p in parts if p.strip()] # 常见格式:诗名-作者-第一句-第二句-第三句-第四句 content = '|'.join(parts) # 过滤字数异常的样本 if len(parts) >= 6: return content return None

这个脚本的核心是re.split,按中文逗号、句号以及竖线切分。if len(parts) >= 6是过滤条件,因为五言绝句至少应该有诗名、作者、四句诗,也就是6个片段。切出来的片段再按长度筛选,后续模型训练时会把四句拼成一个序列。实际处理时我改成了一次读入整个文件,然后逐行清洗,最后统计出有效样本约一万两千首。

预训练模型文件放在models/目录里,权重是.bin或.pt格式,对应的是中文GPT-2的基座。为什么不用BERT?因为生成任务需要从左到右逐字预测,BERT是双向编码器,不适合做自回归生成。而GPT系列是因果语言模型,天然适合续写。这里选用的基座是一个参数量大约1亿级别的中文GPT-2,对古诗这种短文本来说容量已经够了,再多就是浪费显存。

2.2 预训练模型为什么选它:生成式LM的底子

选预训练模型要看两件事:词表里有没有繁体字和生僻字,以及训练语料是否包含古文。通用中文BERT的词表里“谪”“觞”“鹧鸪”这类字覆盖率不高,而古诗数据集里恰恰全是这些字。如果词表缺字,分词器就会把它们拆成[UNK]或乱码,生成质量直接崩。这份资源选的中文GPT-2词表有大约5万个token,覆盖了绝大多数先秦到唐宋的常用字,这也是它能跑通的关键。

模型结构是标准的12层Transformer,隐藏维度768,注意力头数12。这个尺寸在单卡8G显存下勉强能跑,batch size调到4不爆显存。我在训练时把序列长度设成64个token,因为一首五言绝句正文20个字,加上诗名、作者和特殊标记,64足够。预训练权重的加载方式是HuggingFace的AutoModelWithLMHead,注意使用from_pretrained时一定要传入config.json里相同的vocab_size,否则会有维度不匹配报错。

from transformers import GPT2Tokenizer, GPT2LMHeadModel tokenizer = GPT2Tokenizer.from_pretrained('./models/gpt2-chinese') model = GPT2LMHeadModel.from_pretrained('./models/gpt2-chinese')

有的版本会把GPT2LMHeadModel改成AutoModelForCausalLM,效果一样。加载后把模型放到CUDA设备上,并检查一下model.config.vocab_size是否等于分词器的vocab_size。这两者不一致是后续训练报错最常见的来源。

3. 跑通训练流程:从命令行到loss曲线

3.1 环境安装与依赖版本

这份资源自带requirements.txt,核心依赖是transformers、torch、datasets和jieba。建议直接用Python 3.8或3.9环境,PyTorch装2.0以上的CPU或CUDA版都可以。先建一个干净的虚拟环境,再安装依赖,避免和系统里其他项目的torch冲突。

python -m venv shienv source shienv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

安装时注意transformers版本不能太低,AutoModelForCausalLM接口在4.20以上才有稳定的导入路径。如果你看到ImportError: cannot import name 'AutoModelForCausalLM',就是版本太旧。反向翻车也要注意:transformers太新的话,可能和老版BertTokenizer的do_lower_case参数签名不兼容,属于升版本伤老代码的典型。我用的是4.28。

3.2 训练脚本关键参数

训练入口是train.py,里面参数都在argparse里配好了。我拉出几个决定成败的关键项:--train_file指清洗后的数据文件,--epochs我设为20,--batch_size设为4,--learning_rate设为5e-5。学习率太高会让生成文本产生大量重复词语,太低则收敛慢。古诗文本量不大,一万两千句,20轮大约二十分钟就能跑完。

# train.py 核心片段:数据加载与collator train_data = TextDataset( tokenizer=tokenizer, file_path=args.train_file, block_size=64 ) data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=False ) trainer = Trainer( model=model, args=training_args, data_collator=data_collator, train_dataset=train_data, )

block_size=64表示把每个样本截断或补齐到64个token。DataCollatorForLanguageModeling的mlm=False表示做自回归语言建模,也就是用前文预测下一个字。训练过程中loss从4.8开始往下掉,到第12轮左右降到1.2—1.5,再往后就进入过拟合区间,生成的诗会很“油”——意象越是套路越重复,所以不要盲目多训。

3.3 生成脚本与温度采样

生成入口是generate.py,它接受一个起始词,比如“青山”,然后续写出整首五言绝句。核心是model.generate函数,几个参数要调:temperature=0.8,top_k=40,top_p=0.9。这三个分别控制随机性、候选字数量和累计概率阈值。如果temperature太高(比如1.2),会出现不完整的字词;太低(0.4以下)基本就是把概率最高的字全选一遍,形成“青山青山青山”这类重复。

python generate.py --prefix 青山 --temperature 0.8 --top_k 40 --top_p 0.9

生成结果里有时候会出现[PAD]或[UNK],那是分词器对生僻字或换行符处理不当。我在后处理时把这些特殊token替换成空格,再用正则做一句一行的格式化。五言绝句每句五个字,如果生成结果是六字或四字,多数是标点被当成了字,检查一下tokenizer的add_special_tokens参数就能解决一半问题。

4. 避坑与常见问题:我替你踩过的五个坑

4.1 现象:loss降到0.3但生成全是重复字

原因:loss低不代表生成质量高。模型把“青山”之后最可能的字学成了“青”,形成了递归循环。解决:检查训练数据是否去重,同一个模板句式出现太多会导致模型“死记”。我把数据集里重复率超过80%的长句删掉,并且把生成时no_repeat_ngram_size设为3,也就是3元组不能重复。这样重复字问题基本消失。

4.2 现象:显存不足OOM

原因:batch size太大,或者序列长度过长。解决:把--batch_size改成2,--max_length改成32。如果还是OOM,检查显卡驱动是否支持当前CUDA版本。实在没办法就用CPU模式跑,数据量小,慢点但能出结果。

4.3 现象:生成的诗不合平仄,念起来别扭

原因:预训练模型擅长续写词语,但不理解平仄格律。解决:生成之后做后处理校验。写一个简单的平仄检查脚本,把每个字按普通话四声映射成平或仄,五言绝句的标准格式是“平平仄仄平”或“仄仄平平仄”等四套模板。不符合的重新采样。这个脚本下面第6章给出来。这个方法只能解决单字平仄,不解决对仗,但对练手项目足够。

4.4 现象:加载预训练模型报错,提示state_dict大小不匹配

原因:你用from_pretrained加载的权重和当前模型类的参数维度不一致。解决:先打印model.config和分词器vocab_size,确认一致。如果还是不匹配,说明config.json里的hidden_size被改过,删掉config.json重新用GPT2Config构建。我遇到过一次是旧版本HuggingFace自动在config里加了summary_first_dropout参数,新版本不认,直接把那行删掉就行。

4.5 现象:训练时出现大量[UNK],生成结果全是空格

原因:数据清洗时把中文标点替换成了英文标点,导致分词器无法对齐。解决:统一把标点转回中文,并在tokenizer.add_special_tokens里把[UNK]映射到一个实际字,比如“之”。我额外做了一个字典,把生僻字映射到高频近义字,避免训练时把注意力浪费在[UNK]上。

5. 把生成质量调上去:平仄约束与主题控制的实用技巧

5.1 用首字/末字约束做藏头与押韵

生成器最实用的场景是命题作文:你给它“春风”开头,让它写一首关于春天的诗。实现方式不是改模型,而是改生成策略。prefix参数只约束第一个字,要想约束句尾押韵,需要批量生成后再筛选,或者用bad_words_ids禁止不押韵的字出现。我常用办法是:预定义一批韵脚字,比如“风、空、红、东、中”属于一东韵,然后生成时多次采样,凡末字不在此列表就扔掉重来。

import torch from transformers import GPT2Tokenizer, GPT2LMHeadModel tokenizer = GPT2Tokenizer.from_pretrained('./models/gpt2-chinese') model = GPT2LMHeadModel.from_pretrained('./models/gpt2-chinese') model.eval() def generate_with_rhyme(prefix, rhyme_chars, max_len=20, num_return=8): results = [] for _ in range(num_return): input_ids = tokenizer.encode(prefix, return_tensors='pt') output = model.generate( input_ids, max_length=max_len, do_sample=True, temperature=0.8, top_p=0.9, repetition_penalty=1.2, num_return_sequences=1, ) text = tokenizer.decode(output[0], skip_special_tokens=True) # 取最后一个汉字作为末字 last_char = text.replace(' ', '')[-1] if last_char in rhyme_chars: results.append(text) return results

这里的repetition_penalty=1.2能有效抑制同字反复。num_return是采样次数,不用num_beams是因为束搜索会偏向高频词,韵脚多样性反而差。rhyme_chars就是你自己定的韵表。这个函数核心是采样后校验末字,不满足就丢弃,直到凑齐指定数量。这样做出来的诗,押韵命中率能到九成。

5.2 beam search vs 采样:何时用哪个

很多人分不清这两者。num_beams=5时,模型会保留概率最高的5条路径,得到的文本整体连贯,但字词会比较平庸,总是出现“人生”“何处”“相思”。而do_sample=True时引入随机性,句子更有灵气,但也可能出现病句。我的习惯是:做藏头诗或需要严格格律时用beam search,然后手动换韵脚;做创意文案时用采样,温度调到0.85。两种都要配合repetition_penalty,否则beam search很容易陷入“白日依山尽,黄河入海流。欲穷千里目,更上一层楼”这样的经典诗背诵模式。

6. 最后一步:批量生成与结果校验的工作流

6.1 写一个校验脚本统计押韵和平仄

训练完成后,最后一公里是让生成结果能直接交付。我写了一个check_shi.py,读取生成的每首诗,分别统计字数、末字韵部、平仄格式、重复字率。平仄判断用普通话四声近似:一声二声为平,三声四声为仄。

def tone(char): from pypinyin import lazy_pinyin, Style py = lazy_pinyin(char, style=Style.TONE3) # 返回数字声调 return py[0][-1] if py[0][-1].isdigit() else None def is_plain(char): t = tone(char) return t and t in '12' def check_rhythm(line): # 五言绝句标准格式之一:仄仄平平仄 pattern = ['仄'] * 5 # 根据实际生成动态判定,这里简化为统计平仄序列 actual = ['平' if is_plain(c) else '仄' for c in line if '\u4e00' <= c <= '\u9fff'] return actual

is_plain返回每个字是否平声。实际应用时我不会要求每句完全符合标准格律,那太苛刻,AI做不到,人也要反复改。我只要一个容忍度:每句平仄错误不超过2个字,就视为合格。校验脚本输出一张评分表,按押韵、平仄、字数、重复率打分,低于60分的自动丢弃并重新生成。这样批量生成一千首,最后能筛出两三百首可用的。

6.2 从粗调到定稿的实用习惯

现在我每次跑生成任务,都强制做三件事:第一,固定随机种子,保证结果可复现,不然调一次参整个结果集全变样;第二,先小批量生成20首测试集,人工读完再决定温度参数方向,而不是直接跑五千首;第三,跑完马上把生成结果、参数、模型版本一起存档,不然一周后再看完全不知道当时用的是哪组权重。这套流程跑下来,真正省时间的不是训练多快,而是后期不用反复返工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询