Giter VIP home page Giter VIP logo

mljtuning.jl's Introduction

MLJTuning

Hyperparameter optimization for MLJ machine learning models.

See Tuning Models · MLJ for usage examples.

Build Status codecov.io

Contents

Note: This component of the MLJ stack applies to MLJ versions 0.8.0 and higher. Prior to 0.8.0, tuning algorithms resided in MLJ.

Who is this repo for?

This repository is not intended to be directly imported by the general MLJ user. Rather, MLJTuning is a dependency of the MLJ machine learning platform, which allows MLJ users to perform a variety of hyperparameter optimization tasks from there.

MLJTuning is the place for developers to integrate hyperparameter optimization algorithms (here called tuning strategies) into MLJ, either by adding code to /src/strategies, or by importing MLJTuning into a third-party package and implementing MLJTuning's tuning strategy interface.

MLJTuning is a component of the MLJ stack which does not have MLJModels as a dependency (no ability to search and load registered MLJ models). It does however depend on MLJBase and, in particular, on the resampling functionality currently residing there.

What is provided here?

This repository contains:

  • a tuning wrapper called TunedModel for transforming arbitrary MLJ models into "self-tuning" ones - that is, into models which, when fit, automatically optimize a specified subset of the original hyperparameters (using cross-validation or other resampling strategy) before training the optimal model on all supplied data

  • an abstract tuning strategy interface to allow developers to conveniently implement common hyperparameter optimization strategies, such as:

    • search models generated by an arbitrary iterator, eg models = [model1, model2, ...] (built-in Explicit strategy)

    • grid search (built-in Grid strategy)

    • Latin hypercubes (built-in LatinHypercube strategy, interfacing the LatinHypercubeSampling package)

    • random search (built-in RandomSearch strategy)

    • particle swarm optimization (MLJParticleOptimization.jl)

    • bandit

    • simulated annealing

    • Bayesian optimization using Gaussian processes

    • structured tree Parzen estimators (MLJTreeParzenTuning from TreeParzen.jl)

    • multi-objective (Pareto) optimization

    • genetic algorithms

    • AD-powered gradient descent methods

  • a selection of implementations of the tuning strategy interface, currently all those accessible from MLJ itself.

  • the code defining the MLJ functions learning_curves! and learning_curve as these are essentially one-dimensional grid searches

How do I implement a new tuning strategy?

This document assumes familiarity with the Evaluating Model Performance and Performance Measures sections of the MLJ manual. Tuning itself, from the user's perspective, is described in Tuning Models.

Overview

What follows is an overview of tuning in MLJ. After the overview is an elaboration on those terms given in italics.

All tuning in MLJ is conceptualized as an iterative procedure, each iteration corresponding to a performance evaluation of a single model. Each such model is a mutated clone of a fixed prototype. In the general case, this prototype is a composite model, i.e., a model with other models as hyperparameters, and while the type of the prototype mutations is fixed, the types of the sub-models are allowed to vary.

When all iterations of the algorithm are complete, the optimal model is selected by applying a selection heuristic to a history generated according to the specified tuning strategy. Iterations are generally performed in batches, which are evaluated in parallel (sequential tuning strategies degenerating into semi-sequential strategies, unless the batch size is one). At the beginning of each batch, both the history and an internal state object are consulted, and, on the basis of the tuning strategy, a new batch of models to be evaluated is generated. On the basis of these evaluations, and the strategy, the history and internal state are updated.

The tuning algorithm initializes the state object before iterations begin, on the basis of the specific strategy and a user-specified range object.

  • Recall that in MLJ a model is an object storing the hyperparameters of some learning algorithm indicated by the name of the model type (e.g., DecisionTreeRegressor). Models do not store learned parameters.

  • An evaluation is an object E returned by some call to the evaluate! method, when passed the resampling strategy (e.g., resampling=CV(nfolds=9)) and a battery of user-specified performance measures (e.g., measures=[cross_entropy, accuracy]). An evaluation object E contains a list of measures E.measure and a list of corresponding measurements E.measurement, each of which is the aggregrate of measurements for each resampling of the data, which are stored in E.per_fold (a vector of vectors). In the case of a measure that reports a value for each individual observation (to obtain the per-fold measurement, by aggregation) the per-observation values can be retrieved from E.per_observation. This last object includes missing entries for measures that do not report per-observation values (reports_per_observation(measure) = false) such as auc. See Evaluating Model Performance for details. There is a trait for measures called orientation which is :loss for measures you ordinarily want to minimize, and :score for those you want to maximize. See Performance measures for further details.

  • A tuning strategy is an instance of some subtype S <: TuningStrategy, the name S (e.g., Grid) indicating the tuning (optimization) algorithm to be applied. The fields of the tuning strategy - called tuning hyperparameters - are those tuning parameters specific to the strategy that do not refer to specific models or specific model hyperparameters. So, for example, a default resolution to be used in a grid search is a hyperparameter of Grid, but the resolution to be applied to a specific hyperparameter (such as the maximum depth of a decision tree) is not. This latter parameter would be part of the user-specified range object. A multi-objective tuning strategy is one that consults the measurements of all measures specified by the user; otherwise only the first measure is consulted, although measurements for all measures are nevertheless reported.

  • A selection heuristic is a rule describing how the outcomes of the model evaluations will be used to select the best (optimal) model, after all iterations of the optimizer have concluded. For example, the default NaiveSelection() heuristic simply selects the model whose evaluation E has the smallest or largest E.measurement[1] value, according to whether the metric E.measure[1] is a :loss or :score. Most heuristics are generic in the sense they will apply no matter what tuning strategy is applied. A selection heuristic supported by a multi-objective tuning strategy must select some "best" model (e.g., a random Pareto optimal solution).

  • The history is a vector of identically-keyed named tuples, one tuple per iteration. The tuple keys include:

    • model: for the MLJ model instance that has been evaluated

    • measure, measurement, per_fold: for storing the values of E.measure, E.measurement and E.per_fold, where E is the corresponding evaluation object.

    • metadata: for any tuning strategy-specific information required to be recorded in the history but not intended to be reported to the user (for example an implementation-specific representation of model).

    There may be additional keys for tuning-specific information that is to be reported to the user (such as temperature in simulated annhealing).

  • A range is any object whose specification completes the specification of the tuning task, after the prototype, tuning strategy, resampling strategy, performance measure(s), selection heuristic, and total iteration count are given. This definition is intentionally broad and the interface places no restriction on the allowed types of this object, although all strategies should support the one-dimensional range objects defined in MLJBase (see below). Generally, a range may be understood as the "space" of models being searched plus strategy-specific data explaining how models from that space are actually to be generated (e.g., grid resolutions or probability distributions specific to particular hyper-parameters). For more on range types see Range types below.

Interface points for user input

Recall, for context, that in MLJ tuning is implemented as a model wrapper. A model is tuned by fitting the wrapped model to data (which also trains the optimal model on all available data). This process determines the optimal model, as defined by the selection heuristic (see above). To use the optimal model one predicts using the wrapped model. For more detail, see the Tuning Models section of the MLJ manual.

