☰
法律智能问答系统实战:从LSTM序列建模到RAG检索增强的落地路径
2026/10/5 9:36:31 网站建设 项目流程

简介:面向希望学习人工智能与自然语言处理技术的学习者,这份基于神经网络的法律智能问答系统,提供了从数据构建到模型训练再到交互界面的完整工程案例,适用于毕业设计、课程作业或项目实训。资源共包含30个文件,主要类型为11个CSV数据文件、6个Python源码、5个TXT语料与停用词表、5个编译缓存及2个训练模型,压缩包整体约37.48MB,结构清晰,便于按模块研读与复用。已有126人浏览学习。项目以法律领域问答为场景,覆盖劳动合同、工伤保险、员工权益等热点议题,代码中实现了相似度匹配与神经网络分类等关键逻辑,并附带GUI程序。通过学习可掌握数据清洗、特征处理、模型保存与调用等完整流程,也可在此基础上替换语料拓展至其他垂直领域,具备较强的实用性与二次开发价值。

1. 法律智能问答系统:为什么神经网络方案值得认真做

用户输入「试用期最后一天被辞退,公司要赔钱吗」,传统法条检索系统能查出《劳动合同法》相关条目,但用户要的是「要赔,而且按工作年限算经济补偿」这样一句能直接落地的回答。基于神经网络的法律智能问答系统,就是把「查法条」这个动作升级为「读问题、找依据、组织答案」,以深度神经网络为核心在问答对上训练,让模型学会把用户口语映射到法律语义空间。这个方向适合三类人:做法律科技产品的团队、给律所或企业法务搭建知识库的工程师、想把 NLP 落到垂直领域的算法同学。它不一定能在通用榜单拿高分,却能在真实咨询场景里省掉大量重复劳动。

2. 先定架构再写代码:三类神经网络问答方案怎么选

做法律问答系统最容易犯的错,是拿到语料直接上 Transformer。先花半天想清楚架构,能省下后面两周返工。法律问答和开放域闲聊的最大差别在于:答案要有据,不能顺着概率把法条编出来。架构选型本质上是在「可控性」和「自然度」之间做取舍,不同取舍对应完全不同的一套代码。

2.1 检索式、生成式、混合式:各自吃掉哪类法律问题

法律咨询里大约六成是高频 FAQ,试用期辞退、离婚财产分割、合同违约这类,答案稳定且变化少;剩下四成是开放长尾,用户描述一段具体案情问怎么办,这种问题必须让神经网络理解案情细节再组织回答。纯检索式系统对用户来说像个黑匣子,你并不知道它为什么没召回,而且用户换个说法就彻底失效。

架构典型实现优势劣势适合问题
检索式BM25、向量检索匹配 FAQ / 法条库可控、可溯源换说法即召回失败高频标准化咨询
生成式Seq2Seq、LSTM、Transformer 直接生成答案表达自然、能组合案情法条幻觉严重、难溯源开放长尾案情
混合式先检索法条再抽取或生成既给依据又给推理工程链路长、调参点多生产环境的主方案

我一般建议第一版直接做混合式,但不是一上来就搭完整链路,而是先用生成式模型跑通,把错误样本攒一批,再决定加不加检索。生成式模型在这个场景里最大的优势是能吸收整段案情描述,比如「公司以我业绩不达标辞退我,但我入职时签的合同写明了提成比例」这类复合信息,检索式会拆得七零八落。如果后续把案由、法条、审判结果建成知识图谱,图神经网络可以做答案推理的辅助,但工程落地里用得少,别被论文带偏。

2.2 为什么 CNN、BP 神经网络和前馈神经网络做不了法律问答

一维卷积神经网络(TextCNN)在文本分类上是经典选择,但它用固定尺寸卷积核在局部窗口里提取 n-gram 特征,不擅长跨长距离组合信息。法律案情描述动辄几百上千字,第 40 行的事实要和第 300 行的争议焦点关联,卷积核窗口根本够不着。BP 神经网络和前馈神经网络本质上是静态特征映射器,不建模序列顺序,把「小王借了钱没还」和「钱没还小王借了」当成同一输入,这在法律语义里是完全相反的两个命题。

