Learn¶
This package learns the data checks' settings (reconstruction margin,
knife-edge window prefactor, conformal alpha, support minimum,
optionally the decision line, and the calibration scores) from labelled
history, with a certified error rate and a provenance record. The grid
is tested by fixed-sequence testing by default (procedure= selects
"bonferroni" instead), and HistoricalCase.was_acted_on lets the
learner warn about selective labels. It also provides the
exact binomial statistics behind the certificate and RiskMonitor for
watching the live error rate afterwards. Open this page when you call
learn_parameters, load a params.json, or monitor learned settings.
Everything below is importable from grounded_decision.learn. Recipe:
Learn parameters from your history;
the method and its guarantee:
Learning settings with a guarantee.
learn ¶
Learn decision parameters from labelled history, with provenance.
See :mod:grounded_decision.learn.calibrate for the method and its
guarantee, and :mod:grounded_decision.learn.monitor for watching the
live error rate afterwards.
Names are imported on first use (PEP 562), so a module that needs only
:mod:~grounded_decision.learn.statistics (the games' regimes) does not
import numpy.
Procedure
module-attribute
¶
How Learn-then-Test controls the chance of certifying a bad setting.
"fixed_sequence" (default): test the settings one at a time in an
order fixed on the calibration part, each at the full significance, and
stop at the first failure. "bonferroni": test every setting at the
significance divided by the grid size. Both are valid; see
:func:learn_parameters.
CandidateTest
dataclass
¶
CandidateTest(
index: int,
reconstruction_margin: float,
window_prefactor: float,
decision_line: float | None,
conformal_alpha: float,
minimum_support: float,
acts: int,
errors: int,
trials: int,
p_value: float,
order: int,
tested: bool,
certified: bool,
)
One combination of settings and its exact test on the risk part.
What :func:learning_run reports for every combination of the grid,
so a reader (or a chart) can see why the learner chose what it chose.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
Position in the grid order ( |
reconstruction_margin |
float
|
The combination's margin. |
window_prefactor |
float
|
Its knife-edge window prefactor. |
decision_line |
float | None
|
Its decision line ( |
conformal_alpha |
float
|
Its conformal alpha. |
minimum_support |
float
|
Its minimum support. |
acts |
int
|
Risk-part cases these settings would act on. |
errors |
int
|
Risk-part acts that were wrong. |
trials |
int
|
What the risk measure divides by: the acts
( |
p_value |
float
|
Exact p-value for "the error rate is at least
|
order |
int
|
Position in the testing order (0 is tested first): the
calibration-part order for |
tested |
bool
|
Whether the procedure tested it (the fixed sequence stops at its first failure; Bonferroni tests every combination). |
certified |
bool
|
Whether its error rate was certified below the target. |
CaseArrays
dataclass
¶
CaseArrays(
evidence_strength: NDArray[float64],
entity_count: NDArray[float64],
link_density: NDArray[float64],
own_decision_line: NDArray[float64],
conformal_p_value: NDArray[float64],
support: NDArray[float64],
wrong: NDArray[bool_],
)
Per-case quantities the four data checks need, as numpy arrays.
support is NaN where the evidence has no support measurement;
such cases pass the support check at every minimum.
HistoricalCase
dataclass
¶
HistoricalCase(
evidence: EvidenceSnapshot,
proposed_action: str,
action_was_correct: bool,
case_id: str = "",
was_acted_on: bool | None = None,
)
One past decision whose outcome is now known.
Attributes:
| Name | Type | Description |
|---|---|---|
evidence |
EvidenceSnapshot
|
The evidence available when the decision was made. Its
|
proposed_action |
str
|
What the policy proposed ( |
action_was_correct |
bool
|
Whether that action turned out to be right, from the review outcome, chargeback, follow-up, and so on. |
case_id |
str
|
Optional identifier, kept for audit only. |
was_acted_on |
bool | None
|
Whether the proposed action was actually carried out
( |
from_dict
classmethod
¶
from_dict(data: Mapping[str, Any]) -> HistoricalCase
Read one case, e.g. one line of a JSON Lines history export.
Source code in src/grounded_decision/learn/calibrate.py
LearnedParameters
dataclass
¶
LearnedParameters(
status: str,
reconstruction_margin: float,
operational_fk_prefactor: float,
conformal_alpha: float,
decision_line: float | None,
calibration_scores: Mapping[str, tuple[float, ...]],
target: RiskTarget,
grid: ParameterGrid,
risk_split: SplitReport,
holdout: SplitReport,
configurations_tried: int,
configurations_certified: int,
minimum_risk_cases_needed: int,
dataset_fingerprint: str,
split_sizes: Mapping[str, int],
seed: int,
warnings: tuple[str, ...] = tuple(),
minimum_support: float = 0.0,
procedure: Procedure = "fixed_sequence",
)
Settings learned from history, with the record of how.
Use :meth:limits to build the Limits for your Module and
:meth:apply to complete each request's evidence (calibration scores
and decision line). Persist :meth:to_dict next to your policy and
store :attr:fingerprint on every decision record.
minimum_support
class-attribute
instance-attribute
¶
Learned Limits.min_support; 0.0 means the support check is off.
procedure
class-attribute
instance-attribute
¶
procedure: Procedure = 'fixed_sequence'
The multiple-testing procedure that certified the settings.
limits ¶
Limits carrying the learned check settings.
The learner treats evidence without a support measurement as
passing the support check, as Module.decide does when
base.require_support is off (the default). Keep it off, or
measure support on every case, for the certificate to apply.
Source code in src/grounded_decision/learn/calibrate.py
apply ¶
apply(
evidence: EvidenceSnapshot, proposed_action: str
) -> EvidenceSnapshot
Fill in the learned calibration scores and decision line.
The calibration scores are chosen by proposed_action: a case is
compared only with past cases where that action was right.
Source code in src/grounded_decision/learn/calibrate.py
from_dict
classmethod
¶
from_dict(data: Mapping[str, Any]) -> LearnedParameters
Read :meth:to_dict output.
Files written before the support check and the procedure choice
existed load with minimum_support=0.0 (support check off), a
grid of minimum_supports=(0.0,), and procedure="bonferroni"
(the only procedure then).
Source code in src/grounded_decision/learn/calibrate.py
explain ¶
Plain-language lines describing what was learned and how far to trust it.
Source code in src/grounded_decision/learn/calibrate.py
LearningRun
dataclass
¶
LearningRun(
learned: LearnedParameters,
candidates: tuple[CandidateTest, ...],
significance_per_test: float,
chosen: int | None,
)
The learned settings with every combination's test (:func:learning_run).
Attributes:
| Name | Type | Description |
|---|---|---|
learned |
LearnedParameters
|
What :func: |
candidates |
tuple[CandidateTest, ...]
|
Every combination of the grid, in grid order. |
significance_per_test |
float
|
The level each p-value was compared with
( |
chosen |
int | None
|
The chosen combination's |
ParameterGrid
dataclass
¶
ParameterGrid(
reconstruction_margins: tuple[float, ...] = (
-1.0,
0.0,
0.05,
0.25,
0.5,
1.0,
2.0,
4.0,
),
window_prefactors: tuple[float, ...] = (
0.0,
0.5,
1.0,
2.0,
4.0,
),
decision_lines: tuple[float | None, ...] = (None,),
conformal_alphas: tuple[float, ...] = (
0.0,
0.02,
0.05,
0.1,
0.2,
),
minimum_supports: tuple[float, ...] = (
0.0,
0.5,
0.6,
0.7,
0.8,
0.9,
),
)
The candidate settings Learn-then-Test chooses among.
Each tuple lists candidate values for one setting. The defaults include
a value that switches each check off (margin -1.0 requires only
some agreement, prefactor 0.0 gives an empty window, alpha
0.0 never rejects, minimum support 0.0 always passes), so the
learner can find that a check does not help on your data. Larger grids
find better settings; with the "bonferroni" procedure they also
need more history, because the certificate is divided across every
combination.
minimum_supports are candidate Limits.min_support values for
the support check. Cases whose evidence has no support measurement
pass it at every value (as Module.decide does when
require_support is off).
decision_lines are candidate p_threshold values; None keeps
each case's own p_threshold. Use :func:decision_line_candidates
to propose values from your data.
combinations ¶
Every (margin, prefactor, decision_line, alpha, minimum_support).
In a fixed order (the grid order) that ties are broken by.
Source code in src/grounded_decision/learn/calibrate.py
RiskMeasure ¶
RiskTarget
dataclass
¶
RiskTarget(
max_risk: float,
confidence: float = 0.95,
measure: RiskMeasure = ERROR_RATE_AMONG_ACTS,
)
The risk you are willing to accept. This is the business input.
Attributes:
| Name | Type | Description |
|---|---|---|
max_risk |
float
|
Largest acceptable error rate, e.g. |
confidence |
float
|
How sure the certificate must be, e.g. |
measure |
RiskMeasure
|
Which error rate |
SplitReport
dataclass
¶
How the chosen settings did on one part of the history.
MonitorStatus ¶
Bases: StrEnum
INSUFFICIENT_DATA
class-attribute
instance-attribute
¶
Too few outcomes in the window to say anything.
WITHIN_TARGET
class-attribute
instance-attribute
¶
The recent error rate is confidently at or below the target.
UNCERTAIN
class-attribute
instance-attribute
¶
Neither confidently below nor confidently above the target.
ABOVE_TARGET
class-attribute
instance-attribute
¶
The recent error rate is confidently above the target: re-learn now.
RiskMonitor
dataclass
¶
RiskMonitor(target: RiskTarget, window: int = 500)
Rolling check of the realised error rate against a RiskTarget.
Record each decision once its outcome is known. status() reports
ABOVE_TARGET when even the optimistic (lower) confidence bound on
the recent error rate exceeds the target: that is the signal to stop
acting automatically and re-learn.
Repeatedly checking a fixed-confidence bound raises the chance of a
false alarm over time; treat a single ABOVE_TARGET as a reason to
investigate and re-learn, not as proof.
Attributes:
| Name | Type | Description |
|---|---|---|
target |
RiskTarget
|
The target the settings were learned for. |
window |
int
|
How many recent relevant outcomes to keep. |
case_arrays ¶
case_arrays(
cases: Sequence[HistoricalCase],
calibration: Mapping[str, tuple[float, ...]],
) -> CaseArrays
Pre-compute what the checks need for cases.
Pair with :func:predicted_acts to evaluate any settings on your own
history, for example to compare learned settings with hand-set ones.
Source code in src/grounded_decision/learn/calibrate.py
dataset_fingerprint ¶
dataset_fingerprint(cases: Sequence[HistoricalCase]) -> str
Stable hash identifying exactly which history was learned from.
Source code in src/grounded_decision/learn/calibrate.py
decision_line_candidates ¶
decision_line_candidates(
cases: Sequence[HistoricalCase], count: int = 5
) -> tuple[float | None, ...]
Candidate decision lines: quantiles of the observed link density.
Returns None (keep each case's own line) followed by count
evenly spaced quantiles of p_hat. Pass the result as
ParameterGrid(decision_lines=...).
Source code in src/grounded_decision/learn/calibrate.py
learn_parameters ¶
learn_parameters(
cases: Sequence[HistoricalCase],
target: RiskTarget,
*,
grid: ParameterGrid | None = None,
calibration_fraction: float = 0.3,
holdout_fraction: float = 0.2,
seed: int = 0,
procedure: Procedure = "fixed_sequence",
) -> LearnedParameters
Learn check settings from labelled history with a certified error rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cases
|
Sequence[HistoricalCase]
|
Past decisions with known outcomes. Only cases where the
policy proposed an action belong here. Set
|
required |
target
|
RiskTarget
|
The error rate you accept and how sure the certificate must be. |
required |
grid
|
ParameterGrid | None
|
Candidate settings; defaults to :class: |
None
|
calibration_fraction
|
float
|
Share of cases used to build calibration
scores (and, for |
0.3
|
holdout_fraction
|
float
|
Share kept aside to report honest performance. The rest is the risk part used to certify settings. |
0.2
|
seed
|
int
|
Seed for the random split, recorded for reproducibility. |
0
|
procedure
|
Procedure
|
|
'fixed_sequence'
|
Returns:
| Type | Description |
|---|---|
LearnedParameters
|
The learned settings and their provenance. Check |
LearnedParameters
|
is |
Why both procedures are valid. Write delta = 1 - confidence. Fix
the calibration part; the calibration scores, and so the true error
rate R_k of every combination k, are then fixed numbers, and
the risk-part cases are independent draws unrelated to them. For each
k the null hypothesis H_k: R_k > max_risk has an exact binomial
p-value p_k with P(p_k <= u) <= u whenever H_k is true. A
combination is certified when its H_k is rejected; the chosen
combination is a certified one, so it suffices that the chance of
rejecting any true null (the family-wise error) is at most
delta.
"bonferroni"rejectsH_kwhenp_k <= delta / K(Kthe grid size). The family-wise error is at most the sum over true nulls ofP(p_k <= delta / K), which is at mostK * delta / K = delta."fixed_sequence"orders the combinations using the calibration part only (most clearly safe there first, ties strictest first; see_testing_sequence), so the order is independent of the risk part. It rejectsH_{(1)}, H_{(2)}, ...in that order whilep_{(j)} <= deltaand stops at the first failure. Letj0be the position of the first true null in the order. Rejecting any true null requires rejecting every hypothesis before it, in particularH_{(j0)}, which happens with probability at mostdelta.
Choosing, among certified combinations, the one that acts most often
on the risk part does not affect either argument, which bounds the
chance that any certified combination is bad. Fixed-sequence testing
pays no division by K, so it certifies more (and looser)
combinations when the order puts safe combinations first; it loses
power when an unsafe or under-powered combination sits early in the
order, which is why the order is estimated from the calibration part
rather than taken from act rate alone.
Source code in src/grounded_decision/learn/calibrate.py
learning_run ¶
learning_run(
cases: Sequence[HistoricalCase],
target: RiskTarget,
*,
grid: ParameterGrid | None = None,
calibration_fraction: float = 0.3,
holdout_fraction: float = 0.2,
seed: int = 0,
procedure: Procedure = "fixed_sequence",
) -> LearningRun
:func:learn_parameters, also reporting every combination's test.
Takes the same arguments and makes the same choice (it is the code
:func:learn_parameters runs); the extra result is one
:class:CandidateTest per combination of the grid, with its counts on
the risk part, its exact p-value, its place in the testing order and
whether it was tested and certified. Combinations the fixed sequence
never reached still get their p-value, marked tested=False.
Source code in src/grounded_decision/learn/calibrate.py
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 | |
predicted_acts ¶
predicted_acts(
arrays: CaseArrays,
*,
margin: float,
prefactor: float,
decision_line: float | None,
alpha: float,
minimum_support: float = 0.0,
) -> NDArray[bool_]
Which cases all four data checks would pass under these settings.
Mirrors Module.decide with Limits(reconstruction_margin=margin,
operational_fk_prefactor=prefactor, conformal_alpha=alpha,
min_support=minimum_support) and require_support off; the
certificate and game checks depend only on the policy, not on these
settings. minimum_support=0.0 (the default) switches the support
check off.
Source code in src/grounded_decision/learn/calibrate.py
binomial_cdf ¶
P(X <= successes) for X ~ Binomial(trials, probability).
Computed exactly and rounded to the nearest float, so it is the same on every platform.
Source code in src/grounded_decision/learn/statistics.py
lower_confidence_bound ¶
One-sided Clopper–Pearson lower bound on an error rate.
With probability at least confidence the true rate is at or above
the returned value. Returns 0.0 when there were no errors or trials.
The value is the largest :data:BOUND_SIGNIFICANT_DIGITS-digit
decimal (as a float) at which the exact upper binomial tail is at
most 1 - confidence, so it is at or below the exact bound and the
same on every platform.
Source code in src/grounded_decision/learn/statistics.py
minimum_trials_to_certify ¶
Fewest error-free decisions that could certify max_risk.
Even a perfect record of n decisions certifies an error rate below
max_risk only if (1 - max_risk) ** n <= significance /
configurations. Use this to tell a team how much labelled history
it needs before learning can succeed.
Source code in src/grounded_decision/learn/statistics.py
p_value_risk_below ¶
Exact p-value for "the true error rate is at least max_risk".
A small value is evidence that the error rate is below max_risk.
It is the probability of seeing errors or fewer mistakes in
trials independent decisions if the error rate were exactly
max_risk (the least favourable case of the hypothesis), computed
exactly and rounded up to a float, so comparing it with a level
never certifies more than the exact value would.
Returns 1.0 when there were no trials: nothing was observed, so nothing can be certified.
Source code in src/grounded_decision/learn/statistics.py
upper_confidence_bound ¶
One-sided Clopper–Pearson upper bound on an error rate.
With probability at least confidence the true rate is at or below
the returned value. Returns 1.0 when there were no trials. The value
is the smallest :data:BOUND_SIGNIFICANT_DIGITS-digit decimal (as a
float) at which the exact binomial tail is at most 1 -
confidence, so it is at or above the exact bound and the same on
every platform.