In setting up a tuning task, the user constructs an instance of the TunedModel wrapper type, which has these principal fields:

  • model: the prototype model instance mutated during tuning (the model being wrapped)

  • tuning: the tuning strategy, an instance of a concrete TuningStrategy subtype, such as Grid

  • resampling: the resampling strategy used for performance evaluations, which must be an instance of a concrete ResamplingStrategy subtype, such as Holdout or CV

  • measure: a measure (loss or score) or vector of measures available to the tuning algorithm, the first of which is optimized in the common case of single-objective tuning strategies

  • selection_heuristic: some instance of SelectionHeuristic, such as NaiveSelection() (default)

  • range: as defined above - roughly, the space of models to be searched

  • n: the number of iterations, which is the number of distinct model evaluations that will be added to the history, unless the tuning strategy's supply of models is exhausted (e.g., Grid). This is not to be confused with an iteration count specific to the tuning strategy (e.g., Particle Swarm Optimization).

  • acceleration: the computational resources to be applied (e.g., CPUProcesses() for distributed computing and CPUThreads() for multi-threaded processing)

  • acceleration_resampling: the computational resources to be applied at the level of resampling (e.g., in cross-validation)

Implementation requirements for new tuning strategies

As sample implementations, see /src/strategies/

Summary of functions

Several functions are part of the tuning strategy API:

  • clean!: for validating and resetting invalid fields in tuning strategy (optional)

  • setup: for initialization of state (compulsory)

  • extras: for declaring and formatting additional user-inspectable information going into the history

  • tuning_report: for declaring any other strategy-specific information to report to the user (optional)

  • models: for generating batches of new models and updating the state (compulsory)

  • default_n: to specify the total number of models to be evaluated when n is not specified by the user

  • supports_heuristic: a trait used to encode which selection heuristics are supported by the tuning strategy (only needed if you define a strategy-specific heuristic)

Important note on the history. The initialization and update of the history is carried out internally, i.e., is not the responsibility of the tuning strategy implementation. The history is always initialized to nothing, rather than an empty vector.

The above functions are discussed further below, after discussing types.

The tuning strategy type

Each tuning algorithm must define a subtype of TuningStrategy whose fields are the hyperparameters controlling the strategy that do not directly refer to models or model hyperparameters. These would include, for example, the default resolution of a grid search, or the initial temperature in simulated annealing.

The algorithm implementation must include a keyword constructor with defaults. Here's an example:

mutable struct Grid <: TuningStrategy
	goal::Union{Nothing,Int}
	resolution::Int
	shuffle::Bool
	rng::Random.AbstractRNG
end

# Constructor with keywords
Grid(; goal=nothing, resolution=10, shuffle=true,
	 rng=Random.GLOBAL_RNG) =
	Grid(goal, resolution, MLJBase.shuffle_and_rng(shuffle, rng)...)

Range types

Generally new types are defined for each class of range object a tuning strategy should like to handle, and the tuning strategy functions to be implemented are dispatched on these types. It is recommended that every tuning strategy support at least these types:

  • one-dimensional ranges r, where r is a MLJBase.ParamRange instance

  • (optional) pairs of the form (r, data), where data is extra hyper-parameter-specific information, such as a resolution in a grid search, or a distribution in a random search

  • abstract vectors whose elements are of the above form

Recall that ParamRange has two concrete subtypes NumericRange and NominalRange, whose instances are constructed with the MLJBase extension to the range function.

Note in particular that a NominalRange has a values field, while NumericRange has the fields upper, lower, scale, unit and origin. The unit field specifies a preferred length scale, while origin a preferred "central value". These default to (upper - lower)/2 and (upper + lower)/2, respectively, in the bounded case (neither upper = Inf nor lower = -Inf). The fields origin and unit are used in generating grids or fitting probability distributions to unbounded ranges.

A ParamRange object is always associated with the name of a hyperparameter (a field of the prototype in the context of tuning) which is recorded in its field attribute, a Symbol, but for composite models this might be a be an Expr, such as :(atom.max_depth).

Use the iterator and sampler methods to convert ranges into one-dimensional grids or for random sampling, respectively. See the tuning section of the MLJ manual or doc-strings for more on these methods and the Grid and RandomSearch implementations.

The clean! method: For validating tuning strategy

MLJTuning.clean!(tuning::MyTuningStrategy)

Some tuning strategies have mutable fields that only support specific set of values: a particle swarm strategy, for instance, should have at least three agents for the algorithm to work. As such, it is recommended to implement the clean! method to warn the user and correct invalid tuning hyperparameters. The method should return a string message if some fields have been reset or an empty string otherwise, and will be called internally whenever a TunedModel machine is fit!.

The default fallback for clean! returns an empty string.

The setup method: To initialize state

state = setup(tuning::MyTuningStrategy, model, range, n, verbosity)

The setup function is for initializing the state of the tuning algorithm (available to the models method). Be sure to make this object mutable if it needs to be updated by the models method. The arguments model and n are what the user has specified in their TunedModel instance; recall model is the prototype to be cloned and mutated, and n the total number of mutations to be generated.

The state is a place to record the outcomes of any necessary intialization of the tuning algorithm (performed by setup) and a place for the models method to save and read transient information that does not need to be recorded in the history.

The setup function is called once only, when a TunedModel machine is fit! the first time, and not on subsequent calls (unless force=true). (Specifically, MLJBase.fit(::TunedModel, ...) calls setup but MLJBase.update(::TunedModel, ...) does not.)

The verbosity is an integer indicating the level of logging: 0 means logging should be restricted to warnings, -1, means completely silent.

The fallback for setup is:

setup(tuning::TuningStrategy, model, range, n, verbosity) = range

However, a tuning strategy will generally want to implement a setup method for each range type it is going to support:

MLJTuning.setup(tuning::MyTuningStrategy, model, range::RangeType1, n, verbosity) = ...
MLJTuning.setup(tuning::MyTuningStrategy, model, range::RangeType2, n, verbosity) = ...
etc.

The extras method: For adding user-inspectable data to the history

MLJTuning.extras(tuning::MyTuningStrategy, history, state, E) -> named_tuple

This method should return any user-inspectable information to be included in a new history entry, that is in addition to the model, measures, measurement and per_fold data. This method must return a named tuple, human readable if possible. Each key of the returned named tuple becomes a key of the new history entry.

Here E is the full evalutation object for model and history the current history (before adding the new entry).

The fallback for extras returns an empty named tuple.

The models method: For generating model batches to evaluate

MLJTuning.models(tuning::MyTuningStrategy, model, history, state, n_remaining, verbosity)
	-> vector_of_models, new_state

This is the core method of a new implementation. Given the existing history and state, it must return a vector ("batch") of new model instances vector_of_models to be evaluated, and the updated state. Any number of models may be returned (and this includes an empty vector or nothing) and the evaluations will be performed in parallel (using the mode of parallelization defined by the acceleration field of the TunedModel instance).

