Distributions

library(torch)
torch_manual_seed(1) # setting seed for reproducibility

This vignette showcases the basic functionality of distributions in torch. Currently the distributions modules are considered ‘work in progress’ and are still experimental features in the torch package. You can see the progress in this link.

The distributions modules in torch are modelled after PyTorch’s distributions module which in turn is based on the TensorFlow Distributions package.

This vignette is based in the TensorFlow’s distributions tutorial.

Basic univariate distributions

Let’s start and create a new instance of a normal distribution:

n <- distr_normal(loc = 0, scale = 1)
n
#> torch_Normal ()

We can draw samples from it with:

n$sample()
#> torch_tensor
#> -0.5151
#> [ CPUFloatType{1} ]

or, draw multiple samples:

n$sample(3)
#> torch_tensor
#> -1.1739
#> -1.4472
#>  0.9232
#> [ CPUFloatType{3,1} ]

We can evaluate the log probability of values:

n$log_prob(0)
#> torch_tensor
#> -0.9189
#> [ CPUFloatType{1} ]
log(dnorm(0)) # equivalent R code
#> [1] -0.9189385

or, evaluate multiple log probabilities:

n$log_prob(c(0, 2, 4))
#> torch_tensor
#> -0.9189
#> -2.9189
#> -8.9189
#> [ CPUFloatType{3} ]

Multiple distributions

A distribution can take a tensor as it’s parameters:

b <- distr_bernoulli(probs = torch_tensor(c(0.25, 0.5, 0.75)))
b
#> torch_Bernoulli ()

This object represents 3 independent Bernoulli distributions, one for each element of the tensor.

We can sample a single observation:

b$sample()
#> torch_tensor
#>  0
#>  1
#>  1
#> [ CPUFloatType{3} ]

or, a batch of n observations:

b$sample(6)
#> torch_tensor
#>  0  1  0
#>  0  0  1
#>  0  0  1
#>  0  1  1
#>  0  0  1
#>  0  0  1
#> [ CPUFloatType{6,3} ]

Using distributions within models

The log_prob method of distributions can be differentiated, thus, distributions can be used to train models in torch.

Let’s implement a Gaussian linear model, but first let’s simulate some data

x <- torch_randn(100, 1)
y <- 2*x + 1 + torch_randn(100, 1)

and plot:

plot(as.numeric(x), as.numeric(y))

We can now define our model:

GaussianLinear <- nn_module(
  initialize = function() {
    # this linear predictor will estimate the mean of the normal distribution
    self$linear <- nn_linear(1, 1)
    # this parameter will hold the estimate of the variability
    self$scale <- nn_parameter(torch_ones(1))
  },
  forward = function(x) {
    # we estimate the mean
    loc <- self$linear(x)
    # return a normal distribution
    distr_normal(loc, self$scale)
  }
)

model <- GaussianLinear()

We can now train our model with:

opt <- optim_sgd(model$parameters, lr = 0.1)

for (i in 1:100) {
  opt$zero_grad()
  d <- model(x)
  loss <- torch_mean(-d$log_prob(y))
  loss$backward()
  opt$step()
  if (i %% 10 == 0)
    cat("iter: ", i, " loss: ", loss$item(), "\n")
}
#> iter:  10  loss:  1.809854 
#> iter:  20  loss:  1.606322 
#> iter:  30  loss:  1.46065 
#> iter:  40  loss:  1.399433 
#> iter:  50  loss:  1.39078 
#> iter:  60  loss:  1.390136 
#> iter:  70  loss:  1.390089 
#> iter:  80  loss:  1.390086 
#> iter:  90  loss:  1.390085 
#> iter:  100  loss:  1.390085

We can see the parameter estimates with:

model$parameters
#> $linear.weight
#> torch_tensor
#>  1.9772
#> [ CPUFloatType{1,1} ][ requires_grad = TRUE ]
#> 
#> $linear.bias
#> torch_tensor
#>  1.0390
#> [ CPUFloatType{1} ][ requires_grad = TRUE ]
#> 
#> $scale
#> torch_tensor
#>  0.9716
#> [ CPUFloatType{1} ][ requires_grad = TRUE ]

and quickly compare with the glm() function:

summary(glm(as.numeric(y) ~ as.numeric(x)))
#> 
#> Call:
#> glm(formula = as.numeric(y) ~ as.numeric(x))
#> 
#> Deviance Residuals: 
#>     Min       1Q   Median       3Q      Max  
#> -2.5311  -0.6277  -0.1177   0.5544   3.3037  
#> 
#> Coefficients:
#>               Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)    1.03900    0.09844   10.55   <2e-16 ***
#> as.numeric(x)  1.97723    0.09392   21.05   <2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> (Dispersion parameter for gaussian family taken to be 0.9631909)
#> 
#>     Null deviance: 521.260  on 99  degrees of freedom
#> Residual deviance:  94.393  on 98  degrees of freedom
#> AIC: 284.02
#> 
#> Number of Fisher Scoring iterations: 2