Hi, I am currently using Julia to solve some econometric structural models which usually involve the calculation and optimization of Log-likelihood function. While the optimization can be done by Optim.jl, the calculation of loglikelihood needs hand-coding.
The question I face is that, how to calculate the log-likelihood function as fast as possible especially facing the unbalanced panel data as the input. The code below giving a logit implementation has the inputs X
and y
in which each individual has different number of observations, so I use Vector{Matrix{Float64}}
to save X
and use Vector{Vector{Int64}}
to save y
.
I wonder if this is an efficient way to calculate loglikehood function nll_threading
? Is there any other techniques to handle such problems ? (PS: I already used many methods such as multi-threading, views to speed up; however it’s still slow especially when the loglikelihood function for each individual has a more complicated form, such as hidden Markov model, bayesian learning model)
using LogExpFunctions, Optim
using Random, Distributions
using LinearAlgebra:dot
using BenchmarkTools
const seed = Random.seed!(2022 - 1 - 3)
# data generation
begin
Inds = 1000 # number of individuals
nX = 4
Tlen = rand([15, 10, 20], Inds) # number of observations of each individual
X = Vector{Matrix{Float64}}()
for i in eachindex(Tlen)
push!(X, randn(seed, (Tlen[i], nX)))
end
beta = [1.0, 2, -3, 4]
y = Vector{Vector{Int64}}()
for i in 1:Inds
Xi = X[i]
yi = Vector{Int64}()
for t in axes(Xi, 1)
U = @views dot(Xi[t, :], beta)
push!(yi, rand(BernoulliLogit(-U)))
end
push!(y, yi)
end
end
# negative log-likelihood function (to optimize)
function nll_threading(X, beta::Vector{T}, y) where {T <: Real}
ind_ll = zeros(T, Threads.nthreads())
Threads.@threads for i in eachindex(y)
Xi = X[i]
yi = y[i]
for t in axes(Xi, 1)
U = @views dot(Xi[t, :], beta)
ind_ll[Threads.threadid()] += logpdf(BernoulliLogit(-U), yi[t])
end
end
return - sum(ind_ll)
end
# benchmark
@benchmark nll_threading($X, $beta, $y)
# about 380ms for 6 threads with intel i5-10400f chip
# optimization
fit_optim = optimize(
pars -> nll_threading(X, pars, y),
zeros(4),
Newton();
autodiff = :forward
)
fit_optim.minimizer
Benchmark for current version
julia> @btime nll_threading($X, $beta, $y)
551.400 μs (111 allocations: 12.39 KiB)