GPT-2输出进阶实战:三步实现JSON/纯文本/Markdown多格式导出
【免费下载链接】gpt-2Code for the paper "Language Models are Unsupervised Multitask Learners"项目地址: https://gitcode.com/GitHub_Trending/gp/gpt-2
上周我用 GPT-2 给团队批量生成产品文案,跑完interactive_conditional_samples.py后,面对满屏的=========== SAMPLE ===========分隔线,我发现自己居然要手动把几十段文本从终端里一段段复制出来、再逐个整理成运营能直接用的文档。那一刻我意识到:GPT-2 生成文本的能力很强,但"输出"这件事,它真的没替我们想好。这篇文章就是为解决这个问题而写——通过给 GPT-2 增加多格式导出功能,让生成结果从"只能看"变成"可直接交付"。
你可能会问的三个问题
动工之前,先回答几个你可能最关心的问题:
- 它到底能做什么?让
interactive_conditional_samples.py和generate_unconditional_samples.py两个生成脚本支持把结果导出为 JSON、纯文本、Markdown 三种格式,同时保留终端打印。 - 上手难吗?不难。不改动
src/model.py、src/sample.py任何一行模型代码,只新增一个格式化模块、给两个入口脚本各加两个参数,全部改动约 150 行。 - 和原版有什么区别?原版只能
print(text)后靠人肉复制,改造后一句命令行就能把结果落盘成结构化文件,还能自动附带样本编号、时间戳、模型参数等元数据。
先说结论:一套"格式化输出层"解决所有问题
整个方案的思路很简单:在生成流程的末端插入一个可插拔的格式化层,文本从sample_sequence出来后,先经过encoder.decode()还原成字符串,再交给不同的Formatter加工,最后既打印到终端、又按需写入文件。
一句话概括:生成逻辑不动,输出逻辑重写。新增的
src/formatter.py用"策略模式"组织,每个格式一个类,想加新格式只需再写一个类,互不干扰。
第一步:新建 formatter.py,把"输出格式"变成可插拔
在src/目录下新建formatter.py,定义基类、三种格式实现和一个工厂函数:
import json import re import datetime class OutputFormatter: """所有格式化器的基类,子类必须实现 format()""" def format(self, text, metadata=None): raise NotImplementedError class PlainTextFormatter(OutputFormatter): def format(self, text, metadata=None): return text class JsonFormatter(OutputFormatter): def format(self, text, metadata=None): result = { "text": text, "length": len(text), "tokens": len(text.split()), } if metadata: result.update(metadata) return json.dumps(result, ensure_ascii=False, indent=2) class MarkdownFormatter(OutputFormatter): def format(self, text, metadata=None): md = "# GPT-2 Generated Text\n\n" # 把空行分隔的段落重新整理,压缩多余空白 paragraphs = re.split(r'\n\s*\n', text.strip()) md += '\n\n'.join([' '.join(p.split()) for p in paragraphs if p.strip()]) if metadata: md += "\n\n## 生成信息\n" for k, v in metadata.items(): md += f"- **{k}**: {v}\n" return md class FormatterFactory: @staticmethod def get_formatter(format_type): if format_type == 'json': return JsonFormatter() elif format_type == 'markdown': return MarkdownFormatter() else: return PlainTextFormatter()设计上只做三件事:基类定契约、子类管格式、工厂负责分发。JsonFormatter里用ensure_ascii=False保证中文不被转义成\uXXXX,MarkdownFormatter用正则把零散换行整理成规整段落——这两个细节后面避坑小节还会再提。
第二步:给交互式脚本加上输出格式与文件导出参数
打开src/interactive_conditional_samples.py,在interact_model的函数签名末尾追加两个参数,然后在生成循环里插入格式化与写文件逻辑:
def interact_model( model_name='124M', seed=None, nsamples=1, batch_size=1, length=None, temperature=1, top_k=0, top_p=1, models_dir='models', output_format='text', # 新增:text / json / markdown output_file=None, # 新增:输出文件路径 ): # ... 原有模型加载代码保持不变 ... while True: raw_text = input("Model prompt >>> ") # ... 原有 encode 与 sess.run 逻辑保持不变 ... for i in range(batch_size): generated += 1 text = enc.decode(out[i]) formatter = FormatterFactory.get_formatter(output_format) metadata = { "sample_id": generated, "prompt": raw_text, "timestamp": datetime.datetime.now().isoformat(), "model_name": model_name, "temperature": temperature, "top_k": top_k, } formatted = formatter.format(text, metadata) print("=" * 40 + " SAMPLE " + str(generated) + " " + "=" * 40) print(formatted) if output_file: append_to_file(output_file, formatted, output_format)文件写入我单独抽了一个append_to_file函数,重点是JSON 的追加写法。因为 JSON 是"一整个数组"的结构,多次追加不能直接write(),否则文件会变成非法 JSON:
def append_to_file(path, content, fmt): with open(path, 'a', encoding='utf-8') as f: if fmt == 'json': if os.path.getsize(path) == 0: f.write('[\n') else: # 删除末尾的 ']',补逗号,再写新条目 with open(path, 'r+', encoding='utf-8') as rf: rf.seek(0, os.SEEK_END) rf.seek(rf.tell() - 1, os.SEEK_SET) rf.write(',\n') f = open(path, 'a', encoding='utf-8') f.write(content) f.write('\n]') else: f.write("\n" + "=" * 40 + " SAMPLE " + "\n") f.write(content) f.write('\n')JSON 这个"先删尾巴再补逗号"的写法,是让多次追加后文件依然是一个合法 JSON 数组的关键,值得单独记住。
第三步:让批量生成脚本也支持同样的参数
src/generate_unconditional_samples.py的结构和交互版几乎一样,照着同样的套路改即可。注意两点不同:它没有prompt,元数据里去掉该字段;它支持nsamples=0表示无限生成,写文件时建议每满一批就 flush 一次,避免程序中断时丢数据:
def sample_model( # ... 原有参数 ... output_format='text', output_file=None, ): # ... 原有加载逻辑 ... generated = 0 while nsamples == 0 or generated < nsamples: out = sess.run(output) for i in range(batch_size): generated += batch_size text = enc.decode(out[i]) formatter = FormatterFactory.get_formatter(output_format) metadata = { "sample_id": generated, "timestamp": datetime.datetime.now().isoformat(), "model_name": model_name, "temperature": temperature, } formatted = formatter.format(text, metadata) print("=" * 40 + " SAMPLE " + str(generated) + " " + "=" * 40) print(formatted) if output_file: append_to_file(output_file, formatted, output_format)两个脚本都依赖fire.Fire()自动解析命令行参数,所以新增的output_format、output_file不需要任何额外注册,直接就能在命令行使用。
第四步:跑起来看效果
环境准备(如果还没下载模型):
# 克隆项目并安装依赖 git clone https://gitcode.com/GitHub_Trending/gp/gpt-2 cd gpt-2 pip install -r requirements.txt # 下载 124M 最小模型 python download_model.py 124M交互式生成,JSON 格式落盘:
python src/interactive_conditional_samples.py \ --model_name=124M \ --output_format=json \ --output_file=results.json批量生成,一次产出 10 篇 Markdown 文档内容:
python src/generate_unconditional_samples.py \ --model_name=124M \ --nsamples=10 \ --length=200 \ --output_format=markdown \ --output_file=articles.md最终results.json里的每条记录长这样:
{ "text": "人工智能是计算机科学的一个分支,致力于创造能够模拟人类智能的系统……", "length": 856, "tokens": 156, "sample_id": 1, "prompt": "什么是人工智能?", "timestamp": "2026-08-13T17:22:45.123456", "model_name": "124M", "temperature": 1.0, "top_k": 0 }改造前后对比:省下的不只是复制粘贴
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 结果获取 | 终端打印,人肉复制 | 自动落盘成文件 |
| 数据形态 | 纯文本,无结构 | JSON 带元数据,可直接入库 |
| 批量产出 | 每次手动整理 | 一条命令产出 N 个样本 |
| 二次加工 | 需自己写解析 | 交给程序即可消费 |
| 可追溯性 | 无记录 | 时间戳、参数、编号齐全 |
新增参数一览:
| 参数名 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| output_format | string | text | 输出格式,可选 text / json / markdown |
| output_file | string | None | 输出文件路径,不填则仅终端显示 |
| model_name | string | 124M | 模型大小,可选 124M/355M/774M/1558M |
| temperature | float | 1 | 随机性控制,越小越确定 |
| top_k | integer | 0 | 采样候选词数量,0 表示不限制 |
| top_p | float | 1 | 核采样概率阈值,0 到 1 之间 |
| nsamples | integer | 1/0 | 样本数,批量脚本中 0 表示无限生成 |
| length | integer | 由模型决定 | 生成文本长度(token 数) |
避坑指南:这些坑我替你先踩过了
⚠️JSON 文件多次追加会变成非法文件。直接f.write()三次,结果就是{...}{...}{...},任何解析器都会报错。必须用上面"删尾补逗号"的写法,或者干脆改成每个样本存一个独立文件。
⚠️中文变成\uXXXX转义。json.dumps默认会把非 ASCII 字符转义。写代码时务必带上ensure_ascii=False,否则中文全变成乱码一样的转义串。
⚠️fire 参数名别用连字符。本项目用fire.Fire解析参数,命令行里应使用下划线写法如--output_format,而不是--output-format,否则参数会被静默忽略。
⚠️生成长度别超过模型窗口。length大于模型的n_ctx会直接抛错,124M 模型窗口是 1024,建议交互模式用默认值n_ctx // 2。
💡小技巧:想让 Markdown 里标题反映每次 prompt 的主题,可以在metadata里加一个title字段,MarkdownFormatter里用metadata.get('title', ...)兜底即可。
举一反三:还能怎么玩
这个格式化层的设计决定了"加格式"极其廉价,你完全可以按同样套路扩展:
- CSV 格式:继承
OutputFormatter,把文本里的逗号、换行转义后拼成一行,方便导入 Excel 做统计分析。 - HTML 格式:把段落包进
<p>标签,配合metadata生成一个可发布的静态页面。 - 自定义模板:给
MarkdownFormatter增加template参数,让用户自己定义标题层级和元数据排版。 - 自动校验:在写文件前对生成文本做空值、长度检查,把异常样本单独记入
format_errors.log,避免污染主输出文件。 - 流式落盘:批量生成时用缓冲写入(攒够 N 条再 flush),配合大
nsamples跑过夜任务也不怕中断。
现在就去试试
回头看,这件事的本质是:模型负责"生成什么",我们负责"长什么样"。一个不到 200 行的格式化层,就让 GPT-2 从"实验室玩具"变成了能直接对接业务的数据源——无论是把结果喂给下游程序、整理成发布文档,还是积累成微调用的训练语料,都不再需要人肉搬运。
下一步建议:clone 项目后先跑一遍--output_format=text确认改动没破坏原有行为,再切到 JSON 生成一批样本,你会惊喜地发现——原来 GPT-2 的输出也可以这么"听话"。现在就打开终端试试吧。
【免费下载链接】gpt-2Code for the paper "Language Models are Unsupervised Multitask Learners"项目地址: https://gitcode.com/GitHub_Trending/gp/gpt-2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考