Bayesian Optimization

DRAFT!

TODO: description

Setup

{:deps
 {org.clojure/clojure {:mvn/version "1.10.1"}
  cljplot {:mvn/version "0.0.2-SNAPSHOT"}
  generateme/fastmath {:mvn/version "1.4.0-SNAPSHOT"}
  clojure2d {:mvn/version "1.2.0-SNAPSHOT"}}}
deps.edn
Extensible Data Notation
(require '[cljplot.build :as b]
         '[cljplot.core :refer :all]
         '[fastmath.core :as m]
         '[fastmath.kernel :as k]
         '[fastmath.random :as r]
         '[fastmath.optimization :as o]
         '[fastmath.classification :as cl]
         '[clojure2d.color :as c]
         '[clojure.java.io :as io]
         '[clojure.string :as str])
27.5s
Clojure

Usage

(defn black-box-function
  ^double [^double x ^double y]
  (inc (- (- (m/sq x))
       (m/sq (dec y)))))
(def bounds [[2 4] [-3 3]])
(black-box-function 1.0 0.0)
0.1s
Clojure
-1
(o/maximize :bobyqa black-box-function {:bounds bounds})
0.2s
Clojure
Vector(2) [List(2), -3]
(def bo (o/bayesian-optimization black-box-function {:bounds bounds}))
(map #(select-keys % [:x :y]) (take 5 bo))
1.2s
Clojure
List(5) (Map, Map, Map, Map, Map)
(select-keys (nth bo 25) [:x :y])
2.8s
Clojure
Map {:x: List(2), :y: -3.000000003303626}
(first bo)
0.3s
Clojure
Map {:x: List(2), :y: -3.0010215642887346, :util-fn: Vector(4), :gp: Vector(4), :xs: List(4), :ys: List(4), :util-best: List(2)}

Function 1d

Function 2d

(def palette (reverse (:rdylbu-9 c/palette-presets)))
(def gradient (c/gradient palette))
(defn draw-2d
  [f bounds bayesian-optimizer iteration]
  (let [{:keys [util-fn gp xs]} (nth bayesian-optimizer iteration)
        cfg {:x (first bounds)
             :y (second bounds)
             :palette palette
             :contours 30
             :gradient gradient}]
  (xy-chart {:width 700 :height 700}
            (b/series [:contour-2d f (assoc cfg :position [0 1])]
                      [:scatter xs {:size 10 
                                    :color (c/darken (first palette))
                                    :margins {:x [0 0] :y [0 0]}
                                    :position [0 1]
                                    :label "2d function with guessed points."}]
                      [:function-2d util-fn (assoc cfg :position [0 0] 
                                                   :label "Utility function")]
                      [:contour-2d (fn [x y] (gp [x y]))
                       (assoc cfg :position [1 1]
                              :label "Gaussian processes - mean (interpolation)")]
                      [:contour-2d (fn [x y] (second (gp [x y] true))) 
                       (assoc cfg :position [1 0]
                              :label "Gaussian processes - std dev")])
                  (b/add-axes :bottom)
                  (b/add-axes :left))))
0.2s
Clojure
user/draw-2d
(defn black-box-function2
  ^double [^double x ^double y]
  (+ 100.0 (* (m/sinc (+ 2.0 x y))
           (m/sin (+ x x))
           (m/cos (+ y y))
           (m/sin (* 2.0 x y)))))
(def bounds2 [[-1.0 0.5] [-2 -0.5]])
(def draw-black-box2 (partial draw-2d black-box-function2 bounds2))
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2}))
(save (draw-black-box2 bo 0) "/results/black-box2.jpg")
5.1s
Clojure
(save (draw-black-box2 bo 10) "/results/black-box2-10.jpg")
6.0s
Clojure
(save (draw-black-box2 bo 40) "/results/black-box2-40.jpg")
11.0s
Clojure

Data normalization

(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :normalize? false}))
(save (draw-black-box2 bo 10) "/results/black-box2-not-normalized.jpg")
5.2s
Clojure

Utility functions and parameters

(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :ucb
                                                      :utility-param 0.1}))
(save (draw-black-box2 bo 15) "/results/black-box2-ucb-low.jpg")
5.4s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :ucb
                                                      :utility-param 8}))
(save (draw-black-box2 bo 15) "/results/black-box2-ucb-high.jpg")
5.3s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :poi
                                                      :utility-param 0.01}))
(save (draw-black-box2 bo 15) "/results/black-box2-poi-low.jpg")
5.5s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :poi
                                                      :utility-param 0.9}))
