Consider a problem of estimating a constant but unknown \(\mathbf{x} \in \mathbb{R}^n\) given \(m\) measurements, \(\mathbf{y} \in \mathbb{R}^m\) where \(m > n\). Assume that the measurement model is nonlinear scalar functions:
If \(\Delta \mathbf{x}_k\) is small enough, stop the algorithm.
Else, set \(\mathbf{x}_{k + 1} = \mathbf{x}_k + \Delta \mathbf{x}_k\) and repeat from step 2.
The main problem is on how to find \(\Delta \mathbf{x}_k\).
First and Second-Order Methods
Consider the \(k\)-th iteration. Suppose we are at \(\mathbf{x}_k\) and want to find the increment \(\Delta \mathbf{x}_k\). The second-order Taylor series of the measurement function is:
where \(\mathbf{J}(\mathbf{x}_k)\) is the Jacobian or and \(\mathbf{H}(\mathbf{x})\) is the Hessian. Refer to Unconstrained Optimization for more details.
\[
y = \exp \left\{ a x^2 + bx + c \right\} + w,
\]
where \(a, b, c\) are parameters to be estimated, and \(w \sim \mathcal{N}(0, \sigma^2)\) is a zero-mean Gaussian noise. Suppose we have \(N\) observation. The least squares problem can be formulated as:
importnumpyasnpimportmatplotlib.pyplotaspltfromnumpy.randomimportnormaldefmodel(x,a,b,c):d=a*x*x+b*x+cy=np.exp(d)returny# Ground truth valuesa_gt,b_gt,c_gt=1.0,2.0,1.0N=100x=np.zeros((N,),dtype=np.float64)y_gt=np.zeros((N,),dtype=np.float64)y_meas=np.zeros((N,),dtype=np.float64)y_est=np.zeros((N,),dtype=np.float64)# Noise propertiesw_sigma=1.0inv_sigma=1.0/w_sigma# Generate N ground truth and noisy measurementsforiinrange(0,N):x[i]=i/float(N)y_gt[i]=model(x[i],a_gt,b_gt,c_gt)y_meas[i]=y_gt[i]+normal(0,w_sigma)# Gauss-Newtonnum_iter=100cost,last_cost=0.0,0.0a,b,c=2.0,-1.0,5.0forjinrange(0,num_iter):H=np.zeros((3,3),dtype=np.float64)# Hessian = J^T W^{-1} Jbias=np.zeros((3,1),dtype=np.float64)# biascost=0.0foriinrange(0,N):x_i,y_i=x[i],y_meas[i]h_i=model(x_i,a,b,c)r_i=y_i-h_i# Compute Jacobian at x_iJ=np.zeros((3,1),dtype=np.float64)J[0]=-x_i*x_i*h_iJ[1]=-x_i*h_iJ[2]=-h_iH+=inv_sigma*inv_sigma*J@J.Tbias+=-inv_sigma*inv_sigma*r_i*Jcost+=r_i*r_iH_pseudo_inv=np.linalg.inv(H.T@H)dx=H_pseudo_inv@H.T@biasif(j>0andcost>=last_cost):breaka+=dx[0]b+=dx[1]c+=dx[2]last_cost=costforiinrange(0,N):y_est[i]=model(x[i],a,b,c)fig,ax=plt.subplots()ax.plot(x,y_gt,"-r",label="Ground truth")ax.plot(x,y_meas,".b",label="Measurement")ax.plot(x,y_est,".g",label="Estimated")ax.set_xlabel("x")ax.set_ylabel("y")ax.grid(True)ax.legend()fig.savefig("curve_fitting_with_gn.png",dpi=1000)
``` python linenums="1"
include
int main()
{
omp_set_num_threads(16); // OPTIONAL - Can also use
// OMP_NUM_THREADS environment variable