Important note. Parallelization means the order in which the history gets extended after models(...) returns its list of new candidates is generally not the same order in which the candidates are returned. Some implementations may therefore need to attach extra "labeling" metadata to each model, as explained below, so that the existing history can be suitably interpreted.

If more models are returned than needed (because including them would create a history whose length exceeds the user-specified number of iterations tuned_model.n) then the surplus models are saved, for use in a "warm restart" of tuning, when the user increases tuned_model.n. The remaining models are then evaluated and these evaluations are added to the history. In any warm restart, no new call to models will be made until all saved models have been evaluated, and these evaluations added to the history.

If the tuning algorithm exhausts it's supply of new models (because, for example, there is only a finite supply, as in a Grid search) then vector_of_models should be an empty vector or nothing. The interface has no fixed "batch-size" parameter, and the tuning algorithm is happy to receive any number of models; a surplus is handled as explained above, a shortfall will trigger an early stop (so that the final history has length less than tuned_model.n).

If needed, extra metadata may be attached to each model returned; see below.

Sequential tuning strategies generating models non-deterministically (e.g., simulated annealing) might choose to include a batch size hyperparameter, and arrange that models returns batches of the specified size (to be evaluated in parallel when acceleration is set appropriately). However, the evaluations and history updates do not occur until after the models call, so it may be complicated or impossible to preserve the original (strictly) sequential algorithm in that case, which should be clearly documented.

Some simple tuning strategies, such as RandomSearch, will want to return as many models as possible in one hit. To this end, the variable n_remaining is passed to the models call; this is the difference between the current length of the history and tuned_model.n.

Including model metadata

If a tuning strategy implementation needs to record additional metadata in the history, for each model generated, then instead of model instances, vector_of_models should be vector of tuples of the form (m, metadata), where m is a model instance, and metadata the associated data. To access the metadata for the jth element of the existing history, use history[j].metadata.

The tuning_report method: To add to the user-inspectable report

As with any model, fitting a TunedModel instance generates a user-accessible report. Note that the fallback report already includes additions to the history created by the extras method mentioned above. To add more strategy-specific information to the report, one overloads tuning_report.

Specically, the report generated by fitting a TunedModel is constructed with this code:

report1 = (best_model         = best_model,
		   best_history_entry = best_user_history_entry,
		   history            = user_history,
		   best_report        = best_report)

report = merge(report1, tuning_report(tuning, history, state))

where:

  • best_model is the best model instance (as selected according to the user-specified selection heuristic).

  • best_user_history is the corresponding entry in the history with metadata removed.

  • best_report is the report generated when fitting the best_model on all available data.

  • user_history is the full history with metadata entries removed.

  • tuning_report(::MyTuningStrategy, ...) is a method the implementer may overload that ***must return a named tuple, preferably human readable

The fallback for tuning_report returns an empty named-tuple.

The default_n method: For declaring the default number of iterations

MLJTuning.default_n(tuning::MyTuningStrategy, range)

The models method, which is allowed to return multiple models in it's first return value vector_of_models, is called until one of the following occurs:

  • The length of the history matches the number of iterations specified by the user, namely tuned_model.n where tuned_model is the user's TunedModel instance. If tuned_model.n is nothing (because the user has not specified a value) then default_n(tuning, range) is used instead.

  • vector_of_models is empty or nothing.

The fallback is

default_n(tuning::TuningStrategy, range) = DEFAULT_N

where DEFAULT_N is a global constant. Do using MLJTuning; MLJTuning.DEFAULT_N to see check the current value.

The supports_heuristic trait

If you define a selection heuristic SpecialHeuristic (see below) and that heuristic is specific to a tuning strategy TuningStrategy then you must define

MLJTuning.supports_heuristic(::TuningStrategy, ::SpecialHeuristic) = true

Sample implementations

A number of built-in tuning strategy implementations of the MLJTuning API can be found at /src/strategies.

The simplest Explicit strategy is the simplest but is also an odd case as the range is just an iterator of MLJ models. These models need not share a common type and model is never cloned.

For slightly less trivial example, see the Grid search code

How do I implement a new selection heuristic?

Recall that a selection heuristic is a rule which decides on the "best model" given the model evaluations in the tuning history. New heuristics are introduced by defining a new struct SomeHeuristic subtyping SelectionHeuristic and implementing a method

MLJTuning.best(heuristic::SomeHeuristic, history) -> history_entry

where history_entry is the entry in the history corresponding to the model deemed "best".

Below is a simplified version of code defining the default heuristic NaiveSelection() which simply chooses the model with the lowest (or highest) aggregated performance estimate, based on the first measure specified by the user in his TunedModel construction (she may specify more than one).

struct NaiveSelection <: MLJTuning.SelectionHeuristic end

function best(heuristic::NaiveSelection, history)
	measurements = [h.measurement[1] for h in history]
	measure = first(history).measure[1]
	if orientation(measure) == :score
		measurements = -measurements
	end
	best_index = argmin(measurements)
	return history[best_index]
end

Because this selection heuristic is generic (applies to all tuning strategies) we additionally define

MLJTuning.supports_heuristic(strategy, heuristic::NaiveSelection) = true

For strategy-specific selection heuristics, see above on how to set this trait.

mljtuning.jl's People

Contributors

ablaom avatar beastyblacksmith avatar davnn avatar dilumaluthge avatar github-actions[bot] avatar juliatagbot avatar lhnguyen-vn avatar ludoro avatar okonsamuel avatar olivierlabayle avatar pebeto avatar pitmonticone avatar rikhuijzer avatar tlienart avatar vollmersj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mljtuning.jl's Issues

combination of acceleration and acceleration_resampling

@ablaom Ok. I have had a look at the issue #15 and #14. Here are some of my concerns concerning the combination of acceleration and acceleration_resampling parameters.
All combinations of those parameters seemed fine to me except.

  1. acceleration = CPUProcesses() and acceleration_resampling = CPUProcesses(). In the same worker cores used in accelerating the tuning process is also the same used in accelerating the resampling process. This may result in slowdown. Depending on the case using either (acceleration = CPU1() and acceleration_resampling = CPUProcesses()) or ( acceleration = CPUProcesses() and acceleration_resampling = CPU1()) would be better.
  2. acceleration = CPUThreads() and acceleration_resampling = CPUProcesses(). (Same issue as in 1)
    My suggestion would be to not allow uses use the above two case.
    Any Objections?

Incrementing `n` by 1 is adding 2 models to history

dtc = DecisionTreeClassifier()
r   = range(dtc, :max_depth, lower=1, upper=50);

tmodel = TunedModel(model=dtc, ranges=[r, ],
                tuning=Grid(resolution=50),
                measure=cross_entropy,
                n=48);
mach = machine(tmodel, (@load_iris)...)

