Skip to content

Commit e453dc0

Browse files
authored
Merge pull request #7 from JuliaAI/dev
Finish up traits and optional data interface
2 parents 075afa3 + 9803f64 commit e453dc0

22 files changed

+1170
-339
lines changed

Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ authors = ["Anthony D. Blaom <anthony.blaom@gmail.com>"]
44
version = "0.1.0"
55

66
[deps]
7-
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
87
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
98

109
[extras]

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
# LearnAPI.jl
22

3-
A Julia interface for training and applying machine learning models.
3+
A Julia interface for training and applying machine learning models.
44

5-
**Status:** Proposal.
65

6+
**Devlopement Status:**
77

8-
&#x1F6A7;
8+
- [X] Detailed proposal stage ([this
9+
documentation](https://juliaai.github.io/LearnAPI.jl/dev/))
10+
- [ ] Initial feedback stage (opened mid-January, 2023)
11+
- [ ] Implement feedback and finish "To do" list (below)
12+
- [ ] Proof of concept implementation
13+
- [ ] Polish
14+
- [ ] Registration
915

16+
To do:
1017

11-
[![Build Status](https://github.com/JuliaAI/LearnAPI.jl/workflows/CI/badge.svg)](https://github.com/JuliaAI/LearnAPI.jl/actions)
12-
[![Coverage](https://codecov.io/gh/JuliaAI/LearnAPI.jl/branch/master/graph/badge.svg)](https://codecov.io/github/JuliaAI/LearnAPI.jl?branch=master)
18+
- [ ] Add methods to create/save persistent representation of learned parameters
19+
- [ ] Add more repo tests
20+
- [ ] Add methods to test an implementation
21+
- [ ] Add user guide ("Common Implementation Patterns" section of manual)
22+
23+
[![Build Status](https://github.com/JuliaAI/LearnAPI.jl/workflows/CI/badge.svg)](https://github.com/JuliaAI/LearnAPI.jl/actions)
24+
[![Coverage](https://codecov.io/gh/JuliaAI/LearnAPI.jl/branch/master/graph/badge.svg)](https://codecov.io/github/JuliaAI/LearnAPI.jl?branch=master)
1325
[![Docs](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliaai.github.io/LearnAPI.jl/dev/)
1426

15-
Please refer to the documentation for a detailed preview of what this package proposes to
16-
offer.

docs/Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3-
LearnAPI = "92ad9a40-7767-427a-9ee6-6e577f1266cb"
43
ScientificTypesBase = "30f210dd-8aff-4c5f-94ba-8e64358c1161"
54
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
65

docs/make.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
using Documenter
22
using LearnAPI
3+
using ScientificTypesBase
34

45
const REPO="github.com/JuliaAI/LearnAPI.jl"
56

67
makedocs(;
78
modules=[LearnAPI,],
89
format=Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),
910
pages=[
10-
"Introduction" => "index.md",
11+
"Overview" => "index.md",
1112
"Anatomy of an Implementation" => "anatomy_of_an_implementation.md",
1213
"Reference" => "reference.md",
1314
"Fit, update and ingest" => "fit_update_and_ingest.md",
1415
"Predict and other operations" => "operations.md",
16+
"Accessor Functions" => "accessor_functions.md",
17+
"Optional Data Interface" => "optional_data_interface.md",
1518
"Model Traits" => "model_traits.md",
1619
"Common Implementation Patterns" => "common_implementation_patterns.md",
20+
"Testing an Implementation" => "testing_an_implementation.md",
1721
],
1822
repo="https://$REPO/blob/{commit}{path}#L{line}",
1923
sitename="LearnAPI.jl"
@@ -24,4 +28,3 @@ deploydocs(
2428
devbranch="dev",
2529
push_preview=false,
2630
)
27-

docs/src/accessor_functions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
> **Summary.** While byproducts of training are ordinarily recorded in the `report`
44
> component of the output of `fit`/`update!`/`ingest!`, some families of models report an
5-
> itme that is likely shared by multiple model types, and it is useful to have common
5+
> item that is likely shared by multiple model types, and it is useful to have common
66
> interface for accessing these directly. Training losses and feature importances are two
77
> examples.
88
99
```@docs
1010
LearnAPI.feature_importances
11-
LearnAPI.training_labels
1211
LearnAPI.training_losses
1312
LearnAPI.training_scores
13+
LearnAPI.training_labels
1414
```
1515

16+

docs/src/anatomy_of_an_implementation.md

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
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
10-
> `target_proxies` model trait. Other traits articulate the model's training data type
9+
> target (rather than, say, probabilistic predictions); this behavior is flagged by the
10+
> `predict_proxy` model trait. Other traits articulate the model's training data type
1111
> requirements and the input/output type of `predict`.
1212
1313
We begin by describing an implementation of LearnAPI.jl for basic ridge
@@ -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
@@ -139,43 +139,39 @@ LearnAPI.feature_importances(::MyRidge, fitted_params, report) =
139139
nothing # hide
140140
```
141141

142-
Another example of an accessor function is [`training_losses`](@ref).
142+
Another example of an accessor function is [`LearnAPI.training_losses`](@ref).
143143

144144

145145
## [Model traits](@id traits)
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
152-
LearnAPI.target_proxies(::Type{<:MyRidge}) = (; predict=LearnAPI.TrueTarget())
152+
LearnAPI.predict_proxy(::Type{<:MyRidge}) = LearnAPI.TrueTarget()
153153
nothing # hide
154154
```
155155
Or, you can use the shorthand
156156

157157
```@example anatomy
158-
@trait MyRidge target_proxies = (; predict=LearnAPI.TrueTarget())
158+
@trait MyRidge predict_proxy=LearnAPI.TrueTarget()
159159
nothing # hide
160160
```
161161

162162
More generally, `predict` only returns a *proxy* for the target, such as probability
163163
distributions, and we would make a different declaration here. See [Target proxies](@ref)
164164
for details.
165165

166-
`LearnAPI.target_proxies` is an example of a **model trait**. A complete list of traits
166+
`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:
176172

177173
```@example anatomy
178-
@trait MyRidge position_of_target = 2
174+
@trait MyRidge position_of_target=2
179175
nothing # hide
180176
```
181177

@@ -184,7 +180,7 @@ As explained in the introduction, LearnAPI.jl does not attempt to define strict
184180
descriptors, as in
185181

186182
```@example anatomy
187-
@trait MyRidge descriptors = (:regression,)
183+
@trait MyRidge descriptors=(:regression,)
188184
nothing # hide
189185
```
190186

@@ -195,7 +191,7 @@ Finally, we are required to declare what methods (excluding traits) we have expl
195191
overloaded for our type:
196192

197193
```@example anatomy
198-
@trait MyRidge methods = (
194+
@trait MyRidge methods=(
199195
:fit,
200196
:predict,
201197
:feature_importances,
@@ -206,26 +202,25 @@ 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
211-
the ordinary type of `data` and/or the [scientific
212-
type](https://github.com/JuliaAI/ScientificTypes.jl) of `data`. Alternatively, one may
213-
only make a promise about the type/scitype of *observations* in the data . See [Model
214-
Traits](@ref) for further details. In this case we'll be happy to restrict the scitype of
215-
the data:
207+
the ordinary type of `data` or the [scientific
208+
type](https://github.com/JuliaAI/ScientificTypes.jl) of `data` (but not
209+
both). Alternatively, one may only make a promise about the type/scitype of *observations*
210+
in the data . See [Model Traits](@ref) for further details. In this case we'll be happy to
211+
restrict the scitype of the data:
216212

217213
```@example anatomy
218214
import ScientificTypesBase: scitype, Table, Continuous
219-
@trait MyRidge fit_data_scitype = Tuple{Table(Continuous), AbstractVector{Continuous}}
215+
@trait MyRidge fit_scitype = Tuple{Table(Continuous), AbstractVector{Continuous}}
220216
nothing # hide
221217
```
222218

223219
This is a contract that `data` is acceptable in the call `fit(model, verbosity, data...)`
224220
whenever
225221

226-
```@example anatomy
222+
```julia
227223
scitype(data) <: Tuple{Table(Continuous), AbstractVector{Continuous}}
228-
nothing # hide
229224
```
230225

231226
Or, in other words:
@@ -239,33 +234,23 @@ Or, in other words:
239234
AbstractVector{Continuous}` - meaning that it is an abstract vector with `<:AbstractFloat`
240235
elements.
241236

242-
## Input/output types for operations
237+
## Input types for operations
243238

244-
An optional promise that an operation, such as `predict`, returns an object of given
245-
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:
246241

247242
```@example anatomy
248-
@trait output_scitypes = (; predict=AbstractVector{<:Continuous})
249-
nothing # hide
243+
@trait MyRidge predict_input_scitype = Tuple{AbstractVector{<:Continuous}}
250244
```
251245

252-
If `predict` had instead returned probability distributions that implement the
253-
`Distributions.pdf` interface, then one could instead make the declaration
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.
254248

255-
```julia
256-
@trait MyRidge output_scitypes = (; predict=AbstractVector{Density{<:Continuous}})
257-
```
258-
259-
Similarly, there exists a trait called [`output_type`](@ref) for making promises on the
260-
ordinary type resturned by an operation.
261-
262-
Finally, we'll make a promise about what `data` is acceptable in a call like
263-
`predict(model, fitted_params, data...)`. Note that `data` is always a `Tuple`, even if it
264-
has only one component (the typical case).
249+
Optionally, we may express our promise using regular types, using the
250+
[`LearnAPI.predict_input_type`](@ref) trait.
265251

266-
```example anatomy
267-
@trait MyRidge input_scitype = (; predict=Tuple{AbstractVector{<:Continuous}})
268-
```
252+
One can optionally make promises about the outut of an operation. See [Model Traits](@ref)
253+
for details.
269254

270255
## [Illustrative fit/predict workflow](@id workflow)
271256

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

288273
```@example anatomy
289274
model = MyRidge(lambda=0.5)
290275
```
291276

292-
Train the model:
277+
Train the model (the `0` means do so silently):
293278

294279
```@example anatomy
295280
import LearnAPI: fit, predict, feature_importances
296281
297-
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])
298283
```
299284

300-
Inspect the learned paramters and report:
285+
Inspect the learned parameters and report:
301286

302287
```@example anatomy
303288
@info "training outcomes" fitted_params fit_report

docs/src/common_implementation_patterns.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
!!! warning
44

55
This section is only an implementation guide. The definitive specification of the
6-
Learn API is given in [Reference](@ref).
6+
Learn API is given in [Reference](@ref reference).
77

8-
This guide is intended to be consulted after reading [Anatomy of a Model
9-
Implementation](@ref), which introduces the main interface objects and terminology.
8+
This guide is intended to be consulted after reading [Anatomy of an Implementation](@ref),
9+
which introduces the main interface objects and terminology.
1010

1111
Although an implementation is defined purely by the methods and traits it implements, most
1212
implementations fall into one (or more) of the following informally understood patterns or
@@ -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: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
# Fit, update! and ingest!
22

3-
> **Summary.** Models that learn, i.e., generalize to new data, must overload `fit`;
4-
> the fallback performs no operation and returns all `nothing`. Implement `update!` if
5-
> certain hyper-parameter changes do not necessitate retraining from scratch (e.g.,
6-
> increasing iteration parameters). Implement `ingest!` to implement incremental learning.
3+
> **Summary.** Models that learn, i.e., generalize to new data, must overload `fit`; the
4+
> fallback performs no operation and returns all `nothing`. Implement `update!` if certain
5+
> hyper-parameter changes do not necessitate retraining from scratch (e.g., increasing an
6+
> iteration parameter). Implement `ingest!` to implement incremental learning. All
7+
> training methods implemented must be named in the return value of the
8+
> `functions` trait.
79
810
| method | fallback | compulsory? | requires |
911
|:---------------------------|:---------------------------------------------------|-------------|-------------------|
10-
[`LearnAPI.fit`](@ref) | does nothing, returns `(nothing, nothing, nothing)`| no | |
11-
[`LearnAPI.update!`](@ref) | calls `fit` | no | `LearnAPI.fit` |
12-
[`LearnAPI.ingest!`](@ref) | none | no | `LearnAPI.fit` |
12+
| [`LearnAPI.fit`](@ref) | does nothing, returns `(nothing, nothing, nothing)`| no | |
13+
| [`LearnAPI.update!`](@ref) | calls `fit` | no | [`LearnAPI.fit`](@ref) |
14+
| [`LearnAPI.ingest!`](@ref) | none | no | [`LearnAPI.fit`](@ref) |
1315

1416
All three methods above return a triple `(fitted_params, state, report)` whose components
1517
are explained under [`LearnAPI.fit`](@ref) below. Items that might be returned in
16-
`report` include: feature rankings/importances, SVM support vectors, clustering centres,
18+
`report` include: feature rankings/importances, SVM support vectors, clustering centers,
1719
methods for visualizing training outcomes, methods for saving learned parameters in a
1820
custom format, degrees of freedom, deviances. Precisely what `report` includes might be
1921
controlled by model hyperparameters, especially if there is a performance cost to it's
@@ -26,10 +28,12 @@ as a basic DBSCAN clustering algorithm.
2628

2729
The `update!` method is intended for all subsequent calls to train a model *using the same
2830
observations*, but with possibly altered hyperparameters (`model` argument). A fallback
29-
implementation simply calls `fit`. The main use cases for implementing `update` are: (i)
30-
warm-restarting iterative models, and (ii) "smart" training of composite models, such as
31-
linear pipelines. Here "smart" means that hyperparameter changes only trigger the
32-
retraining of downstream components.
31+
implementation simply calls `fit`. The main use cases for implementing `update` are:
32+
33+
- warm-restarting iterative models
34+
35+
- "smart" training of composite models, such as linear pipelines; here "smart" means that
36+
hyperparameter changes only trigger the retraining of downstream components.
3337

3438
The `ingest!` method supports incremental learning (same hyperparameters, but new training
3539
observations). Like `update!`, it depends on the output a preceding `fit` or `ingest!`

0 commit comments

Comments
 (0)