Using the C++ Interface

Jonathan Berrisch

2024-09-21

Introduction

All major parts of online() are implemented in C++ for speed. Usually, this comes at the cost of flexibility. However, the profoc package exposes a C++ class conline that allows you to gain fine grained control over objects.
online() wraps this class and provides a convenient interface for the most common use cases. However, if you need to alter object initialization (i.e. provide custom basis / hat matrices for smoothing) you can use the C++ class directly from R. This vignette shows how to do this.

Note that we will reuse the data from vignette("profoc").

Online learning with conline

First, we need to create a new instance of the c++ class. This can be done by calling new(conline).

library(profoc)
model <- new(conline)

Now we need to pass the data to the class instance. The whole list of accessible field can be printed with names(model). Most of them have defaults.

model$y <- y
tau <- 1:P / (P + 1)
model$tau <- tau

The experts array is a bit more complicated. C++ expects us to pass a list of arrays. Thereby, the list itself must have dimension Tx1 and the elements of the list (the arrays) D x P x K. For convenience we can use init_experts_list() to create such a list from our experts array. Note that we must pass the true observations as well. They are used to detect whether the data is univariate (T x 1 matrix) or multivariate (T x D matrix).

experts_list <- init_experts_list(experts, y)
model$experts <- experts_list

Now suppose we want to alter the smoothing behavior across quantiles. We start by creating a new hat matrix.

hat <- make_hat_mats(
  x = tau,
  mu = 0.2, # Put more knots in the lower tail
  periodic = TRUE
)
str(hat)
#> List of 2
#>  $ hat   :List of 1
#>   ..$ :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
#>   .. .. ..@ i       : int [1:99] 0 1 2 3 4 5 6 7 8 9 ...
#>   .. .. ..@ p       : int [1:100] 0 1 2 3 4 5 6 7 8 9 ...
#>   .. .. ..@ Dim     : int [1:2] 99 99
#>   .. .. ..@ Dimnames:List of 2
#>   .. .. .. ..$ : NULL
#>   .. .. .. ..$ : NULL
#>   .. .. ..@ x       : num [1:99] 1 1 1 1 1 1 1 1 1 1 ...
#>   .. .. ..@ factors : list()
#>   ..- attr(*, "dim")= int [1:2] 1 1
#>  $ params: num [1, 1:9] 99 0.2 1 0 1 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : NULL
#>   .. ..$ : chr [1:9] "n" "mu" "sigma" "nonc" ...

We need a list of sparse matrices which make_hat_mats() returns. So we can pass that directly to our class.

model$hat_pr <- hat$hat

The other smoothing matrices have to be filled with defaults (lists of sparse identity matrices). Usually online() takes care of this. But we can do it manually as well.

model$basis_mv <- list(Matrix::sparseMatrix(i = 1:D, j = 1:D, x = 1))
model$basis_pr <- list(Matrix::sparseMatrix(i = 1:P, j = 1:P, x = 1))
model$hat_mv <- list(Matrix::sparseMatrix(i = 1:D, j = 1:D, x = 1))

Now we can specify the parameter grid. We will stick to the defaults here:

parametergrid <- as.matrix(
  expand.grid(
    forget_regret = 0,
    soft_threshold = -Inf,
    hard_threshold = -Inf,
    fixed_share = 0,
    basis_pr_idx = 1,
    basis_mv_idx = 1,
    hat_pr_idx = 1,
    hat_mv_idx = 1,
    gamma = 1,
    loss_share = 0,
    regret_share = 0
  )
)

model$params <- parametergrid

Finally, we can run model$set_defaults(). This populates initial states (w0 for weights and R0 for regret).

model$set_defaults()

Now model$set_grid_objects() will create the grid objects (performance, weights, regret etc.)

model$set_grid_objects()

Finally, we can run model$learn() to start the learning process.

model$learn()

Accessing the results

The learning process fills the class objects. So we can inspect them using the $ operator, like we would with any other R object. For example, we can access the weights:

head(model$weights[[T]][, , 1])
#> [1] 0.4892732 0.4058720 0.5979021 0.5770314 0.5487374 0.6287706

However, we can also use the post processing function of online() to access the results. This will create output that is identical to the output of online():

names <- list(y = dimnames(y))
names$experts <- list(
  1:T,
  paste("Marginal", 1:D),
  tau,
  paste("Expert", 1:N)
)

output <- post_process_model(model, names)

We can now use output in update(), plot() and others.

At this point, we do not need to keep the model in memory anymore. So we can delete it:

rm(model)

Summary

The C++ class conline allows you to gain fine grained control over the learning process. However, it is not as convenient as the online() wrapper. So you should only use it if you need to alter the default behavior. However, mixing up helper functions from online() and the C++ class is possible. So you can compute your combinations using the class interface while still being able to use update(), plot() and others afterward.