BFGS (Broyden-Fletcher-Goldfarb-Shanno) algorithm for multivariate function minimization.
BFGS is a quasi-Newton optimization method that approximates the inverse Hessian matrix using gradient information. It is well-suited for smooth, differentiable objective functions and typically converges much faster than derivative-free methods like Nelder-Mead.
Algorithm
At each iteration, the algorithm:
- Computes the gradient using automatic differentiation
- Determines a search direction from the inverse Hessian approximation
- Performs a line search to find an acceptable step length
- Updates the inverse Hessian approximation using the BFGS formula
Convergence
The algorithm converges when the gradient norm falls below the specified tolerance.
References
- Nocedal, J. and Wright, S. J. (2006). "Numerical Optimization"
- Fletcher, R. (1987). "Practical Methods of Optimization"
Summary
Functions
Minimizes a multivariate function using the BFGS algorithm.
Types
@type t() :: %Scholar.Optimize.BFGS{ converged: Nx.Tensor.t(), fun: Nx.Tensor.t(), fun_evals: Nx.Tensor.t(), grad_evals: Nx.Tensor.t(), iterations: Nx.Tensor.t(), x: Nx.Tensor.t() }
Functions
Minimizes a multivariate function using the BFGS algorithm.
BFGS is a gradient-based method that uses automatic differentiation to compute gradients and approximates the inverse Hessian matrix for fast convergence.
Arguments
x0- Initial guess as a 1-D tensor of shape{n}.fun- The objective function to minimize. Must be a defn-compatible function that takes a 1-D tensor of shape{n}and returns a scalar tensor.opts- Options (see below).
Options
:gtol- Gradient norm tolerance for convergence. The algorithm stops when the infinity norm of the gradient is below this threshold. The default value is1.0e-5.:maxiter(pos_integer/0) - Maximum number of iterations. The default value is500.
Returns
A Scholar.Optimize.BFGS struct with the optimization result:
:x- The optimal point found (shape{n}):fun- The function value at the optimal point:converged- Whether the optimization converged (1 if true, 0 if false):iterations- Number of iterations performed:fun_evals- Number of function evaluations:grad_evals- Number of gradient evaluations
Examples
iex> # Minimize a simple quadratic: f(x) = x1^2 + x2^2
iex> fun = fn x -> Nx.sum(Nx.pow(x, 2)) end
iex> x0 = Nx.tensor([1.0, 2.0])
iex> result = Scholar.Optimize.BFGS.minimize(x0, fun)
iex> Nx.to_number(result.converged)
1
iex> Nx.all_close(result.x, Nx.tensor([0.0, 0.0]), atol: 1.0e-4) |> Nx.to_number()
1For higher precision, use f64 tensors:
iex> fun = fn x -> Nx.sum(Nx.pow(x, 2)) end
iex> x0 = Nx.tensor([1.0, 2.0], type: :f64)
iex> result = Scholar.Optimize.BFGS.minimize(x0, fun, gtol: 1.0e-10)
iex> Nx.to_number(result.converged)
1
iex> Nx.all_close(result.x, Nx.tensor([0.0, 0.0]), atol: 1.0e-8) |> Nx.to_number()
1Minimizing the Rosenbrock function:
iex> # Rosenbrock: f(x,y) = (1-x)^2 + 100*(y-x^2)^2, minimum at (1, 1)
iex> rosenbrock = fn x ->
...> x0 = x[0]
...> x1 = x[1]
...> term1 = Nx.pow(Nx.subtract(1, x0), 2)
...> term2 = Nx.multiply(100, Nx.pow(Nx.subtract(x1, Nx.pow(x0, 2)), 2))
...> Nx.add(term1, term2)
...> end
iex> x0 = Nx.tensor([0.0, 0.0], type: :f64)
iex> result = Scholar.Optimize.BFGS.minimize(x0, rosenbrock, gtol: 1.0e-8, maxiter: 1000)
iex> Nx.to_number(result.converged)
1
iex> Nx.all_close(result.x, Nx.tensor([1.0, 1.0]), atol: 1.0e-4) |> Nx.to_number()
1