如果读者需要的是“结果大概在哪个范围”,一个点预测并不足够。scikit-learn 的 QuantileRegressor 可以拟合给定输入条件下的目标分位数,再用独立数据检查这个范围到底覆盖了多少真实结果。这里的范围描述未来目标值的变化,不是模型参数的置信区间。
先区分区间含义,再准备环境
三个模型分别拟合条件10%、50%和90%分位数:中间值是条件中位数,两端组成一个名义中央80%的预测区间。

名义80%来自0.9−0.1;它表示建模目标,并不保证每个输入区间或下一批数据都有80%的覆盖。50%分位数也不是条件均值。
本文示例已在 Python 3.11、scikit-learn 1.7.2、NumPy 2.4.6、pandas 2.3.3、SciPy 1.17.1 的本地 CPU 环境运行;输入是人为生成的教学数据,不是客户案例。
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
python -m pip install scikit-learn==1.7.2 pandas==2.3.3 numpy==2.4.6 scipy==1.17.1
生成带不同噪声大小的样本,分别训练三个分位数
本例的唯一特征x取0到10,目标由20+3x加噪声生成;噪声标准差随x增大。这让输入较大时的预测范围也有变宽的理由。训练集420条、测试集180条,测试目标不参与拟合。
将下面代码保存为 quantile_demo.py,在刚才安装依赖的环境运行 python quantile_demo.py;它会保存 quantile-review.csv,便于逐行回查真实值是否落在预测范围内。
import numpy as np
import pandas as pd
from sklearn.linear_model import QuantileRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_pinball_loss
rng = np.random.default_rng(42)
X = rng.uniform(0, 10, size=(600, 1))
y = 20 + 3 * X[:, 0] + rng.normal(0, 2 + X[:, 0], 600)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
quantiles = [0.1, 0.5, 0.9]
models = [QuantileRegressor(quantile=q, alpha=0.01, solver="highs")
.fit(X_train, y_train) for q in quantiles]
pred = np.column_stack([m.predict(X_test) for m in models])
assert pred.shape == (len(y_test), 3)
crossed = (pred[:, 0] > pred[:, 1]) | (pred[:, 1] > pred[:, 2])
if crossed.any():
raise ValueError("预测分位数发生交叉,先检查模型")
covered = (y_test >= pred[:, 0]) & (y_test <= pred[:, 2])
print("测试行数:", len(y_test))
print("区间覆盖率:", round(float(covered.mean()), 4))
print("平均宽度:", round(float((pred[:, 2] - pred[:, 0]).mean()), 4))
for i, q in enumerate(quantiles):
print("分位数", q, "pinball损失", round(
mean_pinball_loss(y_test, pred[:, i], alpha=q), 4))
result = pd.DataFrame({"x": X_test[:, 0], "actual": y_test,
"q10": pred[:, 0], "q50": pred[:, 1],
"q90": pred[:, 2], "covered": covered})
result.to_csv("quantile-review.csv", index=False, encoding="utf-8-sig")
print(result.head(3).round(3).to_string(index=False))
QuantileRegressor 使用 pinball loss(分位数损失):对预测太高或太低的惩罚随目标分位数改变。alpha=0.01控制L1正则化,solver=”highs”指定线性规划求解器;这些值用于跑通例子,并不是调参后的最优配置。
核对覆盖率、区间宽度与越界记录
这次合成样本运行中,180条测试记录的覆盖率为0.8056,平均区间宽度为18.2611;这是该固定数据和划分的结果,不能当成其他数据集的覆盖承诺。
| 测试项 | 本例输出 | 怎样解释 |
|---|---|---|
| 10%分位 pinball损失 | 1.3229 | 同一分位、同一数据下比较不同模型 |
| 50%分位 pinball损失 | 2.7885 | 检查中位数预测的绝对偏差型损失 |
| 90%分位 pinball损失 | 1.1784 | 不能和10%损失直接排名模型优劣 |
| 覆盖率 | 0.8056 | 测试目标同时大于等于q10、小于等于q90的比例 |
CSV同时保留x、actual、q10、q50、q90和covered。第一条输出的actual约43.499,高于q90约42.285,因此covered为False;这类记录正是需要回查的越界样本。不要通过删除越界行来改善覆盖率。
真实项目还要按输入范围、业务人群和时间段分组检查覆盖率,并同时看区间宽度。特别宽的区间可能覆盖很多目标,却不能帮助决策。调参时使用单独验证集或合适的交叉验证,最终测试集只用于验收;持续数据存在时间顺序时,不沿用本例随机划分。
遇到这些情况先停止交付范围
分别拟合的分位数可能交叉;若代码报告交叉,先检查数据、正则化和模型形式,不把下界大于上界的结果直接交给用户。
此处是线性分位数模型。目标关系明显非线性、样本量很小,或后续数据分布改变时,示例结果不适用。训练范围外的x属于外推;区间看起来有宽度,也不能证明外推可靠。
这份教程只在合成数据上核对执行流程,没有验证真实需求、销量或收益预测。需要实际业务覆盖率时,保留未参与建模的有代表性样本,并明确可接受的漏覆盖率和区间宽度。
官方资料与适用版本
本文于2026年10月1日核对下列官方资料,接口说明对应 scikit-learn 1.7;运行示例使用1.7.2。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30716.html