
最近在尝试用AI生成古风诗词时发现很多工具生成的文本要么过于直白缺乏意境要么就是堆砌辞藻逻辑不通。想要得到一首像“玄裳夜渡小楼阴侵霜遍袖浑无觉”这样既有画面感又蕴含情绪的佳作往往需要反复调整和筛选。本文将分享一套从零开始利用现代AI技术打造“赛博诗人”的完整实战方案。我们将从核心原理拆解开始一步步搭建一个能够理解并生成高质量古风诗词的AI模型涵盖数据处理、模型训练、效果优化以及Web应用部署的全流程。无论你是对AI创作感兴趣的开发者还是希望为自己的项目增添文化内涵的技术爱好者都能从本文中找到可复用的代码和清晰的实现路径。1. 背景与核心概念什么是“赛博诗人”“赛博诗人”并非一个严谨的学术概念它更像是一个形象的比喻指的是能够自动创作诗歌尤其是中国古典诗词的人工智能程序或模型。其核心目标是让机器学会古典诗词的格律、用典、意象组合和情感表达生成符合人类审美、甚至能引发共鸣的诗句。为什么这件事有挑战性古典诗词创作不仅仅是词语的排列组合它至少包含以下几个层面的约束与美感形式格律包括平仄、对仗、押韵、字数如五言、七言、词牌曲调等严格的规则。意象与意境通过“月”、“柳”、“舟”、“霜”等意象的有机组合营造出特定的氛围和情感空间如“玄裳夜渡”的孤寂与“侵霜遍袖”的清冷。语言风格使用文言或仿文言词汇语言凝练富有跳跃性和多义性。情感与主题作品需要传达统一的情感或思想不能前后矛盾。传统的基于规则或统计的方法如N-gram很难同时满足以上所有要求。而现代深度学习特别是基于Transformer的序列生成模型为“赛博诗人”的实现提供了强大的技术基础。核心技术路径 目前主流方案是使用预训练语言模型进行微调。我们可以选择一个在大量中文语料上预训练好的模型如GPT、BERT、T5等它已经学会了中文的语言模式和部分知识。然后我们使用一个高质量的古典诗词数据集对这个模型进行“专项训练”微调让它专门学习诗词的创作规律。这样模型就能在预训练获得的通用语言能力基础上掌握诗词这一特殊文体的生成技巧。2. 环境准备与版本说明本文将使用Python作为开发语言并主要依赖PyTorch深度学习框架和Hugging Face Transformers库。这是一个在学术界和工业界都被广泛采用的组合生态丰富社区支持好。基础环境要求操作系统Linux (Ubuntu 20.04/22.04) macOS 或 Windows (建议使用WSL2以获得最佳体验)。Python版本 3.8 本文示例使用 Python 3.9。包管理工具pip或conda。核心依赖库及版本创建一个requirements.txt文件来管理依赖。以下是经过验证可协同工作的版本组合# requirements.txt torch2.0.1cu118 --index-url https://download.pytorch.org/whl/cu118 # 根据你的CUDA版本调整 transformers4.35.0 datasets2.14.6 peft0.6.0 # 用于参数高效微调可选但推荐 accelerate0.24.1 # 用于简化训练流程 sentencepiece0.1.99 # 某些分词器需要 jieba0.42.1 # 用于中文分词和预处理 gradio3.50.2 # 用于快速构建Web演示界面 scikit-learn1.3.0 # 用于评估指标 pandas2.0.3 tqdm4.66.1安装命令# 强烈建议先创建一个虚拟环境 python -m venv venv_cyberpoet # 激活环境 # Linux/macOS: source venv_cyberpoet/bin/activate # Windows: venv_cyberpoet\Scripts\activate # 安装依赖先安装PyTorch请根据官网指令选择适合你CUDA版本的命令 # 例如对于CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 然后安装其他依赖 pip install -r requirements.txt硬件建议GPU强烈推荐使用NVIDIA GPU进行训练和推理可以极大提升速度。显存建议8GB以上如RTX 3070, 3080, 4090等。本文示例代码将支持GPU训练。CPU仅用于小规模测试或推理速度会慢很多。项目结构预览cyber_poet/ ├── data/ │ ├── raw_poems.txt # 原始诗词数据 │ └── processed/ # 处理后的数据 ├── src/ │ ├── data_processor.py # 数据预处理脚本 │ ├── model_trainer.py # 模型训练脚本 │ ├── poem_generator.py # 诗词生成脚本 │ └── app.py # Gradio Web应用 ├── models/ # 保存训练好的模型 ├── outputs/ # 训练日志和输出 ├── requirements.txt └── README.md3. 核心原理与模型选择在开始动手之前我们需要理解将使用的模型是如何工作的并做出合适的选择。3.1 Transformer与生成式模型Transformer模型的核心是自注意力机制它允许模型在处理一个词时同时关注输入序列中的所有其他词从而更好地理解上下文关系。对于诗词生成任务这是一个巨大的优势因为诗句中的词往往与前后文乃至整首诗的意境紧密相关。生成式预训练模型如GPT系列采用“自回归”的方式生成文本。简单来说模型根据已经生成的所有上文来预测下一个最可能的词是什么如此循环直到生成完整序列。这非常符合人类“一句接一句”的创作过程。3.2 模型选型为什么选择ChatGLM或Qwen对于中文古诗词生成我们需要一个在中文语料上预训练充分、且支持生成任务的模型。以下是几个热门选择及其考量GPT-2 (中文版)虽然经典但其原生中文能力通常来自多语言语料对古典中文的“语感”可能不足。Chinese-LLaMA/Alpaca在LLaMA基础上扩充了中文词表并进行了中文预训练是一个很好的基础。ChatGLM系列 (如ChatGLM2-6B, ChatGLM3-6B)由清华智谱AI开发专门为中文优化在中文理解和生成任务上表现出色且对古风文本有一定感知能力。本文将以ChatGLM3-6B为基础模型进行演示因为它开源、效果优秀且社区活跃。Qwen系列 (如Qwen-7B)由阿里云通义千问团队开发同样在中文任务上表现强劲也是一个绝佳的选择。选择ChatGLM3-6B的理由中文原生从预训练开始就深度聚焦中文词汇和语法理解更准确。对话与生成兼顾其训练目标包含对话和文本生成适合我们“给定主题或首句续写诗词”的交互式创作场景。模型大小适中6B参数在消费级GPU如24G显存上可以进行参数高效微调甚至通过量化技术在更小的显存上运行推理。3.3 微调策略Full Fine-tuning vs. PEFT全参数微调更新模型的所有参数。效果通常最好但对计算资源和显存要求极高6B模型全微调可能需要40G显存。参数高效微调只更新模型中一小部分额外的参数如Adapter, LoRA, QLoRA而冻结预训练模型的大部分参数。这是我们在有限资源下的首选方案。LoRA在模型的注意力模块中注入可训练的低秩分解矩阵大幅减少训练参数量可能只有原模型的0.1%却能达到接近全微调的效果。QLoRA在LoRA的基础上将预训练模型量化为4-bit进一步降低显存消耗使得在单张消费级GPU上微调大模型成为可能。本文将采用QLoRA策略对ChatGLM3-6B进行微调这是当前个人开发者进行大模型微调最实用的技术。4. 完整实战打造你的赛博诗人接下来我们将分步实现数据准备、模型微调和应用部署。4.1 数据准备与预处理高质量的数据是“赛博诗人”的灵魂。我们需要一个包含大量古典诗词的数据集格式最好为“标题正文”。步骤1获取数据你可以从开源数据集网站如Hugging Face Datasets下载或自己收集。这里我们假设你已有一个raw_poems.txt文件每行一首诗格式为《标题》正文。示例raw_poems.txt内容《静夜思》床前明月光疑是地上霜。举头望明月低头思故乡。 《春晓》春眠不觉晓处处闻啼鸟。夜来风雨声花落知多少。 《玄裳夜渡小楼阴》玄裳夜渡小楼阴侵霜遍袖浑无觉。玉笛声残梦未成星河欲曙天如削。步骤2数据预处理脚本创建src/data_processor.py用于清洗和格式化数据。# src/data_processor.py import re import json from typing import List import jieba def clean_and_split_poems(file_path: str, output_path: str): 清洗原始诗词文件并格式化为模型训练需要的JSONL格式。 格式{instruction: 写一首关于{主题}的诗, input: , output: {完整诗词}} 或 {text: {完整诗词}} 用于无监督预训练格式。 poems [] with open(file_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue # 简单提取标题和正文这里假设格式为《标题》正文 match re.match(r《(.?)》(.), line) if match: title, content match.groups() # 移除正文中的空格和换行符 content content.replace( , ).replace(\n, ) # 构建训练样本 # 方案A指令微调格式 (更适合ChatGLM这类对话模型) instruction f请创作一首诗标题是《{title}》。 sample { instruction: instruction, input: , # 可以留空或放入关键词 output: content } poems.append(sample) # 方案B也可以同时生成一个仅包含正文的样本用于语言模型训练 # poems.append({text: content}) # 保存为JSONL格式 with open(output_path, w, encodingutf-8) as f_out: for poem in poems: f_out.write(json.dumps(poem, ensure_asciiFalse) \n) print(f共处理 {len(poems)} 首诗词已保存至 {output_path}) def split_dataset(jsonl_path: str, train_ratio0.9): 将数据集拆分为训练集和验证集 with open(jsonl_path, r, encodingutf-8) as f: lines f.readlines() import random random.shuffle(lines) split_idx int(len(lines) * train_ratio) train_lines lines[:split_idx] val_lines lines[split_idx:] train_path jsonl_path.replace(.jsonl, _train.jsonl) val_path jsonl_path.replace(.jsonl, _val.jsonl) with open(train_path, w, encodingutf-8) as f: f.writelines(train_lines) with open(val_path, w, encodingutf-8) as f: f.writelines(val_lines) print(f数据集已拆分训练集 {len(train_lines)} 条验证集 {len(val_lines)} 条) if __name__ __main__: raw_data_path ../data/raw_poems.txt processed_path ../data/processed/poems.jsonl clean_and_split_poems(raw_data_path, processed_path) split_dataset(processed_path)运行此脚本你将在data/processed/下得到poems_train.jsonl和poems_val.jsonl。4.2 使用QLoRA微调ChatGLM3-6B现在进入核心环节模型微调。我们使用transformers和peft库。步骤1编写训练脚本创建src/model_trainer.py。# src/model_trainer.py import os from dataclasses import dataclass, field from typing import Optional import torch from datasets import load_dataset from transformers import ( AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq, BitsAndBytesConfig, set_seed, ) from peft import ( LoraConfig, TaskType, get_peft_model, prepare_model_for_kbit_training, ) import logging # 设置随机种子保证可复现性 set_seed(42) logging.basicConfig(levellogging.INFO) dataclass class ModelArguments: model_name_or_path: str field(defaultTHUDM/chatglm3-6b) use_lora: bool field(defaultTrue) dataclass class DataArguments: train_file: str field(default../data/processed/poems_train.jsonl) val_file: str field(default../data/processed/poems_val.jsonl) max_seq_length: int field(default256) # 诗词通常不长 dataclass class TrainingArgumentsWithLora(TrainingArguments): output_dir: str field(default../models/chatglm3-poet-lora) num_train_epochs: int field(default5) per_device_train_batch_size: int field(default4) per_device_eval_batch_size: int field(default4) gradient_accumulation_steps: int field(default4) learning_rate: float field(default2e-4) warmup_steps: int field(default100) logging_steps: int field(default50) save_steps: int field(default500) eval_steps: int field(default500) save_total_limit: int field(default2) fp16: bool field(defaultTrue) # 使用混合精度训练节省显存 remove_unused_columns: bool field(defaultFalse) report_to: str field(defaultnone) # 可以设置为tensorboard def load_and_preprocess_data(tokenizer, data_args): 加载并预处理数据 def preprocess_function(examples): # 构建模型输入格式 instruction input - output # ChatGLM3的对话格式 [gMASK]sop |user|\n instruction \n|assistant|\n output # 为了简化我们直接使用文本生成格式将instruction和output拼接。 inputs [] outputs [] for i in range(len(examples[instruction])): instr examples[instruction][i] inp examples[input][i] outp examples[output][i] # 构建输入文本 指令 输入 (如果有) if inp: model_input f{instr} {inp} else: model_input instr inputs.append(model_input) outputs.append(outp) # 对输入和输出分别进行编码 model_inputs tokenizer(inputs, max_lengthdata_args.max_seq_length, truncationTrue, paddingFalse) labels tokenizer(outputs, max_lengthdata_args.max_seq_length, truncationTrue, paddingFalse) # 将labels作为模型要预测的部分 model_inputs[labels] labels[input_ids] return model_inputs dataset load_dataset(json, data_files{train: data_args.train_file, validation: data_args.val_file}) tokenized_dataset dataset.map(preprocess_function, batchedTrue, remove_columnsdataset[train].column_names) return tokenized_dataset def main(): model_args ModelArguments() data_args DataArguments() training_args TrainingArgumentsWithLora() # 1. 加载分词器 tokenizer AutoTokenizer.from_pretrained(model_args.model_name_or_path, trust_remote_codeTrue) # ChatGLM3需要设置padding token if tokenizer.pad_token is None: tokenizer.pad_token tokenizer.eos_token # 2. 加载模型并应用4-bit量化 bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.float16, bnb_4bit_use_double_quantTrue, ) model AutoModelForCausalLM.from_pretrained( model_args.model_name_or_path, quantization_configbnb_config, trust_remote_codeTrue, device_mapauto, # 自动将模型层分配到可用的GPU/CPU上 ) model prepare_model_for_kbit_training(model) # 3. 配置LoRA if model_args.use_lora: lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, # LoRA的秩影响参数量和效果通常8或16 lora_alpha32, lora_dropout0.1, target_modules[query_key_value, dense, dense_h_to_4h, dense_4h_to_h], # ChatGLM的注意力层名称 biasnone, ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量应该只占原模型很小一部分 # 4. 加载数据 tokenized_datasets load_and_preprocess_data(tokenizer, data_args) # 5. 初始化Trainer并开始训练 trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_datasets[train], eval_datasettokenized_datasets[validation], data_collatorDataCollatorForSeq2Seq(tokenizertokenizer, paddingTrue), ) trainer.train() # 6. 保存模型 (只保存LoRA权重体积很小) trainer.save_model() tokenizer.save_pretrained(training_args.output_dir) print(f训练完成模型和分词器已保存至 {training_args.output_dir}) if __name__ __main__: main()步骤2运行训练在项目根目录下执行cd src python model_trainer.py训练过程将在你的GPU上进行。根据数据集大小和epoch数可能需要几小时到一天。你可以通过logging_steps观察损失下降情况。4.3 诗词生成与测试训练完成后我们来编写一个生成脚本测试“赛博诗人”的创作能力。创建src/poem_generator.py# src/poem_generator.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig from peft import PeftModel, PeftConfig import argparse def load_lora_model(base_model_path, lora_model_path): 加载基础模型并合并LoRA权重 # 加载4-bit量化的基础模型 bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.float16, ) tokenizer AutoTokenizer.from_pretrained(base_model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( base_model_path, quantization_configbnb_config, trust_remote_codeTrue, device_mapauto, ) # 加载LoRA权重并合并 model PeftModel.from_pretrained(model, lora_model_path) model model.merge_and_unload() # 合并权重到基础模型便于推理 return tokenizer, model def generate_poem(tokenizer, model, instruction, max_new_tokens64, temperature0.9, top_p0.9): 根据指令生成诗词 # 构建符合ChatGLM3格式的输入 prompt f|user|\n{instruction}\n|assistant|\n inputs tokenizer(prompt, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate( **inputs, max_new_tokensmax_new_tokens, do_sampleTrue, # 使用采样而不是贪婪解码使输出更多样 temperaturetemperature, # 控制随机性越高越随机 top_ptop_p, # 核采样保留概率累计达到top_p的词汇 repetition_penalty1.1, # 重复惩罚避免重复用词 pad_token_idtokenizer.pad_token_id, eos_token_idtokenizer.eos_token_id, ) generated_text tokenizer.decode(outputs[0], skip_special_tokensTrue) # 提取assistant的回复部分 if |assistant| in generated_text: generated_text generated_text.split(|assistant|)[-1].strip() return generated_text if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--base_model, typestr, defaultTHUDM/chatglm3-6b, help基础模型路径或名称) parser.add_argument(--lora_model, typestr, default../models/chatglm3-poet-lora, helpLoRA权重路径) parser.add_argument(--instruction, typestr, default请创作一首描绘秋夜孤舟的诗。, help生成指令) args parser.parse_args() print(f加载模型...) tokenizer, model load_lora_model(args.base_model, args.lora_model) print(f模型加载完毕。\n) print(f指令: {args.instruction}) poem generate_poem(tokenizer, model, args.instruction) print(f生成结果:\n{poem}\n) # 多轮测试示例 test_instructions [ 请以‘玄裳夜渡小楼阴’为首句续写一首七言绝句。, 创作一首表达思乡之情的五言律诗。, 写一首关于梅花的词词牌名定为《卜算子》。, ] for instr in test_instructions: print(f指令: {instr}) result generate_poem(tokenizer, model, instr) print(f生成结果:\n{result}\n)运行测试python poem_generator.py --instruction 请创作一首描绘秋夜孤舟的诗。你将看到类似以下的输出具体结果因训练数据而异指令: 请创作一首描绘秋夜孤舟的诗。 生成结果: 秋江夜泊月华流一叶孤舟系柳洲。 露冷蒹葭浑似雪风清笛韵不知愁。4.4 构建交互式Web应用为了让“赛博诗人”更容易使用我们用Gradio快速搭建一个Web界面。创建src/app.py# src/app.py import gradio as gr from poem_generator import load_lora_model, generate_poem import argparse # 加载模型全局加载一次避免每次请求重复加载 def load_models(): base_model THUDM/chatglm3-6b lora_model ../models/chatglm3-poet-lora tokenizer, model load_lora_model(base_model, lora_model) return tokenizer, model tokenizer, model load_models() def generate(instruction, temperature, top_p, max_length): Gradio接口函数 if not instruction.strip(): return 请输入创作指令。 try: poem generate_poem( tokenizer, model, instruction, max_new_tokensmax_length, temperaturetemperature, top_ptop_p ) return poem except Exception as e: return f生成时出现错误: {str(e)} # 构建Gradio界面 demo gr.Interface( fngenerate, inputs[ gr.Textbox( lines3, placeholder请输入创作指令例如请写一首关于春天的七言绝句。, label创作指令 ), gr.Slider(minimum0.1, maximum1.5, value0.9, step0.1, labelTemperature (随机性)), gr.Slider(minimum0.5, maximum1.0, value0.9, step0.05, labelTop-p (核采样)), gr.Slider(minimum16, maximum128, value64, step8, label最大生成长度), ], outputsgr.Textbox(lines10, label赛博诗人作品), title 赛博诗人 - AI古风诗词创作平台, description输入你的创作灵感让AI为你赋诗一首。可调整参数控制诗词的随机性和长度。, examples[ [请以‘玄裳夜渡小楼阴’为首句续写一首七言绝句。, 0.8, 0.9, 64], [创作一首表达羁旅愁思的五言律诗。, 0.7, 0.95, 80], [写一首咏竹的词词牌名用《浣溪沙》。, 1.0, 0.85, 96], ], themesoft ) if __name__ __main__: demo.launch(server_name0.0.0.0, server_port7860, shareFalse) # shareTrue可生成临时公网链接运行应用python app.py然后在浏览器中打开http://localhost:7860你就可以与你的“赛博诗人”互动了。5. 常见问题与排查思路在实践过程中你可能会遇到以下问题问题现象常见原因解决思路CUDA out of memory1. 模型或批次太大显存不足。2. 未使用量化或梯度累积。1. 减小per_device_train_batch_size。2. 增加gradient_accumulation_steps以补偿批次减小。3. 确保使用了BitsAndBytesConfig进行4-bit量化 (load_in_4bitTrue)。4. 使用gradient_checkpointingTrue(在TrainingArguments中) 以时间换空间。训练损失不下降或为NaN1. 学习率过高。2. 数据格式错误模型未学到有效信号。3. 梯度爆炸。1. 降低learning_rate(如从2e-4降至1e-5)。2. 检查数据预处理脚本确保instruction和output字段正确对应。3. 使用梯度裁剪 (max_grad_norm1.0)。4. 尝试更小的LoRAr值。生成的诗词不通顺或胡言乱语1. 训练数据量太少或质量差。2. 训练轮数不足或过多过拟合。3. 生成参数 (temperature,top_p) 设置不当。1. 收集更多、更高质量的诗词数据。2. 监控验证集损失在合适轮数早停。3. 降低temperature(如0.7) 使输出更确定调整top_p(如0.9)。4. 尝试不同的repetition_penalty(如1.2)。模型生成内容与指令无关1. 指令微调格式不正确模型未理解任务。2. 在指令微调格式中未正确设置损失函数只计算输出部分。1. 确保训练数据格式与推理时prompt格式一致。对于ChatGLM遵循其对话模板。2. 在数据预处理时确保labels对应的是输出部分输入部分的标签应设置为-100(在transformers中会被忽略)。本文示例为简化未做此处理对于严格任务需完善。加载模型时报错TrustRemoteCode使用ChatGLM等自定义模型架构时需要信任远程代码。在from_pretrained方法中务必设置trust_remote_codeTrue。6. 最佳实践与工程建议要让你的“赛博诗人”更强大、更实用可以参考以下建议数据质量至上规模至少准备1万首以上的高质量诗词覆盖不同朝代、体裁诗、词、曲、主题。清洗严格清洗数据去除现代文注释、作者介绍、乱码。确保格式统一。增强可以尝试数据增强例如将一首诗拆分成多个训练样本前两句预测后两句或进行简单的回译古诗今译再古译。指令工程优化设计丰富多样的指令模板让模型学会根据不同的指令生成不同风格的诗词。例如“请以‘{首句}’为首句创作一首{体裁}。”“请围绕‘{关键词}’这一意境写一首诗。”“模仿{诗人}的风格写一首关于{主题}的诗。”在训练数据中体现这些多样性。模型融合与后处理融合可以训练多个不同风格豪放、婉约、山水、边塞的LoRA适配器在推理时根据用户选择动态加载。后处理生成后可以添加一个“格律检查”后处理模块使用规则库检查平仄、押韵并对不合格的生成结果进行微调或重排序。部署与性能量化推理使用bitsandbytes或GPTQ进行更低比特的量化如8-bit或4-bit进一步降低推理所需的显存和提升速度。API服务使用FastAPI或vLLM框架将模型封装成高性能API供其他应用调用。缓存对常见的指令或首句生成结果进行缓存提高响应速度。安全与伦理内容过滤在生成管道中加入敏感词过滤避免产生不当内容。版权声明明确告知用户AI生成内容版权归属及可能存在的雷同风险。用途引导将工具定位为“创作辅助”和“灵感启发”而非完全替代人类诗人。通过以上步骤你不仅拥有了一个能创作“玄裳夜渡小楼阴”这般意境诗句的“赛博诗人”更掌握了一套完整的大模型微调与应用部署流程。这套方法可以迁移到其他垂直领域的文本生成任务中如文案创作、代码生成、对话机器人等。技术的魅力在于将想象变为现实现在你的诗意江湖已由代码铸就。