RNN 循环神经网络能建模序列,但标准 RNN 在长文本上梯度消失严重,实践中基线直接用 LSTM 神经网络,靠遗忘门和输入门把信息在长序列里留住。还有一层隐藏原因:数据规模。法律问答对通常只有几万条,甚至几千条,大模型动辄上亿参数,一上去就过拟合。这个领域有个没写进论文的玄学:参数规模越大、训练数据越少,幻觉越严重。所以第一个版本别追求模型大,先把小模型基线的错误格局看清楚再说。

2.3 最小可跑的 LSTM + Seq2Seq 基线

我不太建议直接照搬网上开源的法律问答模型,常见做法是自己基于 Seq2Seq 框架搭一个最小基线,数据量小、跑得快、方便定位问题。下面这个结构是经典 encoder-decoder:encoder 用 LSTM 读用户问题,decoder 用 LSTM 逐词生成答案。

import torch import torch.nn as nn class EncoderRNN(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=2, batch_first=True, dropout=0.3) def forward(self, x, x_len): emb = self.embedding(x) packed = torch.nn.utils.rnn.pack_padded_sequence( emb, x_len.cpu(), batch_first=True, enforce_sorted=False) _, (h, c) = self.lstm(packed) return h, c class DecoderRNN(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=2, batch_first=True, dropout=0.3) self.fc = nn.Linear(hidden_dim, vocab_size) def forward(self, y, h, c): emb = self.embedding(y) out, (h, c) = self.lstm(emb, (h, c)) logits = self.fc(out) return logits, h, c

这里用两层 LSTM 是因为单层对法律问题里的转折结构(「虽然……但是……」)建模不足,两层能捕捉更高阶语义组合。dropout=0.3 防止几千条小语料上直接过拟合。embedding_dim 128、hidden_dim 256 是模型容量和显存之间较保守的选择,显存吃紧时先降 hidden_dim,不要先降 batch_size,batch_size 太小会让梯度更新噪声变大,训练抖动。训练时用 teacher forcing,decoder 每一步输入用真实答案词而不是上一步预测,收敛快,但引出后文要说的暴露偏差问题。

3. 法律语料是真正的护城河:清洗、切分与标注的落地细节

跑通模型很简单,数据才是无底洞。法律语料有三个来源,各有各的坑,处理不当会让后面所有训练都白做。

3.1 原始语料获取与清洗:判例、法条与问答对的合并策略

裁判文书是量大但噪音最重的来源,带固定格式的页眉页脚、案件字号、审判人员名单,如果不清理,模型会把「本院认为」之前的表单文本也学进去。法律法规库结构最规整,但一条法条经常包含多款多项,切分粒度要小心,一个条款下的两款完全可能是两个独立回答单元。公开法律问答平台最接近用户真实口语,是训练问答对的核心数据,但混着律师广告和免责声明,需要单独过滤。

清洗规则上,统一繁体到简体、全角标点转半角、删除 URL 和联系方式、过滤掉长度小于 20 字的无关片段。这些操作不高级,但如果跳过,词表会被各种符号撑大,embedding 学出一堆噪音。