julia> fit!(mach, verbosity=2);
[ Info: Updating Machine{ProbabilisticTunedModel{Grid,},} @969.
[ Info: Attempting to add 1 models to search, bringing total to 49. 
measurement: 9.611640903764574
measurement: 9.611640903764574
[ Info: Training Machine{DecisionTreeClassifier,} @695.

@assert length(report(mach).history) == 48

tmodel.n = 49
fit!(mach, verbosity=2);

julia> length(report(mach).history)
50

Wierd thing is that if I use intial n=10 and increment to n=11, the issue is absent.

replace @distributed with pmap

Currently in MLJ acceleration with CPUThreads is implemented using @distributed. This effectively splits up the given range (1:nfolds or 1:nmetamodels) into equal chunks and sends them off to all workers loaded with addprocs. This is great if the each chunk runs in the same amount of time otherwise some overhead is experienced. Also the user lacks the ability to specify the actual workers to be used in computing. (This might not be a big deal)
pmap implementation allows user more control (if they wish) in how these tasks are sent to to these workers.(this is due to batch_size and AbstractWorkerPool options it exposes).
Previously the main reason for not adopting pmap was because nested pmap hangs see JuliaLang/Distributed.jl#62 (There is a workaround this stated there).
The only limitation left in adopting this is that calling pmap from within Threads.@spawn some times hangs.( Although i don't think it is practical to call pmap from threads. What is more common is calling threads from processes) see JuliaLang/Distributed.jl#69

Skipping parts of search space?

Hi,

There are some parts of search space where my model will fail to return a usable result, and this triggers a BoundError at the prediction step. The parts of search space where this will occur is not clearly defined (and is probabilistic), so clean! is not applicable. I am wondering how I may wrap the tuning with a try-catch so that certain errors are simply returned as an infinite loss, rather than breaking the entire tuning and exiting?

Thanks!
Miles

Machines wrapping `TunedModel` instances should never cache data

Since TunedModel is just a wrapper, caching data might create an unnecessary copy.

Here tmodel::TunedModel wraps an EvoTreeClassifier object and X is a DataFrame.

mach = machine(tmodel, X, y) |> fit!
julia> mach.data # "outer" unecessary cached data
(3×2 DataFrame
 Row │ a        b          
     │ Float64  Float64    
─────┼─────────────────────
   11.0  0.136725
   22.0  0.00546956
   33.0  0.947711, CategoricalArrays.CategoricalValue{Char, UInt32}['a', 'a', 'a'])

julia> mach.cache[end].fitresult.machine.data # atomic model specific cached data
((matrix = [1.0 0.13672511011651545; 2.0 0.005469560151032837; 3.0 0.9477113320687569], names = [:a, :b]), CategoricalArrays.CategoricalValue{Char, UInt32}['a', 'a', 'a'])

The matrix is Tables.matrix(X) and so is a copy, not a view.

Remedy Declare MLJBase.caches_data_by_default(::Type{<:TunedModel}) = false.

Tuned Model interface doesnt have class_weights

The TunedModel interface supports parameter - weights but not class_weights.
For classification problems with highly imbalanced classes, we need tuned model measures working with class weights.

Intermittent failure of CI for Julia 1.3

Sometimes CI fails for Julia 1.3 (never 1.0). Maybe this is related to #48 and will go away when that is resolved (unsafe multithreading). For the record I'm recording the problem here:

ERROR: LoadError: On worker 2:
392LoadError: LoadError: EOFError: read end of file
393read at ./iostream.jl:361
394parse_cache_header at ./loading.jl:1334
395stale_cachefile at ./loading.jl:1413
396_require_search_from_serialized at ./loading.jl:752
397_require at ./loading.jl:1001
398require at ./loading.jl:922
399require at ./loading.jl:917
400include at ./boot.jl:328 [inlined]
401include_relative at ./loading.jl:1105
402include at ./Base.jl:31 [inlined]
403include at /home/travis/build/alan-turing-institute/MLJTuning.jl/test/models.jl:7
404top-level scope at /home/travis/build/alan-turing-institute/MLJTuning.jl/test/models.jl:14
405include at ./boot.jl:328 [inlined]
406include_relative at ./loading.jl:1105
407include at ./Base.jl:31
408include at ./client.jl:424
409top-level scope at none:0
410eval at ./boot.jl:330
411#105 at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Distributed/src/process_messages.jl:290
412run_work_thunk at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Distributed/src/process_messages.jl:79
413run_work_thunk at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Distributed/src/process_messages.jl:88
414#98 at ./task.jl:333
415in expression starting at /home/travis/build/alan-turing-institute/MLJTuning.jl/test/models/DecisionTree.jl:7
416in expression starting at /home/travis/build/alan-turing-institute/MLJTuning.jl/test/models.jl:14
417Stacktrace:
418 [1] sync_end(::Array{Any,1}) at ./task.jl:300
419 [2] macro expansion at ./task.jl:319 [inlined]
420 [3] remotecall_eval(::Module, ::Array{Int64,1}, ::Expr) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Distributed/src/macros.jl:217
421 [4] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.3/Distributed/src/macros.jl:201
422 [5] include at ./boot.jl:328 [inlined]
423 [6] include_relative(::Module, ::String) at ./loading.jl:1105
424 [7] include(::Module, ::String) at ./Base.jl:31
425 [8] include(::String) at ./client.jl:424
426 [9] top-level scope at none:6
427

cc: @OkonSamuel

Non-thread safe use of resampling machines

Strictly speaking, as currently implemented, calling fit! on a resampling machine mach mutates the value of mach.model.resampling if resampling has a RNG as a field (all of them do, I believe). It may make sense to modify this behaviour by changing the interface point for RNGs in resampling (in MLJBase). However, in the meantime, I suggest we insert a deep copy on the right hand side of https://github.com/alan-turing-institute/MLJTuning.jl/blob/db5ab433c0570641eade702db0dca55379643dbe/src/tuned_models.jl#L447

cc @OkonSamuel

Allow hyper-parameter tuning for immutable models.

Some context: JuliaML/TableTransforms.jl#67

I don't think this would be too bad, and useful preparation for making the MLJ model interface more flexible later.

The MLJTuning API doesn't really touch on this point. A tuning strategy needs to implement a models method to generate models to evaluate, but doesn't say how the models are generated. They needn't be mutations of a single object. However, the MLJ model interface currently states that models must be mutable, so some tuning strategies do use mutation to generate their models.

TODO:

  • To see if the change would be breaking, update this table:
tuning strategy assumes model types are mutable pkg providing strategy
Grid yes MLJTuning
RandomSearch yes MLJTuning
LatinHypercube yes MLJTuning.jl
MLJTreeParzenTuning() ? TreeParzen.jl
ParticleSwarm ? MLJParticleSwarmOptimization.jl
AdaptiveParticleSwarm ? MLJParticleSwarmOptimization.jl
Explicit() no MLJTuning.jl

cc @juliohm

For a 0.5 release

  • Modify API to accomodate user-specified "selection heuristic" (#75)

  • Bump version

Decouple training weights from weights for measures, in TunedModel

This brings the tuning in line with the proposal in JuliaAI/MLJBase.jl#405 .

This would be breaking for clients but does not break the API for tuning strategy implementations.

However, the corresponding change in MLJBase (an explicit dependency of this one) will only become live in a new minor release of that package (0.15). So, by lifting MLJTuning's [compat] of MLJBase to this minor version at the same time as implementing the current issue, we can safely release the change as a patch.

Add particle swarm strateg(ies)

Particle swarm optimization (PSO) is a simple and computationally cheap optimization method by exploring the search space with a coordinated swarm. Since most available tuning strategies only generate a list of models without any optimization heuristics, PSO would be a valuable addition to the existing collection of strategies.

For our purposes of hyperparameter tuning, a good implementation should be able to support both NumericRanges and NominalRanges, or any combinations of the two. To this end categorical hyperparameters would be embedded as probability vectors to extend usual PSO variants.

A naive algorithm would expose some meta-hyperparameters to the user (e.g. inertia, cognitive and social coefficients), but an adaptive scheme such as in Optim.jl could automate this choice.

Given the population of the swarm, each agent maps to a model to be evaluated at each iteration. The total number of iterations is calculated from the set number of models n of the TunedModel. In cases where the strategy generates a surplus of models (e.g. 3 agents and a maximum number of 10 models would leave 2 models unevaluated in the last iteration), then the extra models would be saved for later warm-starts (see #131 for more detail).

Typo in error message for `TunedModel` missing arguments

julia> t = TunedModel(ConstantClassifier())
ERROR: ArgumentError: You need to specify `range=...`, unless `tuning=Explicit` and and `models=...` is specified instead. 
Stacktrace:

This should read "tuning=Explicit()" (parentheses forgotten).

Score measure maximization double signal flip

There is a double signal flip when optimizing for a score measure.

As I understand, lines 51-53 should be removed to fix score measure maximization in method best(heuristic::NaiveSelection, history).

https://github.com/alan-turing-institute/MLJTuning.jl/blob/3e989d5e7d677d73b4c06d087f9a10c6fadcd511/src/selection_heuristics.jl#L46-L58

The first signal flip is done at line 50 (where weights[1] is negative from method measure_adjusted_weights) and then a second flip reverts the signal at line 53.

learning_curve is not using "smart" fitting (performance issue)

I have observed that when increasing the number of trees in a random forest the algorithm does not generate evaluations at uniform intervals of time, but slows down. This strongly suggests that the model is being refit from scratch every time.

x1 = rand(100);
x2 = rand(100);
x3 = rand(100);
X = (x1=x1, x2=x2, x3=x3);
y = 2*x1 .+ 5*x2 .- 3*x3 .+ 0.2*rand(100);
atom = DecisioinTreeRegressor()
ensemble = EnsembleModel(atom=atom, n=50)
mach = machine(ensemble, X, y)
 r_n = range(ensemble, :n, lower=10, upper=10000)

Now run the next line and observe the slow down as the output is generated:

learning_curve(mach, range=r_n, verbosity=2)

For a 0.3 release

Merged onto to dev:

  • (breaking) Add enhancements the tuning strategy interface. This should not break use of TunedModel, and the built-in strategies Grid and Explicit are updated with no new behaviour (#21)

  • (breaking) Add n_remaining argument to models! method to give access to number of evaluations remaining. This should not break use of TunedModel, and the built-in strategies Grid and Explicit are updated with no new behaviour (#23 PR #26)

Frameworks for HP optimization

Julia HP optimization packages:

Other HP optimization packages:

There are projects that benchmark different AutoML systems: https://openml.github.io/automlbenchmark/
From our conversation: JuliaAI/MLJ.jl#416 (comment)
I wanted to tell you guys about Optuna (repo & paper) a new framework for HP optimization.
A nice comparison w/ Hyperopt shows what can be done for HP visualization:
https://neptune.ai/blog/optuna-vs-hyperopt

Here are a few snips:
image

image

A 3 minute clip: https://www.youtube.com/watch?v=-UeC4MR3PHM

It would really be amazing for MLJ to incorporate this!

Higher dimensional ranges and nested ranges specification

One dimensional range in MLJBase, how does that fit with MLJTuning and with the generalisation where you may want to specify “spaces” for sets of parameters.

It might be interesting to see how this is done in other optimisation packages in Julia such as JUMP.

Looking beyond Julia, there MLRMBO which can handle seriously complex parameter spaces see example. MLR3 has a parameter package called paradox - nested conditions can be described as outlined here nested parameter conditions.

Improve the `Explicit` strategy

This stategy (originally added for testing purposed only) is has not been publicised, but needs some improvements before doing so:

Problems:

  • Currently the explicit list (or iterator) of models is specified as range and all models need to have the same type - which is extremely restrictive, as the main use case is for evaluating multiple models of different types.

  • Currently the user has to specify some model=... , say the first in the list, which is clunky

Suggested resolution:

  • Add a new keyword models=... whose specification flags the strategy automatically as Explicit, and whose value is the iterator of models to be compared. Internally models is copied to range. An error is thrown if models and range are both specified and not the same, or if model and models are both specified but model isa eltyype(models) is not true. This maintains backwards compatibility.

Implementation:

To get around the design issue requiring all models to have the same type, we can apply a thin wrapper to all models in the list and give a wrapped model the same setproperty!/getproperty interface as the original. The alternative (I'm guessing, from memory) is to remove the model type parameter M from MLJBase.Resampler{M} which could be painful, but worth checking as this would be less of a hack. Any loss of performance in dropping the type parameter is likely trivial in 99% of use cases.

Add `n_remaining` argument to models! (breaking)

Some simple tuning strategies, such as RandomSearch (which I am working on) will want to
return as many models as possible in one hit, for evaluation in parallel. Unfortunately, the number of iterations set by the user in his TunedModel instance, is not currently available to the models! method.

I propose we pass a new argument n_remaining to the models! method:

MLJTuning.models!(tuning::MyTuningStrategy, model, history, state, n_remaining, verbosity)

Here n_remaining is the difference tuned_model.n - length(history).

Are GridSearch using the update! method?

Hi everyone,

While benchmarking some toy grid searches, I obtained odd results, and it seemed to me that performing a grid search using a TunedModel is slower than it should be.

The idea is to run a grid search over a model that implements the update method, and avoid re-fitting models from scratch for each sampled hyper-parameter set. More precisly, by arranging the grid search so it only changes 1 hyper-parameter per iteration.

Here is a sample code on a toy problem, using EnsembleModel and DecisionTree. The idea is to play with the number of estimators of the Ensemble, and find the optimal one. While a naïve approach would be to restart training form scratch for each new number of estimators, a smarter approach would be to start at the lowest number, and add 1 estimator at each iteration. The updating cost of the ensemble model is then very low (only 1 new estimator to fit) and we expect the Grid search to be much faster.

using MLJ, BenchmarkTools, MLJModels

X = MLJ.table(rand(100, 10));
y = 2X.x1 - X.x2 + 0.05*rand(100);
tree_model = @load  DecisionTreeRegressor
RNG = 90125

Solving this problem using the MLJ interface:

# Tuned Model
forest_model = EnsembleModel(atom=tree_model, rng=RNG)
r = range(forest_model, :n; values=[i for i in 4:103]);
all_rows = collect(1:100)

self_tuning_forest_model = TunedModel(model=forest_model,
                                      tuning=Grid(shuffle=false),
                                      resampling=[(all_rows, all_rows)],
                                      range=r,
                                      measure=rms);

self_tuning_forest = machine(self_tuning_forest_model, X, y);
fit!(self_tuning_forest, verbosity=1)
m1 = self_tuning_forest.report.best_history_entry.measurement[1]
n1 = self_tuning_forest.report.best_history_entry.model.n

@btime begin
    self_tuning_forest = machine(self_tuning_forest_model, X, y);
    fit!(self_tuning_forest, verbosity=0)
end

Then, I have implemented 2 manual grid searches. The first is not intelligent and will restart from scratch, the second will only mutate the n_estimator field of the EnsembleModel and update the associated machine.

# Get the ranges values for n_estimator
values = self_tuning_forest.report.plotting.parameter_values

# Dumb Grid
results = Vector{Float64}(undef, 100)
forest_model = EnsembleModel(atom=tree_model, rng=RNG)
for i in values
    forest_model.n = i
    mach = machine(forest_model, X, y)        
    fit!(mach, verbosity=0)
    rms(predict(mach, X), y)
    results[i-3] = rms(predict(mach, X), y)
end

m3, ind = findmin(results)
n3 = values[ind]

@btime begin
    for i in values        
        forest_model.n = i
        mach = machine(forest_model, X, y)        
        fit!(mach, verbosity=0)
        rms(predict(mach, X), y)
    end
end

# Smart retraining
results = Vector{Float64}(undef, 100)
forest_model = EnsembleModel(atom=tree_model, rng=RNG)
mach = machine(forest_model, X, y)
for i in values
    forest_model.n = i
    fit!(mach, verbosity=0)
    results[i-3] = rms(predict(mach, X), y)
end

m2, ind = findmin(results)
n2 = values[ind]

@btime begin
    mach = machine(forest_model, X, y)
    for i in values
        forest_model.n = i
        fit!(mach, verbosity=0)
        rms(predict(mach, X), y)
    end
end

The obtained results are the following:

Measure Tuned Model Dumb Grid Smart Grid
Fitting Time 737ms 737ms 79ms
Metric (rms) 0.097 0.097 0.099
Optimal n_estimator 11 11 4

Given those results, it seems to me that the Grid Search using a TunedModel is just performing a naïve search by retraining every new model from scratch, instead of re-fitting them. We can also see that we can improve the speed of the grid search by a factor of 10 on this toy example.

I started delving into the implementation details, and found that the problem was not coming form the Grid implementation. The Grid creates a list of models to train by cloning and mutating them, but if we mutate the model field of a machine and set it to a new one, the machine should still update itself as in this example:

### Cloning model, keeping the machine
forest_model = EnsembleModel(atom=tree_model, rng=RNG)
mach = machine(forest_model, X, y)        
fit!(mach)

forest_model_2 = deepcopy(forest_model)
forest_model_2.n +=1
mach.model = forest_model_2
fit!(mach)

Then I started looking at the TunedModel code, but things are becoming much more complicated and I'm afraid I would not be able to understand it alone.

As always, thanks for the time and support you provide me.

`learning_curve` throwing nested task error

X, y = make_blobs()

model = (@load RandomForestClassifier pkg=DecisionTree)()
mach = machine(model, X, y)

r = range(model, :n_trees, lower=10, upper=70, scale=:log10)
many_curves = learning_curve(mach,
                             range=r,
                             resampling=Holdout(),
                             measure=cross_entropy,
                             rng_name=:rng,
                             rngs=1)

Evaluating Learning curve with 1 rngs:   0%[>                 ]  ETA: N/A┌ Error: Problem fi
tting the machine Machine{RandomForestClassifier,}.                              
└ @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:533
[ Info: Running type checks... 
[ Info: Type checks okay. 
┌ Error: Problem fitting the machine Machine{Resampler{Holdout},}. 
└ @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:533
[ Info: Running type checks... 
[ Info: Type checks okay. 
┌ Error: Problem fitting the machine Machine{ProbabilisticTunedModel{Grid,},}. 
└ @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:533
[ Info: Running type checks... 
[ Info: Type checks okay. 
ERROR: TaskFailedException
Stacktrace:
  [1] wait
    @ ./task.jl:322 [inlined]
  [2] threading_run(func::Function)
    @ Base.Threads ./threadingconstructs.jl:34
  [3] macro expansion
    @ ./threadingconstructs.jl:93 [inlined]
  [4] build_forest(labels::Vector{UInt32}, features::Matrix{Float64}, n_subfeatures::Int64, 
n_trees::Int64, partial_sampling::Float64, max_depth::Int64, min_samples_leaf::Int64, min_samples_split::Int64, min_purity_increase::Float64; rng::Random.MersenneTwister)        
    @ DecisionTree ~/.julia/packages/DecisionTree/iWCbW/src/classification/main.jl:223
  [5] fit(m::MLJDecisionTreeInterface.RandomForestClassifier, verbosity::Int64, X::DataFrames.DataFrame, y::CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}})                           
    @ MLJDecisionTreeInterface ~/.julia/packages/MLJDecisionTreeInterface/RZmUr/src/MLJDecisionTreeInterface.jl:200                                                             
  [6] fit_only!(mach::Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}; rows::
Vector{Int64}, verbosity::Int64, force::Bool)                                              
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:531
  [7] #fit!#103
    @ ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:598 [inlined]
  [8] fit_and_extract_on_fold
    @ ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:1088 [inlined]
  [9] (::MLJBase.var"#276#277"{MLJBase.var"#fit_and_extract_on_fold#299"{Vector{Tuple{Vector{Int64}, Vector{Int64}}}, Nothing, Nothing, Int64, Vector{LogLoss{Float64}}, Vector{typeof(predict)}, Bool, Bool, CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}}, DataFrames.DataFrame}, Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}, Int64, ProgressMeter.Progress})(k::Int64)
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:932
 [10] mapreduce_first
    @ ./reduce.jl:392 [inlined]
 [11] _mapreduce(f::MLJBase.var"#276#277"{MLJBase.var"#fit_and_extract_on_fold#299"{Vector{Tuple{Vector{Int64}, Vector{Int64}}}, Nothing, Nothing, Int64, Vector{LogLoss{Float64}}, Vector{typeof(predict)}, Bool, Bool, CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}}, DataFrames.DataFrame}, Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}, Int64, ProgressMeter.Progress}, op::typeof(vcat), #unused#::IndexLinear, 
A::UnitRange{Int64})                                                                       
    @ Base ./reduce.jl:403
 [12] _mapreduce_dim
    @ ./reducedim.jl:318 [inlined]
 [13] #mapreduce#672
    @ ./reducedim.jl:310 [inlined]
 [14] mapreduce
    @ ./reducedim.jl:310 [inlined]
 [15] _evaluate!(func::MLJBase.var"#fit_and_extract_on_fold#299"{Vector{Tuple{Vector{Int64}, Vector{Int64}}}, Nothing, Nothing, Int64, Vector{LogLoss{Float64}}, Vector{typeof(predict)}, Bool, Bool, CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}}, DataFrames.DataFrame}, mach::Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}, #unused#::CPU1{Nothing}, nfolds::Int64, verbosity::Int64)
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:931
 [16] evaluate!(mach::Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}, resampling::Vector{Tuple{Vector{Int64}, Vector{Int64}}}, weights::Nothing, class_weights::Nothing, rows::Nothing, verbosity::Int64, repeats::Int64, measures::Vector{LogLoss{Float64}}, operations::Vector{typeof(predict)}, acceleration::CPU1{Nothing}, force::Bool)              
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:1126
 [17] evaluate!(::Machine{MLJDecisionTreeInterface.RandomForestClassifier, true}, ::Holdout, ::Nothing, ::Nothing, ::Nothing, ::Int64, ::Int64, ::Vector{LogLoss{Float64}}, ::Vector{typeof(predict)}, ::CPU1{Nothing}, ::Bool)                                                 
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:1193
 [18] fit(::Resampler{Holdout}, ::Int64, ::DataFrames.DataFrame, ::CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}})                           
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/resampling.jl:1337
 [19] fit_only!(mach::Machine{Resampler{Holdout}, false}; rows::Nothing, verbosity::Int64, force::Bool)                                                                                
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:531
 [20] #fit!#103
    @ ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:598 [inlined]
 [21] event!(metamodel::MLJDecisionTreeInterface.RandomForestClassifier, resampling_machine::Machine{Resampler{Holdout}, false}, verbosity::Int64, tuning::Grid, history::Nothing, state
::NamedTuple{(:models, :fields, :parameter_scales, :models_delivered), Tuple{Vector{MLJDecisionTreeInterface.RandomForestClassifier}, Vector{Symbol}, Vector{Symbol}, Bool}})
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/tuned_models.jl:395
 [22] #35
    @ ~/.julia/packages/MLJTuning/efiDR/src/tuned_models.jl:433 [inlined]
 [23] iterate
    @ ./generator.jl:47 [inlined]
 [24] _collect(c::Vector{MLJDecisionTreeInterface.RandomForestClassifier}, itr::Base.Generator{Vector{MLJDecisionTreeInterface.RandomForestClassifier}, MLJTuning.var"#35#36"{Machine{Resampler{Holdout}, false}, Int64, Grid, Nothing, NamedTuple{(:models, :fields, :parameter_scales, :models_delivered), Tuple{Vector{MLJDecisionTreeInterface.RandomForestClassifier}, Vector{Symbol}, Vector{Symbol}, Bool}}, ProgressMeter.Progress}}, #unused#::Base.EltypeUnknown, 
isz::Base.HasShape{1})                                                                     
    @ Base ./array.jl:695
 [25] collect_similar
    @ ./array.jl:606 [inlined]
 [26] map
    @ ./abstractarray.jl:2294 [inlined]
 [27] assemble_events!(metamodels::Vector{MLJDecisionTreeInterface.RandomForestClassifier}, 
resampling_machine::Machine{Resampler{Holdout}, false}, verbosity::Int64, tuning::Grid, history::Nothing, state::NamedTuple{(:models, :fields, :parameter_scales, :models_delivered), Tuple{Vector{MLJDecisionTreeInterface.RandomForestClassifier}, Vector{Symbol}, Vector{Symbol}, Bool}}, acceleration::CPU1{Nothing})
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/tuned_models.jl:432
 [28] build!(history::Nothing, n::Int64, tuning::Grid, model::MLJDecisionTreeInterface.RandomForestClassifier, model_buffer::Channel{Any}, state::NamedTuple{(:models, :fields, :parameter_scales, :models_delivered), Tuple{Vector{MLJDecisionTreeInterface.RandomForestClassifier}, Vector{Symbol}, Vector{Symbol}, Bool}}, verbosity::Int64, acceleration::CPU1{Nothing}, resampling_machine::Machine{Resampler{Holdout}, false})                                     
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/tuned_models.jl:625
 [29] fit(::MLJTuning.ProbabilisticTunedModel{Grid, MLJDecisionTreeInterface.RandomForestClassifier}, ::Int64, ::DataFrames.DataFrame, ::CategoricalVector{Int64, UInt32, Int64, CategoricalValue{Int64, UInt32}, Union{}})                           
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/tuned_models.jl:704
 [30] fit_only!(mach::Machine{MLJTuning.ProbabilisticTunedModel{Grid, MLJDecisionTreeInterface.RandomForestClassifier}, true}; rows::Nothing, verbosity::Int64, force::Bool)
    @ MLJBase ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:531
 [31] #fit!#103
    @ ~/.julia/packages/MLJBase/HZmTU/src/machines.jl:598 [inlined]
 [32] (::MLJTuning.var"#61#62"{Machine{MLJTuning.ProbabilisticTunedModel{Grid, MLJDecisionTreeInterface.RandomForestClassifier}, true}, Nothing, Symbol, Int64, ProgressMeter.Progress})
(rng::Random.MersenneTwister)                                                              
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/learning_curves.jl:231
 [33] mapreduce_first
    @ ./reduce.jl:392 [inlined]
 [34] _mapreduce(f::MLJTuning.var"#61#62"{Machine{MLJTuning.ProbabilisticTunedModel{Grid, MLJDecisionTreeInterface.RandomForestClassifier}, true}, Nothing, Symbol, Int64, ProgressMeter.Progress}, op::typeof(MLJTuning._collate), #unused#::IndexLinear, A::Vector{Random.MersenneTwister})                                                                   
    @ Base ./reduce.jl:403
 [35] _mapreduce_dim
    @ ./reducedim.jl:318 [inlined]
 [36] #mapreduce#672
    @ ./reducedim.jl:310 [inlined]
 [37] mapreduce
    @ ./reducedim.jl:310 [inlined]
 [38] _tuning_results(rngs::Vector{Random.MersenneTwister}, acceleration::CPU1{Nothing}, tuned::Machine{MLJTuning.ProbabilisticTunedModel{Grid, MLJDecisionTreeInterface.RandomForestClassifier}, true}, rows::Nothing, rng_name::Symbol, verbosity::Int64)
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/learning_curves.jl:229
 [39] learning_curve(::MLJDecisionTreeInterface.RandomForestClassifier, ::MLJBase.Source, ::
Vararg{MLJBase.Source, N} where N; resolution::Int64, resampling::Holdout, weights::Nothing, measures::Nothing, measure::LogLoss{Float64}, rows::Nothing, operation::Nothing, ranges::Nothing, range::MLJBase.NumericRange{Int64, MLJBase.Bounded, Symbol}, repeats::Int64, acceleration::CPU1{Nothing}, acceleration_grid::CPU1{Nothing}, verbosity::Int64, rngs::Int64, rng_name::Symbol, check_measure::Bool)                                                      
    @ MLJTuning ~/.julia/packages/MLJTuning/efiDR/src/learning_curves.jl:173
 [40] #learning_curve#58
    @ ~/.julia/packages/MLJTuning/efiDR/src/learning_curves.jl:92 [inlined]
 [41] top-level scope
    @ REPL[44]:1

    nested task error: AssertionError: length(ints) == 501
    Stacktrace:
      [1] mt_setfull!(r::Random.MersenneTwister, #unused#::Type{UInt64})
        @ Random /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/RNGs.jl:260
      [2] reserve1
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/RNGs.jl:291 [inlined]
      [3] mt_pop!
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/RNGs.jl:296 [inlined]
      [4] rand
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/RNGs.jl:464 [inlined]
      [5] rand
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/Random.jl:256 [inlined]
      [6] rand(rng::Random.MersenneTwister, sp::Random.SamplerRangeNDL{UInt64, Int64})
        @ Random /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/generation.jl:332
      [7] rand!
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/Random.jl:271 [inlined]                              
      [8] rand!(rng::Random.MersenneTwister, A::Vector{Int64}, X::UnitRange{Int64})
        @ Random /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/Random.jl:266
      [9] rand
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/Random.jl:279 [inlined]
     [10] rand
        @ /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/Random/src/Random.jl:282 [inlined]
     [11] macro expansion
        @ ~/.julia/packages/DecisionTree/iWCbW/src/classification/main.jl:224 [inlined]
     [12] (::DecisionTree.var"#62#threadsfor_fun#22"{Random.MersenneTwister, Vector{UInt32}, Matrix{Float64}, Int64, Int64, Int64, Float64, DecisionTree.var"#20#21"{Vector{Float64}}, Vector{Union{DecisionTree.Leaf{UInt32}, DecisionTree.Node{Float64, UInt32}}}, Int64, Int64, UnitRange{Int64}})(onethread::Bool)
        @ DecisionTree ./threadingconstructs.jl:81
     [13] (::DecisionTree.var"#62#threadsfor_fun#22"{Random.MersenneTwister, Vector{UInt32}, Matrix{Float64}, Int64, Int64, Int64, Float64, DecisionTree.var"#20#21"{Vector{Float64}}, Vector{Union{DecisionTree.Leaf{UInt32}, DecisionTree.Node{Float64, UInt32}}}, Int64, Int64, UnitRange{Int64}})()
        @ DecisionTree ./threadingconstructs.jl:48
(MachineLearningInJulia2020) pkg> status
      Status `~/Google Drive/Julia/MLJ/MachineLearningInJulia2020/Project.toml`
  [336ed68f] CSV v0.9.6
  [324d7699] CategoricalArrays v0.10.1
  [ed09eef8] ComputationalResources v0.3.2
  [a93c6f00] DataFrames v1.2.2
  [7806a523] DecisionTree v0.10.11
  [31c24e10] Distributions v0.25.18
  [f6006082] EvoTrees v0.8.4
  [98b081ad] Literate v2.9.3
  [add582a8] MLJ v0.16.9
  [a7f614a8] MLJBase v0.18.23
  [d354fa79] MLJClusteringInterface v0.1.4
  [094fc8d1] MLJFlux v0.2.5
  [6ee0df7b] MLJLinearModels v0.5.6
  [d491faf4] MLJModels v0.14.12
  [1b6a4a23] MLJMultivariateStatsInterface v0.2.2
  [5ae90465] MLJScikitLearnInterface v0.1.10
  [b8a86587] NearestNeighbors v0.4.9
  [a03496cd] PlotlyBase v0.8.18
  [91a5bcdd] Plots v1.22.4
  [321657f4] ScientificTypes v2.3.0
  [2913bbd2] StatsBase v0.33.10
  [bd369af6] Tables v1.6.0
  [b8865327] UnicodePlots v2.4.6
  [9a3f8284] Random

Julia 1.6.3

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

Error message not propagated on setting acceleration=CPUThreads

When setting acceleration=CPUThreads(), the error message, which should be defined in clean!:
https://github.com/alan-turing-institute/MLJTuning.jl/blob/c64bff7d8e68c25f63ac49820b884314046b30d3/src/tuned_models.jl#L190-L191

not shown properly

ERROR: LoadError: MethodError: Cannot `convert` an object of type ComputationalResources.CPU1{Nothing} to an object of type ComputationalResources.CPUThreads{Nothing}
Closest candidates are:
  convert(::Type{S}, ::T) where {S, T<:(Union{CategoricalString{R}, CategoricalValue{T,R} where T} where R)} at /home/darren/.julia/packages/CategoricalArrays/dmrjI/src/value.jl:103
  convert(::Type{T}, ::T) where T at essentials.jl:168
  ComputationalResources.CPUThreads{Nothing}(::Any) where T at /home/darren/.julia/packages/ComputationalResources/vtKSz/src/ComputationalResources.jl:69
Stacktrace:
 [1] setproperty!(::MLJTuning.ProbabilisticTunedModel{Grid,MyLDAPipe,ComputationalResources.CPUThreads{Nothing},ComputationalResources.CPU1{Nothing}}, ::Symbol, ::ComputationalResources.CPU1{Nothing}) at ./Base.jl:21
 [2] clean!(::MLJTuning.ProbabilisticTunedModel{Grid,MyLDAPipe,ComputationalResources.CPUThreads{Nothing},ComputationalResources.CPU1{Nothing}}) at /home/darren/.julia/packages/MLJTuning/65NXe/src/tuned_models.jl:192
 [3] #TunedModel#5(::MyLDAPipe, ::Grid, ::StratifiedCV, ::Nothing, ::MLJBase.CrossEntropy{Float64}, ::Nothing, ::Function, ::Array{Any,1}, ::Array{Any,1}, ::Bool, ::Int64, ::Nothing, ::ComputationalResources.CPUThreads{Nothing}, ::ComputationalResources.CPU1{Nothing}, ::Bool, ::typeof(TunedModel)) at /home/darren/.julia/packages/MLJTuning/65NXe/src/tuned_models.jl:170
 [4] (::MLJTuning.var"#kw##TunedModel")(::NamedTuple{(:model, :tuning, :resampling, :measure, :acceleration, :ranges),Tuple{MyLDAPipe,Grid,StratifiedCV,MLJBase.CrossEntropy{Float64},ComputationalResources.CPUThreads{Nothing},Array{Any,1}}}, ::typeof(TunedModel)) at ./none:0
 [5] top-level scope at /home/darren/project/PTSDClassifier/train_classifier_local.jl:240
 [6] include at ./boot.jl:328 [inlined]
 [7] include_relative(::Module, ::String) at ./loading.jl:1105
 [8] include(::Module, ::String) at ./Base.jl:31
 [9] include(::String) at ./client.jl:424
 [10] top-level scope at REPL[1]:1
in expression starting at /home/darren/project/PTSDClassifier/train_classifier_local.jl:230

This might be closed when we add CPUThreads as an option in this (#15 )?

Make a tuned model's number of iterations, n, available to setup method

Latin hypercube strategy needs to know the total iteration count up-front but it cannot get this information. The temporary solution in #96 is to introduce a separate n_max parameter for the Latin strategy, but there is little use in this having a different value.

So the proposal is to add n to the signature of setup:

state = setup(tuning::MyTuningStrategy, model, range, n, verbosity)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.