In this document we show how to
use the likelihood
method to obtain function handlers for
the objective function, and gradient, (and hessian if there are no
random efects in your model formulation). These handlers can then be
parsed to an optimization algorithm of your own choice.
We use the common Ornstein-Uhlenbeck process to showcase the use of
likelihood
.
dXt = θ(μ − Xt) dt + σX dBt
Ytk = Xtk + etk, etk ∼ 𝒩(0, σY2) We first create data by simulating the process
# Simulate data using Euler Maruyama
set.seed(10)
theta=10; mu=1; sigma_x=1; sigma_y=1e-1
#
dt.sim = 1e-3
t.sim = seq(0,1,by=dt.sim)
dw = rnorm(length(t.sim)-1,sd=sqrt(dt.sim))
x = 3
for(i in 1:(length(t.sim)-1)) {
x[i+1] = x[i] + theta*(mu-x[i])*dt.sim + sigma_x*dw[i]
}
# Extract observations and add noise
dt.obs = 1e-2
t.obs = seq(0,1,by=dt.obs)
y = x[t.sim %in% t.obs] + sigma_y * rnorm(length(t.obs))
# Create data
.data = data.frame(
t = t.obs,
y = y
)
We now construct the ctsmTMB
model object
# Create model object
obj = ctsmTMB$new()
# Set name of model (and the created .cpp file)
obj$setModelname("ornstein_uhlenbeck")
# Add system equations
obj$addSystem(
dx ~ theta * (mu-x) * dt + sigma_x*dw
)
# Add observation equations
obj$addObs(
y ~ x
)
# Set observation equation variances
obj$setVariance(
y ~ sigma_y^2
)
# Specify algebraic relations
obj$setAlgebraics(
theta ~ exp(logtheta),
sigma_x ~ exp(logsigma_x),
sigma_y ~ exp(logsigma_y)
)
# Specify parameter initial values and lower/upper bounds in estimation
obj$setParameter(
logtheta = log(c(initial = 5, lower = 0, upper = 20)),
mu = c( initial = 0, lower = -10, upper = 10),
logsigma_x = log(c(initial = 1e-1, lower = 1e-5, upper = 5)),
logsigma_y = log(c(initial = 1e-1, lower = 1e-5, upper = 5))
)
# Set initial state mean and covariance
obj$setInitialState(list(x[1], 1e-1*diag(1)))
We are in principle ready to call the estimate
method to
run the optimization scheme using the built-in optimization which uses
stats::nlminb
i.e.
## Building model...
## Compiling model...
## Checking data...
## Constructing objective function and derivative tables...
## ...took: 0.301 seconds.
## Minimizing the negative log-likelihood...
## 0: 921.77031: 1.60944 0.00000 -2.30259 -2.30259
## 1: 86.857795: 1.06416 0.614170 -1.83281 -1.97888
## 2: 31.849960: 1.43157 0.566368 -1.48426 -1.11793
## 3: 29.255774: 1.22062 1.42236 -1.18700 -1.48456
## 4: 2.6332703: 1.21162 1.06391 -0.839196 -1.50616
## 5: -28.360004: 1.69842 0.789400 0.678990 -2.68208
## 6: -31.783751: 1.72217 0.792252 0.505927 -2.70162
## 7: -32.935798: 1.82814 0.794076 0.369331 -2.66980
## 8: -33.931559: 1.93816 0.827823 0.404381 -2.54161
## 9: -36.374390: 2.30299 0.846084 0.271620 -2.41241
## 10: -37.306903: 2.33540 0.953594 0.250389 -2.39051
## 11: -38.025359: 2.41802 1.01738 0.207467 -2.36220
## 12: -38.962428: 2.84401 1.08562 0.118876 -2.21170
## 13: -39.245448: 2.75222 1.09405 0.199654 -2.35933
## 14: -39.316533: 2.76256 1.06843 0.205989 -2.34516
## 15: -39.332674: 2.78162 1.08468 0.204880 -2.32578
## 16: -39.343406: 2.81190 1.08012 0.197046 -2.32803
## 17: -39.346207: 2.80311 1.07595 0.205052 -2.32692
## 18: -39.346631: 2.80231 1.07756 0.202408 -2.32695
## 19: -39.346636: 2.80291 1.07748 0.202473 -2.32692
## 20: -39.346636: 2.80285 1.07748 0.202482 -2.32692
## 21: -39.346636: 2.80285 1.07748 0.202481 -2.32692
## Optimization finished!:
## Elapsed time: 0.025 seconds.
## The objective value is: -3.934664e+01
## The maximum gradient component is: 7.2e-07
## The convergence message is: relative convergence (4)
## Iterations: 21
## Evaluations: Fun: 29 Grad: 22
## See stats::nlminb for available tolerance/control arguments.
## Returning results...
## Finished!
Inside the package we optimise the objective function with respect to
the fixed parameters using the construction function handlers from
TMB::MakeADFun
and parsing them to
stats::nlminb
i.e.
The likelihood
method allows you to retrieve the
nll
object that holds the negative log-likelihood, and its
derivatives. The method takes arguments similar to those of
estimate
.
## Building model...
## Compiling model...
## Checking data...
## Constructing objective function...
## ...took: 0.3 seconds.
## Succesfully returned function handlers
The initial parameters (supplied by the user) are stored here
## logtheta mu logsigma_x logsigma_y
## 1.609438 0.000000 -2.302585 -2.302585
The objective function can be evaluted by
## [1] 921.7703
The gradient can be evaluted by
## [,1] [,2] [,3] [,4]
## [1,] 1382.854 -1557.574 -1191.374 -820.9253
The hessian can be evaluted by
## [,1] [,2] [,3] [,4]
## [1,] 2226.705 -2859.2069 -1636.0560 -1136.3013
## [2,] -2859.207 1663.1457 2251.5926 865.5807
## [3,] -1636.056 2251.5926 905.1284 1486.3607
## [4,] -1136.301 865.5807 1486.3607 346.7711
We can now use these to optimize the function using
e.g. stats::optim
instead.
You can extract the parameter bounds specified when calling
setParameter()
method by using the
getParameters
method (note that nll$par
and
pars$initial
are identical).
## type estimate initial lower upper
## logtheta free 2.8028465 1.609438 -Inf 2.995732
## mu free 1.0774801 0.000000 -10.00000 10.000000
## logsigma_x free 0.2024813 -2.302585 -11.51293 1.609438
## logsigma_y free -2.3269227 -2.302585 -11.51293 1.609438
stats::optim
We supply the initial parameter values, objective function handler
and gradient handler, and parameter bounds to optim
.
Lets compare the results from using stats::optim
with
the extracted function handler versus the internal optimisation that
uses stats::nlminb
stored in fit
:
## external internal
## logtheta 2.8028428 2.8028465
## mu 1.0774788 1.0774801
## logsigma_x 0.2024796 0.2024813
## logsigma_y -2.3269222 -2.3269227
## external internal
## 1 -39.34664 -39.34664
## external internal
## 1 -5.284383e-05 -7.872731e-08
## 2 -1.491876e-04 5.834973e-07
## 3 -4.592109e-05 -7.193738e-07
## 4 -2.656532e-05 -4.960521e-07