Skip to content

Commit 7d45e08

Browse files
committed
other tweaks
1 parent 1530a84 commit 7d45e08

10 files changed

+57
-174
lines changed

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
- [ ] classification
1818
- [ ] clustering
1919
- [ ] gradient descent
20-
- [ ] iterative algorithms
20+
- [x] iterative algorithms
2121
- [ ] incremental algorithms
2222
- [ ] dimension reduction
2323
- [x] feature engineering
2424
- [x] static algorithms
2525
- [ ] missing value imputation
2626
- [ ] transformers
27-
- [ ] ensemble algorithms
27+
- [x] ensemble algorithms
2828
- [ ] time series forecasting
2929
- [ ] time series classification
3030
- [ ] survival analysis

docs/src/common_implementation_patterns.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,54 @@ Although an implementation is defined purely by the methods and traits it implem
2020
implementations fall into one (or more) of the following informally understood patterns or
2121
"tasks":
2222

23+
- [Regression](@ref): Supervised learners for continuous targets
24+
2325
- [Classification](@ref): Supervised learners for categorical targets
2426

25-
- [Regression](@ref): Supervised learners for continuous targets
27+
- [Clusterering](@ref): Algorithms that group data into clusters for classification and
28+
possibly dimension reduction. May be true learners (generalize to new data) or static.
29+
30+
- [Gradient Descent](@ref): Including neural networks.
2631

2732
- [Iterative Algorithms](@ref)
2833

2934
- [Incremental Algorithms](@ref)
3035

36+
- [Feature Engineering](@ref): Algorithms for selecting or combining features
37+
38+
- [Dimension Reduction](@ref): Transformers that learn to reduce feature space dimension
39+
40+
- [Missing Value Imputation](@ref)
41+
42+
- [Transformers](@ref): Other transformers, such as standardizers, and categorical
43+
encoders.
44+
3145
- [Static Algorithms](@ref): Algorithms that do not learn, in the sense they must be
3246
re-executed for each new data set (do not generalize), but which have hyperparameters
3347
and/or deliver ancillary information about the computation.
48+
49+
- [Ensemble Algorithms](@ref): Algorithms that blend predictions of multiple algorithms
3450

35-
- [Dimension Reduction](@ref): Transformers that learn to reduce feature space dimension
51+
- [Time Series Forecasting](@ref)
3652

37-
- [Feature Engineering](@ref)
53+
- [Time Series Classification](@ref)
3854

39-
- [Missing Value Imputation](@ref): Transformers that replace missing values.
55+
- [Survival Analysis](@ref)
4056

41-
- [Transformers](@ref): Other transformers, such as standardizers, and categorical
42-
encoders.
57+
- [Density Estimation](@ref): Algorithms that learn a probability distribution
4358

44-
- [Clusterering](@ref): Algorithms that group data into clusters for classification and
45-
possibly dimension reduction. May be true learners (generalize to new data) or static.
59+
- [Bayesian Algorithms](@ref)
4660

4761
- [Outlier Detection](@ref): Supervised, unsupervised, or semi-supervised learners for
4862
anomaly detection.
4963

50-
- [Learning a Probability Distribution](@ref): Algorithms that fit a distribution or
51-
distribution-like object to data
52-
53-
- [Time Series Forecasting](@ref)
64+
- [Text Analysis](@ref)
5465

55-
- [Time Series Classification](@ref)
66+
- [Audio Analysis](@ref)
5667

57-
- [Supervised Bayesian Algorithms](@ref)
68+
- [Natural Language Processing](@ref)
5869

59-
- [Survival Analysis](@ref)
70+
- [Image Processing](@ref)
6071

