Skip to content

Commit ffd0e45

Browse files
authored
Merge pull request #22 from JuliaAI/dev
For a 0.3.0 release
2 parents 3af26af + 5e722f0 commit ffd0e45

16 files changed

+179
-117
lines changed

Project.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LearnTestAPI"
22
uuid = "3111ed91-c4f2-40e7-bb19-7f6c618409b8"
33
authors = ["Anthony D. Blaom <[email protected]>"]
4-
version = "0.2.4"
4+
version = "0.3.0"
55

66
[deps]
77
CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
@@ -25,13 +25,13 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2525
UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
2626

2727
[compat]
28-
CategoricalArrays = "0.10.8"
29-
CategoricalDistributions = "0.1.15"
28+
CategoricalArrays = "1"
29+
CategoricalDistributions = "0.2"
3030
Distributions = "0.25"
3131
InteractiveUtils = "<0.0.1, 1"
3232
IsURL = "0.2.0"
33-
LearnAPI = "0.2.0,1"
34-
LearnDataFrontEnds = "0.1"
33+
LearnAPI = "2"
34+
LearnDataFrontEnds = "0.2"
3535
LinearAlgebra = "<0.0.1, 1"
3636
MLCore = "1.0.0"
3737
MacroTools = "0.5"

src/learners/classification.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct ConstantClassifierFitted
2424
learner::ConstantClassifier
2525
probabilities
2626
names::Vector{Symbol}
27-
classes_seen
27+
levels_seen
2828
codes_seen
2929
decoder
3030
end
@@ -44,10 +44,15 @@ LearnAPI.features(learner::ConstantClassifier, data) =
4444
LearnAPI.target(learner::ConstantClassifier, data) =
4545
LearnAPI.target(learner, data, front_end)
4646

47-
function LearnAPI.fit(learner::ConstantClassifier, observations::FrontEnds.Obs; verbosity=1)
47+
function LearnAPI.fit(
48+
learner::ConstantClassifier,
49+
observations::FrontEnds.Obs;
50+
verbosity=LearnAPI.default_verbosity(),
51+
)
52+
4853
y = observations.target # integer "codes"
4954
names = observations.names
50-
classes_seen = observations.classes_seen
55+
levels_seen = observations.levels_seen
5156
codes_seen = sort(unique(y))
5257
decoder = observations.decoder
5358

