基于GPT-2的创意写作实践:人机协作流程、提示工程与模型微调指南
1. 项目缘起一个写作者的“技术自救”作为一个写了十几年东西的人我常常卡在同一个地方开头。不是没有故事而是有太多故事在脑子里打架每个开头都像一扇门推开一扇就意味着放弃了其他九十九扇门后的风景。这种选择焦虑加上对“完美初稿”的执念常常让我在文档前枯坐数小时最后只留下一个光秃秃的标题和满屏的删除线。去年我偶然读到一篇关于GPT-2的旧闻这个在2019年发布时因“过于危险”而被OpenAI暂缓完全开源的语言模型如今在技术社区里已经像一位过气的明星。大家的目光都聚焦在更强大的GPT-3、GPT-4乃至各种开源大模型上。但正是这种“过气”让我看到了机会它的能力边界相对清晰资源消耗远低于新模型对于一个只想把它当作“写作副驾驶”的个人项目来说或许正合适。我不需要它写出惊世骇俗的文学巨著我只需要它在我思路枯竭时能递给我几块形状各异的“积木”哪怕有些粗糙也能帮我把卡住的叙事齿轮重新推动起来。于是我萌生了一个想法用GPT-2来辅助我完成一部中篇小说的初稿创作。这不是要让AI取代我而是想进行一次严肃的“人机协作”实验看看这位“过气”的AI助手究竟能在创意写作的哪些环节真正帮上忙它的天花板和“怪癖”又在哪里。整个过程更像是一场与一位思维跳跃、知识渊博但偶尔会胡言乱语的伙伴的持续对话。2. 工具选型与环境搭建为什么是GPT-2在开始“写小说”这个宏大目标前第一个现实问题就是用哪个模型以及怎么让它跑起来。2.1 模型选择在能力、成本与控制力之间权衡面对琳琅满目的模型我的选择逻辑很直接能力足够而非最强GPT-3/4的API固然强大但持续调用成本对于个人长期实验来说难以承受。而许多更新的开源大模型如LLaMA系列虽然效果惊艳但对硬件要求尤其是显存很高。我的目标是辅助写作需要的是生成段落、续写句子、提供灵感而不是完成整章。GPT-2特别是其最大的1558M参数版本在文本连贯性、基础叙事能力上已经经过了充分验证完全能满足“灵感激发”和“片段续写”的核心需求。完全本地化数据隐私有保障所有创作内容尤其是未成型的故事构思和片段都是最原始的思维火花。我不希望它们离开我的本地环境。GPT-2作为一个可以完全下载并在本地运行的模型确保了所有生成内容都在我的控制之下没有数据泄露的风险这让我可以毫无心理负担地输入各种半成品甚至混乱的思绪。技术生态成熟踩坑有迹可循GPT-2发布已久相关的加载、推理、微调教程在Hugging Face、GitHub等社区非常丰富。几乎我遇到的所有问题都能通过搜索找到解决方案或讨论。这对于一个更侧重写作而非深度学习研究的项目来说极大地降低了技术门槛和不确定性。基于以上三点我选择了Hugging Facetransformers库 GPT-2 (gpt2-large)的组合。transformers库提供了极简的API让我可以像调用一个普通函数一样使用这个强大的模型。2.2 环境搭建实操五分钟让模型“开口说话”我的开发环境是一台配备RTX 3060显卡的台式机。以下是具体的步骤和核心注意事项# 1. 创建并激活虚拟环境强推避免包冲突 python -m venv gpt2_writing_env source gpt2_writing_env/bin/activate # Linux/macOS # 或 gpt2_writing_env\Scripts\activate # Windows # 2. 安装核心库 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 根据你的CUDA版本调整 pip install transformers pip install sentencepiece # 某些tokenizer需要安装完成后一个最基本的生成脚本如下from transformers import GPT2LMHeadModel, GPT2Tokenizer # 加载模型和分词器首次运行会自动下载模型权重 tokenizer GPT2Tokenizer.from_pretrained(gpt2-large) model GPT2LMHeadModel.from_pretrained(gpt2-large) # 将模型设置为评估模式非训练模式 model.eval() # 准备输入 prompt 在雨夜的城市里一个侦探 input_ids tokenizer.encode(prompt, return_tensorspt) # 生成文本 with torch.no_grad(): # 禁用梯度计算加快推理速度并节省内存 output model.generate( input_ids, max_length100, # 生成文本的最大长度 num_return_sequences1, # 生成几个候选结果 temperature0.9, # 控制随机性越低越确定越高越有创意也越可能胡言乱语 do_sampleTrue, # 启用采样否则就是贪婪搜索 top_k50, # 仅从概率最高的k个词中采样 top_p0.95, # 核采样仅从累积概率达到p的最小词集合中采样 repetition_penalty1.2, # 重复惩罚避免循环 pad_token_idtokenizer.eos_token_id # 设置填充token ) # 解码并打印结果 generated_text tokenizer.decode(output[0], skip_special_tokensTrue) print(generated_text)注意第一次运行from_pretrained时会从Hugging Face模型中心下载大约3GB的模型文件gpt2-large。请确保网络通畅和足够的磁盘空间。国内用户如果下载慢可以配置镜像源或者先通过其他方式下载好模型文件再指定本地路径加载。这段代码跑通意味着你的GPT-2已经准备就绪。但让它写出有用的东西关键不在代码而在后面提到的“提示工程”和参数调校。3. 核心技法如何与GPT-2进行“创作对话”直接给GPT-2一个书名或开头让它自由发挥99%的情况下你会得到一堆看似连贯实则空洞、很快陷入循环或逻辑崩坏的文本。要让AI成为有用的协作者你必须学会如何“引导”它。这其中的核心我称之为“结构化提示”与“渐进式生成”。3.1 构建有效的“提示”不止是开头一句话最初的提示Prompt不是故事的起点而是你为AI划定的“创作沙盒”。一个糟糕的提示等于把AI丢进茫茫词海任其漂流。低效提示“写一个科幻故事。”过于宽泛AI会调用它训练数据中所有科幻相关的陈词滥调。高效提示“[时代背景22世纪地球资源枯竭][人物李明一个负责维护老旧水循环系统的工程师][场景他在地下城第7层的滤水站发现水质数据连续异常][风格冷峻、细节丰富的赛博朋克风格] 日志日期2147.08.11。滤网压力又在峰值徘徊了但这不对劲——”这个提示包含了背景设定框定了故事的世界观。人物锚点给出了一个具体的人物和职业让生成内容有了视角。初始冲突/事件提供了一个具体的“钩子”让AI的续写有了发力点。风格指引虽然GPT-2对风格的精确控制力有限但提供关键词能在一定程度上影响词汇选择。我的做法是为每一个主要场景或章节都先手工编写这样一个包含多要素的“提示卡片”。这本身就是一个极好的故事构思过程。3.2 生成参数详解温度、Top-k与Top-pgenerate函数里的那几个参数是控制AI“创造力”与“稳定性”的旋钮。经过大量测试我找到了对于叙事写作相对好用的配置区间temperature(温度): 这是最重要的参数。它控制采样时的随机性。temperature0.1模型几乎总是选择概率最高的词输出非常确定、保守但也极其枯燥容易陷入重复短语。temperature1.0标准设置按模型原始概率分布采样。temperature0.7~0.9这是我的甜点区。在这个区间AI有一定创意能给出意想不到但尚合理的词汇连接同时又不会太“放飞自我”。写对话和需要灵光一现的描写时我会调到0.85写需要逻辑推进的叙述时调到0.75。top_k与top_p(核采样)这两个参数用于限制采样的词库范围共同防止AI选择那些概率极低的“疯狂”词汇。top_k50意味着只从模型认为概率最高的50个候选词中采样。top_p0.95意味着从概率最高的一批词中采样直到这批词的累积概率达到95%。我通常同时使用top_k50, top_p0.95。这相当于双重保险既保证了多样性又牢牢控制了生成质量的下限。如果只设top_p有时在段落开头可能会抽到过于奇怪的词。repetition_penalty(重复惩罚)GPT-2非常容易陷入重复循环比如不断描述同一个动作或重复相同的形容词。设置repetition_penalty1.2可以有效缓解这个问题它会降低已出现token的概率。但注意值太高如1.5可能导致文本不连贯。一个实操心得不要指望一套参数通吃所有场景。我的工作流是用一组保守参数temperature0.8, top_p0.9生成3-5个候选段落快速浏览。如果都太乏味就把温度调到0.9再试如果开始胡言乱语就降到0.7或者把top_k从50降到30。3.3 “渐进式生成”工作流像打乒乓球一样协作我绝不会让GPT-2一次性生成超过200字。更有效的方式是“你来我往”我写种子段落精心撰写一段100-150字的开头包含清晰的行动、感官细节和情绪基调。AI续写一轮将我的段落作为提示让GPT-2生成接下来的80-150字。我进行编辑与选择从AI生成的结果中挑选出有用的部分可能是一个生动的比喻、一个意外的情节转折甚至只是一个好的动词。通常我会截取其中30%-50%其余删改。融合并撰写下一段将我选中的AI文本与我自己的文字融合、润色形成新的段落再将其作为下一轮生成的提示。循环往复。这个过程类似于“提示 - 生成 - 编辑 - 新提示”的循环。AI的作用不是代笔而是提供了一个高密度的“灵感反应堆”。我作为作者始终掌控着故事的方向、人物的动机和文本的最终质量而AI则负责在我设定的轨道上提供大量我可能想不到的具体表达和情节可能性。4. 实战记录从灵感到片段的协作过程让我用一个具体的例子来展示这个工作流。我想写一个“古董店老板发现一本会吞噬时间的书”的开头。第一轮我提供的种子提示“陈旧的‘时光轩’里弥漫着灰尘和木头腐朽的气味。老板老周在整理一批新收的旧书时指尖触到一本没有书名、封面是黑色皮革的厚册子。就在接触的瞬间他腕上父亲留下的机械表秒针突兀地停顿了三秒。”第一轮GPT-2生成temperature0.85“老周缩回手像是被烫了一下。店里安静得只剩下自己的心跳。他凑近那本书皮革封面冰冷没有任何纹理或刻字。他小心翼翼地翻开第一页纸张泛黄脆弱上面是用一种深褐色的、疑似干涸血液的颜料书写的奇异符号那些符号仿佛在微弱地蠕动。他揉了揉眼睛符号又静止了。窗外经过的汽车灯光扫过书架影子晃动那本书的轮廓似乎比刚才更厚了一些。”我的编辑与融合老周像被静电打到般缩回手店里死寂只剩他自己的心跳在耳膜上撞。他屏息凑近黑色封皮冰冷滑腻像某种生物的皮肤没有任何文字或纹饰。犹豫再三他用指甲轻轻撬开封面。内页纸张是一种不自然的焦黄色脆弱得仿佛一碰即碎。上面布满了用深褐色颜料书写的符号——那颜色让他想起干涸的血迹。更诡异的是当他凝视时那些符号的边缘似乎有极其细微的蠕动。他猛地闭眼再睁开符号又静止了。一定是整理太久眼花了。就在这时窗外一辆晚归的汽车灯光扫过书架影子晃动了一瞬。老周下意识瞥向那本书脊背陡然一凉书脊的厚度似乎比刚才他拿起时增加了微不足道的几毫米。加粗部分为吸收或改编自AI生成的内容第二轮将上面编辑后的段落作为新提示继续生成“他不敢再碰那本书用一块绒布把它推到桌角决定明天找个懂行的朋友来看看。后半夜他睡得极不踏实梦里全是那些蠕动的符号和不断变厚的书脊。清晨回到店里他第一眼就看向那张桌子——黑皮书静静地躺在那里。但他昨晚离开前明明把它放在了柜子上。”…… 如此循环。在这个过程中AI贡献了“符号蠕动”、“影子晃动让书轮廓变化”这些具体的、带有超自然感的细节。而我负责将这些东西合理化、具象化“像某种生物的皮肤”、“脊背一凉”并强化人物的心理反应和故事的逻辑链条“决定明天找人看”、“发现书的位置移动了”。5. GPT-2作为写作伙伴的“长板”与“短板”经过数万字的协作我对这位AI伙伴的能力边界有了清晰的认识。5.1 它真正擅长的令人惊喜的“长板”细节填充与感官描写当你给出一个场景框架如“喧闹的菜市场”GPT-2能快速生成大量生动的细节堆砌——“鱼贩的吆喝声混着腥气”、“蔬菜上未干的水珠折射着晨光”、“地上散落的菜叶被踩成深绿的泥”。这些细节虽然可能流于表面但提供了丰富的素材库我只需从中挑选、重组、深化。提供意外的情节转向当你觉得故事陷入俗套时把当前段落丢给GPT-2它有时会给出完全意想不到的发展方向。比如在我一个侦探故事里主角跟踪嫌疑人到图书馆我正想着如何安排一场安静的智斗AI生成的下一句却是“他发现目标没有走向阅览区而是径直走进了儿童绘本区在一排《猜猜我有多爱你》的书架前停下从其中一本里抽出了一张泛黄的借书卡。” 这个“儿童绘本区”和“借书卡”的转折瞬间打开了新的可能性。克服“空白页恐惧”这是它最大的心理价值。当你面对空白文档不知所措时随便写下一两个关键词丢给GPT-2它反馈回来的文字哪怕大部分是垃圾也能立刻打破僵局让你从“评判者模式”切换到“编辑者模式”。写作最难的永远是开始而AI帮你越过了这个坎。5.2 它根深蒂固的缺陷必须警惕的“短板”缺乏真正的逻辑与长期记忆GPT-2本质上是一个基于统计概率的“下一个词预测机”。它没有故事大纲不记得三句话前的人物动机。它可能会让一个角色在段落A中说自己讨厌猫在段落B中又愉快地撸猫。所有情节的连贯性、人物的一致性、伏笔的回收必须由作者牢牢把控。我养成了随时维护一个“人物-事件”速记表的习惯每次生成新内容后都去核对。情感深度的缺失GPT-2可以模仿情感词汇“他感到一阵撕心裂肺的悲痛”但它无法理解情感背后的复杂成因也无法写出情感细腻的层次变化。它生成的情感描写往往是套路化的、直白的。深刻的内心冲突、复杂的道德困境、微妙的人际关系张力这些都需要作者亲自注入灵魂。容易陷入重复与套话尽管有重复惩罚参数但GPT-2仍偏爱使用它训练数据中高频出现的短语和结构。你需要像警惕“的”、“了”、“然后”这些词一样警惕AI生成文本中反复出现的描写套路比如“眼中闪过一丝光芒”、“嘴角微微上扬”、“深深地吸了一口气”等。对“负面提示”理解有限你不能像要求人类作者那样对GPT-2说“写一段紧张的追逐戏但不要出现汽车和枪战。”它很可能还是会生成汽车和枪战。更好的方法是进行“正面引导”“写一段在狭窄、拥挤的室内菜市场里的追逐戏双方利用摊位和人群躲藏周旋。”6. 进阶技巧微调与风格塑造使用基础GPT-2模型一段时间后你会发现它有一种“通用新闻/网络散文”的混合风格。如果你想让它更贴近某种特定类型比如武侠小说、硬科幻或者古典白话风格就需要进行微调。6.1 准备微调数据微调的本质是让模型在你提供的文本上“继续学习”从而更倾向于生成类似风格的文本。你需要准备一个纯文本文件例如my_novel_style.txt里面包含你希望模型学习的文本风格。数据准备要点文本质量高于数量5万到10万字的精选文本远胜于100万字的垃圾文本。选择你欣赏的、风格统一的作品片段。格式清洗去除无关的版权信息、章节标题、作者说等。保持连贯的叙事段落。领域聚焦如果你写科幻就喂给它科幻小说如果写武侠就喂给它金庸、古龙。风格越纯粹微调效果越好。6.2 执行微调这里给出一个基于Hugging FaceTrainerAPI的简化微调示例。请注意微调需要一定的计算资源GPU和时间。from transformers import GPT2LMHeadModel, GPT2Tokenizer, Trainer, TrainingArguments from datasets import Dataset import torch # 1. 加载模型和分词器从头开始或从已有检查点 model_name gpt2-large tokenizer GPT2Tokenizer.from_pretrained(model_name) model GPT2LMHeadModel.from_pretrained(model_name) # 设置pad_tokenGPT-2原生没有这个 tokenizer.pad_token tokenizer.eos_token # 2. 加载并预处理你的文本数据 def load_texts(file_path): with open(file_path, r, encodingutf-8) as f: text f.read() # 简单按句号分割成多个样本也可以按固定长度分割 samples [s.strip() for s in text.split(。) if len(s.strip()) 20] return samples texts load_texts(my_novel_style.txt) # 3. 对数据进行tokenization def tokenize_function(examples): # 这里我们假设每个样本已经是一个较长的文本片段 return tokenizer(examples[text], truncationTrue, paddingmax_length, max_length256) # 构建数据集 dataset Dataset.from_dict({text: texts}) tokenized_dataset dataset.map(tokenize_function, batchedTrue, remove_columns[text]) # 4. 定义训练参数 training_args TrainingArguments( output_dir./gpt2_finetuned_novel, # 输出目录 overwrite_output_dirTrue, num_train_epochs3, # 训练轮数小数据上3-5轮通常足够 per_device_train_batch_size2, # 根据GPU显存调整gpt2-large很大 save_steps500, save_total_limit2, logging_dir./logs, logging_steps100, fp16True, # 使用混合精度训练以节省显存和加速 ) # 5. 创建Trainer并开始训练 trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset, data_collatorlambda data: {input_ids: torch.stack([torch.tensor(d[input_ids]) for d in data]), attention_mask: torch.stack([torch.tensor(d[attention_mask]) for d in data])} ) trainer.train()微调后的使用训练完成后使用from_pretrained(./gpt2_finetuned_novel)加载你自己的模型。你会发现在生成同类风格文本时需要的“引导”更少生成的文本在词汇选择、句式结构上会更贴近你的微调数据。重要提醒微调是一个需要耐心调试的过程。学习率、训练轮数、数据质量都会极大影响结果。过拟合模型只会复述训练数据是常见问题。建议先从少量数据、1-2个epoch开始实验。微调不是魔法它无法让模型获得训练数据中不存在的知识或能力只是调整了其输出分布的概率权重。7. 避坑指南与常见问题在长达数月的使用中我踩过了几乎所有能踩的坑。以下是一些高频问题的解决方案和核心建议问题1生成内容重复、循环或突然开始胡言乱语。检查参数首先调低temperature(如从0.9降至0.7)或降低top_p(如从0.95降至0.85)。这是最快最有效的方法。检查提示你的输入提示是否太短或太模糊给AI更多上下文。尝试在提示中明确写出“避免重复描述...”虽然效果有限但有时能起到心理暗示作用。启用重复惩罚确保repetition_penalty设置在1.1到1.3之间。手动截断与引导这是终极手段。一旦发现AI开始“车轱辘话”立即停止生成把已经生成的好内容截取下来然后以此为基础你亲自写一两句把故事推向一个新方向再用这个新的、更强的上下文去让AI继续生成。问题2生成速度太慢。使用GPU确保PyTorch安装了CUDA版本并且模型被加载到了GPU上 (model.to(cuda))。调整生成长度一次不要生成太长的文本 (max_length控制在150以内)采用“渐进式生成”。使用fp16半精度在加载模型时使用model.half()可以显著减少显存占用并提升速度但可能会带来轻微的质量损失通常可忽略。考虑使用更小的模型如果只是需要灵感火花gpt2-medium甚至gpt2在速度上会有很大优势且质量对于片段生成来说也足够。问题3如何让AI生成更符合人物性格的对话在提示中“示范”这是最有效的方法。不要只写“张三说”而是先写一段包含人物典型说话风格的示范。低效提示“张三是个急躁的警察。他问道”高效提示“张三是个急躁的警察说话总是又急又冲。‘少废话’他一把拍在桌子上‘人到底在哪给你三秒钟’ 李四缩了缩脖子支吾着说‘我…我真的不知道。’ 张三逼近一步压低声音却更显威胁‘那笔钱呢’ 此时对讲机里传来声音‘张队发现新线索——’” 接下来AI续写的内容就更有可能模仿这种急躁的语气。问题4AI总是写出我不想看到的剧情走向。记住AI不是编剧你才是。不要被动接受AI的所有产出。它的价值在于提供“可能性”而不是“决定性”。当它给出一个你不喜欢的走向时果断舍弃或者将其作为一个“反面教材”思考“如果不想这样那应该怎样”然后自己写下转折再让AI基于你的转折继续。一个最重要的心得把GPT-2看作一个极其高产、但经常跑偏的“初级写手”或“脑暴伙伴”。你的角色是主编和首席作家。你需要明确方向分派任务通过精心设计的提示然后严厉地审阅它的稿件大刀阔斧地删改只留下金子并用你自己的文字将其串联、深化、赋予灵魂。这个过程里AI节省的不是“思考”的时间而是“产出原始素材”的时间。最宝贵的创意、逻辑和情感依然需要从你的大脑中生长出来。这次实验没有让我写出什么惊世之作但它彻底改变了我与“写作障碍”共处的方式。那个在空白页前焦虑的写作者消失了取而代之的是一个更自信的“编辑”和“导演”。我知道无论卡在何处我都有一个永不枯竭的、虽然有时不太靠谱的灵感源泉可供调用。技术的价值或许就在于此它不取代创造而是为创造者赋能让我们能更专注地服务于那些真正唯有人类才能驾驭的领域——情感、意义与灵魂的深度。