| Function | Result | Cost |
|---|---|---|
hessian(f, x[, data]) |
n x n matrix | n sweeps over the tape |
hvp(f, x, v[, data]) |
H %*% v, vector of length n |
one sweep |
f is an inner function mapping a double vector to a
scalar double. data is passed to f unchanged,
as for jacobian. Both are exact (no finite differences) and
work in every translation: plain, derivative = "forward"
and derivative = "reverse".
se <- function(beta, data) {
argtypes(beta |> type(vec(double)), data |> type(mat(double)))
nll <- function(beta, data) {
argtypes(beta |> type(vec(double)), data |> type(mat(double)))
returntype(double)
s <- 0
for (i in 1L:nrow(data)) {
eta <- beta[[1L]] * data[i, 1L] + beta[[2L]] * data[i, 2L]
s <- s + data[i, 3L] * eta - log(1 + exp(eta))
}
-s
}
H <- hessian(nll, beta, data)
sqrt(get_diag(solve(H)))
}
se_cpp <- ast2ast::translate(se)
X <- cbind(1, c(-1, -0.5, 0, 0.5, 1, 1.5))
y <- c(0, 0, 1, 0, 1, 1)
beta_hat <- coef(glm(y ~ X[, 2], family = binomial))
se_cpp(beta_hat, cbind(X, y))hvp gives H %*% v for the cost of about one
gradient, without building H. It is the building block of
Newton-CG and other methods where n is too large for the full
Hessian.
f is recorded once with reverse mode; each direction
then runs a tangent sweep and a second-order reverse sweep over that
recording (forward-over-reverse).translate(derivative = "reverse"),
f has to be a named inner function (as for
jacobian in a plain translation).%*%,
crossprod, tcrossprod, solve,
backsolve, forwardsolve, chol,
det.if branches are
the ones taken at x.