Skip to content

Commit

Permalink
Refactor plot_n_important_coeffs, unexport others (#68)
Browse files Browse the repository at this point in the history
* rewrite plot_n_important_coefficients as nn2poly method

* add dontrun also to luz example

* unexport plot_ functions

* rename source vignettes to get proper syntax highlighting from editors

* reorganize dependencies by type

* rename vignette sources to prevent pkgdown from compiling them

* Added example to plot.nn2poly and fixed bug when n=NULL

* Added test for plot.nn2poly when n=NULL

* Added details.

* Reordered checks for keep_layers and vector in plot function as they needed to be sequential. Also fixed previous bug with n=NULL as dim instead of length had to be used. Added tests for those situations.

* Removed the need to use tidytext::reorder_within

* Removed the need to suggest tidytext by substituting scale_x_reordered.

* Removed faceting when teher is a single polynomial output

---------

Co-authored-by: moralapablo <moralapablo@gmail.com>
  • Loading branch information
Enchufa2 and moralapablo committed Jan 17, 2024
1 parent 45ca38d commit 6a0584a
Show file tree
Hide file tree
Showing 33 changed files with 680 additions and 561 deletions.
23 changes: 7 additions & 16 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,16 @@ Encoding: UTF-8
LazyData: true
Depends: R (>= 3.5.0)
Imports:
Rcpp,
generics,
matrixStats,
pracma,
Rcpp
pracma
Suggests:
cowplot,
ggplot2,
keras,
knitr,
luz,
patchwork,
reticulate,
rmarkdown,
R6,
tensorflow,
testthat (>= 3.0.0),
tidytext,
torch,
vdiffr
keras, tensorflow, reticulate,
luz, torch,
cowplot, ggplot2, patchwork,
testthat (>= 3.0.0), vdiffr,
knitr, rmarkdown
LinkingTo:
Rcpp,
RcppArmadillo
Expand Down
6 changes: 1 addition & 5 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ S3method(add_constraints,luz_module_generator)
S3method(fit,nn2poly)
S3method(nn2poly,default)
S3method(nn2poly,list)
S3method(plot_taylor_and_activation_potentials,default)
S3method(plot_taylor_and_activation_potentials,list)
S3method(plot,nn2poly)
S3method(predict,nn2poly)
export(add_constraints)
export(fit)
export(luz_model_sequential)
export(nn2poly)
export(plot_diagonal)
export(plot_n_important_coeffs)
export(plot_taylor_and_activation_potentials)
importFrom(Rcpp,sourceCpp)
importFrom(generics,fit)
importFrom(stats,density)
Expand Down
3 changes: 2 additions & 1 deletion R/helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#' @seealso [add_constraints()]
#'
#' @examples
#' \dontrun{
#' if (requireNamespace("luz", quietly=TRUE)) {
#' # Create a NN using luz/torch as a sequential model
#' # with 3 fully connected linear layers,
Expand All @@ -35,7 +36,7 @@
#' # Check that the nn is of class nn_squential
#' class(nn)
#' }
#'
#' }
#'
#' @export
luz_model_sequential <- function(...) {
Expand Down
152 changes: 0 additions & 152 deletions R/nn2poly.R
Original file line number Diff line number Diff line change
Expand Up @@ -176,155 +176,3 @@ nn2poly.default <- function(object, ...) {

nn2poly(object, ...)
}

#' Predict method for \code{nn2poly} objects.
#'
#' Predicted values obtained with a nn2poly object on given data.
#'
#' @inherit eval_poly
#' @param object Object of class inheriting from 'nn2poly'.
#' @param layers Vector containing the chosen layers from \code{object} to be
#' evaluated. If set to \code{NULL}, all layers are computed. Default is set
#' to \code{NULL}.
#' @param ... Further arguments passed to or from other methods.
#'
#' @details
#' Internally uses `eval_poly()` to obtain the predictions. However, this only
#' works with a objects of class \code{nn2poly} while `eval_poly()` can be used
#' with a manually created polynomial in list form.
#'
#' When \code{object} contains all the internal polynomials also, as given by
#' \code{nn2poly(object, keep_layers = TRUE)}, it is important to note that there
#' are two polynomial items per layer (input/output). These polynomial items will
#' also contain several polynomials of the same structure, one per neuron in the
#' layer, stored as matrix rows in \code{$values}. Please see the NN2Poly
#' original paper for more details.
#'
#' Note also that "linear" layers will contain the same input and output results
#' as Taylor expansion is not used and thus the polynomials are also the same.
#' Because of this, in the situation of evaluating multiple layers we provide
#' the final layer with "input" and "output" even if they are the same, for
#' consistency.
#'
#' @seealso [nn2poly()]: function that obtains the \code{nn2poly} polynomial
#' object, [eval_poly()]: function that can evaluate polynomials in general,
#' [stats::predict()]: generic predict function.
#'
#' @return Returns a matrix or list of matrices with the evaluation of each
#' polynomial at each layer as given by the provided \code{object} of class
#' \code{nn2poly}.
#'
#' If \code{object} contains the polynomials of the last layer, as given by
#' \code{nn2poly(object, keep_layers = FALSE)}, then the output is a matrix with
#' the evaluation of each data point on each polynomial. In this matrix, each
#' column represents the evaluation of a polynomial and each column corresponds
#' to each point in the new data to be evaluated.
#'
#' If \code{object} contains all the internal polynomials also, as given by
#' \code{nn2poly(object, keep_layers = TRUE)}, then the output is a list of
#' layers (represented by \code{layer_i}), where each one is another list with
#' \code{input} and \code{output} elements, where each one contains a matrix
#' with the evaluation of the "input" or "output" polynomial at the given layer,
#' as explained in the case without internal polynomials.
#'
#'
#' @examples
#' # Build a NN structure with random weights, with 2 (+ bias) inputs,
#' # 4 (+bias) neurons in the first hidden layer with "tanh" activation
#' # function, 4 (+bias) neurons in the second hidden layer with "softplus",
#' # and 1 "linear" output unit
#'
#' weights_layer_1 <- matrix(rnorm(12), nrow = 3, ncol = 4)
#' weights_layer_2 <- matrix(rnorm(20), nrow = 5, ncol = 4)
#' weights_layer_3 <- matrix(rnorm(5), nrow = 5, ncol = 1)
#'
#' # Set it as a list with activation functions as names
#' nn_object = list("tanh" = weights_layer_1,
#' "softplus" = weights_layer_2,
#' "linear" = weights_layer_3)
#'
#' # Obtain the polynomial representation (order = 3) of that neural network
#' final_poly <- nn2poly(nn_object, max_order = 3)
#'
#' # Define some new data, it can be vector, matrix or dataframe
#' newdata <- matrix(rnorm(10), ncol = 2, nrow = 5)
#'
#' # Predict using the obtained polynomial
#' predict(object = final_poly, newdata = newdata)
#'
#' # Change the last layer to have 3 outputs (as in a multiclass classification)
#' # problem
#' weights_layer_4 <- matrix(rnorm(20), nrow = 5, ncol = 4)
#'
#' # Set it as a list with activation functions as names
#' nn_object = list("tanh" = weights_layer_1,
#' "softplus" = weights_layer_2,
#' "linear" = weights_layer_4)
#'
#' # Obtain the polynomial representation of that neural network
#' # Polynomial representation of each hidden neuron is given by
#' final_poly <- nn2poly(nn_object, max_order = 3, keep_layers = TRUE)
#'
#' # Define some new data, it can be vector, matrix or dataframe
#' newdata <- matrix(rnorm(10), ncol = 2, nrow = 5)
#'
#' # Predict using the obtained polynomials (for all layers)
#' predict(object = final_poly, newdata = newdata)
#'
#' # Predict using the obtained polynomials (for chosen layers)
#' predict(object = final_poly, newdata = newdata, layers = c(2,3))
#'
#' @export
predict.nn2poly <- function(object, newdata, layers = NULL, ...) {
if (length(class(object)) > 1)
return(NextMethod())

# Check if object is a single polynomial or a list of polynomials.
# If we get only the output layer, then it has to be a list with 2 elements,
# values and labels. We check one of them:
if (!is.null(object$labels)){
# If we have a final polynomial, directly evaluate the results:
result <- eval_poly(poly = object, newdata = newdata)
} else {
# Multiple layer case:

# If layer = NULL, set all layers to be used
if (is.null(layers)){
layers <- 1:length(object)
}

# Check if a vector or number is given
if (!(is.atomic(layers) & is.numeric(layers))){
stop("Argument layers is neither a numeric vector nor NULL.",
call. = FALSE
)
}
# Check that selected layers are within object dimension
# To do so, we need to check if "layer_maxvalue" exists:
final_layer <- paste0("layer_", max(layers))
if (is.null(object[[final_layer]])){
stop("Argument layers contains elements that exceed number of layers in nn2poly object.",
call. = FALSE
)
}

# Make sure layers are ordered, just for consistent output
layers <- sort(layers)

# Compute results for the given layers.
result <- list()
for (i in layers){
layer_name <- paste0("layer_", i)
result[[layer_name]] <- list()
result[[layer_name]][["input"]] <- eval_poly(
poly = object[[layer_name]][["input"]],
newdata = newdata)
result[[layer_name]][["output"]] <- eval_poly(
poly = object[[layer_name]][["output"]],
newdata = newdata)
}
}

return(result)

}
Loading

0 comments on commit 6a0584a

Please sign in to comment.