Skip to content

Support monotone feature constraints in the distilled whitebox #8

Description

@orgoca

Problem

Credit policy and model risk management routinely require monotone relationships: more delinquencies must never decrease estimated risk. Teachers can enforce this (monotone_constraints in XGBoost/LightGBM, monotonic_cst in sklearn's HistGradientBoostingRegressor).

CompileML's whitebox cannot. train_whitebox uses GradientBoostingRegressor, which does not accept monotonic_cst (verified on sklearn 1.8). So even when the teacher is monotone, the distilled student may not be — and nothing detects it.

This is made worse by our own scorecard feature: build_scorecard will happily print a bin table where 3 late payments scores better than 2. That is a scorecard a validation committee rejects on sight, and we currently make the violation more visible without offering a way to prevent it.

Note the naming collision to avoid: everything in compileml.bands called "monotone" refers to band monotonicity (bad rates rising across bands). This issue is about feature monotonicity, a different property.

Proposed approach

  1. Switch train_whitebox to HistGradientBoostingRegressor, or add it as a selectable backend, so monotonic_cst can be passed through.
  2. Accept monotone_constraints={"BILLS_PAID_LATE": +1, "INCOME": -1} (or a positional array) on train_whitebox and build_artifact.
  3. Record the constraints in the artifact under runtime or metadata so they travel with the decision.
  4. Add a validation check that verifies the compiled scorecard is monotone in every constrained feature — checking the emitted bins, not just trusting the trainer.

Acceptance criteria

  • A constrained feature produces a scorecard whose points are monotone across its bins, asserted in a test
  • Constraints are recorded in the artifact and surfaced by compileml inspect
  • validate_artifact fails when a recorded constraint is violated in the compiled model
  • The extractor still round-trips (HistGradientBoostingRegressor uses a different internal tree structure than GradientBoostingRegressorcompileml/compile/extract.py will need a branch)

Files

src/compileml/compile/distill.py, src/compileml/compile/extract.py, src/compileml/artifact/build.py, src/compileml/validate/framework.py, src/compileml/scorecard/build.py

Scope

Substantial. The extractor work is the non-obvious part: HistGradientBoostingRegressor stores binned thresholds and needs its own extraction path with the same parity gate the existing extractors have.

Metadata

Metadata

Assignees

No one assigned

    Labels

    credit-riskDomain requirement from regulated lending practiceenhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions