在尝试将大模型应用于法律领域时,你是否遇到过这样的困境:模型对专业法律术语理解不深,回答流于表面,甚至“一本正经地胡说八道”?或者,想针对特定法律任务微调模型,却苦于数据量小、算力有限,不知从何下手?
本文将以“刑法大模型”为实战目标,手把手带你打通从零到一的全链路。我们将融合Qwen(通义千问)作为基座模型,利用RAG(检索增强生成)注入精准的法律知识库,并通过LoRA(低秩适应)进行轻量化微调,最终实现罪名识别、刑期预测、司法解释生成三大核心功能。整个过程不仅包含清晰的代码和配置,更会以“学长带debug”的视角,剖析每一步可能遇到的坑及其解决方案,确保你能成功复现并应用于自己的领域。
无论你是想入门大模型应用开发的学生,还是希望将AI能力落地到垂直领域的开发者,这篇涵盖环境搭建、数据处理、模型训练与部署的完整教程,都将为你提供一套可直接复用的工程化方案。
1. 项目背景与核心技术栈解析
在深入代码之前,我们有必要厘清这个项目要解决的核心问题,以及为什么选择Qwen、RAG、LoRA这三项技术。
1.1 为什么需要“刑法大模型”?
通用大模型(如ChatGPT、通义千问)在开放域对话上表现卓越,但在法律、医疗等高度专业化、强逻辑、重事实的垂直领域,往往存在以下短板:
- 知识幻觉:可能编造不存在的法条或司法解释。
- 精度不足:对相似罪名(如“盗窃罪”与“侵占罪”)的区分能力弱。
- 缺乏推理:刑期预测需要综合考量情节、数额、悔罪表现等多重因素,通用模型难以进行量化推理。
- 知识更新滞后:模型训练数据有截止日期,无法即时获取最新的法律修订和判例。
因此,我们需要一个专精于刑法领域的“专家模型”。
1.2 技术选型:Qwen + RAG + LoRA
我们采用“基座模型 + 知识增强 + 轻量微调”的三层架构,在效果、成本与可行性间取得平衡。
基座模型:Qwen(通义千问)
- 为什么选它?Qwen系列模型由阿里云开源,具有优秀的中文理解与生成能力,对中文法律文本友好。其开源协议宽松,支持商用,且提供了从1.8B到72B的不同规模版本,便于根据算力选择。本文将以
Qwen2.5-7B-Instruct版本为例,它在指令跟随和推理上表现更佳。 - 作用:提供强大的语言理解和生成基础能力。
- 为什么选它?Qwen系列模型由阿里云开源,具有优秀的中文理解与生成能力,对中文法律文本友好。其开源协议宽松,支持商用,且提供了从1.8B到72B的不同规模版本,便于根据算力选择。本文将以
知识增强:RAG(检索增强生成)
- 是什么?RAG通过外挂一个知识库(如法律条文、判例文书)来弥补大模型内部知识的不足或过时。其工作流程为:用户提问 -> 从知识库中检索相关文档 -> 将文档作为上下文与大模型问题一并输入 -> 模型生成基于可靠知识的答案。
- 为什么需要?确保模型回答的法律依据(如刑法第几条)绝对准确、可追溯,杜绝知识幻觉。对于“根据XX法,盗窃罪如何量刑?”这类问题,RAG能直接检索出法条原文作为生成依据。
- 技术组件:通常包含文档加载器、文本分割器、向量数据库(如ChromaDB, FAISS)、嵌入模型(如BGE)和检索链。
能力微调:LoRA(低秩适应)
- 是什么?LoRA是一种高效的微调技术。它不在整个庞大的模型参数上做调整,而是通过注入额外的、秩很低的矩阵来模拟全参数微调的效果。这些新增的参数远小于原模型,训练速度快,显存占用低。
- 为什么需要?为了让模型学会“刑法领域”特有的任务格式和推理模式。例如,教会模型按照“案情描述 -> 罪名分析 -> 法条引用 -> 刑期预测”的结构化方式输出。仅靠RAG提供知识,模型可能不会以我们想要的格式回答。LoRA就是用来调整模型“行为模式”的。
- 优势:轻量、高效、可插拔。训练好的LoRA权重文件很小(通常几十到几百MB),可以轻松加载到基座模型上,实现能力增强。
三者关系总结:Qwen是大脑,RAG是随时可查阅的精准法律图书馆,LoRA是让大脑更擅长处理法律考题的特训课程。三者结合,才能打造出既知识准确又擅长法律推理的专用模型。
2. 环境准备与项目搭建
工欲善其事,必先利其器。我们先搭建一个清晰、可复现的开发环境。
2.1 硬件与软件要求
- 操作系统:Linux (Ubuntu 20.04/22.04) 或 Windows (WSL2)。本文以 Ubuntu 22.04 为例。
- GPU:强烈推荐使用 NVIDIA GPU 进行微调和推理。训练LoRA,RTX 3090 (24GB) 或 RTX 4090 (24GB) 是较好的起点。仅推理可使用显存更小的GPU或CPU(速度较慢)。
- Python:3.9 或 3.10。
- CUDA:>= 11.8(与PyTorch版本匹配)。
2.2 创建虚拟环境与安装依赖
首先,创建一个独立的Python环境以避免包冲突。
# 创建并激活虚拟环境 conda create -n law_llm python=3.10 -y conda activate law_llm # 或使用 venv # python -m venv law_llm # source law_llm/bin/activate # Linux # law_llm\Scripts\activate # Windows接下来,安装核心依赖。我们将使用transformers,peft(用于LoRA),accelerate,langchain和chromadb。
# 安装PyTorch (请根据你的CUDA版本访问 https://pytorch.org/ 获取最新命令) # 例如,对于 CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装 transformers, datasets, peft, accelerate (用于模型加载和训练) pip install transformers datasets peft accelerate # 安装 langchain 及相关组件 (用于构建RAG) pip install langchain langchain-community langchain-chroma # 安装向量数据库和嵌入模型 pip install chromadb sentence-transformers # 安装其他工具库 pip install jupyter pandas tqdm2.3 项目目录结构
一个清晰的项目结构有助于管理代码、数据和模型。
law_llm_project/ ├── data/ # 存放原始和预处理后的数据 │ ├── raw/ # 原始法律文本(刑法条文、司法解释等) │ ├── processed/ # 处理后的数据(用于RAG和微调) │ └── dataset/ # 微调任务数据集 ├── knowledge_base/ # RAG知识库存储(ChromaDB持久化路径) ├── scripts/ # 执行脚本 │ ├── build_kb.py # 构建知识库脚本 │ ├── train_lora.py # LoRA微调脚本 │ └── inference.py # 推理演示脚本 ├── models/ # 存放下载的基座模型和训练好的LoRA权重 │ ├── Qwen2.5-7B-Instruct/ │ └── lora_law/ ├── config/ # 配置文件 │ └── lora_config.json ├── requirements.txt # 项目依赖 └── README.md你可以使用以下命令快速创建这个结构:
mkdir -p law_llm_project/{data/{raw,processed,dataset},knowledge_base,scripts,models,config} cd law_llm_project3. 构建法律知识库(RAG核心)
RAG的效果高度依赖于知识库的质量。我们的知识库需要包含《中华人民共和国刑法》条文、重要的司法解释(如关于盗窃、诈骗等罪的数额认定标准)以及典型的指导案例摘要。
3.1 数据收集与预处理
步骤1:准备原始文本将刑法全文(可从权威法律网站获取)保存为data/raw/criminal_law.txt。每一条文最好单独一行或用一个分隔符隔开。 示例criminal_law.txt内容格式:
第二百六十四条 【盗窃罪】盗窃公私财物,数额较大的,或者多次盗窃、入户盗窃、携带凶器盗窃、扒窃的,处三年以下有期徒刑、拘役或者管制,并处或者单处罚金;数额巨大或者有其他严重情节的,处三年以上十年以下有期徒刑,并处罚金;数额特别巨大或者有其他特别严重情节的,处十年以上有期徒刑或者无期徒刑,并处罚金或者没收财产。 第二百六十六条 【诈骗罪】诈骗公私财物,数额较大的,处三年以下有期徒刑、拘役或者管制,并处或者单处罚金;数额巨大或者有其他严重情节的,处三年以上十年以下有期徒刑,并处罚金;数额特别巨大或者有其他特别严重情节的,处十年以上有期徒刑或者无期徒刑,并处罚金或者没收财产。 ...步骤2:编写知识库构建脚本创建scripts/build_kb.py:
# scripts/build_kb.py import os from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain.docstore.document import Document def build_knowledge_base(): # 1. 加载法律文本 raw_path = "./data/raw/criminal_law.txt" if not os.path.exists(raw_path): print(f"错误:未找到原始法律文件 {raw_path}") return loader = TextLoader(raw_path, encoding='utf-8') documents = loader.load() # 2. 分割文本 # 法律条文本身有结构,适合按章节或条文分割,这里使用递归字符分割作为备选 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每个片段约500字符 chunk_overlap=50, # 片段间重叠50字符,保持上下文 separators=["\n\n", "\n", "。", ";", ",", " ", ""] # 中文分隔符优先 ) splits = text_splitter.split_documents(documents) print(f"原始文档分割为 {len(splits)} 个片段。") # 3. 选择嵌入模型 (使用轻量且效果好的中文模型) # 也可以使用 OpenAI API,但本地部署推荐开源模型 model_name = "BAAI/bge-small-zh-v1.5" # 中文嵌入模型,效果不错且速度快 embeddings = HuggingFaceEmbeddings( model_name=model_name, model_kwargs={'device': 'cuda'}, # 使用GPU加速 encode_kwargs={'normalize_embeddings': True} # 归一化,提升检索效果 ) # 4. 创建并持久化向量数据库 persist_directory = "./knowledge_base/chroma_law" vectordb = Chroma.from_documents( documents=splits, embedding=embeddings, persist_directory=persist_directory ) vectordb.persist() # 持久化到磁盘 print(f"知识库已构建并保存至 {persist_directory}") # 5. 简单测试检索 query = "盗窃罪如何量刑?" docs = vectordb.similarity_search(query, k=2) print(f"\n测试检索问题:'{query}'") for i, doc in enumerate(docs): print(f"\n--- 相关片段 {i+1} ---") print(doc.page_content[:200] + "...") # 打印前200字符 if __name__ == "__main__": build_knowledge_base()步骤3:运行脚本构建知识库
cd law_llm_project python scripts/build_kb.py如果一切顺利,你会看到知识库构建完成,并输出与“盗窃罪”相关的法条片段。
3.2 Debug 要点:知识库构建常见问题
问题1:
No module named 'langchain_community'- 原因:LangChain 版本更新,部分模块路径发生变化。
- 解决:确保安装的是较新版本
pip install langchain-community。如果还报错,可以尝试from langchain.embeddings import HuggingFaceEmbeddings(旧路径),但建议使用新路径以保持兼容性。
问题2:嵌入模型下载慢或失败
- 原因:从Hugging Face下载模型可能受网络影响。
- 解决:
- 使用国内镜像源:
export HF_ENDPOINT=https://hf-mirror.com(Linux) 或设置环境变量。 - 或者,先手动下载模型到本地,然后修改
model_name为本地路径。
# 假设模型下载到了 ./models/bge-small-zh model_name = "./models/bge-small-zh" - 使用国内镜像源:
问题3:检索结果不相关
- 原因:文本分割策略不当或嵌入模型不适合中文法律文本。
- 解决:
- 调整分割器:尝试
CharacterTextSplitter或按特定分隔符(如“第xxx条”)分割。 - 尝试其他嵌入模型:如
moka-ai/m3e-base在中文语义搜索上表现也很好。 - 检查原始文本质量:确保文本清晰,没有乱码或多余格式。
- 调整分割器:尝试
4. 准备LoRA微调数据集
为了让模型学会执行“罪名识别”、“刑期预测”等特定任务,我们需要准备指令微调数据集。数据格式通常为instruction-input-output。
4.1 数据集格式设计
我们创建一个合成数据集。在实际项目中,应使用更高质量的标注数据。
创建data/dataset/train.jsonl(JSON Lines格式),每条数据如下:
{ "instruction": "请根据以下案情描述,分析可能构成的罪名,并预测可能的刑期范围。请以'罪名:','法条依据:','刑期预测:'的格式回答。", "input": "张三于2023年5月夜间,潜入某小区多户居民家中,窃取手机、笔记本电脑等财物,总计价值人民币5万元。归案后,张三如实供述了自己的罪行,并积极退赔了部分赃款。", "output": "罪名:盗窃罪。\n法条依据:《中华人民共和国刑法》第二百六十四条。盗窃公私财物,数额巨大的,处三年以上十年以下有期徒刑,并处罚金。本案盗窃数额为5万元,根据相关司法解释,属于‘数额巨大’范畴。\n刑期预测:考虑到张三有入户盗窃情节(法定加重情节),但具有如实供述、部分退赔等酌定从轻情节,综合预测刑期可能在四年至六年有期徒刑之间,并处罚金。" }{ "instruction": "请生成关于‘抢劫罪’中‘携带凶器抢夺’认定的司法解释摘要。", "input": "", "output": "根据《最高人民法院关于审理抢劫刑事案件适用法律若干问题的解释》第六条规定:携带凶器抢夺,是指行为人随身携带枪支、爆炸物、管制刀具等国家禁止个人携带的器械进行抢夺,或者为了实施犯罪而携带其他器械进行抢夺的行为。行为人将随身携带凶器有意加以显示、能为被害人察觉到的,直接适用刑法第二百六十三条的规定定罪处罚(即认定为抢劫罪)。" }你需要准备数百到数千条这样的高质量数据,涵盖多种罪名、情节和任务类型。
4.2 数据集加载脚本
创建scripts/prepare_data.py来加载和格式化数据,供训练使用。
# scripts/prepare_data.py import json from datasets import Dataset def load_and_format_data(file_path): instructions = [] inputs = [] outputs = [] with open(file_path, 'r', encoding='utf-8') as f: for line in f: data = json.loads(line.strip()) instructions.append(data['instruction']) inputs.append(data['input']) outputs.append(data['output']) # 构建模型输入格式:将instruction和input组合成prompt prompts = [] for instr, inp in zip(instructions, inputs): if inp: prompt = f"{instr}\n\n{inp}" else: prompt = instr prompts.append(prompt) # 创建 Hugging Face Dataset 对象 dataset_dict = { 'prompt': prompts, 'completion': outputs } dataset = Dataset.from_dict(dataset_dict) return dataset if __name__ == "__main__": train_dataset = load_and_format_data('./data/dataset/train.jsonl') print(f"训练集样本数:{len(train_dataset)}") print(train_dataset[0])5. LoRA微调Qwen模型
这是项目的核心环节。我们将使用peft和transformers库来微调Qwen2.5-7B-Instruct模型。
5.1 下载基座模型
建议使用huggingface-cli或snapshot_download下载模型。确保你有足够的磁盘空间(约15GB)。
# 方法1:使用 huggingface-cli (需先登录 huggingface-cli login) pip install huggingface-hub huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir ./models/Qwen2.5-7B-Instruct # 方法2:在Python代码中下载(如下一步脚本所示)5.2 编写LoRA微调脚本
创建scripts/train_lora.py。这是一个完整的训练脚本,包含了模型加载、LoRA配置、训练参数设置和训练循环。
# scripts/train_lora.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer from peft import LoraConfig, get_peft_model, TaskType from datasets import load_from_disk import os from huggingface_hub import snapshot_download # 1. 模型与分词器加载 model_name_or_path = "Qwen/Qwen2.5-7B-Instruct" local_model_path = "./models/Qwen2.5-7B-Instruct" # 如果本地没有模型,则下载 if not os.path.exists(local_model_path): print(f"本地未找到模型,正在从Hugging Face下载 {model_name_or_path} ...") snapshot_download(repo_id=model_name_or_path, local_dir=local_model_path) print("下载完成。") print("加载分词器...") tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True) # Qwen模型需要设置padding_side,通常为'left'用于生成 tokenizer.padding_side = 'left' if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token # 设置pad token print("加载模型...") model = AutoModelForCausalLM.from_pretrained( local_model_path, torch_dtype=torch.bfloat16, # 使用BF16节省显存并保持精度 device_map="auto", # 自动分配模型层到可用设备(GPU/CPU) trust_remote_code=True ) # 2. 配置LoRA lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, # 因果语言模型任务 r=8, # LoRA秩,影响参数量,通常8,16,32 lora_alpha=32, # 缩放因子 lora_dropout=0.1, # Dropout概率,防止过拟合 target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 在QKV和输出投影层添加LoRA bias="none", # 是否训练偏置 ) # 将原模型转换为PeftModel,仅训练LoRA参数 model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量,应该只占原模型很小一部分 # 3. 数据准备 from prepare_data import load_and_format_data # 导入之前写的数据处理函数 train_dataset = load_and_format_data('./data/dataset/train.jsonl') # 假设我们划分一小部分作为验证集 split_dataset = train_dataset.train_test_split(test_size=0.1, seed=42) train_data = split_dataset['train'] eval_data = split_dataset['test'] # 数据预处理函数:将文本转换为模型输入所需的token IDs def tokenize_function(examples): # 将prompt和completion拼接,中间加一个分隔符(如\n\nAssistant:) # 对于Qwen Instruct模型,通常格式为:<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n{completion}<|im_end|> # 这里我们简化处理,直接拼接。更严谨的做法是使用模型特定的chat template。 texts = [p + "\n\n" + c for p, c in zip(examples['prompt'], examples['completion'])] # 对拼接后的文本进行tokenize tokenized = tokenizer(texts, truncation=True, padding="max_length", max_length=512) # 将标签设置为与输入相同(用于计算损失),但需要忽略掉prompt部分的损失 labels = tokenized["input_ids"].copy() # 假设我们只想计算completion部分的损失,需要将prompt部分的标签设为-100 # 这里简化处理,实际应根据模型模板和prompt长度精确计算 # 这是一个需要根据实际情况调整的关键点! prompt_lengths = [len(tokenizer.encode(p, truncation=True, max_length=512)) for p in examples['prompt']] for i, label in enumerate(labels): prompt_len = prompt_lengths[i] label[:prompt_len] = [-100] * prompt_len # 将prompt部分的标签设为-100(忽略损失) tokenized["labels"] = labels return tokenized tokenized_train = train_data.map(tokenize_function, batched=True) tokenized_eval = eval_data.map(tokenize_function, batched=True) # 4. 设置训练参数 training_args = TrainingArguments( output_dir="./models/lora_law", # 输出目录 num_train_epochs=3, # 训练轮数 per_device_train_batch_size=2, # 每个设备的训练批次大小,根据GPU显存调整 per_device_eval_batch_size=2, gradient_accumulation_steps=4, # 梯度累积步数,模拟更大batch size warmup_steps=100, # 学习率预热步数 logging_steps=10, # 每多少步打印一次日志 eval_steps=50, # 每多少步评估一次 save_steps=200, # 每多少步保存一次检查点 evaluation_strategy="steps", save_strategy="steps", load_best_model_at_end=True, # 训练结束后加载最佳模型 metric_for_best_model="eval_loss", # 根据评估损失选择最佳模型 greater_is_better=False, learning_rate=2e-4, # 学习率,LoRA通常可以设大一点 fp16=True, # 使用混合精度训练,节省显存 # bf16=True, # 如果GPU支持BF16,优先用BF16 gradient_checkpointing=True, # 梯度检查点,用时间换显存 optim="paged_adamw_8bit", # 使用8-bit优化器,进一步节省显存 report_to="tensorboard", # 可选:记录到tensorboard save_total_limit=3, # 最多保存3个检查点 ) # 5. 创建Trainer并开始训练 trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_train, eval_dataset=tokenized_eval, tokenizer=tokenizer, # data_collator=..., # 可以使用默认的 ) print("开始训练...") trainer.train() # 6. 保存LoRA权重 print("训练完成,保存LoRA适配器...") model.save_pretrained("./models/lora_law_final") tokenizer.save_pretrained("./models/lora_law_final") print("LoRA权重已保存至 ./models/lora_law_final")5.3 Debug 要点:LoRA训练常见坑
问题1:CUDA Out Of Memory (OOM)
- 原因:模型、批次大小、序列长度超出GPU显存。
- 解决:
- 减小
per_device_train_batch_size。 - 启用梯度检查点(
gradient_checkpointing=True)。 - 使用更低的精度(
fp16=True或bf16=True)。 - 使用
optim="paged_adamw_8bit"或adamw_bnb_8bit优化器。 - 减小
max_length(如从512降到256)。 - 使用
device_map="auto"让accelerate自动将部分层卸载到CPU或磁盘(速度会慢)。
- 减小
问题2:损失不下降或输出乱码
- 原因:
- 学习率不合适:LoRA学习率通常可以设得比全参数微调大(如1e-4到5e-4)。
- 数据格式错误:模型没有正确理解指令格式。这是最常见的问题!
- 标签掩码错误:在
tokenize_function中,没有正确地将prompt部分的损失忽略(标签设为-100)。
- 解决:
- 调整学习率。
- 严格按照基座模型要求的对话模板格式化数据。对于Qwen Instruct,应使用其
apply_chat_template方法。修改数据预处理部分:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True) # 使用模型的chat template messages = [ {"role": "user", "content": prompt}, {"role": "assistant", "content": completion} ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False) tokenized = tokenizer(text, ...)- 仔细检查并修正标签掩码逻辑。
- 原因:
问题3:
RuntimeError: expected scalar type Float but found Half- 原因:混合精度训练时数据类型不匹配。
- 解决:确保模型加载时指定了
torch_dtype(如torch.float16或torch.bfloat16),并与TrainingArguments中的fp16/bf16设置一致。
6. 整合推理:RAG + LoRA Qwen
训练完成后,我们将加载基础模型、LoRA权重以及RAG知识库,构建一个完整的推理管道。
6.1 推理脚本编写
创建scripts/inference.py:
# scripts/inference.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline from peft import PeftModel, PeftConfig from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings class LawRAGLoRAAssistant: def __init__(self, base_model_path, lora_model_path, knowledge_base_path): # 1. 加载模型和分词器 (基座 + LoRA) print("加载基座模型和分词器...") self.tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True) self.tokenizer.padding_side = 'left' if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token base_model = AutoModelForCausalLM.from_pretrained( base_model_path, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True ) print("加载LoRA适配器...") self.model = PeftModel.from_pretrained(base_model, lora_model_path) self.model.eval() # 设置为评估模式 print("模型加载完成。") # 2. 加载RAG知识库 print("加载法律知识库...") embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5", model_kwargs={'device': 'cpu'}, # 嵌入模型可以放在CPU encode_kwargs={'normalize_embeddings': True} ) self.vectordb = Chroma( persist_directory=knowledge_base_path, embedding_function=embeddings ) self.retriever = self.vectordb.as_retriever(search_kwargs={"k": 3}) # 检索最相关的3个片段 print("知识库加载完成。") def retrieve_context(self, query): """从知识库中检索相关上下文""" docs = self.retriever.get_relevant_documents(query) context = "\n\n".join([doc.page_content for doc in docs]) return context def generate_answer(self, user_query, use_rag=True): """生成回答""" prompt = user_query if use_rag: # 检索增强 context = self.retrieve_context(user_query) if context: # 构建包含上下文的提示词 enhanced_prompt = f"""请基于以下提供的法律条文信息,回答用户的问题。如果信息不足以完全回答问题,你可以结合自身的法律知识进行补充,但请明确指出哪些信息来自提供的条文。 相关法律条文: {context} 用户问题: {user_query} 请给出专业、准确的分析:""" prompt = enhanced_prompt else: print("警告:未检索到相关法律条文。") # 使用模型的chat template构建输入 messages = [{"role": "user", "content": prompt}] text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) # 将输入转换为tensor inputs = self.tokenizer(text, return_tensors="pt", padding=True).to(self.model.device) # 生成 with torch.no_grad(): outputs = self.model.generate( **inputs, max_new_tokens=512, # 生成的最大token数 temperature=0.7, # 创造性,越低越确定 top_p=0.9, # 核采样参数 do_sample=True, repetition_penalty=1.1, # 重复惩罚 pad_token_id=self.tokenizer.pad_token_id, eos_token_id=self.tokenizer.eos_token_id, ) # 解码输出,并去掉输入部分 generated_ids = outputs[0][inputs['input_ids'].shape[1]:] response = self.tokenizer.decode(generated_ids, skip_special_tokens=True) return response def interactive_chat(self): """交互式对话""" print("\n=== 法律AI助手已启动 (输入 'quit' 退出) ===") print("模式:RAG增强 + LoRA微调模型") while True: try: user_input = input("\n您:") if user_input.lower() in ['quit', 'exit', 'q']: print("再见!") break if not user_input.strip(): continue answer = self.generate_answer(user_input, use_rag=True) print(f"\n助手:{answer}") except KeyboardInterrupt: print("\n程序被中断。") break except Exception as e: print(f"\n生成时发生错误:{e}") if __name__ == "__main__": # 路径配置 BASE_MODEL_PATH = "./models/Qwen2.5-7B-Instruct" LORA_MODEL_PATH = "./models/lora_law_final" KB_PATH = "./knowledge_base/chroma_law" # 初始化助手 assistant = LawRAGLoRAAssistant(BASE_MODEL_PATH, LORA_MODEL_PATH, KB_PATH) # 运行交互式对话 assistant.interactive_chat()6.2 运行与测试
运行推理脚本,开始与你的“刑法大模型”对话吧!
cd law_llm_project python scripts/inference.py你可以尝试提问:
- “张三盗窃了价值3万元的财物,会判多久?”
- “抢劫罪和抢夺罪有什么区别?”
- “请解释一下什么是‘故意伤害罪致人重伤’的认定标准。”
观察模型的回答是否:
- 引用了正确的法条(来自RAG知识库)。
- 按照我们微调的结构化格式(罪名、法条依据、刑期预测)进行回答。
- 推理过程符合法律逻辑。
7. 常见问题与优化方向
7.1 项目运行问题排查清单
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 导入LangChain模块失败 | 版本不兼容或未安装langchain-community | pip install langchain-community langchain-chroma |
| ChromaDB 无法持久化或读取 | 路径权限问题或版本冲突 | 检查路径是否存在且可写;尝试pip install chromadb==0.4.22(一个稳定版本) |
| 模型下载极慢或失败 | 网络连接Hugging Face不畅 | 使用镜像源:export HF_ENDPOINT=https://hf-mirror.com |
| 训练时GPU显存不足 | 批次大小、序列长度或模型太大 | 减小per_device_train_batch_size、max_length;启用gradient_checkpointing和fp16;使用optim=“paged_adamw_8bit” |
| 模型生成无关内容或格式错误 | 1. 数据格式未按模型模板处理 2. LoRA训练不充分或过拟合 | 1. 使用tokenizer.apply_chat_template严格格式化数据2. 检查训练损失曲线,增加数据量或调整超参(如学习率、dropout) |
| RAG检索结果不准确 | 1. 文本分割策略差 2. 嵌入模型不匹配 3. 检索top_k值不合适 | 1. 按法律条文自然结构(如“第X条”)分割 2. 尝试 moka-ai/m3e-base等中文模型3. 调整 search_kwargs={“k”: 3}中的k值 |
7.2 效果优化与进阶方向
知识库优化:
- 多源数据:加入司法解释、权威判例、学术观点,构建更立体的知识体系。
- 混合检索:结合关键词检索(BM25)和向量检索,提升召回率。
- 重排序:使用更精细的模型对检索结果进行重排序,提升top1精度。
微调策略优化:
- 更多任务数据:收集更多高质量的(案情,判决)配对数据。
- 数据格式:尝试不同的指令模板,找到最适合Qwen模型的格式。
- LoRA参数:调整
r(秩)、alpha、target_modules,甚至尝试DoRA等更先进的微调方法。 - 全参数微调:如果数据量和算力充足,可以对整个模型进行全参数微调,效果通常更好。
推理优化:
- 模型量化:使用GPTQ、AWQ等技术将模型量化到4bit或8bit,大幅降低推理显存和加速。
- 推理框架:使用
vLLM、TGI(Text Generation Inference) 等高性能推理框架部署,支持动态批处理,提高吞吐量。 - 前端集成:使用
Gradio或Streamlit快速构建Web界面,方便演示和测试。
工程化部署:
- API服务化:使用
FastAPI将模型封装成RESTful API。 - 配置管理:将模型路径、超参数等写入配置文件(如YAML)。
- 日志与监控:添加详细的日志记录和性能监控。
- API服务化:使用
通过以上步骤,你不仅完成了一个融合RAG与LoRA的“刑法大模型”原型,更掌握了一套在垂直领域构建专业大模型应用的通用方法论。这套方法可以迁移到医疗、金融、教育等任何需要专业知识与复杂推理的领域。