moonconformal

    纯 MoonBit 保形预测:有限样本分位数、回归区间、分类集合与分组诊断

    conformal
    prediction
    uncertainty
    calibration
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    4 hours ago
    Downloads
    6

    No dependencies

    #MoonConformal

    纯 MoonBit 的模型无关保形预测后处理库。把外部模型的独立校准预测和真值转换为回归预测区间、多分类标签集合,并检查组别覆盖率、集合大小与有限样本分辨率。库不训练模型,不需要 Python 运行时或第三方 MoonCakes 依赖。

    版本:LuoYunxin1/moonconformal@0.1.0;许可证:MIT。

    #用途与实际工作流

    • 回归服务:输入未参与训练的校准真值、点预测或上下分位数,输出普通、按尺度归一化、CQR 或非对称尾区间;可保留无界区间与空集合。
    • 分类审核系统:输入概率矩阵与校准标签,使用 LAC、确定性 APS 或 RAPS 产生可变大小标签集合与保守 p 值;可按单标签/多标签结果分派人工审核。
    • 模型评估平台:固定组别分别校准;接收交叉验证模型的样本外预测构造 CV+ 区间;检查联合多输出覆盖与各维边际覆盖;快照可在服务间迁移。

    三个完整工作流在 examples/regression、examples/classification 和 examples/platform。数据为可复现的合成预测,代表接口接入方式;没有虚构真实用户部署或实测业务收益。

    #环境与安装

    开发及 CI 固定 moonc v0.10.18+6c83a7f62,高于赛事要求的 0.10.16;moon 为 0.1.20261028。支持 wasm-gc、wasm、js、Linux native。JS 需要 Node.js;native 需要 GCC/Clang。Windows 开发可使用 wasm-gc/wasm/js,Linux native 可用 WSL 或 CI 复现。

    在已有 MoonBit 项目目录中安装精确版本:

    moon add LuoYunxin1/moonconformal@0.1.0

    在消费者的 moon.pkg 中导入:

    import {
    "LuoYunxin1/moonconformal" @conformal,
    }
    options(is_main: true)

    创建 main.mbt:

    fn main {
    try {
    let scores = @conformal.absolute_scores(
    [1.0, 2.0, 3.0, 4.0],
    [0.0, 0.0, 0.0, 0.0],
    )
    let intervals = @conformal.predict_absolute(scores, [10.0], 0.2)
    println(@conformal.intervals_to_json(intervals))
    } catch {
    _ => abort("calibration failed")
    }
    }

    运行 moon run .,结果中的 lower=6、upper=14(JSON 对象键序不作为契约)。把 alpha 改为 0.1 时,有限样本校正会要求超出已有数据的秩,得到 "-inf" 与 "+inf";不会悄悄返回样本最大值。

    从源代码运行:

    git clone https://github.com/LuoYunxin1/moonconformal.git cd moonconformal moon run examples/regression moon run examples/classification moon run examples/platform

    Linux x86_64 可以执行 bash scripts/install-toolchain.sh /tmp/moonconformal-toolchain 安装固定工具链,随后设置 MOON_HOME、MOON_TOOLCHAIN_ROOT 为该目录,并把其 bin 加到 PATH。安装器要求新目录并校验两个官方归档的 SHA-256,不更改全局登录或系统配置。

    #核心接口

    场景校准预测/诊断
    普通 split conformalabsolute_scores(truth, prediction)predict_absolute(scores, query, alpha)
    异方差尺度scaled_scores(truth, prediction, scale)predict_scaled(scores, query, query_scale, alpha)
    CQR 分位数回归cqr_scores(truth, lower, upper)predict_cqr(scores, query_lower, query_upper, alpha)
    不对称尾tail_scores(truth, prediction)predict_tails(scores, query, alpha, low_fraction)
    多分类calibrate_classifier(rows, labels, Lac/Aps/Raps(lambda,k))model.predict(rows, alpha)、model.p_values(rows)
    固定组别 Mondriangrouped_scores(values, group_names)predict_grouped_absolute、groups.get(name)
    CV+cvplus_calibrate(truth, out_of_fold, fold_ids, folds)model.predict(test_by_fold, alpha)
    多输出联合矩形calibrate_vectors(truth, prediction, scales)model.predict(query, query_scales, alpha)
    自定义非一致性Scores::new(values)threshold、p_value、select_by_p_value
    评估evaluate_intervals、evaluate_setsevaluate_grouped_intervals、evaluate_vectors
    分辨率scores.resolution(alpha)minimum_calibration_count(alpha)、threshold_path、interval_path
    状态/结果交换to_snapshot / from_snapshotintervals_to_json/from_json、sets_to_json/from_json

    Scores、ClassCalibrator、GroupScores 的状态不可变,数组访问返回副本。DataSplit 检查训练/校准/评估索引唯一且互斥,并按原有顺序选择值;它不能检查外部训练器是否实际遵守划分。

    分类示例(放在 try 块内,完整可运行入口见 examples/classification/main.mbt):

    let model = @conformal.calibrate_classifier(
    [[0.8, 0.2], [0.6, 0.4], [0.7, 0.3], [0.9, 0.1]],
    [0, 0, 0, 0],
    @conformal.Lac,
    )
    let sets = model.predict([[0.7, 0.3]], 0.2)
    println(@conformal.sets_to_json(sets, 2)) // rows=[[0]]

    #统计语义与限制

    alpha 是失覆盖水平,严格在 (0,1) 内。split 方法要求模型、尺度、分组规则与超参数选择独立于校准标签,并要求校准样本和待预测样本满足适用的可交换性条件;本库验证数值和形状,不能验证数据生成过程。数据依赖调参、分布漂移、时间相关性或反复选择最有利 alpha 会破坏通常的覆盖解释。经验评估不构成理论保证。

    非一致性越大越不匹配。校正秩在精确实数下为 ceil((n+1)*(1-alpha));实现直接比较离散网格 k/(n+1) 与输入 Double,避免 1-alpha 抵消使边界偏一。秩 n+1 表示正无穷。保守 p 值为 (1 + count(calibration_score >= candidate))/(n+1),所有并列值都计入;集合选择 p > alpha,p 值不是类别概率。

    CQR 的 max(lower-y, y-upper) 可为负,不截零;校正后端点交叉表示合法空集合。非对称方法将总 alpha 分配给两尾,用联合界解释总失覆盖;乘法下溢为零会明确报错。

    APS 按概率递减、标签索引递增排序,分数包含候选本身的完整概率;没有随机化或边界标签强制包含。RAPS 加上 lambda*max(rank-k,0)。该确定性约定适合保守集合,不能假装与 MAPIE 所有随机化/边界处理选项逐项相同。校准和预测必须使用相同规则、类别语义和列顺序。概率和允许绝对误差 1e-10,不自动重归一化。

    Mondrian 在固定组内使用该组的 n;未知组报 UnknownGroup,小组允许无界输出。CV+ 的 test_by_fold[j][f] 必须来自训练时排除第 f 折的模型 f;端点采用下 floor((n+1)*alpha)、上 ceil((n+1)*(1-alpha)) 的离散网格对称构造,要求 alpha<0.5。CV+/jackknife+ 文献的保证随算法和折条件而异,本库不宣称 CV+ 具有精确的 1-alpha 有限样本覆盖,也不自动重训模型。

    向量方法用每行最大归一化误差校准联合矩形。不能把各维单独 90% 区间称为联合 90%。不支持在线/加权移位保形、随机 APS、模型训练、缺失值插补或任意数据上的逐点条件覆盖保证。

    #错误、资源限制与交换格式

    公开失败为 ConformalError:Invalid(message)、ResourceLimit(message)、UnknownGroup(name)。非有限输入、形状不匹配、非正尺度、非法标签、交叉的基础分位数、计算溢出均报错。Interval 公开字段允许手工构造,评估/导出入口会再验证端点;lower>upper 是空集合,宽度为零。

    默认单向量最多 100000 项、类别 2..1024、分组最多 10000、矩阵最多 2000000 单元。CV+ 每批最多 2000000 对候选端点;分辨率路径最多 1000 个 alpha。JSON 导入最多 4000000 UTF-16 代码单元、嵌套深度 12,拒绝重复键、未知字段、越界版本和非有限数值。导出上限遵循元素预算,大型导出可能超过导入文本预算,服务应分批传输。快照不是真实性签名,不能证明校准来源未泄漏。

    状态快照 schema 为 1,类型为 scores、classifier 或 groups。预测 JSON schema 为 1,类型为 intervals 或 sets,无穷端点用字符串 "-inf" / "+inf",不使用 NaN/null 替换。集合保留标签索引;类别名称由调用方管理。

    #开发与验证

    moon info moon fmt moon fmt --check moon check --deny-warn moon test --deny-warn moon build --deny-warn python3 tools/oracle.py --target wasm-gc

    每个目标均可用 --target wasm-gc|wasm|js|native。CI 对四个目标执行格式、严格检查、测试、构建、三个工作流和独立 Python 对照。Python 仅用于开发验收;Python 3.8 以上、仅标准库,发布库不依赖 Python。python3 tools/oracle.py --generate 可重建确定性输入,不生成参考答案。

    独立对照含 320 个秩/阈值/p 值样本(含并列、网格边界和相邻 Double)、12 个分类规则样本,以及 CQR、尺度、CV+、向量四个集成结果,共 336 项。数值比较容差为 1e-12。该对照是公式级独立枚举与另一种分类评分构造,不冒充对 MAPIE 所有功能的兼容认证。测试另覆盖无界/空集、非法输入、状态副本、JSON 损坏和索引泄漏检查。

    #设计、来源与许可

    本项目为原创实现,公开参考包括 MAPIE(BSD-3-Clause)和保形预测论文;没有复制其源码、测试数据或文档段落。仅依赖 MoonBit 标准核心库(随官方工具链提供,Apache-2.0)。本仓库采用 MIT,见 LICENSE。设计、实现、测试、文档和调试使用了 AI 编码辅助;交付内容通过可复现命令验证,统计适用条件由使用者核对。

    ConformalError

    pub(all) suberror ConformalError {
    Invalid(String)
    ResourceLimit(String)
    UnknownGroup(String)
    } derive(
    Debug
    )

    Stable input failures. No invalid input silently falls back to another method.

    CalibrationResolution

    pub(all) struct CalibrationResolution {
    count : Int
    smallest_p_value : Double
    rank : Int
    finite_threshold : Bool
    threshold : Double
    tied_at_threshold : Int
    }

    Explains finite-sample resolution before selecting a model or confidence level.

    ClassCalibrator

    pub struct ClassCalibrator {
    // private fields
    }

    ClassCalibrator::class_count

    fn ClassCalibrator::class_count(self : ClassCalibrator) -> Int

    ClassCalibrator::count

    fn ClassCalibrator::count(self : ClassCalibrator) -> Int

    ClassCalibrator::from_snapshot

    fn ClassCalibrator::from_snapshot(text : String) -> ClassCalibrator raise ConformalError

    Restores only validated states; it cannot certify how calibration data was obtained.

    ClassCalibrator::p_values

    fn ClassCalibrator::p_values(self : ClassCalibrator, rows : Array[Array[Double]]) -> Array[Array[Double]] raise ConformalError

    Conservative label p-values retain ties; they are not class probabilities.

    ClassCalibrator::predict

    fn ClassCalibrator::predict(self : ClassCalibrator, rows : Array[Array[Double]], alpha : Double) -> Array[Array[Int]] raise ConformalError

    ClassCalibrator::to_snapshot

    fn ClassCalibrator::to_snapshot(self : ClassCalibrator) -> String

    The rule and class count are necessary to reproduce prediction sets.

    CvPlus

    pub struct CvPlus {
    // private fields
    }

    Frozen out-of-fold absolute residuals and their held-out model identifiers.

    CvPlus::count

    fn CvPlus::count(self : CvPlus) -> Int

    CvPlus::fold_count

    fn CvPlus::fold_count(self : CvPlus) -> Int

    CvPlus::predict

    fn CvPlus::predict(self : CvPlus, test_by_fold : Array[Array[Double]], alpha : Double) -> Array[Interval] raise ConformalError

    This implements the endpoint construction; it does not promise exact 1-alpha coverage.

    DataSplit

    pub struct DataSplit {
    // private fields
    }

    This validates supplied indices, not the internals of an external model.

    DataSplit::calibration_values

    fn DataSplit::calibration_values(self : DataSplit, values : Array[Double]) -> Array[Double] raise ConformalError

    Selects just the held-out calibration values in the explicitly supplied order.

    DataSplit::evaluation_values

    fn DataSplit::evaluation_values(self : DataSplit, values : Array[Double]) -> Array[Double] raise ConformalError

    DataSplit::indices

    fn DataSplit::indices(self : DataSplit) -> (Array[Int], Array[Int], Array[Int])

    Copies make the role validation stable after callers mutate their inputs.

    DataSplit::sizes

    fn DataSplit::sizes(self : DataSplit) -> (Int, Int, Int)

    GroupIntervalMetrics

    pub(all) struct GroupIntervalMetrics {
    group : String
    metrics : IntervalMetrics
    }

    One empirical report per fixed group; group order is deterministic.

    GroupScores

    pub struct GroupScores {
    // private fields
    }

    Fixed groups must be determined without calibration outcomes.

    GroupScores::from_snapshot

    fn GroupScores::from_snapshot(text : String) -> GroupScores raise ConformalError

    Applies one global score budget, in addition to per-group validation.

    GroupScores::get

    fn GroupScores::get(self : GroupScores, group : String) -> Scores raise ConformalError

    GroupScores::names

    fn GroupScores::names(self : GroupScores) -> Array[String]

    Sorted defensive copy, independent of hash iteration order.

    GroupScores::thresholds

    fn GroupScores::thresholds(self : GroupScores, alpha : Double) -> Array[(String, Double)] raise ConformalError

    Each group's own n is used for the finite-sample correction.

    GroupScores::to_snapshot

    fn GroupScores::to_snapshot(self : GroupScores) -> String raise ConformalError

    Entries are sorted by group name so exports are reproducible.

    Interval

    pub(all) struct Interval {
    lower : Double
    upper : Double
    }

    A prediction set on the real line. lower > upper is a valid empty set.

    Interval::contains

    fn Interval::contains(self : Interval, value : Double) -> Bool

    Interval::intersect

    fn Interval::intersect(self : Interval, other : Interval) -> Interval

    Interval::is_empty

    fn Interval::is_empty(self : Interval) -> Bool

    Interval::width

    fn Interval::width(self : Interval) -> Double

    IntervalMetrics

    pub(all) struct IntervalMetrics {
    count : Int
    covered : Int
    empty : Int
    unbounded : Int
    mean_width : Double
    coverage : Double
    }

    Empirical diagnostics, not estimates of a universal conditional guarantee.

    Scores

    pub struct Scores {
    // private fields
    }

    Frozen sorted calibration values. The underlying array is never returned.

    Scores::count

    fn Scores::count(self : Scores) -> Int

    Scores::from_snapshot

    fn Scores::from_snapshot(text : String) -> Scores raise ConformalError

    Rejects unknown fields, duplicate keys, nonfinite numbers and resource excess.

    Scores::new

    fn Scores::new(values : Array[Double]) -> Scores raise ConformalError

    Accepts signed scores (needed by conformalized quantile regression).

    Scores::p_value

    fn Scores::p_value(self : Scores, score : Double) -> Double raise ConformalError

    Conservative deterministic upper-tail p-value; includes all ties and +1.

    Scores::resolution

    fn Scores::resolution(self : Scores, alpha : Double) -> CalibrationResolution raise ConformalError

    Scores::threshold

    fn Scores::threshold(self : Scores, alpha : Double) -> Double raise ConformalError

    Scores::to_snapshot

    fn Scores::to_snapshot(self : Scores) -> String

    Scores are finite, signed, and sorted; alpha is chosen at prediction time.

    Scores::values

    fn Scores::values(self : Scores) -> Array[Double]

    Defensive copy: mutating this result cannot alter a fitted calibrator.

    SetMetrics

    pub(all) struct SetMetrics {
    count : Int
    covered : Int
    empty : Int
    singletons : Int
    mean_size : Double
    coverage : Double
    singleton_accuracy : Double?
    }

    SetRule

    pub(all) enum SetRule {
    Lac
    Aps
    Raps(Double, Int)
    }

    TailScores

    pub struct TailScores {
    // private fields
    }

    Separate tail calibration, using a union bound for the requested total error.

    TailScores::count

    fn TailScores::count(self : TailScores) -> Int

    VectorCalibrator

    pub struct VectorCalibrator {
    // private fields
    }

    Simultaneous rectangular sets from one maximum normalized score per row.

    VectorCalibrator::count

    fn VectorCalibrator::count(self : VectorCalibrator) -> Int

    VectorCalibrator::dimension

    fn VectorCalibrator::dimension(self : VectorCalibrator) -> Int

    VectorCalibrator::predict

    fn VectorCalibrator::predict(self : VectorCalibrator, prediction : Array[Array[Double]], scale : Array[Array[Double]], alpha : Double) -> Array[Array[Interval]] raise ConformalError

    Each returned row is a rectangle, with joint rather than marginal calibration.

    VectorMetrics

    pub(all) struct VectorMetrics {
    count : Int
    dimensions : Int
    covered : Int
    coverage : Double
    per_dimension : Array[IntervalMetrics]
    }

    Simultaneous coverage: a row is covered only when every coordinate is covered.

    absolute_scores

    fn absolute_scores(truth : Array[Double], prediction : Array[Double]) -> Scores raise ConformalError

    Absolute-residual split conformal calibration, independent of model training.

    calibrate_classifier

    fn calibrate_classifier(rows : Array[Array[Double]], labels : Array[Int], rule : SetRule) -> ClassCalibrator raise ConformalError

    calibrate_vectors

    fn calibrate_vectors(truth : Array[Array[Double]], prediction : Array[Array[Double]], scale : Array[Array[Double]]) -> VectorCalibrator raise ConformalError

    Scales must be fixed or trained independently of calibration outcomes.

    conformal_rank

    fn conformal_rank(n : Int, alpha : Double) -> Int raise ConformalError

    One-based finite-sample rank. n+1 deliberately denotes positive infinity.

    cqr_scores

    fn cqr_scores(truth : Array[Double], lower : Array[Double], upper : Array[Double]) -> Scores raise ConformalError

    Signed CQR score max(lower-y, y-upper); negative scores must not be clipped.

    cvplus_calibrate

    fn cvplus_calibrate(truth : Array[Double], out_of_fold : Array[Double], fold : Array[Int], folds : Int) -> CvPlus raise ConformalError

    Every row's prediction must come from a model trained without that row.

    data_split

    fn data_split(size : Int, train : Array[Int], calibration : Array[Int], evaluation : Array[Int]) -> DataSplit raise ConformalError

    Optional unused rows are allowed; all three supplied roles must be disjoint.

    evaluate_grouped_intervals

    fn evaluate_grouped_intervals(truth : Array[Double], intervals : Array[Interval], groups : Array[String]) -> Array[GroupIntervalMetrics] raise ConformalError

    evaluate_intervals

    fn evaluate_intervals(truth : Array[Double], intervals : Array[Interval]) -> IntervalMetrics raise ConformalError

    Count infinities separately; an unbounded set makes mean_width infinite.

    evaluate_sets

    fn evaluate_sets(truth : Array[Int], sets : Array[Array[Int]], classes : Int) -> SetMetrics raise ConformalError

    evaluate_vectors

    fn evaluate_vectors(truth : Array[Array[Double]], rectangles : Array[Array[Interval]]) -> VectorMetrics raise ConformalError

    grouped_scores

    fn grouped_scores(values : Array[Double], groups : Array[String]) -> GroupScores raise ConformalError

    Keeps small groups separate; never silently pools an unseen group.

    interval_path

    fn interval_path(scores : Scores, prediction : Double, levels : Array[Double]) -> Array[Interval] raise ConformalError

    Nonempty alpha grid in caller order; no sorting or deduplication changes semantics.

    interval_score

    fn interval_score(interval : Interval, truth : Double, alpha : Double) -> Double raise ConformalError

    Winkler/interval score for nonempty intervals; empty sets have no such score.

    intervals_from_json

    fn intervals_from_json(text : String) -> Array[Interval] raise ConformalError

    Same bounded strict parser as calibration snapshots.

    intervals_to_json

    fn intervals_to_json(intervals : Array[Interval]) -> String raise ConformalError

    Valid empty sets retain their endpoints; unbounded sets do not become null.

    label_scores

    fn label_scores(row : Array[Double], rule : SetRule) -> Array[Double] raise ConformalError

    Returns one score per candidate label, in label-index order.

    minimum_calibration_count

    fn minimum_calibration_count(alpha : Double) -> Int raise ConformalError

    Smallest n within the supported budget admitting a finite corrected threshold.

    predict_absolute

    fn predict_absolute(scores : Scores, prediction : Array[Double], alpha : Double) -> Array[Interval] raise ConformalError

    predict_cqr

    fn predict_cqr(scores : Scores, lower : Array[Double], upper : Array[Double], alpha : Double) -> Array[Interval] raise ConformalError

    predict_grouped_absolute

    fn predict_grouped_absolute(scores : GroupScores, prediction : Array[Double], groups : Array[String], alpha : Double) -> Array[Interval] raise ConformalError

    predict_scaled

    fn predict_scaled(scores : Scores, prediction : Array[Double], scale : Array[Double], alpha : Double) -> Array[Interval] raise ConformalError

    predict_tails

    fn predict_tails(scores : TailScores, prediction : Array[Double], alpha : Double, low_fraction : Double) -> Array[Interval] raise ConformalError

    low_fraction allocates total alpha to the lower-tail failure event.

    scaled_scores

    fn scaled_scores(truth : Array[Double], prediction : Array[Double], scale : Array[Double]) -> Scores raise ConformalError

    The scale predictor must be fitted without seeing calibration labels.

    select_by_p_value

    fn select_by_p_value(calibration : Scores, candidates : Array[Double], alpha : Double) -> Array[Int] raise ConformalError

    Generic score calibration for a user-defined, fixed nonconformity function. Selection is p > alpha. Equality at the significance threshold is excluded.

    sets_from_json

    fn sets_from_json(text : String) -> (Int, Array[Array[Int]]) raise ConformalError

    Returns class vocabulary size with the validated prediction sets.

    sets_to_json

    fn sets_to_json(sets : Array[Array[Int]], classes : Int) -> String raise ConformalError

    Label sets have a schema and a fixed class vocabulary size.

    tail_scores

    fn tail_scores(truth : Array[Double], prediction : Array[Double]) -> TailScores raise ConformalError

    threshold_path

    fn threshold_path(scores : Scores, alphas : Array[Double]) -> Array[Double] raise ConformalError

    Query nested real-line radii without refitting or changing calibration state.