ok <- requireNamespace("rpart", quietly = TRUE) && requireNamespace("MASS", quietly = TRUE) knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4, dpi = 72, out.width = "100%", eval = ok)
Machine-learning models often need more data than we have. A synthesizer
fitted to a small sample can generate as many additional training records
as needed. This vignette shows how to do that with augment_data() and,
just as importantly, how to check whether it actually helps.
library(SynthesizerPlus) library(rpart)
A synthesizer only knows what is in its training sample, so synthetic data add no new information. What they provide is a smoothed version of the training distribution: new combinations of values that are plausible under the fitted marginals and copula. For flexible, high-variance learners (regression trees, nearest neighbours, boosting, neural networks) trained on a small sample this acts as a regulariser and can reduce the prediction error. For a model that is already smooth, such as linear regression, there is little to gain.
Whether it helps must therefore be measured, following four rules:
We pretend that only 60 records of the Boston housing data are available
and predict the median house value medv.
data(Boston, package = "MASS") rmse <- function(pred, obs) sqrt(mean((pred - obs)^2)) tree <- function(f, d) rpart(f, d, control = rpart.control(cp = 0.005, minsplit = 5)) one_split <- function(seed) { set.seed(seed) idx <- sample(nrow(Boston), 60) train <- Boston[idx, ] test <- Boston[-idx, ] big <- augment_data(train, n = 2000, seed = seed) # 60 real + 2000 synthetic c(tree_real = rmse(predict(tree(medv ~ ., train), test), test$medv), tree_augmented = rmse(predict(tree(medv ~ ., big), test), test$medv), lm_real = rmse(predict(lm(medv ~ ., train), test), test$medv), lm_augmented = rmse(predict(lm(medv ~ ., big), test), test$medv)) } res <- t(sapply(1:6, one_split)) round(colMeans(res), 2) mean(res[, "tree_augmented"] < res[, "tree_real"])
The regression tree improves on average, though not in every split (the second line is the share of splits in which augmentation lowered its error); the linear model does not change. The gain for a single small data set is uncertain, which is why the comparison must be repeated over several splits. Before training, it is worth checking that the synthetic training data resemble the real training data:
set.seed(1) train <- Boston[sample(nrow(Boston), 60), ] syn <- generate(fit_synthesizer(train), n = 2000, seed = 1) plot_marginals(train, syn, vars = c("medv", "lstat", "rm", "crim"))
augment_data() accepts all arguments of fit_synthesizer(), e.g. by =
to stratify on a categorical variable, and label = to add a column marking
the synthetic rows (useful for down-weighting them in training).
For forecasting, an ML model is trained on windows of lagged values. With one
short series there are few windows; augment_data() on a ts object returns
the real series followed by nsim synthetic series, and ts_windows()
stacks the windows of all of them.
We use the first 48 months of ldeaths for training and forecast the last
24 months one step ahead.
lags <- 12 n_train <- 48 train_ts <- window(ldeaths, end = time(ldeaths)[n_train]) all_w <- ts_windows(ldeaths, lags = lags) test_w <- all_w[(n_train - lags + 1):nrow(all_w), ] # targets in the test period forecast_rmse <- function(seed) { series <- augment_data(train_ts, nsim = 50, order = 12, seed = seed) real_w <- ts_windows(series[1], lags = lags) aug_w <- ts_windows(series, lags = lags) real_w$series <- aug_w$series <- NULL c(tree_real = rmse(predict(tree(y ~ ., real_w), test_w), test_w$y), tree_augmented = rmse(predict(tree(y ~ ., aug_w), test_w), test_w$y), lm_real = rmse(predict(lm(y ~ ., real_w), test_w), test_w$y)) } round(colMeans(t(sapply(1:6, forecast_rmse))), 1)
Two lessons:
order = 12 solves this. Always compare the autocorrelation of
real and synthetic series:fit_ts <- fit_synthesizer(train_ts, order = 12) plot_acf(train_ts, generate(fit_ts, n = 2000, seed = 3), lag_max = 24)
compare_synthetic(),
plot_marginals(), plot_acf()) before trusting the results.by, copula, order) with cross-validation on the training data,
not on the test set.Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.