- Use a
booljudgment with the defaulthorizonof0s. Ask a narrow question about what the record shows. Keep counts asfeatures. - Read related records with
last_n, notwindow. - Write one period of history, and wait until each of its answers exists.
- For each judged document the period touched, post one labelled example: what happened in the next period, with
observed_atnow. - Do the next period. When the replay is done, let the fit run, and read the report.
- Compare
featureswithfeatures_onlyin the report. The difference is what the engine adds to your counts.
Labelled examples, not a horizon
A labelled example joins the answer that was current at itsobserved_at. A label dated after a period’s answers exist, and before the next period’s writes, joins the answer for that period. That is the backtest.
You cannot backtest a judgment with a horizon. Its outcome window opens when Brussle makes the answer and closes one horizon later. Brussle dates each write when it receives it and each answer when it makes it, and nothing dates a write or an answer in the past (created_at dates only a document’s creation). Each replayed answer opens its window now, and an outcome from your history falls before it. To measure a horizon judgment, wait for the horizon to pass. For a backtest, put the next period in the label and keep horizon at 0s.
The engine answers well about what a record shows now, and near chance about what will happen. Ask it a narrow question about the record as it is, such as “Do this machine’s recent reports describe a fault that stops it?”, and keep the counts of reports as features. The label says whether the machine failed in the next quarter. The counts and the fit on your labels carry the number for a failure. See ask about what will happen.
Read each record’s past, not the time of the replay
A replay writes all of your history now. Send each record’s originalcreated_at, so relations sort your records in the order they happened. The two bounds of a relation then behave differently:
last_nreads bycreated_at. Withlast_n: 10, a machine reads the 10 newest reports that exist at that point of the replay. Uselast_n.windowcounts back from the time of a write: the later of the newest write to the judged document, as Brussle received it, and the newestcreated_atamong its related documents. In a replay, the newest write is now, so awindowof365dholds no record created more than 365 days before the replay, and early periods read nothing. Do not usewindowon a replay. See newest created first.
last_n (up to 1,000) and aggregate, and name its numbers in features. "render": false keeps them out of what the engine reads.
A blocking relation reads the newest records of its block. In a replay a whole period arrives at once, so a record early in the period would read the records after it, which it could not have seen. Add "relative": "before": each record then reads the last_n records just before it by created_at, oldest first. A relation with relative takes no window and no order.
Run a backtest
This example backtests a judgment on machines and their service reports. Each report has its machine’sid in attributes.machine_id. The history covers several quarters, and each machine’s label is whether it failed in the next quarter.
1. Make a namespace for the backtest, and set a budget on it. A backtest is billed as live judging: each touched machine is judged again in each period.
2. Create the judgment before the first period, so each period is judged as you write it.
POST /v1/namespaces/acme%2Fbacktest/judgments
- If the create returns a replay estimate, nothing is created yet. In a new namespace the estimate is almost zero. Send the create again with
confirm: true. - Wait for its
reference_indexjobs before the first period. - A judgment with features has a debounce of 10 minutes by default. A short
debounce_msmakes each period’s answers arrive sooner.
Python
quarters,documents_created_in,machines_touched_in,failed_inandnext_quarterstand for your own code.- A post of outcomes is at most 2 MB. Split a large period into more posts.
Wait for each period’s answers
Before you write the next period, wait until each answer of this one exists. Otherwise a machine’s answer can include records of the next period, no answer shows the machine as it was at the end of this one, and your label joins an older answer.- Compare revisions, as in the example. The import returns the highest
revisionit wrote. When the judgment’sjudged_throughis at or above it, each machine the period touched has its answer, or its last attempt failed. A get of the judgment reads no documents. See is a judgment caught up?. - A pause stops the wait.
judged_throughdoes not pass a write that waits for judging to start again, or for the rolling limit. If the budget runs out, the loop waits at that period until you raise it, so set a budget high enough for every period. - Or poll a query for answers that are
pending, to see which machines are not judged yet. When it returns no row, the period is judged, or its answers wait and readstale. wait_foron a write waits for answers too, but does less: it waits only for the documents in that write, not for a machine whose reports you wrote; for at most 10 seconds; and it judges only the first 16 documents of the write at once. See limits.
Date each label
A label joins the answer that was current at itsobserved_at: the answer for the newest write that touched the document at that time, whether to the document itself or to one of its related records.
observed_atmust be at or after theupdated_atof the period’s last write that touched the document. If it is earlier, the label joins an older answer or none, and the report counts a label with no answer inunmatched_outcomes.observed_atnow, taken after the wait, meets this rule. Use a clock that is in sync.- Do not use the date from your history. It falls before the replay, so the label joins no answer.
One label for each answer
An answer keeps one label: the latestobserved_at counts, and of two with the same observed_at, the one posted last. The report counts each replaced label in unmatched_outcomes.same_window.
- Post one label for each touched document in each period. A document the period did not touch keeps its answer, so a second label for it replaces the earlier period’s label.
- To correct a label, post the new value as of now, before you write the next period. After the next period’s writes, a label as of now joins the newer answer.
Fit the labels
- While the current epoch has no fit, Brussle tries each hour. The first run after the labels meet the minimums, 50 with at least 15 of each kind, makes the fit. After that it runs each night.
- To fit now, select Fit now in the judgment’s Calibration section, or send
POST /namespaces/{ns}/judgments/{name}/calibration/fit, once an hour for each judgment. See fit now.
calibrated object and p stays the engine’s. With features, the fit applies from 100 labels with 20 of each kind, and then p is the corrected value, so expect it to move:
pchanges for each answer, with no new judging.- Documents can cross your thresholds on
p.GETon the judgment shows afit_changedwarning until you set a threshold again. Pick new ones with the threshold recommender. - The judgments that read this one are judged again and billed. The event
judgment.fit_appliedreports them.
Read the ranking
A question about a rare event has a low base rate: few machines fail in a quarter.accuracy then says little, because it counts p of 0.5 or more as true, and “no” for every machine is right for most of them.
Read the ranking numbers in each epoch’s held_out_metrics instead, for the engine’s p (raw) and for each fit (scalar, and with features features), on held-out outcomes: auroc, how well p puts the machines that failed above those that did not; and precision_at_5 and precision_at_10, the share of the top 5% and 10% by p that failed. See how well answers rank.
With features, features_only scores a fit on your features alone. If it scores as well as features, the counts carry the number and the engine adds nothing.
Compare the precision with base_rate. If 2% of machines failed and 20% of the top 5% by p failed, the top 5% held failures at 10 times the base rate in your history. This measures your history. It does not say how the next quarter will go.