A practical workflow with neuralnetwork

neuralnetwork fits compact multilayer perceptrons with ordinary R inputs. It is aimed at tabular regression and classification problems where formula interfaces, predictable outputs, validation, tuning, and diagnostics matter as much as the training algorithm.

library(neuralnetwork)

Multiclass Classification

Start with the formula interface and automatic defaults. For small tabular data, hidden = "auto" and optimizer = "auto" are usually enough for a first pass.

fit_class <- nn_fit(
  Species ~ .,
  data = iris,
  hidden = "auto",
  optimizer = "auto",
  epochs = 10,
  validation_split = 0.2,
  seed = 1,
  verbose = FALSE
)

fit_class
#> neuralnetwork model
#>   Task:       classification
#>   Layers:     4 -> 4 -> 3
#>   Optimizer:  lbfgs | activation: tanh | backend: rcpp
#>   Loss:       cross_entropy
#>   Trained:    13 epochs | best epoch: 1
#>   Final:      train loss 0.014447 | accuracy 0.99167
#>               validation loss 0.38622 | validation accuracy 0.9

The printed model is meant to be read at a glance. It shows the task, architecture, optimizer, loss, backend, number of epochs, best epoch, and final training or validation metrics.

Use predict() for classes or probabilities.

predict(fit_class, iris[1:5, ], type = "class")
#> [1] setosa setosa setosa setosa setosa
#> Levels: setosa versicolor virginica
round(predict(fit_class, iris[1:5, ], type = "prob"), 3)
#>      setosa versicolor virginica
#> [1,]      1          0         0
#> [2,]      1          0         0
#> [3,]      1          0         0
#> [4,]      1          0         0
#> [5,]      1          0         0

nn_evaluate() returns task-aware metrics. Multiclass classification includes accuracy, balanced accuracy, macro precision, macro recall, macro F1, and log loss.

ev_class <- nn_evaluate(fit_class, iris)
ev_class
#> neuralnetwork evaluation
#> Metrics:
#>             metric    value
#>           accuracy  0.97333
#>  balanced_accuracy  0.97333
#>    macro_precision  0.97359
#>       macro_recall  0.97333
#>           macro_f1   0.9734
#>           log_loss 0.088802
#> 
#> Confusion matrix:
#>             estimate
#> truth        setosa versicolor virginica
#>   setosa         49          1         0
#>   versicolor      0         48         2
#>   virginica       0          1        49

For imbalanced classification problems, balanced accuracy or F1 is often more informative than raw accuracy. For calibrated probability workflows, inspect log loss as well.

Binary Classification and Weights

Two-class outcomes use a one-output sigmoid model internally. The public prediction API still returns a two-column probability matrix.

iris_binary <- subset(iris, Species != "virginica")
row_weight <- ifelse(iris_binary$Species == "versicolor", 1.5, 1)

fit_binary <- nn_fit(
  Species ~ .,
  data = iris_binary,
  hidden = c(6, 3),
  optimizer = "adam",
  epochs = 8,
  batch_size = 16,
  learning_rate = 0.01,
  sample_weight = row_weight,
  class_weight = "balanced",
  gradient_clip = 5,
  validation_split = 0.2,
  seed = 2,
  verbose = FALSE
)

round(predict(fit_binary, iris_binary[1:5, ], type = "prob"), 3)
#>      setosa versicolor
#> [1,]  0.561      0.439
#> [2,]  0.605      0.395
#> [3,]  0.538      0.462
#> [4,]  0.569      0.431
#> [5,]  0.551      0.449
nn_evaluate(fit_binary, iris_binary)
#> neuralnetwork evaluation
#> Metrics:
#>             metric   value
#>           accuracy    0.95
#>  balanced_accuracy    0.95
#>    macro_precision 0.95018
#>       macro_recall    0.95
#>           macro_f1 0.94999
#>           log_loss 0.56381
#>        sensitivity    0.94
#>        specificity    0.96
#>          precision 0.95918
#>             recall    0.94
#>                 f1 0.94949
#> 
#> Confusion matrix:
#>             estimate
#> truth        setosa versicolor
#>   setosa         48          2
#>   versicolor      3         47

