简介:这套压缩包提供面向中文白话文生成任务的GPT2中文预训练模型,基于Transformer架构在ClueCorpusSmall大规模中文语料上预训练,专门适配新闻、评论、日常对话等白话文场景,适合需要快速生成文本的自然语言处理开发者和研究者。压缩包共11个文件,包括7个json配置与参数文件、1个txt说明文档、safetensors与bin两种格式的模型权重文件,以及1个main入口脚本;各配置对应模型结构、分词器及训练超参数,权重文件可直接被常见深度学习框架加载,整体体积约362.34MB。已有191人学习下载,属于轻量实用的中文预训练模型资源。下载后可获得完整可用的模型权重、配置和调用脚本,无需自行预训练即可应用于文本生成、自动摘要、问答对话、文本校对等下游任务,也可作为迁移学习基础在领域数据上继续微调,显著降低中文文本生成项目的入门与部署成本。 最近在折腾中文文本生成,绕了一圈发现最顺手的一个组合还是哈工大讯飞联合实验室放出来的 gpt2-chinese-cluecorpussmall。这是一个典型的中文预训练模型:基座是 GPT-2,训练语料是 CLUECorpusSmall,专门用来做中文白话文文章生成。给它一句开头,它能接着往下写几百字通顺的中文,用在故事续写、文案草稿、新闻摘要扩写这类场景里特别省事。这个项目实际做的事情很聚焦:把模型怎么下载、数据怎么清洗、怎么在自有语料上继续训练、推理时怎么调参数,完整跑通一遍。适合刚入门文本生成的同学,也适合那些已经在用 BERT 做分类任务、想横向扩展到生成方向的同学。
1. 项目拆解:为什么要做中文白话文生成
1.1 白话文生成到底解决什么问题
很多工业场景里,需求其实不是“造一个对话机器人”,而是“给定一个主题词或开头句,自动生成一段可读的中文段落”。比如电商要批量生成商品描述,新媒体要快速产出文章开头,客服要自动拟写回复草稿。传统模板填充写出来的东西生硬、重复,稍微换个语境就废掉,而基于预训练模型做自回归生成,句式和用词要自然得多。
GPT-2 是自回归语言模型,它做的是“给定前文,预测下一个词”,这个结构和人类写作过程天然一致。对比 LSTM 时代,GPT-2 用了 Transformer 的因果注意力,每个位置只能看到自己和之前的 token,训练时可以并行计算,生成时再逐个往后推。好处是长程依赖抓得比 RNN 稳,RNN 在序列超过 100 之后几乎记不住前文信息,而 GPT-2 在 512 token 范围内表现依然可用,这就给中文长句和段落生成提供了基础保障。
1.2 为什么是 GPT-2:与 BERT、RoBERTa、ResNet 预训练模型的定位差异
预训练模型这个词最近出现频率很高,经常有人把 BERT、RoBERTa、GPT-2、ResNet 放在一起聊,但其实它们解决的问题完全不同。BERT 和 RoBERTa 是双向编码器,训练目标通常是完形填空(MLM),它们做文本分类、序列标注、语义匹配很在行,但输出的是语义表示而不是文本,不能直接用来续写文章。RoBERTa 中文预训练模型在 CLUE 榜单上效果很好,它是“阅读理解型”选手,拿来做生成任务还得挂解码器,改造成本不低。
ResNet 预训练模型又完全是另一条路线,它是图像领域的卷积网络,靠残差结构解决深层网络退化问题,权重和文本特征不通用。虽然“预训练模型”这个词在迁移学习里被泛化了,但不要指望 ResNet 权重能直接延伸到文本生成上。
所以选 GPT-2,核心原因就是它是纯解码器架构,训练目标和生成目标一致,拿来做白话文文章生成不需要额外改造。而 gpt2-chinese-cluecorpussmall 这个模型已经在 CLUECorpusSmall 上做过中文预训练,模型学会了中文词法、句法和一定的事实知识,我们再微调时只需要少量领域数据就能收敛,比自己从零训练省下大量时间和算力。
2. 环境准备与预训练模型下载
2.1 硬件和依赖要求
跑这个小体量模型不需要太夸张的机器。推理场景下,8GB 显存已经很宽裕,纯 CPU 也能跑,但速度会慢到让人怀疑人生。微调场景建议至少 8GB 显存,如果显存不够,可以通过减小 batch size 和梯度累积来缓解。
软件环境方面,推荐 Python 3.8 以上,PyTorch 1.10 以上,transformers 4.20 以上。安装命令非常简单:
pip install torch transformers tokenizers datasets accelerate这里有个细节需要注意:如果你的机器是 NVIDIA 显卡,务必先确认 PyTorch 版本和本地 CUDA 版本匹配,否则torch.cuda.is_available()会一直返回 False,训练时悄悄跑在 CPU 上,速度差几十倍。检查方式:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")2.2 模型下载与文件结构
这个模型的 Hugging Face 路径是uer/gpt2-chinese-cluecorpussmall,在代码里直接指定模型名就能下载。如果你不方便联网,也可以手动下载后放到本地目录,目录里应该有config.json、pytorch_model.bin、vocab.txt、tokenizer_config.json这几个关键文件。
加载代码:
from transformers import AutoTokenizer, AutoModelForCausalLM model_name = "uer/gpt2-chinese-cluecorpussmall" tokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.eos_token = tokenizer.sep_token model = AutoModelForCausalLM.from_pretrained(model_name)这里有个非常重要的点:这个模型用的 tokenizer 是 BERT 风格的 WordPiece,不是 GPT-2 原版 BPE。所以序列里会出现[PAD]、[UNK]、[SEP]这类特殊 token。实际生成时如果不把eos_token设置好,模型可能不会自主输出结束符,导致生成停不下来。常见做法是把[SEP]作为结束符,也就是上面那行tokenizer.eos_token = tokenizer.sep_token,这个细节能省掉你后面大量麻烦。
3. 数据准备与训练细节
3.1 CLUECorpusSmall 数据清洗与样本构造
CLUECorpusSmall 是中文开源语料里比较好上手的,原始数据通常是 json 格式,每条记录包含title和content字段,覆盖百科、新闻、互动问答等类型的书面语文本。虽然叫 Small,实际体量也有十几 GB,足够让模型学到丰富的中文表达。
拿到原始语料后,第一件事是清洗。我习惯按这几步处理:去掉 HTML 标签,统一换行符,全角英文数字转半角,过滤掉长度小于 20 个字符的短文本,以及明显乱码的行。中文文本不需要手动分词,交给 tokenizer 处理就行,但标点要保留,尤其是句号和逗号,它们是模型学习停顿和断句的重要信号。
构造训练样本时,中文文本要切成模型能处理的最大长度。这个模型的n_positions是 512,也就是最多支持 512 个 token,超过的部分会被截断。我推荐用 sliding window 的方式,窗口大小 512,步长 256,相邻样本有 256 token 的重叠,这样既避免文章被硬生生切断,也能让模型在样本边界处有足够上下文。每个样本末尾接上[SEP],作为句子结束信号。
清洗和切分的核心代码大概长这样:
import json from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall") tokenizer.eos_token = tokenizer.sep_token def clean_text(text): text = text.replace("<br>", "\n").replace("<p>", "").replace("</p>", "") text = text.strip() return text def split_text(text, tokenizer, max_len=512, stride=256): tokens = tokenizer.encode(text, add_special_tokens=False) samples = [] for start in range(0, len(tokens), max_len - stride): end = start + max_len sample = tokens[start:end] if len(sample) < 50: continue samples.append(sample + [tokenizer.sep_token_id]) return samples with open("cluecorpussmall.json", "r", encoding="utf-8") as f: lines = f.readlines() all_samples = [] for line in lines: data = json.loads(line) content = clean_text(data.get("content", "")) if len(content) < 50: continue all_samples.extend(split_text(content, tokenizer))这里没有做严格去重,如果读者有精力,可以先用 MinHash 跑一遍近似去重,能明显减少模型复读现象。
3.2 训练脚本关键代码
训练逻辑不算复杂,核心是构造 DataLoader、计算语言模型损失、更新参数。用AutoModelForCausalLM加载模型后,把labels设置为输入序列本身,模型内部会自动把 logits 左移一位,对每个位置计算下一个 token 的交叉熵损失,不需要手工构造 decoder 输入。
一个简化但可运行的训练循环如下:
import torch from torch.utils.data import Dataset, DataLoader from transformers import AdamW, get_linear_schedule_with_warmup class TextGenDataset(Dataset): def __init__(self, samples): self.samples = samples def __len__(self): return len(self.samples) def __getitem__(self, i): return torch.tensor(self.samples[i], dtype=torch.long) def collate_fn(batch): max_len = max(len(x) for x in batch) padded = torch.full((len(batch), max_len), tokenizer.pad_token_id, dtype=torch.long) for i, x in enumerate(batch): padded[i, :len(x)] = x return padded dataset = TextGenDataset(all_samples) dataloader = DataLoader(dataset, batch_size=8, shuffle=True, collate_fn=collate_fn) total_steps = len(dataloader) * 3 optimizer = AdamW(model.parameters(), lr=5e-5) scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=int(total_steps * 0.1), num_training_steps=total_steps) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) for epoch in range(3): for step, input_ids in enumerate(dataloader): input_ids = input_ids.to(device) attention_mask = (input_ids != tokenizer.pad_token_id).long() outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=input_ids) loss = outputs.loss loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad() if step % 100 == 0: print(f"epoch {epoch} step {step} loss {loss.item():.4f}") torch.save(model.state_dict(), f"gpt2_zh_epoch_{epoch}.bin")关于collate_fn,这里用tokenizer.pad_token_id填充,注意这个模型的 pad token id 默认可能也是 0,和[PAD]对应。有些版本里tokenizer.pad_token是 None,建议加载后手动检查,没有就设置为tokenizer.eos_token。
3.3 超参数调节心得
微调 GPT-2 这类模型,超参数直接决定生成效果,但也不需要太多花活。以下几个参数是我实际跑下来的经验值:
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| max_len | 128-512 | 文本较短用 128,长段落生成用 512 |
| batch_size | 4-16 | 根据显存调整,显存不够就减半 |
| learning_rate | 2e-5 到 5e-5 | 学习率太大 loss 会震荡 |
| epochs | 3-5 | 数据量大可以少跑几轮 |
| warmup_steps | 总步数 10% | 帮助模型稳定收敛 |
如果发现 loss 下降很慢,可以优先把学习率降到 2e-5,而不是改模型结构。数据量只有几万条时,epochs 甚至可以只设 2,避免过拟合后生成内容变成复读机。
4. 文本生成推理与效果优化
4.1 快速生成一段中文
训练完或直接加载预训练模型,推理代码很短:
model.eval() prompt = "人工智能在未来的发展" inputs = tokenizer.encode(prompt, return_tensors="pt").to(device) with torch.no_grad(): output_ids = model.generate( inputs, max_new_tokens=200, do_sample=True, temperature=0.9, top_p=0.9, repetition_penalty=1.2, pad_token_id=tokenizer.pad_token_id, eos_token_id=tokenizer.eos_token_id, ) print(tokenizer.decode(output_ids[0], skip_special_tokens=True))max_new_tokens是生成的最大长度,200 相当于中文白话文的一小段,太长容易开始跑题。do_sample=True表示使用随机采样,如果设成 False,模型会走贪心解码,每一句都选概率最高的词,结果非常容易陷入重复循环。
4.2 生成参数这么调,效果才能看
生成参数有点像炒菜的火候,每个参数影响的不只是多样性,还有语义连贯性。推荐用下面这个表当参照:
| 参数 | 推荐范围 | 作用与注意点 |
|---|---|---|
| temperature | 0.7-0.95 | 越低越保守,越高越跳跃,中文生成建议 0.9 附近 |
| top_p | 0.8-0.95 | 保留累计概率达 p 的候选词,过滤掉尾巴上的奇怪词 |
| top_k | 20-40 | 只在概率最高的 k 个词里采样,防止低概率词捣乱 |
| repetition_penalty | 1.1-1.3 | 对重复出现的词做惩罚,太低没用,太高会让句子前言不搭后语 |
举个例子,同样的 prompt,temperature 设成 0.5,输出会比较规矩但略显死板;设到 1.2,句子可能开始出现语法错误。我在实际使用中会先固定 top_p=0.9,再调 repetition_penalty,如果模型总是复读某个词,就把惩罚从 1.1 提到 1.25,基本能压住。
4.3 应用场景与局限,别把模型想得太全能
这个模型的优势是书面语风格稳定,适合做公众号文章开头扩写、电商文案生成、故事续写、新闻标题改写。它生成的句子语法自然,而且能覆盖很多常见话题,直接作为初稿完全没问题。
局限也很明显:模型上下文窗口只有 512 token,超过这个长度后它根本不记得前面在讲什么,长文会前后矛盾;其次,它没有真正的事实推理能力,生成的内容可能看似正确其实是编的,需要人工审核;第三,模型学的是通用书面语,如果要做垂直领域的口播稿或对话文本,必须先在对应语料上微调,否则风格会很飘。
5. 常见问题与排查技巧
5.1 训练阶段问题速查
实际跑训练时,大部分人卡住的点其实都差不多:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| CUDA OOM | batch_size 或 max_len 太大 | 减小 batch_size,或开 gradient_accumulation |
| loss 不下降 | 学习率太高 / 数据噪声太多 | 学习率降到 2e-5,检查清洗逻辑 |
| 模型加载报错 | transformers 版本不一致 | 升级或降级到 4.20 以上 |
| GPU 利用率低 | DataLoader 没开 num_workers | 读取数据耗时太长,适当增加并行 |
| 生成内容出现 [PAD] | pad_token_id 设置不对 | generate 时手动指定 pad_token_id |
里面最容易忽略的是tokenizer.pad_token为 None 的问题。加载 UER 这个模型后,建议加一行代码:
if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token5.2 针对中文生成的独门技巧
中文生成和英文不太一样,有几件事需要额外注意。
一是标点前常常会多出空格。WordPiece tokenizer 对中文按字切分,但某些 token 自带空格前缀,解码后可能变成“人工 智能 在 未来”,需要后处理把空格去掉。可以用简单的正则re.sub(r"\s+", "", text),但要注意英文单词中间的空格也会被去掉,如果文本里有英文,最好先按句子判断再处理。
二是数据清洗不要过度。有些人觉得标点没用就全删掉,结果模型学不会断句,生成一大段不带逗号的文字。中文标点是句法的一部分,保留逗号、句号、引号,训练时才能学到正确的停顿位置。
三是 prompt 风格决定了生成风格。如果你想生成口语化白话文,prompt 就用“我跟你说”“最近我发现”这种开头,模型会顺着风格往下走;如果你给定一个非常学术化的开头,生成结果也会跟着书面语起来。
5.3 卡在原地时先看这三个坑
兜底说一下我坑过的三个地方。第一个是 PyTorch 和 CUDA 版本不匹配,导致 GPU 完全不可用,训练隐形变慢。第二个是dataset忘记开 shuffle,模型每个 epoch 看到的都是相同顺序,loss 震荡剧烈。第三个是保存模型时只存了state_dict,结果加载时忘记先实例化模型,报 key 不匹配的错。更稳妥的方式是直接存全量模型:
model.save_pretrained("./my_gpt2_zh") tokenizer.save_pretrained("./my_gpt2_zh")之后加载就用AutoModelForCausalLM.from_pretrained("./my_gpt2_zh"),省心很多。
结尾
我个人在实际操作中的体会是,gpt2-chinese-cluecorpussmall 特别适合做中文白话文生成的起步模型。它的训练语料覆盖面广,微调难度低,参数量在一亿左右,单卡就能跑起来。如果你手里有几十万条领域文本,在这个模型上继续训练半天,效果就能超过很多从零训的模型。后续还可以往两个方向扩展:一是换更大的 GPT-2 large 版本,二是加 LoRA 做参数高效微调,在效果和成本之间找平衡。最关键的还是先把基础链路跑通,数据清洗、训练、生成、调参这几步走顺了,后面换再大的模型也只是一个参数迁移问题。
本文还有配套的精品资源,点击获取