6172
- [Meta-algorithms](@ref)
6273

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Density Estimation
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Ensemble Algorithms
2+
3+
See [this
4+
example](https://github.com/JuliaAI/LearnAPI.jl/blob/dev/test/integration/iterative_algorithms.jl)
5+
from tests.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
# Iterative Algorithms
2+
3+
See [this
4+
example](https://github.com/JuliaAI/LearnAPI.jl/blob/dev/test/integration/iterative_algorithms.jl)
5+
from tests.

docs/src/patterns/learning_a_probability_distribution.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/src/patterns/transformers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Transformers

src/traits.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,11 @@ tags() = [
195195
"gradient descent",
196196
"iterative algorithms",
197197
"incremental algorithms",
198+
"feature engineering",
198199
"dimension reduction",
200+
"missing value imputation",
199201
"transformers",
200-
"feature engineering",
201202
"static algorithms",
202-
"missing value imputation",
203203
"ensemble algorithms",
204204
"time series forecasting",
205205
"time series classification",

test/integration/iterative_algorithms.jl

Lines changed: 15 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ using StableRNGs
1515
# replacement). In particular this algorithm has an iteration parameter `n`, and we
1616
# implement `update` for warm restarts when `n` increases.
1717

18-
# By re-using the data interface for `Ridge`, we ensure that the resampling (bagging) is
19-
# more efficient (no repeated table -> matrix conversions, and we resample matrices
20-
# directly, not the tables).
21-
2218
# no docstring here - that goes with the constructor
2319
struct RidgeEnsemble
2420
lambda::Float64
@@ -44,22 +40,14 @@ end
4440

4541
LearnAPI.algorithm(model::RidgeEnsembleFitted) = model.algorithm
4642

47-
# we use the same data interface we provided for `Ridge` in regression.jl:
43+
# We add the same data interface we provided for `Ridge` in regression.jl. This is an
44+
# optional step on which the later code does not depend.
4845
LearnAPI.obs(algorithm::RidgeEnsemble, data) = LearnAPI.obs(Ridge(), data)
4946
LearnAPI.obs(model::RidgeEnsembleFitted, data) = LearnAPI.obs(first(model.models), data)
5047
LearnAPI.target(algorithm::RidgeEnsemble, data) = LearnAPI.target(Ridge(), data)
5148
LearnAPI.features(algorithm::Ridge, data) = LearnAPI.features(Ridge(), data)
5249

53-
function d(rng)
54-
i = digits(rng.state)
55-
m = min(length(i), 4)
56-
tail = i[end - m + 1:end]
57-
println(join(string.(tail)))
58-
end
59-
60-
# because we need observation subsampling, we first implement `fit` for output of
61-
# `obs`:
62-
function LearnAPI.fit(algorithm::RidgeEnsemble, data::RidgeFitObs; verbosity=1)
50+
function LearnAPI.fit(algorithm::RidgeEnsemble, data; verbosity=1)
6351

6452
# unpack hyperparameters:
6553
lambda = algorithm.lambda
@@ -69,16 +57,21 @@ function LearnAPI.fit(algorithm::RidgeEnsemble, data::RidgeFitObs; verbosity=1)
6957
# instantiate atomic algorithm:
7058
atom = Ridge(lambda)
7159

60+
# ensure data can be subsampled using MLUtils.jl, and that we're feeding the atomic
61+
# `fit` data in an efficient (pre-processed) form:
62+
63+
observations = obs(atom, data)
64+
7265
# initialize ensemble:
7366
models = []
7467

7568
# get number of observations:
76-
N = MLUtils.numobs(data)
69+
N = MLUtils.numobs(observations)
7770

7871
# train the ensemble:
7972
for _ in 1:n
8073
bag = rand(rng, 1:N, N)
81-
data_subset = MLUtils.getobs(data, bag)
74+
data_subset = MLUtils.getobs(observations, bag)
8275
# step down one verbosity level in atomic fit:
8376
model = fit(atom, data_subset; verbosity=verbosity - 1)
8477
push!(models, model)
@@ -91,21 +84,11 @@ function LearnAPI.fit(algorithm::RidgeEnsemble, data::RidgeFitObs; verbosity=1)
9184

9285
end
9386

94-
# ... and so need a `fit` for unprocessed `data = (X, y)`:
95-
LearnAPI.fit(algorithm::RidgeEnsemble, data; kwargs...) =
96-
fit(algorithm, obs(algorithm, data); kwargs...)
97-
9887
# If `n` is increased, this `update` adds new regressors to the ensemble, including any
9988
# new # hyperparameter updates (e.g, `lambda`) when computing the new
10089
# regressors. Otherwise, update is equivalent to retraining from scratch, with the
10190
# provided hyperparameter updates.
102-
function LearnAPI.update(
103-
model::RidgeEnsembleFitted,
104-
data::RidgeFitObs;
105-
verbosity=1,
106-
replacements...,
107-
)
108-
91+
function LearnAPI.update(model::RidgeEnsembleFitted, data; verbosity=1, replacements...)
10992
:n in keys(replacements) || return fit(model, data)
11093

11194
algorithm_old = LearnAPI.algorithm(model)
@@ -114,24 +97,18 @@ function LearnAPI.update(
11497
Δn = n - algorithm_old.n
11598
n < 0 && return fit(model, algorithm)
11699

117-
# get number of observations:
118-
N = MLUtils.numobs(data)
100+
atom = Ridge(; lambda=algorithm.lambda)
101+
observations = obs(atom, data)
102+
N = MLUtils.numobs(observations)
119103

120104
# initialize:
121105
models = model.models
122106
rng = model.rng # as mutated in previous `fit`/`update` calls
123107

124-
atom = Ridge(; lambda=algorithm.lambda)
125-
126-
rng2 = StableRNG(123)
127-
for _ in 1:10
128-
rand(rng2)
129-
end
130-
131108
# add new regressors to the ensemble:
132109
for _ in 1:Δn
133110
bag = rand(rng, 1:N, N)
134-
data_subset = MLUtils.getobs(data, bag)
111+
data_subset = MLUtils.getobs(observations, bag)
135112
model = fit(atom, data_subset; verbosity=verbosity-1)
136113
push!(models, model)
137114
end
@@ -142,13 +119,6 @@ function LearnAPI.update(
142119
return RidgeEnsembleFitted(algorithm, atom, rng, models)
143120
end
144121

145-
# an `update` for unprocessed `data = (X, y)`:
146-
LearnAPI.update(model::RidgeEnsembleFitted, data; kwargs...) =
147-
update(model, obs(LearnAPI.algorithm(model), data); kwargs...)
148-
149-
# `data` here can be pre-processed or not, because we're just calling the atomic
150-
# `predict`, which already has a data interface, and we don't need any subsampling, like
151-
# we did for `fit`:
152122
LearnAPI.predict(model::RidgeEnsembleFitted, ::Point, data) =
153123
mean(model.models) do atomic_model
154124
predict(atomic_model, Point(), data)
@@ -221,115 +191,6 @@ Xtest = Tables.subset(X, test)
221191
model = fit(LearnAPI.clone(algorithm; n=7), Xtrain, y[train]; verbosity=0);
222192
@test ŷ7 predict(model, Xtest)
223193

224-
225-
update(model, Xtest;
226-
fitobs = LearnAPI.obs(algorithm, data)
227-
predictobs = LearnAPI.obs(model, X)
228-
model = fit(algorithm, MLUtils.getobs(fitobs, train); verbosity=0)
229-
@test LearnAPI.target(algorithm, fitobs) == y
230-
@test predict(model, Point(), MLUtils.getobs(predictobs, test))
231-
@test predict(model, LearnAPI.features(algorithm, fitobs)) predict(model, X)
232-
233-
@test LearnAPI.feature_importances(model) isa Vector{<:Pair{Symbol}}
234-
235-
filename = tempname()
236-
using Serialization
237-
small_model = minimize(model)
238-
serialize(filename, small_model)
239-
240-
recovered_model = deserialize(filename)
241-
@test LearnAPI.algorithm(recovered_model) == algorithm
242-
@test predict(
243-
recovered_model,
244-
Point(),
245-
MLUtils.getobs(predictobs, test)
246-
)
247-
248-
end
249-
250-
# # VARIATION OF RIDGE REGRESSION THAT USES FALLBACK OF LearnAPI.obs
251-
252-
# no docstring here - that goes with the constructor
253-
struct BabyRidge
254-
lambda::Float64
255-
end
256-
257-
"""
258-
BabyRidge(; lambda=0.1)
259-
260-
Instantiate a ridge regression algorithm, with regularization of `lambda`.
261-
262-
"""
263-
BabyRidge(; lambda=0.1) = BabyRidge(lambda) # LearnAPI.constructor defined later
264-
265-
struct BabyRidgeFitted{T,F}
266-
algorithm::BabyRidge
267-
coefficients::Vector{T}
268-
feature_importances::F
269-
end
270-
271-
function LearnAPI.fit(algorithm::BabyRidge, data; verbosity=1)
272-
273-
X, y = data
274-
275-
lambda = algorithm.lambda
276-
table = Tables.columntable(X)
277-
names = Tables.columnnames(table) |> collect
278-
A = Tables.matrix(table)'
279-
280-
# apply core algorithm:
281-
coefficients = (A*A' + algorithm.lambda*I)\(A*y) # vector
282-
283-
feature_importances = nothing
284-
285-
return BabyRidgeFitted(algorithm, coefficients, feature_importances)
286-
287-
end
288-
289-
# extracting stuff from training data:
290-
LearnAPI.target(::BabyRidge, data) = last(data)
291-
292-
LearnAPI.algorithm(model::BabyRidgeFitted) = model.algorithm
293-
294-
LearnAPI.predict(model::BabyRidgeFitted, ::Point, Xnew) =
295-
Tables.matrix(Xnew)*model.coefficients
296-
297-
LearnAPI.minimize(model::BabyRidgeFitted) =
298-
BabyRidgeFitted(model.algorithm, model.coefficients, nothing)
299-
300-
@trait(
301-
BabyRidge,
302-
constructor = BabyRidge,
303-
kinds_of_proxy = (Point(),),
304-
tags = ("regression",),
305-
functions = (
306-
:(LearnAPI.fit),
307-
:(LearnAPI.algorithm),
308-
:(LearnAPI.minimize),
309-
:(LearnAPI.obs),
310-
:(LearnAPI.features),
311-
:(LearnAPI.target),
312-
:(LearnAPI.predict),
313-
:(LearnAPI.feature_importances),
314-
)
315-
)
316-
317-
@testset "test a variation which does not overload LearnAPI.obs" begin
318-
algorithm = BabyRidge(lambda=0.5)
319-
@test
320-
321-
model = fit(algorithm, Tables.subset(X, train), y[train]; verbosity=0)
322-
= predict(model, Point(), Tables.subset(X, test))
323-
@testisa Vector{Float64}
324-
325-
fitobs = obs(algorithm, data)
326-
predictobs = LearnAPI.obs(model, X)
327-
model = fit(algorithm, MLUtils.getobs(fitobs, train); verbosity=0)
328-
@test predict(model, Point(), MLUtils.getobs(predictobs, test)) ====
329-
predict(model, MLUtils.getobs(predictobs, test))
330-
@test LearnAPI.target(algorithm, data) == y
331-
@test LearnAPI.predict(model, X)
332-
LearnAPI.predict(model, LearnAPI.features(algorithm, data))
333194
end
334195

335196
true

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ test_files = [
66
"clone.jl",
77
"integration/regression.jl",
88
"integration/static_algorithms.jl",
9+
"integration/iterative_algorithms.jl",
910
]
1011

1112
files = isempty(ARGS) ? test_files : ARGS

0 commit comments

Comments
 (0)