Skip to content

Commit add7900

Browse files
authored
Merge pull request #8 from JuliaAI/dev
For a 0.1.0 release (second attempt after repository name change)
2 parents 06b87a6 + 0b6da1f commit add7900

File tree

13 files changed

+444
-200
lines changed

13 files changed

+444
-200
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name = "MLJTest"
1+
name = "MLJTestIntegration"
22
uuid = "697918b4-fdc1-4f9e-8ff9-929724cee270"
33
authors = ["Anthony D. Blaom <[email protected]>"]
44
version = "0.1.0"
@@ -9,4 +9,4 @@ Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
99

1010
[compat]
1111
MLJ = "0.18"
12-
julia = "1.6"
12+
julia = "1.6"

README.md

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# MLJTest.jl
1+
# MLJTestIntegration.jl
22

33
Package for applying integration tests to models implementing the
44
[MLJ](https://alan-turing-institute.github.io/MLJ.jl/dev/) model
55
interface.
66

7-
[![Lifecycle:Experimental](https://img.shields.io/badge/Lifecycle-Experimental-339999)](https://github.com/bcgov/repomountie/blob/master/doc/lifecycle-badges.md) [![Build Status](https://github.com/JuliaAI/MLJTest.jl/workflows/CI/badge.svg)](https://github.com/JuliaAI/MLJTest.jl/actions) [![Coverage](https://codecov.io/gh/JuliaAI/MLJTest.jl/branch/master/graph/badge.svg)](https://codecov.io/github/JuliaAI/MLJTest.jl?branch=master)
7+
[![Lifecycle:Experimental](https://img.shields.io/badge/Lifecycle-Experimental-339999)](https://github.com/bcgov/repomountie/blob/master/doc/lifecycle-badges.md) [![Build Status](https://github.com/JuliaAI/MLJTestIntegration.jl/workflows/CI/badge.svg)](https://github.com/JuliaAI/MLJTestIntegration.jl/actions) [![Coverage](https://codecov.io/gh/JuliaAI/MLJTestIntegration.jl/branch/master/graph/badge.svg)](https://codecov.io/github/JuliaAI/MLJTestIntegration.jl?branch=master)
88

99
# Installation
1010

1111
```julia
1212
using Pkg
13-
Pkg.add("MLJTest")
13+
Pkg.add("MLJTestIntegration")
1414
```
1515

1616
# Usage
@@ -19,8 +19,52 @@ This package provides a single method for testing a collection of
1919
`models` (types or named tuples with keys `:name` and `:package_name`)
2020
using the specified training `data`:
2121

22+
```julia
23+
MLJTestIntegration.test(models, data...; mod=Main, level=2, throw=false, verbosity=1)
24+
-> failures, summary
2225
```
23-
MLJTest.test(models, data...; verbosity=1, mod=Main, loading_only=false) -> failures, summary
26+
27+
For detailed documentation, run `using MLJTestIntegration; @doc MLJTestIntegration.test`.
28+
29+
30+
# Examples
31+
32+
## Testing models in a new MLJ model interface implementation
33+
34+
The following tests the model interface implemented by some model type
35+
`MyClassifier`, as might appear in tests for a package providing that
36+
type:
37+
38+
```julia
39+
import MLJTestIntegration
40+
using Test
41+
X, y = MLJTestIntegration.MLJ.make_blobs()
42+
failures, summary = MLJTestIntegration.test([MyClassifier, ], X, y, verbosity=1, mod=@__MODULE__)
43+
@test isempty(failures)
2444
```
2545

26-
See the method document string for details.
46+
## Testing models after filtering models in the registry
47+
48+
The following applies comprehensive integration tests to all
49+
regressors provided by the package GLM.jl appearing in the MLJ Model
50+
Registry. Since GLM.jl models are provided through the interface
51+
package `MLJGLMInterface`, this must be in the current environment:
52+
53+
```julia
54+
Pkg.add("MLJGLMInterface")
55+
import MLJBase, MLJTestIntegration
56+
using DataFrames # to view summary
57+
X, y = MLJTestIntegration.MLJ.make_regression();
58+
regressors = MLJTestIntegration.MLJ.models(matching(X, y)) do m
59+
m.package_name == "GLM"
60+
end
61+
62+
# to test code loading *and* load code:
63+
MLJTestIntegration.test(regressors, X, y, verbosity=1, mod=@__MODULE__, level=1)
64+
65+
# comprehensive tests:
66+
failures, summary =
67+
MLJTestIntegration.test(regressors, X, y, verbosity=3, mod=@__MODULE__, level=1)
68+
69+
summary |> DataFrame
70+
```

examples/bigtest/Project.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[deps]
2+
BetaML = "024491cd-cc6b-443e-8034-08ea7eb7db2b"
3+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
4+
EvoTrees = "f6006082-12f8-11e9-0c9c-0d5d367ab1e5"
5+
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
6+
LightGBM = "7acf609c-83a4-11e9-1ffb-b912bcd3b04a"
7+
MLJBase = "a7f614a8-145f-11e9-1d2a-a57a1082229d"
8+
MLJClusteringInterface = "d354fa79-ed1c-40d4-88ef-b8c7bd1568af"
9+
MLJDecisionTreeInterface = "c6f25543-311c-4c74-83dc-3ea6d1015661"
10+
MLJFlux = "094fc8d1-fd35-5302-93ea-dabda2abf845"
11+
MLJGLMInterface = "caf8df21-4939-456d-ac9c-5fefbfb04c0c"
12+
MLJLIBSVMInterface = "61c7150f-6c77-4bb1-949c-13197eac2a52"
13+
MLJLinearModels = "6ee0df7b-362f-4a72-a706-9e79364fb692"
14+
MLJModels = "d491faf4-2d78-11e9-2867-c94bc002c0b7"
15+
MLJMultivariateStatsInterface = "1b6a4a23-ba22-4f51-9698-8599985d3728"
16+
MLJNaiveBayesInterface = "33e4bacb-b9e2-458e-9a13-5d9a90b235fa"
17+
MLJScikitLearnInterface = "5ae90465-5518-4432-b9d2-8a1def2f0cab"
18+
MLJTSVDInterface = "7fa162e1-0e29-41ca-a6fa-c000ca4e7e7e"
19+
MLJTestIntegration = "697918b4-fdc1-4f9e-8ff9-929724cee270"
20+
MLJText = "5e27fcf9-6bac-46ba-8580-b5712f3d6387"
21+
MLJXGBoostInterface = "54119dfa-1dab-4055-a167-80440f4f7a91"
22+
NearestNeighborModels = "636a865e-7cf4-491e-846c-de09b730eb36"
23+
OneRule = "90484964-6d6a-4979-af09-8657dbed84ff"
24+
OutlierDetectionNeighbors = "51249a0a-cb36-4849-8e04-30c7f8d311bb"
25+
OutlierDetectionNetworks = "c7f57e37-4fcb-4a0b-a36c-c2204bc839a7"
26+
OutlierDetectionPython = "2449c660-d36c-460e-a68b-92ab3c865b3e"
27+
ParallelKMeans = "42b8e9d4-006b-409a-8472-7f34b3fb58af"
28+
PartialLeastSquaresRegressor = "f4b1acfe-f311-436c-bb79-8483f53c17d5"
29+
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
30+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
31+
32+
[extras]
33+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

examples/bigtest/notebook.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# **Assumption.** This file has not been separated from the
2+
# Project.toml file that
3+
# [originally](https://github.com/JuliaAI/MLJTestIntegration.jl/blob/dev/examples/bigtest/Project.toml)
4+
# accompanied it.
5+
6+
using Pkg
7+
Pkg.activate(@__DIR__)
8+
Pkg.instantiate()
9+
10+
using MLJTestIntegration
11+
using MLJModels
12+
using Test
13+
using DataFrames # for displaying tables
14+
15+
if false
16+
# # Regression
17+
18+
known_issues = models() do model
19+
any([
20+
# https://github.com/lalvim/PartialLeastSquaresRegressor.jl/issues/29
21+
model.package_name == "PartialLeastSquaresRegressor",
22+
23+
#https://github.com/JuliaAI/MLJXGBoostInterface.jl/issues/17
24+
model.name == "XGBoostRegressor",
25+
])
26+
end
27+
28+
MLJTestIntegration.test_single_target_regressors(ignore=known_issues, level=1)
29+
fails, summary =
30+
MLJTestIntegration.test_single_target_regressors(ignore=known_issues, level=3)
31+
32+
@test isempty(fails)
33+
summary |> DataFrame
34+
end
35+
36+
37+
# # Classification
38+
39+
# https://github.com/alan-turing-institute/MLJ.jl/issues/939
40+
known_issues = [
41+
#
42+
(name = "DecisionTreeClassifier", package_name="BetaML"),
43+
(name = "NuSVC", package_name="LIBSVM"),
44+
(name="PegasosClassifier", package_name="BetaML"),
45+
(name="RandomForestClassifier", package_name="BetaML"),
46+
(name="SVMNuClassifier", package_name="ScikitLearn"),
47+
]
48+
49+
MLJTestIntegration.test_single_target_classifiers(ignore=known_issues, level=1)
50+
fails, summary =
51+
MLJTestIntegration.test_single_target_classifiers(ignore=known_issues, level=3)

src/MLJTest.jl renamed to src/MLJTestIntegration.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
module MLJTest
1+
module MLJTestIntegration
22

33
using MLJ
44
using Pkg
55

66
include("attemptors.jl")
77
include("test.jl")
8+
include("special_cases.jl")
89
include("dummy_model.jl")
910

1011
using .DummyModel

src/attemptors.jl

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
str(model_metadata) = "$(model_metadata.name) from $(model_metadata.package_name)"
2-
31
"""
4-
attempt(f, message="")
2+
attempt(f, message; throw=false)
53
64
Return `(f(), "✓") if `f()` executes without throwing an
75
exception. Otherwise, return `(ex, "×"), where `ex` is the exception
8-
thrown.
6+
caught. Only truly throw the exception if `throw=true`.
97
108
If `message` is not empty, then it is logged to `Info`, together with
119
the second return value ("✓" or "×").
1210
11+
1312
"""
14-
function attempt(f, message="")
13+
function attempt(f, message; throw=false)
1514
ret = try
1615
(f(), "")
1716
catch ex
17+
throw && Base.throw(ex)
1818
(ex, "×")
1919
end
2020
isempty(message) || @info message*last(ret)
@@ -30,7 +30,7 @@ finalize(message, verbosity) = verbosity < 2 ? "" : message
3030
# is updated to 0.16. And delete the two methods immediately
3131
# following. What's required will already be in MLJModels 0.15.10, but
3232
# the current implementation avoids an explicit MLJModels dependency
33-
# for MLJTest.
33+
# for MLJTestIntegration.
3434
load_path(model_type) = MLJ.load_path(model_type)
3535
function load_path(proxy::NamedTuple)
3636
handle = (name=proxy.name, pkg=proxy.package_name)
@@ -39,11 +39,11 @@ end
3939

4040
root(load_path) = split(load_path, '.') |> first
4141

42-
function model_type(proxy, mod; verbosity=1)
42+
function model_type(proxy, mod; throw=false, verbosity=1)
4343
# check interface package really is in current environment:
44-
message = "`[:model_type]` Loading model type "
45-
model_type, outcome = attempt(finalize(message, verbosity)) do
46-
load_path = MLJTest.load_path(proxy) # MLJ.load_path(proxy) *****
44+
message = "[:model_type] Loading model type "
45+
model_type, outcome = attempt(finalize(message, verbosity); throw) do
46+
load_path = MLJTestIntegration.load_path(proxy) # MLJ.load_path(proxy) *****
4747
load_path_ex = load_path |> Meta.parse
4848
api_pkg_ex = root(load_path) |> Symbol
4949
import_ex = :(import $api_pkg_ex)
@@ -61,39 +61,39 @@ function model_type(proxy, mod; verbosity=1)
6161
# above, `model_type`, was triggered because of API package is
6262
# missing from in environment.
6363
api_pkg = try
64-
load_path = MLJTest.load_path(proxy) # MLJ.load_path(proxy) *****
64+
load_path = MLJTestIntegration.load_path(proxy) # MLJ.load_path(proxy) *****
6565
api_pkg = root(load_path)
6666
catch
6767
nothing
6868
end
6969
if !isnothing(api_pkg) &&
7070
api_pkg != "unknown" &&
7171
contains(model_type.msg, "$api_pkg not found in")
72-
throw(model_type)
72+
Base.throw(model_type)
7373
end
7474
end
7575

7676
return model_type, outcome
7777
end
7878

79-
function model_instance(model_type; verbosity=1)
80-
message = "`[:model_instance]` Instantiating default model "
81-
attempt(finalize(message, verbosity)) do
79+
function model_instance(model_type; throw=false, verbosity=1)
80+
message = "[:model_instance] Instantiating default model "
81+
attempt(finalize(message, verbosity); throw) do
8282
model_type()
8383
end
8484
end
8585

86-
function fitted_machine(model, data...; verbosity=1)
87-
message = "`[:fitted_machine]` Fitting machine "
88-
attempt(finalize(message, verbosity)) do
86+
function fitted_machine(model, data...; throw=false, verbosity=1)
87+
message = "[:fitted_machine] Fitting machine "
88+
attempt(finalize(message, verbosity); throw) do
8989
mach = machine(model, data...)
9090
fit!(mach, verbosity=-1)
9191
end
9292
end
9393

94-
function operations(fitted_machine, data...; verbosity=1)
95-
message = "`[:operations]` Calling `predict`, `transform` and/or `inverse_transform` "
96-
attempt(finalize(message, verbosity)) do
94+
function operations(fitted_machine, data...; throw=false, verbosity=1)
95+
message = "[:operations] Calling `predict`, `transform` and/or `inverse_transform` "
96+
attempt(finalize(message, verbosity); throw) do
9797
operations = String[]
9898
methods = MLJ.implemented_methods(fitted_machine.model)
9999
if :predict in methods
@@ -112,30 +112,30 @@ function operations(fitted_machine, data...; verbosity=1)
112112
end
113113
end
114114

115-
function threshold_prediction(model, data...; verbosity=1)
116-
message = "`[:threshold_predictor]` Calling fit!/predict for threshold predictor "*
115+
function threshold_prediction(model, data...; throw=false, verbosity=1)
116+
message = "[:threshold_predictor] Calling fit!/predict for threshold predictor "*
117117
"test) "
118-
attempt(finalize(message, verbosity)) do
118+
attempt(finalize(message, verbosity); throw) do
119119
tmodel = BinaryThresholdPredictor(model)
120120
mach = machine(tmodel, data...)
121121
fit!(mach, verbosity=0)
122122
predict(mach, first(data))
123123
end
124124
end
125125

126-
function evaluation(measure, model, data...; verbosity=1)
127-
message = "`[:evaluation]` Evaluating performance "
128-
attempt(finalize(message, verbosity)) do
126+
function evaluation(measure, model, data...; throw=false, verbosity=1)
127+
message = "[:evaluation] Evaluating performance "
128+
attempt(finalize(message, verbosity); throw) do
129129
evaluate(model, data...;
130130
measure=measure,
131131
resampling=Holdout(),
132132
verbosity=0)
133133
end
134134
end
135135

136-
function tuned_pipe_evaluation(measure, model, data...; verbosity=1)
137-
message = "`[:tuned_pipe_evaluation]` Evaluating perfomance in a tuned pipeline "
138-
attempt(finalize(message, verbosity)) do
136+
function tuned_pipe_evaluation(measure, model, data...; throw=false, verbosity=1)
137+
message = "[:tuned_pipe_evaluation] Evaluating perfomance in a tuned pipeline "
138+
attempt(finalize(message, verbosity); throw) do
139139
pipe = identity |> model
140140
tuned_pipe = TunedModel(models=[pipe,],
141141
measure=measure)
@@ -145,8 +145,8 @@ function tuned_pipe_evaluation(measure, model, data...; verbosity=1)
145145
end
146146
end
147147

148-
function ensemble_prediction(model, data...; verbosity=1)
149-
attempt(finalize("`[:ensemble_prediction]` Ensembling ", verbosity)) do
148+
function ensemble_prediction(model, data...; throw=false, verbosity=1)
149+
attempt(finalize("[:ensemble_prediction] Ensembling ", verbosity); throw) do
150150
imodel = EnsembleModel(model=model,
151151
n=2)
152152
mach = machine(imodel, data...)
@@ -155,9 +155,9 @@ function ensemble_prediction(model, data...; verbosity=1)
155155
end
156156
end
157157

158-
function iteration_prediction(measure, model, data...; verbosity=1)
159-
message = "`[:iteration_prediction]` Iterating with controls "
160-
attempt(finalize(message, verbosity)) do
158+
function iteration_prediction(measure, model, data...; throw=false, verbosity=1)
159+
message = "[:iteration_prediction] Iterating with controls "
160+
attempt(finalize(message, verbosity); throw) do
161161
imodel = IteratedModel(model=model,
162162
measure=measure,
163163
controls=[Step(1),

src/dummy_model.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ end
142142
MMI.predict(::DummyIterativeModel, fitresult, Xnew) =
143143
[fitresult[c].avg for c in Xnew]
144144

145-
MMI.package_name(::Type{<:DummyIterativeModel}) = "MLJTest"
146-
MMI.load_path(::Type{<:DummyIterativeModel}) = "MLJTest.DummyIterativeModel"
145+
MMI.package_name(::Type{<:DummyIterativeModel}) = "MLJTestIntegration"
146+
MMI.load_path(::Type{<:DummyIterativeModel}) = "MLJTestIntegration.DummyIterativeModel"
147147
MMI.iteration_parameter(::Type{<:DummyIterativeModel}) = :n
148148
MMI.training_losses(::DummyIterativeModel, report) = report.training_losses
149149

0 commit comments

Comments
 (0)