Cross-fit the held-out discrepancy indices of optop_index_holdout():
split the corpus into V folds, refit the model grid on each fold's
complement with a user-supplied fitting function, score the held-out
fold, and pool. Every document is scored exactly once, by models
trained without it, so the pooled indices are out-of-sample for the
whole corpus rather than for one evaluation split; this is the
implementation the paper recommends in practice (Appendix B).
Usage
optop_crossfit(
dtm,
K,
fit_fun,
V = 5,
metrics = c("se", "chisq", "deviance"),
c = 1,
conf = 0.95,
stabilize = 0,
min_null = NULL,
stratify = TRUE,
seed = NULL,
n_threads = 1L,
verbose = FALSE,
...
)Arguments
- dtm
A document-term matrix of raw counts (a
quanteda::dfm, adgCMatrix, or a base matrix with dimnames). Shardedoptop_corpus()objects are not supported: cross-fitting refits the gridVtimes, which is engine-scale work outside the sharded evaluation contract.- K
Integer vector of topic counts, strictly increasing, all at least 2. The grid is refit in every fold.
- fit_fun
A function
(dtm_train, k)returning a fitted model; see the dedicated section.- V
Number of folds, between 2 and
floor(J / 2); the default 5 follows the paper's appendix.- metrics
Character subset of
c("se", "chisq", "deviance").- c
Positive rare-word threshold constant of the harmonized partition, as in
optop_make_partition().- conf
Confidence level of the reported intervals.
- stabilize
Nonnegative stabilization constant kappa, as in
optop_index_holdout().- min_null
Null-discrepancy floor;
NULL(the default) resolves toc, and0restores the strict-positivity rule.- stratify
Logical; when
TRUE(the default) folds are balanced on document length (documents are ordered by length and each consecutive block ofVis dealt across folds at random), so no fold concentrates the short documents. WhenFALSEthe assignment is simple random.- seed
Optional integer seeding the fold assignment (the only randomness this function itself draws).
- n_threads
OpenMP threads for the compiled scoring kernels; never changes results.
- verbose
Logical; when
TRUE, progress alerts per fold.- ...
Passed to
optop_fold_in()through the per-fold scoring (engine-specific fold-in controls).
Value
An object of class optop_crossfit: a list with
summaryOne row per metric and K: pooled
macrowith fold-replicatemacro_seand its interval, pooledmicroandmicro_se, the Micro-Macrogapandgap_se,n_docsin the pooled J+, andn_null_excluded.gainsPer metric, the cross-fitted adjacent gains with fold-replicate upper bounds and the selected
k_hatat the default tolerance (epsilon = 0.01,alpha = 0.05).scoresPer metric, the J x length(K) matrix of out-of-fold document indices (NA for floor-excluded documents).
d_model,d_nullThe pooled out-of-fold discrepancies.
foldsThe fold assignment, named by document.
fold_summaryThe per-fold holdout summaries with a
foldcolumn, the replicates behind every standard error.K,V,metrics,conf,stabilize,c,min_null,seedThe configuration.
The fitting function
fit_fun(dtm_train, k) receives the training complement of the fold
(rows of dtm in the class you passed) and one topic count, and must
return a fitted model OpTop supports: anything
optop_as_theta_phi() adapts and optop_fold_in() can project, so
fits from topicmodels, seededlda, NLPstudio, and text2vec via
optop_warplda() all qualify. A bare optop_model() does not: it has
no engine to fold unseen documents in. Seeding the engine is
fit_fun's own business; make it depend on k (and, if you want
fold-level reproducibility, on the training row names) for
reproducible fits.
Pooling and standard errors
Point estimates are computed at the document level on the pooled
out-of-fold discrepancies, under the same null-discrepancy floor and
stabilization conventions as optop_index_holdout(). Standard errors
are fold-replicate (cluster) estimates: the V per-fold values of
each quantity (Macro, Micro, the Micro-Macro gap, and the adjacent
gains) are treated as exchangeable replicates and the standard error
of their mean is sd / sqrt(V). This estimator sees the fold-to-fold
variation induced by refitting, which the per-document standard error
of a single split ignores; it is the conservative choice the paper's
appendix recommends, at the price of only V replicates (a warning is
issued when folds are small). The epsilon-adequacy selection of
optop_gain_table() is applied to the cross-fitted gains with these
fold-replicate bounds; the result is reported per metric and
recomputable at other tolerances through plot.optop_crossfit().
See also
optop_index_holdout() for the single-split machinery this
builds on, optop_gain_table() for the selection rule applied to
the cross-fitted gains, plot.optop_crossfit().
Examples
# \donttest{
# simulate a corpus with known truth
rdirich <- function(n, k) {
g <- matrix(stats::rgamma(n * k, shape = 1), n, k)
g / rowSums(g)
}
theta <- rdirich(60, 3)
phi <- rdirich(3, 120)
colnames(phi) <- sprintf("w%03d", 1:120)
dtm <- sim_dfm(theta, phi, doc_length = 150, seed = 7)
fit_vem <- function(dtm_train, k) {
topicmodels::LDA(quanteda::convert(dtm_train, to = "topicmodels"),
k = k, method = "VEM",
control = list(seed = 1000 + k))
}
cf <- optop_crossfit(dtm, K = 2:4, fit_fun = fit_vem, V = 3,
metrics = "deviance", seed = 42)
#> Warning: fewer than 30 documents per fold: the fold Macro means are noisy replicates and the fold-replicate standard errors will be unstable
cf
#> <optop_crossfit>: V-fold cross-fitted discrepancy indices
#> 60 documents in 3 folds, K = {2, 3, 4}, metrics: deviance
#> 95% intervals from fold-replicate standard errors
#> epsilon-adequacy pick (deviance, epsilon = 0.01, alpha = 0.05): K = 2
#> metric K macro macro_se ci_lo ci_hi
#> <char> <int> <num> <num> <num> <num>
#> 1: deviance 2 0.0009836828 2.081519e-04 0.0005757127 0.001391653
#> 2: deviance 3 0.0014449503 1.837466e-04 0.0010848137 0.001805087
#> 3: deviance 4 0.0009548638 4.745472e-05 0.0008618542 0.001047873
#> micro micro_se gap gap_se n_docs n_null_excluded
#> <num> <num> <num> <num> <int> <int>
#> 1: 0.0009719143 1.596900e-04 -1.176853e-05 4.864777e-05 60 0
#> 2: 0.0014532048 1.833317e-04 8.254474e-06 1.439177e-06 60 0
#> 3: 0.0009745113 4.907993e-05 1.964757e-05 3.450178e-06 60 0
plot(cf)
# }