(save (draw-black-box2 bo 15) "/results/black-box2-poi-high.jpg")
5.0s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :ei
                                                      :utility-param 0.01}))
(save (draw-black-box2 bo 15) "/results/black-box2-ei-low.jpg")
5.4s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :noise 0.01
                                                      :utility-function-type :ei
                                                      :utility-param 0.9}))
(save (draw-black-box2 bo 15) "/results/black-box2-ei-high.jpg")
5.4s
Clojure

Kernel

(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :optimizer :cmaes
                                                      :utility-function-type :ei
                                                      :kernel (k/kernel :mattern-12 0.1)
                                                      :noise 0.1}))
(save (draw-black-box2 bo 15) "/results/black-box2-narrow.jpg")
5.5s
Clojure
(def bo (o/bayesian-optimization black-box-function2 {:bounds bounds2
                                                      :utility-function-type :ei
                                                      :kernel (k/kernel :mattern-12 2)
                                                      :noise 0.1}))
(save (draw-black-box2 bo 15) "/results/black-box2-wide.jpg")
5.0s
Clojure

Hyperparameter search

sonar.csv
(def dataset (with-open [data (io/reader 
sonar.csv
)]
             (mapv #(-> %
                        (str/trim)
                        (str/split #",")
                        (->> (map read-string))) (line-seq data))))
(count dataset)
0.6s
Clojure
208
(def xs (map butlast dataset))
(def ys (map (comp keyword last) dataset))
(frequencies ys)
0.3s
Clojure
Map {:R: 97, :M: 111}

Ada boost

(defn ada-boost-params
  ^double [^double trees ^double nodes]
  (let [ada-boost (cl/ada-boost {:number-of-trees (int trees)
                                 :max-nodes (int nodes)} xs ys)]
    (:accuracy (cl/cv ada-boost))))
(def ada-bounds [[1 (count dataset)]
                 [2 (count dataset)]])
(ada-boost-params 10 20)
0.9s
Clojure
0.7548076923076923
(def ada-boost-bo (o/bayesian-optimization ada-boost-params
                                           {:init-points 5
                                            :kernel (k/kernel :mattern-52 55.0)
                                            :noise 0.05
                                            :utility-param 0.1
                                            :bounds ada-bounds}))
(select-keys (first ada-boost-bo) [:x :y])
8.3s
Clojure
Map {:x: Vector(2), :y: 0.8461538461538461}
(select-keys (nth ada-boost-bo 20) [:x :y])
26.1s
Clojure
Map {:x: List(2), :y: 0.8557692307692307}
(defn draw-2d-2
  [bounds bayesian-optimizer iteration]
  (let [{:keys [gp xs]} (nth bayesian-optimizer iteration)
        cfg {:x (first bounds)
             :y (second bounds)
             :palette palette
             :contours 30}]
    (xy-chart {:width 700 :height 300}
              (b/series [:contour-2d (fn [x y] (gp [x y])) cfg]
                        [:contour-2d (fn [x y] (second (gp [x y] true))) 
                         (assoc cfg :position [1 0]
                                :label "Gaussian processes - std dev")]
                        [:scatter xs {:size 10 
                                      :color (c/darken (first palette))
                                      :margins {:x [0 0] :y [0 0]}
                                      :position [0 0]
                                      :label "Gaussian processes - mean (interpolation)"}])
              (b/add-axes :bottom)
              (b/add-axes :left))))
(save (draw-2d-2 ada-bounds ada-boost-bo 20) "/results/ada-boost.jpg")
3.4s
Clojure

SVM

(defn svm-params
  ^double [^double cp ^double cn]
  (let [cp (m/pow 10 cp)
        cn (m/pow 10 cn)
        svm (cl/svm {:c-or-cp cp :cn cn
                     :kernel (k/kernel :gaussian)} xs ys)]
    (m/log (:accuracy (cl/cv svm)))))
(def svm-bounds [[-6 6] [-6 6]])
(m/exp (svm-params 1 1))
1.8s
Clojure
0.8798076923076923
(def svm-bo (o/bayesian-optimization svm-params 
                                     {:init-points 5
                                      :kernel (k/kernel :mattern-52 1.5)
                                      :utility-param 0.3
                                      :noise 0.05
                                      :bounds svm-bounds}))
(m/exp (:y (nth svm-bo 10)))
21.6s
Clojure
0.8990384615384615
(save (draw-2d-2 svm-bounds svm-bo 10) "/results/svm.jpg")
3.0s
Clojure

Runtimes (1)