This article explains how to solve linear and nonlinear least-squares problems and provides C# implementation for it. All the numerical methods used in the demo project stand on top of vector and matrix operations provided by the Math.NET numerical library. You can, of course, adjust the presented algorithms to work with any other numerical library that can handle commonly used matrix operations (e.g. LAPACK).
The tutorial covers discrete least-squares problems (i.e. fitting a set of points with known model). We will care about over-determined problems (e.g. where the number of measurements are greater than number of unknowns) and assume the problems are well-conditioned. The tutorial goes from simple to more complex and more general methods.
This article was written for programmers with basic knowledge of matrix computations and calculus.
The least-squares problem arise in many applications (statistics, financial, physics...). It can be viewed as an optimization problem. We gathered some data and want to find model parameters for which the model best fits the data in some sense.
The following section is purely mathematical. It is a general description of the problem, without reference to any specific application (e.g. fitting parabola to a set of points...). However, it is crucial for the following derivation of solutions for both linear and nonlinear problems.
The least-squares problem can be written compactly as:
where is vector of model parameters and is called a residual vector formed from individual residuals:
Here is a number of measurements (number of data points) and each represents a discrepancy between the expected and measured value:
As you can see, the function is our model function (or expectation model) which return expected value for the given point and the model parameters. This value is compared to , which is the observed quantity. The data points form vectors (e.g. points on time axis) and (e.g. measurements in the corresponding times). The tilde mark (~) is used to distinguish data points from model parameters () and the model function ().
The residual value can be either positive or negative and in both cases it means that the model deviates from measured values. We would like to have as small residuals as possible, but in absolute value. This is why we look for least squares of the residuals, rather than residual values itself. Note that there exist variants of the above optimization problem, where different norms (e.g. absolute values) are used instead of squares. But least squares problem is easy to solve and have a remarkable statistical property of being so called maximum likelihood estimator when errors between measured values and expectations are distributed normally.
The norm in equation (1) is a 2-norm, so we can re-write this equation in terms of the objective function in the well-known form:
Of course, we would like to find parameters for which this function reaches its minimum. The constant does not have any effect on the solution, but makes derivatives simpler (the exponent 2 of each becomes a factor of 2, so the factor before sum disappears in the derivative).
We place partial derivatives of equal to zero to find the local minimizer of . This is straightforward in the linear case, since the objective function happens to be convex. This means that any local minimizer of the function is also its global minimizer. In the nonlinear case, however, there can be multiple local minima and we need to exploit more of the function's structural properties.
Now we can be more specific about the problems we solve, but linear least-squares can still be applied to a whole family of functions. For example, when we are fitting straight line to 2D points (a common example), our model function has a form:
But we are not limited to just straight lines. We can fit any fuction that is linear in the paramters . For example, this model function:
can be used in linear least-squares framework as well.
The general form of the model function is:
Here are the model parameters, and are basis functions. The basis functions can be any functions of and can be nonlinear.
As we can observe, derivatives of residuals with respect to model parameters are particularly simple:
We can put all the partial derivatives to a single matrix called design matrix:
The residuals can be written as:
or in matrix terms:
With the above knowledge, we can write in terms of Jacobian:
We know from (1) that we need to minimize to obtain solution. This can be done by taking a derivative of the above expression with respect to and placing it equal to zero:
This is equivalent to a linear system called normal equations:
We first compute product on the left hand side, and the product on the right hand side. Then we have a system of linear equations in a form .
This can be solved in various ways, so let's play with the numerical library and try different approcaches...
Since the matrix is square, we can compute its inverse and obtain solution directly from equation (4):
The parameters can be computed this way in a single line of code:
x = A.Transpose().Multiply(A).Inverse().Multiply(A.Transpose().Multiply(dataY));
Much less expensive and more stable approach would be the Cholesky decomposition of the matrix on left hand side:
The Cholesky decomposition produces an upper triangular matrix and its transpose so we are able to compute solution by successive forward and backward substitutions:
Math.NET library provides means for computing both the Cholesky factorization and methods for solving linear systems of the form :
x = A.Transpose().Multiply(A).Cholesky().Solve(A.Transpose().Multiply(dataY));
The Cholesky decomposition is guaranteed to exist in our well-behaved problems (which are overdetermined - there are more points than unknowns and the Jacobian has full rank).
The presented approach (use of normal equations) works well, but only when is well conditioned (i.e. it is not singular or near-singular). If is ill-conditioned, then the product will be even worse (condition number gets squared by the matrix multiplication) and we might not be able to compute solution due to roundoff errors (the computation of either matrix inverse or Cholesky decomposition may fail).
The solution with normal equations and Cholesky decomposition is useful when and it is efficient to store product rather than . It can be also effective when is sparse.
This approach gets over the problem with squaring the condition number by decomposing the Jacobian into product of orthogonal () an upper triangular () matrix. Since the multiplication by orthogonal matrix preserves norm of the resulting vector, the condition number of won't get spoiled.
We substitute in equation (5) to obtain:
This is equivalent to linear system can be solved with Math.NET very easily:
x = A.QR().Solve(dataY);
If we are forced to use just basic matrix operations, we should be aware of the fact that the matrix is rectangular, so the QR decomposition have form:
The matrix would be of size and the matrix of size in the original decomposition. We should split the and keep only first columns (so we get matrix of size . We should also cut off zero rows from such that it will be of size :
// compute the QR decomposition QR qr = A.QR(); // get orthogonal matrix with first n columns of Q Matrix<double> Q1 = qr.Q.SubMatrix(0, m,0, n); // get upper-triangular matrix of size n x n Matrix<double> R = qr.R.SubMatrix(0, n,0, n); x = R.Inverse().Multiply(Q1.Transpose().Multiply(dataY));
Of course, it is more effective to solve the linear system .
The solution using QR decomposition is numerically more stable than normal equations.
The third (and the most advanced) way of solving linear least squares problem is using SVD. This decomposition splits our matrix into product where , are orthogonal and , where 's are so called singular values with ordering: .
If the decomposed matrix is singular or near singular, we get equal to zero, or very close to zero. Furthermore, the fraction is condition number of the decomposed matrix. This allows us to check for stability of our solution before we actually compute it. We can also solve underdetermined problems with SVD, but this is out of scope of this article (see [nocedal] or [watkins] for further reference).
Following the same logic as in the QR approach, we substitute in equation (5):
The computation of system can be carried out by the numerical library:
x = A.Svd(true).Solve(dataY);
If we want to use just the basic matrix operations, we should be aware of the fact that is not square:
The matrix would be of size and the matrices and of size in the original decomposition. We should split the and keep only first columns (so we get matrix of size . We should also cut off zero rows from such that it will be of size :
// compute the SVD Svd svd = A.Svd(true); // get matrix of left singular vectors with first n columns of U Matrix<double> U1 = svd.U().SubMatrix(0, m,0, n); // get matrix of singular values Matrix<double> S =newDiagonalMatrix(n, n, svd.S().ToArray()); // get matrix of right singular vectors Matrix<double> V = svd.VT().Transpose(); x = V.Multiply(S.Inverse()).Multiply(U1.Transpose().Multiply(dataY));
And of course, it is more efficient to solve the linear system .
The plot shows a line () with parameters. The data points are generated around this line:
Linear least-squares method yields a new line () that minimized sum of squared vertical distances from the data points:
Most of the data points are placed below the function with true model parameters (see the gray line). The estimated line is thus placed a little lower than the original one and the estimated parametes are slightly different. We chose strongly perturbed dataset to show how errors can affect the estimated parameters. Of course, this also depends on the number of points, their relative distance and magnitude of the error introduced.
Up to now, we could solve least-squares problems directly because the model function depended on its parameters linearly. In the purely nonlinear waters, however, the parameters cannot be separated from the model function. So instead of solving linear problem , we have something like (i.e. the design matrix also depends on model parameters).
The best we can do in nonlinear case is to guess some starting values for parameter vector and move toward solution, then update the parameters and repeat the process, hence use iterative method.
What we called the design matrix in linear problems, would be Jacobian matrix in nonlinear problems. It is defined the same way as the design matrix:
but the difference is that it depends on model parameters .
We can construct Jacobian directly from our model function (it follows from the definition):
In linear least squares, the partial derivatives would leave only basis functions and we would get the design matrix . But now we have to stick with this general form since our model function cannot be broken to products of parameters and basis functions which do not depend on .
The other difference from linear least squares it that we will need to examine not only values of the objective function, but also its first and second order derivatives. Since the objective function is , we will use multivariate counterparts for derivatives, namely gradient and Hessian. Both can be expressed in terms of Jacobian:
When solving nonlinear least squares problems, the second summation term in Hessian:
is usually negligible compared to the first term . Furthermore, the quantities and are small near the solution (thus their product is even smaller) so we can drop the term without much harm.
This allows us to compute approximate Hessian without using second derivatives of . The simplified expressions for gradient and Hessian follows:
This convenient, because user to provide just prescription for the model function (as in the linear least squares) plus its partial derivatives (with respect to each parameter) and the algorithm constructs all the neccessary structures by itself:
- Jacobian: Matrix of first-order derivatives of the residuals with respect to every parameter and every measurement. Approximates design matrix of the linear least squares model.
- gradient: Vector of first-order derivatives of the objective function (or current approximation to it) with respect to model parameters. Describes slopes of the objective function at some point .
- Hessian: Matrix of second-order partial derivatives of the objective function (or current approximation to it) with respect to every combination of parameters. Describes curvature of objective function at some point .
Every iterative method starts with an initial estimate (here we use index in parenthesses to distinguish iteration counter from element of a vector) and compute new estimate in a step with the following form:
Forthermore, value of the objective function should be smaller at each step:
By approximating left hand side of this inequality by first-order Taylor polynomial we have:
This gives us decrease condition in terms of objective function's gradient:
The steepest descent method is a general function minimization method. It is not a method specialized in solving least squares problems, but it is a good one to start with.
Each iteration consists of three steps:
Select step direction .
Select step length (line search).
Update parameters with the steepest descent step , where .
Direction of the step () is always the one in which the objective function decreases most rapidly, hence the name of the method.
To derive speepest descent direction, let's start with directional derivative of our objective function in direction , which is
We take the right side and continue computing the step:
Note that we assume and because only angle in which the derivative is minimal is considered (the steepest direction) and we care only about direction, not magnitude of the step, so the length of the step is considered unit.
Since is a vector, we can rewrite its inverse as
The steepest descent step can thus be expressed as:
or in terms of Jacobian and residual:
Obtaining the step direction is straightforward, but computing step length is tricky, since we do not have information about scale.
The best option would be to select such that is minimized. This is called exact line search. It is possible to perform exact line search in linear case and other cases when we know the form of the model function.
In general (nonlinear) case, however, we can only perform inexact line search. There are several strategies on how to find . The line search algorithms consists of setting boundaries for the value in which the minimizer lies (bracketing phase) and then trying several values of to approximate the minimizer (backtracking phase). Good line search algorithm should also consider sufficient decrease conditions, such as Wolfe conditions to ensure that the step decreases objective function value sufficiently and the method will not need to run thousands of iterations to obtain reasonable precision.
To keep implementation understandable, the line search in demo implementation is reduced to just shortening the step if objective value function does not decrease, and lenghtening it oterwise. This is very ineffective, but the demo implementation of steepest descent is just a proof of concept.
To compute the iteration step , we approximate by second-order Taylor polynomial:
and set derivative of the right hand side (with respect to ) to zero:
From there we have formula for the so called Newton step:
We can conclude from the above that the Newton step will take us to a stationary point of the quadratic approximation of . We cannot say whether it is a minimum (desired), maximum or a saddle point.
In either case, the Newton step should agree with the decrease condition (8) to work. We substitute this step into condition to inspect when it actually decreases function value:
As we can see, this condition holds for any nonzero (i.e. ) only if (or some approximation to it, like ) is positive definite. The positive definiteness of Hessian is an assumption in method that uses pure Newton steps.
Substituing expressions for gradient (6) and approximate Hessian (7) into (9) gives us definition of the Gauss-Newton step:
The step can as be written as:
Compare the last equation to (4). The Gauss-Newton method actually solves system similar to normal equations in each iteration. The method actually approximates nonlinear model by the linear one and solves it. Then updates the current estimate for better approximation.
Of course, any of the presented approaches for linear least squares (Cholesky, QR, SVD) can be used to solve for Gauss-Newton step.
Being more general, Gauss-Newton method can be used to solve linear problems as well. In such case, it converges in a single iteration.
The Gauss-Newton method works well unless the Hessian gets singular or near-singular. This can happen when dealing with "highly nonlinear" functions. Other drawback of the Gauss-Newton method is when the initial guess is far from minimizer. In such case, the method converges very slowly or may not converge at all.
These problems can be addressed by modifying the Hessian matrix to improve convergence of the method. As we said earlier, the step direction is a descent direction whenever the Hessian is positive definite. This may not be the case in every point of the objective function. The function can be convex in some direction and concave in other (i.e. the Hessian is indefinite). The simple aid to make the hessian approximation positive definite is by adding multiple of identity:
where is a positive number (called damping parameter) and is an identity matrix of size . What happens is that we add positive numbers to the diagonal of , thus making it diagonally dominant - any negative diagonal terms become positive and any negative off-diagonal terms eventually negligible, hence the matrix can be made positive definite.
If we replace in the Gauss-Newton formula (10) by our updated version with damping parameter, we obtain the Levenberg step:
As the approaches zero, the method becomes more like Gauss-Newton. But the larger the is, however, the smaller and safer steps are made in the descent direction (now governed by rather then ).
Since is a scalar, it scales all diagonal elements of Hessian equally. This works generally well, if the problem is well scaled. Each parameter, however, can use different units (e.g. kilometers versus millimeters) and thus be scaled differently. Here comes aid from Marquardt, who noticed that diagonal elements of Hessian actually holds information about scaling. Instead of identity matrix, we can use a diagonal matrix that takes scaling into acount:
This is the Levenberg-Marquardt step used in most implementations.
We can rewrite it in terms of Jacobian and residual as
Here is an outline for the Levenberg-Marquardt algorithm:
The algorithm takes initial guess (p_curr) as an input. It initializes (we use 0.001 in demo project as the initial value), computes value of the objective function, obtains Jacobian and residual and computes the Levenberg-Marquardt step using all the obtained information.
Next, a new parameter vector (p_new) and objective function value (v_new) are computed.
If the objective function value has decreased (v_new < v_curr), the algorithm decreases (division by 10 in the demo project) and sets newly computed parameters and objective function value as the current.
If the objective function has not decreased, the is increased (multiplication by 10.0 in the demo project) so that safer step in descent direction will be made in the next iteration.
Finally, the algorithm checks termination conditions and either ends or continues with the next iteration.
We start with a model function where and . The data points are scattered over the graph with variable horizontal distances. This is important since we are fitting a periodic function (bad sampling would cause unstable or ambiguous solution).
The first graph shows initial guess with . The latter three graphs contain model function for parameters from the first, third and tenth iteration of Gauss-Newton method:
The data points are not perturbed so the estimated paramters match the true ones to four decimal places after ten iterations.
All the presented methods have been implemented in a C# application called LeastSquaresDemo:
The combo box in the top-left corner allows you to select preset. There are several presets with sample settings.
Below is the options for the current preset:
These allows you to adjust current model, dataset (set of points to fit) and the method to be used for data fitting.
Further options are available through the "cogwheel" buttons.
The only options for model are its parameters:
You can edit them in the dialog window. For example, the line model has two parameters that define the line.
There are two kinds of datasets: Exact and Perturbed. The first one generates data points that corresponds to function values exactly. The second one adds noise to simulate real-world data. Both the horizontal and vertical positioning of data points can be further adjusted:
Finally, the method to be used for data fitting can be adjusted. You can choose whether to use effective solvers from Math.NET that use matrix decompositions (applies to linear models) and stopping conditions (applies to nonlinear models):
Under the panel with options, there is an iteration list:
It contains initial parameters (the initial guess) which you can edit by clicking on the numbers.
When the fitting is performed, it contains estimated parameters from every iteration of the fitting method. The linear least squares method need jsut one iteration, so there can appear only two lines (the guess which is actually not used and the solution). When nonlinear models and methods are used, there can be many iterations.
The fitting process is started by the button on the bottom left part of the form:
The button contains context menu so you can only fit the current data, or generate new and then fit.
Finally, there is a graph showing the data points and all the currently selected iterations. Model function for initial guess is plotted in gray, while the computed iterations are in red:
The code regarding least squares fitting is divided into three sets of classes:
The Dataset class serves for generating data points, either lying on the model curve, or being scattered around it.
The Model class represents model function (or expectation model, if you will). There are two linear model and two non-linear. The class provides value of the model function (and its gradient () for specified data point coordinate value and model parameters .
The Solver class represents a method used for solving the least-squares problem, either linear or nonlinear. There are three implementations for linear least-squares and three for nonlinear least-squares.
The actual fitting is done in the Fit() method of MainForm.
The method checks whether the problem can be solved (there is enough points and we are not trying to use linear solver for a nonlinear model).
I have not implemented any extra dummy-proofing, so beware of putting in values that are out of range.
All the presented methods can be improved on multiple levels.
One of the possible improvement is the computations of function gradient when the Jacobian is expected to be sparse. Large and sparse matrices often arise in real-world application, thus I would like to point reader to [Watkins] (linear problems) or [Nocedal] (nonlinear problems) where such efficient implementations are described in depth.
Another improvement lies in better conditioning of the matrix we invert during computation. Better conditioning brings more accurate and stable solution with less iterations needed. [Watkins] covers the topic of preconditioning well.
Least squares method works best if the data points are scattered around our expectation model with normal distribution. There can also be a systematic error and we would like to know precision and accuracy of our estimator. The noisy data can also have other than normal distribution (e.g. Poisson). [Bos] covers the topic of dealing with such cases, even estimating the precision and accuracy of the expectation model is covered here.
The data can also have error in both coordinates. Such problem is discussed in [Press] (as well as the measurement of confidence intervals and other statistical properties) and in [Nocedal] (orthogonal distance regression).
[Bos] Van den Box, A., "Parameter Estimation for Scientists and Engineers", Wiley Online Library, 2007
[Nocedal] J. Nocedal and S. J. Wright, "Numerical Optimization", Springer Verlag, 1999
[Press] Press, W.H. and Flannery, B.P. and Teukolsky, S.A. and Vetterling, W.T. and others, "Numerical Recipes", Cambridge Univ Press, 2007
[Watkins] Watkins, D.S., "Fundamentals of Matrix Computations", Wiley, 2005