| Title: | Dense Neural Networks for Tabular Regression, Classification and Survival |
|---|---|
| Description: | Dense feed-forward neural networks (multilayer perceptrons) for tabular regression, classification and survival analysis, with a formula or x/y interface. Supports residual and gated hidden blocks, batch normalization, per-layer dropout, learned cross-feature interactions, exponential moving-average weights, learning-rate schedules, internal bootstrap ensembles and Adam optimization. Survival outcomes are trained with either a batch-wise Breslow-tie Cox partial likelihood or a discrete-time inverse-probability-of-censoring-weighted integrated Brier score. The numerical kernels are implemented natively in C++ via 'RcppArmadillo', with no external deep learning framework dependency (no 'torch' / 'libtorch'). Companion helpers provide k-fold cross-validation, hyperparameter search and task-aware evaluation metrics. |
| Authors: | Imad El Badisy [aut, cre] |
| Maintainer: | Imad El Badisy <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.8.1 |
| Built: | 2026-10-18 14:51:11 UTC |
| Source: | https://github.com/ielbadisy/densemlp |
Fits densemlp() on each of folds training splits and evaluates it on the
held-out fold via densemlp_metrics().
cv_densemlp( x, y, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), folds = 5L, seed = 1L, ncores = 1L, verbose = FALSE, ... )cv_densemlp( x, y, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), folds = 5L, seed = 1L, ncores = 1L, verbose = FALSE, ... )
x |
Predictor data.frame or matrix. |
y |
Outcome vector. |
task |
|
folds |
Number of cross-validation folds. |
seed |
Random seed for fold assignment; fold |
ncores |
Number of cores used to fit folds in parallel (see
|
verbose |
Print per-fold progress. |
... |
Additional arguments passed to |
A list of class densemlp_cv with fold_metrics (one row per fold),
summary (mean and SD per metric across folds), and task.
set.seed(1) x <- data.frame(a = rnorm(60), b = rnorm(60)) y <- x$a - 0.5 * x$b + rnorm(60, sd = 0.1) cv <- cv_densemlp(x, y, folds = 3, epochs = 20, hidden_units = c(8)) cv$summaryset.seed(1) x <- data.frame(a = rnorm(60), b = rnorm(60)) y <- x$a - 0.5 * x$b + rnorm(60, sd = 0.1) cv <- cv_densemlp(x, y, folds = 3, epochs = 20, hidden_units = c(8)) cv$summary
A compact feedforward network for regression and classification on
tabular data. Forward propagation, backpropagation, and Adam optimization
are all implemented natively in C++ via RcppArmadillo – no torch /
libtorch dependency.
densemlp( x = NULL, y = NULL, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), loss = c("cox", "brier"), n_bins = 10L, hidden_units = c(32, 16), epochs = 100L, batch_size = 32L, lr = 0.001, validation = 0.2, early_stopping = TRUE, patience = 10L, min_delta = 0, min_epochs = max(10L, floor(epochs * 0.2)), residual = FALSE, gated = FALSE, dropout = 0, batch_norm = TRUE, input_projection = NULL, interaction = FALSE, ema_decay = 0, ensemble = 1L, ensemble_bootstrap = TRUE, lr_schedule = c("none", "cosine", "step"), seed = 1L, verbose = FALSE, ncores = 1L, formula = NULL, data = NULL, offset = NULL, groups = NULL, group_penalty = 0 ) ## S3 method for class 'densemlp' print(x, ...)densemlp( x = NULL, y = NULL, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), loss = c("cox", "brier"), n_bins = 10L, hidden_units = c(32, 16), epochs = 100L, batch_size = 32L, lr = 0.001, validation = 0.2, early_stopping = TRUE, patience = 10L, min_delta = 0, min_epochs = max(10L, floor(epochs * 0.2)), residual = FALSE, gated = FALSE, dropout = 0, batch_norm = TRUE, input_projection = NULL, interaction = FALSE, ema_decay = 0, ensemble = 1L, ensemble_bootstrap = TRUE, lr_schedule = c("none", "cosine", "step"), seed = 1L, verbose = FALSE, ncores = 1L, formula = NULL, data = NULL, offset = NULL, groups = NULL, group_penalty = 0 ) ## S3 method for class 'densemlp' print(x, ...)
x |
A |
y |
Outcome ( |
task |
|
loss |
For |
n_bins |
For |
|
Integer vector of hidden layer sizes. An empty vector
( |
|
epochs |
Maximum number of training epochs (an upper bound when
|
batch_size |
Mini-batch size. |
lr |
Adam learning rate. |
validation |
Validation fraction in |
early_stopping |
Logical; stop once validation loss stops improving.
Ignored if |
patience |
Number of non-improving epochs to wait before stopping. |
min_delta |
Minimum validation loss improvement to reset patience. |
min_epochs |
Minimum number of epochs before early stopping can
trigger. Defaults to |
residual |
Logical; add a residual skip to every hidden block. A learned linear projection is used when the block dimensions differ. |
gated |
Logical; use a learned sigmoid gate in every hidden block. |
dropout |
Dropout probability, either one value or one per hidden
layer. Values must be in |
batch_norm |
Logical; apply batch normalization inside every hidden
block ( |
input_projection |
Optional positive integer. When set, a plain
linear layer (no activation, no batch normalization) maps the encoded
predictors to |
interaction |
Logical; prepend an efficient learned cross-feature
layer that models explicit second-order interactions in |
ema_decay |
Exponential moving-average decay for model parameters.
Set to |
ensemble |
Number of internally fitted members whose predictions are
averaged. |
ensemble_bootstrap |
Logical; bootstrap rows independently for each
member when |
lr_schedule |
Learning-rate schedule: |
seed |
Integer seed. |
verbose |
Logical; print training/validation loss every 10 epochs. |
ncores |
Number of cores used to fit ensemble members in parallel
when |
formula |
A formula, e.g. |
data |
A data.frame used with |
offset |
Optional fixed term added to the linear predictor (the
output before its activation), on the scale of the outcome for
regression and of the link otherwise (log for |
groups |
Optional grouping factor(s): a vector, or a list or
data.frame of vectors, each with one value per row. Every level of
every grouping factor gets its own learned intercept on the linear
predictor (one per class for multiclass), trained jointly with the
network. This gives fixed-effect style cluster or period intercepts,
as in panel data. Not available for |
group_penalty |
Non-negative ridge penalty on the group intercepts,
added to each mini-batch loss as |
... |
Unused. |
Hidden layers are Linear -> BatchNorm -> ReLU -> optional gate -> optional dropout -> optional residual (batch statistics during training, running
mean/var at prediction time, exponential decay 0.9), with He initialization
for the linear weights. Set batch_norm = FALSE to drop the normalization
step, leaving Linear -> ReLU hidden blocks. Residual blocks use a learned
linear projection when dimensions differ. With input_projection = k, a
bare linear layer maps the encoded inputs to k dimensions before the
first hidden block (mutually exclusive with interaction). The optional
interaction layer is a one-layer cross network initialized as the
identity. With ema_decay > 0, validation and
final predictions use moving-average parameters. With ensemble > 1, fully
fitted internal members are trained with successive seeds and optionally
bootstrapped rows, and their response probabilities/predictions are averaged
transparently. The output layer is plain
Linear -> task activation (no BN): linear (regression, MSE loss),
sigmoid (binary, binary cross-entropy), softmax (multiclass, categorical
cross-entropy), exponential (count, Poisson log-likelihood), or a linear
risk score (survival, Cox partial likelihood); the first four share the
output-gradient simplification
dZ = (yhat - y) / n, while survival uses its own closed-form Cox
gradient (see the "Survival" section below). With validation > 0 and
early_stopping = TRUE (both on by default), the parameters (weights,
biases, and BN affine/running-stat parameters) from the best validation
epoch are restored at the end.
offset and groups both enter the linear predictor additively, after
the output layer and before its activation, so the fitted model is
g(E[y]) = f(x) + offset + sum_k alpha_k[group_k], where f is the
network and the alpha_k are the per-level intercepts (see
group_effects()).
A densemlp object.
task = "survival" supports two losses, both trained batch-wise against
a (time, event) outcome:
loss = "cox" (the default) trains a single linear risk score using a
batch-wise Breslow-tie Cox partial log-likelihood – the same
"risk set = current mini-batch" approximation used by DeepSurv/pycox,
which turns Cox regression into an ordinary per-batch loss compatible
with masked-free Adam training, BN, and early stopping unchanged. For a
batch sorted by descending time, with each observation's risk set
approximated by the sorted prefix of observations with a time at least
as large, the loss is the negative mean, over events, of the risk
score minus the log of the cumulative sum of exponentiated risk scores
over its risk set – computed via a single cumulative sum with a
closed-form O(n) gradient (no autodiff).
loss = "brier" trains a discrete-time hazard head with n_bins
outputs (a quantile grid of the observed follow-up times) directly
against the IPCW (Graf et al.) integrated Brier score. Bin k's
sigmoid output is a conditional hazard, and the survival probability
at that bin is the running product of one minus every hazard up to
and including it, which is monotonically non-increasing by
construction. Each bin's Brier term
is inverse-probability-of-censoring weighted using a Kaplan-Meier
estimate of the censoring distribution fit once on the training
data; subjects censored before a bin are excluded from that bin's
term, per Graf's correction. The gradient is closed-form (no
autodiff): a bin's survival probability depends on every hazard
logit at or before it, so each bin's Brier-score gradient is
distributed back across all of them.
predict(fit, newdata, type = "response") returns a linear risk score
for either loss (for "brier", the negative log of the predicted
survival probability at the final time bin, so higher is still
riskier); with
loss = "brier", type = "survival" additionally returns the full
n_bins-column survival-probability matrix. densemlp_metrics() reports
Harrell's concordance index for task = "survival" regardless of loss.
set.seed(1) x <- data.frame(a = rnorm(100), b = rnorm(100)) y <- x$a - 0.5 * x$b + rnorm(100, sd = 0.1) fit <- densemlp(x, y, epochs = 50, hidden_units = c(16)) predict(fit, x[1:5, ])set.seed(1) x <- data.frame(a = rnorm(100), b = rnorm(100)) y <- x$a - 0.5 * x$b + rnorm(100, sd = 0.1) fit <- densemlp(x, y, epochs = 50, hidden_units = c(16)) predict(fit, x[1:5, ])
The IPCW (Graf et al.) integrated Brier score, evaluated at the model's
own time grid (object$survival_breaks), using a Kaplan-Meier estimate
of the censoring distribution fit on y (i.e. on whatever data is
passed in – pass the held-out set's own outcome for an honest
out-of-sample estimate). Lower is better; this is exactly the objective
densemlp() minimizes when fit with task = "survival", loss = "brier".
densemlp_integrated_brier_score(object, newdata, y)densemlp_integrated_brier_score(object, newdata, y)
object |
A |
newdata |
Predictor data to evaluate on. |
y |
The corresponding survival outcome ( |
A single numeric integrated Brier score.
Prediction metrics for a fitted densemlp model
densemlp_metrics(truth, estimate, task, prob = NULL)densemlp_metrics(truth, estimate, task, prob = NULL)
truth |
Observed outcome values, or, for |
estimate |
Predicted values ( |
task |
|
prob |
Optional matrix of class probabilities (classification only),
used to compute |
A named list of metrics: rmse, nrmse, rsq for regression,
accuracy, balanced_accuracy, macro_auc, log_loss for
classification, or concordance (Harrell's C-index) for survival.
Returns the per-level intercepts that a model fitted with groups
adds to its linear predictor, on the scale of the outcome for
regression and of the link otherwise.
group_effects(object, ...) ## S3 method for class 'densemlp' group_effects(object, ...)group_effects(object, ...) ## S3 method for class 'densemlp' group_effects(object, ...)
object |
A fitted |
... |
Unused. |
A named list with one entry per grouping factor: a named numeric vector of intercepts (one per level), or for multiclass a matrix with one row per level and one column per class. For an ensemble, the members' intercepts are averaged.
Model-agnostic permutation importance: for each predictor, the column is randomly shuffled and the drop in predictive performance (relative to the unpermuted baseline) is recorded. Larger values mean the model relied more on that predictor.
perm_importance(object, new_data, truth, metric = NULL, seed = object$seed) ## S3 method for class 'densemlp_importance' print(x, ...)perm_importance(object, new_data, truth, metric = NULL, seed = object$seed) ## S3 method for class 'densemlp_importance' print(x, ...)
object |
A fitted |
new_data |
Evaluation predictor data. |
truth |
Ground-truth outcome for |
metric |
Metric name understood by |
seed |
Random seed used for the column shuffles. |
x |
A |
... |
Unused. |
A densemlp_importance object: a list with data (a data frame
of feature / importance, ordered by decreasing importance),
metric, baseline and task.
set.seed(1) x <- data.frame(a = rnorm(120), b = rnorm(120), c = rnorm(120)) y <- x$a - 0.5 * x$b + rnorm(120, sd = 0.1) fit <- densemlp(x, y, epochs = 40, hidden_units = c(16)) perm_importance(fit, x, y)set.seed(1) x <- data.frame(a = rnorm(120), b = rnorm(120), c = rnorm(120)) y <- x$a - 0.5 * x$b + rnorm(120, sd = 0.1) fit <- densemlp(x, y, epochs = 40, hidden_units = c(16)) perm_importance(fit, x, y)
A named wrapper around the plot.densemlp() method: draws the per-epoch
training and validation loss curves.
plot_history(object, ...)plot_history(object, ...)
object |
A fitted |
... |
Passed to |
object, invisibly.
set.seed(1) x <- data.frame(a = rnorm(80), b = rnorm(80)) y <- x$a - 0.5 * x$b + rnorm(80, sd = 0.1) fit <- densemlp(x, y, epochs = 40, hidden_units = c(16)) plot_history(fit)set.seed(1) x <- data.frame(a = rnorm(80), b = rnorm(80)) y <- x$a - 0.5 * x$b + rnorm(80, sd = 0.1) fit <- densemlp(x, y, epochs = 40, hidden_units = c(16)) plot_history(fit)
Plot training history
## S3 method for class 'densemlp' plot(x, ...)## S3 method for class 'densemlp' plot(x, ...)
x |
A fitted |
... |
Additional arguments passed to |
x, invisibly.
Plot permutation importance
## S3 method for class 'densemlp_importance' plot(x, top = 20L, ...)## S3 method for class 'densemlp_importance' plot(x, top = 20L, ...)
x |
A |
top |
Number of top features to show. |
... |
Passed to |
x, invisibly.
Predict from a fitted densemlp model
## S3 method for class 'densemlp' predict( object, newdata, type = c("response", "class", "prob", "survival"), offset = NULL, groups = NULL, ... )## S3 method for class 'densemlp' predict( object, newdata, type = c("response", "class", "prob", "survival"), offset = NULL, groups = NULL, ... )
object |
A fitted |
newdata |
New predictor data, in the same representation used to fit. |
type |
|
offset |
Offset for |
groups |
Grouping factor(s) for |
... |
Unused. |
A numeric vector ("response"), a factor ("class"), or a
matrix ("prob" or "survival").
Fits densemlp() for every combination in grid (each repeated repeats
times with successive seeds), ranks candidates by their best internal
validation loss, and optionally refits the best configuration on the
full data.
tune_densemlp( x, y, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), grid = NULL, validation = 0.2, seed = 1L, repeats = 3L, ncores = 1L, verbose = FALSE, refit = TRUE )tune_densemlp( x, y, task = c("auto", "regression", "binary", "multiclass", "survival", "count"), grid = NULL, validation = 0.2, seed = 1L, repeats = 3L, ncores = 1L, verbose = FALSE, refit = TRUE )
x |
Predictor data.frame or matrix. |
y |
Outcome vector. |
task |
|
grid |
A named list of candidate values. Supported names:
|
validation |
Validation fraction used for every candidate fit. |
seed |
Base random seed. |
repeats |
Number of repeated seeds per candidate. |
ncores |
Number of cores used to fit candidates in parallel (see
|
verbose |
Print per-candidate progress. |
refit |
Refit the best configuration on the supplied data. |
A list of class densemlp_tuned with results (one row per
candidate, ranked best first by mean validation loss), best_config,
and, when refit = TRUE, best_fit.
set.seed(1) x <- data.frame(a = rnorm(80), b = rnorm(80)) y <- x$a - 0.5 * x$b + rnorm(80, sd = 0.1) tuned <- tune_densemlp( x, y, repeats = 1, grid = list(hidden_units = list(c(8), c(16, 8)), epochs = c(20)) ) tuned$best_configset.seed(1) x <- data.frame(a = rnorm(80), b = rnorm(80)) y <- x$a - 0.5 * x$b + rnorm(80, sd = 0.1) tuned <- tune_densemlp( x, y, repeats = 1, grid = list(hidden_units = list(c(8), c(16, 8)), epochs = c(20)) ) tuned$best_config