# app.py —— 永久一键切换:微调 / 测试 / 机器人 import os import gradio as gr from transformers import GPT2LMHeadModel, GPT2Tokenizer, TextDataset, DataCollatorForLanguageModeling from transformers import Trainer, TrainingArguments import re import torch MODEL_DIR = "./fine_tuned_model" BASE_MODEL = "uer/gpt2-chinese-cluecorpussmall" # ==================== 微调函数 ==================== def finetune_model(progress=gr.Progress()): if os.path.exists(MODEL_DIR): return "已存在微调好的模型,无需重复微调!直接切换到「测试」或「机器人」模式即可。" progress(0, desc="正在读取家谱文件...") with open("genealogy.txt", encoding="utf-8") as f: text = f.read() # 简单但有效的信息抽取 blocks = re.split(r"\n\d+——|\n故祖考|\n故祖妣|\n孝男|\n孝媳|\n孝孙|\n曾孙|\n外曾孙|\n外玄孙", text) examples = [] for block in blocks[1:]: if len(block) < 10: continue # 提取姓名 name_match = re.search(r"[\u4e00-\u9fa5]{2,4}", block[:15]) name = name_match.group() if name_match else "某人" born = re.search(r"生[於于]\s*([^\n地名]+)", block) died = re.search(r"(死[於于]|卒于|去世于)\s*([^\n地名]+)", block) place = re.search(r"地名\s*([^\n]+)", block) born = born.group(1).strip() if born else "未知" place = place.group(1).strip() if place else "未知" died = died.group(2).strip() if died else None examples.append(f"查询: {name}出生时间和地点?\n回答: {name}出生于{born},地点{place}。") if died: examples.append(f"查询: {name}去世时间?\n回答: {name}去世于{died}。") data_text = "\n\n".join(examples) with open("train.txt", "w", encoding="utf-8") as f: f.write(data_text) progress(0.3, desc="加载 tokenizer 和数据集...") tokenizer = GPT2Tokenizer.from_pretrained(BASE_MODEL) dataset = TextDataset(tokenizer=tokenizer, file_path="train.txt", block_size=128) data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False) progress(0.6, desc="开始微调(预计40–90分钟,请耐心等待勿关闭页面)...") model = GPT2LMHeadModel.from_pretrained(BASE_MODEL) training_args = TrainingArguments( output_dir="./results", overwrite_output_dir=True, num_train_epochs=3, per_device_train_batch_size=2, save_steps=500, logging_steps=20, save_total_limit=1, ) trainer = Trainer( model=model, args=training_args, data_collator=data_collator, train_dataset=dataset, ) trainer.train() progress(1.0, desc="保存模型...") model.save_pretrained(MODEL_DIR) tokenizer.save_pretrained(MODEL_DIR) return "微调成功!模型已保存。现在可以切换到「测试」或「机器人」模式使用啦!" # ==================== 推理函数 ==================== def load_model(): if not os.path.exists(MODEL_DIR): return None, None tokenizer = GPT2Tokenizer.from_pretrained(MODEL_DIR) model = GPT2LMHeadModel.from_pretrained(MODEL_DIR) return model, tokenizer model, tokenizer = load_model() def chat(message, history): if model is None: return "模型尚未微调完成!请先在上面选择「微调模型」运行一次。" prompt = "\n".join([f"查询: {h[0]}\n回答: {h[1]}" for h in history if h[1]] + [f"查询: {message}\n回答:"]) inputs = tokenizer.encode(prompt, return_tensors="pt") outputs = model.generate( inputs, max_length=inputs.shape[1] + 120, do_sample=True, temperature=0.7, top_p=0.9, no_repeat_ngram_size=3, pad_token_id=tokenizer.eos_token_id ) reply = tokenizer.decode(outputs[0], skip_special_tokens=True) return reply.split("回答:")[-1].strip() # ==================== 主界面 ==================== with gr.Blocks(theme=gr.themes.Soft(), title="冯氏家谱智能查询") as demo: gr.Markdown("# 冯氏家谱智能查询机器人\n一次上传,永久使用,三个模式一键切换") mode = gr.Radio( ["微调模型", "测试模型", "正式机器人"], label="请选择当前模式", value="正式机器人" if os.path.exists(MODEL_DIR) else "微调模型" ) with gr.Column(visible=False) as finetune_box: gr.Markdown("### 第一步:点击下方按钮开始微调(只需执行一次,耐心等待40–90分钟)") btn = gr.Button("开始微调模型(免费CPU可完成)", variant="primary", size="lg") output = gr.Textbox(label="微调日志", lines=20) btn.click(finetune_model, None, output) with gr.Column(visible=True) as chat_box: gr.ChatInterface( fn=chat, title=None, description="直接问:冯达尊出生地、冯乔福几个儿子、冯宗德姐妹是谁……", examples=[ "冯达尊出生时间和地点?", "冯乔源的孩子有哪些?", "冯宗福的兄弟姐妹是谁?", "冯永明是哪一支的?", "冯仁杰的父亲是谁?" ] ) def update_interface(choice): if choice == "微调模型": return gr.update(visible=True), gr.update(visible=False) else: return gr.update(visible=False), gr.update(visible=True) mode.change(update_interface, mode, [finetune_box, chat_box]) # 启动时自动显示正确界面 demo.launch()