Browse code

Merge branch 'feature/skopt' into 'master'

Feature/skopt

See merge request qt/adaptive!64

Joseph Weston authored on 20/06/2018 14:50:23
Showing 4 changed files
... ...
@@ -8,6 +8,12 @@ from . import utils
8 8
 
9 9
 from .learner import (Learner1D, Learner2D, AverageLearner,
10 10
                       BalancingLearner, DataSaver, IntegratorLearner)
11
+try:
12
+    # Only available if 'scikit-optimize' is installed
13
+    from .learner import SKOptLearner
14
+except ImportError:
15
+    pass
16
+
11 17
 from .runner import Runner, BlockingRunner
12 18
 from . import version
13 19
 
... ...
@@ -6,3 +6,9 @@ from .learner1D import Learner1D
6 6
 from .learner2D import Learner2D
7 7
 from .integrator_learner import IntegratorLearner
8 8
 from .data_saver import DataSaver
9
+
10
+try:
11
+    # Only available if 'scikit-optimize' is installed
12
+    from .skopt_learner import SKOptLearner
13
+except ImportError:
14
+    pass
9 15
new file mode 100644
... ...
@@ -0,0 +1,99 @@
1
+# -*- coding: utf-8 -*-
2
+from copy import deepcopy
3
+import heapq
4
+import itertools
5
+import math
6
+
7
+import numpy as np
8
+import sortedcontainers
9
+
10
+from ..notebook_integration import ensure_holoviews
11
+from .base_learner import BaseLearner
12
+
13
+from skopt import Optimizer
14
+
15
+
16
+class SKOptLearner(Optimizer, BaseLearner):
17
+    """Learn a function minimum using 'skopt.Optimizer'.
18
+
19
+    This is an 'Optimizer' from 'scikit-optimize',
20
+    with the necessary methods added to make it conform
21
+    to the 'adaptive' learner interface.
22
+
23
+    Parameters
24
+    ----------
25
+    function : callable
26
+        The function to learn.
27
+    **kwargs :
28
+        Arguments to pass to 'skopt.Optimizer'.
29
+    """
30
+
31
+    def __init__(self, function, **kwargs):
32
+        self.function = function
33
+        super().__init__(**kwargs)
34
+
35
+    def _tell(self, x, y, fit=True):
36
+        if y is not None:
37
+            # 'skopt.Optimizer' takes care of points we
38
+            # have not got results for.
39
+            super()._tell([x], y, fit)
40
+
41
+    def remove_unfinished(self):
42
+        pass
43
+
44
+    def loss(self, real=True):
45
+        if not self.models:
46
+            return np.inf
47
+        else:
48
+            model = self.models[-1]
49
+            # Return the in-sample error (i.e. test the model
50
+            # with the training data). This is not the best
51
+            # estimator of loss, but it is the cheapest.
52
+            return 1 - model.score(self.Xi, self.yi)
53
+
54
+    def ask(self, n, add_data=True):
55
+        points = super().ask(n)
56
+        # TODO: Choose a better estimate for the loss improvement.
57
+        if self.space.n_dims > 1:
58
+            return points, [self.loss() / n] * n
59
+        else:
60
+            return [p[0] for p in points], [self.loss() / n] * n
61
+
62
+    @property
63
+    def npoints(self):
64
+        return len(self.Xi)
65
+
66
+    def plot(self, nsamples=200):
67
+        hv = ensure_holoviews()
68
+        if self.space.n_dims > 1:
69
+            raise ValueError('Can only plot 1D functions')
70
+        bounds = self.space.bounds[0]
71
+        if not self.Xi:
72
+            p = hv.Scatter([]) * hv.Curve([]) * hv.Area([])
73
+        else:
74
+            scatter = hv.Scatter(([p[0] for p in self.Xi], self.yi))
75
+            if self.models:
76
+                model = self.models[-1]
77
+                xs = np.linspace(*bounds, nsamples)
78
+                xsp = self.space.transform(xs.reshape(-1, 1).tolist())
79
+                y_pred, sigma = model.predict(xsp, return_std=True)
80
+                # Plot model prediction for function
81
+                curve = hv.Curve(
82
+                    (xs, y_pred)
83
+                ).opts(style=dict(line_dash='dashed'))
84
+                # Plot 95% confidence interval as colored area around points
85
+                area = hv.Area(
86
+                    (xs, y_pred - 1.96 * sigma, y_pred + 1.96 * sigma),
87
+                    vdims=['y', 'y2'],
88
+                ).opts(style=dict(alpha=0.5, line_alpha=0))
89
+
90
+            else:
91
+                area = hv.Area([])
92
+                curve = hv.Curve([])
93
+            p = scatter * curve * area
94
+
95
+        # Plot with 5% empty margins such that the boundary points are visible
96
+        margin = 0.05 * (bounds[1] - bounds[0])
97
+        plot_bounds = (bounds[0] - margin, bounds[1] + margin)
98
+
99
+        return p.redim(x=dict(range=plot_bounds))
... ...
@@ -759,6 +759,65 @@
759 759
     "learner.extra_data"
