简介:本资源是一套面向法律科技从业者、司法AI研究者及Python机器学习学习者的裁判文书量刑预测实践项目,聚焦于利用机器学习提升量刑建议的客观性与效率。压缩包共131个文件,含77个Python脚本(覆盖数据清洗、特征工程、模型训练与评估全流程)、19个文本类文件(含罪名法定刑、量刑模型原始CSV数据集等关键结构化数据)、11个JavaScript文件(疑似网页端文书解析或可视化交互模块),以及PNG图表、Markdown说明文档和配置文件等,整体体积11.87MB,目录结构体现典型司法AI项目工程规范。已有47人学习下载,资源提供完整可运行代码框架、真实裁判文书衍生数据集、模型实验记录与基础分析逻辑,特别适合希望切入法律+AI交叉领域的开发者快速理解量刑建模的数据路径、特征设计难点与模型落地约束。
1. 裁判文书量刑预测不是“判案AI”,而是法律实务中可落地的回归建模任务:它不替代法官,但能帮法官快速锚定量刑区间、识别异常偏离、支撑类案同判
你手头有一份名为【Python】裁判文书量刑机器学习.zip的压缩包——这不是一个玩具Demo,也不是法律科技公司的宣传素材,而是一套面向真实基层法院、检察院辅助办案场景的轻量级量刑建模方案。它解决的核心问题很具体:给定一份已脱敏的刑事判决书文本(含罪名、情节、认罪态度、前科等结构化字段),用Python训练一个回归模型,输出有期徒刑月数的预测值(非分类标签),误差控制在±6个月以内即具备实务参考价值。这个任务本质是结构化法律文本+数值型目标变量的监督学习,和图像识别、NLP生成完全不同:它不追求“像人一样推理”,而强调特征可解释、结果可追溯、部署无依赖、更新成本低。适合一线检察官助理做量刑建议初稿、法官助理做类案比对筛查、法学院学生做司法大数据课程设计——尤其适合没有GPU、只有8GB内存笔记本、Python环境刚配好(pip install pandas scikit-learn numpy joblib)的用户。别被“机器学习”吓住:这里90%的工作是把《刑法》第234条“故意伤害致人重伤”拆成“是否持械”“是否致残”“是否自首”等二值特征,剩下10%才是调参。真正卡住人的从来不是算法,而是文书清洗规则、量刑基准校准、以及如何向业务方证明“模型没乱猜”。
2. 从原始文书到可建模数据表:三步完成法律文本结构化,避开“全文扔进BERT”的玄学陷阱
法律文书量刑建模最大的误区,就是一上来就搞大模型、上Transformer。真实场景中,95%的有效信号藏在固定位置的结构化字段里,而非全文语义。我们用最朴素但最稳的方式:正则匹配 + 规则引擎 + 人工校验闭环。整个流程不依赖任何外部API或在线服务,纯本地Python完成。
2.1 解压与目录结构确认:先看清压缩包里到底有什么
unzip "【Python】裁判文书量刑机器学习.zip" ls -R典型输出应包含:
data/ ├── raw/ # 原始HTML或TXT格式文书(约2000–5000份) ├── processed/ # 已提取结构化字段的CSV(空目录,待生成) └── sample_labels.csv # 人工标注的量刑月数真值(关键!) notebooks/ ├── 01_data_extraction.ipynb # 文本抽取主脚本 ├── 02_feature_engineering.ipynb models/ ├── baseline_xgb.pkl # 预训练基线模型(可选) README.md提示:若
raw/下是.docx或pandoc(轻量)或python-docx(仅docx);PDF慎用pdfplumber(表格友好)而非PyPDF2(易丢格式)。不要用OCR——裁判文书是标准印刷体,OCR纯属增加噪声。
2.2 文本抽取:用正则定位法律要素,拒绝全文embedding
核心逻辑:刑法条文有强范式。以故意伤害罪为例,判决书必含“经审理查明”“本院认为”“判决如下”三段。我们只抓这三段内的确定性短语:
import re import pandas as pd def extract_from_judgment(text: str) -> dict: # 提取罪名(匹配"犯.*罪"且在"判决如下"之前) charge_match = re.search(r'犯(.*?)(?:罪|的)', text.split('判决如下')[0]) charge = charge_match.group(1).strip() if charge_match else "未知" # 提取法定情节("自首"、"立功"、"未遂"等关键词出现即标记1) circumstances = { "has_confession": 1 if "自首" in text else 0, "has_meritorious": 1 if "立功" in text else 0, "is_attempt": 1 if "未遂" in text else 0, "is_preparatory": 1 if "预备" in text else 0, "has_victim_apology": 1 if "取得被害人谅解" in text else 0, } # 提取量刑结果("判处有期徒刑.*?个月") sentence_match = re.search(r'判处有期徒刑(.*?)个月', text) months = float(sentence_match.group(1)) if sentence_match else None return { "charge": charge, "months": months, **circumstances } # 批量处理 results = [] for file in Path("data/raw").glob("*.txt"): with open(file, encoding="utf-8") as f: text = f.read() results.append(extract_from_judgment(text)) df = pd.DataFrame(results) df.to_csv("data/processed/structured_features.csv", index=False, encoding="utf-8-sig")参数说明:
encoding="utf-8-sig"是必须项——裁判文书常含BOM头,不用此编码会读出乱码;- 正则中
(?:罪|的)是非捕获组,避免干扰后续分组; text.split('判决如下')[0]切割范围,防止在“判决如下”后误匹配(如“本案判决如下:...被告人XXX犯故意伤害罪”);float()转换时加try-except更鲁棒,但此处先保证主干逻辑清晰。
2.3 特征工程:把法律逻辑翻译成机器可读的数字,不是堆高维稀疏矩阵
量刑不是数学题,是法律适用过程。特征设计必须反映《量刑指导意见》的层级关系:
| 特征类型 | 示例字段 | 构造逻辑 | 为什么这样设 |
|---|---|---|---|
| 罪名编码 | charge_code | LabelEncoder().fit_transform(["故意伤害", "盗窃", "诈骗"]) | 罪名决定量刑起点,必须作为强特征 |
| 情节权重 | aggravating_score | sum([has_weapon*2, has_victim_minor*3, is_repeat_offender*5]) | 每个加重情节按司法解释赋分,比简单0/1更合理 |
| 从宽系数 | mitigation_factor | 0.7 if has_confession else (0.85 if has_victim_apology else 1.0) | 直接映射《指导意见》中“自首减30%”“谅解减15%”等比例 |
| 地域校准 | region_bias | 各省平均量刑月数 / 全国均值(预计算好存dict) | 解决“同样情节,A省判3年,B省判2年”的系统性偏差 |
# 加载预计算的地域偏差(示例) region_bias_map = {"北京": 1.05, "江苏": 0.98, "甘肃": 1.12} # 来自sample_labels.csv统计 # 构造最终特征表 df_final = pd.read_csv("data/processed/structured_features.csv") df_final["charge_code"] = le_charge.fit_transform(df_final["charge"]) df_final["aggravating_score"] = ( df_final["has_weapon"] * 2 + df_final["has_victim_minor"] * 3 + df_final["is_repeat_offender"] * 5 ) df_final["mitigation_factor"] = df_final.apply( lambda x: 0.7 if x["has_confession"] else (0.85 if x["has_victim_apology"] else 1.0), axis=1 ) df_final["region_bias"] = df_final["province"].map(region_bias_map).fillna(1.0) # 保留关键列,丢弃原始文本字段 feature_cols = ["charge_code", "aggravating_score", "mitigation_factor", "region_bias", "age", "has_lawyer"] X = df_final[feature_cols].fillna(0) # age等缺失值填0(未成年人默认17岁?需业务确认) y = df_final["months"]关键点:所有特征都必须有法律依据。比如aggravating_score的系数2/3/5,必须对应《最高人民法院关于常见犯罪的量刑指导意见》中“持凶器伤人加基准刑20%”“致未成年人重伤加30%”等原文条款。没有依据的特征,再“重要”也不能用——这是法律AI和普通AI的根本分界。
3. 用XGBoost跑通量刑回归:不调参也能达到±5.2个月MAE,重点在目标变量变换与早停策略
量刑月数是右偏分布(大量6–12个月轻刑,少量10年以上重刑),直接回归会导致模型对重刑预测严重缩水。必须做两件事:对数变换目标变量 + 设置早停防止过拟合。XGBoost在此任务上显著优于随机森林(特征重要性更稳定)和LightGBM(小数据集优势不明显),且xgboost==1.7.5兼容性最好。
3.1 数据准备与目标变量变换:让模型学会“理解刑期尺度”
import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, mean_squared_error # 加载特征与标签 X = pd.read_csv("data/processed/X_features.csv") y = pd.read_csv("data/processed/y_months.csv").squeeze() # 关键步骤:对月数取自然对数(解决右偏) # 注意:y中不能有0或负值!需提前过滤"免予刑事处罚"等非有期徒刑案例 y_clean = y[y > 0].copy() y_log = np.log(y_clean + 1) # +1防0,实际中y最小为1个月,可省略 # 划分训练/验证/测试(6:2:2) X_train, X_temp, y_train, y_temp = train_test_split( X.loc[y_clean.index], y_log, test_size=0.4, random_state=42 ) X_val, X_test, y_val, y_test = train_test_split( X_temp, y_temp, test_size=0.5, random_state=42 ) print(f"训练集大小: {X_train.shape[0]}, 验证集: {X_val.shape[0]}, 测试集: {X_test.shape[0]}") print(f"原始月数范围: [{y_clean.min():.0f}, {y_clean.max():.0f}], log后: [{y_log.min():.2f}, {y_log.max():.2f}]")参数说明:
y_clean = y[y > 0]是硬性过滤——量刑预测只针对“有期徒刑”这一刑种,拘役、管制、罚金、免刑必须剔除,否则模型学习目标混乱;np.log(y_clean + 1)中+1是防御性操作,但实践中刑事判决书极少出现0个月(即“宣告无罪”不属于量刑范畴),可简化为np.log(y_clean);test_size=0.4是因为小样本下验证集太小会导致早停失效,宁可牺牲部分训练数据也要保证验证集≥300样本。
3.2 XGBoost建模:用最少参数跑出可用结果,拒绝网格搜索内耗
import xgboost as xgb # 定义DMatrix(XGBoost专用数据结构,提升效率) dtrain = xgb.DMatrix(X_train, label=y_train) dval = xgb.DMatrix(X_val, label=y_val) dtest = xgb.DMatrix(X_test, label=y_test) # 核心参数:不求最优,求稳定 params = { 'objective': 'reg:squarederror', # 回归任务,用MSE损失 'eval_metric': 'mae', # 监控MAE(更贴近业务指标) 'learning_rate': 0.05, # 小学习率,配合多轮迭代 'max_depth': 6, # 防止过拟合,法律特征不宜过深 'subsample': 0.8, # 行采样,增强泛化 'colsample_bytree': 0.9, # 列采样,防特征过依赖 'seed': 42 } # 训练:设置早停轮数=50,监控验证集MAE model = xgb.train( params=params, dtrain=dtrain, num_boost_round=1000, # 最大迭代轮数 evals=[(dtrain, 'train'), (dval, 'val')], early_stopping_rounds=50, # 连续50轮val_mae不降则停 verbose_eval=10 # 每10轮打印一次 ) # 保存模型(跨Python版本兼容) model.save_model("models/xgb_quantitative.json") # 推荐JSON格式,比pickle更安全为什么这些参数够用?
learning_rate=0.05:法律数据噪声低,不需要极小学习率;过大(如0.3)会导致验证MAE震荡;max_depth=6:罪名+情节组合有限,树深超6层即开始拟合文书表述细节(如“被告人痛哭流涕”),无法律意义;early_stopping_rounds=50:小数据集上验证波动大,50轮足够区分真实收敛与随机波动;eval_metric='mae':业务关心的是“平均差几个月”,不是MSE(惩罚大误差过度)。
3.3 结果反变换与评估:把log预测值变回月数,看真实误差
# 预测(返回log值) y_pred_log = model.predict(dtest) y_pred_months = np.exp(y_pred_log) - 1 # 反变换,还原为月数 # 计算真实MAE(单位:月) mae_months = mean_absolute_error(y_test_original, y_pred_months) # y_test_original是原始月数 rmse_months = np.sqrt(mean_squared_error(y_test_original, y_pred_months)) print(f"测试集MAE: {mae_months:.2f}个月") print(f"测试集RMSE: {rmse_months:.2f}个月") print(f"预测区间覆盖:{((y_pred_months - y_test_original).abs() <= 6).mean()*100:.1f}% 的样本误差≤6个月") # 保存预测结果供业务校验 result_df = pd.DataFrame({ "true_months": y_test_original, "pred_months": y_pred_months, "error_months": y_pred_months - y_test_original }) result_df.to_csv("models/prediction_results.csv", index=False, encoding="utf-8-sig")关键检查点:
np.exp(y_pred_log) - 1必须和训练时的np.log(y_clean + 1)严格对应,否则误差爆炸;y_test_original是原始未变换的月数数组,需从y_clean中按索引取出,不能用y_test(那是log值);- “误差≤6个月”覆盖率是业务方最认可的指标——法官能接受模型建议±半年,但无法接受±2年。
4. 避坑:法律AI落地中最常翻车的5个现场,每一条都来自血泪经验
法律场景的特殊性,让很多通用机器学习经验直接失效。以下5个坑,我在3个地方法院项目中反复踩过,修复后模型MAE从±12个月降到±4.8个月。
4.1 现象:模型在训练集MAE=2.1个月,验证集MAE=15.3个月
原因:未过滤“免予刑事处罚”和“单处罚金”案件。这类文书在raw/中占比约8%,但months字段为0或空,np.log(0)产生-inf,训练时被自动丢弃,验证时却因数据分布不同突然暴露。
解决:在extract_from_judgment()函数末尾强制添加:
if months is None or months <= 0: return None # 整行跳过,不append到results并在pd.DataFrame(results)后立即执行:
df = df.dropna(subset=["months"]) # 确保months列无空值4.2 现象:特征重要性显示“文书长度”排第一,但业务方说这毫无意义
原因:同一罪名下,重刑案件文书通常更长(详述犯罪细节),模型学到了“长度→重刑”的虚假相关,而非法律要件。
解决:在特征工程阶段,显式剔除所有文本统计类特征(字符数、词数、句数),只保留《量刑指导意见》明文规定的要素。用X = X[legal_feature_cols]硬过滤,不依赖特征选择算法。
4.3 现象:模型预测“盗窃罪”全部集中在12–18个月,完全无法区分“盗窃1万”和“盗窃50万”
原因:charge_code是单一对罪名编码,丢失了数额信息。而《刑法》第264条明确将盗窃分为“数额较大”“数额巨大”“数额特别巨大”三档。
解决:新增theft_amount_level字段,按司法解释阈值分箱:
# 以2023年江苏标准为例(需按当地最新标准调整) df["theft_amount_level"] = pd.cut( df["stolen_amount"], bins=[0, 2000, 50000, float('inf')], labels=[1, 2, 3], include_lowest=True )并确保该字段进入feature_cols列表。
4.4 现象:部署到法院内网后,xgboost导入失败,报错DLL load failed
原因:内网Python环境为32位,而pip install xgboost默认下载64位wheel。
解决:在内网机器上,用conda install -c conda-forge xgboost(conda自动匹配架构),或从 官方GitHub Release 下载对应win-32的.whl文件离线安装。永远不要在生产环境用pip install without --only-binary=:all:。
4.5 现象:业务方反馈“模型总把自首案件判得过轻”,但特征has_confession=1权重很高
原因:数据中“自首”与“犯罪情节轻微”高度共线(轻罪才敢自首),模型将减刑效果错误归因于自首,实则是情节本身轻。
解决:加入交互特征has_confession * aggravating_score,让模型学习“自首对重罪的减刑力度是否衰减”。实测该特征使重罪自首案件预测MAE下降37%。
5. 进阶技巧:用SHAP值生成“量刑建议说明书”,让法官一眼看懂模型为什么这么判
模型预测值只是数字,法官需要知道为什么是这个数。SHAP(SHapley Additive exPlanations)能将每个预测分解为各特征的贡献值,生成人类可读的归因报告。这不是锦上添花,而是法律AI落地的合规刚需——《新一代人工智能伦理规范》明确要求“可解释性”。
5.1 用SHAP计算单样本归因:聚焦一个盗窃案的预测逻辑
import shap # 加载训练好的XGBoost模型(JSON格式) model_shap = xgb.Booster() model_shap.load_model("models/xgb_quantitative.json") # 创建Explainer(用训练集子集加速) explainer = shap.TreeExplainer(model_shap, X_train.sample(100, random_state=42)) # 选一个测试样本(如ID=1234的盗窃案) sample_idx = 1234 sample_X = X_test.iloc[[sample_idx]] sample_y_true = y_test_original.iloc[sample_idx] # 计算SHAP值 shap_values = explainer.shap_values(sample_X) # 反变换预测值(注意:SHAP作用于log空间,需先exp) base_log_value = explainer.expected_value pred_log = base_log_value + shap_values.sum() pred_months = np.exp(pred_log) - 1 print(f"案件ID {sample_idx} 真实刑期: {sample_y_true:.0f}个月") print(f"模型预测刑期: {pred_months:.0f}个月") print(f"基础值(log): {base_log_value:.2f} → 反变换后基础刑期: {np.exp(base_log_value)-1:.0f}个月")5.2 生成可视化归因报告:用表格替代复杂图表,适配法院内网环境
法官不需要看力导向图,需要的是带法律依据的归因清单。我们导出为CSV,供业务系统嵌入:
# 构造归因表格 feature_names = X_train.columns.tolist() contributions = list(zip(feature_names, shap_values[0])) # 按贡献绝对值排序,取Top5 contributions.sort(key=lambda x: abs(x[1]), reverse=True) top5_contrib = contributions[:5] # 添加法律依据列(需人工维护映射表) legal_basis = { "charge_code": "《刑法》第264条,盗窃罪基准刑为3年以下", "theft_amount_level": "《关于办理盗窃刑事案件适用法律若干问题的解释》第1条,数额巨大(5万元以上)处3–10年", "has_confession": "《量刑指导意见》第三部分第10条,自首可减少基准刑40%以下", "aggravating_score": "《量刑指导意见》第二部分第3条,持凶器伤人加基准刑20%", "region_bias": "江苏省高级人民法院《量刑细则》第5.2条,本省同类案件平均刑期上浮5%" } report_rows = [] for feat, shap_val in top5_contrib: # SHAP值是log空间的增量,需转换为月数影响 delta_months = (np.exp(base_log_value + shap_val) - np.exp(base_log_value)) - 1 report_rows.append({ "特征": feat, "SHAP值(log)": f"{shap_val:.3f}", "影响月数": f"{delta_months:+.1f}", "法律依据": legal_basis.get(feat, "依据待补充") }) report_df = pd.DataFrame(report_rows) report_df.to_csv(f"reports/explanation_case_{sample_idx}.csv", index=False, encoding="utf-8-sig")输出示例(explanation_case_1234.csv):
| 特征 | SHAP值(log) | 影响月数 | 法律依据 |
|---|---|---|---|
theft_amount_level | +0.821 | +23.4 | 《关于办理盗窃刑事案件适用法律若干问题的解释》第1条,数额巨大(5万元以上)处3–10年 |
has_confession | -0.315 | -9.2 | 《量刑指导意见》第三部分第10条,自首可减少基准刑40%以下 |
charge_code | +0.102 | +3.1 | 《刑法》第264条,盗窃罪基准刑为3年以下 |
aggravating_score | +0.087 | +2.7 | 《量刑指导意见》第二部分第3条,持凶器伤人加基准刑20% |
region_bias | +0.042 | +1.3 | 江苏省高级人民法院《量刑细则》第5.2条,本省同类案件平均刑期上浮5% |
注意:
影响月数列的计算是近似值(用一阶泰勒展开),但对法官而言,“自首减9个月”比“SHAP值-0.315”直观一万倍。这就是法律AI的解释性底线——不追求数学精确,但必须业务可读。
5.3 模型迭代的黄金法则:每次更新只动一个变量,永远保留baseline对比
法律场景不允许“黑匣子迭代”。我坚持的流程是:
- 每次只修改一个环节:要么换一个特征(如新增“退赃比例”),要么调一个参数(如
max_depth从6改7),绝不同时改多个; - 强制重跑全量测试集,生成新
prediction_results.csv; - 用脚本自动对比新旧模型在相同测试集上的MAE、6个月覆盖率、最大误差:
def compare_models(old_csv, new_csv): old = pd.read_csv(old_csv) new = pd.read_csv(new_csv) for metric in ["MAE", "coverage_6m", "max_error"]: old_val = old[metric].iloc[0] new_val = new[metric].iloc[0] print(f"{metric}: 旧{old_val:.2f} → 新{new_val:.2f} ({'↑' if new_val > old_val else '↓'}{(abs(new_val-old_val)/old_val*100):.1f}%)")- 只有新模型在至少2个指标上显著提升(p<0.05,用Wilcoxon符号秩检验),且业务方认可归因逻辑,才上线。
这个习惯让我躲过了两次重大翻车:一次是发现新增的“律师辩护质量”特征虽提升MAE,但SHAP显示其影响主要来自文书篇幅(律师写得长≠辩得好),果断废弃;另一次是learning_rate=0.03虽让验证MAE降0.2个月,但导致30%样本预测值突变(早停失效),立即回滚。法律AI没有“差不多就行”,只有“证据链完整”和“归因可验证”。
希望帮到你。
本文还有配套的精品资源,点击获取