Nothing
#' Functions to generate `nn_module` (language) expression
#'
#' @name nn_gens
#'
#' @section Feed-Forward Neural Network Module Generator:
#' The `ffnn_generator()` function generates a feed-forward neural network (FFNN) module expression
#' from the `torch` package in R. It allows customization of the FFNN architecture,
#' including the number of hidden layers, neurons, and activation functions.
#'
#' @param nn_name Character. Name of the generated FFNN module class. Default is `"DeepFFN"`.
#' @param hd_neurons Integer vector. Number of neurons in each hidden layer.
#' @param no_x Integer. Number of input features.
#' @param no_y Integer. Number of output features.
#' @param activations Activation function specifications for each hidden layer.
#' Can be:
#' - `NULL`: No activation functions.
#' - Character vector: e.g., `c("relu", "sigmoid")`.
#' - List: e.g., `act_funs(relu, elu, softshrink = args(lambd = 0.5))`.
#' - `activation_spec` object from `act_funs()`.
#'
#' If the length of `activations` is `1L`, this will be the activation throughout the architecture.
#'
#' @param output_activation Optional. Activation function for the output layer.
#' Same format as `activations` but should be a single activation.
#' @param bias Logical. Whether to use bias weights. Default is `TRUE`.
#'
#' @return A `torch` module expression representing the FFNN.
#'
#' @details The generated FFNN module will have the specified number of hidden layers,
#' with each layer containing the specified number of neurons. Activation functions
#' can be applied after each hidden layer as specified.
#' This can be used for both classification and regression tasks.
#'
#' The generated module properly namespaces all torch functions to avoid
#' polluting the global namespace.
#'
#' @examples
#' \donttest{
#' # FFNN
#' if (torch::torch_is_installed()) {
#' # Generate an MLP module with 3 hidden layers
#' ffnn_mod = ffnn_generator(
#' nn_name = "MyFFNN",
#' hd_neurons = c(64, 32, 16),
#' no_x = 10,
#' no_y = 1,
#' activations = 'relu'
#' )
#'
#' # Evaluate and instantiate
#' model = eval(ffnn_mod)()
#'
#' # More complex: With different activations
#' ffnn_mod2 = ffnn_generator(
#' nn_name = "MyFFNN2",
#' hd_neurons = c(128, 64, 32),
#' no_x = 20,
#' no_y = 5,
#' activations = act_funs(
#' relu,
#' selu,
#' sigmoid
#' )
#' )
#'
#' # Even more complex: Different activations and customized argument
#' # for the specific activation function
#' ffnn_mod2 = ffnn_generator(
#' nn_name = "MyFFNN2",
#' hd_neurons = c(128, 64, 32),
#' no_x = 20,
#' no_y = 5,
#' activations = act_funs(
#' relu,
#' selu,
#' softshrink = args(lambd = 0.5)
#' )
#' )
#'
#' # Customize output activation (softmax is useful for classification tasks)
#' ffnn_mod3 = ffnn_generator(
#' hd_neurons = c(64, 32),
#' no_x = 10,
#' no_y = 3,
#' activations = 'relu',
#' output_activation = act_funs(softmax = args(dim = 2L))
#' )
#' } else {
#' message("Torch not fully installed — skipping example")
#' }
#' }
#'
#' @importFrom rlang new_function call2 expr sym
#' @importFrom purrr map map2
#' @importFrom glue glue
#' @importFrom cli cli_abort
#'
#' @export
ffnn_generator = function(nn_name = "DeepFFN",
hd_neurons,
no_x,
no_y,
activations = NULL,
output_activation = NULL,
bias = TRUE) {
if (missing(hd_neurons) || is.null(hd_neurons) || length(hd_neurons) == 0L) {
hd_neurons = integer(0)
}
nodes = c(no_x, hd_neurons, no_y)
n_layers = length(nodes) - 1L
n_hidden = length(hd_neurons)
if (n_layers < 1L) {
cli_abort(c(
"{.arg hd_neurons} must contain at least one hidden layer size.",
"i" = "You provided {.val {hd_neurons}} (length {n_hidden})."
), class = "nn_module_error")
}
act_specs = eval_act_funs({{ activations }}, {{ output_activation }})
activations = act_specs$activations
output_activation = act_specs$output_activation
activation_spec = parse_activation_spec(activations, n_hidden)
activation_calls = process_activations(activation_spec, prefix = "nnf_")
if (!is.null(output_activation)) {
output_spec = parse_activation_spec(output_activation, 1L)
output_call = process_activations(output_spec, prefix = "nnf_")[[1]]
} else {
output_call = NULL
}
all_activation_calls = c(activation_calls, list(output_call))
# ---- Build initialize() ----
init_body = map2(
.x = seq_len(n_layers),
.y = map2(nodes[-length(nodes)], nodes[-1], c),
.f = function(i, dims) {
layer_name = if (i == n_layers) "out" else glue("fc{i}")
call2(
"=",
call2("$", expr(self), sym(layer_name)),
call2(
sym("nn_linear"),
!!!dims,
bias = bias,
.ns = "torch"
)
)
}
)
init = new_function(
args = pairlist(),
body = call2("{", !!!init_body)
)
# ---- Build forward() ----
forward_body_exprs = map(seq_len(n_layers), function(i) {
layer_name = if (i == n_layers) "out" else glue("fc{i}")
act_call_fn = all_activation_calls[[i]]
line1 = call2(
"=", expr(x),
call2(call2("$", expr(self), sym(layer_name)), expr(x))
)
if (is.null(act_call_fn)) {
list(line1)
} else {
line2 = call2("=", expr(x), act_call_fn(expr(x)))
list(line1, line2)
}
})
forward_body_exprs = c(unlist(forward_body_exprs, recursive = FALSE), list(expr(x)))
forward = new_function(
args = list(x = expr()),
body = call2("{", !!!forward_body_exprs)
)
call2(
sym("nn_module"),
nn_name, initialize = init, forward = forward,
.ns = "torch"
)
}
#' Check RNN Type Validity
#'
#' @param rnn_type Character. The RNN type to validate.
#' @param hd_neurons Integer vector. Hidden neurons (for error context).
#'
#' @importFrom cli cli_abort
#'
#' @noRd
check_rnn_type = function(rnn_type, hd_neurons) {
valid_types = c("rnn", "lstm", "gru")
rnn_type = tolower(rnn_type)
if (!rnn_type %in% valid_types) {
cli_abort(c(
"{.arg rnn_type} must be one of {.val {valid_types}}.",
x = "You provided {.val {rnn_type}}."
), class = "rnn_type_error")
}
n_rnn_layers = length(hd_neurons)
if (n_rnn_layers == 0) {
cli_abort(c(
"{.arg hd_neurons} must contain at least one hidden layer size.",
i = "RNNs require at least one recurrent layer."
), class = "rnn_module_error")
}
invisible(NULL)
}
#' @rdname nn_gens
#'
#' @section Recurrent Neural Network Module Generator:
#' The `rnn_generator()` function generates a recurrent neural network (RNN) module expression
#' from the `torch` package in R. It allows customization of the RNN architecture,
#' including the number of hidden layers, neurons, RNN type, activation functions,
#' and other parameters.
#'
#' @param nn_name Character. Name of the generated RNN module class. Default is `"DeepRNN"`.
#' @param hd_neurons Integer vector. Number of neurons in each hidden RNN layer.
#' @param no_x Integer. Number of input features.
#' @param no_y Integer. Number of output features.
#' @param rnn_type Character. Type of RNN to use. Must be one of `"rnn"`, `"lstm"`, or `"gru"`. Default is `"lstm"`.
#' @param activations Activation function specifications for each hidden layer.
#' Can be:
#' - `NULL`: No activation functions.
#' - Character vector: e.g., `c("relu", "sigmoid")`.
#' - List: e.g., `act_funs(relu, elu, softshrink = args(lambd = 0.5))`.
#' - `activation_spec` object from `act_funs()`.
#'
#' If the length of `activations` is `1L`, this will be the activation throughout the architecture.
#'
#' @param output_activation Optional. Activation function for the output layer.
#' Same format as `activations` but should be a single activation.
#' @param bias Logical. Whether to use bias weights. Default is `TRUE`
#' @param bidirectional Logical. Whether to use bidirectional RNN layers. Default is `TRUE`.
#' @param dropout Numeric. Dropout rate between RNN layers. Default is `0`.
#' @param ... Additional arguments (currently unused).
#'
#' @return A `torch` module expression representing the RNN.
#'
#' @details The generated RNN module will have the specified number of recurrent layers,
#' with each layer containing the specified number of hidden units. Activation functions
#' can be applied after each RNN layer as specified. The final output is taken from the
#' last time step and passed through a linear layer.
#'
#' The generated module properly namespaces all torch functions to avoid
#' polluting the global namespace.
#'
#' @examples
#' \donttest{
#' ## RNN
#' if (torch::torch_is_installed()) {
#' # Basic LSTM with 2 layers
#' rnn_mod = rnn_generator(
#' nn_name = "MyLSTM",
#' hd_neurons = c(64, 32),
#' no_x = 10,
#' no_y = 1,
#' rnn_type = "lstm",
#' activations = 'relu'
#' )
#'
#' # Evaluate and instantiate
#' model = eval(rnn_mod)()
#'
#' # GRU with different activations
#' rnn_mod2 = rnn_generator(
#' nn_name = "MyGRU",
#' hd_neurons = c(128, 64, 32),
#' no_x = 20,
#' no_y = 5,
#' rnn_type = "gru",
#' activations = act_funs(relu, elu, relu),
#' bidirectional = FALSE
#' )
#'
#' } else {
#' message("Torch not fully installed — skipping example")
#' }
#' }
#'
#' \dontrun{
#' ## Parameterized activation and dropout
#' # (Will throw an error due to `nnf_tanh()` not being available in `{torch}`)
#' # rnn_mod3 = rnn_generator(
#' # hd_neurons = c(100, 50, 25),
#' # no_x = 15,
#' # no_y = 3,
#' # rnn_type = "lstm",
#' # activations = act_funs(
#' # relu,
#' # leaky_relu = args(negative_slope = 0.01),
#' # tanh
#' # ),
#' # bidirectional = TRUE,
#' # dropout = 0.3
#' # )
#' }
#'
#' @importFrom rlang new_function call2 expr sym
#' @importFrom purrr map map2
#' @importFrom glue glue
#' @importFrom cli cli_abort
#'
#' @export
rnn_generator = function(nn_name = "DeepRNN",
hd_neurons,
no_x,
no_y,
rnn_type = "lstm",
bias = TRUE,
activations = NULL,
output_activation = NULL,
bidirectional = TRUE,
dropout = 0,
...) {
if (missing(hd_neurons) || is.null(hd_neurons) || length(hd_neurons) == 0L) {
hd_neurons = integer(0)
}
check_rnn_type(rnn_type, hd_neurons)
n_rnn_layers = length(hd_neurons)
act_specs = eval_act_funs({{ activations }}, {{ output_activation }})
activations = act_specs$activations
output_activation = act_specs$output_activation
activation_spec = parse_activation_spec(activations, n_rnn_layers)
activation_calls = process_activations(activation_spec, prefix = "nnf_")
if (!is.null(output_activation)) {
output_spec = parse_activation_spec(output_activation, 1L)
output_call = process_activations(output_spec, prefix = "nnf_")[[1]]
} else {
output_call = NULL
}
# ---- Build initialize() ----
input_sizes = c(no_x, hd_neurons[-n_rnn_layers] * (if (bidirectional) 2L else 1L))
rnn_layers = map2(seq_len(n_rnn_layers), input_sizes, function(i, input_size) {
layer_name = glue("rnn{i}")
hidden_size = hd_neurons[i]
layer_dropout = if (i < n_rnn_layers && dropout > 0) dropout else 0
rnn_fn_name = paste0("nn_", rnn_type)
rnn_call = call2(
sym(rnn_fn_name),
input_size = input_size,
hidden_size = hidden_size,
num_layers = 1L,
bias = bias,
batch_first = TRUE,
bidirectional = bidirectional,
dropout = layer_dropout,
.ns = "torch"
)
call2("=", call2("$", expr(self), sym(layer_name)), rnn_call)
})
final_hidden = hd_neurons[n_rnn_layers] * (if (bidirectional) 2L else 1L)
output_layer = call2(
"=", call2("$", expr(self), sym("out")),
call2(sym("nn_linear"), final_hidden, no_y, .ns = "torch")
)
init_body = c(rnn_layers, list(output_layer))
init = new_function(
args = pairlist(),
body = call2("{", !!!init_body)
)
# ---- Build forward() ----
rnn_forward_exprs = map(seq_len(n_rnn_layers), function(i) {
layer_name = glue("rnn{i}")
act_call_fn = activation_calls[[i]]
rnn_call_expr = call2(
"=", expr(x),
call2("[[",
call2(call2("$", expr(self), sym(layer_name)), expr(x)),
1L)
)
if (is.null(act_call_fn)) {
list(rnn_call_expr)
} else {
activation_expr = call2("=", expr(x), act_call_fn(expr(x)))
list(rnn_call_expr, activation_expr)
}
})
slice_expr = call2(
"=", expr(x),
call2(
"[", expr(x), expr(),
call2(call2("$", expr(x), sym("size")), 2L),
expr()
)
)
output_expr = call2(
"=", expr(x), call2(call2("$", expr(self), sym("out")), expr(x))
)
if (!is.null(output_call)) {
output_activation_expr = call2("=", expr(x), output_call(expr(x)))
forward_body_exprs = c(
unlist(rnn_forward_exprs, recursive = FALSE),
list(slice_expr, output_expr, output_activation_expr, expr(x))
)
} else {
forward_body_exprs = c(
unlist(rnn_forward_exprs, recursive = FALSE),
list(slice_expr, output_expr, expr(x))
)
}
forward = new_function(
args = alist(x = ),
body = call2("{", !!!forward_body_exprs)
)
call2(
sym("nn_module"),
nn_name,
initialize = init,
forward = forward,
.ns = "torch"
)
}
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.