用Python实现随机森林分位数回归:从点预测到区间预测

发布时间:2026/10/11 20:12:30
用Python实现随机森林分位数回归:从点预测到区间预测 简介面向具备Python与机器学习基础的开发者、数据科学从业者这份资源针对多输入单输出回归任务中仅有点预测、缺乏不确定性评估的痛点系统讲解如何用QRFR随机森林分位数回归实现区间预测。文档从分位数回归与随机森林的理论基础入手结合项目背景、目标、挑战及创新点覆盖金融分析、气候预测、医疗健康等典型应用场景并给出完整的模型描述与可运行的示例代码。内容同时涉及数据预处理、超参数调节、过拟合控制等实践难点以及QRFR在复杂异构数据下的泛化优势帮助读者理解从原理到落地的完整链路。压缩包共1个docx文档大小仅33KB内容以模型理论讲解与实战代码为主目录结构清晰可快速定位到模型架构、背景介绍与示例代码章节。已有1116人学习下载适合希望提升预测可靠性、掌握区间估计方法的读者参考实践。1. QRFR 不只是随机森林加个分位数它把“预测一个数”换成“预测一段区间”你手头有一组多维特征要预测一个连续目标业务方却追问“给个区间吧别只报一个数。”这种诉求在量化风控、设备寿命预测、电力负荷预测里非常常见。普通随机森林只能出点预测而 QRFRQuantile Regression Forest随机森林分位数回归的做法是在每棵树的叶节点上保留训练样本的目标值再按分位数把预测不确定性量化成区间。本文用 Python 从零实现一个多输入单输出的 QRFR覆盖模型原理、评估指标和落地避坑清单示例代码可以直接换成自己的数据跑通。2. 从点预测到区间预测QRFR 的原理与选型理由2.1 为什么点预测不够区间预测到底在回答什么问题随机森林回归输出的往往是一个均值比如预测某台设备剩余寿命是 120 天。实际使用中这个数字意义有限如果业务上要在 100 天时安排检修你需要知道的是“有多少把握落在 100 天到 140 天之间”。点预测隐藏了两类信息一是数据本身的噪声二是模型对这个特定样本的把握程度。更关键的是预测不确定性通常不是均匀的。特征取值在训练数据密集区时模型很有把握落在稀疏区或者超出训练范围时误差会放大。普通随机森林对这些毫无区分只会给一个不痛不痒的均值。QRFR 能回答“输入 x 时目标值 y 的分布大约在哪个区间”而且这个区间会随着输入位置自动变宽变窄这恰好是很多工程场景真正需要的。所以区间预测不是在点预测之上加一个固定误差带。它要回答的问题是给定输入目标值的条件分位数在哪里。条件分位数是随特征变化的QRFR 就是用来估计这个条件分位数的。2.2 QRFR 是怎么算出分位数的叶节点样本分布普通随机森林在训练时每棵决策树不断分裂最终每个叶节点里保留了一批训练样本。预测时新样本落到某棵树的某个叶节点模型取该叶节点训练样本目标值的平均作为输出。QRFR 的核心变化在于不再丢弃分布信息而是把这些样本的目标值全部保留下来。具体实现上训练阶段和普通随机森林几乎一样只是额外记录每个叶节点对应的训练样本索引。预测阶段对于一个新样本 x先让它穿过每一棵树收集它在每棵树上落入的叶节点里的所有目标值然后把所有树收集到的目标值拼成一个混合样本集合。对这个集合直接取分位数比如 2.5%、50%、97.5%就得到了下界、中位数和上界。用公式表达会更清楚。条件分位数的定义为Q_q(x) min{ y : P(Y ≤ y | X x) ≥ q }QRFR 用训练样本的经验分布去近似这个条件分布P(Y ≤ y | X x) ≈ (1 / T) · Σ_t (1 / n_t) · Σ_{i ∈ L_t(x)} 1(y_i ≤ y)其中 T 是树的数量L_t(x) 是第 t 棵树中样本 x 落入的叶节点n_t 是该叶节点中的训练样本数。可以看到每棵树对分布的贡献被等权平均叶节点内每个样本也等权。这就是 Meinshausen 在 2006 年提出的分位数回归森林的核心思想。这里要特别说明sklearn 的 RandomForestRegressor.predict 返回的是叶节点均值的平均它把分布信息丢掉了。QRFR 正好是在这个基础上多保留了一步这也是为什么很多随机森林的框架里没有直接提供分位数接口需要自己做一层封装。2.3 常见区间预测方案对比为什么我选 QRFR工程里做区间预测的方案不止一种各有利弊。我这些年在实际项目里对比过几个常用路线给出一张表供参考。方案实现难度区间宽度是否随输入变化可解释性适合场景固定误差带均值 ± kσ最低否高误差分布近似同方差时分位数回归 GBDT中是中需要更平滑的分位数曲线QRFR低-中是高表格数据、特征维度 5-200 的回归NGBoost中高是中想要完整概率分布输出贝叶斯神经网络 / MC Dropout高是低图像、序列等非表格数据QRFR 最大的优势是训练逻辑与普通随机森林一致不需要自定义损失函数也不用调神经网络的超参。它保留了随机森林的特性对特征尺度不敏感、能自动处理特征交互、不需要归一化。这在真实业务数据上非常省心尤其当你面对的是几十个量纲各异的业务特征又没有时间做精细清洗的时候。另一个现实理由是代码可靠。quantile-forest 这个库提供了完整的 RandomForestQuantileRegressor但完全依赖第三方实现会有版本兼容顾虑。我常用的做法是直接用 sklearn 的 RandomForestRegressor 封装一层代码量不大还能完全控制叶节点样本的收集逻辑出了问题自己就能查。3. 多输入单输出 QRFR 的 Python 实现模型封装、示例代码与参数说明3.1 环境依赖与合成数据多输入单输出的训练集怎么构造实现 QRFR 只需要三个基础库numpy、scikit-learn、matplotlib。不需要额外安装专用包我用的是 Python 3.9 以上版本sklearn 1.2 以上如果你用的是更早的版本apply 接口和 RandomForestRegressor 的行为基本一致代码可以兼容。为了演示多输入单输出我不会去加载某个固定的公开数据集而是直接构造一个带异方差噪声的合成回归问题。异方差的意思是噪声幅度随特征变化这正好能看出 QRFR 区间预测的价值普通固定误差带做不到这一点。import numpy as np import matplotlib.pyplot as plt from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split rng np.random.default_rng(42) n 1000 # 5 个输入特征多输入单输出 X rng.uniform(-3, 3, size(n, 5)) # 目标由前三个特征的非线性组合决定噪声幅度随 x0 增大而增大 y ( np.sin(X[:, 0]) 0.5 * X[:, 1] ** 2 - 0.3 * X[:, 2] rng.normal(0, 0.2 0.1 * np.abs(X[:, 0]), sizen) ) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state7 )这段代码生成了 5 维输入和一个连续目标。y 与特征的函数关系是非线性的而且噪声标准差随第一个特征绝对值增大而增大。这样设置有两个目的第一测试集能真实评估区间预测能力第二后面你会看到 QRFR 的区间宽度在 x0 绝对值大的样本上自动变宽而均值加固定误差带做不到。train_test_split 的 random_state 固定为 7保证后面每次跑实验切分一致。这里特别提醒切分要在任何模型训练之前完成否则会在后面第 5 章踩到数据泄露的坑。3.2 用 sklearn 的 RandomForestRegressor 封装 QRFR 类核心封装类如下。思路是训练时保存每个叶节点覆盖的训练样本索引预测时收集对应目标值并计算分位数。class QuantileRegressionForest: 极简 QRFR在随机森林叶节点上收集样本再算分位数。 def __init__(self, n_estimators200, min_samples_leaf10, max_depthNone, max_featuressqrt, random_state42): self.rf RandomForestRegressor( n_estimatorsn_estimators, min_samples_leafmin_samples_leaf, max_depthmax_depth, max_featuresmax_features, random_staterandom_state, ) self.quantiles None def fit(self, X, y, quantiles(0.025, 0.5, 0.975)): self.rf.fit(X, y) self.quantiles list(quantiles) self.y_train_ np.asarray(y) # apply 返回每个训练样本在每棵树中落在哪个叶节点 leaf_ids self.rf.apply(X) # 按树缓存每个叶节点覆盖的训练样本索引 self._node_samples [] for tid in range(self.rf.n_estimators): node_map {} for i, leaf in enumerate(leaf_ids[:, tid]): node_map.setdefault(leaf, []).append(i) self._node_samples.append(node_map) return self def predict_interval(self, X, quantilesNone): quantiles quantiles if quantiles is not None else self.quantiles X np.asarray(X) leaf_ids self.rf.apply(X) out np.zeros((X.shape[0], len(quantiles))) for i in range(X.shape[0]): vals [] for tid in range(self.rf.n_estimators): leaf leaf_ids[i, tid] idx self._node_samples[tid].get(leaf) if idx is not None: vals.append(self.y_train_[idx]) if vals: all_vals np.concatenate(vals) out[i] np.quantile(all_vals, quantiles) else: out[i] self.rf.predict(X[i:i 1]) return out这段代码里有几个关键设计。fit 阶段调用 rf.apply(X)得到形状为 (n_samples, n_estimators) 的叶节点编号矩阵。随后遍历每棵树把每个叶节点对应的训练样本索引存进 node_map 字典。predict_interval 阶段对每个测试样本查出它在每棵树上的叶节点再从字典里拿到训练样本索引把索引对应的 y 值全部收集起来最后用 np.quantile 一次性计算多个分位数。np.quantile 默认采用 linear 插值方式这意味着叶节点样本少时分位数会出现明显的跳变。这一点在第 5 章会展开讲。如果你的数据量特别大、预测样本也有几十万个这种逐样本循环会偏慢可以改用矩阵化方式先批量拿到所有样本的叶节点矩阵再对每个叶节点预先聚合样本索引。但考虑到多数场景单次预测样本在几千量级这个实现够用且直观。3.3 跑通完整流程训练、预测区间与覆盖率核对模型封装好后主流程非常短。qrf QuantileRegressionForest( n_estimators300, min_samples_leaf30, max_featuressqrt, random_state42, ) qrf.fit(X_train, y_train, quantiles(0.025, 0.5, 0.975)) pred qrf.predict_interval(X_test) lower, mid, upper pred[:, 0], pred[:, 1], pred[:, 2] # 95% 预测区间覆盖率 coverage np.mean((y_test lower) (y_test upper)) print(fPICP: {coverage:.3f})PICP 是 Prediction Interval Coverage Probability即真实目标值落入预测区间的比例。95% 名义水平下的区间PICP 通常在 0.9 到 0.97 之间属于正常。如果明显低于 0.9说明区间太窄需要调整参数如果长期高于 0.98说明区间过宽预测区间对决策帮助有限。再看一眼区间宽度是否随输入变化width upper - lower # 按第一个特征把测试样本分成低区和高区比较平均宽度 mask_low X_test[:, 0] 0 mask_high X_test[:, 0] 0 print(fx00 平均宽度: {width[mask_low].mean():.3f}) print(fx00 平均宽度: {width[mask_high].mean():.3f})由于数据构造时噪声幅度随 x0 绝对值增大这里你应该能看到明显的宽度差异。这验证了 QRFR 不是给一个固定误差带而是真的在按输入位置调整区间。如果宽度的落差很小通常是因为 min_samples_leaf 设得过大把不同区域的样本混在一起把异方差信息磨平了。4. 区间预测效果怎么评估与调参覆盖率、区间宽度与三个必调参数4.1 PICP、PINAW 和区间得分三个指标一起看工程上评估区间预测只看覆盖率一个指标远远不够。把区间拉得无限宽覆盖率 100%但毫无决策价值。反过来区间极窄覆盖率高不了。所以实际落地我至少同时看三个指标。PICP 就是覆盖率公式很简单PICP (1 / N) · Σ 1(L(x_i) ≤ y_i ≤ U(x_i))PINAW 是归一化平均区间宽度。直接把宽度除以目标值的极差PINAW (1 / (N · R)) · Σ (U(x_i) - L(x_i))其中 R 是目标值在测试集上的取值范围。PINAW 越小说明区间越紧凑。两个指标结合看能得到“窄且准”的区间。还有一个更严格的综合指标叫区间得分Interval Score它同时惩罚过宽和漏掉真实值S (1 / N) · Σ [ (U - L) (2 / α) · (L - y_i) · 1(y_i L) (2 / α) · (y_i - U) · 1(y_i U) ]公式里 α 是显著性水平比如 95% 区间对应 α 0.05。区间越宽第一项越大真实值漏在区间外第二或第三项会带来沉重惩罚。这个得分越低越好。我一般把这三个指标一起打印def evaluate_interval(y_true, lower, upper, alpha0.05): y_true np.asarray(y_true) lower np.asarray(lower) upper np.asarray(upper) # PICP picp np.mean((y_true lower) (y_true upper)) # PINAW r np.ptp(y_true) pinaw np.mean(upper - lower) / r # Interval Score penalty np.zeros_like(y_true) penalty 2 / alpha * (lower - y_true) * (y_true lower) penalty 2 / alpha * (y_true - upper) * (y_true upper) score np.mean((upper - lower) penalty) return {PICP: picp, PINAW: pinaw, IntervalScore: score} print(evaluate_interval(y_test, lower, upper))输出结果里 Interval Score 是相对值没有绝对好坏标准但可以在参数调优时作为单一目标。实际业务中我也会关注漏检的位置如果漏掉的样本总是集中在某个特征区间说明模型在那个区域表达能力不够或者训练数据稀疏。4.2 三个必调参数min_samples_leaf、n_estimators、max_featuresQRFR 的参数和随机森林大同小异但影响方向有差异。min_samples_leaf 是影响区间质量的第一参数。它直接决定每个叶节点里至少有多少训练样本。设得太小比如默认的 1叶节点样本极少分位数估计方差大预测区间会呈现严重锯齿状且覆盖率不稳设得太大叶节点混入大量分布不同的样本区间被平均化宽度整体变大异方差信息丢失。我的经验是先从样本总量的 1% 起步比如 1000 条训练数据设 105000 条设 30-50再用验证集微调。n_estimators 在点预测里往往 100 棵就够但对 QRFR 来说区间稳定性的提升是持续的。因为分位数估计依赖每个叶节点的样本集合树越多收集到的混合样本越丰富分位数曲线越平滑。我在实践中一般设 300 到 500低于 100 时候区间抖动非常明显。max_features 默认的 sqrt 对大多数表格数据都合适。如果你的输入特征之间有强相关性可以试 log2如果特征数很少比如 3 到 5 个可以把它加大到 1.0让每棵树看到全部特征。特征数多、样本量不足时sqrt 能有效减少过拟合避免某些特征主导分裂导致叶节点样本偏向。这三个参数的调优顺序我习惯是先固定 n_estimators300粗调 min_samples_leaf 找到覆盖率合理的区间然后把 max_features 在 [sqrt, log2, 0.8] 里试一遍最后加大 n_estimators 看稳定性。4.3 参数敏感性实验怎么跑别只调参不看稳定性调参最忌讳只看一次随机切分的结果。同样的数据和参数换一个 random_statePICP 波动超过 0.03 都很正常。我一般会做一个小实验固定参数用不同的 random_state 跑 10 次切分记录每次 PICP、PINAW 和 Interval Score看均值和标准差。标准差大说明模型对这个超参组合很敏感这本身就是一种不稳健部署上线后很容易翻车。反之如果参数让平均值变好但标准差翻倍我会宁愿退一步选更平滑的组合。QRFR 的随机性来源有两个随机森林自身的 bootstrap 抽样和特征抽样以及训练测试切分。这两个都要在实验中一起抖动才能看出模型真实水平。5. QRFR 落地避坑五个常见翻车点与排查方法5.1 现象预测区间画出来是锯齿状阶梯区间曲线不平滑一步一级台阶像阶梯函数。原因是叶节点样本少np.quantile 能取到的值就那么几个离散档位分位数估计跳变。解决的办法按优先级排序先调大 min_samples_leaf让每个叶节点至少有 20 到 50 个样本再把 n_estimators 提高到 300 以上增加重叠样本量如果还不行检查特征是否有大量唯一值太少的哑变量它们会制造大量叶子节点把样本打散。还有一种更治本的方式是直接用 quantile-forest 库它内部对分位数做了平滑处理但代价是失去对这些细节的控制力。5.2 现象95% 区间实际覆盖率只有 70% 到 80%这是最常见也最危险的翻车。先检查训练集内部覆盖率用训练数据预测并计算 PICP。如果训练集覆盖率正常而测试集崩了说明模型分布外泛化能力差典型的过拟合。如果训练集覆盖率本身就低多半是 min_samples_leaf 太小叶节点分位数估计偏差太大。还有一个容易忽视的原因测试集分布和训练集不一致。比如训练数据是去年一整年的测试数据是最近两个月的业务环境已经变了。这不是调参能解决的要考虑滚动训练或做特征漂移检测。最直接的验证方式是把测试集按时间排序逐段看覆盖率如果后段普遍低就是分布漂移不是模型自身问题。5.3 现象先 fit 再切分覆盖率魔幻般高达 0.99这是个典型的思路错误而且代码里非常隐蔽。有人把全量数据直接丢进模型的 fit然后在训练集上随机抽样一部分去做“预测验证”得到的覆盖率当然高得离谱因为模型见过这些样本了。正确顺序一定是先切分、再训练、再预测。更隐蔽的变体是用 GridSearchCV 做参数调优后直接用同一份全量数据的最优参数模型去预测全量数据来评估。这同样是数据泄露因为交叉验证中验证集的信息已经通过参数选择流入了模型。评估时我永远保留一个从未参与任何训练和调参的 hold-out 测试集。5.4 现象特征维度高、样本量少区间宽度发散特征 100 个、训练样本 800 条QRFR 的预测区间忽宽忽窄完全没有规律。原因在于随机森林在高维稀疏空间中分裂时每棵树的叶节点覆盖的邻域非常不均匀某些测试样本落进极小的叶节点收集到的样本少且分散分位数自然不稳定。解决思路有三条一是用特征筛选把维度压到 20 以内再训练二是把 max_features 设成 log2 降低每棵树对高维空间的依赖三是提高 n_estimators 到 500让每棵树能互补。如果这些都不够考虑先做 PCA 或树模型的特征重要性筛选再进 QRFR。5.5 现象异常值把尾部拉飞区间宽度被个别点放大训练集里有一两个极端大的 y 值它们落进某个叶节点后所有经过这个区域的测试样本上界会突然飙高。分位数对尾部天然敏感尤其是 97.5% 这种高分位数一个异常值就能把区间拉宽。我一般在训练前先对 y 做一次简单的截断处理比如用分位数把上下 1% 的极值压缩Winsorize。这不会对点预测模型伤筋动骨但能让区间宽度稳定很多。如果业务场景不允许修改原始目标值那就只能调大 min_samples_leaf让异常值被更多正常样本稀释代价是整个区间会变宽。6. 一个进阶习惯用滚动验证与分段覆盖率检验 QRFR 是否真的可靠当你决定把 QRFR 用到真实业务里我建议养成两个习惯滚动验证和分段覆盖率检验。前者解决数据分布漂移问题后者解决区间是否在全局均匀有效的问题。滚动验证的做法是把训练数据按时间排序用前 k 个窗口训练预测下一个窗口逐步向后滑动。这和时序预测里的 walk-forward 验证一样能模拟上线后的真实使用方式。如果业务数据没有时间属性也可以用 KFold 或者拟随机切分但千万别用默认的 StratifiedKFold 直接套在回归任务上要知道回归的切分需要保持分布完整shuffle 随机切分通常就够了。分段覆盖率检验更直观把测试集按预测中位数排序分成若干段分别统计每段覆盖率。如果某一段覆盖率明显偏低说明模型在预测值偏大或偏小的区域上估计不准。一个实用的实现片段order np.argsort(mid) n_seg 5 seg_size len(y_test) // n_seg for k in range(n_seg): idx order[k * seg_size:(k 1) * seg_size] seg_cov ((y_test[idx] lower[idx]) (y_test[idx] upper[idx])).mean() print(fsegment {k 1}: coverage {seg_cov:.3f})如果前几段覆盖率 0.97、后几段 0.72说明模型对高预测值区间的把握明显不足可能是训练数据在这些区间样本少也可能是异方差结构没被充分学习。这时候我会回头检查是不是 min_samples_leaf 设太大把尾部特征磨平了。我还习惯在模型上线前做一次保守性检查故意挑一批训练集中很少出现的特征组合跑一遍预测看区间宽度是否明显加宽。如果宽度和常规样本差不多那这个区间在稀疏区域就是不诚实的需要靠调参或者补充数据来修正。有一段时间我做完区间预测直接看整体 PICP 就交付了后来某次换新品数据后整个模型翻车才发现没做分段校验。从那以后只要是 QRFR 的多输入单输出区间预测我一定会同时出整体覆盖率、分段覆盖率和宽度三张图交叉确认。希望这个习惯也帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询