tl;tr; How to improve performance of regularised Interpolation
?
Context
Since Version 12, Mathematica now incorporates a range of (underrated IMHO) regularisation methods to Fit
and FindFit
.
The option FitRegularization
is particularly useful to regularise a fit,
i.e. allow the BSpline
basis not to go through the data points but provide
a smoother fit. More generally these two functions also provide different NormFunction, which specify what norm[residual] should be minimised when doing the fit, which is also useful and general.
Hence I find it would be great that these two options be added to
Interpolation
transparently so that when interpolating, the option of not going exactly through the points can be controlled, e.g. if the purpose is to later differentiate the interpolation function.
(fairly successful) Attempt
Hence I have wrapped a new function, RegularisedInterpolation
, inspired by this (elegant) post by @J.M. which does the interpolation explicitly using Fit
and therefore inherits these functionalities.
Clear[RegularisedInterpolation];
RegularisedInterpolation[dat_, opt : OptionsPattern[]] :=
Module[{fspl, fb, sd, scpts, sk, var, dim, x, y, z , t},
fspl = Interpolation[dat, Method -> "Spline",
FilterRules[{opt}, Options[Interpolation]]];
If[Length[FilterRules[{opt}, Options[Fit]]] == 0, Return[fspl]];
fb = First[Cases[fspl, _BSplineFunction, \[Infinity]]];
{sd, scpts, sk} = fb /@ {"Degree", "ControlPoints", "Knots"};
dim = Last@Dimensions[dat];
var = Which[dim == 2, {x},
dim == 3, {x, y}, dim == 4, {x, y, z}, dim == 5, {x, y, z, t}];
Compile[var // Evaluate,
Fit[dat,
Flatten[Outer[Times,
Sequence @@ MapThread[
Table[BSplineBasis[{#1, #2}, k - 1, #3], {k, #4}] &, {sd,
sk, var // Evaluate, Dimensions[scpts]}]]] // Evaluate,
var // Evaluate,
FilterRules[{opt}, Options[Fit]]
] // Chop // Evaluate]
]
It works as expected (in arbitrary dimension) as I will illustrate, up to 3 glitchs for which I am seeking help.
Validation
Let's first look at one dimensional data
dat1 = Table[{i, Sin[i^2]}, {i, 0, 2 Pi, Pi/64}] // N;
dat1 = dat1 /. {x_, y_} :> {x, y (1 + RandomVariate[NormalDistribution[0, 0.05]])};
First note that by default it does a standard interpolation.
g1 = RegularisedInterpolation[dat1];
Then I can add options from Fit
and from Interpolation
gr = RegularisedInterpolation[dat1,
FitRegularization -> {"Curvature", 10^-0.5}, InterpolationOrder -> 3]
If I plot the 2 Interpolations (pink and yellow) they look fairly similar
Show[Plot[{Sin[x^2], gr[x], g1[x]}, {x, 0, 2 Pi}], ListPlot[dat1], PlotRange -> All]
but when I differentiate it the regularised version (in pink) remains closer to the un-noised data (in red), when compared to the (yellow) interpolation.
Plot[{2 x Cos[x^2], D[gr[x], x], D[g1[x], x]} // Evaluate, {x, 0, 2 Pi}]
This is why I believe regularised interpolation is useful (even though it is a bit of a contradiction in terms).
Let's now look at two or three dimensional data
dat2 = Flatten[
Table[{i, j, Sin[i*j]}, {i, 0, 5, 0.5}, {j, 0, 5, 0.5}], 1] // N;
g2 = RegularisedInterpolation[dat2]
g2r = RegularisedInterpolation[dat2,
FitRegularization -> {"Curvature", 10^-0.5}]
pl2 = Plot3D[{D[g2[x, y], x],D[g2r[x, y], x] } // Evaluate, {x, 0, 5}, {y, 0, 5},
PlotStyle -> Opacity[0.4], PlotPoints -> 20, PlotRange -> All];
Note the difference between the two curves: one is slightly smoother than the other.
Similarly in 3D
dat3 = Flatten[
Table[{i, j, k, Sin[i*j*k]}, {i, 0, 2, 0.5}, {j, 0, 2, 0.5}, {k, 0, 2, 0.5}], 2];
g3 = RegularisedInterpolation[dat3,
FitRegularization -> {"Curvature", 10^-7.5}];
The regularised fit does not go exactly through the 3D data (as it should)
dat3 - Flatten[
Table[{x, y, z, g3[x, y, z]}, {x, 0, 2, 0.5}, {y, 0, 2, 0.5}, {z,
0, 2, 0.5}], 2] // Transpose // Last // ListPlot
While I am fairly happy with the result, I still have 3 issues
Questions
I have in fact three questions:
- The code, while working complains about this
Experimental`NumericalFunction::dimsl: {y} given in {x,y}
should be a list of dimensions for a particular argument.
I have no idea what the issue is. Does anyone?
- The performance is not very good in 2 and 3 dimensions when evaluating the fit (not when doing the inversion).
pl2 = Plot3D[D[g2[x, y], x] // Evaluate, {x, 0, 5}, {y, 0, 5},
PlotStyle -> Opacity[0.4], PlotPoints -> 20,
PlotRange -> All]; // Timing
(* {0.179184,Null} *)
pl2r = Plot3D[D[g2r[x, y], x] // Evaluate, {x, 0, 5}, {y, 0, 5},
PlotStyle -> Opacity[0.4], PlotPoints -> 20,
PlotRange -> All]; // Timing
(* {2.12889,Null} *)
It may be because the plotting routine refine on the boundaries of the splines? Would you know how to get back to Interpolation
's native performance?
- I would ideally prefer that the result of the fit show the same kind of wrapper as Interpolation (specifying the boundary of the interpolation)
I.e. it should return something like this:
Do you have any idea how to 'hide' information in this manner? Through associations??
Thank you for your help. I am hoping that many people will find this generic wrapper useful eventually.
Comments/ improvements
It was drawn to me by @Henkik that the formulation is semantically incorrect since interpolation is meant to refer to 'going through the points'. The present function could/should be called
BSplineRegularisedFit
. On the other hand it has the same structure asInterpolation
, and could seamlessly be integrated to the built-in function by simply increasing the number of options.@Kuba pointed out that this and that question addresses the wrapping issue. He also pointed out that
Needs@"GeneralUtilities`" PrintDefinitions@InterpolatingFunction
yields the wrapper for InterpolatingFunction.
This post shows how it could be generalised to un evenly sampled data as well. Here the main advantage is to inherit all the nice properties of Interpolation.
It might be useful to be able to impose extra knots at the boundary of the domain?
Complement
To illustrate the benefit of regularisation let's add a glitch to the data and see how different penalty operate while changing the method and its amplitude.
Table[Plot[
Table[RegularisedInterpolation[dat1,
FitRegularization -> {meth, 10^i}][x], {i, -2, 2, 0.5}] //
Evaluate,
{x, 0, 2 Pi}], {meth, {"Curvature", "Variation", "TotalVariation",
"LASSO"}}] // Partition[#, 2] &
To illustrate the issue with the end point let us consider the successive derivative of the sine wave
dat1 = Table[{i, Sin[3 i]}, {i, 0, 2 Pi, Pi/8}] // N;
Clear[g];
g[x_] = RegularisedInterpolation[dat1,
FitRegularization -> {"Curvature", 10^-9.5},
InterpolationOrder -> 12][x];
Table[Plot[D[{g[x], Sin[3 x]}, {x, i}] // Evaluate, {x, 0, 2 Pi}]
, {i, 0, 5}] // Partition[#, 2] &
The match at the edge becomes worse with order of the derivative.