Nonlinear regressions
This NLS_Solver.jl package is a generic nonlinear least squares solver. However a very common use of this kind of solver is nonlinear regression. In this tutorial we show how this package can be used in this context. You can reproduce the computation thanks to sandbox/nonlinear_regression.jl
.
Synthetic data
We will fit an exponential baseline plus a gaussian peak of the form:
\[h \exp{\left(-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2\right)} + b \exp{\left(-\frac{x}{\tau}\right)}\]
We first define some bricks for our model
function gaussian_peak(x::Real,
h::Real,
μ::Real,
σ::Real)
@assert σ>0
h*exp(-((x-μ)/σ)^2/2)
end
function exp_baseline(x::Real,
b::Real,
τ::Real)
@assert τ>0
b*exp(-x/τ)
end
function model(x::Real,θ::AbstractVector)
@assert length(θ) == 5
# one gaussian peaks + exp baseline
#
(h, μ, σ, b, τ) = (θ_i for θ_i in θ)
gaussian_peak(x,h,μ,σ) + exp_baseline(x,b,τ)
end
Now the important function that computes the residue:
\[r_i(θ) = Y_i - m(X_i,θ),\ i=1,\dots,n_{\text{sample}}\]
where the $r_i$ components are used to define the objective function $\frac{1}{2}\|r(θ)\|_2^2$.
function residue(X::AbstractVector,Y::AbstractVector,θ::AbstractVector)
map(zip(X,Y)) do (X_i,Y_i)
Y_i - model(X_i, θ)
end
end
We are now ready to generate our synthetic data:
X=[1:0.1:30;]
θ_true=[2.0,15,3.0,3.0,8.0]
Y_true = map(X_i -> model(X_i,θ_true), X)
Y_noisy = map(Y_i -> Y_i + 0.5 * randn(), Y_true)
plot(X,Y_noisy,label="noisy signal")
plot!(X,Y_true,linewidth=3,linestyle=:dot,label="true signal")
Solve the problem
Now we solve the problem in as we have done for Bound constrained nonlinear least squares.
The problem dimensions are:
n_θ = length(θ_true)
n_sample = length(X)
We impose some bound constraints:
# h, μ, σ, b, τ
θ_lowerbound = Float64[ 1, 10, 1, 0, 1 ]
θ_upperbound = Float64[ Inf, 20, 10, Inf, 10 ]
bc = BoundConstraints(θ_lowerbound, θ_upperbound)
The initial guess is the vector $\mathbf{1}$.
θ_init = ones(n_θ)
nothing # hide
You can call the solve()
function with an unfeasible initial vector θ. (where ==unfeasible== means that the bound constraints are not fulfilled).
Then we choose the solver (only one choice for the moment):
conf = LevenbergMarquardt_BC_Conf()
nothing # hide
We define the nonlinear least squares problem from the residue()
function:
nls = create_NLS_problem_using_ForwardDiff(θ->residue(X,Y_noisy,θ),n_θ => n_sample)
nothing # hide
The residue Jacobian is computed using automatic differentiation.
We can now solve the problem:
result = solve(nls,θ_init,bc,conf)
Fit result
We check that the solver converged:
@assert converged(result)
θ_solution = solution(result)
5-element ReadOnlyArrays.ReadOnlyArray{Float64, 1, Vector{Float64}}:
2.1355486897654368
14.949393304469943
3.0620384522116106
3.453584227962735
6.664473318364955
and plot the fitted model:
Y_solution = map(X_i -> model(X_i,θ_solution), X)
plot!(X,Y_solution,linewidth=3,label="fitted model")
We can get furthers information from the result
structure:
iteration_count(result)
13
objective_value(result)
34.02480305862584
In the future I will add gradient, Hessian and multipliers at the solution. However, please note that in the constrained case you cannot use $(J^t.J)^{-1}$ directly to estimate your parameter uncertainties.
More fit examples
This NLS_Solver.jl package is used by the NLS_Fit.jl package which is dedicated to peak fitting in spectrometry. You will find there other examples of model fitting.