Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docstrings and markdown docs #198

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions docs/src/document_strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,39 @@ Your document string must include the following components, in order:
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 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.
- 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`.
- 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 *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.
- 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.)
- 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).

Expand Down
36 changes: 20 additions & 16 deletions docs/src/implementing_a_data_front_end.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,34 @@ 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)
```julia
fit(model::SomeSupervised, verbosity, X, y)
fit(model::SomeSupervised, verbosity, X, y, w)

predict(model::SomeSupervised, fitresult, Xnew)
predict(model::SomeSupervised, fitresult, Xnew)
```

Without a data front-end implemented, suppose `X` is expected to be a
table and `y` a vector, but suppose the core algorithm always converts
`X` to a matrix with features as rows (each record corresponds to
a column in the table). Then a new data-front end might look like
this:

constant MMI = MLJModelInterface

# for fit:
MMI.reformat(::SomeSupervised, X, y) = (MMI.matrix(X)', y)
MMI.reformat(::SomeSupervised, X, y, w) = (MMI.matrix(X)', y, w)
MMI.selectrows(::SomeSupervised, I, Xmatrix, y) =
(view(Xmatrix, :, I), view(y, I))
MMI.selectrows(::SomeSupervised, I, Xmatrix, y, w) =
(view(Xmatrix, :, I), view(y, I), view(w, I))

# for predict:
MMI.reformat(::SomeSupervised, X) = (MMI.matrix(X)',)
MMI.selectrows(::SomeSupervised, I, Xmatrix) = (view(Xmatrix, :, I),)
```julia
constant MMI = MLJModelInterface

