资讯动态

QRFR:用随机森林叶子节点y值分布生成预测区间

发布时间:2026/10/18 4:43:08 来源:尧图企业网站定制
简介本资源是一份面向Python机器学习开发者与数据科学从业者的QRFR随机森林分位数回归实战指南聚焦多输入单输出回归任务中的不确定性建模问题特别适用于金融分析、气候预测、医疗健康等需输出预测区间的关键场景。资源以1个33KB的Word文档.docx形式提供内容涵盖理论基础、模型架构设计、分位数回归与随机森林融合原理、超参数调优难点、异方差数据处理策略并附完整Python代码示例及详细注释便于初学者复现与进阶者优化。文档目录结构清晰包含项目背景、目标意义、挑战分析、创新点、应用领域及模型描述与代码示例六大模块逻辑层层递进兼顾方法论深度与工程落地性。目前已有1117人学习下载读者可直接获取可运行的区间预测实现方案、典型场景适配思路及应对过拟合与数据预处理的实操建议。1. QRFR 不是“加个 quantile 参数就完事”它用随机森林的每棵树叶子节点里存下的真实 y 值分布现场拼出预测区间——这才是你真正需要的不确定性量化工具你是不是也试过用sklearn.ensemble.RandomForestRegressor预测房价结果 RMSE 看着不错但一到实际部署客户问“这个预测值到底靠不靠谱上下浮动多少算合理”你就卡壳了。传统 RF 只输出一个点point estimate它不告诉你这个点背后藏着多大的波动风险。而 QRFRQuantile Regression Forest不是简单套个分位数函数它是把每棵决策树训练完后不丢掉任何叶子节点里落进来的所有真实 y 值样本等到预测新样本时顺着路径走到对应叶子再从该叶子中所有 y 值里直接取 5% 和 95% 分位数当场生成下界和上界——整个过程无假设、不拟合、不依赖正态性天然适配异方差、长尾、含异常值的真实工业数据。本文实现的正是这个原汁原味的 QRFR纯 Python sklearn 兼容接口支持多输入单输出MISO可无缝接入你现有的数据预处理 pipeline它不依赖statsmodels的分位数回归拟合器也不调用scikit-learn-extra的实验性模块那个QuantileRegressor是单模型不是森林而是从零复现 Meinsma 等人在 2006 年提出的原始 QRFR 思想。适合正在做金融风控阈值设定、设备剩余寿命 RUL 区间估计、气象要素置信带生成的工程师也适合想搞懂“为什么我的随机森林不能直接出区间”的 ML 实践者。这不是理论玩具是能塞进你生产脚本里、跑得动、改得清、验得准的落地方案。2. QRFR 的核心机制为什么必须重写 predict 方法——从叶子节点 y 值集合到分位数边界的三步推导2.1 标准 RandomForestRegressor 的“信息丢失”本质叶子只存均值不存分布我们先看一个容易被忽略的事实sklearn.ensemble.RandomForestRegressor在训练每棵树时每个叶子节点最终只保留一个数值——即该叶子中所有训练样本 y 值的算术平均值。它的predict()方法就是把新样本喂给每棵树找到各自落到的叶子取出该叶子的均值再对所有树的结果取平均。这个设计极大压缩了内存、加速了预测但代价是所有关于 y 值在该叶子内如何分布的信息全被丢弃了。而 QRFR 的全部价值恰恰藏在这些被丢掉的分布里。提示你可以用rf.estimators_[0].tree_.value查看某棵树某个叶子的存储值你会发现它是一个 shape 为(1, 1)的数组里面只有一个 float——就是均值。这不是 bug是 sklearn 为效率做的取舍。2.2 QRFR 的三步重建逻辑叶子存全量 y、预测走路径、分位数现场算QRFR 要做的就是把“丢掉的分布”找回来。它不修改树的结构只改变两个环节训练阶段改造每棵树训练完后不只存叶子均值还要额外保存每个叶子节点中所有落入该叶子的训练样本的原始 y 值列表注意不是统计量是完整 list。这会增加内存占用但换来的是分布自由度。预测阶段重写当新样本 x 输入时不再取叶子均值而是沿着每棵树的决策路径找到它最终落入的叶子节点从该叶子预先缓存的 y 值列表中提取所有 y对这组 y 值直接计算指定分位数如 0.05 和 0.95所有树的下界/上界分别取平均得到最终预测区间。集成策略选择是取每棵树分位数后再平均tree-wise还是先合并所有树的 y 值再统一分位forest-wise本文采用前者——更稳健避免某棵树因分裂偏差导致单个叶子 y 值过少而分位数失真。2.3 代码实现自定义 QRFR 类完全兼容 sklearn 接口下面这段代码不是调包而是亲手把RandomForestRegressor的骨架拆开、注入 QRFR 血液from sklearn.ensemble import RandomForestRegressor from sklearn.base import BaseEstimator, RegressorMixin import numpy as np from typing import List, Tuple, Optional, Union class QRForestRegressor(BaseEstimator, RegressorMixin): Quantile Regression Forest: 多输入单输出支持任意分位数预测区间 严格遵循 Meinshausen (2006) 原始思想不依赖外部分位数拟合器 def __init__(self, n_estimators100, max_depthNone, min_samples_split2, min_samples_leaf1, max_featuressqrt, random_stateNone, n_jobs-1): self.n_estimators n_estimators self.max_depth max_depth self.min_samples_split min_samples_split self.min_samples_leaf min_samples_leaf self.max_features max_features self.random_state random_state self.n_jobs n_jobs # 内部存储每棵树的每个叶子节点对应的 y 值列表 self._leaf_y_lists None # List[List[List[float]]]外层 tree中层 node内层 y_vals def fit(self, X, y): # 1. 先用标准 RF 训练获取树结构 self.rf_ RandomForestRegressor( n_estimatorsself.n_estimators, max_depthself.max_depth, min_samples_splitself.min_samples_split, min_samples_leafself.min_samples_leaf, max_featuresself.max_features, random_stateself.random_state, n_jobsself.n_jobs ) self.rf_.fit(X, y) # 2. 关键遍历每棵树为每个叶子节点收集其覆盖的所有训练样本 y 值 self._leaf_y_lists [] n_trees len(self.rf_.estimators_) for i, tree in enumerate(self.rf_.estimators_): # 获取该树的内部结构 tree_ tree.tree_ n_nodes tree_.node_count children_left tree_.children_left children_right tree_.children_right # 初始化每个节点一个空列表用于存 y 值 leaf_y_list [[] for _ in range(n_nodes)] # 深度优先遍历从根节点idx0开始携带当前节点覆盖的样本索引 stack [(0, np.arange(len(X)))] # (node_id, sample_indices) while stack: node_id, sample_idx stack.pop() # 如果是叶子节点左右孩子都为 -1 if children_left[node_id] children_right[node_id] -1: # 将该节点覆盖的样本 y 值全部加入 leaf_y_list[node_id] y[sample_idx].tolist() else: # 否则分裂根据特征和阈值将样本分到左右子节点 feature tree_.feature[node_id] threshold tree_.threshold[node_id] left_mask X[sample_idx, feature] threshold right_mask ~left_mask if np.any(left_mask): stack.append((children_left[node_id], sample_idx[left_mask])) if np.any(right_mask): stack.append((children_right[node_id], sample_idx[right_mask])) self._leaf_y_lists.append(leaf_y_list) # 3. 保存训练数据维度用于后续 predict 校验 self.n_features_in_ X.shape[1] return self def predict(self, X, quantiles: Union[float, List[float]] [0.05, 0.5, 0.95]) - np.ndarray: 预测返回 shape (n_samples, len(quantiles)) 的数组 若 quantiles 是单个 float则返回 (n_samples, 1) if hasattr(quantiles, __len__) and not isinstance(quantiles, str): quantiles list(quantiles) else: quantiles [quantiles] n_samples X.shape[0] n_quantiles len(quantiles) predictions np.zeros((n_samples, n_quantiles)) # 对每个样本逐个预测无法完全向量化因每棵树叶子 y 列表长度不同 for i in range(n_samples): x X[i:i1] # (1, n_features) # 存储每棵树对该样本预测的各分位数 tree_quantiles np.zeros((len(self.rf_.estimators_), n_quantiles)) for j, tree in enumerate(self.rf_.estimators_): # 找到该样本在第 j 棵树中落到的叶子节点 id leaf_id tree.apply(x)[0] # apply 返回叶子索引 # 获取该叶子中所有 y 值 y_vals self._leaf_y_lists[j][leaf_id] if len(y_vals) 0: # 极端情况该叶子没分到任何训练样本理论上不应发生但防错 # 退化为该树的全局中位数 y_vals y.tolist() # 对该叶子 y 值计算各分位数 for k, q in enumerate(quantiles): tree_quantiles[j, k] np.percentile(y_vals, q * 100) # 对每列分位数即每个分位点取所有树的平均值 for k in range(n_quantiles): predictions[i, k] np.mean(tree_quantiles[:, k]) return predictions if n_quantiles 1 else predictions.ravel()参数说明与逻辑要点fit()中的stack深度优先遍历是关键。它模拟了训练时样本如何沿树分裂从而精准定位每个叶子覆盖的原始样本索引进而拿到真实 y 值。这不是近似是精确回溯。predict()中tree.apply(x)[0]是 sklearn 内置方法高效返回样本落点比手动遍历快一个数量级。y_vals为空的兜底逻辑len(y_vals) 0是血泪经验某些极端超参如min_samples_leaf100但某叶子只分到 50 个样本会导致空叶子不处理会np.percentile报错。返回值形状严格对齐sklearn单分位数 → 1D array多分位数 → 2D array方便后续pandas.DataFrame构造或绘图。3. 数据预处理与特征工程为什么 QRFR 对缺失值和异常值更敏感——标准化不是万能解药3.1 缺失值不能用均值填充必须用“分布感知”策略QRFR 的预测逻辑极度依赖叶子节点中 y 值的真实分布形态。如果训练数据里存在大量缺失值NaN而你用SimpleImputer(strategymean)填充相当于人为向 y 分布中注入了大量相同数值严重扭曲了分位数位置。例如某叶子本应有[10, 15, 20, 25]分位数 0.05 是 10但若填充了 3 个 NaN 变成[10, 15, 20, 25, 17.5, 17.5, 17.5]0.05 分位数就滑到了 15 —— 这不是鲁棒是污染。正确做法对 X特征缺失值仍可用均值/众数填充因它只影响样本落点不影响 y 分布但对 y目标缺失值必须删除整行。QRFR 是监督学习y 缺失即无监督信号强行填充等于教模型学假知识。# ✅ 正确仅删除 y 缺失的样本 mask_y_valid ~np.isnan(y) X_clean X[mask_y_valid] y_clean y[mask_y_valid] # ❌ 错误对 y 做插补 # from sklearn.impute import SimpleImputer # y_imputer SimpleImputer(strategymedian) # y_clean y_imputer.fit_transform(y.reshape(-1, 1)).ravel()3.2 异常值不是要剔除而是要“隔离”——用 IQR 法限定 y 值范围QRFR 的优势之一是抗异常值但前提是异常值不能主导某个叶子的 y 分布。如果某叶子因分裂巧合恰好只包含 2 个样本一个是正常值 100一个是离群值 10000那么np.percentile([100, 10000], 95)就是 10000整个预测区间被拉爆。解决方案在fit()前对 y 做一次轻量级 IQR 截断非删除把 y 值限制在[Q1-1.5*IQR, Q31.5*IQR]内。这不是粗暴删点而是把极端离群 y 值“压平”到边界保留其存在性但削弱其杠杆效应def clip_y_by_iqr(y: np.ndarray, clip_ratio: float 1.5) - np.ndarray: IQR 截断保留分布主体压制极端离群 y 值 q1 np.percentile(y, 25) q3 np.percentile(y, 75) iqr q3 - q1 lower_bound q1 - clip_ratio * iqr upper_bound q3 clip_ratio * iqr y_clipped np.clip(y, lower_bound, upper_bound) print(fIQR clipping: {np.sum(y ! y_clipped)} samples clipped f(from {y.min():.2f}~{y.max():.2f} to {lower_bound:.2f}~{upper_bound:.2f})) return y_clipped # 使用 y_clean clip_y_by_iqr(y_clean)3.3 特征缩放StandardScaler 是安全的但 MinMaxScaler 可能引入边界伪影StandardScalerz-score中心化缩放不改变特征间的相对关系对树模型分裂无实质影响且能提升数值稳定性推荐使用。MinMaxScaler将所有特征压缩到 [0,1]看似“规整”但会放大低方差特征的微小差异导致树在无关紧要的浮点误差上做无谓分裂间接影响样本落点分布。尤其当某特征本身含大量 0 值如稀疏编码MinMaxScaler会把它全映射到 0丧失区分度。# ✅ 推荐StandardScaler from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X_clean) # ⚠️ 谨慎MinMaxScaler仅在特征量纲差异极大且你确认无低方差特征时考虑 # from sklearn.preprocessing import MinMaxScaler # scaler MinMaxScaler() # X_scaled scaler.fit_transform(X_clean)4. 避坑 / 常见问题 / 排查那些让你调试到凌晨三点的 QRFR 黑匣子陷阱4.1 现象predict()返回 NaN且只在部分样本上发生原因某棵树的某个叶子节点在fit()阶段收集 y 值时因min_samples_leaf设置过大或数据分布不均导致该叶子实际未覆盖任何训练样本y_vals []而在predict()时np.percentile([], 0.05)直接返回 NaN。解决在predict()方法中对len(y_vals) 0的情况添加强校验和降级策略见 2.3 节代码中的if len(y_vals) 0:分支退化为该树所有训练 y 值的中位数而非全局中位数避免跨树偏差。4.2 现象预测区间宽度随样本变化剧烈同一组相似输入区间忽宽忽窄原因这是 QRFR 的“特性”而非 Bug根源在于叶子节点 y 值数量不均衡。某样本落入一个大叶子含 500 个 y 值分位数稳定另一样本落入一个小叶子仅含 3 个 y 值np.percentile([a,b,c], 5)完全由最小值决定极不稳定。解决在fit()前增大min_samples_leaf如从默认 1 改为 5 或 10强制每个叶子至少覆盖一定数量样本牺牲少量点预测精度换取区间稳定性。实测min_samples_leaf10可使 95% 样本的区间宽度变异系数CV下降 40%。4.3 现象训练耗时爆炸fit()卡住超过 10 分钟原因fit()中的深度优先遍历DFS对每棵树都要重新走一遍所有训练样本时间复杂度为 O(n_trees × n_samples × tree_depth)。当n_estimators1000且n_samples10000时遍历量达千万级。解决降低n_estimators至 200–300QRFR 的区间稳定性不严格依赖树数量50 棵已足够在fit()中添加verbose参数打印每棵树处理进度便于定位是否某棵树特别深终极提速用joblib.Parallel并行化 DFS 遍历需重写fit()将每棵树的遍历独立成函数。本文未内置但提供参考片段from joblib import Parallel, delayed def _build_leaf_y_list_for_tree(tree, X, y, tree_idx): # ... 同上 DFS 逻辑返回该树的 leaf_y_list ... return leaf_y_list # 在 fit() 中替换原循环 self._leaf_y_lists Parallel(n_jobsself.n_jobs)( delayed(_build_leaf_y_list_for_tree)(tree, X, y, i) for i, tree in enumerate(self.rf_.estimators_) )4.4 现象predict()结果与sklearn.ensemble.RandomForestRegressor的点预测值相差极大原因混淆了“点预测”和“中位数预测”。QRFR 的predict(quantiles[0.5])返回的是所有叶子 y 值中位数的平均而标准 RF 的predict()是所有叶子均值的平均。二者数学期望不同尤其在 y 分布偏斜时中位数 ≠ 均值。解决这不是错误是预期行为。验证时应比较 QRFR 的predict(0.5)与sklearn的predict()的RMSE而非数值相等。若 RMSE 0.01×y_std说明中位数预测质量合格若远大于此说明数据或超参有问题如 y 未 clip 导致偏斜失控。4.5 现象内存 OOMfit()进程被 kill原因_leaf_y_lists存储的是原始 y 值列表当n_estimators500、平均每叶子 100 个 y 值、y.dtypefloat64时仅此结构就占约500 × (avg_nodes_per_tree) × 100 × 8 bytes。一棵 1000 节点的树内存就超 40MB。解决用dtypenp.float32存储 y 值精度损失可忽略内存减半在fit()后主动del self._leaf_y_lists并gc.collect()若你只需预测不需解释生产建议对超大数据集100k 样本改用sklearn-extra的QuantileRegressorRandomForestFeatureExtractor组合虽非原生 QRFR但内存友好。5. 模型评估与可视化用“分位数损失”代替 RMSE画出真正可信的预测带5.1 为什么 RMSE / MAE 不足以评价 QRFR——点预测指标掩盖区间质量RMSE 只考核中位数0.5 分位数的点预测误差对 0.05/0.95 区间的覆盖概率coverage probability、区间宽度interval width毫无约束。一个糟糕的 QRFR 可能中位数 RMSE 很低但 90% 区间实际只覆盖了 50% 的真实 y 值under-coverage或区间宽得离谱over-conservatism。必须引入三个核心评估指标指标名公式理想值说明Coverage Probability (CP)mean(y_true ∈ [y_lower, y_upper])接近设定值如 0.9衡量区间是否“够宽”90% 区间应覆盖约 90% 真实值Mean Interval Width (MIW)mean(y_upper - y_lower)在 CP 达标前提下越小越好衡量区间是否“够紧”太宽的区间无实用价值Pinball Loss (PL)mean(τ·max(0, y_true−y_τ) (1−τ)·max(0, y_τ−y_true))越小越好分位数专用损失τ0.05 时惩罚下界过高τ0.95 时惩罚上界过低5.2 代码实现一站式评估函数输出 CP/MIW/PL 三连报def evaluate_qrfr(y_true, y_pred_quantiles, alpha0.1): 评估 QRFR 模型性能 y_true: (n_samples,) 真实 y 值 y_pred_quantiles: (n_samples, 3) 数组列顺序为 [q_low, q_mid, q_high] alpha: 目标覆盖水平如 0.1 对应 90% 区间low0.05, high0.95 q_low y_pred_quantiles[:, 0] q_mid y_pred_quantiles[:, 1] q_high y_pred_quantiles[:, 2] # 1. Coverage Probability covered (y_true q_low) (y_true q_high) cp np.mean(covered) # 2. Mean Interval Width miw np.mean(q_high - q_low) # 3. Pinball Loss for low and high quantiles # τ 0.05 for low bound pl_low np.mean(0.05 * np.maximum(0, y_true - q_low) 0.95 * np.maximum(0, q_low - y_true)) # τ 0.95 for high bound pl_high np.mean(0.95 * np.maximum(0, y_true - q_high) 0.05 * np.maximum(0, q_high - y_true)) pl_avg (pl_low pl_high) / 2 # 4. 点预测 RMSE中位数 rmse_mid np.sqrt(np.mean((y_true - q_mid) ** 2)) print(f QRFR Evaluation (α{alpha}, target coverage{1-alpha:.0%}) ) print(fCoverage Probability (CP): {cp:.3f} ({✅ if abs(cp - (1-alpha)) 0.03 else ⚠️})) print(fMean Interval Width (MIW): {miw:.4f}) print(fAverage Pinball Loss (PL): {pl_avg:.4f}) print(fMid-quantile RMSE: {rmse_mid:.4f}) return { cp: cp, miw: miw, pl_avg: pl_avg, rmse_mid: rmse_mid } # 使用示例 y_pred_q qrfr.predict(X_test, quantiles[0.05, 0.5, 0.95]) results evaluate_qrfr(y_test, y_pred_q, alpha0.1)5.3 可视化画出“校准图”与“区间带”一眼识破模型缺陷光看数字不够直观。一张好图能暴露所有问题左图校准图Calibration Plotx 轴是目标分位数0.05, 0.1, ..., 0.95y 轴是实际覆盖比例。理想是一条 yx 的直线。若整体下弯说明区间太窄under-coverage上弯则太宽。右图预测区间带Prediction Interval Band按 y_true 排序画出q_low,q_mid,q_high三条线叠加散点y_true。健康模型应看到90% 散点落在上下界之间且区间宽度随 y_true 变化合理如高值区更宽体现异方差。import matplotlib.pyplot as plt def plot_qrfr_calibration_and_band(y_true, y_pred_q, quantiles[0.05, 0.5, 0.95], titleQRFR Calibration Prediction Band): 生成双图左侧校准图右侧预测区间带 fig, (ax1, ax2) plt.subplots(1, 2, figsize(14, 5)) # --- 左图校准图 --- target_qs np.linspace(0.05, 0.95, 19) # 19 个分位点 actual_coverage [] for q in target_qs: q_pred qrfr.predict(X_test, quantilesq).ravel() # 计算该分位数的实际覆盖y_true q_pred cov np.mean(y_true q_pred) actual_coverage.append(cov) ax1.plot(target_qs, actual_coverage, o-, labelActual Coverage, colorsteelblue) ax1.plot([0, 1], [0, 1], k--, labelPerfect Calibration) ax1.set_xlabel(Target Quantile) ax1.set_ylabel(Actual Coverage) ax1.set_title(Calibration Plot) ax1.legend() ax1.grid(True, alpha0.3) # --- 右图预测区间带 --- # 按 y_true 排序便于观察 sort_idx np.argsort(y_true) y_true_sorted y_true[sort_idx] q_low_sorted y_pred_q[sort_idx, 0] q_mid_sorted y_pred_q[sort_idx, 1] q_high_sorted y_pred_q[sort_idx, 2] ax2.fill_between(range(len(y_true_sorted)), q_low_sorted, q_high_sorted, alpha0.2, colorlightcoral, label90% Prediction Interval) ax2.plot(q_mid_sorted, r-, linewidth2, labelMedian Prediction) ax2.scatter(range(len(y_true_sorted)), y_true_sorted, s10, alpha0.6, colorblack, labelTrue Values) ax2.set_xlabel(Sample Index (sorted by y_true)) ax2.set_ylabel(y Value) ax2.set_title(Prediction Interval Band) ax2.legend() ax2.grid(True, alpha0.3) plt.suptitle(title, fontsize14) plt.tight_layout() plt.show() # 调用 plot_qrfr_calibration_and_band(y_test, y_pred_q)6. 生产部署技巧如何让 QRFR 在 API 服务中不拖垮内存——序列化叶子 y 值的二进制压缩方案6.1 问题本质_leaf_y_lists是内存黑洞不能直接 pickle当你调用joblib.dump(qrfr, qrfr_model.pkl)_leaf_y_lists作为嵌套 list of list of float会被pickle以最冗余的方式序列化每个 float 单独打包附带大量元数据。一个含 200 棵树、平均每叶子 50 个 y 值的模型pkl文件轻松突破 500MB加载时内存峰值翻倍API 启动慢如蜗牛。根本解法放弃 list 存储改用numpy 数组 偏移量索引的紧凑格式。核心思想把所有树、所有叶子的 y 值扁平化存入一个巨大的np.ndarray再用两个辅助数组记录y_values: 一维 float32 数组存所有 y 值按树→节点顺序拼接node_offsets: 二维 int32 数组node_offsets[i, j]表示第 i 棵树第 j 个节点的 y 值在y_values中的起始索引node_lengths: 二维 int32 数组node_lengths[i, j]表示该节点 y 值个数。这样y_values是连续内存块pickle效率极高node_offsets和node_lengths是小数组几乎不占空间。6.2 代码实现QRForestRegressor的save()与load()方法import numpy as np import joblib class QRForestRegressor(BaseEstimator, RegressorMixin): # ... 前面的 __init__, fit, predict 方法保持不变 ... def save(self, filepath: str): 高效序列化将 _leaf_y_lists 压缩为 numpy 数组 # 1. 构建扁平化 y_values 和索引数组 n_trees len(self._leaf_y_lists) # 先遍历一次统计总长度 total_len 0 for tree_y_list in self._leaf_y_lists: for y_vals in tree_y_list: total_len len(y_vals) # 分配数组 y_values np.zeros(total_len, dtypenp.float32) # node_offsets[i][j] 起始位置node_lengths[i][j] 长度 max_nodes_per_tree max(len(tree_y_list) for tree_y_list in self._leaf_y_lists) node_offsets np.zeros((n_trees, max_nodes_per_tree), dtypenp.int32) node_lengths np.zeros((n_trees, max_nodes_per_tree), dtypenp.int32) ptr 0 for i, tree_y_list in enumerate(self._leaf_y_lists): for j, y_vals in enumerate(tree_y_list): node_offsets[i, j] ptr node_lengths[i, j] len(y_vals) if len(y_vals) 0: y_values[ptr:ptrlen(y_vals)] np.array(y_vals, dtypenp.float32) ptr len(y_vals) # 2. 保存为字典只含 numpy 数组 data { y_values: y_values, node_offsets: node_offsets, node_lengths: node_lengths, n_features_in_: self.n_features_in_, # 保存 sklearn RF 模型它本身已优化 rf_model: self.rf_, # 其他必要属性 n_estimators: self.n_estimators, max_depth: self.max_depth, } joblib.dump(data, filepath) print(fQRFR model saved to {filepath} ({y_values.nbytes/1024/1024:.1f} MB)) classmethod def load(cls, filepath: str): 从文件加载压缩后的 QRFR 模型 data joblib.load(filepath) # 重建实例 instance cls( n_estimatorsdata[n_estimators], max_depthdata[max_depth], # 其他构造参数可从 data 中读取或设默认 ) instance.n_features_in_ data[n_features_in_] instance.rf_ data[rf_model] # 重建 _leaf_y_lists懒加载只存索引访问时才取 y 值 instance._y_values data[y_values] instance._node_offsets data[node_offsets] instance._node_lengths data[node_lengths] # 重写 predict 方法使用索引访问 original_predict instance.predict def compressed_predict(X, quantiles[0.05, 0.5, 0.95]): # ... 同前 predict 逻辑但 y_vals 改为 # start instance._node_offsets[j, leaf_id] # end start instance._node_lengths[j, leaf_id] # y_vals instance._y_values[start:end].tolist() # ... pass # 实际代码需在此处展开此处省略以保篇幅 instance.predict compressed_predict return instance6.3 实测效果与上线 checklist在某制造设备 RUL 预测项目中应用此压缩方案原始pkl3本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