--- title: "Custom Activation Function" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Custom Activation Function} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE, warning=FALSE, message=FALSE} knitr::opts_chunk$set( eval = identical(Sys.getenv("NOT_CRAN"), "true"), fig.width = 7, fig.height = 5, warning = FALSE, message = FALSE ) library(kindling) ``` # Rationale The biggest strength of `{kindling}` when modelling neural networks is its versatility — it inherits `{torch}`'s versatility while being human friendly, including the ability to apply custom optimizer functions, loss functions, and per-layer activation functions. Learn more: [https://kindling.joshuamarie.com/articles/special-cases](https://kindling.joshuamarie.com/articles/special-cases). With `act_funs()`, you are not limited to the activation functions available in `{torch}`'s namespace. Use `new_act_fn()` to wrap any compatible function into a validated custom activation. This feature, however, is only available on version 0.3.0 and above. # Function to use To do this, use `new_act_fn()`. It takes a user-supplied function, validates it against a small dummy tensor at *definition time* (a dry-run probe), and wraps it in a call-time type guard. This means errors surface early — before your model ever starts training. The function you supply must: - Accept at least one argument (the input tensor). - Return a `torch_tensor`. ## Basic Usage Currently, `nnf_tanh` doesn't exist in the `{torch}` namespace, so `tanh` is not a valid argument to `act_funs()`. With `new_act_fn()`, you can wrap `torch::torch_tanh()` to make it usable. Here's a basic example that wraps `torch::torch_tanh()` as a custom activation: ``` r hyper_tan = new_act_fn(\(x) torch::torch_tanh(x)) ``` You can also pass it directly into `act_funs()`, just like any built-in activation: ``` r act_funs(relu, elu, new_act_fn(\(x) torch::torch_tanh(x))) ``` ## Using Custom Activations in a Model Naturally, functions for modelling, like `ffnn()`, accept `act_funs()` into the `activations` argument. Again, you can pass a custom activation function within `new_act_fn()`, then pass it through `act_funs()`. Here's a basic example: ```{r} model = ffnn( Sepal.Length ~ ., data = iris[, 1:4], hidden_neurons = c(64, 32, 16), activations = act_funs( relu, silu, new_act_fn(\(x) torch::torch_tanh(x)) ), epochs = 50 ) model ``` Each element of `act_funs()` corresponds to one hidden layer, in order. Here, the first hidden layer uses ReLU, the second uses SiLU (Swish), and the third uses Tanh. You can also use a single custom activation recycled across all layers: ```{r} ffnn( Sepal.Length ~ ., data = iris[, 1:4], hidden_neurons = c(64, 32), activations = act_funs(new_act_fn(\(x) torch::torch_tanh(x))), epochs = 50 ) ``` ## Skipping the Dry-Run Probe By default, `new_act_fn()` runs a quick dry-run with a small dummy tensor to validate your function before training. You can disable this with `probe = FALSE`, though this is generally not recommended: ```{r, eval = FALSE} my_act = new_act_fn(\(x) torch::torch_tanh(x), probe = FALSE) ``` ## Naming Your Custom Activation You can provide a human-readable name via `.name`, which is used in print output and diagnostics: ```{r} my_act = new_act_fn(\(x) torch::torch_tanh(x), .name = "my_tanh") ``` Here's a simple application: ```{r} ffnn( Sepal.Length ~ ., data = iris[, 1:4], hidden_neurons = c(64, 32), activations = act_funs( relu, new_act_fn(\(x) torch::torch_tanh(x), .name = "hyper_tanh") ), epochs = 50 ) ``` # Error Handling `new_act_fn()` is designed to fail loudly and early. Common errors include: 1. Function returns a non-tensor. This will error at definition time: ```{r, error = TRUE} new_act_fn(\(x) as.numeric(x)) ``` 2. Function accepts no arguments. This will error immediately: ```{r, error = TRUE} new_act_fn(function() torch::torch_zeros(2)) ``` These checks ensure your model's architecture is valid before any data ever flows through it. # Summary | Feature | Details | |---|---| | Wraps any R function | Must accept a tensor, return a tensor | | Dry-run probe | Validates at definition time (`probe = TRUE` by default) | | Call-time guard | Type-checks output on every forward pass | | Compatible with `act_funs()` | Use alongside built-in activations freely | | Closures supported | Parametric activations work naturally |