760 760
    ]
761 761
   },
762
+  {
763
+   "cell_type": "markdown",
764
+   "metadata": {},
765
+   "source": [
766
+    "# `Scikit-Optimize`"
767
+   ]
768
+  },
769
+  {
770
+   "cell_type": "markdown",
771
+   "metadata": {},
772
+   "source": [
773
+    "We have wrapped the `Optimizer` class from [`scikit-optimize`](https://github.com/scikit-optimize/scikit-optimize), to show how existing libraries can be integrated with `adaptive`.\n",
774
+    "\n",
775
+    "The `SKOptLearner` attempts to \"optimize\" the given function `g` (i.e. find the global minimum of `g` in the window of interest).\n",
776
+    "\n",
777
+    "Here we use the same example as in the `scikit-optimize` [tutorial](https://github.com/scikit-optimize/scikit-optimize/blob/master/examples/ask-and-tell.ipynb). Although `SKOptLearner` can optimize functions of arbitrary dimensionality, we can only plot the learner if a 1D function is being learned."
778
+   ]
779
+  },
780
+  {
781
+   "cell_type": "code",
782
+   "execution_count": null,
783
+   "metadata": {
784
+    "collapsed": true
785
+   },
786
+   "outputs": [],
787
+   "source": [
788
+    "def g(x, noise_level=0.1):\n",
789
+    "    return (np.sin(5 * x) * (1 - np.tanh(x ** 2))\n",
790
+    "            + np.random.randn() * noise_level)"
791
+   ]
792
+  },
793
+  {
794
+   "cell_type": "code",
795
+   "execution_count": null,
796
+   "metadata": {},
797
+   "outputs": [],
798
+   "source": [
799
+    "learner = adaptive.SKOptLearner(g, dimensions=[(-2., 2.)],\n",
800
+    "                                base_estimator=\"GP\",\n",
801
+    "                                acq_func=\"gp_hedge\",\n",
802
+    "                                acq_optimizer=\"lbfgs\",\n",
803
+    "                               )\n",
804
+    "runner = adaptive.Runner(learner, ntasks=1, goal=lambda l: l.npoints > 40)\n",
805
+    "runner.live_info()"
806
+   ]
807
+  },
808
+  {
809
+   "cell_type": "code",
810
+   "execution_count": null,
811
+   "metadata": {},
812
+   "outputs": [],
813
+   "source": [
814
+    "%%opts Overlay [legend_position='top']\n",
815
+    "xs = np.linspace(*learner.space.bounds[0])\n",
816
+    "to_learn = hv.Curve((xs, [g(x, 0) for x in xs]), label='to learn')\n",
817
+    "\n",
818
+    "runner.live_plot().relabel('prediction', depth=2) * to_learn"
819
+   ]
820
+  },
762 821
   {
763 822
    "cell_type": "markdown",
764 823
    "metadata": {