Skip to content

Commit aa7a0fe

Browse files
committed
finish optional data interface
1 parent 7e0f37a commit aa7a0fe

13 files changed

+108
-89
lines changed

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ makedocs(;
1313
"Reference" => "reference.md",
1414
"Fit, update and ingest" => "fit_update_and_ingest.md",
1515
"Predict and other operations" => "operations.md",
16+
"Accessor Functions" => "accessor_functions.md",
17+
"Optional Data Interface" => "optional_data_interface.md",
1618
"Model Traits" => "model_traits.md",
1719
"Common Implementation Patterns" => "common_implementation_patterns.md",
1820
"Testing an Implementation" => "testing_an_implementation.md",
@@ -26,4 +28,3 @@ deploydocs(
2628
devbranch="dev",
2729
push_preview=false,
2830
)
29-

docs/src/anatomy_of_an_implementation.md

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
> `transform`). In this example we also implement an **accessor function**, called
77
> `feature_importance`, returning the absolute values of the linear coefficients. The
88
> ridge regressor has a target variable and `predict` makes literal predictions of the
9-
> target (rather than, say, probablistic predictions); this behaviour is flagged by the
9+
> target (rather than, say, probabilistic predictions); this behavior is flagged by the
1010
> `predict_proxy` model trait. Other traits articulate the model's training data type
1111
> requirements and the input/output type of `predict`.
1212
@@ -35,7 +35,7 @@ nothing # hide
3535
```
3636

3737
The subtyping `MyRidge <: LearnAPI.Model` is optional but recommended where it is not
38-
otherwise disruptive (it allows models to be displayed in a standard way, for example).
38+
otherwise disruptive.
3939

4040
Instances of `MyRidge` are called **models** and `MyRidge` is a **model type**.
4141

@@ -75,7 +75,7 @@ function LearnAPI.fit(model::MyRidge, verbosity, X, y)
7575
feature_importances =
7676
[features[j] => abs(coefficients[j]) for j in eachindex(features)]
7777
sort!(feature_importances, by=last) |> reverse!
78-
verbosity > 1 && @info "Features in order of importance: $(first.(feature_importances))"
78+
verbosity > 0 && @info "Features in order of importance: $(first.(feature_importances))"
7979
report = (; feature_importances)
8080
8181
return fitted_params, state, report
@@ -92,15 +92,15 @@ Regarding the return value of `fit`:
9292
or [`LearnAPI.ingest!`](@ref) method (see [Fit, update! and ingest!](@ref)).
9393

9494
- The `report` is for other byproducts of training, apart from the learned parameters (the
95-
ones will need to provide `predict` below).
95+
ones we'll need to provide `predict` below).
9696

97-
Our `fit` method assumes that `X` is a table (satifies the [Tables.jl
97+
Our `fit` method assumes that `X` is a table (satisfies the [Tables.jl
9898
spec](https://github.com/JuliaData/Tables.jl)) whose rows are the observations; and it
9999
will need need `y` to be an `AbstractFloat` vector. A model implementation is free to
100100
dictate the representation of data that `fit` accepts but articulates its requirements
101101
using appropriate traits; see [Training data types](@ref) below. We recommend against data
102102
type checks internal to `fit`; this would ordinarily be the responsibility of a higher
103-
level API, using those trasits.
103+
level API, using those traits.
104104

105105

106106
## Operations
@@ -146,7 +146,7 @@ Another example of an accessor function is [`LearnAPI.training_losses`](@ref).
146146

147147
Our model has a target variable, in the sense outlined in [Scope and undefined
148148
notions](@ref scope), and `predict` returns an object with exactly the same form as the
149-
target. We indicate this behaviour by declaring
149+
target. We indicate this behavior by declaring
150150

151151
```@example anatomy
152152
LearnAPI.predict_proxy(::Type{<:MyRidge}) = LearnAPI.TrueTarget()
@@ -166,10 +166,6 @@ for details.
166166
`LearnAPI.predict_proxy` is an example of a **model trait**. A complete list of traits
167167
and the contracts they imply is given in [Model Traits](@ref).
168168

