| Title: | Adversarial Random Forests |
|---|---|
| Description: | Adversarial random forests (ARFs) recursively partition data into fully factorized leaves, where features are jointly independent. The procedure is iterative, with alternating rounds of generation and discrimination. Data becomes increasingly realistic at each round, until original and synthetic samples can no longer be reliably distinguished. This is useful for several unsupervised learning tasks, such as density estimation and data synthesis. Methods for both are implemented in this package. ARFs naturally handle unstructured data with mixed continuous and categorical covariates. They inherit many of the benefits of random forests, including speed, flexibility, and solid performance with default parameters. For details, see Watson et al. (2023) <https://proceedings.mlr.press/v206/watson23a.html>. |
| Authors: | Marvin N. Wright [aut, cre] (ORCID: <https://orcid.org/0000-0002-8542-6291>), David S. Watson [aut] (ORCID: <https://orcid.org/0000-0001-9632-2159>), Kristin Blesch [aut] (ORCID: <https://orcid.org/0000-0001-6241-3079>), Jan Kapar [aut] (ORCID: <https://orcid.org/0009-0000-6408-2840>), Lukas Burk [ctb] (ORCID: <https://orcid.org/0000-0001-7528-3795>), Martin Jullum [ctb] (ORCID: <https://orcid.org/0000-0003-3908-5155>) |
| Maintainer: | Marvin N. Wright <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.2.5.9000 |
| Built: | 2026-10-04 21:45:12 UTC |
| Source: | https://github.com/bips-hb/arf |
Adversarial random forests (ARFs) recursively partition data into fully factorized leaves, where features are jointly independent. The procedure is iterative, with alternating rounds of generation and discrimination. Data becomes increasingly realistic at each round, until original and synthetic samples can no longer be reliably distinguished. This is useful for several unsupervised learning tasks, such as density estimation and data synthesis. Methods for both are implemented in this package. ARFs naturally handle unstructured data with mixed continuous and categorical covariates. They inherit many of the benefits of random forests, including speed, flexibility, and solid performance with default parameters. For details, see Watson et al. (2023) https://proceedings.mlr.press/v206/watson23a.html.
Maintainer: Marvin N. Wright [email protected] (ORCID)
Authors:
Marvin N. Wright [email protected] (ORCID)
David S. Watson [email protected] (ORCID)
Kristin Blesch (ORCID)
Jan Kapar (ORCID)
Other contributors:
Lukas Burk [email protected] (ORCID) [contributor]
Martin Jullum [email protected] (ORCID) [contributor]
adversarial_rf, forde, forge,
expct, lik
Useful links:
Report bugs at https://github.com/bips-hb/arf/issues
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Implements an adversarial random forest to learn independence-inducing splits.
adversarial_rf( x, num_trees = 10L, min_node_size = 2L, mtry = NULL, delta = 0, max_iters = 10L, early_stop = TRUE, prune = TRUE, verbose = TRUE, parallel = TRUE, ... )adversarial_rf( x, num_trees = 10L, min_node_size = 2L, mtry = NULL, delta = 0, max_iters = 10L, early_stop = TRUE, prune = TRUE, verbose = TRUE, parallel = TRUE, ... )
x |
Input data. Integer variables are recoded as ordered factors with a warning. See Details. |
num_trees |
Number of trees to grow in each forest. The default works well for most generative modeling tasks, but should be increased for likelihood estimation. See Details. |
min_node_size |
Minimal number of real data samples in leaf nodes. |
mtry |
Number of candidate features at each split. Default is
|
delta |
Tolerance parameter. Algorithm converges when OOB accuracy is
< 0.5 + |
max_iters |
Maximum iterations for the adversarial loop. |
early_stop |
Terminate loop if performance fails to improve from one round to the next? |
prune |
Impose |
verbose |
Print discriminator accuracy after each round? Will also show additional warnings. |
parallel |
Compute in parallel? Enables multithreaded ranger training
(no backend needed) and parallelizes the pruning step, which requires a
registered |
... |
Extra parameters to be passed to |
The adversarial random forest (ARF) algorithm partitions data into fully
factorized leaves where features are jointly independent. ARFs are trained
iteratively, with alternating rounds of generation and discrimination. In
the first instance, synthetic data is generated via independent bootstraps of
each feature, and a RF classifier is trained to distinguish between real and
fake samples. In subsequent rounds, synthetic data is generated separately in
each leaf, using splits from the previous forest. This creates increasingly
realistic data that satisfies local independence by construction. The
algorithm converges when a RF cannot reliably distinguish between the two
classes, i.e. when OOB accuracy falls below 0.5 + delta.
ARFs are useful for several unsupervised learning tasks, such as density
estimation (see forde) and data synthesis (see
forge). For the former, we recommend increasing the number of
trees for improved performance (typically on the order of 100-1000 depending
on sample size).
Integer variables are recoded with a warning (set verbose = FALSE to
silence these). Default behavior is to convert integer variables with six or
more unique values to numeric, while those with up to five unique values are
treated as ordered factors. To override this behavior, explicitly recode
integer variables to the target type prior to training.
Note: convergence is not guaranteed in finite samples. The max_iters
argument sets an upper bound on the number of training rounds. Similar
results may be attained by increasing delta. Even a single round can
often give good performance, but data with strong or complex dependencies may
require more iterations. With the default early_stop = TRUE, the
adversarial loop terminates if performance does not improve from one round
to the next, in which case further training may be pointless.
A random forest object of class ranger.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Options controlling the parallel backend and its messaging, set via
options.
arf.backendParallel backend used when parallel = TRUE:
"foreach" or "mirai". If unset, arf uses "mirai" when
mirai daemons are running and "foreach" otherwise.
arf.verboseReport the selected backend once per backend
configuration per session? Default TRUE; set FALSE to
silence.
arf.block_rowsCap on rows materialized per block of
conditions in expct. Default 5e6. Lower it to
trade speed for memory on large forests with many conditions.
arf.block_rows does not affect results, only peak memory and speed.
For memory-constrained hardware, combine a small daemon count with a lower
arf.block_rows and the batch/stepsize arguments of
lik, forge and expct.
The "foreach" backend uses whatever adapter is registered (e.g.
doParallel, doFuture). The "mirai" backend uses
mirai daemons and shares large read-only inputs (training data,
forest, learned parameters) across workers via mori, so workers do
not each copy them. In our benchmarks (n = 20000, 200 trees, up to 16
workers) run time was similar between the backends, though on a cold daemon
pool "mirai" can be noticeably slower. The memory benefit is largest
for the tree-parallel operations (forde,
adversarial_rf) on large forests with many workers: at 16
workers peak memory was about 30 percent lower for forde and about
half for adversarial_rf. Expect smaller gains on smaller problems,
where the daemon pool's fixed overhead can outweigh the sharing, so prefer
"mirai" at scale and either backend otherwise.
Daemons started via future.mirai (e.g.
plan(future.mirai::mirai_multisession)) are detected like any other
mirai daemons, so futureverse users get the "mirai" backend
automatically. None of these packages are installed with arf: install
mirai and mori for the "mirai" backend,
doParallel or doFuture for "foreach", and
doRNG or future.mirai if you use them below.
Workers and data.table threads multiply. If their product exceeds
the core count, cap threads per worker with
data.table::setDTthreads() and set OMP_WAIT_POLICY=passive
before starting R so idle OpenMP threads sleep instead of spinning.
Reproducibility of stochastic operations (forge, categorical
expct) under parallel execution: set.seed only
governs the calling process, not the workers. With the "mirai"
backend, seed the daemons instead: mirai::daemons(n, seed = 42)
gives reproducible results provided the daemon count, the seed and the
sequence of calls on a fresh daemon pool are kept fixed (changing the
daemon count changes how work is chunked and therefore the random stream
assignment). This also applies to pools started via future.mirai:
future's own seed machinery covers only work dispatched through its
API, which arf's backend bypasses, and plan() does not accept a
seed argument, so seed the pool with mirai::daemons()
directly. For the "foreach" backend, register doRNG on top of
the adapter (doRNG::registerDoRNG(42) after
registerDoParallel() or registerDoFuture()): results are then
reproducible, identical across adapters, and independent of the worker
count, provided stepsize is set explicitly (its default depends on
the worker count). Without doRNG, doFuture flags the
stochastic operations with "UNRELIABLE VALUE" warnings. Sequential
execution (parallel = FALSE) with set.seed is exact as
always.
## Not run: arf <- adversarial_rf(iris) # foreach backend doParallel::registerDoParallel(cores = 4) psi <- forde(arf, iris) # mirai backend: start daemons, then call as usual mirai::daemons(4) psi <- forde(arf, iris) mirai::daemons(0) # shut down when done # futureverse: future.mirai daemons are detected automatically future::plan(future.mirai::mirai_multisession, workers = 4) psi <- forde(arf, iris) future::plan("sequential") # shuts the daemons down # force a backend regardless of what is registered (NULL restores auto-detection) options(arf.backend = "mirai") options(arf.backend = NULL) # silence the backend message options(arf.verbose = FALSE) # reproducible parallel sampling with mirai: seeded daemons # (fixed daemon count, fresh pool) evi <- data.frame(Species = sample(levels(iris$Species), 100, replace = TRUE)) mirai::daemons(4, seed = 42) # stepsize = evidence rows per step; 25 = 100 conditions / 4 workers, # i.e. the default sizing, made explicit x <- forge(psi, n_synth = 1, evidence = evi, stepsize = 25) mirai::daemons(0) # reproducible parallel sampling with foreach: doRNG on top of the # adapter; set stepsize explicitly (its default depends on worker count) doParallel::registerDoParallel(cores = 4) doRNG::registerDoRNG(42) x <- forge(psi, n_synth = 1, evidence = evi, stepsize = 25) ## End(Not run)## Not run: arf <- adversarial_rf(iris) # foreach backend doParallel::registerDoParallel(cores = 4) psi <- forde(arf, iris) # mirai backend: start daemons, then call as usual mirai::daemons(4) psi <- forde(arf, iris) mirai::daemons(0) # shut down when done # futureverse: future.mirai daemons are detected automatically future::plan(future.mirai::mirai_multisession, workers = 4) psi <- forde(arf, iris) future::plan("sequential") # shuts the daemons down # force a backend regardless of what is registered (NULL restores auto-detection) options(arf.backend = "mirai") options(arf.backend = NULL) # silence the backend message options(arf.verbose = FALSE) # reproducible parallel sampling with mirai: seeded daemons # (fixed daemon count, fresh pool) evi <- data.frame(Species = sample(levels(iris$Species), 100, replace = TRUE)) mirai::daemons(4, seed = 42) # stepsize = evidence rows per step; 25 = 100 conditions / 4 workers, # i.e. the default sizing, made explicit x <- forge(psi, n_synth = 1, evidence = evi, stepsize = 25) mirai::daemons(0) # reproducible parallel sampling with foreach: doRNG on top of the # adapter; set stepsize explicitly (its default depends on worker count) doParallel::registerDoParallel(cores = 4) doRNG::registerDoRNG(42) x <- forge(psi, n_synth = 1, evidence = evi, stepsize = 25) ## End(Not run)
Calls adversarial_rf, forde and lik.
For repeated application, it is faster to save outputs of adversarial_rf
and forde and pass them via ... or directly use lik.
darf(x, query = NULL, ...)darf(x, query = NULL, ...)
x |
Input data. Integer variables are recoded as ordered factors with a warning. See Details. |
query |
Data frame of samples, optionally comprising just a subset of
training features. See Details of |
... |
Extra parameters to be passed to |
A vector of likelihoods, optionally on the log scale. A dataset of
n_synth synthetic samples or of nrow(x) synthetic
samples if n_synth is undefined.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde, forge
# Estimate log-likelihoods ll <- darf(iris) # Partial evidence query ll <- darf(iris, query = iris[1, 1:3]) # Condition on Species = "setosa" ll <- darf(iris, query = iris[1, 1:3], evidence = data.frame(Species = "setosa"))# Estimate log-likelihoods ll <- darf(iris) # Partial evidence query ll <- darf(iris, query = iris[1, 1:3]) # Condition on Species = "setosa" ll <- darf(iris, query = iris[1, 1:3], evidence = data.frame(Species = "setosa"))
Calls adversarial_rf, forde and expct.
For repeated application, it is faster to save outputs of adversarial_rf
and forde and pass them via ... or directly use expct.
earf(x, ...)earf(x, ...)
x |
Input data. Integer variables are recoded as ordered factors with a warning. See Details. |
... |
Extra parameters to be passed to |
A one row data frame with values for all query variables.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde, expct
# What is the expected values of each feature? earf(iris) #' # What is the expected values of Sepal.Length? earf(iris, query = "Sepal.Length") # What if we condition on Species = "setosa"? earf(iris, query = "Sepal.Length", evidence = data.frame(Species = "setosa"))# What is the expected values of each feature? earf(iris) #' # What is the expected values of Sepal.Length? earf(iris, query = "Sepal.Length") # What if we condition on Species = "setosa"? earf(iris, query = "Sepal.Length", evidence = data.frame(Species = "setosa"))
Compute the expectation of some query variable(s), optionally conditioned on some event(s).
expct( params, query = NULL, evidence = NULL, evidence_row_mode = c("separate", "or"), round = FALSE, nomatch = c("force", "na"), verbose = TRUE, stepsize = 0, parallel = TRUE )expct( params, query = NULL, evidence = NULL, evidence_row_mode = c("separate", "or"), round = FALSE, nomatch = c("force", "na"), verbose = TRUE, stepsize = 0, parallel = TRUE )
params |
Circuit parameters learned via |
query |
Optional character vector of variable names. Estimates will be
computed for each. If |
evidence |
Optional set of conditioning events. This can take one of three forms: (1) a partial sample, i.e. a single row of data with some but not all columns; (2) a data frame of conditioning events, which allows for inequalities and intervals; or (3) a posterior distribution over leaves. See Details and Examples. |
evidence_row_mode |
Interpretation of rows in multi-row evidence. If
|
round |
Round continuous variables to their respective maximum precision in the real data set? |
nomatch |
What to do if no leaf matches a condition in |
verbose |
Show warnings, e.g. when no leaf matches a condition? |
stepsize |
How many rows of evidence should be handled at each step?
Smaller steps lower peak memory. Defaults to |
parallel |
Compute in parallel? Requires a registered |
This function computes expected values for any subset of features, optionally conditioned on some event(s).
There are three methods for (optionally) encoding conditioning events via the
evidence argument. The first is to provide a partial sample, where
some columns from the training data are missing or set to NA. The
second is to provide a data frame with condition events. This supports
inequalities and intervals. Alternatively, users may directly input a
pre-calculated posterior distribution over leaves, with columns f_idx
and wt. This may be preferable for complex constraints. See Examples.
Please note that results for continuous features which are both included in
query and in evidence with an interval condition are currently
inconsistent.
A one row data frame with values for all query variables.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde,
forge, lik
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # What is the expected value of Sepal.Length? expct(psi, query = "Sepal.Length") # What if we condition on Species = "setosa"? evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) # Compute expectations for all features other than Species expct(psi, evidence = evi) # Condition on Species = "setosa" and Petal.Width > 0.3 evi <- data.frame(Species = "setosa", Petal.Width = ">0.3") expct(psi, evidence = evi) # Condition on first two rows with some missing values evi <- iris[1:2,] evi[1, 1] <- NA_real_ evi[1, 5] <- NA_character_ evi[2, 2] <- NA_real_ x_synth <- expct(psi, evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # What is the expected value of Sepal.Length? expct(psi, query = "Sepal.Length") # What if we condition on Species = "setosa"? evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) # Compute expectations for all features other than Species expct(psi, evidence = evi) # Condition on Species = "setosa" and Petal.Width > 0.3 evi <- data.frame(Species = "setosa", Petal.Width = ">0.3") expct(psi, evidence = evi) # Condition on first two rows with some missing values evi <- iris[1:2,] evi[1, 1] <- NA_real_ evi[1, 5] <- NA_character_ evi[2, 2] <- NA_real_ x_synth <- expct(psi, evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Uses a pre-trained ARF model to estimate leaf and distribution parameters.
forde( arf, x, oob = FALSE, family = "truncnorm", finite_bounds = c("no", "local", "global"), alpha = 0, epsilon = 0, parallel = TRUE )forde( arf, x, oob = FALSE, family = "truncnorm", finite_bounds = c("no", "local", "global"), alpha = 0, epsilon = 0, parallel = TRUE )
arf |
Pre-trained |
x |
Training data for estimating parameters. |
oob |
Only use out-of-bag samples for parameter estimation? If
|
family |
Distribution to use for density estimation of continuous
features. Current options include truncated normal (the default
|
finite_bounds |
Impose finite bounds on all continuous variables? If
|
alpha |
Optional pseudocount for Laplace smoothing of categorical features. This avoids zero-mass points when test data fall outside the support of training data. Effectively parameterizes a flat Dirichlet prior on multinomial likelihoods. |
epsilon |
Optional slack parameter on empirical bounds when
|
parallel |
Compute in parallel? Requires a registered |
forde extracts leaf parameters from a pretrained forest and learns
distribution parameters for data within each leaf. The former includes
coverage (proportion of data falling into the leaf) and split criteria. The
latter includes proportions for categorical features and mean/variance for
continuous features. The result is a probabilistic circuit, stored as a
data.table, which can be used for various downstream inference tasks.
Currently, forde only provides support for a limited number of
distributional families: truncated normal or uniform for continuous data,
and multinomial for discrete data.
Though forde was designed to take an adversarial random forest as
input, the function's first argument can in principle be any object of class
ranger. This allows users to test performance with alternative
pipelines (e.g., with supervised forest input). There is also no requirement
that x be the data used to fit arf, unless oob = TRUE.
In fact, using another dataset here may protect against overfitting. This
connects with Wager & Athey's (2018) notion of "honest trees".
A list with 5 elements: (1) parameters for continuous data; (2)
parameters for discrete data; (3) leaf indices and coverage; (4) metadata on
variables; and (5) the data input class. This list is used for estimating
likelihoods with lik and generating data with forge.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
Wager, S. & Athey, S. (2018). Estimation and inference of heterogeneous treatment effects using random forests. J. Am. Stat. Assoc., 113(523): 1228-1242.
arf, adversarial_rf, forge,
expct, lik
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Expectation of Sepal.Length for class setosa evi <- data.frame(Species = "setosa") expct(psi, query = "Sepal.Length", evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Uses pre-trained FORDE model to simulate synthetic data.
forge( params, n_synth, evidence = NULL, evidence_row_mode = c("separate", "or"), round = TRUE, sample_NAs = FALSE, nomatch = c("force", "na"), verbose = TRUE, stepsize = 0, parallel = TRUE )forge( params, n_synth, evidence = NULL, evidence_row_mode = c("separate", "or"), round = TRUE, sample_NAs = FALSE, nomatch = c("force", "na"), verbose = TRUE, stepsize = 0, parallel = TRUE )
params |
Circuit parameters learned via |
n_synth |
Number of synthetic samples to generate. |
evidence |
Optional set of conditioning events. This can take one of three forms: (1) a partial sample, i.e. a single row of data with some but not all columns; (2) a data frame of conditioning events, which allows for inequalities; or (3) a posterior distribution over leaves. See Details. |
evidence_row_mode |
Interpretation of rows in multi-row evidence. If
|
round |
Round continuous variables to their respective maximum precision in the real data set? |
sample_NAs |
Sample |
nomatch |
What to do if no leaf matches a condition in |
verbose |
Show warnings, e.g. when no leaf matches a condition? |
stepsize |
How many rows of evidence should be handled at each step?
Smaller steps lower peak memory. Defaults to |
parallel |
Compute in parallel? Requires a registered |
forge simulates a synthetic dataset of n_synth samples. First,
leaves are sampled in proportion to either their coverage (if
evidence = NULL) or their posterior probability. Then, each feature is
sampled independently within each leaf according to the probability mass or
density function learned by forde. This will create realistic
data so long as the adversarial RF used in the previous step satisfies the
local independence criterion. See Watson et al. (2023).
There are three methods for (optionally) encoding conditioning events via the
evidence argument. The first is to provide a partial sample, where
some columns from the training data are missing or set to NA. The
second is to provide a data frame with condition events. This supports
inequalities and intervals. Alternatively, users may directly input a
pre-calculated posterior distribution over leaves, with columns f_idx
and wt. This may be preferable for complex constraints. See Examples.
A dataset of n_synth synthetic samples.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde,
expct, lik
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" evi <- data.frame(Species = "setosa") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Alternative syntax for </> conditions evi <- data.frame(Sepal.Length = ">6") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Negation condition, i.e. all classes except "setosa" evi <- data.frame(Species = "!setosa") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Condition on first two data rows with some missing values evi <- iris[1:2,] evi[1, 1] <- NA_real_ evi[1, 5] <- NA_character_ evi[2, 2] <- NA_real_ x_synth <- forge(psi, n_synth = 1, evidence = evi) # Or just input some distribution on leaves # (Weights that do not sum to unity are automatically scaled) n_leaves <- nrow(psi$forest) evi <- data.frame(f_idx = psi$forest$f_idx, wt = rexp(n_leaves)) x_synth <- forge(psi, n_synth = 100, evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Generate 100 synthetic samples from the iris dataset x_synth <- forge(psi, n_synth = 100) # Condition on Species = "setosa" evi <- data.frame(Species = "setosa") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Condition on Species = "setosa" and Sepal.Length > 6 evi <- data.frame(Species = "setosa", Sepal.Length = "(6, Inf)") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Alternative syntax for </> conditions evi <- data.frame(Sepal.Length = ">6") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Negation condition, i.e. all classes except "setosa" evi <- data.frame(Species = "!setosa") x_synth <- forge(psi, n_synth = 100, evidence = evi) # Condition on first two data rows with some missing values evi <- iris[1:2,] evi[1, 1] <- NA_real_ evi[1, 5] <- NA_character_ evi[2, 2] <- NA_real_ x_synth <- forge(psi, n_synth = 1, evidence = evi) # Or just input some distribution on leaves # (Weights that do not sum to unity are automatically scaled) n_leaves <- nrow(psi$forest) evi <- data.frame(f_idx = psi$forest$f_idx, wt = rexp(n_leaves)) x_synth <- forge(psi, n_synth = 100, evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Perform single or multiple imputation with ARFs. Calls adversarial_rf,
forde and expct/forge.
impute( x, m = 1, expectation = m == 1, num_trees = 100L, min_node_size = 10L, round = TRUE, finite_bounds = "local", epsilon = 1e-14, verbose = FALSE, ... )impute( x, m = 1, expectation = m == 1, num_trees = 100L, min_node_size = 10L, round = TRUE, finite_bounds = "local", epsilon = 1e-14, verbose = FALSE, ... )
x |
Input data. |
m |
Number of imputed datasets to generate. The default is single
imputation ( |
expectation |
Return expected value instead of multiple imputations. By
default, for single imputation ( |
num_trees |
Number of trees to grow in the ARF. |
min_node_size |
Minimal number of real data samples in leaf nodes. |
round |
Round continuous variables to their respective maximum precision in the real data set? |
finite_bounds |
Impose finite bounds on all continuous variables? See
|
epsilon |
Slack parameter on empirical bounds; see |
verbose |
Print progress for |
... |
Extra parameters to be passed to |
Imputed data. A single dataset is returned for m = 1, a list
of datasets for m > 1.
# Generate some missings iris_na <- iris for (j in 1:ncol(iris)) { iris_na[sample(1:nrow(iris), 5), j] <- NA } # Single imputation iris_imputed <- arf::impute(iris_na, num_trees = 10, m = 1) # Multiple imputation iris_imputed <- arf::impute(iris_na, num_trees = 10, m = 10) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Generate some missings iris_na <- iris for (j in 1:ncol(iris)) { iris_na[sample(1:nrow(iris), 5), j] <- NA } # Single imputation iris_imputed <- arf::impute(iris_na, num_trees = 10, m = 1) # Multiple imputation iris_imputed <- arf::impute(iris_na, num_trees = 10, m = 10) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Compute the likelihood of input data, optionally conditioned on some event(s).
lik( params, query, evidence = NULL, arf = NULL, oob = FALSE, log = TRUE, batch = NULL, parallel = TRUE )lik( params, query, evidence = NULL, arf = NULL, oob = FALSE, log = TRUE, batch = NULL, parallel = TRUE )
params |
Circuit parameters learned via |
query |
Data frame of samples, optionally comprising just a subset of training features. Likelihoods will be computed for each sample. Missing features will be marginalized out. See Details. |
evidence |
Optional set of conditioning events. This can take one of three forms: (1) a partial sample, i.e. a single row of data with some but not all columns; (2) a data frame of conditioning events, which allows for inequalities; or (3) a posterior distribution over leaves. See Details. |
arf |
Pre-trained |
oob |
Only use out-of-bag leaves for likelihood estimation? If
|
log |
Return likelihoods on log scale? Recommended to prevent underflow. |
batch |
Batch size. The default computes densities for all queries in
one round. Splitting into batches lowers peak memory with large samples or
many trees, and is what |
parallel |
Compute in parallel? Batches are processed in parallel, so
this only takes effect when |
This function computes the likelihood of input data, optionally conditioned on some event(s). Queries may be partial, i.e. covering some but not all features, in which case excluded variables will be marginalized out.
There are three methods for (optionally) encoding conditioning events via the
evidence argument. The first is to provide a partial sample, where
some but not all columns from the training data are present. The second is to
provide a data frame with three columns: variable, relation,
and value. This supports inequalities via relation.
Alternatively, users may directly input a pre-calculated posterior
distribution over leaves, with columns f_idx and wt. This may
be preferable for complex constraints. See Examples.
A vector of likelihoods, optionally on the log scale.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde, forge, expct
# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Identical but slower ll <- lik(psi, iris, log = TRUE) mean(ll) # Partial evidence query lik(psi, query = iris[1, 1:3]) # Condition on Species = "setosa" evi <- data.frame(Species = "setosa") lik(psi, query = iris[1, 1:3], evidence = evi) # Condition on Species = "setosa" and Petal.Width > 0.3 evi <- data.frame(Species = "setosa", Petal.Width = ">0.3") lik(psi, query = iris[1, 1:3], evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)# Train ARF and estimate leaf parameters arf <- adversarial_rf(iris) psi <- forde(arf, iris) # Estimate average log-likelihood ll <- lik(psi, iris, arf = arf, log = TRUE) mean(ll) # Identical but slower ll <- lik(psi, iris, log = TRUE) mean(ll) # Partial evidence query lik(psi, query = iris[1, 1:3]) # Condition on Species = "setosa" evi <- data.frame(Species = "setosa") lik(psi, query = iris[1, 1:3], evidence = evi) # Condition on Species = "setosa" and Petal.Width > 0.3 evi <- data.frame(Species = "setosa", Petal.Width = ">0.3") lik(psi, query = iris[1, 1:3], evidence = evi) ## Not run: # Parallelization with doParallel doParallel::registerDoParallel(cores = 4) # ... or with doFuture doFuture::registerDoFuture() future::plan("multisession", workers = 4) # ... or with mirai (shares large read-only inputs across workers via mori) mirai::daemons(4) ## End(Not run)
Calls adversarial_rf, forde and forge.
For repeated application, it is faster to save outputs of adversarial_rf
and forde and pass them via ... or directly use forge.
rarf(x, n_synth = NULL, ...)rarf(x, n_synth = NULL, ...)
x |
Input data. Integer variables are recoded as ordered factors with a warning. See Details. |
n_synth |
Number of synthetic samples to generate for unconditional
generation with no |
... |
Extra parameters to be passed to |
A dataset of n_synth synthetic samples or of nrow(x) synthetic
samples if n_synth is undefined.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf, adversarial_rf, forde, forge
# Generate 150 (size of original iris dataset) synthetic samples from the iris dataset x_synth <- rarf(iris) # Generate 100 synthetic samples from the iris dataset x_synth <- rarf(iris, n_synth = 100) # Condition on Species = "setosa" x_synth <- rarf(iris, evidence = data.frame(Species = "setosa"))# Generate 150 (size of original iris dataset) synthetic samples from the iris dataset x_synth <- rarf(iris) # Generate 100 synthetic samples from the iris dataset x_synth <- rarf(iris, n_synth = 100) # Condition on Species = "setosa" x_synth <- rarf(iris, evidence = data.frame(Species = "setosa"))
Draws synthetic samples by sampling, for each observation, a leaf from the
forest and then drawing each feature independently (marginally) from the real
observations that fall into that leaf. This is the intra-leaf sampling step
used internally by adversarial_rf to generate synthetic data
during the adversarial loop, exposed here as a stand-alone function.
sample_from_leaves( arf, x_real, params = NULL, round = TRUE, factor_cols = NULL, lvls = NULL, prep = TRUE )sample_from_leaves( arf, x_real, params = NULL, round = TRUE, factor_cols = NULL, lvls = NULL, prep = TRUE )
arf |
A trained ARF, as returned by |
x_real |
Data whose intra-leaf structure is used for sampling, typically the data the forest was trained on. |
params |
Optional circuit parameters as returned by |
round |
Round continuous variables to their maximum precision in the real
data? Only relevant when |
factor_cols |
Optional logical vector flagging the factor columns of
|
lvls |
Optional list of factor levels for the factor columns of
|
prep |
Prepare |
A dataset of nrow(x_real) synthetic samples. When params
is supplied, its class and column types match the original data; otherwise a
data.table with factor columns encoded as character.
Watson, D., Blesch, K., Kapar, J., & Wright, M. (2023). Adversarial random forests for density estimation and generative modeling. In Proceedings of the 26th International Conference on Artificial Intelligence and Statistics, pp. 5357-5375.
arf <- adversarial_rf(iris) # Minimally processed output (factors as character) x_synth <- sample_from_leaves(arf, iris) # Fully post-processed output, consistent with forge() psi <- forde(arf, iris) x_synth <- sample_from_leaves(arf, iris, params = psi)arf <- adversarial_rf(iris) # Minimally processed output (factors as character) x_synth <- sample_from_leaves(arf, iris) # Fully post-processed output, consistent with forge() psi <- forde(arf, iris) x_synth <- sample_from_leaves(arf, iris, params = psi)