# for fit:
MMI.reformat(::SomeSupervised, X, y) = (MMI.matrix(X)', y)
MMI.reformat(::SomeSupervised, X, y, w) = (MMI.matrix(X)', y, w)
MMI.selectrows(::SomeSupervised, I, Xmatrix, y) =
(view(Xmatrix, :, I), view(y, I))
MMI.selectrows(::SomeSupervised, I, Xmatrix, y, w) =
(view(Xmatrix, :, I), view(y, I), view(w, I))

# 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.
15 changes: 7 additions & 8 deletions docs/src/quick_start_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ 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](https://github.com/JuliaAI/MLJBase.jl/issues/68)
Please see [this issue](https://github.com/JuliaAI/MLJBase.jl/issues/68)
for a known issue and workaround relating to the use of `@mlj_model`
with negative defaults.

Expand Down Expand Up @@ -201,7 +200,7 @@ For a classifier, the steps are fairly similar to a regressor with these differe
1. `y` will be a categorical vector and you will typically want to use
the integer encoding of `y` instead of `CategoricalValue`s; use
`MLJModelInterface.int` for this.
1. You will need to pass the full pool of target labels (not just
2. You will need to pass the full pool of target labels (not just
those observed in the training data) and additionally, in the
`Deterministic` case, the encoding, to make these available to
`predict`. A simple way to do this is to pass `y[1]` in the
Expand All @@ -210,19 +209,19 @@ For a classifier, the steps are fairly similar to a regressor with these differe
method for recovering categorical elements from their integer
representations (e.g., `d(2)` is the categorical element with `2`
as encoding).
2. In the case of a *probabilistic* classifier you should pass all
3. In the case of a *probabilistic* classifier you should pass all
probabilities simultaneously to the [`UnivariateFinite`](@ref) constructor
to get an abstract `UnivariateFinite` vector (type
`UnivariateFiniteArray`) rather than use comprehension or
broadcasting to get a vanilla vector. This is for performance
reasons.

If implementing a classifier, you should probably consult the more
detailed instructions at [The predict method](@ref).

**Examples**:

- GLM's [BinaryClassifier](https://github.com/JuliaAI/MLJModels.jl/blob/3687491b132be8493b6f7a322aedf66008caaab1/src/GLM.jl#L119-L131) (`Probabilistic`)
- GLM's [BinaryClassifier](https://github.com/JuliaAI/MLJModels.jl/blob/3687491b132be8493b6f7a322aedf66008caaab1/src/GLM.jl#L119-L131) (`Probabilistic`)

- LIBSVM's [SVC](https://github.com/JuliaAI/MLJModels.jl/blob/master/src/LIBSVM.jl) (`Deterministic`)

Expand Down Expand Up @@ -349,8 +348,8 @@ MLJModelInterface.metadata_model(YourModel1,
output_scitype = MLJModelInterface.Table(MLJModelInterface.Continuous), # for an unsupervised, what output?
supports_weights = false, # does the model support sample weights?
descr = "A short description of your model"
load_path = "YourPackage.SubModuleContainingModelStructDefinition.YourModel1"
)
load_path = "YourPackage.SubModuleContainingModelStructDefinition.YourModel1"
)
```

*Important.* Do not omit the `load_path` specification. Without a
Expand Down
6 changes: 3 additions & 3 deletions docs/src/summary_of_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ Optional, if `SomeSupervisedModel <: Probabilistic`:

```julia
MMI.predict_mode(model::SomeSupervisedModel, fitresult, Xnew) =
mode.(predict(model, fitresult, Xnew))
mode.(predict(model, fitresult, Xnew))
MMI.predict_mean(model::SomeSupervisedModel, fitresult, Xnew) =
mean.(predict(model, fitresult, Xnew))
mean.(predict(model, fitresult, Xnew))
MMI.predict_median(model::SomeSupervisedModel, fitresult, Xnew) =
median.(predict(model, fitresult, Xnew))
median.(predict(model, fitresult, Xnew))
```

Required, if the model is to be registered (findable by general users):
Expand Down
18 changes: 9 additions & 9 deletions docs/src/supervised_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ The following sections were written with `Supervised` models in mind, but also c
material relevant to general models:

- [Summary of methods](@ref)
- [The form of data for fitting and predicting](@ref)
- [The form of data for fitting and predicting](@ref)
- [The fit method](@ref)
- [The fitted_params method](@ref)
- [The predict method](@ref)
- [The predict_joint method](@ref)
- [Training losses](@ref)
- [Feature importances](@ref)
- [Trait declarations](@ref)
- [Iterative models and the update! method](@ref)
- [Implementing a data front end](@ref)
- [Supervised models with a transform method](@ref)
- [The predict method](@ref)
- [The predict_joint method](@ref)
- [Training losses](@ref)
- [Feature importances](@ref)
- [Trait declarations](@ref)
- [Iterative models and the update! method](@ref)
- [Implementing a data front end](@ref)
- [Supervised models with a transform method](@ref)
- [Models that learn a probability distribution](@ref)
26 changes: 13 additions & 13 deletions docs/src/the_fit_method.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ MMI.fit(model::SomeSupervisedModel, verbosity, X, y) -> fitresult, cache, report
```

1. `fitresult` is the fitresult in the sense above (which becomes an
argument for `predict` discussed below).
argument for `predict` discussed below).

2. `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).

3. 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.
`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).

3. 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

Expand Down
29 changes: 14 additions & 15 deletions docs/src/the_predict_method.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ A compulsory `predict` method has the form
MMI.predict(model::SomeSupervisedModel, fitresult, Xnew) -> yhat
```

Here `Xnew` will have the same form as the `X` passed to
`fit`.
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
Expand Down Expand Up @@ -44,26 +43,26 @@ may look something like this:

```julia
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
yint = MMI.int(y)
a_target_element = y[1] # a CategoricalValue/String
decode = MMI.decoder(a_target_element) # can be called on integers

core_fitresult = SomePackage.fit(X, yint, verbosity=verbosity)
core_fitresult = SomePackage.fit(X, yint, verbosity=verbosity)

fitresult = (decode, core_fitresult)
cache = nothing
report = nothing
return fitresult, cache, report
fitresult = (decode, core_fitresult)
cache = nothing
report = nothing
return fitresult, cache, report
end
```

while a corresponding deterministic `predict` operation might look like this:

```julia
function MMI.predict(model::SomeSupervisedModel, fitresult, Xnew)
decode, core_fitresult = fitresult
yhat = SomePackage.predict(core_fitresult, Xnew)
return decode.(yhat)
decode, core_fitresult = fitresult
yhat = SomePackage.predict(core_fitresult, Xnew)
return decode.(yhat)
end
```

Expand Down Expand Up @@ -155,8 +154,8 @@ yhat = MLJModelInterface.UnivariateFinite([:FALSE, :TRUE], probs, augment=true,
```

The constructor has a lot of options, including passing a dictionary
instead of vectors. See
`CategoricalDistributions.UnivariateFinite`](@ref) for details.
instead of vectors. See [`CategoricalDistributions.UnivariateFinite`](@ref)
for details.

See
[LinearBinaryClassifier](https://github.com/JuliaAI/MLJModels.jl/blob/master/src/GLM.jl)
Expand Down
3 changes: 1 addition & 2 deletions docs/src/trait_declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ MMI.input_scitype(::Type{<:DecisionTreeClassifier}) = Table(Continuous)
```

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
values, or (ii) `Count` (i.e., integer) values, then the declaration would be

```julia
MMI.input_scitype(::Type{<:DecisionTreeClassifier}) = Table(Union{Continuous,Missing},Count)
Expand Down
42 changes: 21 additions & 21 deletions docs/src/type_declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,32 @@ import MLJModelInterface
const MMI = MLJModelInterface

mutable struct RidgeRegressor <: MMI.Deterministic
lambda::Float64
lambda::Float64
end
```

Models (which are mutable) should not be given internal
constructors. It is recommended that they be given an external lazy
keyword constructor of the same name. This constructor defines default values
for every field, and optionally corrects invalid field values by calling a
`clean!` method (whose fallback returns an empty message string):
Models (which are mutable) should not be given internal constructors.
It is recommended that they be given an external lazy keyword constructor
of the same name. This constructor defines default values for every field,
and optionally corrects invalid field values by calling a `clean!`
method (whose fallback returns an empty message string):

```julia
function MMI.clean!(model::RidgeRegressor)
warning = ""
if model.lambda < 0
warning *= "Need lambda ≥ 0. Resetting lambda=0. "
model.lambda = 0
end
return warning
warning = ""
if model.lambda < 0
warning *= "Need lambda ≥ 0. Resetting lambda=0. "
model.lambda = 0
end
return warning
end

# keyword constructor
function RidgeRegressor(; lambda=0.0)
model = RidgeRegressor(lambda)
message = MMI.clean!(model)
isempty(message) || @warn message
return model
model = RidgeRegressor(lambda)
message = MMI.clean!(model)
isempty(message) || @warn message
return model
end
```

Expand Down Expand Up @@ -96,8 +96,8 @@ following example:

```julia
@mlj_model mutable struct YourModel <: MMI.Deterministic
a::Float64 = 0.5::(_ > 0)
b::String = "svd"::(_ in ("svd","qr"))
a::Float64 = 0.5::(_ > 0)
b::String = "svd"::(_ in ("svd","qr"))
end
```

Expand All @@ -115,22 +115,22 @@ expects its value to be positive.
You cannot use the `@mlj_model` macro if your model struct has type
parameters.

#### Known issue with @mlj_macro
#### Known issue with `@mlj_macro`

Defaults with negative values can trip up the `@mlj_macro` (see [this
issue](https://github.com/JuliaAI/MLJBase.jl/issues/68)). So,
for example, this does not work:

```julia
@mlj_model mutable struct Bar
a::Int = -1::(_ > -2)
a::Int = -1::(_ > -2)
end
```

But this does:

```julia
@mlj_model mutable struct Bar
a::Int = (-)(1)::(_ > -2)
a::Int = (-)(1)::(_ > -2)
end
```
4 changes: 2 additions & 2 deletions docs/src/unsupervised_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ similar fashion. The main differences are:
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
output_scitype(SomeSupervisedModel)` (see below); and
- must return an object `Xin` satisfying `scitype(Xin) <:
input_scitype(SomeSupervisedModel)`.
input_scitype(SomeSupervisedModel)`.

For sample implementatations, see MLJ's [built-in
transformers](https://github.com/JuliaAI/MLJModels.jl/blob/dev/src/builtins/Transformers.jl)
Expand Down
Loading
Loading