diff --git a/DESCRIPTION b/DESCRIPTION index fbbc137a..ad437f0c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -51,7 +51,7 @@ Suggests: keras, tfestimators, callr -RoxygenNote: 7.0.2 +RoxygenNote: 7.1.1 Config/reticulate: list( packages = list( diff --git a/NAMESPACE b/NAMESPACE index 72bbc659..428ee647 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -89,6 +89,7 @@ export(np_array) export(parse_arguments) export(parse_flags) export(run_dir) +export(set_random_seed) export(shape) export(tensorboard) export(tf) diff --git a/R/seed.R b/R/seed.R index f4a59fef..61ab4b21 100644 --- a/R/seed.R +++ b/R/seed.R @@ -6,7 +6,6 @@ #' and TensorFlow. GPU computations and CPU parallelism will also be disabled by #' default. #' -#' @inheritParams reticulate py_set_seed #' #' @param seed A single value, interpreted as an integer #' @param disable_gpu `TRUE` to disable GPU execution (see *Parallelism* below). @@ -50,8 +49,15 @@ use_session_with_seed <- function(seed, disable_parallel_cpu = TRUE, quiet = FALSE) { - if (tf_version() >= "2.0") + + msg <- "use_session_with_seed will be deprecated in the future. use set_random_seed instead." + if (tf_version() >= "2.0") { tf <- tf$compat$v1 + warning(msg) + } + + if (tf_version() >= "2.3") + stop(msg) # cast seed to integer seed <- as.integer(seed) @@ -123,3 +129,50 @@ use_session_with_seed <- function(seed, # return session invisibly invisible(sess) } + +#' Set random seed for TensorFlow +#' +#' Sets all random seeds needed to make TensorFlow code reproducible. +#' +#' @details +#' +#' This function should be used instead of [use_session_with_seed()] if +#' you are using TensorFlow >= 2.0, as the concept of `session` doesn't +#' really make sense anymore. +#' +#' This functions sets: +#' +#' - The R random seed with [set.seed()]. +#' - The python and Numpy seeds via ([reticulate::py_set_seed()]). +#' - The TensorFlow seed with (`tf$random$set_seed()`) +#' +#' It also optionally disables the GPU execution as this is a potential +#' source of non-reproducibility. +#' +#' @param seed A single value, interpreted as an integer +#' @param disable_gpu `TRUE` to disable GPU execution (see *Parallelism* below). +#' +#' @export +set_random_seed <- function(seed, disable_gpu = TRUE) { + + if (tf_version() < "2.0") + stop("set_random_seed only works for TF >= 2.0") + + # cast seed to integer + seed <- as.integer(seed) + + # set R random seed + set.seed(seed) + + # set Python/NumPy random seed + py_set_seed(seed) + + # set tensorflow random seed + tensorflow::tf$random$set_seed(seed) + + if (disable_gpu) { + Sys.setenv(CUDA_VISIBLE_DEVICES = "-1") + } + + invisible(NULL) +} diff --git a/man/install_tensorflow.Rd b/man/install_tensorflow.Rd index 832fe2d8..334efd7b 100644 --- a/man/install_tensorflow.Rd +++ b/man/install_tensorflow.Rd @@ -49,8 +49,8 @@ only occur within RStudio).} \item{conda_python_version}{the python version installed in the created conda environment. Python 3.6 is installed by default.} -\item{...}{other arguments passed to \code{\link[reticulate:conda_install]{reticulate::conda_install()}} or -\code{\link[reticulate:virtualenv_install]{reticulate::virtualenv_install()}}.} +\item{...}{other arguments passed to \code{\link[reticulate:conda-tools]{reticulate::conda_install()}} or +\code{\link[reticulate:virtualenv-tools]{reticulate::virtualenv_install()}}.} } \description{ Install TensorFlow and its dependencies diff --git a/man/reexports.Rd b/man/reexports.Rd index ef58fc30..83fc2ec3 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -26,8 +26,8 @@ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ - \item{reticulate}{\code{\link[reticulate]{\%as\%}}, \code{\link[reticulate]{array_reshape}}, \code{\link[reticulate]{dict}}, \code{\link[reticulate]{import}}, \code{\link[reticulate]{iterate}}, \code{\link[reticulate]{np_array}}, \code{\link[reticulate]{tuple}}, \code{\link[reticulate]{use_condaenv}}, \code{\link[reticulate]{use_python}}, \code{\link[reticulate]{use_virtualenv}}} + \item{reticulate}{\code{\link[reticulate:with-as-operator]{\%as\%}}, \code{\link[reticulate]{array_reshape}}, \code{\link[reticulate]{dict}}, \code{\link[reticulate]{import}}, \code{\link[reticulate]{iterate}}, \code{\link[reticulate]{np_array}}, \code{\link[reticulate]{tuple}}, \code{\link[reticulate:use_python]{use_condaenv}}, \code{\link[reticulate]{use_python}}, \code{\link[reticulate:use_python]{use_virtualenv}}} - \item{tfruns}{\code{\link[tfruns]{flag_boolean}}, \code{\link[tfruns]{flag_integer}}, \code{\link[tfruns]{flag_numeric}}, \code{\link[tfruns]{flag_string}}, \code{\link[tfruns]{flags}}, \code{\link[tfruns]{run_dir}}} + \item{tfruns}{\code{\link[tfruns:flags]{flag_boolean}}, \code{\link[tfruns:flags]{flag_integer}}, \code{\link[tfruns:flags]{flag_numeric}}, \code{\link[tfruns:flags]{flag_string}}, \code{\link[tfruns]{flags}}, \code{\link[tfruns]{run_dir}}} }} diff --git a/man/set_random_seed.Rd b/man/set_random_seed.Rd new file mode 100644 index 00000000..eb563dd4 --- /dev/null +++ b/man/set_random_seed.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/seed.R +\name{set_random_seed} +\alias{set_random_seed} +\title{Set random seed for TensorFlow} +\usage{ +set_random_seed(seed, disable_gpu = TRUE) +} +\arguments{ +\item{seed}{A single value, interpreted as an integer} + +\item{disable_gpu}{\code{TRUE} to disable GPU execution (see \emph{Parallelism} below).} +} +\description{ +Sets all random seeds needed to make TensorFlow code reproducible. +} +\details{ +This function should be used instead of \code{\link[=use_session_with_seed]{use_session_with_seed()}} if +you are using TensorFlow >= 2.0, as the concept of \code{session} doesn't +really make sense anymore. + +This functions sets: +\itemize{ +\item The R random seed with \code{\link[=set.seed]{set.seed()}}. +\item The python and Numpy seeds via (\code{\link[reticulate:py_set_seed]{reticulate::py_set_seed()}}). +\item The TensorFlow seed with (\code{tf$random$set_seed()}) +} + +It also optionally disables the GPU execution as this is a potential +source of non-reproducibility. +} diff --git a/man/tf.Rd b/man/tf.Rd index f11eadef..9427e7de 100644 --- a/man/tf.Rd +++ b/man/tf.Rd @@ -4,7 +4,9 @@ \name{tf} \alias{tf} \title{Main TensorFlow module} -\format{TensorFlow module} +\format{ +TensorFlow module +} \usage{ tf } diff --git a/man/tf_extract_opts.Rd b/man/tf_extract_opts.Rd index 35ef365e..2da5b7d7 100644 --- a/man/tf_extract_opts.Rd +++ b/man/tf_extract_opts.Rd @@ -9,7 +9,8 @@ tf_extract_opts( ..., one_based = getOption("tensorflow.extract.one_based", TRUE), inclusive_stop = getOption("tensorflow.extract.inclusive_stop", TRUE), - disallow_out_of_bounds = getOption("tensorflow.extract.dissallow_out_of_bounds", TRUE), + disallow_out_of_bounds = getOption("tensorflow.extract.dissallow_out_of_bounds", + TRUE), warn_tensors_passed_asis = getOption("tensorflow.extract.warn_tensors_passed_asis", TRUE), warn_negatives_pythonic = getOption("tensorflow.extract.warn_negatives_pythonic", diff --git a/tests/testthat/test-seed.R b/tests/testthat/test-seed.R index c75bcf19..c1b9ea0f 100644 --- a/tests/testthat/test-seed.R +++ b/tests/testthat/test-seed.R @@ -1,6 +1,9 @@ test_that("use_session_with_seed works", { skip_if_no_tensorflow() + if (tf_version() >= "2.3") + skip("use_session_with seed doesn't work with TF >= 2.3") + f <- function() { library(keras) use_session_with_seed(seed = 1) @@ -14,3 +17,24 @@ test_that("use_session_with_seed works", { expect_equal(run1, run2) }) + +test_that("set_random_seed", { + + skip_if_no_tensorflow() + + if (tf_version() < "2.0") + skip("set_random_seed only works for TF >= 2.0") + + f <- function() { + library(keras) + tensorflow::set_random_seed(seed = 1) + model <- keras_model_sequential() %>% + layer_dense(units = 1) + predict(model, matrix(1, ncol = 1)) + } + + run1 <- callr::r(f) + run2 <- callr::r(f) + + expect_equal(run1, run2) +})