@@ -153,14 +153,17 @@ begin, on the basis of the specific strategy and a user-specified
153
153
measures] ( https://alan-turing-institute.github.io/MLJ.jl/dev/performance_measures/ )
154
154
for details.
155
155
156
- - The * history* is a vector of tuples generated by the tuning
157
- algorithm - one tuple per iteration - used to determine the optimal
158
- model and which also records other user-inspectable statistics that
159
- may be of interest - for example, evaluations of a measure (loss or
160
- score) different from one being explicitly optimized. Each tuple is
161
- of the form ` (m, r) ` , where ` m ` is a model instance and ` r ` is
162
- information
163
- about ` m ` extracted from an evaluation.
156
+ - The * history* is a vector of tuples of the form ` (m, r) ` generated
157
+ by the tuning algorithm - one tuple per iteration - where ` m ` is a
158
+ model instance that has been evaluated, and ` r ` (called the
159
+ * result* ) contains three kinds of information: (i) whatever parts of
160
+ the evaluation needed to determine the optimal model; (ii)
161
+ additional user-inspectable statistics that may be of interest - for
162
+ example, evaluations of a measure (loss or score) different from one
163
+ being explicitly optimized; and (iii) any model "metadata" that a
164
+ tuning strategy implementation may need to be recorded for
165
+ generating the next batch of model candidates - for example an
166
+ implementation-specific representation of the model.
164
167
165
168
- A * tuning strategy* is an instance of some subtype `S <:
166
169
TuningStrategy` , the name ` S` (e.g., ` Grid`) indicating the tuning
@@ -179,8 +182,12 @@ begin, on the basis of the specific strategy and a user-specified
179
182
iteration count are given - and is essentially the space of models
180
183
to be searched. This definition is intentionally broad and the
181
184
interface places no restriction on the allowed types of this
182
- object. For the range objects supported by the ` Grid ` strategy, see
183
- [ below] ( #range-types ) .
185
+ object. It may be generally viewed as the "space" of models being
186
+ searched * plus* strategy-specific data explaining how models from
187
+ that space are actually to be generated (e.g.,
188
+ hyperparameter-specific grid resolutions or probability
189
+ distributions). For the range objects supported by the ` Grid `
190
+ strategy, see [ below] ( #range-types ) .
184
191
185
192
186
193
### Interface points for user input
@@ -242,7 +249,7 @@ Several functions are part of the tuning strategy API:
242
249
- ` tuning_report ` : for selecting what to report to the user apart from
243
250
details on the optimal model
244
251
245
- - ` default_n ` : to specify the number of models to be evaluated when
252
+ - ` default_n ` : to specify the total number of models to be evaluated when
246
253
` n ` is not specified by the user
247
254
248
255
** Important note on the history.** The initialization and update of the
@@ -316,19 +323,22 @@ which is recorded in its `field` attribute, but for composite models
316
323
this might be a be a "nested name", such as ` :(atom.max_depth) ` .
317
324
318
325
319
- #### The ` result ` method: For declaring what parts of an evaluation goes into the history
326
+ #### The ` result ` method: For building each entry of the history
320
327
321
328
``` julia
322
- MLJTuning. result (tuning:: MyTuningStrategy , history, e )
329
+ MLJTuning. result (tuning:: MyTuningStrategy , history, state, e, metadata )
323
330
```
324
331
325
- This method is for extracting from an evaluation ` e ` of some model ` m `
326
- the value of ` r ` to be recorded in the corresponding tuple ` (m, r) ` of
327
- the history. The value of ` r ` is also allowed to depend on previous
328
- events in the history. The fallback is:
332
+ This method is for constructing the result object ` r ` in each tuple
333
+ ` (m, r) ` written to the history. Here ` e ` is the evaluation of the
334
+ model ` m ` (as returned by a call to ` evaluation! ` ) and ` metadata ` is
335
+ any metadata associated with ` m ` when this is included in the output
336
+ of ` models! ` (see below), and ` nothing ` otherwise. The value of ` r ` is
337
+ also allowed to depend on previous events in the history. The fallback
338
+ is:
329
339
330
340
``` julia
331
- MLJTuning. result (tuning, history, e ) = (measure= e. measure, measurement= e. measurement)
341
+ MLJTuning. result (tuning, history, state, e, metadata ) = (measure= e. measure, measurement= e. measurement)
332
342
```
333
343
334
344
Note in this case that the result is always a named tuple of
@@ -350,18 +360,18 @@ state = setup(tuning::MyTuningStrategy, model, range, verbosity)
350
360
```
351
361
352
362
The ` setup ` function is for initializing the ` state ` of the tuning
353
- algorithm (needed, by the algorithm's ` models! ` method; see below ). Be
354
- sure to make this object mutable if it needs to be updated by the
355
- ` models! ` method. The ` state ` generally stores, at the least, the
356
- range or some processed version thereof. In momentum-based gradient
357
- descent, for example, the state would include the previous
358
- hyperparameter gradients, while in GP Bayesian optimization, it would
359
- store the (evolving) Gaussian processes .
360
-
361
- If a variable is to be reported as part of the user-inspectable
362
- history, then it should be written to the history instead of stored in
363
- state. An example of this might be the ` temperature ` in simulated
364
- annealing.
363
+ algorithm (available to the ` models! ` method). Be sure to make this
364
+ object mutable if it needs to be updated by the ` models! ` method.
365
+
366
+ The ` state ` is a place to record the outcomes of any necessary
367
+ intialization of the tuning algorithm (performed by ` setup ` ) and a
368
+ place for the ` models! ` method to save and read transient information
369
+ that does not need to be recorded in the history .
370
+
371
+ The ` setup ` function is called once only, when a ` TunedModel ` machine
372
+ is ` fit! ` the first time, and not on subsequent calls (unless
373
+ ` force=true ` ). (Specifically, ` MLJBase.fit(::TunedModel, ...) ` calls
374
+ ` setup ` but ` MLJBase.update(::TunedModel, ...) ` does not.)
365
375
366
376
The ` verbosity ` is an integer indicating the level of logging: ` 0 `
367
377
means logging should be restricted to warnings, ` -1 ` , means completely
@@ -411,17 +421,24 @@ selection of `n - length(history)` models from the grid, so that
411
421
non-deterministically (such as simulated annealing), ` models! ` might
412
422
return a single model, or return a small batch of models to make use
413
423
of parallelization (the method becoming "semi-sequential" in that
414
- case). In sequential methods that generate new models
415
- deterministically (such as those choosing models that optimize the
416
- expected improvement of a surrogate statistical model) ` models! ` would
417
- return a single model.
424
+ case).
425
+
426
+ ##### Including model metadata
427
+
428
+ If a tuning strategy implementation needs to pass additional
429
+ "metadata" along with each model, to be passed to ` result ` for
430
+ recording in the history, then instead of model instances, ` models! `
431
+ should returne a vector of * tuples* of the form ` (m, metadata) ` , where
432
+ ` m ` is a model instance, and ` metadata ` the associated data. See the
433
+ discussion above on ` result ` .
418
434
419
435
If the tuning algorithm exhausts it's supply of new models (because,
420
436
for example, there is only a finite supply) then ` models! ` should
421
- return an empty vector. Under the hood, there is no fixed "batch-size"
422
- parameter, and the tuning algorithm is happy to receive any number
423
- of models.
424
-
437
+ return an empty vector or ` nothing ` . Under the hood, there is no fixed
438
+ "batch-size" parameter, and the tuning algorithm is happy to receive
439
+ any number of models. If ` models! ` returns a number of models
440
+ exceeding the number needed to complete the history, the list returned
441
+ is simply truncated.
425
442
426
443
#### The ` best ` method: To define what constitutes the "optimal model"
427
444
@@ -483,52 +500,94 @@ MLJTuning.tuning_report(tuning, history, state) = (history=history,)
483
500
MLJTuning. default_n (tuning:: MyTuningStrategy , range)
484
501
```
485
502
486
- The ` methods! ` method (which is allowed to return multiple models) is
487
- called until a history of length ` n ` has been built, or ` models! `
488
- returns an empty list or ` nothing ` . If the user does not specify a
489
- value for ` n ` when constructing her ` TunedModel ` object, then ` n ` is
490
- set to ` default_n(tuning, range) ` at construction, where ` range ` is
491
- the user specified range.
503
+ The ` models! ` method (which is allowed to return multiple models) is
504
+ called until one of the following occurs:
505
+
506
+ - The length of the history matches the number of iterations specified
507
+ by the user, namely ` tuned_model.n ` where ` tuned_model ` is the user's
508
+ ` TunedModel ` instance. If ` tuned_model.n ` is ` nothing ` (because the
509
+ user has not specified a value) then ` default_n(tuning, range) ` is
510
+ used instead.
511
+
512
+ - ` models! ` returns an empty list or ` nothing ` .
492
513
493
514
The fallback is
494
515
495
516
``` julia
496
- MLJTuning . default_n (:: TuningStrategy , range) = 10
517
+ default_n (tuning :: TuningStrategy , range) = DEFAULT_N
497
518
```
498
519
520
+ where ` DEFAULT_N ` is a global constant. Do `using MLJTuning;
521
+ MLJTuning.DEFAULT_N` to see check the current value.
499
522
500
- ### Implementation example: Search through an explicit list
501
523
502
- The most rudimentary tuning strategy just evaluates every model in a
503
- specified list of models sharing a common type, such lists
504
- constituting the only kind of supported range. (In this special case
505
- ` range ` is an arbitrary iterator of models, which are ` Probabilistic `
506
- or ` Deterministic ` , according to the type of the prototype ` model ` ,
507
- which is otherwise ignored.) The fallback implementations for ` setup ` ,
508
- ` result ` , ` best ` and ` report_history ` suffice. In particular, there
509
- is not distinction between ` range ` and ` state ` in this case.
524
+ ### Implementation example: Search through an explicit list
510
525
511
- Here's the complete implementation:
526
+ The most rudimentary tuning strategy just evaluates every model
527
+ generated by some iterator, such iterators constituting the only kind
528
+ of supported range. The models generated must all have a common type
529
+ and, in th implementation below, the type information is conveyed by
530
+ the specified prototype ` model ` (which is otherwise ignored). The
531
+ fallback implementations for ` result ` , ` best ` and ` report_history `
532
+ suffice.
512
533
513
534
``` julia
514
535
515
- import MLJBase
516
-
517
536
mutable struct Explicit <: TuningStrategy end
518
537
538
+ mutable struct ExplicitState{R,N}
539
+ range:: R
540
+ next:: Union{Nothing,N} # to hold output of `iterate(range)`
541
+ end
542
+
543
+ ExplicitState (r:: R , :: Nothing ) where R = ExplicitState {R,Nothing} (r,nothing )
544
+ ExplictState (r:: R , n:: N ) where {R,N} = ExplicitState {R,Union{Nothing,N}} (r,n)
545
+
546
+ function MLJTuning. setup (tuning:: Explicit , model, range, verbosity)
547
+ next = iterate (range)
548
+ return ExplicitState (range, next)
549
+ end
550
+
519
551
# models! returns all available models in the range at once:
520
- MLJTuning. models! (tuning:: Explicit , model, history:: Nothing ,
521
- state, verbosity) = state
522
- MLJTuning. models! (tuning:: Explicit , model, history,
523
- state, verbosity) = state[length (history) + 1 : end ]
524
-
525
- function MLJTuning. default_n (tuning:: Explicit , range)
526
- try
527
- length (range)
528
- catch MethodError
529
- 10
530
- end
552
+ function MLJTuning. models! (tuning:: Explicit ,
553
+ model,
554
+ history,
555
+ state,
556
+ n_remaining,
557
+ verbosity)
558
+
559
+ range, next = state. range, state. next
560
+
561
+ next === nothing && return nothing
562
+
563
+ m, s = next
564
+ models = [m, ]
565
+
566
+ next = iterate (range, s)
567
+
568
+ i = 1 # current length of `models`
569
+ while i < n_remaining
570
+ next === nothing && break
571
+ m, s = next
572
+ push! (models, m)
573
+ i += 1
574
+ next = iterate (range, s)
575
+ end
576
+
577
+ state. next = next
578
+
579
+ return models
580
+
531
581
end
582
+
583
+ function default_n (tuning:: Explicit , range)
584
+ try
585
+ length (range)
586
+ catch MethodError
587
+ DEFAULT_N
588
+ end
589
+ end
590
+
532
591
```
533
592
534
593
For slightly less trivial example, see
0 commit comments