@@ -59,7 +64,7 @@ function LearnAPI.fit(learner::ConstantClassifier, observations::FrontEnds.Obs;
5964
learner,
6065
probabilities,
6166
names,
62-
classes_seen,
67+
levels_seen,
6368
codes_seen,
6469
decoder,
6570
)
@@ -89,7 +94,7 @@ function LearnAPI.predict(
8994
probs = model.probabilities
9095
# repeat vertically to get rows of a matrix:
9196
probs_matrix = reshape(repeat(probs, n), (length(probs), n))'
92-
return CategoricalDistributions.UnivariateFinite(model.classes_seen, probs_matrix)
97+
return CategoricalDistributions.UnivariateFinite(model.levels_seen, probs_matrix)
9398
end
9499
LearnAPI.predict(model::ConstantClassifierFitted, ::Distribution, data) =
95100
predict(model, Distribution(), obs(model, data))

src/learners/dimension_reduction.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ LearnAPI.obs(model::TruncatedSVDFitted, data) =
6565
LearnAPI.features(learner::TruncatedSVD, data) =
6666
LearnAPI.features(learner, data, FrontEnds.Tarragon())
6767

68-
function LearnAPI.fit(learner::TruncatedSVD, observations::FrontEnds.Obs; verbosity=1)
68+
function LearnAPI.fit(
69+
learner::TruncatedSVD,
70+
observations::FrontEnds.Obs;
71+
verbosity=LearnAPI.default_verbosity(),
72+
)
6973

7074
# unpack hyperparameters:
7175
codim = learner.codim

src/learners/ensembling.jl

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ LearnAPI.obs(model::EnsembleFitted, data) = LearnAPI.obs(first(model.models), da
6969
LearnAPI.target(learner::Ensemble, data) = LearnAPI.target(learner.atom, data)
7070
LearnAPI.features(learner::Ensemble, data) = LearnAPI.features(learner.atom, data)
7171

72-
function LearnAPI.fit(learner::Ensemble, data; verbosity=1)
72+
function LearnAPI.fit(learner::Ensemble, data; verbosity=LearnAPI.default_verbosity())
7373

7474
# unpack hyperparameters:
7575
atom = learner.atom
@@ -112,7 +112,7 @@ function LearnAPI.update(
112112
model::EnsembleFitted,
113113
data,
114114
replacements::Pair{Symbol}...;
115-
verbosity=1,
115+
verbosity=LearnAPI.default_verbosity(),
116116
)
117117
learner_old = LearnAPI.learner(model)
118118
learner = LearnAPI.clone(learner_old, replacements...)
@@ -205,7 +205,7 @@ LearnAPI.components(model::EnsembleFitted) = [:atom => model.models,]
205205
# - `update`
206206
# - `predict` (`Point` predictions)
207207
# - `predictions` (returns predictions on all supplied data)
208-
# - `out_of_sample_indices` (articluates which data is the internal validation data)
208+
# - `out_of_sample_indices` (articulates which data is the internal validation data)
209209
# - `trees`
210210
# - `training_losses`
211211
# - `out_of_sample_losses`
@@ -361,7 +361,7 @@ struct StumpRegressorFitted
361361
rng
362362
end
363363

364-
function LearnAPI.fit(learner::StumpRegressor, data; verbosity=1)
364+
function LearnAPI.fit(learner::StumpRegressor, data; verbosity=LearnAPI.default_verbosity())
365365

366366
x, y = data
367367
rng = deepcopy(learner.rng)
@@ -426,7 +426,7 @@ function LearnAPI.update(
426426
model::StumpRegressorFitted,
427427
data, # ignored as cached
428428
replacements::Pair{Symbol}...;
429-
verbosity=1,
429+
verbosity=LearnAPI.default_verbosity(),
430430
)
431431

432432
learner_old = LearnAPI.learner(model)
@@ -490,8 +490,9 @@ function LearnAPI.update(
490490

491491
end
492492

493-
# needed, because model is supervised:
494-
LearnAPI.target(learner::StumpRegressor, observations) = last(observations)
493+
# training data deconstructors:
494+
LearnAPI.features(learner::StumpRegressor, data) = first(data)
495+
LearnAPI.target(learner::StumpRegressor, data) = last(data)
495496

496497
LearnAPI.predict(model::StumpRegressorFitted, ::Point, x) =
497498
_predict(model.forest, x)

src/learners/gradient_descent.jl

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ using StableRNGs
1010
import Optimisers
1111
import Zygote
1212
import NNlib
13+
import CategoricalArrays
1314
import CategoricalDistributions
1415
import CategoricalDistributions: pdf, mode
1516
import ComponentArrays
@@ -55,7 +56,7 @@ for the specified number of `epochs`.
5556
- `perceptron`: component array with components `weights` and `bias`
5657
- `optimiser`: optimiser from Optimiser.jl
5758
- `X`: feature matrix, of size `(p, n)`
58-
- `y_hot`: one-hot encoded target, of size `(nclasses, n)`
59+
- `y_hot`: one-hot encoded target, of size `(nlevels, n)`
5960
- `epochs`: number of epochs
6061
- `state`: optimiser state
6162
@@ -108,7 +109,7 @@ point predictions with `predict(model, Point(), Xnew)`.
108109
109110
# Warm restart options
110111
111-
update(model, newdata, :epochs=>n, other_replacements...; verbosity=1)
112+
update(model, newdata, :epochs=>n, other_replacements...)
112113
113114
If `Δepochs = n - perceptron.epochs` is non-negative, then return an updated model, with
114115
the weights and bias of the previously learned perceptron used as the starting state in
@@ -117,7 +118,7 @@ instead of the previous training data. Any other hyperparaameter `replacements`
117118
adopted. If `Δepochs` is negative or not specified, instead return `fit(learner,
118119
newdata)`, where `learner=LearnAPI.clone(learner; epochs=n, replacements....)`.
119120
120-
update_observations(model, newdata, replacements...; verbosity=1)
121+
update_observations(model, newdata, replacements...)
121122
122123
Return an updated model, with the weights and bias of the previously learned perceptron
123124
used as the starting state in new gradient descent updates. Adopt any specified
@@ -132,38 +133,38 @@ PerceptronClassifier(; epochs=50, optimiser=Optimisers.Adam(), rng=Random.defaul
132133
struct PerceptronClassifierObs
133134
X::Matrix{Float32}
134135
y_hot::BitMatrix # one-hot encoded target
135-
classes # the (ordered) pool of `y`, as `CategoricalValue`s
136+
levels # the (ordered) pool of `y`, as `CategoricalValue`s
136137
end
137138

138139
# For pre-processing the training data:
139140
function LearnAPI.obs(::PerceptronClassifier, data::Tuple)
140141
X, y = data
141-
classes = CategoricalDistributions.classes(y)
142-
y_hot = classes .== permutedims(y) # one-hot encoding
143-
return PerceptronClassifierObs(X, y_hot, classes)
142+
levels = CategoricalArrays.levels(y)
143+
y_hot = levels .== permutedims(y) # one-hot encoding
144+
return PerceptronClassifierObs(X, y_hot, levels)
144145
end
145146
LearnAPI.obs(::PerceptronClassifier, observations::PerceptronClassifierObs) =
146147
observations # involutivity
147148

148149
# helper:
149-
function decode(y_hot, classes)
150+
function decode(y_hot, levels)
150151
n = size(y_hot, 2)
151-
[only(classes[y_hot[:,i]]) for i in 1:n]
152+
[only(levels[y_hot[:,i]]) for i in 1:n]
152153
end
153154

154155
# implement `RadomAccess()` interface for output of `obs`:
155156
Base.length(observations::PerceptronClassifierObs) = size(observations.y_hot, 2)
156157
Base.getindex(observations::PerceptronClassifierObs, I) = PerceptronClassifierObs(
157158
observations.X[:, I],
158159
observations.y_hot[:, I],
159-
observations.classes,
160+
observations.levels,
160161
)
161162

162163
# training data deconstructors:
163164
LearnAPI.target(
164165
learner::PerceptronClassifier,
165166
observations::PerceptronClassifierObs,
166-
) = decode(observations.y_hot, observations.classes)
167+
) = decode(observations.y_hot, observations.levels)
167168
LearnAPI.target(learner::PerceptronClassifier, data) =
168169
LearnAPI.target(learner, obs(learner, data))
169170
LearnAPI.features(
@@ -184,7 +185,7 @@ struct PerceptronClassifierFitted
184185
learner::PerceptronClassifier
185186
perceptron # component array storing weights and bias
186187
state # optimiser state
187-
classes # target classes
188+
levels # target levels
188189
losses
189190
end
190191

@@ -194,7 +195,7 @@ LearnAPI.learner(model::PerceptronClassifierFitted) = model.learner
194195
function LearnAPI.fit(
195196
learner::PerceptronClassifier,
196197
observations::PerceptronClassifierObs;
197-
verbosity=1,
198+
verbosity=LearnAPI.default_verbosity(),
198199
)
199200

200201
# unpack hyperparameters:
@@ -205,20 +206,20 @@ function LearnAPI.fit(
205206
# unpack data:
206207
X = observations.X
207208
y_hot = observations.y_hot
208-
classes = observations.classes
209-
nclasses = length(classes)
209+
levels = observations.levels
210+
nlevels = length(levels)
210211

211212
# initialize bias and weights:
212-
weights = randn(rng, Float32, nclasses, p)
213-
bias = zeros(Float32, nclasses)
213+
weights = randn(rng, Float32, nlevels, p)
214+
bias = zeros(Float32, nlevels)
214215
perceptron = (; weights, bias) |> ComponentArrays.ComponentArray
215216

216217
# initialize optimiser:
217218
state = Optimisers.setup(optimiser, perceptron)
218219

219220
perceptron, state, losses = corefit(perceptron, X, y_hot, epochs, state, verbosity)
220221

221-
return PerceptronClassifierFitted(learner, perceptron, state, classes, losses)
222+
return PerceptronClassifierFitted(learner, perceptron, state, levels, losses)
222223
end
223224

224225
# `fit` for unprocessed data:
@@ -230,16 +231,16 @@ function LearnAPI.update_observations(
230231
model::PerceptronClassifierFitted,
231232
observations_new::PerceptronClassifierObs,
232233
replacements...;
233-
verbosity=1,
234+
verbosity=LearnAPI.default_verbosity(),
234235
)
235236

236237
# unpack data:
237238
X = observations_new.X
238239
y_hot = observations_new.y_hot
239-
classes = observations_new.classes
240-
nclasses = length(classes)
240+
levels = observations_new.levels
241+
nlevels = length(levels)
241242

242-
classes == model.classes || error("New training target has incompatible classes.")
243+
levels == model.levels || error("New training target has incompatible levels.")
243244

244245
learner_old = LearnAPI.learner(model)
245246
learner = LearnAPI.clone(learner_old, replacements...)
@@ -252,7 +253,7 @@ function LearnAPI.update_observations(
252253
perceptron, state, losses_new = corefit(perceptron, X, y_hot, epochs, state, verbosity)
253254
losses = vcat(losses, losses_new)
254255

255-
return PerceptronClassifierFitted(learner, perceptron, state, classes, losses)
256+
return PerceptronClassifierFitted(learner, perceptron, state, levels, losses)
256257
end
257258
LearnAPI.update_observations(model::PerceptronClassifierFitted, data, args...; kwargs...) =
258259
update_observations(model, obs(LearnAPI.learner(model), data), args...; kwargs...)
@@ -262,16 +263,16 @@ function LearnAPI.update(
262263
model::PerceptronClassifierFitted,
263264
observations::PerceptronClassifierObs,
264265
replacements...;
265-
verbosity=1,
266+
verbosity=LearnAPI.default_verbosity(),
266267
)
267268

268269
# unpack data:
269270
X = observations.X
270271
y_hot = observations.y_hot
271-
classes = observations.classes
272-
nclasses = length(classes)
272+
levels = observations.levels
273+
nlevels = length(levels)
273274

274-
classes == model.classes || error("New training target has incompatible classes.")
275+
levels == model.levels || error("New training target has incompatible levels.")
275276

276277
learner_old = LearnAPI.learner(model)
277278
learner = LearnAPI.clone(learner_old, replacements...)
@@ -289,7 +290,7 @@ function LearnAPI.update(
289290
corefit(perceptron, X, y_hot, Δepochs, state, verbosity)
290291
losses = vcat(losses, losses_new)
291292

292-
return PerceptronClassifierFitted(learner, perceptron, state, classes, losses)
293+
return PerceptronClassifierFitted(learner, perceptron, state, levels, losses)
293294
end
294295
LearnAPI.update(model::PerceptronClassifierFitted, data, args...; kwargs...) =
295296
update(model, obs(LearnAPI.learner(model), data), args...; kwargs...)
@@ -299,9 +300,9 @@ LearnAPI.update(model::PerceptronClassifierFitted, data, args...; kwargs...) =
299300

300301
function LearnAPI.predict(model::PerceptronClassifierFitted, ::Distribution, Xnew)
301302
perceptron = model.perceptron
302-
classes = model.classes
303+
levels = model.levels
303304
probs = perceptron.weights*Xnew .+ perceptron.bias |> NNlib.softmax
304-
return CategoricalDistributions.UnivariateFinite(classes, probs')
305+
return CategoricalDistributions.UnivariateFinite(levels, probs')
305306
end
306307

307308
LearnAPI.predict(model::PerceptronClassifierFitted, ::Point, Xnew) =

0 commit comments

Comments
 (0)