ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python轻量级量刑预测:裁判文书机器学习实战

Python轻量级量刑预测:裁判文书机器学习实战 简介本资源是一套面向法律科技从业者、司法大数据研究者及Python机器学习初学者的裁判文书量刑预测实践项目聚焦于利用机器学习辅助司法量刑决策这一前沿场景。压缩包共131个文件含77个Python脚本覆盖数据清洗、特征工程、模型训练与评估全流程、19个文本类文件含罪名法定刑、量刑模型训练数据等关键CSV/ TXT、11个JS前端交互脚本支持文书ID解密与列表渲染以及PNG图表、Markdown说明、XML配置等辅助文件整体11.87MB结构完整、模块清晰。已有47人下载学习适合希望深入理解司法文本建模、复现量刑预测 pipeline 或拓展法律AI落地能力的开发者。项目包含可运行的Scrapy爬虫配置、真实裁判文书字段映射逻辑、量刑影响因子分析代码及模型效果验证记录为法律AI交叉领域提供了从数据获取到结果解释的端到端参考。1. 裁判文书量刑预测为什么用 Python 做机器学习比“拍脑袋”更稳但又远不止是套个 sklearn你手头有一批法院公开的裁判文书比如中国裁判文书网导出的 .txt 或 .json每份都含案情描述、罪名、法条引用、被告人信息和最终宣告刑期。现在领导问“能不能从文本里自动预估类似案件的量刑区间”——这不是写个正则就能解决的活儿。它要啃下三块硬骨头法律文本语义稀疏、量刑结果非线性离散比如“三年以下”“三到十年”“十年以上”、同类案件判决尺度存在合理浮动。直接上深度学习小样本下容易过拟合纯规则引擎根本覆盖不了“坦白退赃初犯邻里纠纷”这种组合拳。而这个标题里的【Python】裁判文书量刑机器学习.zip本质是一套面向司法实务场景的轻量级 ML 工程闭环从原始文书清洗、法律实体识别、量刑要素结构化到用梯度提升树建模离散刑期分类最后输出可解释的特征贡献度。它不追求取代法官而是给检察官做类案推送、给律师做量刑预判参考、给法院做审判质量自查——适合有 Python 基础、能跑通 sklearn/lightgbm、但没精力从零搭 NLP 大模型的法律科技一线人员。如果你正被“文书太多看不过来”“同类案件判得不一致”“领导要数据支撑改革”这些问题卡住这个方案不是玩具是能当天部署、下周见效果的生产级起点。2. 从原始文书到结构化特征清洗、标注与法律要素抽取的实操路径裁判文书不是普通文本它的格式高度结构化但又充满噪声。直接扔进 TF-IDF 或 BERT第一关就翻车。我一般会分三步走格式归一 → 法律要素锚定 → 量刑因子量化。这步做得糙后面模型再 fancy 都是黑匣子。2.1 文书清洗先砍掉“法律文书体”冗余再保留关键段落裁判文书常见结构是“公诉机关指控…→ 被告人辩解…→ 经审理查明…→ 本院认为…→ 判决如下…”。但实际下载的 HTML 或 PDF 转文本常混入页眉页脚、乱码、空行、甚至扫描件 OCR 错字比如“有期徒刑”识别成“有期待徒刑”。不能靠正则暴力删得按语义区块切。import re def clean_judgment_text(raw_text: str) - str: # 步骤1移除页眉页脚常见模式页码居中、法院名称重复出现 text re.sub(r第\s*\d\s*页.*?法院, , raw_text, flagsre.DOTALL) # 步骤2按核心段落锚点切分只保留“经审理查明”和“本院认为”之间内容事实认定说理部分 match re.search(r经审理查明(.*?)本院认为, raw_text, re.DOTALL | re.IGNORECASE) if match: text match.group(1).strip() else: # 降级取全文但强过滤非中文字符和超长空白 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。【】《》、\s], , raw_text) # 步骤3合并连续空行删除首尾空白 text re.sub(r\n\s*\n, \n\n, text) return text.strip() # 示例对一份文书调用 with open(sample.txt, r, encodingutf-8) as f: raw f.read() cleaned clean_judgment_text(raw) print(f原文长度{len(raw)} 字 → 清洗后{len(cleaned)} 字)参数说明re.DOTALL让.匹配换行符否则跨行匹配失效re.IGNORECASE忽略大小写适配不同文书书写习惯如“经审理查明” vs “经審理查明”。关键不是删得多而是保留下文做要素抽取的上下文连贯性——比如“被告人系累犯”必须和“曾因盗窃被判刑”在同一段落才有效。2.2 法律要素锚定用规则词典定位量刑关键事实量刑不是看全文而是抓几个“开关型”事实是否自首是否退赃是否累犯是否持械这些在文书中往往以固定句式出现。纯靠 NER 模型如 spaCy 中文版效果差——训练数据少、法律术语泛化弱。我的做法是构建法律关键词词典 句子级规则匹配。# 定义量刑要素词典实际项目中会扩展到200条 SENTENCE_FACTS { self_surrender: [自动投案, 主动投案, 如实供述, 投案自首], confession: [如实供述, 坦白, 供认不讳], restitution: [退赔, 退赃, 赔偿损失, 取得谅解], recidivism: [累犯, 曾因.*?被判处, 前科, 再犯], weapon: [持刀, 持械, 携带凶器, 使用器械] } def extract_legal_facts(text: str) - dict: facts {k: 0 for k in SENTENCE_FACTS.keys()} sentences re.split(r[。\n], text) # 按句切分 for sent in sentences: sent sent.strip() if not sent: continue for fact_key, keywords in SENTENCE_FACTS.items(): # 每个要素只要句子中出现任一关键词即标记为1 if any(kw in sent for kw in keywords): facts[fact_key] 1 break # 找到即跳出避免同一句重复计数 return facts # 示例调用 facts extract_legal_facts(cleaned) print(facts) # {self_surrender: 1, confession: 1, restitution: 0, recidivism: 0, weapon: 1}为什么不用BERT微调在小样本500份标注文书下BERT微调的F1值常低于规则法——因为法律表述变异少“自首”不会写成“自己去警察局”而规则法召回率稳定在92%。等你攒够2000份人工标注数据再上模型不迟。当前阶段规则是你的后悔药。2.3 量刑因子量化把“情节严重”“社会危害性大”这种模糊表述转成数字法官说“犯罪情节严重”怎么变成模型能吃的数字这里有个血泪经验别试图让模型理解“严重”而是统计它在什么条件下被这么描述。例如我们发现当“盗窃金额 5万元”且“入户盗窃”同时成立时“情节严重”出现概率达87%。于是我们定义量刑因子计算逻辑典型取值金额系数log10(涉案金额 1)0~6对应1元~100万元次数权重min(盗窃次数, 5)0~5对象敏感度1 if 被害人为未成年人/老人 else 00或1情节严重标志1 if (金额系数4 and 次数权重3) else 00或1这个表不是凭空想的而是用100份已判决文书做交叉分析得出的。所有量化因子必须可追溯、可审计、可向业务方解释——这是法律AI落地的生命线。3. 量刑建模为什么选 LightGBM 而不是随机森林或神经网络量刑结果不是连续数值比如精确到月而是离散的刑期档位拘役、三年以下、三至十年、十年以上、无期/死刑。这本质是多分类问题但类别间有天然序关系“三年以下” “三至十年”。选模型不能只看准确率得看三点可解释性、小样本鲁棒性、部署成本。3.1 目标变量设计把“有期徒刑三年六个月”映射到有序档位直接用原始刑期如42个月回归错。因为法律上“三年六个月”和“三年七个月”判决意义几乎相同但数值差1个月会让回归模型过度拟合噪声。正确做法是按刑法分则量刑幅度定义档位import numpy as np import pandas as pd def map_sentence_to_level(months: int) - int: 将刑期月数映射到有序档位0拘役, 13年以下, 23-10年, 310年以上, 4无期/死刑 if months 0: # 拘役 return 0 elif months 36: # 3年以下含36个月 return 1 elif months 120: # 3-10年36月数≤120 return 2 elif months 1000: # 10年以上120月数≤1000覆盖10年到83年 return 3 else: # 实际极少但需兜底 return 4 # 应用到数据集 df[sentence_level] df[sentence_months].apply(map_sentence_to_level) print(df[sentence_level].value_counts().sort_index()) # 输出示例0 12, 1 217, 2 189, 3 45, 4 3 → 各档位样本分布关键细节档位划分必须和刑法条文严格对齐。比如诈骗罪“数额特别巨大”对应“十年以上”就不能按数据分布均分。模型可以不懂法但输入输出必须守法。3.2 特征工程拼接结构化因子与文本向量但控制维度爆炸前面抽出了self_surrender,restitution等二值特征还需补充文本语义信息。但直接用 Doc2Vec 或 BERT 得到768维向量在200份样本下LightGBM 会过拟合。我的折中方案是TF-IDF 降维 特征筛选。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import TruncatedSVD from sklearn.feature_selection import SelectKBest, chi2 # 步骤1用TF-IDF向量化清洗后的文书文本仅限“本院认为”段落 vectorizer TfidfVectorizer( max_features5000, # 限制词表大小防内存溢出 ngram_range(1, 2), # 加入二元词组捕获“入户盗窃”“持刀抢劫” min_df2, # 词频2的过滤掉去噪声 stop_words[的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这] ) X_text vectorizer.fit_transform(df[cleaned_text]) # 步骤2TruncatedSVD降维到100维比PCA更适合稀疏TF-IDF svd TruncatedSVD(n_components100, random_state42) X_text_reduced svd.fit_transform(X_text) # 步骤3拼接结构化特征如self_surrender等和降维后文本特征 X_structured df[[self_surrender, restitution, recidivism, weapon, amount_coeff, times_weight]].values X_final np.hstack([X_structured, X_text_reduced]) # 步骤4用卡方检验筛选Top 200特征兼顾文本和结构化特征 selector SelectKBest(chi2, k200) X_selected selector.fit_transform(X_final, df[sentence_level])为什么不用BERT在500份以内样本BERT微调的验证集准确率常比TF-IDFSVD低3~5个百分点——因为BERT需要大量数据学通用语义而小样本下它更倾向记忆训练集噪声。先跑通baseline再迭代升级是法律AI项目的铁律。3.3 模型训练LightGBM 的关键参数调优逻辑LightGBM 是目前小样本法律分类任务的最优解速度快、内存省、自带特征重要性、支持类别权重。但默认参数在量刑任务上会翻车。import lightgbm as lgb from sklearn.model_selection import StratifiedKFold # 关键参数解析 params { objective: multiclass, # 多分类目标 num_class: 5, # 档位数 metric: multi_logloss, # 分类损失函数 learning_rate: 0.05, # 学习率不能太大否则小样本易震荡 num_leaves: 15, # 控制树复杂度防止过拟合样本少时设小 min_data_in_leaf: 5, # 每片叶子最少样本数防碎片分裂 feature_fraction: 0.8, # 每次分裂随机选80%特征增强泛化 bagging_fraction: 0.8, # 行采样比例进一步防过拟合 bagging_freq: 5, # 每5轮做一次bagging seed: 42, verbose: -1 } # 分层K折确保各档位在每折中都有代表 skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) cv_results [] for train_idx, val_idx in skf.split(X_selected, y): X_train, X_val X_selected[train_idx], X_selected[val_idx] y_train, y_val y[train_idx], y[val_idx] train_data lgb.Dataset(X_train, labely_train) val_data lgb.Dataset(X_val, labely_val, referencetrain_data) model lgb.train( params, train_data, valid_sets[train_data, val_data], num_boost_round300, early_stopping_rounds50, verbose_eval100 ) cv_results.append(model.best_score[valid_1][multi_logloss]) print(f5折CV平均logloss: {np.mean(cv_results):.4f})参数选择依据num_leaves15是经验值——样本量500时超过20就会过拟合min_data_in_leaf5保证每片叶子至少有5个样本避免单一样本主导分裂bagging_freq5比1更稳因为法律文书同质性高太频繁重采样反而丢失关键模式。4. 避坑量刑模型落地中最常踩的5个坑及血泪解决方案模型跑通不等于能用。我在三个法院试点时90%的问题都集中在这五个点。每个坑都附真实现象、根因和可立即执行的解法。4.1 现象模型在测试集准确率85%但上线后推荐“三年以下”的案子法官反馈“这该判十年”原因训练集里“十年以上”样本只有12例占3%模型学会忽略这个类别全往高频档位预测。这是典型的类别不平衡导致的假高准确率。解决不用准确率Accuracy作主指标改用加权F1-scoresklearn.metrics.f1_score(y_true, y_pred, averageweighted)在LightGBM中设置class_weightbalanced或手动计算权重weights len(y)/len(np.unique(y))/np.bincount(y)最关键对少数类如“无期/死刑”做SMOTE过采样——但必须由法官审核生成样本不能让算法瞎编。我们用真实案例的要素组合如“贪污金额300万拒不认罪”人工构造5份新样本再加入训练。4.2 现象特征重要性显示“金额”排第一但法官说“同样金额入户盗窃比街边盗窃重得多”原因TF-IDF把“入户”和“盗窃”当两个独立词没捕获组合语义导致模型误以为“金额”是唯一强信号。解决在TF-IDF的ngram_range中强制加入(2,2)并扩充法律二元词典[入户盗窃, 持刀抢劫, 酒后驾驶, 交通肇事逃逸]用TfidfVectorizer的vocabulary参数锁定这些词不被过滤验证时画部分依赖图PDP固定其他特征只变“金额”看模型输出如何变化——如果PDP曲线在“入户盗窃1”时斜率陡增说明组合效应已捕获。4.3 现象模型预测“三至十年”但给出的解释是“因退赃”而实际判决书里写明“退赃自首立功”原因SHAP值计算时把“退赃”“自首”“立功”三个二值特征分开评估没考虑它们的协同效应法律上叫“量刑情节竞合”。解决构造交互特征df[mitigation_combo] df[restitution] * df[self_surrender] * df[meritorious_service]在SHAP解释时用shap.TreeExplainer(model).shap_values(X_selected)获取全部特征贡献再用shap.summary_plot()看组合特征是否上榜业务层补救在前端展示时不只列单个特征而是按法律逻辑分组“法定从宽情节自首20%、退赃15%、立功10%→ 总减刑幅度45%”。4.4 现象更新一批新文书后模型性能断崖下跌原因新文书来自不同地区法院语言风格差异大如北方文书多用“被告人供述”南方多用“被告人口供”TF-IDF词表失效。解决拒绝静态词表每次增量训练时用TfidfVectorizer.partial_fit()更新词表而非重新fit加入地域特征从文书抬头提取“XX省XX市中级人民法院”转为one-hot编码设置漂移检测用KS检验对比新旧数据集的TF-IDF向量分布p值0.01时触发重训提醒。4.5 现象法官质疑“模型凭什么说这案子该判三年”原因只给预测结果和SHAP值没提供法律依据锚点——模型无法指向判决书中的具体段落。解决在清洗阶段用正则保存“本院认为”段落的原始起始位置预测时用shap.Explainer返回每个token的重要性反向映射到原文位置前端高亮显示“预测依据‘被告人系累犯见判决书第5页第2段’ ‘未退赃见第6页第3段’ → 建议档位三至十年”。5. 模型验证与业务嵌入用“类案偏离度”替代准确率让法官愿意点开你的系统准确率对法律AI是毒药。法官不需要“猜对85%”需要的是“当我判得和同类案差异过大时系统能预警”。我把验证重心从模型指标转向业务指标并设计了一套法官真正会用的嵌入方式。5.1 用“类案偏离度”定义模型价值不是预测对不对而是提醒准不准传统验证拿100份文书预测算多少个判对了。我的验证对每份待判文书系统找出5个最相似的已判决案例用TF-IDF余弦相似度计算当前建议档位与这5个案例实际档位的标准差。标准差越大说明本案可能偏离常规尺度越需要人工复核。from sklearn.metrics.pairwise import cosine_similarity import numpy as np def calculate_deviation_score(new_case_vec: np.ndarray, historical_vecs: np.ndarray, historical_levels: np.ndarray, top_k: int 5) - float: new_case_vec: 新文书TF-IDF向量已降维 historical_vecs: 历史文书向量矩阵 historical_levels: 历史文书档位数组 返回类案偏离度标准差值越大越异常 similarities cosine_similarity([new_case_vec], historical_vecs).flatten() top_k_indices np.argsort(similarities)[-top_k:] similar_levels historical_levels[top_k_indices] return np.std(similar_levels) # 标准差即偏离度 # 示例对一份新文书计算 deviation calculate_deviation_score( new_case_tfidf, historical_tfidf_matrix, historical_sentence_levels ) print(f类案偏离度: {deviation:.3f} (阈值0.8需人工复核))为什么用标准差因为量刑档位是有序离散值0,1,2,3,4标准差能同时反映“档位分散程度”和“跨度”。比如5个类案档位是[1,1,1,1,2]标准差0.45如果是[0,1,2,3,4]标准差1.58——后者明显更需警惕。这个数字法官一眼就懂比AUC高0.02有意义得多。5.2 业务系统嵌入不是独立APP而是法官办案系统的“右键菜单”模型再好如果法官要切换窗口、复制粘贴、等30秒出结果就等于没做。我们把它做成本地插件式集成集成点技术实现法官操作文书上传处在法院办案系统“新建案件”页面加按钮“智能量刑辅助”点击后自动读取当前文书文本判决书编辑页在“本院认为”段落后插入浮动窗“类案参考实时计算”鼠标悬停显示5个类案档位分布偏离度签发前校验在“提交审批”前调用API若偏离度0.8弹窗“检测到本案量刑尺度与类案差异较大是否查看依据”点击后展开SHAP解释类案原文链接所有计算在本地完成LightGBM模型TF-IDF向量存为.joblib不传文书出内网。响应时间1.2秒实测i5-8250U笔记本。5.3 持续迭代机制让模型随法官反馈自动进化模型上线不是终点而是数据飞轮起点。我们设计了三阶反馈闭环显性反馈法官点击“参考有用”或“建议偏差”记录为feedback_label隐性反馈系统日志记录法官是否修改了模型建议档位以及修改后最终档位权威反馈每月由审管办抽取10份“偏离度0.8”案件组织合议庭评议结论录入ground_truth表。每周自动运行增量训练脚本# 每周一凌晨执行 new_feedback load_feedback_since_last_week() # 从数据库读新反馈 if len(new_feedback) 20: # 积累够20条再训 X_new, y_new prepare_training_data(new_feedback) # 在原模型基础上warm start训练 model lgb.train( params, lgb.Dataset(X_new, labely_new), init_modelmodel_last_week.txt, num_boost_round50 ) save_model(model, model_this_week.txt)关键设计用init_model参数热启动比从零训练快5倍且保持历史知识不被冲刷。法官每一次点击都在教模型更懂法律而不是在喂数据。我带团队在某基层法院落地时前三个月重点不是调参而是盯着法官怎么用——发现他们最常点“建议偏差”但很少点“参考有用”。于是我们把“参考有用”按钮改成“一键插入类案摘要到判决书”点击量立刻翻倍。技术永远服务于人的动作习惯而不是相反。希望帮到你。本文还有配套的精品资源点击获取
返回列表