mirror of
https://github.com/fjosw/pyerrors.git
synced 2025-05-14 19:43:41 +02:00
Merge pull request #121 from fjosw/feat/num_diff_fit
Least square fit with numerical differentiation
This commit is contained in:
commit
889e24367d
2 changed files with 98 additions and 12 deletions
|
@ -9,8 +9,11 @@ import matplotlib.pyplot as plt
|
||||||
from matplotlib import gridspec
|
from matplotlib import gridspec
|
||||||
from scipy.odr import ODR, Model, RealData
|
from scipy.odr import ODR, Model, RealData
|
||||||
import iminuit
|
import iminuit
|
||||||
from autograd import jacobian
|
from autograd import jacobian as auto_jacobian
|
||||||
|
from autograd import hessian as auto_hessian
|
||||||
from autograd import elementwise_grad as egrad
|
from autograd import elementwise_grad as egrad
|
||||||
|
from numdifftools import Jacobian as num_jacobian
|
||||||
|
from numdifftools import Hessian as num_hessian
|
||||||
from .obs import Obs, derived_observable, covariance, cov_Obs
|
from .obs import Obs, derived_observable, covariance, cov_Obs
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,6 +117,8 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs):
|
||||||
If True, a plot which displays fit, data and residuals is generated (default False).
|
If True, a plot which displays fit, data and residuals is generated (default False).
|
||||||
qqplot : bool
|
qqplot : bool
|
||||||
If True, a quantile-quantile plot of the fit result is generated (default False).
|
If True, a quantile-quantile plot of the fit result is generated (default False).
|
||||||
|
num_grad : bool
|
||||||
|
Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
|
||||||
'''
|
'''
|
||||||
if priors is not None:
|
if priors is not None:
|
||||||
return _prior_fit(x, y, func, priors, silent=silent, **kwargs)
|
return _prior_fit(x, y, func, priors, silent=silent, **kwargs)
|
||||||
|
@ -160,6 +165,8 @@ def total_least_squares(x, y, func, silent=False, **kwargs):
|
||||||
corrected by effects caused by correlated input data.
|
corrected by effects caused by correlated input data.
|
||||||
This can take a while as the full correlation matrix
|
This can take a while as the full correlation matrix
|
||||||
has to be calculated (default False).
|
has to be calculated (default False).
|
||||||
|
num_grad : bool
|
||||||
|
Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
|
@ -174,6 +181,13 @@ def total_least_squares(x, y, func, silent=False, **kwargs):
|
||||||
|
|
||||||
x_shape = x.shape
|
x_shape = x.shape
|
||||||
|
|
||||||
|
if kwargs.get('num_grad') is True:
|
||||||
|
jacobian = num_jacobian
|
||||||
|
hessian = num_hessian
|
||||||
|
else:
|
||||||
|
jacobian = auto_jacobian
|
||||||
|
hessian = auto_hessian
|
||||||
|
|
||||||
if not callable(func):
|
if not callable(func):
|
||||||
raise TypeError('func has to be a function.')
|
raise TypeError('func has to be a function.')
|
||||||
|
|
||||||
|
@ -268,7 +282,7 @@ def total_least_squares(x, y, func, silent=False, **kwargs):
|
||||||
|
|
||||||
fitp = out.beta
|
fitp = out.beta
|
||||||
try:
|
try:
|
||||||
hess = jacobian(jacobian(odr_chisquare))(np.concatenate((fitp, out.xplus.ravel())))
|
hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplus.ravel())))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
|
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
|
||||||
|
|
||||||
|
@ -277,7 +291,7 @@ def total_least_squares(x, y, func, silent=False, **kwargs):
|
||||||
chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
|
chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
|
||||||
return chisq
|
return chisq
|
||||||
|
|
||||||
jac_jac_x = jacobian(jacobian(odr_chisquare_compact_x))(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel())))
|
jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel())))
|
||||||
|
|
||||||
# Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
|
# Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv
|
||||||
try:
|
try:
|
||||||
|
@ -290,7 +304,7 @@ def total_least_squares(x, y, func, silent=False, **kwargs):
|
||||||
chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
|
chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2)
|
||||||
return chisq
|
return chisq
|
||||||
|
|
||||||
jac_jac_y = jacobian(jacobian(odr_chisquare_compact_y))(np.concatenate((fitp, out.xplus.ravel(), y_f)))
|
jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplus.ravel(), y_f)))
|
||||||
|
|
||||||
# Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
|
# Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv
|
||||||
try:
|
try:
|
||||||
|
@ -318,6 +332,11 @@ def _prior_fit(x, y, func, priors, silent=False, **kwargs):
|
||||||
|
|
||||||
x = np.asarray(x)
|
x = np.asarray(x)
|
||||||
|
|
||||||
|
if kwargs.get('num_grad') is True:
|
||||||
|
hessian = num_hessian
|
||||||
|
else:
|
||||||
|
hessian = auto_hessian
|
||||||
|
|
||||||
if not callable(func):
|
if not callable(func):
|
||||||
raise TypeError('func has to be a function.')
|
raise TypeError('func has to be a function.')
|
||||||
|
|
||||||
|
@ -406,14 +425,15 @@ def _prior_fit(x, y, func, priors, silent=False, **kwargs):
|
||||||
if not m.fmin.is_valid:
|
if not m.fmin.is_valid:
|
||||||
raise Exception('The minimization procedure did not converge.')
|
raise Exception('The minimization procedure did not converge.')
|
||||||
|
|
||||||
hess_inv = np.linalg.pinv(jacobian(jacobian(chisqfunc))(params))
|
hess = hessian(chisqfunc)(params)
|
||||||
|
hess_inv = np.linalg.pinv(hess)
|
||||||
|
|
||||||
def chisqfunc_compact(d):
|
def chisqfunc_compact(d):
|
||||||
model = func(d[:n_parms], x)
|
model = func(d[:n_parms], x)
|
||||||
chisq = anp.sum(((d[n_parms: n_parms + len(x)] - model) / dy_f) ** 2) + anp.sum(((d[n_parms + len(x):] - d[:n_parms]) / dp_f) ** 2)
|
chisq = anp.sum(((d[n_parms: n_parms + len(x)] - model) / dy_f) ** 2) + anp.sum(((d[n_parms + len(x):] - d[:n_parms]) / dp_f) ** 2)
|
||||||
return chisq
|
return chisq
|
||||||
|
|
||||||
jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((params, y_f, p_f)))
|
jac_jac = hessian(chisqfunc_compact)(np.concatenate((params, y_f, p_f)))
|
||||||
|
|
||||||
deriv = -hess_inv @ jac_jac[:n_parms, n_parms:]
|
deriv = -hess_inv @ jac_jac[:n_parms, n_parms:]
|
||||||
|
|
||||||
|
@ -441,6 +461,13 @@ def _standard_fit(x, y, func, silent=False, **kwargs):
|
||||||
|
|
||||||
x = np.asarray(x)
|
x = np.asarray(x)
|
||||||
|
|
||||||
|
if kwargs.get('num_grad') is True:
|
||||||
|
jacobian = num_jacobian
|
||||||
|
hessian = num_hessian
|
||||||
|
else:
|
||||||
|
jacobian = auto_jacobian
|
||||||
|
hessian = auto_hessian
|
||||||
|
|
||||||
if x.shape[-1] != len(y):
|
if x.shape[-1] != len(y):
|
||||||
raise Exception('x and y input have to have the same length')
|
raise Exception('x and y input have to have the same length')
|
||||||
|
|
||||||
|
@ -571,9 +598,9 @@ def _standard_fit(x, y, func, silent=False, **kwargs):
|
||||||
fitp = fit_result.x
|
fitp = fit_result.x
|
||||||
try:
|
try:
|
||||||
if kwargs.get('correlated_fit') is True:
|
if kwargs.get('correlated_fit') is True:
|
||||||
hess = jacobian(jacobian(chisqfunc_corr))(fitp)
|
hess = hessian(chisqfunc_corr)(fitp)
|
||||||
else:
|
else:
|
||||||
hess = jacobian(jacobian(chisqfunc))(fitp)
|
hess = hessian(chisqfunc)(fitp)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
|
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None
|
||||||
|
|
||||||
|
@ -589,7 +616,7 @@ def _standard_fit(x, y, func, silent=False, **kwargs):
|
||||||
chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2)
|
chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2)
|
||||||
return chisq
|
return chisq
|
||||||
|
|
||||||
jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((fitp, y_f)))
|
jac_jac = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f)))
|
||||||
|
|
||||||
# Compute hess^{-1} @ jac_jac[:n_parms, n_parms:] using LAPACK dgesv
|
# Compute hess^{-1} @ jac_jac[:n_parms, n_parms:] using LAPACK dgesv
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -83,8 +83,65 @@ def test_least_squares():
|
||||||
assert math.isclose(pcov[i, i], betac[i].dvalue ** 2, abs_tol=1e-3)
|
assert math.isclose(pcov[i, i], betac[i].dvalue ** 2, abs_tol=1e-3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_least_squares_num_grad():
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for i in range(2, 5):
|
||||||
|
x.append(i * 0.01)
|
||||||
|
y.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens"))
|
||||||
|
|
||||||
|
num = pe.fits.least_squares(x, y, lambda a, x: np.exp(a[0] * x) + a[1], num_grad=True)
|
||||||
|
auto = pe.fits.least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False)
|
||||||
|
|
||||||
|
assert(num[0] == auto[0])
|
||||||
|
assert(num[1] == auto[1])
|
||||||
|
|
||||||
|
|
||||||
|
def test_prior_fit_num_grad():
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for i in range(2, 5):
|
||||||
|
x.append(i * 0.01)
|
||||||
|
y.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens"))
|
||||||
|
|
||||||
|
num = pe.fits.least_squares(x, y, lambda a, x: np.exp(a[0] * x) + a[1], num_grad=True, priors=y[:2])
|
||||||
|
auto = pe.fits.least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False, piors=y[:2])
|
||||||
|
|
||||||
|
|
||||||
|
def test_least_squares_num_grad():
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for i in range(2, 5):
|
||||||
|
x.append(i * 0.01)
|
||||||
|
y.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens"))
|
||||||
|
|
||||||
|
num = pe.fits.least_squares(x, y, lambda a, x: np.exp(a[0] * x) + a[1], num_grad=True)
|
||||||
|
auto = pe.fits.least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False)
|
||||||
|
|
||||||
|
assert(num[0] == auto[0])
|
||||||
|
assert(num[1] == auto[1])
|
||||||
|
|
||||||
|
|
||||||
|
assert(num[0] == auto[0])
|
||||||
|
assert(num[1] == auto[1])
|
||||||
|
|
||||||
|
|
||||||
|
def test_total_least_squares_num_grad():
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for i in range(2, 5):
|
||||||
|
x.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens"))
|
||||||
|
y.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens"))
|
||||||
|
|
||||||
|
num = pe.fits.total_least_squares(x, y, lambda a, x: np.exp(a[0] * x) + a[1], num_grad=True)
|
||||||
|
auto = pe.fits.total_least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False)
|
||||||
|
|
||||||
|
assert(num[0] == auto[0])
|
||||||
|
assert(num[1] == auto[1])
|
||||||
|
|
||||||
|
|
||||||
def test_alternative_solvers():
|
def test_alternative_solvers():
|
||||||
dim = 192
|
dim = 92
|
||||||
x = np.arange(dim)
|
x = np.arange(dim)
|
||||||
y = 2 * np.exp(-0.06 * x) + np.random.normal(0.0, 0.15, dim)
|
y = 2 * np.exp(-0.06 * x) + np.random.normal(0.0, 0.15, dim)
|
||||||
yerr = 0.1 + 0.1 * np.random.rand(dim)
|
yerr = 0.1 + 0.1 * np.random.rand(dim)
|
||||||
|
@ -158,7 +215,7 @@ def test_correlated_fit():
|
||||||
|
|
||||||
|
|
||||||
def test_fit_corr_independent():
|
def test_fit_corr_independent():
|
||||||
dim = 50
|
dim = 30
|
||||||
x = np.arange(dim)
|
x = np.arange(dim)
|
||||||
y = 0.84 * np.exp(-0.12 * x) + np.random.normal(0.0, 0.1, dim)
|
y = 0.84 * np.exp(-0.12 * x) + np.random.normal(0.0, 0.1, dim)
|
||||||
yerr = [0.1] * dim
|
yerr = [0.1] * dim
|
||||||
|
@ -470,7 +527,7 @@ def test_correlated_fit_vs_jackknife():
|
||||||
|
|
||||||
|
|
||||||
def test_fit_no_autograd():
|
def test_fit_no_autograd():
|
||||||
dim = 10
|
dim = 3
|
||||||
x = np.arange(dim)
|
x = np.arange(dim)
|
||||||
y = 2 * np.exp(-0.08 * x) + np.random.normal(0.0, 0.15, dim)
|
y = 2 * np.exp(-0.08 * x) + np.random.normal(0.0, 0.15, dim)
|
||||||
yerr = 0.1 + 0.1 * np.random.rand(dim)
|
yerr = 0.1 + 0.1 * np.random.rand(dim)
|
||||||
|
@ -486,6 +543,8 @@ def test_fit_no_autograd():
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
pe.least_squares(x, oy, func)
|
pe.least_squares(x, oy, func)
|
||||||
|
|
||||||
|
pe.least_squares(x, oy, func, num_grad=True)
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
pe.total_least_squares(oy, oy, func)
|
pe.total_least_squares(oy, oy, func)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue