Convert a named tuple of vectors or tuples columntable, into a table of the "preferred sink type" of prototype. This is often the type of prototype itself, when prototype is a sink; see the Tables.jl documentation. If prototype is not specified, then a named tuple of vectors is returned.
Wrap an abstract matrix A as a Tables.jl compatible table with the specified column names (a tuple of symbols). If names are not specified, names=(:x1, :x2, ..., :xn) is used, where n=size(A, 2).
If a prototype is specified, then the matrix is materialized as a table of the preferred sink type of prototype, rather than wrapped. Note that if prototype is not specified, then matrix(table(A)) is essentially a no-op.
The positional integer of the CategoricalString or CategoricalValuex, in the ordering defined by the pool of x. The type of int(x) is the reference type of x.
Not to be confused with x.ref, which is unchanged by reordering of the pool of x, but has the same type.
Convert a named tuple of vectors or tuples columntable, into a table of the "preferred sink type" of prototype. This is often the type of prototype itself, when prototype is a sink; see the Tables.jl documentation. If prototype is not specified, then a named tuple of vectors is returned.
Wrap an abstract matrix A as a Tables.jl compatible table with the specified column names (a tuple of symbols). If names are not specified, names=(:x1, :x2, ..., :xn) is used, where n=size(A, 2).
If a prototype is specified, then the matrix is materialized as a table of the preferred sink type of prototype, rather than wrapped. Note that if prototype is not specified, then matrix(table(A)) is essentially a no-op.
The positional integer of the CategoricalString or CategoricalValuex, in the ordering defined by the pool of x. The type of int(x) is the reference type of x.
Not to be confused with x.ref, which is unchanged by reordering of the pool of x, but has the same type.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
All the categorical elements with the same pool as x (including x), returned as a list, with an ordering consistent with the pool. Here x has CategoricalValue type, and classes(x) is a vector of the same eltype. Note that x in classes(x) is always true.
Not to be confused with levels(x.pool). See the example below.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
All the categorical elements with the same pool as x (including x), returned as a list, with an ordering consistent with the pool. Here x has CategoricalValue type, and classes(x) is a vector of the same eltype. Note that x in classes(x) is always true.
Not to be confused with levels(x.pool). See the example below.
Return a callable object for decoding the integer representation of a CategoricalValue sharing the same pool the CategoricalValuex. Specifically, one has decoder(x)(int(y)) == y for all CategoricalValues y having the same pool as x. One can also call decoder(x) on integer arrays, in which case decoder(x) is broadcast over all elements.
Examples
julia> v = categorical(["c", "b", "c", "a"])
+ "c"
Return a callable object for decoding the integer representation of a CategoricalValue sharing the same pool the CategoricalValuex. Specifically, one has decoder(x)(int(y)) == y for all CategoricalValues y having the same pool as x. One can also call decoder(x) on integer arrays, in which case decoder(x) is broadcast over all elements.
Examples
julia> v = categorical(["c", "b", "c", "a"])
4-element CategoricalArrays.CategoricalArray{String,1,UInt32}:
"c"
"b"
@@ -122,7 +122,7 @@
julia> d = decoder(v[3]);
julia> d(int(v)) == v
-true
Select element(s) of a table or matrix at row(s) r and column(s) c. An object of the sink type of X (or a matrix) is returned unless c is a single integer or symbol. In that case a vector is returned, unless r is a single integer, in which case a single element is returned.
Select single or multiple rows from a table, abstract vector or matrix X. If X is tabular, the object returned is a table of the preferred sink type of typeof(X), even if only a single row is selected.
If the object is neither a table, abstract vector or matrix, X is returned and r is ignored.
Select single or multiple columns from a matrix or table X. If c is an abstract vector of integers or symbols, then the object returned is a table of the preferred sink type of typeof(X). If c is a single integer or column, then an AbstractVector is returned.
Select element(s) of a table or matrix at row(s) r and column(s) c. An object of the sink type of X (or a matrix) is returned unless c is a single integer or symbol. In that case a vector is returned, unless r is a single integer, in which case a single element is returned.
Select single or multiple rows from a table, abstract vector or matrix X. If X is tabular, the object returned is a table of the preferred sink type of typeof(X), even if only a single row is selected.
If the object is neither a table, abstract vector or matrix, X is returned and r is ignored.
Select single or multiple columns from a matrix or table X. If c is an abstract vector of integers or symbols, then the object returned is a table of the preferred sink type of typeof(X). If c is a single integer or column, then an AbstractVector is returned.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
To be registered, MLJ models must include a detailed document string for the model type, and this must conform to the standard outlined below. We recommend you simply adapt an existing compliant document string and read the requirements below if you're not sure, or to use as a checklist. Here are examples of compliant doc-strings (go to the end of the linked files):
Regular supervised models (classifiers and regressors): MLJDecisionTreeInterface.jl (see the end of the file)
Return a string suitable for interpolation in the document string of an MLJ model type. In the example given below, the header expands to something like this:
FooRegressor
A model type for constructing a foo regressor, based on FooRegressorPkg.jl.
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Ordinarily, doc_header is used in document strings defined after the model type definition, as doc_header assumes model traits (in particular, package_name and package_url) to be defined; see also MLJModelInterface.metadata_pkg.
Example
Suppose a model type and traits have been defined by:
To be registered, MLJ models must include a detailed document string for the model type, and this must conform to the standard outlined below. We recommend you simply adapt an existing compliant document string and read the requirements below if you're not sure, or to use as a checklist. Here are examples of compliant doc-strings (go to the end of the linked files):
Regular supervised models (classifiers and regressors): MLJDecisionTreeInterface.jl (see the end of the file)
Return a string suitable for interpolation in the document string of an MLJ model type. In the example given below, the header expands to something like this:
FooRegressor
A model type for constructing a foo regressor, based on FooRegressorPkg.jl.
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Ordinarily, doc_header is used in document strings defined after the model type definition, as doc_header assumes model traits (in particular, package_name and package_url) to be defined; see also MLJModelInterface.metadata_pkg.
Example
Suppose a model type and traits have been defined by:
For models that have a native API with separate documentation, one may want to call doc_header(FooRegressor, augment=true) instead. In that case, the output will look like this:
From MLJ, the FooRegressor type can be imported using
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Your document string must include the following components, in order:
A header, closely matching the example given above.
A reference describing the algorithm or an actual description of the algorithm, if necessary. Detail any non-standard aspects of the implementation. Generally, defer details on the role of hyperparameters to the "Hyperparameters" section (see below).
Instructions on how to import the model type from MLJ (because a user can already inspect the doc-string in the Model Registry, without having loaded the code-providing package).
Instructions on how to instantiate with default hyperparameters or with keywords.
A Training data section: explains how to bind a model to data in a machine with all possible signatures (eg, machine(model, X, y) but also machine(model, X, y, w) if, say, weights are supported); the role and scitype requirements for each data argument should be itemized.
Instructions on how to fit the machine (in the same section).
A Hyperparameters section (unless there aren't any): an itemized list of the parameters, with defaults given.
An Operations section: each implemented operation (predict, predict_mode, transform, inverse_transform, etc ) is itemized and explained. This should include operations with no data arguments, such as training_losses and feature_importances.
A Fitted parameters section: To explain what is returned by fitted_params(mach) (the same as MLJModelInterface.fitted_params(model, fitresult) - see later) with the fields of that named tuple itemized.
A Report section (if report is non-empty): To explain what, if anything, is included in the report(mach) (the same as the report return value of MLJModelInterface.fit) with the fields itemized.
An optional but highly recommended Examples section, which includes MLJ examples, but which could also include others if the model type also implements a second "local" interface, i.e., defined in the same module. (Note that each module referring to a type can declare separate doc-strings which appear concatenated in doc-string queries.)
A closing "See also" sentence which includes a @ref link to the raw model type (if you are wrapping one).
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+
Variation to augment existing document string
For models that have a native API with separate documentation, one may want to call doc_header(FooRegressor, augment=true) instead. In that case, the output will look like this:
From MLJ, the FooRegressor type can be imported using
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Your document string must include the following components, in order:
A header, closely matching the example given above.
A reference describing the algorithm or an actual description of the algorithm, if necessary. Detail any non-standard aspects of the implementation. Generally, defer details on the role of hyperparameters to the "Hyperparameters" section (see below).
Instructions on how to import the model type from MLJ (because a user can already inspect the doc-string in the Model Registry, without having loaded the code-providing package).
Instructions on how to instantiate with default hyperparameters or with keywords.
A Training data section: explains how to bind a model to data in a machine with all possible signatures (eg, machine(model, X, y) but also machine(model, X, y, w) if, say, weights are supported); the role and scitype requirements for each data argument should be itemized.
Instructions on how to fit the machine (in the same section).
A Hyperparameters section (unless there aren't any): an itemized list of the parameters, with defaults given.
An Operations section: each implemented operation (predict, predict_mode, transform, inverse_transform, etc ) is itemized and explained. This should include operations with no data arguments, such as training_losses and feature_importances.
A Fitted parameters section: To explain what is returned by fitted_params(mach) (the same as MLJModelInterface.fitted_params(model, fitresult) - see later) with the fields of that named tuple itemized.
A Report section (if report is non-empty): To explain what, if anything, is included in the report(mach) (the same as the report return value of MLJModelInterface.fit) with the fields itemized.
An optional but highly recommended Examples section, which includes MLJ examples, but which could also include others if the model type also implements a second "local" interface, i.e., defined in the same module. (Note that each module referring to a type can declare separate doc-strings which appear concatenated in doc-string queries.)
A closing "See also" sentence which includes a @ref link to the raw model type (if you are wrapping one).
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
For a given model of model type M supporting intrinsic feature importances, calculate the feature importances from the model's fitresult and report as an abstract vector of feature::Symbol => importance::Real pairs (e.g [:gender =>0.23, :height =>0.7, :weight => 0.1]).
New model implementations
The following trait overload is also required: MLJModelInterface.reports_feature_importances(::Type{<:M}) = true
If for some reason a model is sometimes unable to report feature importances then feature_importances should return all importances as 0.0, as in [:gender =>0.0, :height =>0.0, :weight => 0.0].
For a given model of model type M supporting intrinsic feature importances, calculate the feature importances from the model's fitresult and report as an abstract vector of feature::Symbol => importance::Real pairs (e.g [:gender =>0.23, :height =>0.7, :weight => 0.1]).
New model implementations
The following trait overload is also required: MLJModelInterface.reports_feature_importances(::Type{<:M}) = true
If for some reason a model is sometimes unable to report feature importances then feature_importances should return all importances as 0.0, as in [:gender =>0.0, :height =>0.0, :weight => 0.0].
Trait values can also be set using the metadata_model method, see below.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/fitting_distributions/index.html b/dev/fitting_distributions/index.html
index aaab38a..55f366b 100644
--- a/dev/fitting_distributions/index.html
+++ b/dev/fitting_distributions/index.html
@@ -1,2 +1,2 @@
-Models that learn a probability distribution · MLJModelInterface
The following API is experimental. It is subject to breaking changes during minor or major releases without warning. Models implementing this interface will not work with MLJBase versions earlier than 0.17.5.
Models that fit a probability distribution to some data should be regarded as Probabilistic <: Supervised models with target y = data and X = nothing.
The predict method should return a single distribution.
A working implementation of a model that fits a UnivariateFinite distribution to some categorical data using Laplace smoothing controlled by a hyperparameter alpha is given here.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+Models that learn a probability distribution · MLJModelInterface
The following API is experimental. It is subject to breaking changes during minor or major releases without warning. Models implementing this interface will not work with MLJBase versions earlier than 0.17.5.
Models that fit a probability distribution to some data should be regarded as Probabilistic <: Supervised models with target y = data and X = nothing.
The predict method should return a single distribution.
A working implementation of a model that fits a UnivariateFinite distribution to some categorical data using Laplace smoothing controlled by a hyperparameter alpha is given here.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/form_of_data/index.html b/dev/form_of_data/index.html
index 7c0a845..1ab25eb 100644
--- a/dev/form_of_data/index.html
+++ b/dev/form_of_data/index.html
@@ -1,2 +1,2 @@
-The form of data for fitting and predicting · MLJModelInterface
The model implementer does not have absolute control over the types of data X, y and Xnew appearing in the fit and predict methods they must implement. Rather, they can specify the scientific type of this data by making appropriate declarations of the traits input_scitype and target_scitype discussed later under Trait declarations.
Important Note. Unless it genuinely makes little sense to do so, the MLJ recommendation is to specify a Table scientific type for X (and hence Xnew) and an AbstractVector scientific type (e.g., AbstractVector{Continuous}) for targets y. Algorithms requiring matrix input can coerce their inputs appropriately; see below.
If the core algorithm being wrapped requires data in a different or more specific form, then fit will need to coerce the table into the form desired (and the same coercions applied to X will have to be repeated for Xnew in predict). To assist with common cases, MLJ provides the convenience method MMI.matrix. MMI.matrix(Xtable) has type Matrix{T} where T is the tightest common type of elements of Xtable, and Xtable is any table. (If Xtable is itself just a wrapped matrix, Xtable=Tables.table(A), then A=MMI.table(Xtable) will be returned without any copying.)
Alternatively, a more performant option is to implement a data front-end for your model; see Implementing a data front-end.
Other auxiliary methods provided by MLJModelInterface for handling tabular data are: selectrows, selectcols, select and schema (for extracting the size, names and eltypes of a table's columns). See Convenience methods below for details.
It is to be understood that the columns of table X correspond to features and the rows to observations. So, for example, the predict method for a linear regression model might look like predict(model, w, Xnew) = MMI.matrix(Xnew)*w, where w is the vector of learned coefficients.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+The form of data for fitting and predicting · MLJModelInterface
The model implementer does not have absolute control over the types of data X, y and Xnew appearing in the fit and predict methods they must implement. Rather, they can specify the scientific type of this data by making appropriate declarations of the traits input_scitype and target_scitype discussed later under Trait declarations.
Important Note. Unless it genuinely makes little sense to do so, the MLJ recommendation is to specify a Table scientific type for X (and hence Xnew) and an AbstractVector scientific type (e.g., AbstractVector{Continuous}) for targets y. Algorithms requiring matrix input can coerce their inputs appropriately; see below.
If the core algorithm being wrapped requires data in a different or more specific form, then fit will need to coerce the table into the form desired (and the same coercions applied to X will have to be repeated for Xnew in predict). To assist with common cases, MLJ provides the convenience method MMI.matrix. MMI.matrix(Xtable) has type Matrix{T} where T is the tightest common type of elements of Xtable, and Xtable is any table. (If Xtable is itself just a wrapped matrix, Xtable=Tables.table(A), then A=MMI.table(Xtable) will be returned without any copying.)
Alternatively, a more performant option is to implement a data front-end for your model; see Implementing a data front-end.
Other auxiliary methods provided by MLJModelInterface for handling tabular data are: selectrows, selectcols, select and schema (for extracting the size, names and eltypes of a table's columns). See Convenience methods below for details.
It is to be understood that the columns of table X correspond to features and the rows to observations. So, for example, the predict method for a linear regression model might look like predict(model, w, Xnew) = MMI.matrix(Xnew)*w, where w is the vector of learned coefficients.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/how_to_register/index.html b/dev/how_to_register/index.html
index 74f8d26..75ef7d2 100644
--- a/dev/how_to_register/index.html
+++ b/dev/how_to_register/index.html
@@ -1,2 +1,2 @@
-How to add models to the MLJ Model Registry · MLJModelInterface
The MLJ model registry is located in the MLJModels.jl repository. To add a model, you need to follow these steps
Ensure your model conforms to the interface defined above
Raise an issue at MLJModels.jl and point out where the MLJ-interface implementation is, e.g. by providing a link to the code.
An administrator will then review your implementation and work with you to add the model to the registry
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/implementing_a_data_front_end/index.html b/dev/implementing_a_data_front_end/index.html
index f612de2..73e581b 100644
--- a/dev/implementing_a_data_front_end/index.html
+++ b/dev/implementing_a_data_front_end/index.html
@@ -1,5 +1,5 @@
-Implementing a data front end · MLJModelInterface
It is suggested that packages implementing MLJ's model API, that later implement a data front-end, should tag their changes in a breaking release. (The changes will not break the use of models for the ordinary MLJ user, who interacts with models exclusively through the machine interface. However, it will break usage for some external packages that have chosen to depend directly on the model API.)
MLJModelInterface.reformat(model, args...) -> data
+Implementing a data front end · MLJModelInterface
It is suggested that packages implementing MLJ's model API, that later implement a data front-end, should tag their changes in a breaking release. (The changes will not break the use of models for the ordinary MLJ user, who interacts with models exclusively through the machine interface. However, it will break usage for some external packages that have chosen to depend directly on the model API.)
MLJModelInterface.reformat(model, args...) -> data
MLJModelInterface.selectrows(::Model, I, data...) -> sampled_data
Models optionally overload reformat to define transformations of user-supplied data into some model-specific representation (e.g., from a table to a matrix). Computational overheads associated with multiple fit!/predict/transform calls (on MLJ machines) are then avoided when memory resources allow. The fallback returns args (no transformation).
The selectrows(::Model, I, data...) method is overloaded to specify how the model-specific data is to be subsampled, for some observation indices I (a colon, :, or instance of AbstractVector{<:Integer}). In this way, implementing a data front-end also allows more efficient resampling of data (in user calls to evaluate!).
After detailing formal requirements for implementing a data front-end, we give a Sample implementation. A simple implementation also appears in the MLJDecisionTreeInterface.jl package.
Here "user-supplied data" is what the MLJ user supplies when constructing a machine, as in machine(models, args...), which coincides with the arguments expected by fit(model, verbosity, args...) when reformat is not overloaded.
Overloading reformat is permitted for any Model subtype, except for subtypes of Static. Here is a complete list of responsibilities for such an implementation, for some model::SomeModelType (a sample implementation follows after):
A reformat(model::SomeModelType, args...) -> data method must be implemented for each form of args... appearing in a valid machine construction machine(model, args...) (there will be one for each possible signature of fit(::SomeModelType, ...)).
Additionally, if not included above, there must be a single argument form of reformat, reformat(model::SomeModelType, arg) -> (data,), serving as a data front-end for operations like predict. It must always hold that reformat(model, args...)[1] = reformat(model, args[1]).
The fallback is reformat(model, args...) = args (i.e., slurps provided data).
Important.reformat(model::SomeModelType, args...) must always return a tuple, even if this has length one. The length of the tuple need not match length(args).
fit(model::SomeModelType, verbosity, data...) should be implemented as if data is the output of reformat(model, args...), where args is the data an MLJ user has bound to model in some machine. The same applies to any overloading of update.
Each implemented operation, such as predict and transform - but excluding inverse_transform - must be defined as if its data arguments are reformated versions of user-supplied data. For example, in the supervised case, data_new in predict(model::SomeModelType, fitresult, data_new) is reformat(model, Xnew), where Xnew is the data provided by the MLJ user in a call predict(mach, Xnew) (mach.model == model).
To specify how the model-specific representation of data is to be resampled, implement selectrows(model::SomeModelType, I, data...) -> resampled_data for each overloading of reformat(model::SomeModel, args...) -> data above. Here I is an arbitrary abstract integer vector or : (type Colon).
Important.selectrows(model::SomeModelType, I, args...) must always return a tuple of the same length as args, even if this is one.
The fallback for selectrows is described at selectrows.
Suppose a supervised model type SomeSupervised supports sample weights, leading to two different fit signatures, and that it has a single operation predict:
fit(model::SomeSupervised, verbosity, X, y)
fit(model::SomeSupervised, verbosity, X, y, w)
@@ -15,4 +15,4 @@
# for predict:
MMI.reformat(::SomeSupervised, X) = (MMI.matrix(X)',)
-MMI.selectrows(::SomeSupervised, I, Xmatrix) = (view(Xmatrix, :, I),)
With these additions, fit and predict are refactored, so that X and Xnew represent matrices with features as rows.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+MMI.selectrows(::SomeSupervised, I, Xmatrix) = (view(Xmatrix, :, I),)
With these additions, fit and predict are refactored, so that X and Xnew represent matrices with features as rows.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
The machine learning tools provided by MLJ can be applied to the models in any package that imports MLJModelInterface and implements the API defined there, as outlined in this document.
Tip
This is a reference document, which has become rather sprawling over the evolution of the MLJ project. We recommend starting with Quick start guide, which covers the main points relevant to most new model implementations.
Interface code can be hosted by the package providing the core machine learning algorithm, or by a stand-alone "interface-only" package, using the template MLJExampleInterface.jl (see Where to place code implementing new models below). For a list of packages implementing the MLJ model API (natively, and in interface packages) see here.
MLJModelInterface is a very light-weight interface allowing you to define your interface, but does not provide the functionality required to use or test your interface; this requires MLJBase. So, while you only need to add MLJModelInterface to your project's [deps], for testing purposes you need to add MLJBase to your project's [extras] and [targets]. In testing, simply use MLJBase in place of MLJModelInterface.
It is assumed the reader has read the Getting Started section of the MLJ manual. To implement the API described here, some familiarity with the following packages is also helpful:
CategoricalArrays.jl (essential if you are implementing a model handling data of Multiclass or OrderedFactor scitype; familiarity with CategoricalPool objects required)
Tables.jl (if your algorithm needs input data in a novel format).
In MLJ, the basic interface exposed to the user, built atop the model interface described here, is the machine interface. After a first reading of this document, the reader may wish to refer to MLJ Internals for context.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
The machine learning tools provided by MLJ can be applied to the models in any package that imports MLJModelInterface and implements the API defined there, as outlined in this document.
Tip
This is a reference document, which has become rather sprawling over the evolution of the MLJ project. We recommend starting with Quick start guide, which covers the main points relevant to most new model implementations.
Interface code can be hosted by the package providing the core machine learning algorithm, or by a stand-alone "interface-only" package, using the template MLJExampleInterface.jl (see Where to place code implementing new models below). For a list of packages implementing the MLJ model API (natively, and in interface packages) see here.
MLJModelInterface is a very light-weight interface allowing you to define your interface, but does not provide the functionality required to use or test your interface; this requires MLJBase. So, while you only need to add MLJModelInterface to your project's [deps], for testing purposes you need to add MLJBase to your project's [extras] and [targets]. In testing, simply use MLJBase in place of MLJModelInterface.
It is assumed the reader has read the Getting Started section of the MLJ manual. To implement the API described here, some familiarity with the following packages is also helpful:
CategoricalArrays.jl (essential if you are implementing a model handling data of Multiclass or OrderedFactor scitype; familiarity with CategoricalPool objects required)
Tables.jl (if your algorithm needs input data in a novel format).
In MLJ, the basic interface exposed to the user, built atop the model interface described here, is the machine interface. After a first reading of this document, the reader may wish to refer to MLJ Internals for context.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/iterative_models/index.html b/dev/iterative_models/index.html
index effae2c..cb5078d 100644
--- a/dev/iterative_models/index.html
+++ b/dev/iterative_models/index.html
@@ -1,5 +1,5 @@
-Iterative models and the update! method · MLJModelInterface
An update method may be optionally overloaded to enable a call by MLJ to retrain a model (on the same training data) to avoid repeating computations unnecessarily.
MMI.update(model::SomeSupervisedModel, verbosity, old_fitresult, old_cache, X, y) -> fit
+Iterative models and the update! method · MLJModelInterface
An update method may be optionally overloaded to enable a call by MLJ to retrain a model (on the same training data) to avoid repeating computations unnecessarily.
MMI.update(model::SomeSupervisedModel, verbosity, old_fitresult, old_cache, X, y) -> fit
result, cache, report
MMI.update(model::SomeSupervisedModel, verbosity, old_fitresult, old_cache, X, y, w=nothing) -> fit
-result, cache, report
Here the second variation applies if SomeSupervisedModel supports sample weights.
If an MLJ Machine is being fit! and it is not the first time, then update is called instead of fit, unless the machine fit! has been called with a new rows keyword argument. However, MLJModelInterface defines a fallback for update which just calls fit. For context, see the Internals section of the MLJ manual.
Learning networks wrapped as models constitute one use case (see the Composing Models section of the MLJ manual): one would like each component model to be retrained only when hyperparameter changes "upstream" make this necessary. In this case, MLJ provides a fallback (specifically, the fallback is for any subtype of SupervisedNetwork = Union{DeterministicNetwork,ProbabilisticNetwork}). A second more generally relevant use case is iterative models, where calls to increase the number of iterations only restarts the iterative procedure if other hyperparameters have also changed. (A useful method for inspecting model changes in such cases is MLJModelInterface.is_same_except. ) For an example, see MLJEnsembles.jl.
A third use case is to avoid repeating the time-consuming preprocessing of X and y required by some models.
If the argument fitresult (returned by a preceding call to fit) is not sufficient for performing an update, the author can arrange for fit to output in its cache return value any additional information required (for example, pre-processed versions of X and y), as this is also passed as an argument to the update method.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+result, cache, report
Here the second variation applies if SomeSupervisedModel supports sample weights.
If an MLJ Machine is being fit! and it is not the first time, then update is called instead of fit, unless the machine fit! has been called with a new rows keyword argument. However, MLJModelInterface defines a fallback for update which just calls fit. For context, see the Internals section of the MLJ manual.
Learning networks wrapped as models constitute one use case (see the Composing Models section of the MLJ manual): one would like each component model to be retrained only when hyperparameter changes "upstream" make this necessary. In this case, MLJ provides a fallback (specifically, the fallback is for any subtype of SupervisedNetwork = Union{DeterministicNetwork,ProbabilisticNetwork}). A second more generally relevant use case is iterative models, where calls to increase the number of iterations only restarts the iterative procedure if other hyperparameters have also changed. (A useful method for inspecting model changes in such cases is MLJModelInterface.is_same_except. ) For an example, see MLJEnsembles.jl.
A third use case is to avoid repeating the time-consuming preprocessing of X and y required by some models.
If the argument fitresult (returned by a preceding call to fit) is not sufficient for performing an update, the author can arrange for fit to output in its cache return value any additional information required (for example, pre-processed versions of X and y), as this is also passed as an argument to the update method.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
The Outlier Detection API is experimental and may change in future releases of MLJ.
Outlier detection or anomaly detection is predominantly an unsupervised learning task, transforming each data point to an outlier score quantifying the level of "outlierness". However, because detectors can also be semi-supervised or supervised, MLJModelInterface provides a collection of abstract model types, that enable the different characteristics, namely:
All outlier detection models subtyping from any of the above supertypes have to implement MLJModelInterface.fit(model, verbosity, X, [y]). Models subtyping from either SupervisedDetector or UnsupervisedDetector have to implement MLJModelInterface.transform(model, fitresult, Xnew), which should return the raw outlier scores (<:Continuous) of all points in Xnew.
Probabilistic and deterministic outlier detection models provide an additional option to predict a normalized estimate of outlierness or a concrete outlier label and thus enable evaluation of those models. All corresponding supertypes have to implement (in addition to the previously described fit and transform) MLJModelInterface.predict(model, fitresult, Xnew), with deterministic predictions conforming to OrderedFactor{2}, with the first class being the normal class and the second class being the outlier. Probabilistic models predict a UnivariateFinite estimate of those classes.
It is typically possible to automatically convert an outlier detection model to a probabilistic or deterministic model if the training scores are stored in the model's report. Below mentioned OutlierDetection.jl package, for example, stores the training scores under the scores key in the report returned from fit. It is then possible to use model wrappers such as OutlierDetection.ProbabilisticDetector to automatically convert a model to enable predictions of the required output type.
External outlier detection packages
OutlierDetection.jl provides an opinionated interface on top of MLJ for outlier detection models, standardizing things like class names, dealing with training scores, score normalization and more.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
The Outlier Detection API is experimental and may change in future releases of MLJ.
Outlier detection or anomaly detection is predominantly an unsupervised learning task, transforming each data point to an outlier score quantifying the level of "outlierness". However, because detectors can also be semi-supervised or supervised, MLJModelInterface provides a collection of abstract model types, that enable the different characteristics, namely:
All outlier detection models subtyping from any of the above supertypes have to implement MLJModelInterface.fit(model, verbosity, X, [y]). Models subtyping from either SupervisedDetector or UnsupervisedDetector have to implement MLJModelInterface.transform(model, fitresult, Xnew), which should return the raw outlier scores (<:Continuous) of all points in Xnew.
Probabilistic and deterministic outlier detection models provide an additional option to predict a normalized estimate of outlierness or a concrete outlier label and thus enable evaluation of those models. All corresponding supertypes have to implement (in addition to the previously described fit and transform) MLJModelInterface.predict(model, fitresult, Xnew), with deterministic predictions conforming to OrderedFactor{2}, with the first class being the normal class and the second class being the outlier. Probabilistic models predict a UnivariateFinite estimate of those classes.
It is typically possible to automatically convert an outlier detection model to a probabilistic or deterministic model if the training scores are stored in the model's report. Below mentioned OutlierDetection.jl package, for example, stores the training scores under the scores key in the report returned from fit. It is then possible to use model wrappers such as OutlierDetection.ProbabilisticDetector to automatically convert a model to enable predictions of the required output type.
External outlier detection packages
OutlierDetection.jl provides an opinionated interface on top of MLJ for outlier detection models, standardizing things like class names, dealing with training scores, score normalization and more.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
The following are condensed and informal instructions for implementing the MLJ model interface for a new machine learning model. We assume: (i) you have a Julia registered package YourPackage.jl implementing some machine learning models; (ii) that you would like to interface and register these models with MLJ; and (iii) that you have a rough understanding of how things work with MLJ. In particular, you are familiar with:
what Probabilistic, Deterministic and Unsupervised models are
the fact that MLJ generally works with tables rather than matrices. Here a table is a container X satisfying the Tables.jl API and satisfying Tables.istable(X) == true (e.g., DataFrame, JuliaDB table, CSV file, named tuple of equal-length vectors)
If you're not familiar with any one of these points, the Getting Started section of the MLJ manual may help.
But tables don't make sense for my model! If a case can be made that tabular input does not make sense for your particular model, then MLJ can still handle this; you just need to define a non-tabular input_scitype trait. However, you should probably open an issue to clarify the appropriate declaration. The discussion below assumes input data is tabular.
For simplicity, this document assumes no data front-end is to be defined for your model. Adding a data front-end, which offers the MLJ user some performance benefits, is easy to add post-facto, and is described in Implementing a data front-end.
MLJModelInterface is a very light-weight interface allowing you to define your interface, but does not provide the functionality required to use or test your interface; this requires MLJBase. So, while you only need to add MLJModelInterface to your project's [deps], for testing purposes you need to add MLJBase to your project's [extras] and [targets]. In testing, simply use MLJBase in place of MLJModelInterface.
We give some details for each step below with, each time, a few examples that you can mimic. The instructions are intentionally brief.
The following are condensed and informal instructions for implementing the MLJ model interface for a new machine learning model. We assume: (i) you have a Julia registered package YourPackage.jl implementing some machine learning models; (ii) that you would like to interface and register these models with MLJ; and (iii) that you have a rough understanding of how things work with MLJ. In particular, you are familiar with:
what Probabilistic, Deterministic and Unsupervised models are
the fact that MLJ generally works with tables rather than matrices. Here a table is a container X satisfying the Tables.jl API and satisfying Tables.istable(X) == true (e.g., DataFrame, JuliaDB table, CSV file, named tuple of equal-length vectors)
If you're not familiar with any one of these points, the Getting Started section of the MLJ manual may help.
But tables don't make sense for my model! If a case can be made that tabular input does not make sense for your particular model, then MLJ can still handle this; you just need to define a non-tabular input_scitype trait. However, you should probably open an issue to clarify the appropriate declaration. The discussion below assumes input data is tabular.
For simplicity, this document assumes no data front-end is to be defined for your model. Adding a data front-end, which offers the MLJ user some performance benefits, is easy to add post-facto, and is described in Implementing a data front-end.
MLJModelInterface is a very light-weight interface allowing you to define your interface, but does not provide the functionality required to use or test your interface; this requires MLJBase. So, while you only need to add MLJModelInterface to your project's [deps], for testing purposes you need to add MLJBase to your project's [extras] and [targets]. In testing, simply use MLJBase in place of MLJModelInterface.
We give some details for each step below with, each time, a few examples that you can mimic. The instructions are intentionally brief.
MLJ-compatible constructors for your models need to meet the following requirements:
be mutable struct,
be subtypes of MLJModelInterface.Probabilistic or MLJModelInterface.Deterministic or MLJModelInterface.Unsupervised,
have fields corresponding exclusively to hyperparameters,
have a keyword constructor assigning default values to all hyperparameters.
You may use the @mlj_model macro from MLJModelInterface to declare a (non parametric) model type:
MLJModelInterface.@mlj_model mutable struct YourModel <: MLJModelInterface.Deterministic
a::Float64 = 0.5::(_ > 0)
b::String = "svd"::(_ in ("svd","qr"))
end
That macro specifies:
A keyword constructor (here YourModel(; a=..., b=...)),
Default values for the hyperparameters,
Constraints on the hyperparameters where _ refers to a value passed.
Further to the last point, a::Float64 = 0.5::(_ > 0) indicates that the field a is a Float64, takes 0.5 as its default value, and expects its value to be positive.
Please see this issue for a known issue and workaround relating to the use of @mlj_model with negative defaults.
If you decide not to use the @mlj_model macro (e.g. in the case of a parametric type), you will need to write a keyword constructor and a clean! method:
mutable struct YourModel <: MLJModelInterface.Deterministic
@@ -47,4 +47,4 @@
supports_weights = false, # does the model support sample weights?
descr = "A short description of your model"
load_path = "YourPackage.SubModuleContainingModelStructDefinition.YourModel1"
-)
Important. Do not omit the load_path specification. Without a correct load_path MLJ will be unable to import your model.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
All the categorical elements with the same pool as x (including x), returned as a list, with an ordering consistent with the pool. Here x has CategoricalValue type, and classes(x) is a vector of the same eltype. Note that x in classes(x) is always true.
Not to be confused with levels(x.pool). See the example below.
If augment=true the provided array is augmented by inserting appropriate elements ahead of those provided, along the last dimension of the array. This means the user only provides probabilities for the classes c2, c3, ..., cn. The class c1 probabilities are chosen so that each UnivariateFinite distribution in the returned array is a bona fide probability distribution.
Construct a discrete univariate distribution whose finite support is the set of keys of the provided dictionary, prob_given_class, and whose values specify the corresponding probabilities.
The type requirements on the keys of the dictionary are the same as the elements of support given above with this exception: if non-categorical elements (raw labels) are used as keys, then pool=... must be specified and cannot be missing.
If the values (probabilities) are arrays instead of scalars, then an abstract array of UnivariateFinite elements is created, with the same size as the array.
All the categorical elements with the same pool as x (including x), returned as a list, with an ordering consistent with the pool. Here x has CategoricalValue type, and classes(x) is a vector of the same eltype. Note that x in classes(x) is always true.
Not to be confused with levels(x.pool). See the example below.
Return a callable object for decoding the integer representation of a CategoricalValue sharing the same pool the CategoricalValuex. Specifically, one has decoder(x)(int(y)) == y for all CategoricalValues y having the same pool as x. One can also call decoder(x) on integer arrays, in which case decoder(x) is broadcast over all elements.
Examples
julia> v = categorical(["c", "b", "c", "a"])
+ "c"
Return a callable object for decoding the integer representation of a CategoricalValue sharing the same pool the CategoricalValuex. Specifically, one has decoder(x)(int(y)) == y for all CategoricalValues y having the same pool as x. One can also call decoder(x) on integer arrays, in which case decoder(x) is broadcast over all elements.
Examples
julia> v = categorical(["c", "b", "c", "a"])
4-element CategoricalArrays.CategoricalArray{String,1,UInt32}:
"c"
"b"
@@ -102,7 +102,7 @@
julia> d = decoder(v[3]);
julia> d(int(v)) == v
-true
All models must implement a fit method. Here data is the output of reformat on user-provided data, or some some resampling thereof. The fallback of reformat returns the user-provided data (eg, a table).
The positional integer of the CategoricalString or CategoricalValuex, in the ordering defined by the pool of x. The type of int(x) is the reference type of x.
Not to be confused with x.ref, which is unchanged by reordering of the pool of x, but has the same type.
All models must implement a fit method. Here data is the output of reformat on user-provided data, or some some resampling thereof. The fallback of reformat returns the user-provided data (eg, a table).
The positional integer of the CategoricalString or CategoricalValuex, in the ordering defined by the pool of x. The type of int(x) is the reference type of x.
Not to be confused with x.ref, which is unchanged by reordering of the pool of x, but has the same type.
If both m1 and m2 are of MLJType, return true if the following conditions all hold, and false otherwise:
typeof(m1) === typeof(m2)
propertynames(m1) === propertynames(m2)
with the exception of properties listed as exceptions or bound to an AbstractRNG, each pair of corresponding property values is either "equal" or both undefined. (If a property appears as a propertyname but not a fieldname, it is deemed as always defined.)
The meaining of "equal" depends on the type of the property value:
values that are themselves of MLJType are "equal" if they are equal in the sense of is_same_except with no exceptions.
values that are not of MLJType are "equal" if they are ==.
In the special case of a "deep" property, "equal" has a different meaning; see deep_properties) for details.
If m1 or m2 are not MLJType objects, then return ==(m1, m2).
If both m1 and m2 are of MLJType, return true if the following conditions all hold, and false otherwise:
typeof(m1) === typeof(m2)
propertynames(m1) === propertynames(m2)
with the exception of properties listed as exceptions or bound to an AbstractRNG, each pair of corresponding property values is either "equal" or both undefined. (If a property appears as a propertyname but not a fieldname, it is deemed as always defined.)
The meaining of "equal" depends on the type of the property value:
values that are themselves of MLJType are "equal" if they are equal in the sense of is_same_except with no exceptions.
values that are not of MLJType are "equal" if they are ==.
In the special case of a "deep" property, "equal" has a different meaning; see deep_properties) for details.
If m1 or m2 are not MLJType objects, then return ==(m1, m2).
Helper function to write the metadata for a package providing model T. Use it with broadcasting to define the metadata of the package providing a series of models.
Keywords
package_name="unknown" : package name
package_uuid="unknown" : package uuid
package_url="unknown" : package url
is_pure_julia=missing : whether the package is pure julia
package_license="unknown": package license
is_wrapper=false : whether the package is a wrapper
Helper function to write the metadata for a package providing model T. Use it with broadcasting to define the metadata of the package providing a series of models.
Keywords
package_name="unknown" : package name
package_uuid="unknown" : package uuid
package_url="unknown" : package url
is_pure_julia=missing : whether the package is pure julia
package_license="unknown": package license
is_wrapper=false : whether the package is a wrapper
Recursively convert any transparent object m into a named tuple, keyed on the fields of m. An object is transparent if MLJModelInterface.istransparent(m) == true. The named tuple is possibly nested because params is recursively applied to the field values, which themselves might be transparent.
Recursively convert any transparent object m into a named tuple, keyed on the fields of m. An object is transparent if MLJModelInterface.istransparent(m) == true. The named tuple is possibly nested because params is recursively applied to the field values, which themselves might be transparent.
MLJModelInterface.reformat(model, args...) -> data
Models optionally overload reformat to define transformations of user-supplied data into some model-specific representation (e.g., from a table to a matrix). When implemented, the MLJ user can avoid repeating such transformations unnecessarily, and can additionally make use of more efficient row subsampling, which is then based on the model-specific representation of data, rather than the user-representation. When reformat is overloaded, selectrows(::Model, ...) must be as well (see selectrows). Furthermore, the model fit method(s), and operations, such as predict and transform, must be refactored to act on the model-specific representations of the data.
To implement the reformat data front-end for a model, refer to "Implementing a data front-end" in the MLJ manual.
MLJModelInterface.reformat(model, args...) -> data
Models optionally overload reformat to define transformations of user-supplied data into some model-specific representation (e.g., from a table to a matrix). When implemented, the MLJ user can avoid repeating such transformations unnecessarily, and can additionally make use of more efficient row subsampling, which is then based on the model-specific representation of data, rather than the user-representation. When reformat is overloaded, selectrows(::Model, ...) must be as well (see selectrows). Furthermore, the model fit method(s), and operations, such as predict and transform, must be refactored to act on the model-specific representations of the data.
To implement the reformat data front-end for a model, refer to "Implementing a data front-end" in the MLJ manual.
Select element(s) of a table or matrix at row(s) r and column(s) c. An object of the sink type of X (or a matrix) is returned unless c is a single integer or symbol. In that case a vector is returned, unless r is a single integer, in which case a single element is returned.
Select single or multiple columns from a matrix or table X. If c is an abstract vector of integers or symbols, then the object returned is a table of the preferred sink type of typeof(X). If c is a single integer or column, then an AbstractVector is returned.
Select single or multiple rows from a table, abstract vector or matrix X. If X is tabular, the object returned is a table of the preferred sink type of typeof(X), even if only a single row is selected.
If the object is neither a table, abstract vector or matrix, X is returned and r is ignored.
MLJModelInterface.selectrows(::Model, I, data...) -> sampled_data
A model overloads selectrows whenever it buys into the optional reformat front-end for data preprocessing. See reformat for details. The fallback assumes data is a tuple and calls selectrows(X, I) for each X in data, returning the results in a new tuple of the same length. This call makes sense when X is a table, abstract vector or abstract matrix. In the last two cases, a new object and not a view is returned.
Convert a named tuple of vectors or tuples columntable, into a table of the "preferred sink type" of prototype. This is often the type of prototype itself, when prototype is a sink; see the Tables.jl documentation. If prototype is not specified, then a named tuple of vectors is returned.
Wrap an abstract matrix A as a Tables.jl compatible table with the specified column names (a tuple of symbols). If names are not specified, names=(:x1, :x2, ..., :xn) is used, where n=size(A, 2).
If a prototype is specified, then the matrix is materialized as a table of the preferred sink type of prototype, rather than wrapped. Note that if prototype is not specified, then matrix(table(A)) is essentially a no-op.
If M is an iterative model type which calculates training losses, implement this method to return an AbstractVector of the losses in historical order. If the model calculates scores instead, then the sign of the scores should be reversed.
The following trait overload is also required: MLJModelInterface.supports_training_losses(::Type{<:M}) = true.
Given an MLJType subtype M, the value of this trait should be a tuple of any properties of M to be regarded as "deep".
When two instances of type M are to be tested for equality, in the sense of == or is_same_except, then the values of a "deep" property (whose values are assumed to be of composite type) are deemed to agree if all corresponding properties of those property values are ==.
Any property of M whose values are themselves of MLJType are "deep" automatically, and should not be included in the trait return value.
Select element(s) of a table or matrix at row(s) r and column(s) c. An object of the sink type of X (or a matrix) is returned unless c is a single integer or symbol. In that case a vector is returned, unless r is a single integer, in which case a single element is returned.
Select single or multiple columns from a matrix or table X. If c is an abstract vector of integers or symbols, then the object returned is a table of the preferred sink type of typeof(X). If c is a single integer or column, then an AbstractVector is returned.
Select single or multiple rows from a table, abstract vector or matrix X. If X is tabular, the object returned is a table of the preferred sink type of typeof(X), even if only a single row is selected.
If the object is neither a table, abstract vector or matrix, X is returned and r is ignored.
MLJModelInterface.selectrows(::Model, I, data...) -> sampled_data
A model overloads selectrows whenever it buys into the optional reformat front-end for data preprocessing. See reformat for details. The fallback assumes data is a tuple and calls selectrows(X, I) for each X in data, returning the results in a new tuple of the same length. This call makes sense when X is a table, abstract vector or abstract matrix. In the last two cases, a new object and not a view is returned.
Convert a named tuple of vectors or tuples columntable, into a table of the "preferred sink type" of prototype. This is often the type of prototype itself, when prototype is a sink; see the Tables.jl documentation. If prototype is not specified, then a named tuple of vectors is returned.
Wrap an abstract matrix A as a Tables.jl compatible table with the specified column names (a tuple of symbols). If names are not specified, names=(:x1, :x2, ..., :xn) is used, where n=size(A, 2).
If a prototype is specified, then the matrix is materialized as a table of the preferred sink type of prototype, rather than wrapped. Note that if prototype is not specified, then matrix(table(A)) is essentially a no-op.
If M is an iterative model type which calculates training losses, implement this method to return an AbstractVector of the losses in historical order. If the model calculates scores instead, then the sign of the scores should be reversed.
The following trait overload is also required: MLJModelInterface.supports_training_losses(::Type{<:M}) = true.
Given an MLJType subtype M, the value of this trait should be a tuple of any properties of M to be regarded as "deep".
When two instances of type M are to be tested for equality, in the sense of == or is_same_except, then the values of a "deep" property (whose values are assumed to be of composite type) are deemed to agree if all corresponding properties of those property values are ==.
Any property of M whose values are themselves of MLJType are "deep" automatically, and should not be included in the trait return value.
Build the expression of the keyword constructor associated with a model definition. When the constructor is called, the clean! function is called as well to check that parameter assignments are valid.
Take an expression defining a model (mutable struct Model ...) and unpack key elements for further processing:
Model name (modelname)
Names of parameters (params)
Default values (defaults)
Constraints (constraints)
When no default field value is given a heuristic is to guess an appropriate default (eg, zero for a Float64 parameter). To this end, the specified type expression is evaluated in the module modl.
Internal function to allow to read a constraint given after a default value for a parameter and transform it in an executable condition (which is returned to be executed later). For instance if we have
alpha::Int = 0.5::(arg > 0.0)
Then it would transform the (arg > 0.0) in (alpha > 0.0) which is executable.
Return a string suitable for interpolation in the document string of an MLJ model type. In the example given below, the header expands to something like this:
FooRegressor
A model type for constructing a foo regressor, based on FooRegressorPkg.jl.
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Ordinarily, doc_header is used in document strings defined after the model type definition, as doc_header assumes model traits (in particular, package_name and package_url) to be defined; see also MLJModelInterface.metadata_pkg.
Example
Suppose a model type and traits have been defined by:
mutable struct FooRegressor
+end
Then the mutability of Foo implies Foo(1) != Foo(1) and so, by the definition == for MLJType objects (see is_same_except) we have
Build the expression of the keyword constructor associated with a model definition. When the constructor is called, the clean! function is called as well to check that parameter assignments are valid.
Take an expression defining a model (mutable struct Model ...) and unpack key elements for further processing:
Model name (modelname)
Names of parameters (params)
Default values (defaults)
Constraints (constraints)
When no default field value is given a heuristic is to guess an appropriate default (eg, zero for a Float64 parameter). To this end, the specified type expression is evaluated in the module modl.
Internal function to allow to read a constraint given after a default value for a parameter and transform it in an executable condition (which is returned to be executed later). For instance if we have
alpha::Int = 0.5::(arg > 0.0)
Then it would transform the (arg > 0.0) in (alpha > 0.0) which is executable.
Return a string suitable for interpolation in the document string of an MLJ model type. In the example given below, the header expands to something like this:
FooRegressor
A model type for constructing a foo regressor, based on FooRegressorPkg.jl.
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
Ordinarily, doc_header is used in document strings defined after the model type definition, as doc_header assumes model traits (in particular, package_name and package_url) to be defined; see also MLJModelInterface.metadata_pkg.
Example
Suppose a model type and traits have been defined by:
For models that have a native API with separate documentation, one may want to call doc_header(FooRegressor, augment=true) instead. In that case, the output will look like this:
From MLJ, the FooRegressor type can be imported using
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
For a given model of model type M supporting intrinsic feature importances, calculate the feature importances from the model's fitresult and report as an abstract vector of feature::Symbol => importance::Real pairs (e.g [:gender =>0.23, :height =>0.7, :weight => 0.1]).
New model implementations
The following trait overload is also required: MLJModelInterface.reports_feature_importances(::Type{<:M}) = true
If for some reason a model is sometimes unable to report feature importances then feature_importances should return all importances as 0.0, as in [:gender =>0.0, :height =>0.0, :weight => 0.0].
Deconstruct any Model instance model as a flat named tuple, keyed on property names. Properties of nested model instances are recursively exposed,.as shown in the example below. For most Model objects, properties are synonymous with fields, but this is not a hard requirement.
julia> using MLJModels
+
Variation to augment existing document string
For models that have a native API with separate documentation, one may want to call doc_header(FooRegressor, augment=true) instead. In that case, the output will look like this:
From MLJ, the FooRegressor type can be imported using
Construct an instance with default hyper-parameters using the syntax model = FooRegressor(). Provide keyword arguments to override hyper-parameter defaults, as in FooRegressor(a=...).
For a given model of model type M supporting intrinsic feature importances, calculate the feature importances from the model's fitresult and report as an abstract vector of feature::Symbol => importance::Real pairs (e.g [:gender =>0.23, :height =>0.7, :weight => 0.1]).
New model implementations
The following trait overload is also required: MLJModelInterface.reports_feature_importances(::Type{<:M}) = true
If for some reason a model is sometimes unable to report feature importances then feature_importances should return all importances as 0.0, as in [:gender =>0.0, :height =>0.0, :weight => 0.0].
Deconstruct any Model instance model as a flat named tuple, keyed on property names. Properties of nested model instances are recursively exposed,.as shown in the example below. For most Model objects, properties are synonymous with fields, but this is not a hard requirement.
julia> using MLJModels
julia> using EnsembleModels
julia> tree = (@load DecisionTreeClassifier pkg=DecisionTree)();
@@ -202,4 +202,4 @@
rng = Random._GLOBAL_RNG(),
n = 100,
acceleration = CPU1{Nothing}(nothing),
- out_of_bag_measure = Any[],)
Merge the reports in the dictionary report_given_method into a single property-accessible object. It is supposed that each key of the dictionary is either :fit or the name of an operation, such as :predict or :transform. Each value will be the report component returned by a training method (fit or update) dispatched on the model type, in the case of :fit, or the report component returned by an operation that supports reporting.
New model implementations
Overloading this method is optional, unless the model generates reports that are neither named tuples nor nothing.
Assuming each value in the report_given_method dictionary is either a named tuple or nothing, and there are no conflicts between the keys of the dictionary values (the individual reports), the fallback returns the usual named tuple merge of the dictionary values, ignoring any nothing value. If there is a key conflict, all operation reports are first wrapped in a named tuple of length one, as in (predict=predict_report,). A :fit report is never wrapped.
If any dictionary value is neither a named tuple nor nothing, it is first wrapped as (report=value, ) before merging.
Generates a value for the docstring trait for use with a model which does not have a standard document string, to use as the fallback. See metadata_model.
Merge the reports in the dictionary report_given_method into a single property-accessible object. It is supposed that each key of the dictionary is either :fit or the name of an operation, such as :predict or :transform. Each value will be the report component returned by a training method (fit or update) dispatched on the model type, in the case of :fit, or the report component returned by an operation that supports reporting.
New model implementations
Overloading this method is optional, unless the model generates reports that are neither named tuples nor nothing.
Assuming each value in the report_given_method dictionary is either a named tuple or nothing, and there are no conflicts between the keys of the dictionary values (the individual reports), the fallback returns the usual named tuple merge of the dictionary values, ignoring any nothing value. If there is a key conflict, all operation reports are first wrapped in a named tuple of length one, as in (predict=predict_report,). A :fit report is never wrapped.
If any dictionary value is neither a named tuple nor nothing, it is first wrapped as (report=value, ) before merging.
Generates a value for the docstring trait for use with a model which does not have a standard document string, to use as the fallback. See metadata_model.
The following API is incompatible with versions of MLJBase < 0.20, even for model implementations compatible with MLJModelInterface 1^
This section may be occasionally relevant when wrapping models implemented in languages other than Julia.
The MLJ user can serialize and deserialize machines, as she would any other julia object. (This user has the option of first removing data from the machine. See the Saving machines section of the MLJ manual for details.) However, a problem can occur if a model's fitresult (see The fit method) is not a persistent object. For example, it might be a C pointer that would have no meaning in a new Julia session.
If that is the case a model implementation needs to implement a save and restore method for switching between a fitresult and some persistent, serializable representation of that result.
Implement this method to reconstruct a valid fitresult (as would be returned by MMI.fit) from a persistent representation constructed using MMI.save as described above.
The fallback of restore performs no action and returns serializable_fitresult.
The following API is incompatible with versions of MLJBase < 0.20, even for model implementations compatible with MLJModelInterface 1^
This section may be occasionally relevant when wrapping models implemented in languages other than Julia.
The MLJ user can serialize and deserialize machines, as she would any other julia object. (This user has the option of first removing data from the machine. See the Saving machines section of the MLJ manual for details.) However, a problem can occur if a model's fitresult (see The fit method) is not a persistent object. For example, it might be a C pointer that would have no meaning in a new Julia session.
If that is the case a model implementation needs to implement a save and restore method for switching between a fitresult and some persistent, serializable representation of that result.
Implement this method to reconstruct a valid fitresult (as would be returned by MMI.fit) from a persistent representation constructed using MMI.save as described above.
The fallback of restore performs no action and returns serializable_fitresult.
A model type subtypes Static <: Unsupervised if it does not generalize to new data but nevertheless has hyperparameters. See the Static transformers section of the MLJ manual for examples. In the Static case, transform can have multiple arguments and input_scitype refers to the allowed scitype of the slurped data, even if there is only a single argument. For example, if the signature is transform(static_model, X1, X2), then the allowed input_scitype might be Tuple{Table(Continuous), Table(Continuous)}; if the signature is transform(static_model, X), the allowed input_scitype might be Tuple{Table(Continuous)}. The other traits are as for regular Unsupervised models.
As a static transformer does not implement fit, the usual mechanism for creating a report is not available. Instead, byproducts of the computation performed by transform can be returned by transform itself by returning a pair (output, report) instead of just output. Here report should be a named tuple. In fact, any operation, (e.g., predict) can do this for any model type. However, this exceptional behavior must be flagged with an appropriate trait declaration, as in
If mach is a machine wrapping a model of this kind, then the report(mach) will include the report item form transform's output. For sample implementations, see this issue or the code for DBSCAN clustering.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
A model type subtypes Static <: Unsupervised if it does not generalize to new data but nevertheless has hyperparameters. See the Static transformers section of the MLJ manual for examples. In the Static case, transform can have multiple arguments and input_scitype refers to the allowed scitype of the slurped data, even if there is only a single argument. For example, if the signature is transform(static_model, X1, X2), then the allowed input_scitype might be Tuple{Table(Continuous), Table(Continuous)}; if the signature is transform(static_model, X), the allowed input_scitype might be Tuple{Table(Continuous)}. The other traits are as for regular Unsupervised models.
As a static transformer does not implement fit, the usual mechanism for creating a report is not available. Instead, byproducts of the computation performed by transform can be returned by transform itself by returning a pair (output, report) instead of just output. Here report should be a named tuple. In fact, any operation, (e.g., predict) can do this for any model type. However, this exceptional behavior must be flagged with an appropriate trait declaration, as in
If mach is a machine wrapping a model of this kind, then the report(mach) will include the report item form transform's output. For sample implementations, see this issue or the code for DBSCAN clustering.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/summary_of_methods/index.html b/dev/summary_of_methods/index.html
index d0c987c..4fa3c46 100644
--- a/dev/summary_of_methods/index.html
+++ b/dev/summary_of_methods/index.html
@@ -1,5 +1,5 @@
-Summary of methods · MLJModelInterface
Optionally, an implementation may add a data front-end, for transforming user data (such as a table) into some model-specific format (such as a matrix), and/or add methods to specify how reformatted data is resampled. This alters the interpretation of the data arguments of fit, update and predict, whose number may also change. See Implementing a data front-end for details). A data front-end provides the MLJ user certain performance advantages when retraining a machine.
Third-party packages that interact directly with models using the MLJModelInterface.jl API, rather than through the machine interface, will also need to understand how the data front-end works, so they incorporate reformat into their fit/update/predict calls. See also this issue.
MLJModelInterface.reformat(model::SomeSupervisedModel, args...) = args
-MLJModelInterface.selectrows(model::SomeSupervisedModel, I, data...) = data
Optionally, to customized support for serialization of machines (see Serialization), overload
At present, MLJ's performance estimate functionality (resampling using evaluate/evaluate!) tacitly assumes that feature-label pairs of observations (X1, y1), (X2, y2), (X2, y2), ... are being modelled as identically independent random variables (i.i.d.), and constructs some kind of representation of an estimate of the conditional probability p(y | X) (y and Xsingle observations). It may be that a model implementing the MLJ interface has the potential to make predictions under weaker assumptions (e.g., time series forecasting models). However the output of the compulsory predict method described below should be the output of the model under the i.i.d assumption.
In the future, newer methods may be introduced to handle weaker assumptions (see, e.g., The predict_joint method below).
The following sections were written with Supervised models in mind, but also cover material relevant to general models:
At present, MLJ's performance estimate functionality (resampling using evaluate/evaluate!) tacitly assumes that feature-label pairs of observations (X1, y1), (X2, y2), (X2, y2), ... are being modelled as identically independent random variables (i.i.d.), and constructs some kind of representation of an estimate of the conditional probability p(y | X) (y and Xsingle observations). It may be that a model implementing the MLJ interface has the potential to make predictions under weaker assumptions (e.g., time series forecasting models). However the output of the compulsory predict method described below should be the output of the model under the i.i.d assumption.
In the future, newer methods may be introduced to handle weaker assumptions (see, e.g., The predict_joint method below).
The following sections were written with Supervised models in mind, but also cover material relevant to general models:
A supervised model may optionally implement a transform method, whose signature is the same as predict. In that case, the implementation should define a value for the output_scitype trait. A declaration
output_scitype(::Type{<:SomeSupervisedModel}) = T
is an assurance that scitype(transform(model, fitresult, Xnew)) <: T always holds, for any model of type SomeSupervisedModel.
A use-case for a transform method for a supervised model is a neural network that learns feature embeddings for categorical input features as part of overall training. Such a model becomes a transformer that other supervised models can use to transform the categorical features (instead of applying the higher-dimensional one-hot encoding representations).
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+Supervised models with a transform method · MLJModelInterface
A supervised model may optionally implement a transform method, whose signature is the same as predict. In that case, the implementation should define a value for the output_scitype trait. A declaration
output_scitype(::Type{<:SomeSupervisedModel}) = T
is an assurance that scitype(transform(model, fitresult, Xnew)) <: T always holds, for any model of type SomeSupervisedModel.
A use-case for a transform method for a supervised model is a neural network that learns feature embeddings for categorical input features as part of overall training. Such a model becomes a transformer that other supervised models can use to transform the categorical features (instead of applying the higher-dimensional one-hot encoding representations).
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
diff --git a/dev/the_fit_method/index.html b/dev/the_fit_method/index.html
index fc6cd3b..afd3b8d 100644
--- a/dev/the_fit_method/index.html
+++ b/dev/the_fit_method/index.html
@@ -1,2 +1,2 @@
-The fit method · MLJModelInterface
fitresult is the fitresult in the sense above (which becomes an argument for predict discussed below).
report is a (possibly empty) NamedTuple, for example, report=(deviance=..., dof_residual=..., stderror=..., vcov=...). Any training-related statistics, such as internal estimates of the generalization error, and feature rankings, should be returned in the report tuple. How, or if, these are generated should be controlled by hyperparameters (the fields of model). Fitted parameters, such as the coefficients of a linear model, do not go in the report as they will be extractable from fitresult (and accessible to MLJ through the fitted_params method described below).
The value of cache can be nothing, unless one is also defining an update method (see below). The Julia type of cache is not presently restricted.
Note
The fit (and update) methods should not mutate the model. If necessary, fit can create a deepcopy of model first.
It is not necessary for fit to provide type or dimension checks on X or y or to call clean! on the model; MLJ will carry out such checks.
The types of X and y are constrained by the input_scitype and target_scitype trait declarations; see Trait declarations below. (That is, unless a data front-end is implemented, in which case these traits refer instead to the arguments of the overloaded reformat method, and the types of X and y are determined by the output of reformat.)
The method fit should never alter hyperparameter values, the sole exception being fields of type <:AbstractRNG. If the package is able to suggest better hyperparameters, as a byproduct of training, return these in the report field.
The verbosity level (0 for silent) is for passing to the learning algorithm itself. A fit method wrapping such an algorithm should generally avoid doing any of its own logging.
Sample weight support. If supports_weights(::Type{<:SomeSupervisedModel}) has been declared true, then one instead implements the following variation on the above fit:
MMI.fit(model::SomeSupervisedModel, verbosity, X, y, w=nothing) -> fitresult, cache, report
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
fitresult is the fitresult in the sense above (which becomes an argument for predict discussed below).
report is a (possibly empty) NamedTuple, for example, report=(deviance=..., dof_residual=..., stderror=..., vcov=...). Any training-related statistics, such as internal estimates of the generalization error, and feature rankings, should be returned in the report tuple. How, or if, these are generated should be controlled by hyperparameters (the fields of model). Fitted parameters, such as the coefficients of a linear model, do not go in the report as they will be extractable from fitresult (and accessible to MLJ through the fitted_params method described below).
The value of cache can be nothing, unless one is also defining an update method (see below). The Julia type of cache is not presently restricted.
Note
The fit (and update) methods should not mutate the model. If necessary, fit can create a deepcopy of model first.
It is not necessary for fit to provide type or dimension checks on X or y or to call clean! on the model; MLJ will carry out such checks.
The types of X and y are constrained by the input_scitype and target_scitype trait declarations; see Trait declarations below. (That is, unless a data front-end is implemented, in which case these traits refer instead to the arguments of the overloaded reformat method, and the types of X and y are determined by the output of reformat.)
The method fit should never alter hyperparameter values, the sole exception being fields of type <:AbstractRNG. If the package is able to suggest better hyperparameters, as a byproduct of training, return these in the report field.
The verbosity level (0 for silent) is for passing to the learning algorithm itself. A fit method wrapping such an algorithm should generally avoid doing any of its own logging.
Sample weight support. If supports_weights(::Type{<:SomeSupervisedModel}) has been declared true, then one instead implements the following variation on the above fit:
MMI.fit(model::SomeSupervisedModel, verbosity, X, y, w=nothing) -> fitresult, cache, report
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
A fitted_params method may be optionally overloaded. Its purpose is to provide MLJ access to a user-friendly representation of the learned parameters of the model (as opposed to the hyperparameters). They must be extractable from fitresult.
A fitted_params method may be optionally overloaded. Its purpose is to provide MLJ access to a user-friendly representation of the learned parameters of the model (as opposed to the hyperparameters). They must be extractable from fitresult.
A model is an object storing hyperparameters associated with some machine learning algorithm, and that is all. In MLJ, hyperparameters include configuration parameters, like the number of threads, and special instructions, such as "compute feature rankings", which may or may not affect the final learning outcome. However, the logging level (verbosity below) is excluded. Learned parameters (such as the coefficients in a linear model) have no place in the model struct.
The name of the Julia type associated with a model indicates the associated algorithm (e.g., DecisionTreeClassifier). The outcome of training a learning algorithm is called a fitresult. For ordinary multivariate regression, for example, this would be the coefficients and intercept. For a general supervised model, it is the (generally minimal) information needed to make new predictions.
The ultimate supertype of all models is MLJModelInterface.Model, which has two abstract subtypes:
abstract type Supervised <: Model end
+The model type hierarchy · MLJModelInterface
A model is an object storing hyperparameters associated with some machine learning algorithm, and that is all. In MLJ, hyperparameters include configuration parameters, like the number of threads, and special instructions, such as "compute feature rankings", which may or may not affect the final learning outcome. However, the logging level (verbosity below) is excluded. Learned parameters (such as the coefficients in a linear model) have no place in the model struct.
The name of the Julia type associated with a model indicates the associated algorithm (e.g., DecisionTreeClassifier). The outcome of training a learning algorithm is called a fitresult. For ordinary multivariate regression, for example, this would be the coefficients and intercept. For a general supervised model, it is the (generally minimal) information needed to make new predictions.
The ultimate supertype of all models is MLJModelInterface.Model, which has two abstract subtypes:
abstract type Supervised <: Model end
abstract type Unsupervised <: Model end
Supervised models are further divided according to whether they are able to furnish probabilistic predictions of the target (which they will then do by default) or directly predict "point" estimates, for each new input pattern:
abstract type Probabilistic <: Supervised end
-abstract type Deterministic <: Supervised end
Associated with every concrete subtype of Model there must be a fit method, which implements the associated algorithm to produce the fitresult. Additionally, every Supervised model has a predict method, while Unsupervised models must have a transform method. More generally, methods such as these, that are dispatched on a model instance and a fitresult (plus other data), are called operations. Probabilistic supervised models optionally implement a predict_mode operation (in the case of classifiers) or a predict_mean and/or predict_median operations (in the case of regressors) although MLJModelInterface also provides fallbacks that will suffice in most cases. Unsupervised models may implement an inverse_transform operation.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
Associated with every concrete subtype of Model there must be a fit method, which implements the associated algorithm to produce the fitresult. Additionally, every Supervised model has a predict method, while Unsupervised models must have a transform method. More generally, methods such as these, that are dispatched on a model instance and a fitresult (plus other data), are called operations. Probabilistic supervised models optionally implement a predict_mode operation (in the case of classifiers) or a predict_mean and/or predict_median operations (in the case of regressors) although MLJModelInterface also provides fallbacks that will suffice in most cases. Unsupervised models may implement an inverse_transform operation.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
Any Probabilistic model type SomeModelmay optionally implement a predict_joint method, which has the same signature as predict, but whose predictions are a single distribution (rather than a vector of per-observation distributions).
Specifically, the output yhat of predict_joint should be an instance of Distributions.Sampleable{<:Multivariate,V}, where scitype(V) = target_scitype(SomeModel) and samples have length n, where n is the number of observations in Xnew.
If a new model type subtypes JointProbabilistic <: Probabilistic then implementation of predict_joint is compulsory.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
Any Probabilistic model type SomeModelmay optionally implement a predict_joint method, which has the same signature as predict, but whose predictions are a single distribution (rather than a vector of per-observation distributions).
Specifically, the output yhat of predict_joint should be an instance of Distributions.Sampleable{<:Multivariate,V}, where scitype(V) = target_scitype(SomeModel) and samples have length n, where n is the number of observations in Xnew.
If a new model type subtypes JointProbabilistic <: Probabilistic then implementation of predict_joint is compulsory.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
Here Xnew will have the same form as the X passed to fit.
Note that while Xnew generally consists of multiple observations (e.g., has multiple rows in the case of a table) it is assumed, in view of the i.i.d assumption recalled above, that calling predict(..., Xnew) is equivalent to broadcasting some method predict_one(..., x) over the individual observations x in Xnew (a method implementing the probability distribution p(X |y) above).
In the case of Deterministic models, yhat should have the same scitype as the y passed to fit (see above). If y is a CategoricalVector (classification) then elements of the prediction yhatmust have a pool == to the pool of the target y presented in training, even if not all levels appear in the training data or prediction itself.
Unfortunately, code not written with the preservation of categorical levels in mind poses special problems. To help with this, MLJModelInterface provides some utilities: MLJModelInterface.int (for converting a CategoricalValue into an integer, the ordering of these integers being consistent with that of the pool) and MLJModelInterface.decoder (for constructing a callable object that decodes the integers back into CategoricalValue objects). Refer to Convenience methods below for important details.
Note that a decoder created during fit may need to be bundled with fitresult to make it available to predict during re-encoding. So, for example, if the core algorithm being wrapped by fit expects a nominal target yint of type Vector{<:Integer} then a fit method may look something like this:
function MMI.fit(model::SomeSupervisedModel, verbosity, X, y)
+The predict method · MLJModelInterface
Here Xnew will have the same form as the X passed to fit.
Note that while Xnew generally consists of multiple observations (e.g., has multiple rows in the case of a table) it is assumed, in view of the i.i.d assumption recalled above, that calling predict(..., Xnew) is equivalent to broadcasting some method predict_one(..., x) over the individual observations x in Xnew (a method implementing the probability distribution p(X |y) above).
In the case of Deterministic models, yhat should have the same scitype as the y passed to fit (see above). If y is a CategoricalVector (classification) then elements of the prediction yhatmust have a pool == to the pool of the target y presented in training, even if not all levels appear in the training data or prediction itself.
Unfortunately, code not written with the preservation of categorical levels in mind poses special problems. To help with this, MLJModelInterface provides some utilities: MLJModelInterface.int (for converting a CategoricalValue into an integer, the ordering of these integers being consistent with that of the pool) and MLJModelInterface.decoder (for constructing a callable object that decodes the integers back into CategoricalValue objects). Refer to Convenience methods below for important details.
Note that a decoder created during fit may need to be bundled with fitresult to make it available to predict during re-encoding. So, for example, if the core algorithm being wrapped by fit expects a nominal target yint of type Vector{<:Integer} then a fit method may look something like this:
function MMI.fit(model::SomeSupervisedModel, verbosity, X, y)
yint = MMI.int(y)
a_target_element = y[1] # a CategoricalValue/String
decode = MMI.decoder(a_target_element) # can be called on integers
@@ -18,4 +18,4 @@
y = ybig[1:6]
Your fit method has bundled the first element of y with the fitresult to make it available to predict for purposes of tracking the complete pool of classes. Let's call this an_element = y[1]. Then, supposing the corresponding probabilities of the observed classes [:a, :b] are in an n x 2 matrix probs (where n the number of rows of Xnew) then you return
This object automatically assigns zero-probability to the unseen class :rare (i.e., pdf.(yhat, :rare) works and returns a zero vector). If you would like to assign :rare non-zero probabilities, simply add it to the first vector (the support) and supply a larger probs matrix.
In a binary classification problem, it suffices to specify a single vector of probabilities, provided you specify augment=true, as in the following example, and note carefully that these probabilities are associated with thelast(second) class you specify in the constructor:
Important note on binary classifiers. There is no "Binary" scitype distinct from Multiclass{2} or OrderedFactor{2}; Binary is just an alias for Union{Multiclass{2},OrderedFactor{2}}. The target_scitype of a binary classifier will generally be AbstractVector{<:Binary} and according to the mlj scitype convention, elements of y have type CategoricalValue, and notBool. See BinaryClassifier for an example.
A predict method, or other operation such as transform, can contribute to the report accessible in any machine associated with a model. See Reporting byproducts of a static transformation below for details.
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
Important note on binary classifiers. There is no "Binary" scitype distinct from Multiclass{2} or OrderedFactor{2}; Binary is just an alias for Union{Multiclass{2},OrderedFactor{2}}. The target_scitype of a binary classifier will generally be AbstractVector{<:Binary} and according to the mlj scitype convention, elements of y have type CategoricalValue, and notBool. See BinaryClassifier for an example.
A predict method, or other operation such as transform, can contribute to the report accessible in any machine associated with a model. See Reporting byproducts of a static transformation below for details.
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.
If M is an iterative model type which calculates training losses, implement this method to return an AbstractVector of the losses in historical order. If the model calculates scores instead, then the sign of the scores should be reversed.
The following trait overload is also required: MLJModelInterface.supports_training_losses(::Type{<:M}) = true.
If M is an iterative model type which calculates training losses, implement this method to return an AbstractVector of the losses in historical order. If the model calculates scores instead, then the sign of the scores should be reversed.
The following trait overload is also required: MLJModelInterface.supports_training_losses(::Type{<:M}) = true.
Two trait functions allow the implementer to restrict the types of data X, y and Xnew discussed above. The MLJ task interface uses these traits for data type checks but also for model search. If they are omitted (and your model is registered) then a general user may attempt to use your model with inappropriately typed data.
The trait functions input_scitype and target_scitype take scientific data types as values. We assume here familiarity with ScientificTypes.jl (see Getting Started for the basics).
For example, to ensure that the X presented to the DecisionTreeClassifierfit method is a table whose columns all have Continuous element type (and hence AbstractFloat machine type), one declares
If, instead, columns were allowed to have either: (i) a mixture of Continuous and Missing values, or (ii) Count (i.e., integer) values, then the declaration would be
The trait functions controlling the form of data are summarized as follows:
method
return type
declarable return values
fallback value
input_scitype
Type
some scientific type
Unknown
target_scitype
Type
some scientific type
Unknown
Additional trait functions tell MLJ's @load macro how to find your model if it is registered, and provide other self-explanatory metadata about the model:
method
return type
declarable return values
fallback value
load_path
String
unrestricted
"unknown"
package_name
String
unrestricted
"unknown"
package_uuid
String
unrestricted
"unknown"
package_url
String
unrestricted
"unknown"
package_license
String
unrestricted
"unknown"
is_pure_julia
Bool
true or false
false
supports_weights
Bool
true or false
false
supports_class_weights
Bool
true or false
false
supports_training_losses
Bool
true or false
false
reports_feature_importances
Bool
true or false
false
Here is the complete list of trait function declarations for DecisionTreeClassifier, whose core algorithms are provided by DecisionTree.jl, but whose interface actually lives at MLJDecisionTreeInterface.jl.
Two trait functions allow the implementer to restrict the types of data X, y and Xnew discussed above. The MLJ task interface uses these traits for data type checks but also for model search. If they are omitted (and your model is registered) then a general user may attempt to use your model with inappropriately typed data.
The trait functions input_scitype and target_scitype take scientific data types as values. We assume here familiarity with ScientificTypes.jl (see Getting Started for the basics).
For example, to ensure that the X presented to the DecisionTreeClassifierfit method is a table whose columns all have Continuous element type (and hence AbstractFloat machine type), one declares
If, instead, columns were allowed to have either: (i) a mixture of Continuous and Missing values, or (ii) Count (i.e., integer) values, then the declaration would be
The trait functions controlling the form of data are summarized as follows:
method
return type
declarable return values
fallback value
input_scitype
Type
some scientific type
Unknown
target_scitype
Type
some scientific type
Unknown
Additional trait functions tell MLJ's @load macro how to find your model if it is registered, and provide other self-explanatory metadata about the model:
method
return type
declarable return values
fallback value
load_path
String
unrestricted
"unknown"
package_name
String
unrestricted
"unknown"
package_uuid
String
unrestricted
"unknown"
package_url
String
unrestricted
"unknown"
package_license
String
unrestricted
"unknown"
is_pure_julia
Bool
true or false
false
supports_weights
Bool
true or false
false
supports_class_weights
Bool
true or false
false
supports_training_losses
Bool
true or false
false
reports_feature_importances
Bool
true or false
false
Here is the complete list of trait function declarations for DecisionTreeClassifier, whose core algorithms are provided by DecisionTree.jl, but whose interface actually lives at MLJDecisionTreeInterface.jl.
Unsupervised models implement the MLJ model interface in a very similar fashion. The main differences are:
The fit method, which still returns (fitresult, cache, report) will typically have only one training argument X, as in MLJModelInterface.fit(model, verbosity, X), although this is not a hard requirement. For example, a feature selection tool (wrapping some supervised model) might also include a target y as input. Furthermore, in the case of models that subtype Static <: Unsupervised (see Static models) fit has no training arguments at all, but does not need to be implemented as a fallback returns (nothing, nothing, nothing).
A transform and/or predict method is implemented, and has the same signature as predict does in the supervised case, as in MLJModelInterface.transform(model, fitresult, Xnew). However, it may only have one data argument Xnew, unless model <: Static, in which case there is no restriction. A use-case for predict is K-means clustering that predicts labels and transforms input features into a space of lower dimension. See the Transformers that also predict section of the MLJ manual for an example.
The target_scitype refers to the output of predict, if implemented. A new trait, output_scitype, is for the output of transform. Unless the model is Static (see Static models) the trait input_scitype is for the single data argument of transform (and predict, if implemented). If fit has more than one data argument, you must overload the trait fit_data_scitype, which bounds the allowed data passed to fit(model, verbosity, data...) and will always be a Tuple type.
An inverse_transform can be optionally implemented. The signature is the same as transform, as in MLJModelInterface.inverse_transform(model, fitresult, Xout), which:
must make sense for any Xout for which scitype(Xout) <: output_scitype(SomeSupervisedModel) (see below); and
must return an object Xin satisfying scitype(Xin) <: input_scitype(SomeSupervisedModel).
Unsupervised models implement the MLJ model interface in a very similar fashion. The main differences are:
The fit method, which still returns (fitresult, cache, report) will typically have only one training argument X, as in MLJModelInterface.fit(model, verbosity, X), although this is not a hard requirement. For example, a feature selection tool (wrapping some supervised model) might also include a target y as input. Furthermore, in the case of models that subtype Static <: Unsupervised (see Static models) fit has no training arguments at all, but does not need to be implemented as a fallback returns (nothing, nothing, nothing).
A transform and/or predict method is implemented, and has the same signature as predict does in the supervised case, as in MLJModelInterface.transform(model, fitresult, Xnew). However, it may only have one data argument Xnew, unless model <: Static, in which case there is no restriction. A use-case for predict is K-means clustering that predicts labels and transforms input features into a space of lower dimension. See the Transformers that also predict section of the MLJ manual for an example.
The target_scitype refers to the output of predict, if implemented. A new trait, output_scitype, is for the output of transform. Unless the model is Static (see Static models) the trait input_scitype is for the single data argument of transform (and predict, if implemented). If fit has more than one data argument, you must overload the trait fit_data_scitype, which bounds the allowed data passed to fit(model, verbosity, data...) and will always be a Tuple type.
An inverse_transform can be optionally implemented. The signature is the same as transform, as in MLJModelInterface.inverse_transform(model, fitresult, Xout), which:
must make sense for any Xout for which scitype(Xout) <: output_scitype(SomeSupervisedModel) (see below); and
must return an object Xin satisfying scitype(Xin) <: input_scitype(SomeSupervisedModel).
Note that different packages can implement models having the same name without causing conflicts, although an MLJ user cannot simultaneously load two such models.
There are two options for making a new model implementation available to all MLJ users:
Native implementations (preferred option). The implementation code lives in the same package that contains the learning algorithms implementing the interface. An example is EvoTrees.jl. In this case, it is sufficient to open an issue at MLJ requesting the package to be registered with MLJ. Registering a package allows the MLJ user to access its models' metadata and to selectively load them.
Separate interface package. Implementation code lives in a separate interface package, which has the algorithm-providing package as a dependency. See the template repository MLJExampleInterface.jl.
Additionally, one needs to ensure that the implementation code defines the package_name and load_path model traits appropriately, so that MLJ's @load macro can find the necessary code (see MLJModels/src for examples).
Settings
This document was generated with Documenter.jl version 1.4.1 on Monday 3 June 2024. Using Julia version 1.10.3.
+Where to place code implementing new models · MLJModelInterface
Note that different packages can implement models having the same name without causing conflicts, although an MLJ user cannot simultaneously load two such models.
There are two options for making a new model implementation available to all MLJ users:
Native implementations (preferred option). The implementation code lives in the same package that contains the learning algorithms implementing the interface. An example is EvoTrees.jl. In this case, it is sufficient to open an issue at MLJ requesting the package to be registered with MLJ. Registering a package allows the MLJ user to access its models' metadata and to selectively load them.
Separate interface package. Implementation code lives in a separate interface package, which has the algorithm-providing package as a dependency. See the template repository MLJExampleInterface.jl.
Additionally, one needs to ensure that the implementation code defines the package_name and load_path model traits appropriately, so that MLJ's @load macro can find the necessary code (see MLJModels/src for examples).
Settings
This document was generated with Documenter.jl version 1.5.0 on Thursday 27 June 2024. Using Julia version 1.10.4.