lr_anneal <- torch::lr_scheduler(
"lr_lambda",
initialize = function(
optimizer,
start_lr = 1e-7,
end_lr = 1e-1,
n_iters = 100,
log_spaced_intervals = TRUE,
last_epoch=-1,
verbose=FALSE) {
self$optimizer <- optimizer
self$end_lr <- end_lr
self$base_lrs <- start_lr
self$iters <- n_iters
self$multiplier <- (end_lr/start_lr)^(1/n_iters)
self$log_spaced_intervals <- log_spaced_intervals
super$initialize(optimizer, last_epoch, verbose)
},
get_lr = function() {
if (self$last_epoch > 0) {
lrs <- numeric(length(self$optimizer$param_groups))
for (i in seq_along(self$optimizer$param_groups)) {
if (self$log_spaced_intervals) {
lrs[i] <- self$optimizer$param_groups[[i]]$lr * self$multiplier
} else {
lrs[i] <- self$optimizer$param_groups[[i]]$lr * (self$end_lr / self$optimizer$param_groups[[i]]$lr) ^ (self$last_epoch / self$iters)
}
}
} else {
lrs <- as.numeric(self$base_lrs)
}
lrs
}
)
luz_callback_record_lr <- luz_callback(
name = "luz_callback_profile_lr",
initialize = function(steps, verbose) {
self$total_steps <- steps
self$steps <- 0
self$verbose <- if (is.null(verbose)) interactive() else verbose
if (self$verbose) {
self$pb <- cli::cli_progress_bar(
name = "Finding the learning rate",
total = self$total_steps,
type = "iterator",
.envir = self
)
}
},
on_train_batch_end = function() {
loss <- ctx$loss$opt$cpu()$item()
ctx$log("lr_finder", "lr", ctx$optimizers$opt$param_groups[[1]]$lr)
ctx$log("lr_finder", "loss", loss)
self$steps <- self$steps + 1
if (self$verbose) cli::cli_progress_update(id = self$pb)
if (self$steps >= self$total_steps) {
if (self$verbose) cli::cli_progress_done(id = self$pb)
rlang::interrupt()
}
}
)
#' Learning Rate Finder
#'
#' @param object An nn_module that has been setup().
#' @param data (dataloader) A dataloader created with torch::dataloader() used for learning rate finding.
#' @param steps (integer) The number of steps to iterate over in the learning rate finder. Default: 100.
#' @param start_lr (float) The smallest learning rate. Default: 1e-7.
#' @param end_lr (float) The highest learning rate. Default: 1e-1.
#' @param log_spaced_intervals (logical) Whether to divide the range between start_lr and end_lr into log-spaced intervals (alternative: uniform intervals). Default: TRUE
#' @param ... Other arguments passed to `fit`.
#' @param verbose Wether to show a progress bar during the process.
#'
#' @examples
#' if (torch::torch_is_installed()) {
#' library(torch)
#' ds <- torch::tensor_dataset(x = torch_randn(100, 10), y = torch_randn(100, 1))
#' dl <- torch::dataloader(ds, batch_size = 32)
#' model <- torch::nn_linear
#' model <- model %>% setup(
#' loss = torch::nn_mse_loss(),
#' optimizer = torch::optim_adam
#' ) %>%
#' set_hparams(in_features = 10, out_features = 1)
#' records <- lr_finder(model, dl, verbose = FALSE)
#' plot(records)
#' }
#' @returns A dataframe with two columns: learning rate and loss
#' @export
lr_finder <- function(object, data, steps = 100, start_lr = 1e-7, end_lr = 1e-1,
log_spaced_intervals = TRUE, ..., verbose = NULL) {
scheduler <- luz_callback_lr_scheduler(
lr_anneal,
verbose=FALSE,
start_lr = start_lr,
end_lr = end_lr,
n_iters = steps,
log_spaced_intervals = log_spaced_intervals,
call_on="on_train_batch_begin"
)
lr_profiler <- luz_callback_record_lr(steps, verbose)
fitted <- object %>%
set_opt_hparams(lr = start_lr) %>%
fit(...,
data = data,
epochs = 999999, # the callback will be responsible for interrupting
callbacks = list(scheduler, lr_profiler),
verbose = FALSE
)
lr_records <- data.frame(sapply(fitted$records$lr_finder, as.numeric))
class(lr_records) <- c("lr_records", class(lr_records))
lr_records
}
#' @export
print.lr_records <- function(x, ...) {
NextMethod()
}
utils::globalVariables(c("lr", "loss", "smoothed_loss"))
#' @export
plot.lr_records <- function(x, ...) {
rlang::check_installed("ggplot2")
x <- as.data.frame(x)
loss_exp_avg <- 0
beta <- 0.9
for (i in 1:nrow(x)) {
loss_exp_avg <- beta * loss_exp_avg + (1 - beta) * x$loss[i]
x$smoothed_loss[i] <- loss_exp_avg / (1 - beta^i)
}
ggplot2::ggplot(x, ggplot2::aes(x = lr)) +
ggplot2::geom_line(ggplot2::aes(y = loss), linetype="dotted", linewidth = 0.7) +
ggplot2::geom_line(ggplot2::aes(y = smoothed_loss), color="cyan", linewidth = 1) +
ggplot2::scale_x_log10() +
ggplot2::xlab("Learning Rate") +
ggplot2::ylab("Loss")
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.