169-
> **MLJ only.** The values of all traits constitute a model's **metadata**, which is
170-
> recorded in the searchable MLJ Model Registry, assuming the implementation-providing
171-
> package is registered there.
172-
173169
We also need to indicate that a target variable appears in training (this is a supervised
174170
model). We do this by declaring *where* in the list of training data arguments (in this
175171
case `(X, y)`) the target variable (in this case `y`) appears:
@@ -206,7 +202,7 @@ nothing # hide
206202
## Training data types
207203

208204
Since LearnAPI.jl is a basement level API, one is discouraged from including explicit type
209-
checks in an implementation of `fit`. Instead one uses traits to make promisises about the
205+
checks in an implementation of `fit`. Instead one uses traits to make promises about the
210206
acceptable type of `data` consumed by `fit`. In general, this can be a promise regarding
211207
the ordinary type of `data` or the [scientific
212208
type](https://github.com/JuliaAI/ScientificTypes.jl) of `data` (but not
@@ -238,37 +234,23 @@ Or, in other words:
238234
AbstractVector{Continuous}` - meaning that it is an abstract vector with `<:AbstractFloat`
239235
elements.
240236

241-
## Input/output types for operations
237+
## Input types for operations
242238

243-
An optional promise that an operation, such as `predict`, returns an object of given
244-
scientific type is articulated in this way:
239+
An optional promise about what `data` is guaranteed to work in a call like
240+
`predict(model, fitted_params, data...)` is articulated this way:
245241

246242
```@example anatomy
247-
@trait predict_output_scitype=AbstractVector{<:Continuous}
248-
nothing # hide
249-
```
250-
251-
If `predict` had instead returned probability distributions that implement the
252-
`Distributions.pdf` interface, then one could instead make the declaration
253-
254-
```julia
255-
@trait MyRidge predict_output_scitype=AbstractVector{Density{<:Continuous}}
243+
@trait MyRidge predict_input_scitype = Tuple{AbstractVector{<:Continuous}}
256244
```
257245

258-
Similarly, there exists a trait called [`LearnAPI.predict_output_type`](@ref) for making promises
259-
on the ordinary type returned by an operation.
260-
261-
Finally, we'll make a promise about what `data` is guaranteed to work in a call like
262-
`predict(model, fitted_params, data...)`. Note that `data` is always a `Tuple`, even if it
263-
has only one component (the typical case).
264-
265-
```@example anatomy
266-
@trait MyRidge predict_input_scitype = (; predict=Tuple{AbstractVector{<:Continuous}})
267-
```
246+
Note that `data` is always a `Tuple`, even if it has only one component (the typical
247+
case), which explains the `Tuple` on the right-hand side.
268248

269249
Optionally, we may express our promise using regular types, using the
270250
[`LearnAPI.predict_input_type`](@ref) trait.
271251

252+
One can optionally make promises about the outut of an operation. See [Model Traits](@ref)
253+
for details.
272254

273255
## [Illustrative fit/predict workflow](@id workflow)
274256

@@ -286,21 +268,21 @@ X = (; a, b, c) |> Tables.rowtable
286268
y = 2a - b + 3c + 0.05*rand(n)
287269
nothing # hide
288270
```
289-
Instantiate a model with relevant hyperparameters:
271+
Instantiate a model with relevant hyperparameters (which is all the object stores):
290272

291273
```@example anatomy
292274
model = MyRidge(lambda=0.5)
293275
```
294276

295-
Train the model:
277+
Train the model (the `0` means do so silently):
296278

297279
```@example anatomy
298280
import LearnAPI: fit, predict, feature_importances
299281
300-
fitted_params, state, fit_report = fit(model, 1, X[train], y[train])
282+
fitted_params, state, fit_report = fit(model, 0, X[train], y[train])
301283
```
302284

303-
Inspect the learned paramters and report:
285+
Inspect the learned parameters and report:
304286

305287
```@example anatomy
306288
@info "training outcomes" fitted_params fit_report

docs/src/common_implementation_patterns.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ implementations fall into one (or more) of the following informally understood p
2121
- [Incremental Models](@ref)
2222

2323
- [Static Transformers](@ref): Transformations that do not learn but which have
24-
hyper-parameters and/or deliver ancilliary information about the transformation
24+
hyper-parameters and/or deliver ancillary information about the transformation
2525

2626
- [Dimension Reduction](@ref): Transformers that learn to reduce feature space dimension
2727

docs/src/fit_update_and_ingest.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
All three methods above return a triple `(fitted_params, state, report)` whose components
1515
are explained under [`LearnAPI.fit`](@ref) below. Items that might be returned in
16-
`report` include: feature rankings/importances, SVM support vectors, clustering centres,
16+
`report` include: feature rankings/importances, SVM support vectors, clustering centers,
1717
methods for visualizing training outcomes, methods for saving learned parameters in a
1818
custom format, degrees of freedom, deviances. Precisely what `report` includes might be
1919
controlled by model hyperparameters, especially if there is a performance cost to it's

docs/src/index.md

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,28 @@ A basic Julia interface for training and applying machine learning models </span
1010

1111
## Quick tours
1212

13-
- For developers wanting to **IMPLEMEMT** LearnAPI: [Anatomy of
14-
an Implementation](@ref).
15-
1613
- To see how to **USE** models implementing LearnAPI: [Basic fit/predict
1714
workflow](@ref workflow).
1815

16+
- For developers wanting to **IMPLEMENT** LearnAPI: [Anatomy of
17+
an Implementation](@ref).
18+
1919
## Approach
2020

2121
Machine learning algorithms, also called *models*, have a complicated
22-
taxonomy. Grouping models, or modelling tasks, into a relatively small number of types,
23-
such as "classifier" and "clusterer", and attempting to impose uniform behaviour within
22+
taxonomy. Grouping models, or modeling tasks, into a relatively small number of types,
23+
such as "classifier" and "clusterer", and attempting to impose uniform behavior within
2424
each group, is challenging. In our experience developing the [MLJ
2525
ecosystem](https://github.com/alan-turing-institute/MLJ.jl), this either leads to
2626
limitations on the models that can be included in a general interface, or additional
2727
complexity needed to cope with exceptional cases. Even if a complete user interface for
2828
machine learning might benefit from such groupings, a basement-level API for ML should, in
2929
our view, avoid them.
3030

31-
In a addition to basic methods, like `fit` and `predict`, LearnAPI provides a large number
31+
In addition to basic methods, like `fit` and `predict`, LearnAPI provides a number
3232
of optional model
3333
[traits](https://ahsmart.com/pub/holy-traits-design-patterns-and-best-practice-book/),
34-
each promising a specific kind of behaviour, such as "The predictions of this model are
34+
each promising a specific kind of behavior, such as "The predictions of this model are
3535
probability distributions". There is no abstract type model hierarchy.
3636

3737
Our preceding remarks notwithstanding, there is, for certain applications involving a
@@ -48,12 +48,12 @@ not supervised, can generalize to new data observations, or not generalize.
4848
## Methods
4949

5050
In LearnAPI.jl a *model* is just a container for the hyper-parameters of some machine
51-
learning algorithm, and that's all. It does not include learned parameters.
51+
learning algorithm, and does not typically include learned parameters.
5252

5353
The following methods, dispatched on model type, are provided:
5454

5555
- `fit`, for regular training, overloaded if the model generalizes to new data, as in
56-
classical supervised learning
56+
classical supervised learning; the principal output of `fit` is the learned parameters
5757

5858
- `update!`, for adding model iterations, or responding efficiently to other
5959
post-`fit`changes in hyperparameters
@@ -66,11 +66,11 @@ The following methods, dispatched on model type, are provided:
6666
- common **accessor functions**, such as `feature_importances` and `training_losses`, for
6767
extracting, from training outcomes, information common to some models
6868

69-
- **model traits**, such as `target_proxies(model)`, for promising specific behaviour
69+
- **model traits**, such as `predict_output_type(model)`, for promising specific behavior
7070

71-
There is flexibility about how much of the interface is implemented by a given model
72-
object `model`. A special trait `functions(model)` declares what has been explicitly
73-
implemented to work with `model`, excluding traits.
71+
There is flexibility about how much of the interface is implemented by a given model type.
72+
A special trait `functions(model)` declares what has been explicitly implemented to work
73+
with `model`, excluding traits.
7474

7575
Since this is a functional-style interface, `fit` returns model `state`, in addition to
7676
learned parameters, for passing to the optional `update!` and `ingest!` methods. These
@@ -89,10 +89,10 @@ formalize:
8989
- An object which generates ordered sequences of individual **observations** is called
9090
**data**. For example a `DataFrame` instance, from
9191
[DataFrames.jl](https://dataframes.juliadata.org/stable/), is considered data, the
92-
observatons being the rows. A matrix can be considered data, but whether the
92+
observations being the rows. A matrix can be considered data, but whether the
9393
observations are rows or columns is ambiguous and not fixed by LearnAPI.
9494

95-
- Each machine learning model's behaviour is governed by a number of user-specified
95+
- Each machine learning model's behavior is governed by a number of user-specified
9696
**hyperparameters**. The regularization parameter in ridge regression is an
9797
example. Hyperparameters are data-independent. For example, the number of target classes
9898
is not a hyperparameter.
@@ -119,21 +119,20 @@ for the general user - such as a table (dataframe) or the path to a directory co
119119
image files - and a performant, model-specific representation of that data, such as a
120120
matrix or image "data loader". When retraining using the same data with new
121121
hyper-parameters, one wants to avoid recreating the model-specific representation, and,
122-
accordingly, a higher level ML interface may want to cache model-specific
122+
accordingly, a higher level ML interface may want to cache such
123123
representations. Furthermore, in resampling (e.g., performing cross-validation), a higher
124124
level interface wants only to resample the model-specific representation, so it needs to
125125
know how to do that. To meet these two ends, LearnAPI provides two additional **data
126126
methods** dispatched on model type:
127127

128-
- `reformat(model, ...)`, for converting from a user data representation to a peformant model-specific
129-
representation
128+
- `reformat(model, ...)`, for converting from a user data representation to a performant model-specific representation, whose output is for use in `fit`, `predict`, etc. above
130129

131130
- `getobs(model, ...)`, for extracting a subsample of observations of the model-specific
132131
representation
133132

134133
It should be emphasized that LearnAPI is itself agnostic to particular representations of
135-
data or the particular methods of accessing observations within them. Each `model` is free
136-
to choose its own data interface.
134+
data or the particular methods of accessing observations within them. By overloading these
135+
methods, Each `model` is free to choose its own data interface.
137136

138137
See [Optional data Interface](@ref data_interface) for more details.
139138

@@ -158,6 +157,6 @@ interface is the [Reference](@ref reference) section.
158157

159158
**Note.** In the future, LearnAPI.jl may become the new foundation for the
160159
[MLJ](https://alan-turing-institute.github.io/MLJ.jl/dev/) toolbox created by the same
161-
developers. However, LearnAPI.jl is meant as a general purpose, standalone, lightweight,
160+
developers. However, LearnAPI.jl is meant as a general purpose, stand-alone, lightweight,
162161
low level API for machine learning algorithms (and has no reference to the "machines" used
163162
there).

0 commit comments

Comments
 (0)