[SciPy-Dev] curve_fit() should require initial values for parameters

Matt Newville newville at cars.uchicago.edu
Wed Jan 30 11:04:13 EST 2019


Hi Ilhan,

On Tue, Jan 29, 2019 at 10:54 AM Ilhan Polat <ilhanpolat at gmail.com> wrote:

> > The problem I have with this is that there really is not an option to "try
> automatic first".  There is "try `np.ones(n_variables)` first".   This,
> or any other value, is really not a defensible choice for starting values.
>  Starting values always depend on the function used and the data being
> fit.
>
> Why not? 1.s are as good as any other choice.
>

Well, I agree that `np.ones(n_variables)` is as good as any other choice.
All default choices are horrible and not defensible.

Mathematically, algorithmically, and conceptually, initial values ARE
REQUIRED for non-linear least squares optimization.  The codes underlying
`curve_fit()` (including `leastsq` and the mess that is `least_square`) do
not permit the user to not provide initial values.  The algorithms used
simply do not make sense without initial values.

Programmatically, an optional keyword argument to a function, say with a
default value of `None`, implies that there is a sensible default for that
value.  Thus, default fitting tolerances might be 1.e-7 (or, perhaps square
root of machine precision) or the default value for "method to calculate
jacobian" might be `None` to mean "calculate by finite difference".  For
these optional inputs, the function (say, `curve_fit()`) has a sensible
default value that will work independently from the other input.

That notion of "independent, sensible default value" is not ever possible
for the initial values `p0` for `curve_fit`.  Sensible initial values
always depend on the data to be fit and the function modeling the data.
 Change the data values dramatically and `p0` must change.  Change the
definition of the function (or even the order of the arguments), and `p0`
must change.   There is not and cannot be a sensible default.

Telling the user that `p0` is optional and can be `None` (as the current
documentation does clearly state) is utterly and profoundly wrong.   It is
mathematically indefensible.  It is horrible API design.  It harms the
integretiy of `scipy.optimize` to tell the user this.

 I don't know anything about the curve fit I will get in the end. So I
> don't need to pretend that I know a good starting value.
>

It is not possible to do curve-fitting or non-linear least-squares
minimization when you "don't know anything".  The user MUST provide data to
be modeled and MUST provide a function to model that data.   It is
"pretending" to think that this is sufficient.  The user also must provide
initial values.

Maybe for 3 parameter functions, fine I can come up with an argument but
> you surely don't expect me to know the starting point if I am fitting a 7
> parameter func involving esoteric structure. At that point I am completely
> ignorant about anything about this function. So not knowing where to start
> is not due to my noviceness about the tools but because by definition. My
> search might even turn out to be convex so initial value won't matter.
>

It is not possible to do curve-fitting or non-linear least-squares
minimization when one is completely ignorant of the the function.



> > Currently `curve_fit`  converts `p0=None` to `np.ones(n_variables)`
> without warning or explanation.  Again, I do not use `curve_fit()` myself.
> I find several aspects of it unpleasant.
>
> It is documented in the p0 argument docs. I am using this function quite
> often. That's why I don't like extra required arguments. It's annoying to
> enter some random array just to please the API where I know that I am just
> taking a shot in the dark.
>

It is not an extra keyword argument.  It is required input for the problem.
`curve_fit()` is converting your feigned (or perhaps obstinate) ignorance
to a set of starting values for you.   But starting values simply cannot be
independent of the input model function or input data.


I am pretty confident that if we force this argument most of the people you
> want to educate will enter np.zeros(n). Then they will get an even weirder
> error then they'll try np.ones(n) but misremember n then they get another
> error to remember the func parameter number which has already trippped up
> twice. This curve_fit function is one of those functions that you don't run
> just once and be done with it but over and over again until you give up or
> satisfied. Hence defaults matter a lot from a UX perspective. "If you have
> an initial value in mind fine enter it otherwise let me do my thing" is
> much better than "I don't care about your quick experiment give me some
> values or I will keep tripping up".
>
>
I refuse to speculate on what "most users" will do, and I also refuse to
accept your speculation on this without evidence.  There a great many
applications of curve-fitting for which `np.ones(n_variables)` and
`np.zeros(n_variables)` will completely fail -- the fit will never move
from the starting point.   For the kinds of fits done in the programs I
support, either of these would mean that essentially all fits would never
move from its starting point, as at least one parameter being 0 or 1 would
essentially always mean the model function was 0 over the full data range.


But, again, I don't use `curve_fit` but other tools built on top of
`scipy.optimize()`.  Generally, the model function and data together imply
sensible or at least guessable default values, but these cannot independent
of model or data.  In lmfit we do not permit the user to not supply default
starting values -- default parameter values are `None` which will quickly
raise a ValueError. I don't recall ever getting asked to change this.
Because it should be obvious to all users that each parameter requires an
initial value.  Where appropriate and possible, we do provide methods for
model functions to make initial guesses based on data.  But again, the
starting values always depend on model function and data.

Default arguments *do* matter from a UX perspective when defaults are
sensible.   `curve_fit` has three required positional arguments:  a model
function (that must be annoying to have to provide), "y" data to be fit
(well, I guess I have that), and "x" data.  Why are those all required?
Why not allow `func=None` to be a function that calculates a sine wave?
Why not allow `y=None` to mean `np.ones(1000)`?  Why not allow `x=None` to
mean `np.arange(len(y))`?    Wouldn't that be friendlier to the user?


>   But this behavior strikes me as utterly wrong and a disservice to the
> scipy ecosystem.   I do not think that a documentation change is
> sufficient.
>
> Maybe a bit overzealous?
>
>
Nope, just trying to help `curve_fit()` work better.

--Matt
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/scipy-dev/attachments/20190130/a8ee5cba/attachment.html>


More information about the SciPy-Dev mailing list