Regression

Regression follows the same shape. By default, regression targets are scaled for training and predictions are returned on the original scale.

fit_reg <- nn_fit(
  mpg ~ wt + hp + disp,
  data = mtcars,
  hidden = c(8, 4),
  optimizer = "adam",
  epochs = 25,
  batch_size = 8,
  learning_rate = 0.01,
  validation_split = 0.2,
  seed = 3,
  verbose = FALSE
)

fit_reg
#> neuralnetwork model
#>   Task:       regression
#>   Layers:     3 -> 8 -> 4 -> 1
#>   Optimizer:  adam | activation: sigmoid | backend: rcpp
#>   Loss:       squared_error
#>   Trained:    25 epochs | best epoch: 23
#>   Final:      train loss 0.11705 | rmse 3.0527
#>               validation loss 0.031615 | validation rmse 1.5865
round(predict(fit_reg, mtcars[1:5, ]), 2)
#> [1] 23.12 22.42 24.74 19.54 16.42
nn_evaluate(fit_reg, mtcars)
#> neuralnetwork evaluation
#> Metrics:
#>  metric   value
#>    rmse  3.0025
#>     mae   2.281
#>     rsq 0.74381

Robust Regression

Squared error is the default regression loss. If a few observations may be unusually influential, use Huber loss.

mtcars_outlier <- mtcars
mtcars_outlier$mpg[1] <- mtcars_outlier$mpg[1] + 40

fit_huber <- nn_fit(
  mpg ~ wt + hp,
  data = mtcars_outlier,
  hidden = 4,
  optimizer = "adam",
  loss = "huber",
  huber_delta = 1,
  epochs = 20,
  batch_size = 8,
  learning_rate = 0.01,
  seed = 4,
  verbose = FALSE
)

summary(fit_huber)
#> neuralnetwork summary
#>   Task:       regression
#>   Layers:     2 -> 4 -> 1
#>   Optimizer:  adam | activation: sigmoid | backend: rcpp
#>   Loss:       huber (delta=1)
#>   Epochs:     20 | best epoch: 20
#> 
#> Final training row:
#>  epoch train_loss validation_loss train_metric validation_metric gradient_norm
#>     20    0.18135              NA       7.6733                NA       0.41666
#>  learning_rate backtracked
#>           0.01       FALSE

Training Controls

The training loop supports dropout, L2 regularization, gradient clipping, learning-rate decay, validation splits, early stopping, and callbacks. This example stops after two epochs so the mechanism is visible without making the vignette slow.

epochs_seen <- 0L

fit_callback <- nn_fit(
  mpg ~ wt + hp,
  data = mtcars,
  hidden = 4,
  optimizer = "adam",
  epochs = 20,
  batch_size = 8,
  learning_rate = 0.01,
  l2 = 1e-4,
  dropout = 0.05,
  gradient_clip = 5,
  validation_split = 0.2,
  callbacks = function(state) {
    epochs_seen <<- state$epoch
    if (state$epoch >= 2) {
      return(list(stop = TRUE))
    }
    NULL
  },
  seed = 5,
  verbose = FALSE
)

fit_callback
#> neuralnetwork model
#>   Task:       regression
#>   Layers:     2 -> 4 -> 1
#>   Optimizer:  adam | activation: sigmoid | backend: rcpp
#>   Loss:       squared_error
#>   Trained:    2 epochs | best epoch: 2
#>   Final:      train loss 0.52135 | rmse 6.4176
#>               validation loss 0.13129 | validation rmse 3.2201
#>   Stopped:    callback

Some useful starting points:

  • Use validation_split = 0.2 when you want validation loss, early stopping, or validation-based tuning.
  • Use gradient_clip when gradients can spike.
  • Use dropout and l2 when the model begins to overfit.
  • Use learning_rate_decay or a callback when a fixed learning rate is too blunt.

