1. 项目缘起与整体设计思路
1.1 为什么选择GPT-2做中文新闻生成
中文新闻文本生成这个方向,说实话,坑比想象中多得多。我最初接触这个项目的时候,想法很简单:找一个预训练语言模型,喂一批新闻语料,微调一下,让它能写出像模像样的新闻稿。但真正动手之后才发现,从模型选型到数据处理再到生成质量控制,每一步都有大量需要权衡的细节。
选GPT-2作为基座模型,核心原因有三个。第一,它的架构足够经典且成熟,Decoder-only的自回归结构天然适合文本生成任务,每个token的预测都依赖前面所有token的上下文信息,这和新闻写作中“前文决定后文走向”的逻辑高度一致。第二,GPT-2的参数量适中,最小的124M版本在单张消费级显卡上就能完成微调,最大的1.5B版本也不算离谱,对于个人开发者和小团队来说非常友好。第三,中文社区有大量基于GPT-2架构重新预训练的模型权重可以直接拿来用,省去了从零预训练的巨大算力开销。
这里要特别说明一点:原版GPT-2是在英文语料上训练的,它的tokenizer对中文的支持非常糟糕。一个中文字符可能被拆成多个byte级别的token,导致序列长度暴增,模型学起来效率极低。所以做中文新闻生成,第一步不是直接拿原版GPT-2来微调,而是要用中文语料重新训练过的GPT-2权重,或者至少换一个针对中文优化的tokenizer。
1.2 新闻文本生成的独特挑战
新闻文本和小说、诗歌、对话等文本类型有本质区别。它的特点非常鲜明:结构规整、信息密度高、用词正式、句式相对固定。一篇标准的新闻稿通常包含标题、导语、正文、背景信息等几个部分,每部分都有约定俗成的写作规范。
这就带来几个技术上的挑战。首先是事实一致性问题。新闻的核心是传递真实信息,但语言模型本质上是概率生成器,它可能会编造出看起来合理但实际不存在的事实。比如生成“某公司第三季度营收达到XX亿元”这样的句子,数字可能是模型随机编的。这个问题在当前技术条件下没有完美解法,但可以通过训练数据的筛选和生成策略的调整来缓解。
其次是长度控制问题。新闻稿有长有短,短的几百字,长的几千字。GPT-2在生成时容易出现“越写越偏”的情况,前面几句还在说A事件,后面就跑到B事件去了。这需要在训练时注意数据的组织方式,并在推理时加入适当的重复惩罚和主题约束。
第三个挑战是风格一致性。不同媒体、不同栏目的新闻风格差异很大,有的偏严肃正式,有的偏通俗易懂。如果训练数据混杂了多种风格,模型生成出来的文本就会“四不像”。我的做法是在数据预处理阶段就做好风格标注,训练时可以通过前缀提示来控制生成风格。
1.3 整体方案架构
整个项目的架构可以分成四个层次来理解。最底层是数据层,负责新闻语料的采集、清洗、格式化和分词。往上是模型层,包括基座模型的选择、微调策略的设计和训练过程的监控。再往上是推理层,处理生成时的解码策略、长度控制、重复抑制等问题。最顶层是应用层,提供API接口或交互界面,让用户能够方便地输入提示词并获取生成结果。
这个分层设计的核心思路是解耦。数据处理的逻辑和模型训练的逻辑分开,模型训练的逻辑和推理部署的逻辑分开。这样做的好处是,当你想换一个基座模型时,只需要改动模型层,数据层和推理层的代码基本不用动。当你想调整生成策略时,也不需要重新训练模型。
在实际操作中,我建议把配置文件独立出来,用YAML或JSON格式管理各种超参数。训练时的学习率、批次大小、序列长度,推理时的温度系数、top-k值、重复惩罚系数,这些参数都需要反复调整,硬编码在代码里会非常痛苦。
2. 数据准备与预处理的核心细节
2.1 新闻语料的采集与清洗
数据质量直接决定了生成质量,这句话在文本生成任务里是铁律。我见过太多人随便找几万条新闻就开始训练,结果生成出来的东西惨不忍睹。新闻语料的采集有几个关键点需要注意。
来源多样性很重要。如果只从一家媒体采集数据,模型学到的风格就会非常单一。理想情况下,应该覆盖不同地区、不同题材、不同风格的新闻源。时政、财经、科技、体育、娱乐,每个类别的新闻写法都有差异,模型见过的类别越多,泛化能力越强。
时间跨度也要考虑。新闻语言会随着时间演变,五年前的新闻和现在的新闻在用词、句式上都有区别。如果要做通用新闻生成,建议采集最近两到三年的数据。如果要做特定时期的新闻风格模拟,那就需要针对性地采集对应时间段的数据。
清洗环节是体力活,但绝对不能省。我通常按以下步骤来处理:
- 去除HTML标签和特殊字符:新闻网页抓下来往往带有大量HTML标签、JavaScript代码、CSS样式,这些必须彻底清除。用BeautifulSoup或正则表达式都可以,但要注意不要误删正文中的合法符号。
- 过滤过短和过长的文本:太短的新闻(比如只有一句话的快讯)信息量不足,太长的新闻(比如几万字的深度报道)会超出模型的序列长度限制。我一般保留200字到2000字之间的新闻。
- 去除重复内容:新闻转载非常普遍,同一篇稿子可能出现在几十个网站上。用SimHash或MinHash做去重,相似度超过阈值的只保留一篇。
- 处理乱码和编码问题:中文新闻常见的编码问题包括GBK和UTF-8混用导致的乱码,需要统一转成UTF-8。
- 去除广告和无关信息:新闻页面里的“点击查看更多”、“相关推荐”、“版权声明”等内容要过滤掉。
2.2 数据格式化与分词处理
清洗完的原始文本不能直接喂给模型,还需要格式化成模型能理解的输入形式。对于GPT-2这样的自回归模型,训练数据就是一个个token序列,模型的目标是预测下一个token。
我的做法是把每篇新闻组织成以下格式:
<|title|>新闻标题<|content|>新闻正文<|endoftext|>这样设计的好处是,模型在生成时可以通过控制特殊token来决定生成标题还是正文。比如输入<|title|>,模型就会生成标题;输入<|title|>某个标题<|content|>,模型就会基于这个标题生成正文。
分词环节是中文GPT-2项目的关键差异点。原版GPT-2用的是BPE分词器,对中文不友好。我试过几种方案:
| 分词方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原版GPT-2 BPE | 无需额外处理 | 中文token效率极低 | 不推荐 |
| 字符级分词 | 词表小,无OOV问题 | 序列长,语义粒度粗 | 小规模实验 |
| 中文BERT分词器 | 词表适中,语义粒度好 | 需要适配GPT-2架构 | 推荐方案 |
| SentencePiece | 可自定义词表大小 | 需要重新训练 | 有充足语料时 |
我最终选择的是基于中文BERT分词器改造的方案。具体做法是保留BERT的WordPiece分词逻辑,但把词表映射到GPT-2的embedding层。这样既能利用BERT分词器对中文的良好支持,又能保持GPT-2的架构不变。
实际操作中,序列长度设置为512或1024比较合适。512对于大多数新闻正文来说够用,如果要做长文本生成,可以扩展到1024。但要注意,序列越长,训练时的显存占用越大,需要根据硬件条件做权衡。
2.3 训练集、验证集、测试集的划分
数据划分看似简单,但有几个坑需要注意。首先是不能随机划分,因为新闻数据往往有时间相关性,随机划分会导致验证集和测试集中出现与训练集高度相似的样本,评估结果会虚高。正确的做法是按时间划分,用较早的数据做训练,较晚的数据做验证和测试。
其次是类别平衡。如果训练集里财经新闻占80%,体育新闻只占5%,模型生成体育新闻的能力就会很弱。在划分数据时,要确保各个类别的新闻在三个集合中的比例大致相当。
我通常按8:1:1的比例划分,训练集占80%,验证集和测试集各占10%。如果数据量特别大,可以调整为98:1:1。划分完成后,还要做一次交叉检查,确保没有数据泄露。
3. 模型微调与训练实操
3.1 基座模型的选择与加载
前面提到,原版GPT-2对中文支持不好,所以需要找一个中文预训练权重。目前社区里有几个可用的选择,比如基于GPT-2架构在中文语料上重新预训练的模型,或者用中文数据继续训练原版GPT-2得到的权重。选择时主要看预训练语料的规模和质量,语料越大、越干净,微调效果通常越好。
加载模型时要注意几个参数。vocab_size要和分词器的词表大小一致,n_positions决定了模型能处理的最大序列长度,n_embd、n_layer、n_head决定了模型的参数量和表达能力。如果显存有限,可以选择较小的配置,比如12层、768维的版本;如果追求生成质量,可以用24层、1024维甚至更大的版本。
from transformers import GPT2LMHeadModel, GPT2Config config = GPT2Config( vocab_size=21128, # 与分词器词表一致 n_positions=1024, # 最大序列长度 n_embd=768, # 嵌入维度 n_layer=12, # Transformer层数 n_head=12, # 注意力头数 ) model = GPT2LMHeadModel(config)如果使用已有的中文预训练权重,直接用from_pretrained加载即可。加载后建议先跑几条测试样本,确认模型能正常输出中文,再开始微调。
3.2 训练超参数的设置与调整
超参数设置是微调过程中最需要经验的部分。我把自己反复试验后总结的参数范围列出来,供参考:
| 超参数 | 推荐范围 | 说明 |
|---|---|---|
| 学习率 | 1e-5 ~ 5e-5 | 太大容易震荡,太小收敛慢 |
| 批次大小 | 8 ~ 32 | 受显存限制,可用梯度累积 |
| 训练轮数 | 3 ~ 10 | 看验证集loss,过拟合就停 |
| 权重衰减 | 0.01 ~ 0.1 | 防止过拟合 |
| 预热步数 | 总步数的5%~10% | 避免初期震荡 |
| 梯度裁剪 | 1.0 | 防止梯度爆炸 |
学习率是最关键的参数。我的经验是,如果基座模型已经充分预训练过,微调学习率设在2e-5左右比较稳妥。如果基座模型质量一般,可以适当提高到5e-5。训练过程中要密切关注loss曲线,如果loss下降后又反弹,说明学习率可能太大了。
批次大小受显存限制时,可以用梯度累积来模拟大批次。比如想要32的有效批次但显存只够放8,就设置gradient_accumulation_steps=4,每4个step更新一次参数。
训练轮数不是越多越好。新闻生成任务通常3到5轮就能看到不错的效果,继续训练可能会过拟合。判断过拟合的方法是看验证集loss,如果训练集loss持续下降但验证集loss开始上升,就该停了。
3.3 训练过程中的监控与调试
训练不是设好参数就等着出结果,中间需要持续监控。我通常会记录以下指标:
- 训练loss和验证loss:最基本的指标,判断模型是否在学习。
- 学习率变化曲线:确认预热和衰减策略是否按预期执行。
- 梯度范数:如果梯度范数突然变得很大,说明可能出现了梯度爆炸。
- 生成样本:每隔一定步数,用固定的prompt让模型生成几条文本,直观感受生成质量的变化。
生成样本的监控特别重要。有时候loss在下降,但生成出来的文本反而变差了,这可能是模型在“死记硬背”训练数据,而不是真正学会了语言规律。通过定期查看生成样本,可以及时发现这种问题。
调试时常见的问题包括:loss不下降(检查学习率、数据格式、标签对齐)、loss变成NaN(检查梯度裁剪、学习率是否过大)、生成结果全是重复内容(检查重复惩罚、训练数据是否有大量重复)。这些问题在后面的章节会详细展开。
4. 文本生成策略与质量控制
4.1 解码策略的选择与对比
模型训练好之后,怎么从模型输出的概率分布中选出最终的token序列,这就是解码策略要解决的问题。不同的解码策略对生成质量的影响非常大,我逐一分析。
贪心搜索是最简单的方式,每一步都选概率最大的token。优点是稳定、可复现,缺点是生成结果往往很单调,容易陷入重复循环。比如一直生成“的的的的”或者“是是是是”。
束搜索维护多个候选序列,每一步保留概率最高的k个候选。比贪心搜索好一些,但计算开销大,而且生成的文本仍然偏向保守,缺乏多样性。
随机采样按照概率分布随机选token,温度系数控制随机程度。温度越高越随机,温度越低越确定。温度设0.7到0.9之间通常效果不错,既有一定多样性,又不会太离谱。
Top-k采样只从概率最高的k个token中采样,避免选到概率极低的奇怪token。k值一般设30到50。
Top-p采样也叫核采样,从累积概率达到p的最小token集合中采样。p值一般设0.9到0.95。相比Top-k,Top-p能动态调整候选集大小,在概率分布尖锐时选少量token,在分布平坦时选更多token。
重复惩罚是必加的,对已经出现过的token降低其概率。惩罚系数一般设1.1到1.3,太高会导致语句不通顺。
我的推荐组合是:温度0.8 + Top-p 0.92 + 重复惩罚1.15。这个组合在新闻生成任务上表现比较均衡。
4.2 生成质量控制与后处理
模型生成出来的原始文本不能直接发布,需要经过后处理。后处理主要包括以下几个方面:
重复检测与去除:模型有时会连续生成相同的句子或段落。可以用n-gram重叠度来检测,超过阈值的重复部分直接截断。
事实性校验:对于新闻中的数字、日期、人名、机构名等关键信息,如果条件允许,应该与知识库做交叉验证。这一步在自动化流程中比较难做,但至少可以标记出可疑内容供人工审核。
格式规范化:生成的文本可能存在标点符号使用不规范、段落划分不合理等问题。可以用规则做一轮清洗,比如统一全角半角标点、合并过短的段落、拆分过长的段落。
敏感内容过滤:虽然训练数据已经做过清洗,但模型仍可能生成不合适的内容。需要建立敏感词库,对生成结果做过滤。
4.3 提示词工程在新闻生成中的应用
GPT-2是自回归模型,生成的内容完全由输入的提示词决定。设计好的提示词,可以显著提升生成质量。
对于新闻标题生成,提示词可以设计为:
<|title|>科技模型会接着生成“科技”开头的标题。如果想控制标题长度,可以在提示词中加入长度暗示,比如:
<|title|>科技[短标题]对于新闻正文生成,提示词包含标题和开头:
<|title|>某公司发布新一代产品<|content|>据悉,模型会基于这个开头续写正文。提示词给得越具体,生成结果越可控。
我实测下来,提示词的长度在10到30个字之间效果最好。太短了模型没有足够的方向感,太长了模型容易照抄提示词。
5. 常见问题与排查技巧实录
5.1 生成重复内容的排查与解决
重复生成是GPT-2微调后最常见的问题。表现是模型反复输出相同的短语、句子甚至段落。原因通常有三个:训练数据中重复内容太多、解码策略没有加重复惩罚、模型过拟合。
排查步骤:先检查训练数据,用n-gram统计找出高频重复的片段,在数据清洗阶段去除。然后检查解码参数,确保repetition_penalty大于1.0。如果这两步都做了还有重复,可能是模型过拟合了,减少训练轮数或增加Dropout。
我踩过的一个坑是:重复惩罚设得太高(比如1.5),导致模型不敢重复任何token,生成出来的句子虽然不重复了,但读起来非常别扭,像是把正常句子里的词随机替换了一样。后来把惩罚系数降到1.15,效果就自然多了。
5.2 生成内容偏离主题的处理
有时候模型生成的文本开头还扣题,写着写着就跑到完全无关的方向去了。这个问题在生成长文本时尤其明显。
解决方法有几个。一是缩短单次生成的长度,用滑动窗口的方式分段生成,每段都重新给提示词。二是加入主题词约束,在解码时对与主题相关的token给予更高的权重。三是用强化学习的方法微调模型,奖励扣题的生成结果,惩罚跑题的结果,但这个方案实现复杂度较高。
我常用的方法是第一种,把长文本拆成多个短段落分别生成,然后用规则或小模型做段落间的衔接。虽然麻烦一点,但可控性最好。
5.3 显存不足与训练中断的应对
显存不足是个人开发者最常遇到的问题。解决方案按优先级排列:
- 减小批次大小:最直接有效,但可能影响训练稳定性。
- 使用梯度累积:模拟大批次效果,不增加显存占用。
- 混合精度训练:用fp16代替fp32,显存占用减半,速度也有提升。
- 梯度检查点:用计算时间换显存空间,适合深层模型。
- 模型并行:把模型拆分到多张显卡上,适合大模型。
- 减小序列长度:从1024降到512或256,显存占用显著降低。
训练中断的恢复也很重要。要定期保存检查点,包括模型权重、优化器状态、学习率调度器状态。恢复训练时从最近的检查点加载,确保训练状态完全恢复。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| loss不下降 | 学习率过小/数据格式错误 | 检查数据编码和标签对齐 | 调整学习率,重新检查数据 |
| loss变NaN | 学习率过大/梯度爆炸 | 查看梯度范数 | 降低学习率,加梯度裁剪 |
| 生成重复内容 | 数据重复/无重复惩罚 | 统计训练数据n-gram | 清洗数据,加重复惩罚 |
| 生成跑题 | 提示词不明确/训练不足 | 检查提示词设计 | 优化提示词,增加训练数据 |
| 显存不足 | 批次过大/序列过长 | 查看显存占用 | 减小批次,用梯度累积 |
| 生成速度慢 | 模型太大/解码策略复杂 | 测试不同解码策略 | 用贪心或小模型 |
| 中文乱码 | 编码问题 | 检查文件编码 | 统一转UTF-8 |
| 过拟合 | 训练轮数过多 | 对比训练和验证loss | 早停,加正则化 |
6. 项目扩展与优化方向
6.1 从GPT-2到更大模型的迁移
GPT-2毕竟是比较早期的架构,现在有更多更强的模型可以选择。如果硬件条件允许,可以考虑迁移到更大的模型上。迁移时需要注意几点:新模型的tokenizer可能不同,需要重新处理数据;模型的输入输出格式可能有差异,需要调整代码;超参数需要重新调优,不能直接套用GPT-2的参数。
迁移的收益通常很明显,更大的模型在语言理解和生成质量上有显著提升。但代价是训练和推理的成本也成倍增加,需要根据实际需求做权衡。
6.2 引入检索增强生成
纯生成模型的一个硬伤是事实性无法保证。引入检索增强生成(RAG)可以缓解这个问题。具体做法是:在生成之前,先从新闻数据库中检索与主题相关的真实新闻片段,把这些片段作为上下文一起输入模型,让模型基于真实信息来生成。
这样做的好处是生成内容有事实依据,不容易编造。实现上需要搭建一个检索系统,可以用向量数据库存储新闻的embedding,查询时做相似度搜索。检索到的片段和用户输入拼接后一起送给模型。
6.3 多任务联合训练
新闻生成不是孤立的任务,它和新闻分类、摘要生成、关键词提取等任务有很强的关联性。可以设计多任务学习框架,让模型同时学习多个任务,共享底层表示。这样做的好处是各个任务之间可以互相促进,提升整体效果。
实现上可以在模型输出层加多个任务头,每个任务头负责一个任务的输出。训练时交替优化各个任务的loss,或者用加权求和的方式组合loss。任务权重的设置需要根据实际效果调整。
6.4 部署与性能优化
模型训练好之后,最终要部署上线提供服务。部署时需要考虑几个问题:推理速度、并发能力、资源占用。
推理加速的手段包括:模型量化(把fp32转成int8,速度提升2到4倍)、模型剪枝(去掉不重要的参数)、知识蒸馏(用大模型教小模型)、ONNX Runtime或TensorRT等推理引擎加速。
并发处理可以用批处理的方式,把多个请求拼成一个批次一起推理,提高GPU利用率。但要注意批次太大会增加延迟,需要根据实际QPS做权衡。
我在实际部署中发现,对于新闻生成这种场景,用户对延迟的容忍度比较高(几秒钟都能接受),但对生成质量要求很高。所以优先保证质量,速度可以通过加机器来弥补。
7. 一些实操心得与避坑建议
做这个项目前后折腾了几个月,踩过的坑不计其数。有几个心得我觉得特别值得分享。
数据质量比数据数量重要得多。我一开始贪多,采集了上百万条新闻,但清洗不彻底,里面混了大量广告、乱码、重复内容。结果模型训练出来生成的东西乱七八糟。后来把数据量砍到二十万条,但每条都经过严格清洗,效果反而好了很多。
不要迷信大模型。124M的GPT-2在新闻生成任务上,只要数据质量够好、训练策略得当,生成效果完全可以接受。我对比过124M和355M的版本,在新闻这个相对规整的领域,差距没有想象中那么大。先用小模型快速迭代,验证方案可行后再上大模型,这样效率最高。
生成参数需要反复试验。温度、Top-p、重复惩罚这些参数没有万能的最优值,不同的模型、不同的数据、不同的应用场景,最优参数都不一样。建议做一个参数扫描的实验框架,自动跑不同的参数组合,人工评估生成质量,找到最适合当前场景的参数。
人工评估不可替代。自动评估指标(如困惑度、BLEU、ROUGE)只能作为参考,真正判断生成质量还是要靠人来看。我通常会随机抽100条生成结果,逐条评估流畅度、相关性、事实性,记录问题类型和频率。这个过程很耗时,但能发现很多自动指标发现不了的问题。
版本管理很重要。数据处理脚本、训练代码、模型权重、配置文件,这些都需要用Git或类似工具管理起来。每次实验记录清楚用了什么数据、什么参数、什么代码版本,否则过一段时间自己都忘了当时是怎么跑的。
最后再分享一个小技巧:在训练初期,可以用较小的学习率预热几百步,让模型先适应新的数据分布,然后再用正常学习率训练。这个技巧对微调效果提升很明显,尤其是当训练数据和预训练数据分布差异较大时。