import re def clean_legal_text(text: str) -> str: text = text.replace("(", "(").replace(")", ")") text = text.replace(",", ",").replace("。", ".") text = re.sub(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+", "", text) # 邮箱 text = re.sub(r"1[3-9]\d{9}", "", text) # 手机号脱敏 text = re.sub(r"\s+", " ", text) return text.strip()

正则里第一个是邮箱,第二个是大陆手机号,公开数据里经常夹带当事人隐私,上线前必须统一脱敏,这不是研发技巧问题,是数据合规底线。另外注意清洗顺序:先把标点统一,再做正则脱敏,最后压缩空白,顺序反了会出现「手机号中间被换行截断、正则匹配不上」的尴尬情况。

3.2 滑窗切分与答案重定位:让长判决书变成训练样本

裁判文书平均几千字,直接整篇丢给 LSTM 不现实。常见做法是滑窗切分,按 256 或 512 token 切成片段,与问题配对,然后判断答案是否完整落在片段内。答案被切断是最常见的翻车点,用带重叠的滑窗能缓解。

def slide_windows(tokens, window=256, stride=128, answer_span=None): windows = [] for start in range(0, len(tokens), stride): end = min(start + window, len(tokens)) windows.append([start, end, tokens[start:end]]) if end == len(tokens): break if answer_span is None: return windows merged = [] for start, end, w in windows: if answer_span[0] < start and start > 0: merged[-1][1] = end merged[-1][2] += w continue merged.append([start, end, w]) return merged

window 控制单样本最大长度,stride 控制窗口重叠。stride 太小样本冗余度过高,训练慢;太大容易切丢答案。我一般设 stride 为 window 的一半,这个比例在大多数判决书上都能覆盖到答案。merged 的逻辑是把跨窗口答案补到前一个窗口,保证至少有一个样本包含完整答案片段。更稳妥的做法是切完后再校验一次答案 token 序列是否完整出现在窗口里,没有就直接丢弃该样本,不要硬凑。

3.3 数据增强与标注一致性:两名标注员的冲突怎么处理

法律问答对的人工标注成本高,一名熟练标注员一天大约能标 150 到 200 条。两个标注员对同一问题给出的答案不一致时别用投票解决,法律场景里少数派意见经常更严谨,因为多数派容易顺着常识写而忽略例外条款。我建议两标注员加一个法律背景复核,三个人出结论。

标注流程里要算两人之间的一致性(Cohen's kappa),低于 0.6 说明标注规范没写清楚,先回头改规范再继续标,不然数据越多模型越乱。数据增强方面,法律场景不能乱做同义词替换,「甲方」「乙方」「用人单位」「劳动者」这类术语可以互相替换,但「赔偿」和「补偿」绝不能互替,法律意义上差很远。安全做法是只对问题做句式改写,陈述句改疑问句、加口语语气词,答案保持原样,避免污染。

4. 用 PyTorch 训练法律问答模型:数据加载、训练循环与评估

模型结构定好、数据清洗完,进入训练阶段。这个阶段最容易反复折腾的是数据加载和训练策略,而不是网络结构本身。

4.1 数据加载器与词表构建:jieba 分词后的长度控制

中文不像英文天然按空格分词。法律文本里专业术语密集,用 jieba 时要配自定义词典,把「劳动仲裁」「经济补偿金」「竞业限制」这类词加进去,不然它们会被切成零碎字。词表构建要设置最小词频,否则低频词占满词表,embedding 层大部分参数在重复学同一个 UNK。

import jieba from collections import Counter from torch.utils.data import Dataset for term in ["经济补偿金", "劳动仲裁", "竞业限制", "抚养权"]: jieba.add_word(term) def build_vocab(all_texts, min_freq=2): counter = Counter() for text in all_texts: counter.update(jieba.cut(text)) vocab = {w: i + 4 for i, (w, c) in enumerate(counter.items()) if c >= min_freq} vocab["<pad>"] = 0 vocab["<unk>"] = 1 vocab["<bos>"] = 2 vocab["<eos>"] = 3 return vocab

c >= min_freq会把只出现一次的词过滤掉,词表从四五万压到一两万,训练速度和显存占用都会好看很多。i + 4是因为 0 到 3 被固定占位符占用,顺序无所谓,保证词表遍历和索引一致就行。min_freq 设 2 是经验值,设 5 以上会让不少法律术语直接进 UNK,模型看不懂「竞业限制」时答出来的东西基本没法用。

4.2 训练循环:损失函数、学习率调度与早停策略

损失函数用 CrossEntropyLoss,必须把<pad>位置 ignore 掉,否则模型会把大量梯度花在预测无意义的占位符上。起步学习率 1e-3,优化器用 AdamW,weight_decay 设 1e-4。学习率调度用 ReduceLROnPlateau,验证集损失连续两个 epoch 不降就把学习率减半。早停条件是验证集 loss 五个 epoch 不降就停。

import torch from torch.utils.data import DataLoader model = Seq2Seq(vocab_size=len(vocab), embed_dim=128, hidden_dim=256) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=2) criterion = torch.nn.CrossEntropyLoss(ignore_index=vocab["<pad>"]) for epoch in range(epochs): model.train() total_loss = 0 for q_ids, a_ids in loader: decoder_input = a_ids[:, :-1] decoder_target = a_ids[:, 1:] logits = model(q_ids, decoder_input) loss = criterion(logits.reshape(-1, len(vocab)), decoder_target.reshape(-1)) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total_loss += loss.item() val_loss = evaluate(model, val_loader) scheduler.step(val_loss)

这里的梯度裁剪很重要,LSTM 在长序列上梯度范数很容易冲到几十。每步打印梯度范数是个好习惯,如果经常超过 5,说明存在异常长样本或学习率偏大。注意 ReduceLROnPlateau 是在每个 epoch 结束后用验证集 loss 调学习率,不是每个 batch 调一次,这是新手经常搞错的地方。

4.3 评估指标:BLEU 和 F1 之外,还要看引法条准确率

BLEU 是 n-gram 重合度指标,适合机器翻译这类对表达顺序有要求的任务,但对法律问答有欺骗性。模型回答「根据《劳动法》第 57 条,不需要赔偿」,标准答案是「根据《劳动合同法》第 39 条,无需支付经济补偿」,语义接近但 BLEU 只有 0.3,分不清好坏。所以评估要同时看三个维度。

import re from collections import Counter law_pattern = re.compile(r"《([^》]+)》第([0-9]+)条") def check_citation(pred: str, law_article_map: dict) -> bool: matched = law_pattern.findall(pred) if not matched: return False for law_name, article_no in matched: if law_name not in law_article_map: return False if article_no not in law_article_map[law_name]: return False return True def token_f1(pred_tokens, gold_tokens): pred_counter, gold_counter = Counter(pred_tokens), Counter(gold_tokens) overlap = sum((pred_counter & gold_counter).values()) if overlap == 0: return 0.0 precision, recall = overlap / len(pred_tokens), overlap / len(gold_tokens) return 2 * precision * recall / (precision + recall)

law_article_map 是从法条库构造的{"劳动合同法": {"39": "条文内容", "47": "条文内容"}}结构。check_citation 上线后要挂在生成管线后面,一旦引用法条不存在就触发兜底或重生成。法条引用准确率是法律问答独有的指标,它比任何模型层面的约束都直接,因为幻觉的集中体现就是编法条。token-level F1 衡量关键信息有没有答全,人工抽评则看有据、完整、可读三个维度,每个版本抽 50 条让律师打分,这部分工作量不能省。

5. 法律问答系统避坑指南:五条踩坑记录

这些坑有些是自己踩的,有些是看同事翻车学到的,每一条都对应一次返工。写在这里,希望能帮你绕过去。

5.1 训练阶段的三条踩坑记录

坑一:模型编造法条编号,而且编得特别像。现象是验证集里六成生成答案都带法条引用,其中三成引用的条款在法条库里根本不存在,但句子读起来非常顺畅。原因是生成式模型优化的是「下一个词概率最大」,法条编号对模型来说只是高频共现符号,它没有核对知识源的能力。解决方法是生成管线后面挂法条引用校验,也就是第 4 章那个 check_citation,引用不存在的直接触发重生成,重生成两次都不行就降级为检索式回答。

坑二:长答案截断后信息残缺。现象是模型在 max_len 设 64 时生成的答案经常答到一半就停,验证集 token-F1 上不去。原因是标注答案普遍 80 到 120 字,而我把解码长度拍脑袋设为 64。解决方法是先统计标注答案长度的分位数,按 P95 设置 max_len,该调的是数据统计分析而不是拍脑袋。P95 是多少就设多少,模型参数量不变,只是生成步数变长,显存会相应涨一点。

坑三:类别不平衡让模型变成「离婚问答机」。现象是模型对婚姻家庭类问题答得不错,劳动合同类一塌糊涂。原因是公开语料里婚姻家庭咨询量大约是劳动合同的三倍。解决方法是按案由做分层采样,每个案由在每个 epoch 里抽到的样本数保持接近,少数类靠重复采样补齐。注意不是简单复制少数类样本,而是在一个 epoch 内让每个案由进入 batch 的比例均衡,避免连续几个 batch 全是同一案由导致优化方向被带偏。

5.2 上线产品阶段的两条踩坑记录

坑四:用户口语和法言法语之间映射没做好。「我被公司开了有赔偿吗」和「公司单方解除劳动合同未提前通知是否需支付补偿金」在语义上是同一件事,但检索或生成的结果完全不同。原因是训练数据里书面语比例太高,模型没学会口语到术语的等价替换。解决方法是做一份「口语-术语」同义词典,开了等于解除、赔钱等于经济补偿金或赔偿金,在生成前先把用户问题改写一轮。等价替换只发生在问题侧,不发生在答案侧,防止生成带病文本。

坑五:没有置信度兜底,系统硬着头皮回答不知道的问题。现象是模型对超出语料范围的地方性政策问题会生成一段语气笃定的错误回答,免费使用阶段无所谓,一旦涉及付费咨询就会被投诉。原因是训练时没有定义「不知道」。解决方法是统计验证集样本的预测概率分布,设置一个阈值,低于阈值时返回固定话术:「根据现有资料无法准确回答,建议咨询执业律师。」这个兜底是所有优化手段里投入产出比最高的。

6. 进阶:检索增强让模型学会引法条,以及上线前的验证方法

单靠 Seq2Seq 模型解决不了幻觉问题,检索增强生成(RAG)才是法律问答落地里最实用的解法。具体做法是把法条库按「法条编号加正文」切成条目,用 BM25 对用户问题做召回,取 top3 法条拼进生成上下文,让模型在参考信息的基础上组织答案。

from rank_bm25 import BM25Okapi law_articles = [] # 每条形如 "劳动合同法#39: 劳动者有下列情形之一的,用人单位可以解除劳动合同……" bm25 = BM25Okapi([list(jieba.cut(a)) for a in law_articles]) def retrieve_law(question: str, top_k: int = 3): scores = bm25.get_scores(list(jieba.cut(question))) top_idx = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k] return [law_articles[i] for i in top_idx]

BM25 的 k1 和 b 参数保持默认值 1.5 和 0.75 在法条库里一般不用调。真正影响召回质量的是切条粒度,一条法条按「编号加正文」切完整单元效果最好,单独把「第X条」切开会让 BM25 打分失真。召回结果拼进模型输入的格式一般是[上下文] 用户问题,decoder 生成时会参考上下文里的法条表述,而不是凭空记忆。

上线前验证方法:准备 200 条没进过训练集的问答对,100 条高频、100 条长尾,分别跑纯生成基线和 RAG 增强版本,对比法条引用准确率和 token-F1。我自己的项目经验是 RAG 对高频问题提升不大,因为基线本来就能答对,但长尾问题的法条引用准确率明显上升。这个趋势你可以拿自己语料验证。最后说一个我的教训:不要迷信「更大模型」能解决幻觉,越大的模型越能流畅编造法条,它的流畅本身就是 bug。现在我的习惯是每个版本发布前先跑一遍法条引用校验通过率,再谈其他指标。法律问答系统的第一原则不是像人,而是有据,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询