Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor plot_n_important_coeffs, unexport others #68

Merged
merged 13 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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