[SciPy-Dev] Some remarks on least-squares algorithms
Benoit Rosa
b.rosa at unistra.fr
Mon Apr 12 05:21:22 EDT 2021
Hi all,
I've been experimenting lately with the least squares algorithms on
SciPy, and noted a few interesting things.
First, some backstory: with a Grad student, we are looking at soving
complex BVP problems, with lots of variables. The first step was
reimplementing a known algorithm from the literature. In the original
paper, the problem is square (126 elements in the state vector, and 126
elements in the error vector). But as soon as we changed one variable of
the problem, it became underdetermined (i.e. less elements in the error
vector than in the state vector).
The student originally implemented all in MATLAB, using
Levenberg-Marquardt for the solver. It was working well, even for the
underdetermined case (I'll come back to this point). However, we noticed
that on the same machine, with the same solver parameters, results were
quite different with different versions of Matlab. Sometimes leading to
failures in the optimization in Matlab 2019 and success in Matlab 2018
(version numbers not fully accurate, but you get the idea).
For this reason, we decided to switch to Python and SciPy, in order to
profit from more stability in the optimization algorithms (and also the
possibility get answers from the dev community in case of problems).
The first remark in the process is that SciPy's LM implementation (based
on MINPACK) does not allow to solve underdetermined problems.
Interestingly, both Matlab and SciPy have three available methods to
solve nonlinear least squares : LM, dogbox and trust region reflective.
MATLAB does allow underdetermined problems for LM, but not for dogbox
and trf, while SciPy does allow underdetermined problems for dogbox and
trf, but not for LM !
The second remark is that, when looking at MATLAB's implementation, I
found this:
===
if ~scaleStep % Unscaled Jacobian
if ~jacIsSparse
scaleMat = sqrt(lambda)*eye(nVar);
else
scaleMat = sqrt(lambda)*speye(nVar);
end
else
if ~jacIsSparse
scaleMat = diag(sqrt(lambda*diagJacTJac));
else
scaleMat = spdiags(sqrt(lambda*diagJacTJac),0,nVar,nVar);
end
end
% Compute LM step
if successfulStep
% Augmented Jacobian
AugJac = [JAC; scaleMat];
AugRes = [-costFun; zeroPad]; % Augmented residual
else
% If previous step failed, replace only the part of the matrix
that has changed
AugJac(nfun+1:end,:) = scaleMat;
end
====
Bottom line is, they augment the Jacobian using "AugJac = [JAC;
scaleMat];", even if the Jacobian Scaling option is off (first "if'). In
the end, the augmented Jacobian will always be square and invertible,
which means their implementation of the algorithm is applicable to any
problem, determined or underdetermined.
I know that an underdetermined problem is fundamentally problematic,
since there isn't a unique solution to it. However, in this case, Matlab
does solve the problem efficiently and SciPy does not. I know SciPy's LM
is based on MINPACK, which is a verified and benchmarked solution, but
I'm wondering whether the LM algorithm in Scipy should be updated ?
Also, a Python (or Cython?) implementation would allow to follow the
optimization process (right now, verbose=2 doesn't output anything
because of the MINPACK routine being called).
I'm afraid I can't share the code I used because it's ongoing research,
but I'd be glad to get your thoughts on these points.
Best,
Benoit
--
Mr. Benoit ROSA, PhD
Research Scientist / Chargé de recherche CNRS
ICube (UMR 7357 CNRS-University of Strasbourg)
s/c IHU
1, place de l'Hôpital
67091 Strasbourg Cedex, France
b.rosa at unistra.fr
More information about the SciPy-Dev
mailing list