Reputation: 1
I'm trying to fit modeling data as a curve using Scipy's optimize curve_fit to some scattered data, but I got a negative value of (bX) parameter that makes no sense. So I'm not sure what is wrong with the codes.
# Define linear quadratic curve
def linq(x, aparam, bparam):
return np.exp(-(aparam*x+ bparam*x**2))
#new model
NX=46
def abX(x, aXparam, bXparam):
return np.exp(-(aXparam + bXparam)*x)*(1 + bXparam*x/NX)**NX
# define result array
avalues =[]
davalues =[]
bvalues=[]
dbvalues =[]
Chi2=[]
# Model parameters
Dmax = 10.
a1=0.740; b1=.1100
# ensemble size
Nensemble = 10
for iexp in range(1, Nensemble+1):
# Create the artificial dataset
nobs = int(Dmax + .5)
x = np.arange(.5,nobs)
N = linq(x,a1,b1)
Nfluct = 0.3*N*np.random.normal(size=nobs)
N = N + Nfluct
sig = np.zeros(nobs) + 0.3*N
# Fit the curve
start = (0,1.)
popt, pcov = curve_fit(abX,x,N,sigma = sig,p0 = start,absolute_sigma=True)
# add result to result vectors
avalues=avalues+[popt[0]]
bvalues=bvalues+[popt[1]]
davalues=davalues+[np.sqrt(pcov[0,0])]
dbvalues=dbvalues+[np.sqrt(pcov[1,1])]
print("mean aX value =",np.mean(avalues),"+/-",np.std(avalues)/np.sqrt(Nensemble-1),", gen =",a1)
print("mean bX value =",np.mean(bvalues),"+/-",np.std(bvalues)/np.sqrt(Nensemble-1),", gen =",b1)
mean aX value = 0.9829722097543518 +/- 0.019840535134131847 , gen = 0.74
mean bX value = -2.3048945875855527 +/- 0.024140253716692855 , gen = 0.11
Upvotes: 0
Views: 104
Reputation: 11042
There are multiple minima for the second model, changing the initial parameter for (0., 5.)
allow the algorithm to reach the second minima where b>0
.
Or as suggested by reinderien
add bounds to filter out the first minima.
First lets check your second model can fit the first one:
a1 = 0.740
b1 = 0.1100
def model1(x, a, b):
return np.exp(-(a * x + b * x **2))
def model2(x, a, b, N=46):
return np.exp(- (a + b) * x) * (1 + b * x / N) ** N
xlin = np.linspace(0.5, 9.5, 200)
ylin = model1(xlin, a1, b1)
popt, pcov = optimize.curve_fit(model2, xlin, ylin, p0=[0., 1.])
# (array([ 0.7524258 , -2.93622762]),
# array([[2.51911174e-07, 1.96469005e-06],
# [1.96469005e-06, 1.92412517e-05]]))
We can confirm that we have correct fitness between both model.
Now lets check this is the optimal set of parameters:
def loss_factory(model, x, y):
@np.vectorize
def wrapper(a, b):
return 0.5 * np.sum(np.power(y - model(x, a, b), 2))
return wrapper
alin = np.linspace(0, 2, 300)
blin = np.linspace(-5, 5, 300)
A, B = np.meshgrid(alin, blin)
loss = loss_factory(model2, xlin, ylin)
SSE = loss(A, B)
We can see there are two minima in the explored parameters region. Where the lower one (negative b
) is found because of the initial parameters.
To reach the second minima, just change initial parameters:
popt, pcov = optimize.curve_fit(model2, xlin, ylin, p0=[0., 5.])
# (array([0.72716386, 3.44478853]),
# array([[ 2.99618275e-07, -2.70844576e-06],
# [-2.70844576e-06, 2.85220271e-05]]))
Or add bounds:
popt, pcov = optimize.curve_fit(model2, xlin, ylin, p0=[0., 1.], bounds=(0., np.inf))
# (array([0.72716384, 3.44478875]),
# array([[ 2.99617959e-07, -2.70844633e-06],
# [-2.70844633e-06, 2.85220715e-05]]))
The very nature of error landscape SSE
is due to the dataset (here strictly following the model2
) and the existence of multiple minima is most probably an effect of the even power (N=46
).
Upvotes: 1