I have been itching to write a post on writing an optimization/minimization code using GNU Scientific Library for Lisp (GSLL). For this post, I had to go into the GSLL code itself which contains a good set of examples. I highly recommend reading it. So, here it goes:
Let me start with optimizing a real function with one variable. For a theoretical survey on the subject of one dimensional optimization problems I recommend Wotao Yin’s lecture notes.
First, let us load up GSLL:
require :gsll) (
(SB-CLTL2)
Next, let us define the function we are going to optimize:
defun fn (x)
(+ (expt x 4) (* -1.4d1 (expt x 3)) (* 6.0d1 (expt x 2)) (* -7.0d1 x))) (
FN
In GSLL, I need to create an minimizer which takes a function, an interval and an initial point.
defparameter minimizer
(
(gsl:make-one-dimensional-minimizer
gsl:+quad-golden-fminimizer+#'fn
1d0 0.0d0 2.0d0
1d0) (fn 0.0d0) (fn 2.0d0))) (fn
MINIMIZER
I have used a specific minimizer but there are few others. Now, I will iterate the minimizer until it converges unless it takes more than 1000 steps:
let ((absolute-error 1.0d-3)
(1.0d0)
(size nil))
(flag do ((i 0 (1+ i)))
(or flag (> i 1000))
((cons (gsl:solution minimizer) i))
(
(gsl:iterate minimizer)setf size (- (gsl:fminimizer-x-upper minimizer)
(
(gsl:fminimizer-x-lower minimizer)))setf flag (gsl:min-test-size size absolute-error)))) (
0.7809263974476345d0 . 16) (
For the multivariate optimization function, I am going to re-implement ordinary least square regression as my running example. First, I am going to need data on which I am going to fit a regression line:
defparameter data
(let ((a 2.0d0)
(1.0d0))
(b loop repeat 200 collect
(let ((x (random 1.0d0)))
(cons x (+ (* a x) b (* 0.1 (random 1.0d0)))))))) (
DATA
Next, the function we need to optimize. This is the ordinary euclidean distance between the data points and their interpolation:
defun fn (u v)
(sqrt (loop for point in data sum
(let ((x (car point))
(cdr point)))
(y (expt (- (+ (* u x) v) y) 2))))) (
FN
Of course, we need a minimizer:
defparameter minimizer
(
(gsl:make-multi-dimensional-minimizer-f;; method
gsl:+simplex-nelder-mead+ 2 ;; dimension
#'fn
'double-float :initial-contents '(0.0d0 0.0d0)) ;; initial approximation
(grid:make-foreign-array 'double-float :initial-contents '(1.0d-2 1.0d-2)))) ;; step-size (grid:make-foreign-array
MINIMIZER
Let us roll the ball:
let ((absolute-error 1.0d-3)
(nil))
(flag do ((i 0 (1+ i)))
(or flag (> i 1000))
((cons (gsl:solution minimizer) i))
(
(gsl:iterate minimizer)setf flag (gsl:min-test-size (gsl:size minimizer) absolute-error)))) (
1.998 1.052) . 46) ((
We get an approximate solution in 46 steps. Nice!
In the past here and here, I used minimizers that myself wrote. But, as pleasurable as it was to write my own routines, it is safer and less error-prone to use a highly optimized well-written stable specialized library such as GSLL.