There are many questions on this site about wrong exponential fitting in Mathematica but no one considers this well-known problem as a potential bug. Usually people suggest well-known workarounds allowing to obtain expected results but they don't investigate the source of the problem. My recent attempt to understand the situation lead me to conclusion that there is a bug in FindFit
and FindMinimum
related to exponential fitting.
Let us take a simple dataset and try to fit it to the exponential model:
data = {{0, 20}, {20, 10}, {40, 5}, {60, 2.5}, {80, 1.25}};
fit1 = FindFit[data, a*Exp[b*x], {a, b}, x, EvaluationMonitor :> Print[{a, b}]]
{1.,1.}
{0.,1.}
{a -> 0., b -> 1.}
Isn't the observed behavior already strange? Only two points are taken and then the last of them is returned as the minimum without any messages! It is easy to see that the returned solution is completely wrong and the correct solution can be obtained with Method -> NMinimize
:
fit2 = FindFit[data, a*Exp[b*x], {a, b}, x, Method -> NMinimize]
Show[ListPlot[data], Plot[Evaluate[a*Exp[b*x] /. {fit1, fit2}], {x, 0, 80}]]
{a -> 20., b -> -0.0346574}
But WHY the default method (which is "LevenbergMarquardt"
) gives wrong result? Maybe there actually is a local minimum with zero gradient? Let us check the gradient of the Sum of Squared Residuals (which is the function minimized by FindFit
; SSR
below) at the points checked by FindFit
:
Clear[a, b, x]
model[x_] := a*Exp[b*x];
resudual[{x_, y_}] := y - model[x];
residuals = Table[resudual[point], {point, data}];
SSR = Total[residuals^2];
gradientVector = D[SSR, {{a, b}}];
gradientVector /. {{a -> 1., b -> 1.}, {a -> 0., b -> 1.}}
{{6.1397*10^69, 4.91176*10^71}, {-1.38516*10^35, 0.}}
The gradient is HUGE in absolute value at both points checked by FindFit
but it returns the last of them as the solution without any messages! FindMinimum
behaves identically:
FindMinimum[SSR, {a, b}]
{532.813, {a -> 0., b -> 1.}}
Can anyone explain this behavior (I'm not looking for a workaround, I'm looking for an explanation)? Is this a bug?