Reruns arm live on the items of a frozen hr_benchmark() and compares
the live runs with the frozen ones item by item. The live rerun is itself
an hr_benchmark() of the arm, with the frozen benchmark's split and
fresh draws, and is returned in $live; it can be reported with
hr_report() or kept as the next frozen sample.
Usage
hr_drift_check(
frozen,
arm,
runs = 5,
alpha = 0.05,
alert = NULL,
client,
reference,
scorer = hr_scorer_exact(),
governor = NULL,
B = 2000,
seed = NULL
)
# S3 method for class 'hr_drift_check'
print(x, digits = 3, ...)Arguments
- frozen
An
hr_benchfromhr_benchmark()holding at least 5 runs ofarm: the frozen sample.- arm
The
hr_arm()to rerun, the same arm (by hash) that the frozen benchmark ran.- runs
The number of live runs, a whole number, at least 2, so that the live runs have replicate flips of their own. As many as the frozen runs compares like with like.
- alpha
The level of each test, in (0, 0.5): two-sided for (a), one-sided for (b).
- alert
The accuracy change that must not go unnoticed, a proportion in (0, 1), or
NULLfor no alert.- client
The client the live runs call; see
hr_benchmark().- reference
The
hr_reference()the frozen benchmark scored.- scorer
The scorer the frozen benchmark used; checked by name.
- governor
An
hr_governor()the live rerun is held to, orNULLto take the limits fromgr_options().- B
The number of cluster resamples, at least 100.
- seed
The seed of the live runs' private random stream and of the resamples.
NULLtakes the frozen benchmark's seed plus 1, so an arm that samples makes fresh draws rather than replaying the frozen ones.- x
An
hr_drift_check.- digits
Digits to print.
- ...
Ignored.
Value
An object of class hr_drift_check, a list with
arm,arm_hash,reference_hash,frozen_runs,live_runs,n_items,n_clusters,alpha,alert,B,seedWhat was compared and how.
itemsOne row per labelled item:
cluster,item,frozen_accuracy(p_i),live_accuracy(q_i),change("lost"when most frozen runs were right and most live runs wrong,"gained"the reverse,"worse"or"better"for a smaller move,"same"when p_i = q_i),frozen_outcomeandlive_outcome(the most common scored output, or the failure kind in angle brackets), andreplicate_flip(f_i),live_flip(h_i),cross_flip(g_i) andexcess(e_i).changecompares shares of runs only: on an item whose frozen or live runs disagree among themselves (replicate_fliporlive_flipabove 0), a move can be ordinary run-to-run variation, and only test (b) says whether the moves as a whole exceed it. An item that was stable before (replicate_flip0) and moved is a real flip.accuracyTest (a):
frozen,live,change,lower,upper,bca(FALSEwhen the percentile interval stood in),p_value,detected.mcnemarThe cross-check:
statistic,df,p_value,detected, andagrees, whether it reaches the same decision as test (a).instabilityTest (b):
replicate_flip,live_flip,cross_flip,excess,lower(the one-sided bound),bca,p_value,detected.replicate_pairs,cross_pairsThe flip rate over items of each pair of frozen runs, and of each frozen run with each live run.
driftWhether either test rejected.
alert_crossedWhether the interval of test (a) lies wholly beyond the alert, a change at least that large;
NAwithoutalertor without an interval.alert_not_ruled_outWhether the interval of test (a) reaches beyond the alert, so a change that large cannot be ruled out;
NAas foralert_crossed.liveThe live
hr_bench.notesWhat the reader should know about the result.
Details
Two tests are run, with alpha and alert fixed in the call before
anything is rerun, so neither can be chosen after the result is seen.
(a) Accuracy change. For item i, p_i is the share of the frozen runs
that were correct and q_i the share of the live runs. The change is
mean(q_i - p_i) over items, live minus frozen. Its two-sided interval at
level 1 - alpha is a BCa cluster bootstrap (Efron 1987): each of the B
resamples draws whole clusters with replacement, with all their items and
runs, so the frozen and live values of an item stay paired. Accuracy
changed when the interval excludes 0.
As a cross-check the clustered McNemar test of Obuchowski (1998) is run on the same item accuracies. With x_1k and x_2k the sums of p_i and q_i over the items of cluster k, n_k its items, N the items and K the clusters, the estimates are P_j = sum_k x_jk / N and
X^2 = (P_1 - P_2)^2 / (V_1 + V_2 - 2 V_12),
V_j = K / ((K - 1) N^2) sum_k (x_jk - n_k P_j)^2, V_12 = K / ((K - 1) N^2) sum_k (x_1k - n_k P_1) (x_2k - n_k P_2),
referred to a chi-squared distribution on 1 degree of freedom. An unclustered McNemar test is not offered: it treats items from one document as independent and overstates the evidence.
(b) Excess instability. A model that answers differently from run to run flips items between any two runs, frozen or live. So the flips between frozen and live runs are compared with the flips among replicate runs. With R_f frozen runs of which k_i were correct on item i, and R_l live runs of which m_i were,
f_i = 2 k_i (R_f - k_i) / (R_f (R_f - 1))
is the share of pairs of frozen runs that disagree on item i,
h_i = 2 m_i (R_l - m_i) / (R_l (R_l - 1))
the same share among the live runs, and
g_i = (k_i (R_l - m_i) + (R_f - k_i) m_i) / (R_f R_l)
the share of frozen-live pairs that disagree. With frozen accuracy p and live accuracy q on the item, f_i, h_i and g_i estimate 2 p (1 - p), 2 q (1 - q) and p (1 - q) + q (1 - p) without bias, so the item's excess
e_i = g_i - (f_i + h_i) / 2
estimates (p - q)^2. It is 0 in expectation when nothing changed and
above 0 for any move, in either direction and from any starting
accuracy, so it sees losses and gains that cancel in the accuracy change
and noisy items that turn deterministic. The live replicate flips h_i
enter beside the frozen ones because g_i - f_i alone estimates
(q - p) (1 - 2 p), which is 0 for an item at p = 0.5 whatever q is and
below 0 when an item settles toward 0 or 1. The test is one-sided:
instability is in excess when the lower BCa bound of mean(e_i) at
one-sided level alpha, from the same cluster resamples, is above 0. The
flip rate of every pair of frozen runs and of every frozen-live pair is
returned too, so the frozen replicate distribution can be read directly.
P-values of the bootstrap tests are the smallest level at which the test rejects, found by bisection over [1 / (B + 1), 0.5]; 1 when it does not reject even at 0.5.
Drift and alert. drift is TRUE when either test rejects. Each test
runs at alpha, so the chance that drift is TRUE with no change at
all can reach 2 alpha; pass alpha / 2 to hold it at alpha. alert,
when given, is the accuracy change, in points as a proportion, that may
not go unnoticed, and it is read two ways. The alert is crossed
(alert_crossed) when the whole interval of test (a) lies beyond
-alert or alert: a change at least that large is shown. A change of
alert is not ruled out (alert_not_ruled_out) when the interval reaches
beyond -alert or alert. A small or noisy frozen sample often cannot
rule it out even with no change at all, which says the sample cannot
vouch for the arm, not that the arm moved. Size the sample with
hr_n_needed().
What is refused. Fewer than 5 frozen runs of the arm (class
hr_drift_few_runs): with fewer, the replicate flips are too few pairs to
compare against. An arm whose hash is not the frozen arm's, a reference
or scorer other than the frozen benchmark's (class hr_drift_mismatch):
those would compare two configurations, which is hr_report()'s job. A
live run any of whose requests a cache or a recording answered (class
hr_drift_not_live): that is the frozen reply again, not the model
today. Turn cache reads off for the drift check.
The cluster bootstrap can undercover below 300 items or 30 clusters, and
the result notes it. With one cluster there is nothing to resample, and
both tests are left NA.
References
Efron, B. (1987). Better bootstrap confidence intervals. Journal of the American Statistical Association 82(397), 171-185.
Obuchowski, N. A. (1998). On the comparison of correlated proportions for clustered data. Statistics in Medicine 17(13), 1495-1507.
Examples
ref <- hr_reference(
items = list(q1 = "France", q2 = "Peru", q3 = "Chad", q4 = "Fiji",
q5 = "Chile", q6 = "Laos"),
labels = c("Paris", "Lima", "N'Djamena", "Suva", "Santiago", "Vientiane"),
cluster = c("a", "a", "b", "b", "c", "c"), split = "test")
capitals <- c(France = "Paris", Peru = "Lima", Chad = "N'Djamena", Fiji = "Suva",
Chile = "Santiago", Laos = "Vientiane")
answer <- function(capitals) {
gr_mock_client(function(messages, params) {
capitals[[sub("Capital of ", "", messages[[1]]$content)]]
})
}
ask <- hr_arm("ask", function(item, ctx) ctx$call(paste0("Capital of ", item))$text)
frozen <- hr_benchmark(ask, ref, runs = 5, client = answer(capitals))
# Months later the model gets two capitals wrong.
today <- capitals
today[c("Chad", "Laos")] <- c("Ndjamena", "Luang Prabang")
drift <- hr_drift_check(frozen, ask, client = answer(today), reference = ref,
alert = 0.1, B = 500)
drift
#> <hr_drift_check> arm 'ask': 5 frozen run(s) vs 5 live, 6 item(s) in 3 cluster(s); alpha 0.05
#> (a) Accuracy: frozen 1.000, live 0.667, change -0.333 [-0.500, -0.167], p 0.0122; changed: yes
#> Clustered McNemar (Obuchowski): X^2 4.000, p 0.0455; agrees: yes
#> (b) Flips: between frozen runs 0.000, between live runs 0.000, frozen vs live 0.333, excess 0.333 (one-sided lower 0.000), p 0.333; in excess: no
#> Frozen pairs flip 0.000 to 0.000; frozen-live pairs 0.333 to 0.333
#> Drift: yes; alert at 0.100: crossed
#> Items: lost 2, gained 0, worse 0, better 0, same 4
#> item cluster change frozen live frozen_outcome live_outcome
#> q3 b lost 1.000 0.000 n'djamena ndjamena
#> q6 c lost 1.000 0.000 vientiane luang prabang
#> Note: 6 items in 3 clusters fall short of 300 items and 30 clusters, where the cluster bootstrap can undercover; read the tests as approximate.
drift$items[drift$items$change != "same", ]
#> cluster item frozen_accuracy live_accuracy change frozen_outcome
#> 3 b q3 1 0 lost n'djamena
#> 6 c q6 1 0 lost vientiane
#> live_outcome replicate_flip live_flip cross_flip excess
#> 3 ndjamena 0 0 1 1
#> 6 luang prabang 0 0 1 1