Speeding up inference

This page is designed to explain how outerbase can facilitate fast inference with smart modeling choices.

library(outerbase)

The potential benefits grow as the sample size grows. We use a sample size of 500 here in the spirit of running quickly. The point will be obvious, but more dramatic results can be had by increasing the sample size.

sampsize = 500
d = 8
x = matrix(runif(sampsize*d),ncol=d)
y = obtest_borehole8d(x)

First setup an outermod object.

om = new(outermod)
setcovfs(om, rep("mat25pow",8))
knotlist = list();
for(k in 1:d) knotlist[[k]] = seq(0.01,1,by=0.025)
setknot(om, knotlist) #40 knot point for each dim

More data should mean more basis functions. So we will choose 250 terms for our feature space approximation.

p = 250
terms = om$selectterms(p)

Different models

To begin, lets use ?loglik_std to represent our slow approach.

loglik_slow = new(loglik_std, om, terms, y, x) 
logpr_slow = new(logpr_gauss, om, terms)
logpdf_slow = new(lpdfvec, loglik_slow, logpr_slow)

logpdf_slow can be optimized using lpdf$optnewton.

logpdf_slow$optnewton()

Newton’s method involves solving a linear system, thus it takes one step, but is expensive.

?loglik_gauss is a lpdf model designed for speed. It is a nice comparison because loglik_gauss uses the same model as loglik_std, with a few approximations for speed.

loglik_fast = new(loglik_gauss, om, terms, y, x) 
logpr_fast = new(logpr_gauss, om, terms)
logpdf_fast = new(lpdfvec, loglik_fast, logpr_fast)

logpdf_fast will through an error if you try to use optnewton. This is because it is written so that it never builds a Hessian (hess in the code) matrix.

logpdf_fast$optnewton()
#> Error in logpdf_fast$optnewton(): addition: incompatible matrix dimensions: 0x0 and 250x250

It is instead suggested to use lpdf$optcg (conjugate gradient) to optimize the coefficients in the fast version.

logpdf_fast$optcg(0.001,  # tolerance
                  100)    # max epochs

As an aside, omp speed ups are possible, but you need to have correctly compiled with omp. One check is to call the following.

ob = new(outerbase, om, x) 
ob$nthreads
#> [1] 4

If the answer is 1 but you have a multicore processor (most modern processors), your installation might be incorrect.

You can manually set the number of threads for lpdf objects.

logpdf_slow$setnthreads(4)
logpdf_fast$setnthreads(4)

Timing

The main cost of fitting outerbase models is hyperparameter optimization. The difference between logpdf_slow and logpdf_fast will be apparent. Let’s save starting points (since they share om) for fairness.

parlist_slow = list(para = getpara(logpdf_slow), hyp = gethyp(om))
parlist_fast = list(para = getpara(logpdf_fast), hyp = gethyp(om))

Test points will verify the predictions are equally good with either model, the only difference is speed.

xtest = matrix(runif(1000*d),ncol=d) #prediction points
ytest =  obtest_borehole8d(xtest)

We will use the unsophisticated proc.time to do some quick timing comparisons.

ptm = proc.time()
opth = BFGS_lpdf(om, logpdf_slow, 
                 parlist=parlist_slow, newt=TRUE)    
t_slow = proc.time() - ptm
pred_slow = new(predictor,loglik_slow)
pred_slow$update(xtest)
yhat_slow = as.vector(pred_slow$mean())
print(t_slow)
#>    user  system elapsed 
#>   13.36    0.20   12.85
ptm = proc.time()
opth = BFGS_lpdf(om, logpdf_fast, 
                 parlist=parlist_fast, newt=FALSE)  
t_fast = proc.time() - ptm
pred_fast = new(predictor,loglik_fast)
pred_fast$update(xtest)
yhat_fast = as.vector(pred_fast$mean())
print(t_fast)
#>    user  system elapsed 
#>    1.52    0.03    0.53

Comparison of results

And simply plotting the results tells the story: faster inference with no discernible drop off in quality. Note there are serious approximations here, but the approximations just have a negligible effect.

rmse_slow = sqrt(mean((ytest-yhat_slow)^2))
hist((ytest-yhat_slow), main=paste("slow method \n rmse:", 
                                    round(rmse_slow,3),
                                   ", time:",
                                   round(t_slow[3],2),'s'),
     xlab = "prediction residuals")
rmse_fast = sqrt(mean((ytest-yhat_fast)^2))
hist((ytest-yhat_fast), main=paste("fast method \n rmse =",
                                      round(rmse_fast,3),
                                   ", time:",
                                   round(t_fast[3],2),'s'), 
     xlab = "prediction residuals")