-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathREADME.Rmd
199 lines (137 loc) · 8.42 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
---
output:
md_document:
variant: gfm
bibliography: bibliography.bib
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-"
)
```
# optimizeR <img src="man/figures/logo.png" align="right" height="139" />
<!-- badges: start -->
[![CRAN status](https://www.r-pkg.org/badges/version/optimizeR)](https://CRAN.R-project.org/package=optimizeR)
[![metacran downloads](https://cranlogs.r-pkg.org/badges/last-month/optimizeR)](https://cran.r-project.org/package=optimizeR)
[![R-CMD-check](https://github.com/loelschlaeger/optimizeR/workflows/R-CMD-check/badge.svg)](https://github.com/loelschlaeger/optimizeR/actions)
[![Codecov test coverage](https://codecov.io/gh/loelschlaeger/optimizeR/branch/master/graph/badge.svg)](https://app.codecov.io/gh/loelschlaeger/optimizeR?branch=master)
<!-- badges: end -->
The `{optimizeR}` package
- provides an object-oriented framework for optimizer functions in R
- and offers some convenience for useRs when minimizing or maximizing.
❌ **You won't need the package if you...**
- already know which optimizer you want to use and if you are happy with its constraints (e.g., only minimization over the first function argument possible),
- want to compare optimizers that are already covered by [`{optimx}`](https://CRAN.R-project.org/package=optimx) [@optimx] (they provide a framework to compare about 30 optimizers),
- or search for new optimization algorithms (because this package does not implement any optimizer functions itself).
✅ **But you might find the package useful if you want to...**
- compare any optimizer function (also those not covered by `{optimx}` or other frameworks; see the [CRAN Task View: Optimization and Mathematical Programming](https://CRAN.R-project.org/view=Optimization) [@taskview] for an overview of R optimizers),
- have consistently named inputs and outputs across different optimizers (which is generally not the case),
- view optimizers as objects (which can be helpful when implementing packages that depend on optimization),
- use optimizers for both minimization and maximization,
- optimize over more than one function argument,
- measure computation time or set a time limit for long optimization tasks.
## How to use the package?
The following demo is a bit artificial but showcases the package purpose. Let's assume we want to
- maximize a function over two of its arguments,
- interrupt optimization if it exceeds 10 seconds,
- and compare the performance between the optimizers `stats::nlm` and `pracma::nelder_mead`.
We can easily do this task with `{optimizeR}`:
```{r, load optimizeR}
library("optimizeR")
```
**1. Define the objective function**
Let $f:\mathbb{R}^4\to\mathbb{R}$ with
```{r, ackley}
f <- function(a, b, x, y) {
a * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) + exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) - exp(1) - b
}
```
For `a = b = 20`, this is the inverted [Ackley function](https://en.wikipedia.org/wiki/Ackley_function) with a global maximum in `x = y = 0`:
```{r, plot-ackley, echo = FALSE, out.width = "50%", warning = FALSE, fig.align = "center"}
x <- y <- seq(-3, 3, length.out = 100)
grid <- expand.grid(x, y)
z <- mapply(f, x = grid[, 1], y = grid[, 2], a = 20, b = 20)
data <- data.frame(x = grid[, 1], y = grid[, 2], z = z)
library("ggplot2")
ggplot(data = data, aes(x, y, z = z)) +
geom_contour_filled() +
theme_minimal()
```
We want to keep `a` and `b` fixed here and optimize over `x` and `y` (which are also both single numeric values).
Two problems would occur if we would optimize `f` with say `stats::nlm` directly:
1. there are two target arguments (`x` and `y`) and
2. the position of the target argument is not in the first place.
Both artifacts are not allowed by `stats::nlm` and most of other available optimizers, but supported by `{optimizeR}`. We just have to define an objective object which we later can pass to the optimizers:
```{r, define objective}
objective <- Objective$new(
f = f, # f is our objective function
target = c("x", "y"), # x and y are the target arguments
npar = c(1, 1), # the target arguments have both a length of 1
"a" = 20,
"b" = 20 # a and b have fixed values
)
```
**2. Create the optimizer objects**
Now that we have defined the objective function, let's define our optimizer objects. For `stats::nlm`, this is a one-liner:
```{r, define nlm}
nlm <- Optimizer$new(which = "stats::nlm")
```
The `{optimizeR}` package provides a dictionary of optimizers, that can be directly selected via the `which` argument. For an overview of available optimizers, see:
```{r, optimizer dictionary}
optimizer_dictionary
```
But in fact any optimizer that is not contained in the dictionary can be put into the `{optimizeR}` framework by setting `which = "custom"` first...
```{r, define nelder mead 1}
nelder_mead <- Optimizer$new(which = "custom")
```
... and using the `$definition()` method next:
```{r, define nelder mead 2}
nelder_mead$definition(
algorithm = pracma::nelder_mead, # the optimization function
arg_objective = "fn", # the argument name for the objective function
arg_initial = "x0", # the argument name for the initial values
out_value = "fmin", # the element for the optimal function value in the output
out_parameter = "xmin", # the element for the optimal parameters in the output
direction = "min" # the optimizer minimizes
)
```
**3. Set a time limit**
Each optimizer object has a field called `$seconds` which equals `Inf` by default. You can optionally set a different, single numeric value here to set a time limit in seconds for the optimization:
```{r, set time limit}
nlm$seconds <- 10
nelder_mead$seconds <- 10
```
Note that not everything (especially compiled C code) can technically be timed out, see the help site `help("withTimeout", package = "R.utils")` for more details.
**4. Maximize the objective function**
Each optimizer object has the two methods `$maximize()` and `$minimize()` for function maximization or minimization, respectively. Both methods require values for the two arguments
1. `objective` (either an objective object as defined above or just a function) and
2. `initial` (an initial parameter vector from where the optimizer should start)
and optionally accepts additional arguments to be passed to the optimizer or the objective function.
```{r, maximize with nlm}
nlm$maximize(objective = objective, initial = c(3, 3))
```
```{r, maximize with nelder mead}
nelder_mead$maximize(objective = objective, initial = c(3, 3))
```
Note that
- the inputs for the objective function and initial parameter values are named consistently across optimizers,
- the output values for the optimal parameter vector and the maximimum function value are also named consistently across optimizers,
- the output contains the initial parameter values and the optimization time in seconds and additionally other optimizer-specific elements,
- `pracma::nelder_mead` outperforms `stats::nlm` here both in terms of optimization time and convergence to the global maximum.
## How to get the access?
You can install the released package version from [CRAN](https://CRAN.R-project.org) with:
```{r, install released version, eval = FALSE}
install.packages("optimizeR")
```
Then load the package via `library("optimizeR")` and you should be ready to go.
## Roadmap
The following steps to further improve the package are currently on our agenda:
- [ ] The package already provides a dictionary that stores optimizers together with information about names of their inputs and outputs (see the `optimizer_dictionary` object). We want to extend this dictionary with more optimizers that are commonly used.
- [ ] We want to use alias for optimizers in the dictionary that group optimizers into classes (such as "unconstrained optimization", "constrained Optimization", "direct search", "Newton-type" etc.). This would help to find alternative optimizers for a given task.
- [ ] We want to implement a `$summary()` method for an optimizer object that gives an overview of the optimizer, its arguments, and its properties.
## Getting in touch
You have a question, found a bug, request a feature, want to give feedback, or like to contribute? It would be great to hear from you, [please file an issue on GitHub](https://github.com/loelschlaeger/optimizeR/issues/new/choose). 😊
## References