Tuning and Cross-Validation

Use nn_tune() for a compact grid search. Classification metrics include accuracy, balanced_accuracy, f1, and log_loss. Regression metrics include rmse, mae, and rsq.

tuned <- nn_tune(
  Species ~ .,
  data = iris,
  grid = list(
    hidden = list(4, c(6, 3)),
    learning_rate = c(0.01)
  ),
  metric = "balanced_accuracy",
  epochs = 4,
  validation_split = 0.2,
  seed = 6,
  verbose = FALSE
)

tuned
#> neuralnetwork tuning result
#>   Candidates: 2
#>   Objective:  balanced_accuracy (higher is better)
#>   Best score: 0.91534
#> 
#> Top candidates:
#>  hidden learning_rate            metric   score rank
#>       4          0.01 balanced_accuracy 0.91534    1
#>    6, 3          0.01 balanced_accuracy 0.88889    2
tuned$best_params
#>   hidden learning_rate
#> 1      4          0.01

Use nn_cv() when you want fold-level estimates.

cv <- nn_cv(
  Species ~ .,
  data = iris,
  k = 3,
  metric = "f1",
  hidden = 4,
  epochs = 2,
  seed = 7,
  verbose = FALSE
)

cv
#> neuralnetwork cross-validation
#>   Folds:   3
#>   Repeats: 1
#> 
#>    metric    mean      sd
#>  macro_f1 0.86362 0.10213

Permutation Importance

Permutation importance measures how much a metric changes when one feature is shuffled.

imp <- nn_permutation_importance(
  fit_reg,
  mtcars,
  metric = "mae",
  n_repeats = 2,
  seed = 8
)

imp
#> neuralnetwork permutation importance
#>   Metric:  mae
#>   Repeats: 2
#> 
#>  feature importance baseline permuted metric n_repeats
#>       wt    0.98792    2.281   3.2689    mae         2
#>     disp    0.57524    2.281   2.8562    mae         2
#>       hp    0.54879    2.281   2.8298    mae         2

Save, Load, and Inspect

Models are ordinary R objects. Use nn_save() and nn_load() when you want a small checked wrapper around saveRDS() and readRDS().

model_path <- tempfile(fileext = ".rds")
nn_save(fit_reg, model_path)
fit_loaded <- nn_load(model_path)

all.equal(
  predict(fit_reg, mtcars[1:3, ]),
  predict(fit_loaded, mtcars[1:3, ])
)
#> [1] TRUE

The package also includes compatibility helpers for common nnet and neuralnet workflows.

nn_class_ind(iris$Species[1:4])
#>      setosa versicolor virginica
#> [1,]      1          0         0
#> [2,]      1          0         0
#> [3,]      1          0         0
#> [4,]      1          0         0

computed <- nn_compute(fit_class, iris[1:2, ])
names(computed$neurons)
#> [1] "input"   "hidden1" "output"
round(computed$net.result, 3)
#>      setosa versicolor virginica
#> [1,]      1          0         0
#> [2,]      1          0         0

Function Map

Use this as a quick map when moving from older shallow-network workflows.

Need Use
Fit a regression or classification network nn_fit()
Fit a no-hidden-layer multinomial model nn_multinom()
Get class probabilities or numeric predictions predict()
Score a fitted model nn_evaluate()
Tune a small grid nn_tune()
Run repeated k-fold validation nn_cv()
Estimate feature importance nn_permutation_importance()
Get compute-style hidden activations nn_compute()
Get generalized weights nn_generalized_weights()
Save and reload a model nn_save() and nn_load()

For most projects, start with nn_fit(), inspect nn_evaluate(), and add nn_tune() or nn_cv() only when the first model is promising enough to justify the extra computation.

For reference-style help, see ?neuralnetwork, ?neuralnetwork-metrics, ?neuralnetwork-callbacks, and ?neuralnetwork-objects.