Open njtierney opened 2 years ago
Oh this is juicy, turns out that tensorflow implements the same use of SD as numpy.
Numpy uses N, not N-1 , when computing variance R uses N-1 when computing variance
If we want to make greta "R equivalent", then we need to use N - 1. Which to me makes sense, since greta is about providing an interface into TF while remaining familiar to an R user. We could also have an argument where you can control which one you use...which adds a bit of confusion / spice to everything here.
Here's a bit of proof about this, using the current implementation of it in this branch:
# ugh, OK so tensorflow divides by N, not N - 1
regular_var <- function(x, n_minus_1 = TRUE){
total_ss <- sum((x - mean(x))^2)
if (n_minus_1){
return(total_ss / (length(x) - 1))
} else if (!n_minus_1){
return(total_ss / length(x))
}
}
regular_sd <- function(x, n_minus_1 = TRUE){
var <- regular_var(x, n_minus_1 = n_minus_1)
sqrt(var)
}
# this is what R does
sd(1:100)
#> [1] 29.01149
regular_sd(1:100, n_minus_1 = TRUE)
#> [1] 29.01149
# this is what numpy does
# https://numpy.org/doc/stable/reference/generated/numpy.var.html#numpy.var
# which is what tensorflow replicated
#
regular_sd(1:100, n_minus_1 = FALSE)
#> [1] 28.86607
library(greta)
#>
#> Attaching package: 'greta'
#> The following objects are masked from 'package:stats':
#>
#> binomial, cov2cor, poisson, sd
#> The following objects are masked from 'package:base':
#>
#> %*%, apply, backsolve, beta, chol2inv, colMeans, colSums, diag,
#> eigen, forwardsolve, gamma, identity, rowMeans, rowSums, sweep,
#> tapply
greta_sitrep()
#> ℹ checking if python available
#> ✓ python (version 3.7) available
#>
#> ℹ checking if TensorFlow available
#> ✓ TensorFlow (version 1.14.0) available
#>
#> ℹ checking if TensorFlow Probability available
#> ✓ TensorFlow Probability (version 0.7.0) available
#>
#> ℹ checking if greta conda environment available
#> ✓ greta conda environment available
#>
#> ℹ Initialising python and checking dependencies, this may take a moment.
#> ✓ Initialising python and checking dependencies ... done!
#>
#> ℹ greta is ready to use!
x <- as_data(1:100)
greta_sd <- as.numeric(calculate(val = sd(x)))
greta_sd
#> [1] 28.86607
greta_sd == regular_sd(1:100, n_minus_1 = FALSE)
#> [1] TRUE
Created on 2022-03-18 by the reprex package (v2.0.1)
One alternative would be to write out the following in tensorflow:
regular_sd <- function(x, n_minus_1 = TRUE){
if (!n_minus_1) {
n_dim <- length(dim(x))
reduction_dims <- seq_len(n_dim - 1)
sd_result <- tf$math$reduce_std(x, axis = reduction_dims, keepdims = !drop)
} else if (n_minus_1){
# replace these parts with tf_sum and friends?
x_mean_sq <- tf_mean(x) * tf_mean(x)
total_ss <- tf_sum(x - tf_mean_sq)
var <- total_ss / (length(x) - 1)
sd_result <- sqrt(var)
# total_ss <- sum((x - mean(x))^2)
# var <- (total_ss / (length(x) - 1))
# sd_result <- sqrt(var)
}
return(sd_result)
}
And provide an optional ddof
argument, as provided in NumPy (but not TF): https://numpy.org/doc/stable/reference/generated/numpy.std.html
with ddof
being 1, which is R's default. If it is 0, perhaps just run reduce_std
This is how benchmark results would change (along with a 95% confidence interval in relative change) if dbadadfa7a90b2aadd9e51a0d5ecfa18e25cfb2b is merged into master:
This is how benchmark results would change (along with a 95% confidence interval in relative change) if 1ab817d7be5888d5406c863bed1952ca51bdefcb is merged into master:
This is how benchmark results would change (along with a 95% confidence interval in relative change) if 897c035478f8fa1c508cce7b6d565c0702dca693 is merged into master:
Looks like {touchstone} is working. :D. Note that you can make confidence intervals arbitrary tight by running more iterations in each expression you benchmark, see the doc for benchmark_run()
.
resolves #504