From ffc69a79656d0dad8d6fd970702fcd9ca3895db2 Mon Sep 17 00:00:00 2001 From: "Anthony D. Blaom" Date: Fri, 22 Sep 2023 14:10:40 +1200 Subject: [PATCH 1/2] Remove deprecated code supporting @from_network and @pipeline --- src/MLJBase.jl | 13 +- src/composition/deprecated_abstract_types.jl | 40 -- .../learning_networks/deprecated_machines.jl | 440 ------------- src/composition/learning_networks/replace.jl | 23 - .../models/deprecated_from_network.jl | 272 -------- src/composition/models/deprecated_methods.jl | 74 --- .../models/deprecated_pipelines.jl | 11 - src/composition/models/inspection.jl | 44 -- src/machines.jl | 1 - src/operations.jl | 40 +- test/_models/simple_composite_model.jl | 43 +- .../learning_networks/deprecated_machines.jl | 168 ----- .../models/deprecated_from_network.jl | 621 ------------------ test/composition/models/deprecated_methods.jl | 459 ------------- test/composition/models/network_composite.jl | 2 +- test/operations.jl | 17 +- test/runtests.jl | 3 - 17 files changed, 15 insertions(+), 2256 deletions(-) delete mode 100644 src/composition/deprecated_abstract_types.jl delete mode 100644 src/composition/learning_networks/deprecated_machines.jl delete mode 100644 src/composition/models/deprecated_from_network.jl delete mode 100644 src/composition/models/deprecated_methods.jl delete mode 100644 src/composition/models/deprecated_pipelines.jl delete mode 100644 src/composition/models/inspection.jl delete mode 100644 test/composition/learning_networks/deprecated_machines.jl delete mode 100644 test/composition/models/deprecated_from_network.jl delete mode 100644 test/composition/models/deprecated_methods.jl diff --git a/src/MLJBase.jl b/src/MLJBase.jl index c186d1de..64678967 100644 --- a/src/MLJBase.jl +++ b/src/MLJBase.jl @@ -150,18 +150,13 @@ include("models.jl") include("sources.jl") include("machines.jl") -include("composition/deprecated_abstract_types.jl") include("composition/learning_networks/nodes.jl") include("composition/learning_networks/inspection.jl") include("composition/learning_networks/signatures.jl") -include("composition/learning_networks/deprecated_machines.jl") include("composition/learning_networks/replace.jl") -include("composition/models/deprecated_pipelines.jl") -include("composition/models/deprecated_methods.jl") include("composition/models/network_composite_types.jl") include("composition/models/network_composite.jl") -include("composition/models/deprecated_from_network.jl") include("composition/models/inspection.jl") include("composition/models/pipelines.jl") include("composition/models/transformed_target_model.jl") @@ -183,9 +178,7 @@ include("composition/models/stacking.jl") const EXTENDED_ABSTRACT_MODEL_TYPES = vcat( MLJBase.MLJModelInterface.ABSTRACT_MODEL_SUBTYPES, MLJBase.NETWORK_COMPOSITE_TYPES, # src/composition/models/network_composite_types.jl - MLJBase.COMPOSITE_TYPES, # src/composition/abstract_types.jl - MLJBase.SURROGATE_TYPES, # src/composition/abstract_types.jl - [:MLJType, :Model, :NetworkComposite, :Surrogate, :Composite], + [:MLJType, :Model, :NetworkComposite], ) # =================================================================== @@ -283,8 +276,8 @@ export machine, Machine, fit!, report, fit_only!, default_scitype_check_level, # datasets_synthetics.jl export make_blobs, make_moons, make_circles, make_regression -# composition (surrogates and composites are exported in composition): -export machines, sources, @from_network, @pipeline, Stack, +# composition +export machines, sources, Stack, glb, @tuple, node, @node, sources, origins, return!, nrows_at_source, machine, rebind!, nodes, freeze!, thaw!, Node, AbstractNode, Pipeline, diff --git a/src/composition/deprecated_abstract_types.jl b/src/composition/deprecated_abstract_types.jl deleted file mode 100644 index e71ef88e..00000000 --- a/src/composition/deprecated_abstract_types.jl +++ /dev/null @@ -1,40 +0,0 @@ -## COMPOSITE AND SURRUGOTE MODEL TYPES - -# For example, we want to define - -# abstract type ProbabilisticComposite <: Probabilistic end -# struct ProbabilisticSurrogate <: Probabilistic end -# Probabilistic() = ProbablisiticSurrogate() - -# but also want this for all the abstract `Model` subtypes: - -const COMPOSITE_TYPES = Symbol[] -const SURROGATE_TYPES = Symbol[] -const composite_types = Any[] -const surrogate_types = Any[] - -for T in MLJModelInterface.ABSTRACT_MODEL_SUBTYPES - composite_type_name = string(T, "Composite") |> Symbol - surrogate_type_name = string(T, "Surrogate") |> Symbol - - @eval(abstract type $composite_type_name <: $T end) - @eval(struct $surrogate_type_name <: $T end) - - push!(COMPOSITE_TYPES, composite_type_name) - push!(SURROGATE_TYPES, surrogate_type_name) - push!(composite_types, @eval($composite_type_name)) - push!(surrogate_types, @eval($surrogate_type_name)) - - # shorthand surrogate constructor: - @eval($T() = $surrogate_type_name()) -end - - -const Surrogate = Union{surrogate_types...} -const Composite = Union{composite_types...} - -MLJModelInterface.is_wrapper(::Type{<:Union{Composite,Surrogate}}) = true -MLJModelInterface.package_name(::Type{<:Union{Composite,Surrogate}}) = "MLJBase" -for T in surrogate_types - MLJModelInterface.load_path(::Type{T}) = string("MLJBase.", T) -end diff --git a/src/composition/learning_networks/deprecated_machines.jl b/src/composition/learning_networks/deprecated_machines.jl deleted file mode 100644 index 9080d196..00000000 --- a/src/composition/learning_networks/deprecated_machines.jl +++ /dev/null @@ -1,440 +0,0 @@ -# # SIGNATURES - -function _operation_part(signature) - ops = filter(in(OPERATIONS), keys(signature)) - return NamedTuple{ops}(map(op->getproperty(signature, op), ops)) -end -function _report_part(signature) - :report in keys(signature) || return NamedTuple() - return signature.report -end - -_operations(signature) = keys(_operation_part(signature)) - -function _nodes(signature) - return (values(_operation_part(signature))..., - values(_report_part(signature))...) -end - -function _call(nt::NamedTuple) - _call(n) = deepcopy(n()) - _keys = keys(nt) - _values = values(nt) - return NamedTuple{_keys}(_call.(_values)) -end - -""" - model_supertype(interface) - -Return, if this can be inferred, which of `Deterministic`, -`Probabilistic` and `Unsupervised` is the appropriate supertype for a -composite model obtained by exporting a learning network with the -specified learning network interface. - -$DOC_NETWORK_INTERFACES - -If a supertype cannot be inferred, `nothing` is returned. - -If the network with given `signature` is not exportable, this method -will not error but it will not a give meaningful return value either. - -**Private method.** - -""" -function model_supertype(signature) - - operations = _operations(signature) - - length(intersect(operations, (:predict_mean, :predict_median))) == 1 && - return Deterministic - - if :predict in operations - node = signature.predict - if node isa Source - return Deterministic - end - if node.machine !== nothing - model = node.machine.model - model isa Deterministic && return Deterministic - model isa Probabilistic && return Probabilistic - end - end - - return nothing - -end - - -# # FITRESULTS FOR COMPOSITE MODELS - -mutable struct CompositeFitresult - signature - glb - network_model_names - function CompositeFitresult(signature) - signature_node = glb(_nodes(signature)...) - new(signature, signature_node) - end -end -signature(c::CompositeFitresult) = getfield(c, :signature) -glb(c::CompositeFitresult) = getfield(c, :glb) - -# To accommodate pre-existing design (operations.jl) arrange -# that `fitresult.predict` returns the predict node, etc: -Base.propertynames(c::CompositeFitresult) = keys(signature(c)) -Base.getproperty(c::CompositeFitresult, name::Symbol) = - getproperty(signature(c), name) - - -# # LEARNING NETWORK MACHINES - -surrogate(::Type{<:Deterministic}) = Deterministic() -surrogate(::Type{<:Probabilistic}) = Probabilistic() -surrogate(::Type{<:Unsupervised}) = Unsupervised() -surrogate(::Type{<:Static}) = Static() - -caches_data_by_default(::Type{<:Surrogate}) = false - -const ERR_MUST_PREDICT = ArgumentError( - "You must specify at least `predict=`. ") -const ERR_MUST_TRANSFORM = ArgumentError( - "You must specify at least `transform=`. ") -const ERR_MUST_OPERATE = ArgumentError( - "You must specify at least one operation, as in `predict=`. ") -const ERR_MUST_SPECIFY_SOURCES = ArgumentError( - "You must specify at least one source `Xs`, as in "* - "`machine(surrogate_model, Xs, ...; kwargs...)`. ") -const ERR_BAD_SIGNATURE = ArgumentError( - "Only the following keyword arguments are supported in learning network "* - "machine constructors: `report` or one of: `$OPERATIONS`. ") -const ERR_EXPECTED_NODE_IN_SIGNATURE = ArgumentError( - "Learning network machine constructor syntax error. "* - "Did not enounter `Node` in place one was expected. ") - -function check_surrogate_machine(::Surrogate, signature, _sources) - isempty(_operations(signature)) && throw(ERR_MUST_OPERATE) - isempty(_sources) && throw(ERR_MUST_SPECIFY_SOURCES) - return nothing -end - -function check_surrogate_machine(::Union{Supervised,SupervisedAnnotator}, - signature, - _sources) - isempty(_operations(signature)) && throw(ERR_MUST_PREDICT) - length(_sources) > 1 || throw(err_supervised_nargs()) - return nothing -end - -function check_surrogate_machine(::Union{Unsupervised}, - signature, - _sources) - isempty(_operations(signature)) && throw(ERR_MUST_TRANSFORM) - length(_sources) < 2 || throw(err_unsupervised_nargs()) - return nothing -end - -const WARN_NETWORK_MACHINES_DEPRECATION = - "Learning network machines are deprecated. For the recommended way of exporting "* - "learning networks as new stand-alone model types, see the \"Learning Networks\" "* - "section of the MLJ manual. " - -function machine(model::Surrogate, _sources::Source...; depwarn=true, pair_itr...) - - depwarn && Base.depwarn(WARN_NETWORK_MACHINES_DEPRECATION, :machine, force=true) - - # named tuple, such as `(predict=yhat, transform=W)`: - signature = (; pair_itr...) - - # signature checks: - isempty(_operations(signature)) && throw(ERR_MUST_OPERATE) - for k in keys(signature) - if k in OPERATIONS - getproperty(signature, k) isa AbstractNode || - throw(ERR_EXPECTED_NODE_IN_SIGNATURE) - elseif k === :report - all(v->v isa AbstractNode, values(signature.report)) || - throw(ERR_EXPECTED_NODE_IN_SIGNATURE) - else - throw(ERR_BAD_SIGNATURE) - end - end - - check_surrogate_machine(model, signature, _sources) - - mach = Machine(model, _sources...) - - mach.fitresult = CompositeFitresult(signature) - - return mach - -end - -function machine(_sources::Source...; depwarn=true, pair_itr...) - - signature = (; pair_itr...) - - T = model_supertype(signature) - if T == nothing - @warn "Unable to infer surrogate model type. \n"* - "Using Deterministic(). To override specify "* - "surrogate model, as in "* - "`machine(Probabilistic(), ...)` or `machine(Interval(), ...)`" - model = Deterministic() - else - model = surrogate(T) - end - - return machine(model, _sources...; depwarn, pair_itr...) - -end - -""" - N = glb(mach::Machine{<:Union{Composite,Surrogate}}) - -A greatest lower bound for the nodes appearing in the learning network interface of -`mach`. - -$DOC_NETWORK_INTERFACES - -**Private method.** - -""" -glb(mach::Machine{<:Union{Composite,Surrogate}}) = glb(mach.fitresult) - -""" - report(fitresult::CompositeFitresult) - -Return a tuple combining the report from `fitresult.glb` (a `Node` report) with the -additions coming from nodes declared as report nodes in `fitresult.signature`, but without -merging the two. - -$DOC_NETWORK_INTERFACES - -**Private method** -""" -function report(fitresult::CompositeFitresult) - basic = report(glb(fitresult)) - additions = _call(_report_part(signature(fitresult))) - return (; basic, additions) -end - -""" - fit!(mach::Machine{<:Surrogate}; - rows=nothing, - acceleration=CPU1(), - verbosity=1, - force=false)) - -Train the complete learning network wrapped by the machine `mach`. - -More precisely, if `s` is the learning network signature used to -construct `mach`, then call `fit!(N)`, where `N` is a greatest lower -bound of the nodes appearing in the signature (values in the signature -that are not `AbstractNode` are ignored). For example, if `s = -(predict=yhat, transform=W)`, then call `fit!(glb(yhat, W))`. - -See also [`machine`](@ref) - -""" -function fit!(mach::Machine{<:Surrogate}; kwargs...) - glb = MLJBase.glb(mach) - fit!(glb; kwargs...) - mach.state += 1 - mach.report = Dict{Symbol,Any}(:fit => MLJBase.report(mach.fitresult)) - mach.old_model = deepcopy(mach.model) - return mach -end - -MLJModelInterface.fitted_params(mach::Machine{<:Surrogate}) = - fitted_params(glb(mach)) - - -# # CONSTRUCTING THE RETURN VALUE FOR A COMPOSITE FIT METHOD - -logerr_identical_models(name, model) = - "The hyperparameters $name of "* - "$model have identical model "* - "instances as values. " -const ERR_IDENTICAL_MODELS = ArgumentError( - "Two distinct hyper-parameters of a "* - "composite model that are both "* - "associated with models in the underlying learning "* - "network (eg, any two components of a `@pipeline` model) "* - "cannot have identical values, although they can be `==` "* - "(corresponding nested properties are `==`). "* - "Consider constructing instances "* - "separately or use `deepcopy`. ") - -# Identify which properties of `model` have, as values, a model in the -# learning network wrapped by `mach`, and check that no two such -# properties have have identical values (#377). Return the property name -# associated with each model in the network (in the order appearing in -# `models(glb(mach))`) using `nothing` when the model is not -# associated with any property. -network_model_names(model::Nothing, mach::Machine{<:Surrogate}) = nothing - -function network_model_names(model::M, mach::Machine{<:Surrogate}) where M<:Model - - network_model_ids = objectid.(MLJBase.models(glb(mach))) - - names = propertynames(model) - - # intialize dict to detect duplicity a la #377: - name_given_id = Dict{UInt64,Vector{Symbol}}() - - # identify location of properties whose values are models in the - # learning network, and build name_given_id: - for name in names - id = objectid(getproperty(model, name)) - if id in network_model_ids - if haskey(name_given_id, id) - push!(name_given_id[id], name) - else - name_given_id[id] = [name,] - end - end - end - - # perform #377 check: - no_duplicates = all(values(name_given_id)) do name - length(name) == 1 - end - if !no_duplicates - for (id, name) in name_given_id - if length(name) > 1 - @error logerr_identical_models(name, model) - end - end - throw(ERR_IDENTICAL_MODELS) - end - - return map(network_model_ids) do id - if id in keys(name_given_id) - return name_given_id[id] |> first - else - return nothing - end - end - -end - -const WARN_RETURN_DEPWARN = - "The use of `return!` is deprecated. For the recommended way of exporting "* - "learning networks as new stand-alone model types, see the \"Learning Networks\" "* - "section of the MLJ manual. " - -""" - - return!(mach::Machine{<:Surrogate}, model, verbosity; acceleration=CPU1()) - -The last call in custom code defining the `MLJBase.fit` method for a -new composite model type. Here `model` is the instance of the new type -appearing in the `MLJBase.fit` signature, while `mach` is a learning -network machine constructed using `model`. Not relevant when defining -composite models using `@pipeline` (deprecated) or `@from_network`. - -For usage, see the example given below. Specifically, the call does -the following: - -- Determines which hyper-parameters of `model` point to model - instances in the learning network wrapped by `mach`, for recording - in an object called `cache`, for passing onto the MLJ logic that - handles smart updating (namely, an `MLJBase.update` fallback for - composite models). - -- Calls `fit!(mach, verbosity=verbosity, acceleration=acceleration)`. - -- Records (among other things) a copy of `model` in a variable called `cache` - -- Returns `cache` and outcomes of training in an appropriate form - (specifically, `(mach.fitresult, cache, mach.report)`; see [Adding - Models for General - Use](https://alan-turing-institute.github.io/MLJ.jl/dev/adding_models_for_general_use/) - for technical details.) - - -### Example - -The following code defines, "by hand", a new model type `MyComposite` -for composing standardization (whitening) with a deterministic -regressor: - -``` -mutable struct MyComposite <: DeterministicComposite - regressor -end - -function MLJBase.fit(model::MyComposite, verbosity, X, y) - Xs = source(X) - ys = source(y) - - mach1 = machine(Standardizer(), Xs) - Xwhite = transform(mach1, Xs) - - mach2 = machine(model.regressor, Xwhite, ys) - yhat = predict(mach2, Xwhite) - - mach = machine(Deterministic(), Xs, ys; predict=yhat) - return!(mach, model, verbosity) -end -``` - -""" -function return!(mach::Machine{<:Surrogate}, - model::Union{Model,Nothing}, - verbosity; - acceleration=CPU1(), depwarn=true) - - depwarn && Base.depwarn(WARN_RETURN_DEPWARN, :return!, force=true) - - network_model_names_ = network_model_names(model, mach) - - verbosity isa Nothing || fit!(mach, verbosity=verbosity, acceleration=acceleration) - setfield!(mach.fitresult, :network_model_names, network_model_names_) - - # record the current hyper-parameter values: - old_model = deepcopy(model) - - glb = MLJBase.glb(mach) - cache = (; old_model) - - return mach.fitresult, cache, report_given_method(mach)[:fit] -end - - - -############################################################################### -##### SAVE AND RESTORE FOR COMPOSITES ##### -############################################################################### - - -# Returns a new `CompositeFitresult` that is a shallow copy of the original one. -function save(model::Composite, fitresult) - interface = MLJBase.signature(fitresult) - newsignature = replace(Signature(interface), serializable=true) |> unwrap - newfitresult = MLJBase.CompositeFitresult(newsignature) - setfield!( - newfitresult, - :network_model_names, - getfield(fitresult, :network_model_names) - ) - return newfitresult -end - - -# Restores a machine of a composite model by restoring all -# submachines contained in it. -function restore!(mach::Machine{<:Composite}) - glb_node = glb(mach) - for submach in machines(glb_node) - restore!(submach) - end - mach.state = 1 - return mach -end - -function report_for_serialization(mach::Machine{<:Composite}) - basic = report(glb(mach.fitresult)) - additions = report_given_method(mach)[:fit].additions - return Dict{Symbol,Any}(:fit => (; basic, additions)) -end diff --git a/src/composition/learning_networks/replace.jl b/src/composition/learning_networks/replace.jl index 3c0cc248..175c4b91 100644 --- a/src/composition/learning_networks/replace.jl +++ b/src/composition/learning_networks/replace.jl @@ -177,29 +177,6 @@ function Base.replace(signature::Signature, pairs::Pair...; node_dict=false, kwa return newsignature, newnode_given_old end -""" - replace(mach, a1=>b1, a2=>b2, ...; options...) - -Return a copy the learning network machine `mach`, and it's underlying learning network, -but replacing any specified sources and models `a1, a2, ...` of the original underlying -network with `b1, b2, ...`. - -$DOC_REPLACE_OPTIONS - -""" -function Base.replace(mach::Machine{<:Surrogate}, pairs::Pair...; kwargs...) - signature = MLJBase.signature(mach.fitresult) |> Signature - - newsignature, newnode_given_old = - replace(signature, pairs...; node_dict=true, kwargs...) - - newinterface = unwrap(newsignature) - - newargs = [newnode_given_old[arg] for arg in mach.args] - - return machine(mach.model, newargs...; newinterface...) -end - # Copy the complete learning network having `W` as a greatest lower bound, executing the # specified replacements, and return the dictionary mapping old nodes to new nodes. function _replace( diff --git a/src/composition/models/deprecated_from_network.jl b/src/composition/models/deprecated_from_network.jl deleted file mode 100644 index dfe27807..00000000 --- a/src/composition/models/deprecated_from_network.jl +++ /dev/null @@ -1,272 +0,0 @@ -## EXPORTING LEARNING NETWORKS AS MODELS WITH @from_network - -# closure to generate the fit methods for exported composite. Here -# `mach` is a learning network machine. -function fit_method(mach, models...) - - signature = mach.fitresult - mach_args = mach.args - - function _fit(model, verbosity::Integer, args...) - length(args) > length(mach_args) && - throw(ArgumentError("$M does not support more than "* - "$(length(mach_args)) training arguments")) - replacement_models = [getproperty(model, fld) - for fld in propertynames(model)] - model_replacements = [models[j] => replacement_models[j] - for j in eachindex(models)] - source_replacements = [mach_args[i] => source(args[i]) - for i in eachindex(args)] - replacements = vcat(model_replacements, source_replacements) - - new_mach = - replace(mach, replacements...; empty_unspecified_sources=true) - - return!(new_mach, model, verbosity; depwarn=false) - end - - return _fit -end - -net_error(message) = throw(ArgumentError("Learning network export error.\n"* - string(message))) -net_error(k::Int) = throw(ArgumentError("Learning network export error $k. ")) - -_insert_subtyping(ex, subtype_ex) = - Expr(:(<:), ex, subtype_ex) - -# create the exported type symbol, e.g. abstract_type(T) == Unsupervised -# would result in :UnsupervisedComposite -_exported_type(T::Model) = Symbol(nameof(abstract_type(T)), :Composite) - -function eval_and_reassign(modl, ex) - s = gensym() - evaluated = modl.eval(ex) - if evaluated isa Symbol - hack = String(evaluated) - modl.eval(:($s = Symbol($hack))) - else - modl.eval(:($s = $evaluated)) - end - return s, evaluated -end - -function without_line_numbers(block_ex) - block_ex.head == :block || throw(ArgumentError) - args = filter(block_ex.args) do arg - !(arg isa LineNumberNode) - end - return Expr(:block, args...) -end - -function from_network_preprocess(modl, mach_ex, block_ex) - - mach_ex, mach = eval_and_reassign(modl, mach_ex) - mach isa Machine{<:Surrogate} || - net_error("$mach is not a learning network machine. ") - if block_ex.head == :block - block_ex = without_line_numbers(block_ex) - struct_ex = block_ex.args[1] - trait_declaration_exs = block_ex.args[2:end] - elseif block_ex.head == :struct - struct_ex = block_ex - trait_declaration_exs = [] - else - net_error("Expected `struct`, `mutable struct` or "* - "`begin ... end` block, but got `$block_ex` ") - end - - # if necessary add or modify struct subtyping: - if struct_ex.args[2] isa Symbol - struct_ex.args[2] = _insert_subtyping(struct_ex.args[2], - _exported_type(mach.model)) - modeltype_ex = struct_ex.args[2].args[1] - elseif struct_ex.args[2] isa Expr - struct_ex.args[2].head == :(<:) || - net_error("Badly formed `struct` subtying. ") - modeltype_ex = struct_ex.args[2].args[1] - super = eval(struct_ex.args[2].args[2]) - inferred_super_ex = _exported_type(mach.model) - if !(super <: Composite) - @warn "New composite type must subtype `Composite` but "* - "`$super` does not. Instead declaring "* - "`$modeltype_ex <: $inferred_super_ex`. " - struct_ex.args[2].args[2] = inferred_super_ex - end - else - net_error(41) - end - - # test if there are no fields: - field_exs = without_line_numbers(struct_ex.args[3]).args - no_fields = isempty(field_exs) - - # extract trait definitions: - trait_ex_given_name_ex = Dict{Symbol,Any}() - - ne() = net_error("Bad trait declaration. ") - for ex in trait_declaration_exs - ex isa Expr || ne() - ex.head == :(=) || ne() - ex.args[1] isa Symbol || ne() - ex.args[1] in MLJModelInterface.MODEL_TRAITS || - net_error("Expected a model trait as keywork but "* - "got $(ex.args[2]). Options are:\n"* - "$MLJModelInterface.MODEL_TRAIES. ") - length(ex.args) == 2 || ne() - trait_ex_given_name_ex[ex.args[1]] = ex.args[2] - end - - return mach_ex, modeltype_ex, struct_ex, no_fields, trait_ex_given_name_ex - -end - -function from_network_(modl, - mach_ex, - modeltype_ex, - struct_ex, - no_fields, - trait_ex_given_name_ex) - - args = gensym(:args) - models = gensym(:models) - instance = gensym(:instance) - - # Define the new model type with keyword constructor: - if no_fields - modl.eval(struct_ex) - else - modl.eval(MLJBase.Parameters.with_kw(struct_ex, modl, false)) - end - - # Test that an instance can be created: - try - modl.eval(:($modeltype_ex())) - catch e - @error "Problem instantiating a default instance of the "* - "new composite type. Each field name in the struct expression "* - "must have a corresponding model instance (that also appears "* - "somewhere in the network). "* - "Perhaps you forgot to specify one of these?" - throw(e) - end - - # code defining fit method: - program1 = quote - - $(isdefined(modl, :MLJ) ? :(import MLJ.MLJBase) : :(import MLJBase)) - $(isdefined(modl, :MLJ) ? :(import MLJ.MLJBase.MLJModelInterface) : - :(import MLJBase.MLJModelInterface)) - - $instance = $modeltype_ex() - $models = [getproperty($instance, name) - for name in fieldnames($modeltype_ex)] - - MLJModelInterface.fit(model::$modeltype_ex, verb::Integer, $args...) = - MLJBase.fit_method($mach_ex, $models...)(model, verb, $args...) - - end - - modl.eval(program1) - - # define composite model traits: - for (name_ex, value_ex) in trait_ex_given_name_ex - program = quote - MLJBase.$name_ex(::Type{<:$modeltype_ex}) = $value_ex - end - modl.eval(program) - end - - return nothing - -end - -const WARN_FROM_NETWORK_DEPRECATION = - "The `@from_network` macro is deprecated. See the \"Learning Networks\" section "* - "of the MLJ manual for recommended way to export learning networks as new "* - "composite model types. " - -""" - - @from_network mach [mutable] struct NewCompositeModel - ... - end - -or - - @from_network mach begin - [mutable] struct NewCompositeModel - ... - end - - end - -Create a new stand-alone model type called `NewCompositeModel`, using -the specified learning network machine `mach` as a blueprint. - -For more on learning network machines, see [`machine`](@ref). - - -### Example - -Consider the following simple learning network for training a decision -tree after one-hot encoding the inputs, and forcing the predictions to -be point-predictions (rather than probabilistic): - -```julia -Xs = source() -ys = source() - -hot = OneHotEncoder() -tree = DecisionTreeClassifier() - -W = transform(machine(hot, Xs), Xs) -yhat = predict_mode(machine(tree, W, ys), W) -``` - -A learning network machine is defined by - -```julia -mach = machine(Deterministic(), Xs, ys; predict=yhat) -``` - -To specify a new `Deterministic` composite model type `WrappedTree` we -specify the model instances appearing in the network as "default" -values in the following decorated struct definition: - -```julia -@from_network mach struct WrappedTree - encoder=hot - decision_tree=tree -end -``` -and create a new instance with `WrappedTree()`. - -To allow the second model component to be replaced by any other -probabilistic model we instead make a mutable struct declaration and, -if desired, annotate types appropriately. In the following code -illustration some model trait declarations have also been added: - -```julia -@from_network mach begin - mutable struct WrappedTree - encoder::OneHotEncoder=hot - classifier::Probabilistic=tree - end - input_scitype = Table(Continuous, Finite) - is_pure_julia = true -end -``` - -""" -macro nodepwarn_from_network(exs...) - args = from_network_preprocess(__module__, exs...) - modeltype_ex = args[2] - from_network_(__module__, args...) -end -macro from_network(exs...) - Base.depwarn(WARN_FROM_NETWORK_DEPRECATION, :from_network, force=true) - args = from_network_preprocess(__module__, exs...) - modeltype_ex = args[2] - from_network_(__module__, args...) -end diff --git a/src/composition/models/deprecated_methods.jl b/src/composition/models/deprecated_methods.jl deleted file mode 100644 index 38b48e69..00000000 --- a/src/composition/models/deprecated_methods.jl +++ /dev/null @@ -1,74 +0,0 @@ -## FALL-BACK METHODS FOR COMPOSITE MODELS (EXPORTED LEARNING NETWORKS) - -# *Note.* Be sure to read Note 4 in src/operations.jl to see see how -# fallbacks are provided for operations acting on Composite models. - -caches_data_by_default(::Type{<:Composite}) = true - -# builds on `fitted_params(::CompositeFitresult)` defined in -# composition/learning_networks/machines.jl: -fitted_params(::Union{Composite,Surrogate}, fitresult::CompositeFitresult) = - fitted_params(glb(fitresult)) - -function update(model::M, - verbosity::Integer, - fitresult::CompositeFitresult, - cache, - args...) where M <: Composite - - # This method falls back to `fit` to force rebuilding of the - # underlying learning network if, since the last fit: - # - # (i) Any hyper-parameter of `model` that has, as a value, a model in the network, has - # been replaced with a new value (and not merely mutated), OR - - # (ii) Any OTHER hyper-parameter has changed it's value (in the sense - # of `==`). - - # Otherwise, a "smart" fit is carried out by calling `fit!` on a - # greatest lower bound node for nodes in the signature of the - # underlying learning network machine. - - network_model_names = getfield(fitresult, :network_model_names) - - old_model = cache.old_model - glb = MLJBase.glb(fitresult) # greatest lower bound of network, a node - - if fallback(model, old_model, network_model_names, glb) - return fit(model, verbosity, args...) - end - - fit!(glb; verbosity=verbosity) - - # Retrieve additional report values - report = MLJBase.report(fitresult) - - # record current model state: - cache = (; old_model = deepcopy(model)) - - return (fitresult, - cache, - report) - -end - -# helper for preceding method (where logic is explained): -function fallback(model::M, old_model, network_model_names, glb_node) where M - # check the hyper-parameters corresponding to models: - network_models = MLJBase.models(glb_node) - for j in eachindex(network_models) - name = network_model_names[j] - name === nothing || - objectid(network_models[j])===objectid(getproperty(model, name)) || - return true - end - # check any other hyper-parameter: - for name in propertynames(model) - if !(name in network_model_names) - old_value = getproperty(old_model, name) - value = getproperty(model, name) - value == old_value || return true - end - end - return false -end diff --git a/src/composition/models/deprecated_pipelines.jl b/src/composition/models/deprecated_pipelines.jl deleted file mode 100644 index fbebd897..00000000 --- a/src/composition/models/deprecated_pipelines.jl +++ /dev/null @@ -1,11 +0,0 @@ -const ERR_PIPELINE = ErrorException( - "The `@pipeline` macro is deprecated. For pipelines without "* - "target transformations use pipe syntax, as in "* - "`ContinuousEncoder() |> Standardizer() |> my_classifier`. "* - "For details and advanced optioins, query the `Pipeline` docstring. "* - "To wrap a supervised model in a target transformation, use "* - "`TransformedTargetModel`, as in "* - "`TransformedTargetModel(my_regressor, target=Standardizer())`" -) - -macro pipeline(ex...) throw(ERR_PIPELINE) end diff --git a/src/composition/models/inspection.jl b/src/composition/models/inspection.jl deleted file mode 100644 index ece70326..00000000 --- a/src/composition/models/inspection.jl +++ /dev/null @@ -1,44 +0,0 @@ -## USER FRIENDLY INSPECTION OF COMPOSITE MACHINES - -try_scalarize(v) = length(v) == 1 ? v[1] : v - -function machines_given_model_name(mach::Machine{M}) where M<:Composite - network_model_names = getfield(mach.fitresult, :network_model_names) - names = unique(filter(name->!(name === nothing), network_model_names)) - glb = MLJBase.glb(mach) - network_models = MLJBase.models(glb) - network_machines = MLJBase.machines(glb) - ret = LittleDict{Symbol,Any}() - for name in names - mask = map(==(name), network_model_names) - _models = network_models[mask] - _machines = filter(mach->mach.model in _models, network_machines) - ret[name] = _machines - end - return ret -end - -function tuple_keyed_on_model_names(machines, mach, f) - dict = MLJBase.machines_given_model_name(mach) - names = tuple(keys(dict)...) - named_tuple_values = map(names) do name - [f(m) for m in dict[name]] |> try_scalarize - end - return NamedTuple{names}(named_tuple_values) -end - -function report(mach::Machine{<:Union{Composite,Surrogate}}) - report_additions = MLJBase.report_given_method(mach)[:fit].additions - report_basic = MLJBase.report_given_method(mach)[:fit].basic - report_components = mach isa Machine{<:Surrogate} ? NamedTuple() : - MLJBase.tuple_keyed_on_model_names(report_basic.machines, mach, MLJBase.report) - return merge(report_components, report_basic, report_additions) -end - -function fitted_params(mach::Machine{<:Composite}) - fp_basic = fitted_params(mach.model, mach.fitresult) - machines = fp_basic.machines - fp_components = - MLJBase.tuple_keyed_on_model_names(machines, mach, MLJBase.fitted_params) - return merge(fp_components, fp_basic) -end diff --git a/src/machines.jl b/src/machines.jl index 50544212..b6fabca6 100644 --- a/src/machines.jl +++ b/src/machines.jl @@ -682,7 +682,6 @@ function fit_only!( @error "Problem fitting the machine $mach. " _sources = sources(glb(mach.args...)) length(_sources) > 2 || - model isa Composite || all((!isempty).(_sources)) || @warn "Some learning network source nodes are empty. " @info "Running type checks... " diff --git a/src/operations.jl b/src/operations.jl index 7bd4da4d..9fab3999 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -168,43 +168,7 @@ const err_unsupported_operation(operation) = ErrorException( "network machine that does not support it. " ) -## SURROGATE AND COMPOSITE MODELS - - -for operation in [:predict, - :predict_joint, - :transform, - :inverse_transform] - ex = quote - function $operation(model::Union{Composite,Surrogate}, fitresult,X) - if hasproperty(fitresult, $(QuoteNode(operation))) - return fitresult.$operation(X) - else - throw(err_unsupported_operation($operation)) - end - end - end - eval(ex) -end - -for (operation, fallback) in [(:predict_mode, :mode), - (:predict_mean, :mean), - (:predict_median, :median)] - ex = quote - function $(operation)(m::Union{ProbabilisticComposite,ProbabilisticSurrogate}, - fitresult, - Xnew) - if hasproperty(fitresult, $(QuoteNode(operation))) - return fitresult.$(operation)(Xnew) - end - return $(fallback).(predict(m, fitresult, Xnew)) - end - end - eval(ex) -end - - -## NETWORKCOMPOSITE MODELS +## NETWORK COMPOSITE MODELS # In the case of `NetworkComposite` models, the `fitresult` is a learning network # signature. If we call a node in the signature (eg, do `fitresult.predict()`) then we may @@ -242,7 +206,7 @@ for (operation, fallback) in [(:predict_mode, :mode), return output_and_report(fitresult, $(QuoteNode(operation)), Xnew) end # The following line retuns a `Tuple` since `m` is a `NetworkComposite` - predictions, report = predict(m, fitresult, Xnew) + predictions, report = predict(m, fitresult, Xnew) return $(fallback).(predictions), report end end |> eval diff --git a/test/_models/simple_composite_model.jl b/test/_models/simple_composite_model.jl index 0ff413cb..09951d49 100644 --- a/test/_models/simple_composite_model.jl +++ b/test/_models/simple_composite_model.jl @@ -1,47 +1,38 @@ -export SimpleDeterministicCompositeModel, SimpleDeterministicNetworkCompositeModel, - SimpleProbabilisticCompositeModel, SimpleProbabilisticNetworkCompositeModel +export SimpleDeterministicNetworkCompositeModel, + SimpleProbabilisticNetworkCompositeModel using MLJBase const COMPOSITE_MODELS = [ - :SimpleDeterministicCompositeModel, - :SimpleProbabilisticCompositeModel, :SimpleDeterministicNetworkCompositeModel, :SimpleProbabilisticNetworkCompositeModel ] const REGRESSORS = Dict( - :SimpleDeterministicCompositeModel => :DeterministicConstantRegressor, :SimpleDeterministicNetworkCompositeModel => :DeterministicConstantRegressor, - :SimpleProbabilisticCompositeModel => :ConstantRegressor, :SimpleProbabilisticNetworkCompositeModel => :ConstantRegressor, ) const REGRESSOR_SUPERTYPES = Dict( - :SimpleDeterministicCompositeModel => :Deterministic, :SimpleDeterministicNetworkCompositeModel => :Deterministic, - :SimpleProbabilisticCompositeModel => :Probabilistic, :SimpleProbabilisticNetworkCompositeModel => :Probabilistic, ) const COMPOSITE_SUPERTYPES = Dict( - :SimpleDeterministicCompositeModel => :DeterministicComposite, :SimpleDeterministicNetworkCompositeModel => :DeterministicNetworkComposite, - :SimpleProbabilisticCompositeModel => :ProbabilisticComposite, :SimpleProbabilisticNetworkCompositeModel => :ProbabilisticNetworkComposite, ) - for model in COMPOSITE_MODELS regressor = REGRESSORS[model] regressor_supertype = REGRESSOR_SUPERTYPES[model] composite_supertype = COMPOSITE_SUPERTYPES[model] - quote + quote """ (model)(; regressor=$($(regressor))(), transformer=FeatureSelector()) Construct a composite model consisting of a transformer - (`Unsupervised` model) followed by a `$($(regressor_supertype))` model. Mainly - intended for internal testing . + (`Unsupervised` model) followed by a `$($(regressor_supertype))` model. + Intended for internal testing only. """ mutable struct $(model){ @@ -67,36 +58,18 @@ for model in COMPOSITE_MODELS is_pure_julia = true, is_wrapper = true ) - + MLJBase.input_scitype(::Type{<:$(model){L,T}}) where {L,T} = MLJBase.input_scitype(T) MLJBase.target_scitype(::Type{<:$(model){L,T}}) where {L,T} = MLJBase.target_scitype(L) - + end |> eval end ## FIT METHODS -for model in COMPOSITE_MODELS[1:2] - @eval function MLJBase.fit( - composite::$(model), verbosity::Integer, Xtrain, ytrain - ) - X = source(Xtrain) # instantiates a source node - y = source(ytrain) - - t = machine(composite.transformer, X) - Xt = transform(t, X) - l = machine(composite.model, Xt, y) - yhat = predict(l, Xt) - - mach = machine($(REGRESSOR_SUPERTYPES[model])(), X, y; predict=yhat) - - return!(mach, composite, verbosity) - end -end - -for model in COMPOSITE_MODELS[3:4] +for model in COMPOSITE_MODELS @eval function MLJBase.prefit( composite::$(model), verbosity::Integer, diff --git a/test/composition/learning_networks/deprecated_machines.jl b/test/composition/learning_networks/deprecated_machines.jl deleted file mode 100644 index bad68bd2..00000000 --- a/test/composition/learning_networks/deprecated_machines.jl +++ /dev/null @@ -1,168 +0,0 @@ -module TestLearningNetworkMachines - -const depwarn=false - -using Test -using ..Models -using ..TestUtilities -using MLJBase -using Tables -using StableRNGs -using Serialization -using StatisticalMeasures -rng = StableRNG(616161) - -# A dummy clustering model: -mutable struct DummyClusterer <: Unsupervised - n::Int -end -DummyClusterer(; n=3) = DummyClusterer(n) -function MLJBase.fit(model::DummyClusterer, verbosity::Int, X) - Xmatrix = Tables.matrix(X) - n = min(size(Xmatrix, 2), model.n) - centres = Xmatrix[1:n, :] - levels = categorical(1:n) - report = (centres=centres,) - fitresult = levels - return fitresult, nothing, report -end -MLJBase.transform(model::DummyClusterer, fitresult, Xnew) = - selectcols(Xnew, 1:length(fitresult)) -MLJBase.predict(model::DummyClusterer, fitresult, Xnew) = - [fill(fitresult[1], nrows(Xnew))...] - - -N = 20 -X = (a = rand(N), b = categorical(rand("FM", N))) - -@testset "signature helpers" begin - @test MLJBase._call(NamedTuple()) == NamedTuple() - a = source(:a) - b = source(:b) - W = source(:W) - yhat = source(:yhat) - s = (transform=W, - report=(a=a, b=b), - predict=yhat) - @test MLJBase._report_part(s) == (a=a, b=b) - @test MLJBase._operation_part(s) == (transform=W, predict=yhat) - @test MLJBase._nodes(s) == (W, yhat, a, b) - @test MLJBase._operations(s) == (:transform, :predict) - R = MLJBase._call(MLJBase._report_part(s)) - @test R.a == :a - @test R.b == :b -end - -@testset "wrapping a learning network in a machine" begin - - # unsupervised: - Xs = source(X) - W = transform(machine(OneHotEncoder(), Xs), Xs) - clust = DummyClusterer(n=2) - m = machine(clust, W) - yhat = predict(m, W) - Wout = transform(m, W) - rnode = source(:stuff) - - # test of `fitted_params(::NamedTuple)': - fit!(Wout, verbosity=0) - - @test_throws(MLJBase.ERR_BAD_SIGNATURE, - machine(Unsupervised(); - predict=yhat, - fitted_params=rnode, - depwarn) - ) - @test_throws(MLJBase.ERR_EXPECTED_NODE_IN_SIGNATURE, - machine(Unsupervised(); - predict=42, - depwarn) - ) - @test_throws(MLJBase.ERR_EXPECTED_NODE_IN_SIGNATURE, - machine(Unsupervised(), Xs; - predict=yhat, - transform=Wout, - report=(some_stuff=42,), - depwarn) - ) - mach = machine(Unsupervised(), Xs; - predict=yhat, - transform=Wout, - report=(some_stuff=rnode,), - depwarn) - @test mach.args == (Xs, ) - @test mach.args[1] == Xs - fit!(mach, force=true, verbosity=0) - Θ = mach.fitresult - @test Θ.predict == yhat - @test Θ.transform == Wout - Θ.report.some_stuff == rnode - @test report(mach).some_stuff == :stuff - @test report(mach).machines == fitted_params(mach).machines - - # supervised - y = rand("ab", N) |> categorical; - ys = source(y) - mm = machine(ConstantClassifier(), W, ys) - yhat = predict(mm, W) - e = @node auc(yhat, ys) - - @test_throws Exception machine(; predict=yhat, depwarn) - mach = machine(Probabilistic(), Xs, ys; - predict=yhat, - report=(training_auc=e,), - depwarn) - @test mach.model isa Probabilistic - @test_throws ArgumentError machine(Probabilistic(), Xs, ys; depwarn) - @test_throws ArgumentError machine(Probabilistic(), Xs, ys; - report=(training_auc=e,), - depwarn) - - # test extra report items coming from `training_auc=e` above - fit!(mach, verbosity=0) - err = auc(yhat(), y) - @test report(mach).training_auc ≈ err - - # supervised - predict_mode - @test predict_mode(mach, X) == mode.(predict(mach, X)) - predict_mode(mach, rows=1:2) == predict_mode(mach, rows=:)[1:2] - - # evaluate a learning machine - evaluate!(mach, measure=LogLoss(), verbosity=0) - - # supervised - predict_median, predict_mean - X1, y1 = make_regression(20) - - Xs = source(X1); ys = source(y1) - mm = machine(ConstantRegressor(), Xs, ys) - yhat = predict(mm, Xs) - mach = fit!(machine(Probabilistic(), Xs, ys; predict=yhat, depwarn), verbosity=0) - @test predict_mean(mach, X1) ≈ mean.(predict(mach, X1)) - @test predict_median(mach, X1) ≈ median.(predict(mach, X1)) - -end - -mutable struct DummyComposite <: DeterministicComposite - stand1 - stand2 -end - -@testset "issue 377" begin - stand = Standardizer() - model = DummyComposite(stand, stand) - - Xs = source() - mach1 = machine(model.stand1, Xs) - X1 = transform(mach1, Xs) - mach2 = machine(model.stand2, X1) - X2 = transform(mach2, X1) - - mach = machine(Unsupervised(), Xs; transform=X2, depwarn) - @test_logs((:error, r"The hyper"), - @test_throws(ArgumentError, - MLJBase.network_model_names(model, mach))) -end - -end - -true diff --git a/test/composition/models/deprecated_from_network.jl b/test/composition/models/deprecated_from_network.jl deleted file mode 100644 index 15b56d03..00000000 --- a/test/composition/models/deprecated_from_network.jl +++ /dev/null @@ -1,621 +0,0 @@ -module TestFromComposite - -using Test -using Tables -using MLJBase -using ..Models -using ..TestUtilities -using CategoricalArrays -using StableRNGs -using Parameters -rng = StableRNG(616161) - -ridge_model = FooBarRegressor(lambda=0.1) -selector_model = FeatureSelector() - -import MLJBase.@nodepwarn_from_network -const depwarn = false - -## FROM_NETWORK_PREPROCESS - -# supervised: -Xs = source(nothing) -ys = source(nothing) -z = log(ys) -stand = UnivariateStandardizer() -standM = machine(stand, z) -u = transform(standM, z) -hot = OneHotEncoder() -hotM = machine(hot, Xs) -W = transform(hotM, Xs) -knn = KNNRegressor() -knnM = machine(knn, W, u) -oak = DecisionTreeRegressor() -oakM = machine(oak, W, u) -uhat = 0.5*(predict(knnM, W) + predict(oakM, W)) -zhat = inverse_transform(standM, uhat) -yhat = exp(zhat) - -mach_ex = :(machine(Deterministic(), Xs, ys; predict=yhat, depwarn=false)) - -## TESTING `from_network_preprocess` - -ex = Meta.parse( - "begin - mutable struct CompositeX - knn_rgs=knn - one_hot_enc=hot - end - target_scitype=AbstractVector{<:Continuous} - input_scitype=Table(Continuous,Multiclass) - end") -mach_, modeltype_ex, struct_ex, no_fields, dic = - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex) - -eval(Parameters.with_kw(struct_ex, TestFromComposite, false)) -@test supertype(CompositeX) == DeterministicComposite -composite = CompositeX() -@test composite.knn_rgs == knn -@test composite.one_hot_enc == hot -@test dic[:target_scitype] == :(AbstractVector{<:Continuous}) -@test dic[:input_scitype] == :(Table(Continuous, Multiclass)) - -ex = Meta.parse( - "begin - mutable struct Composite4 <: ProbabilisticComposite - knn_rgs=knn - one_hot_enc=hot - end - end") -mach_, modeltype_ex, struct_ex = - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex) -eval(Parameters.with_kw(struct_ex, TestFromComposite, false)) -@test supertype(Composite4) == ProbabilisticComposite - -ex = Meta.parse( - "mutable struct Composite2 - knn_rgs=knn - one_hot_enc=hot - end") -mach_, modeltype_ex, struct_ex, no_fields, dic = - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex) -eval(Parameters.with_kw(struct_ex, TestFromComposite, false)) -composite = Composite2() -@test composite.knn_rgs == knn -@test composite.one_hot_enc == hot - -ex = Meta.parse( - "begin - mutable struct Composite6 <: Probabilistic - knn_rgs=knn - one_hot_enc=hot - end - end") -@test_logs((:warn, r"New composite"), - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -ex = Meta.parse( - "begin - mutable struct Composite20 - knn_rgs=knn - one_hot_enc=hot - end - target_scitype == Continuous - end") -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -ex = Meta.parse( - "begin - mutable struct Composite20 - knn_rgs=knn - one_hot_enc=hot - end - Continuous - end") -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -ex = Meta.parse( - "begin - mutable struct Composite20 - knn_rgs=knn - one_hot_enc=hot - end - 43 = Continuous - end") -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -ex = Meta.parse( - "begin - mutable struct Composite7 < Probabilistic - knn_rgs=knn - one_hot_enc=hot - end - end") -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, knn, ex)) - -ex = Meta.parse( - "begin - Composite3( - knn_rgs=knn, - one_hot_enc=hot) - end") -@test_throws(ArgumentError, - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex)) - -ex = Meta.parse( - "begin - mutable struct Composite8 - knn_rgs::KNNRegressor=knn - one_hot_enc=hot - end - end") -mach_, modeltype_ex, struct_ex = - MLJBase.from_network_preprocess(TestFromComposite, mach_ex, ex) -eval(Parameters.with_kw(struct_ex, TestFromComposite, false)) -VERSION ≥ v"1.3.0-" && - @test fieldtypes(Composite8) == (KNNRegressor, Any) - -# test that you cannot leave "default" component models unspecified: -modeltype_ex = :Composite9 -struct_ex = :(mutable struct Composite9 <: DeterministicComposite - knn_rgs::KNNRegressor - one_hot_enc = hot - end) -@test_logs (:error, r"Problem instantiating") begin - @test_throws Exception begin - MLJBase.from_network_(TestFromComposite, - mach_ex, modeltype_ex, - struct_ex, false, Dict{Symbol,Any}()) - end -end - - -## TEST MACRO-EXPORTED NETWORKS -# (CANNOT WRAP IN @testset) - -# some actual data: -N = 10 -X = MLJBase.table(rand(N, 3)) -y = rand(N) -w = rand(N) - -# supervised with sample weights: -ws = source() -knnM = machine(knn, W, u, ws) -uhat = 0.5*(predict(knnM, W) + predict(oakM, W)) -zhat = inverse_transform(standM, uhat) -yhat = exp(zhat) - -@nodepwarn_from_network machine( - Deterministic(), Xs, ys, ws; predict=yhat, depwarn=false -) begin - mutable struct CompositeX1 - knn_rgs=knn - one_hot_enc=hot - end - supports_weights = true - target_scitype = AbstractVector{<:Continuous} -end -model = CompositeX1() -@test supports_weights(model) -@test target_scitype(model) == AbstractVector{<:Continuous} -@test_logs((:warn, r""), predict(fit!(machine(model, X, y, w), verbosity=-1), X)); -# unsupervised: -@nodepwarn_from_network machine(Unsupervised(), Xs; transform=W, depwarn=false) begin - mutable struct CompositeX2 - one_hot_enc=hot - end -end -model = CompositeX2() -@test_logs((:warn, r""), transform(fit!(machine(model, X), verbosity=-1), X)); - - -# second supervised test: -fea = FeatureSelector() -feaM = machine(fea, Xs) -G = transform(feaM, Xs) -hotM = machine(hot, G) -H = transform(hotM, G) -elm = DecisionTreeClassifier() -elmM = machine(elm, H, ys) -yhat = predict(elmM, H) - -@nodepwarn_from_network machine(Probabilistic(), Xs, ys; predict=yhat, depwarn=false) begin - mutable struct CompositeX3 - selector=fea - one_hot=hot - tree=elm - end -end -model = CompositeX3() -y = coerce(y, Multiclass) -@test @test_logs((:warn, r""), predict(fit!(machine(model, X, y), verbosity=-1), X)) isa - AbstractVector{<:UnivariateFinite} - -# yet more examples: -x1 = map(n -> mod(n,3), rand(rng, UInt8, 100)) |> categorical; -x2 = randn(rng, 100); -X = (x1=x1, x2=x2); -y = x2.^2; - -Xs = source(X) -ys = source(y) -z = log(ys) -stand = UnivariateStandardizer() -standM = machine(stand, z) -u = transform(standM, z) -hot = OneHotEncoder() -hotM = machine(hot, Xs) -W = transform(hotM, Xs) -knn = KNNRegressor() -knnM = machine(knn, W, u) -oak = DecisionTreeRegressor() -oakM = machine(oak, W, u) -uhat = 0.5*(predict(knnM, W) + predict(oakM, W)) -zhat = inverse_transform(standM, uhat) -yhat = exp(zhat) - -mach = machine(Deterministic(), Xs, ys; predict=yhat, depwarn=false) - -@nodepwarn_from_network mach begin - mutable struct Composite10 - knn_rgs::KNNRegressor=knn - one_hot_enc=hot - end -end - -model_ = Composite10() - -mach = machine(model_, X, y) - -@test_logs((:warn, r""), - @test_model_sequence(fit_only!(mach), - [(:train, model_), (:train, stand), (:train, hot), - (:train, knn), (:train, oak)], - [(:train, model_), (:train, hot), (:train, stand), - (:train, knn), (:train, oak)], - [(:train, model_), (:train, stand), (:train, hot), - (:train, oak), (:train, knn)], - [(:train, model_), (:train, hot), (:train, stand), - (:train, oak), (:train, knn)]) - ) - -model_.knn_rgs.K = 55 -knn = model_.knn_rgs -@test_model_sequence(fit_only!(mach), - [(:update, model_), (:skip, stand), (:skip, hot), - (:update, knn), (:skip, oak)], - [(:update, model_), (:skip, hot), (:skip, stand), - (:update, knn), (:skip, oak)], - [(:update, model_), (:skip, stand), (:skip, hot), - (:skip, oak), (:update, knn)], - [(:update, model_), (:skip, hot), (:skip, stand), - (:skip, oak), (:update, knn)]) - - -@test MLJBase.tree(mach.fitresult.predict).arg1.arg1.arg1.arg1.model.K == 55 - -multistand = Standardizer() -multistandM = machine(multistand, W) -W2 = transform(multistandM, W) - -mach = machine(Unsupervised(), Xs; transform=W2, depwarn=false) - -@nodepwarn_from_network mach begin - mutable struct MyTransformer - one_hot=hot - end -end - -model_ = MyTransformer() - -mach = machine(model_, X) -@test_logs((:warn, r""), - @test_model_sequence fit_only!(mach) [(:train, model_), - (:train, hot), (:train, multistand)] - ) -model_.one_hot.drop_last=true -hot = model_.one_hot -@test_model_sequence fit_only!(mach) [(:update, model_), - (:update, hot), (:train, multistand)] - -# check nested fitted_params: -FP = MLJBase.fitted_params(mach) -@test keys(FP) == (:one_hot, :machines, :fitted_params_given_machine) -@test Set(FP.one_hot.fitresult.all_features) == Set(keys(X)) - -transform(mach, X); - - -## TEST MACRO-EXPORTED SUPERVISED NETWORK WITH SAMPLE WEIGHTS - -rng = StableRNG(56161) -N = 500 -X = (x = rand(rng, 3N), ); -y = categorical(rand(rng, "abc", 3N)); -# define class weights :a, :b, :c in ration 2:4:1 -w = map(y) do η - if η == 'a' - return 2 - elseif η == 'b' - return 4 - else - return 1 - end -end; -Xs = source(X) -ys = source(y) -ws = source(w) - -standM = machine(Standardizer(), Xs) -W = transform(standM, Xs) - -rgs = ConstantClassifier() # supports weights -rgsM = machine(rgs, W, ys, ws) -yhat = predict(rgsM, W) - -fit!(yhat, verbosity=0) -fit!(yhat, rows=1:div(N,2), verbosity=0) -yhat(rows=1:div(N,2)); - -mach = machine(Probabilistic(), Xs, ys, ws; predict=yhat, depwarn=false) - -@nodepwarn_from_network mach begin - mutable struct MyComposite - regressor=rgs - end - supports_weights=true -end - -my_composite = MyComposite() -@test MLJBase.supports_weights(my_composite) -mach = @test_logs((:warn, r""), fit!(machine(my_composite, X, y), verbosity=0)) -Xnew = selectrows(X, 1:div(N,2)) -predict(mach, Xnew)[1] -posterior = predict(mach, Xnew)[1] - -# "posterior" is roughly uniform: -@test abs(pdf(posterior, 'b')/(pdf(posterior, 'a')) - 1) < 0.15 -@test abs(pdf(posterior, 'b')/(pdf(posterior, 'c')) - 1) < 0.15 - -# now add weights: -mach = @test_logs((:warn, r""), - fit!(machine(my_composite, X, y, w), rows=1:div(N,2), verbosity=0) - ) -posterior = predict(mach, Xnew)[1] - -# "posterior" is skewed appropriately in weighted case: -@test abs(pdf(posterior, 'b')/(2*pdf(posterior, 'a')) - 1) < 0.15 -@test abs(pdf(posterior, 'b')/(4*pdf(posterior, 'c')) - 1) < 0.19 - -# composite with no fields: -mach = machine(Probabilistic(), Xs, ys, ws; predict=yhat, depwarn=false) -@nodepwarn_from_network mach begin - struct CompositeWithNoFields - end -end -composite_with_no_fields = CompositeWithNoFields() -mach = @test_logs((:warn, r""), fit!(machine(composite_with_no_fields, X, y), verbosity=0)) - - -## EXPORTING A TRANSFORMER WITH PREDICT AND TRANSFORM - -# A dummy clustering model: -mutable struct DummyClusterer <: Unsupervised - n::Int -end -DummyClusterer(; n=3) = DummyClusterer(n) -function MLJBase.fit(model::DummyClusterer, verbosity::Int, X) - Xmatrix = Tables.matrix(X) - n = min(size(Xmatrix, 2), model.n) - centres = Xmatrix[1:n, :] - levels = categorical(1:n) - report = (centres=centres,) - fitresult = levels - return fitresult, nothing, report -end -MLJBase.transform(model::DummyClusterer, fitresult, Xnew) = - selectcols(Xnew, 1:length(fitresult)) -MLJBase.predict(model::DummyClusterer, fitresult, Xnew) = - [fill(fitresult[1], nrows(Xnew))...] - -N = 20 -X = (a = rand(N), b = categorical(rand("FM", N))) - -Xs = source(X) -W = transform(machine(OneHotEncoder(), Xs), Xs) -clust = DummyClusterer(n=2) -m = machine(clust, W) -yhat = predict(m, W) -Wout = transform(m, W) -foo = first(yhat) -mach = machine(Unsupervised(), Xs; - predict=yhat, - transform=Wout, - report=(foo=foo,), - depwarn=false) - -@nodepwarn_from_network mach begin - mutable struct WrappedClusterer - clusterer::Unsupervised = clust - end - input_scitype = Table(Continuous,Multiclass) -end - -model = WrappedClusterer() -mach = @test_logs((:warn, r""), fit!(machine(model, X), verbosity=0)) -fit!(yhat, verbosity=0) -@test predict(mach, X) == yhat() -@test transform(mach, X).a ≈ Wout().a -rep = report(mach) -@test rep.foo == yhat() |> first - - -## EXPORTING A STATIC LEARNING NETWORK (NO TRAINING ARGUMENTS) - -age = [23, 45, 34, 25, 67] -X = (age = age, - gender = categorical(['m', 'm', 'f', 'm', 'f'])) - -struct MyStaticTransformer <: Static - ftr::Symbol -end - -MLJBase.transform(transf::MyStaticTransformer, verbosity, X) = - selectcols(X, transf.ftr) - -Xs = source() -W = transform(machine(MyStaticTransformer(:age)), Xs) -Z = 2*W - -@nodepwarn_from_network machine(Static(), Xs; transform=Z, depwarn=false) begin - struct NoTraining - end -end - -mach = @test_logs((:warn, r""), fit!(machine(NoTraining()), verbosity=0)) -@test transform(mach, X) == 2*X.age - - -## TESTINGS A STACK AND IN PARTICULAR FITTED_PARAMS - -folds(data, nfolds) = - partition(1:nrows(data), (1/nfolds for i in 1:(nfolds-1))...); - -model1 = RidgeRegressor() -model2 = KNNRegressor(K=1) -judge = KNNRegressor(K=1) - -X = source() -y = source() - -folds(X::AbstractNode, nfolds) = node(XX->folds(XX, nfolds), X) -MLJBase.restrict(X::AbstractNode, f::AbstractNode, i) = - node((XX, ff) -> restrict(XX, ff, i), X, f); -MLJBase.corestrict(X::AbstractNode, f::AbstractNode, i) = - node((XX, ff) -> corestrict(XX, ff, i), X, f); - -f = folds(X, 3) - -m11 = machine(model1, corestrict(X, f, 1), corestrict(y, f, 1)) -m12 = machine(model1, corestrict(X, f, 2), corestrict(y, f, 2)) -m13 = machine(model1, corestrict(X, f, 3), corestrict(y, f, 3)) - -y11 = predict(m11, restrict(X, f, 1)); -y12 = predict(m12, restrict(X, f, 2)); -y13 = predict(m13, restrict(X, f, 3)); - -m21 = machine(model2, corestrict(X, f, 1), corestrict(y, f, 1)) -m22 = machine(model2, corestrict(X, f, 2), corestrict(y, f, 2)) -m23 = machine(model2, corestrict(X, f, 3), corestrict(y, f, 3)) - -y21 = predict(m21, restrict(X, f, 1)); -y22 = predict(m22, restrict(X, f, 2)); -y23 = predict(m23, restrict(X, f, 3)); - -y1_oos = vcat(y11, y12, y13); -y2_oos = vcat(y21, y22, y23); - -X_oos = MLJBase.table(hcat(y1_oos, y2_oos)) - -m_judge = machine(judge, X_oos, y) - -m1 = machine(model1, X, y) -m2 = machine(model2, X, y) - -y1 = predict(m1, X); -y2 = predict(m2, X); - -X_judge = MLJBase.table(hcat(y1, y2)) -yhat = predict(m_judge, X_judge) - -@nodepwarn_from_network machine(Deterministic(), X, y; predict=yhat, depwarn=false) begin - mutable struct MyStack - regressor1=model1 - regressor2=model2 - judge=judge - end -end - -my_stack = MyStack() -X, y = make_regression(18, 2) -mach = machine(my_stack, X, y) -@test_logs((:warn, r""), fit!(mach, verbosity=0)) - -fp = fitted_params(mach) -@test keys(fp.judge) == (:tree,) -@test length(fp.regressor1) == 4 -@test length(fp.regressor2) == 4 -@test keys(fp.regressor1[1]) == (:coefficients, :intercept) -@test keys(fp.regressor2[1]) == (:tree,) - - -## ISSUE #377 - -stand1 = Standardizer() -stand2 = Standardizer() - -Xraw = (x=[-2.0, 0.0, 2.0],) -X = source(Xraw) - -mach1 = machine(stand1, X) -X2 = transform(mach1, X) - -mach2 = machine(stand2, X2) -X3 = transform(mach2, X2) - -@nodepwarn_from_network machine(Unsupervised(), X; transform=X3, depwarn=false) begin - mutable struct CompositeZ - s1=stand1 - s2=stand2 - end -end - -# check no problems with network: -fit!(X3) -@test X3().x ≈ [-1.0, 0.0, 1.0] - -# instantiate with identical (===) models in two places: -model = CompositeZ(s1=stand1, s2=stand1) -mach = machine(model, Xraw) -@test_logs((:warn, MLJBase.WARN_NETWORK_MACHINES_DEPRECATION), - (:error, MLJBase.logerr_identical_models([:s1, :s2], model)), - (:error, r"Problem"), - (:info, r"Running"), - (:info, r"Type checks okay"), - @test_throws(MLJBase.ERR_IDENTICAL_MODELS, - fit!(mach, verbosity=-1))) - - -## SOURCE NODES THAT ARE ALSO OPERATION NODES - -stand = Standardizer() - -Xs = source() -mach1 = machine(stand, Xs) -X2 = transform(mach1, Xs) - -network_mach = machine(Unsupervised(), Xs, transform=X2, inverse_transform=Xs, depwarn=false) - -@nodepwarn_from_network network_mach begin - struct AppleComposite - standardizer = stand - end -end - -X = (x = Float64[1, 2, 3],) -mach = machine(AppleComposite(), X) -@test_logs((:warn, r""), fit!(mach, verbosity=0, force=true)) -@test transform(mach, X).x ≈ Float64[-1, 0, 1] -@test inverse_transform(mach, X) == X - -end - -true diff --git a/test/composition/models/deprecated_methods.jl b/test/composition/models/deprecated_methods.jl deleted file mode 100644 index 4cd7c907..00000000 --- a/test/composition/models/deprecated_methods.jl +++ /dev/null @@ -1,459 +0,0 @@ -module TestCompositesCore - -using Test -using MLJBase -using Tables -import MLJBase -using ..Models -using ..TestUtilities -using CategoricalArrays -using OrderedCollections -import Random.seed! -seed!(1234) - -const depwarn=false - -mutable struct Rubbish <: DeterministicComposite - model_in_network - model_not_in_network - some_other_variable -end - -knn = KNNRegressor() -model = Rubbish(knn, OneHotEncoder(), 42) -X, y = make_regression(10, 2) - -@testset "logic for composite model update - fallback()" begin - Xs = source(X) - ys = source(y) - mach0 = machine(Standardizer(), Xs) - W = transform(mach0, Xs) - mach1 = machine(model.model_in_network, W, ys) - yhat = predict(mach1, W) - mach = machine(Deterministic(), Xs, ys; predict=yhat, depwarn) - fitresult, cache, _ = return!(mach, model, 0; depwarn) - network_model_names = getfield(fitresult, :network_model_names) - @test network_model_names == [:model_in_network, nothing] - old_model = cache.old_model - glb_node = MLJBase.glb(mach) - @test !MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # don't fallback if mutating field for a network model: - model.model_in_network.K = 24 - @test !MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # do fallback if replacing field for a network model: - model.model_in_network = KNNRegressor() - @test MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # return to original state: - model.model_in_network = knn - @test !MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # do fallback if a non-network field changes: - model.model_not_in_network.features = [:x1,] - @test MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # return to original state: - model.model_not_in_network = OneHotEncoder() - @test !MLJBase.fallback(model, old_model, network_model_names, glb_node) - - # do fallback if any non-model changes: - model.some_other_variable = 123412 - @test MLJBase.fallback(model, old_model, network_model_names, glb_node) - -end - -model = Rubbish(KNNRegressor(), Standardizer(), 42) - -function MLJBase.fit(model::Rubbish, verbosity, X, y) - Xs = source(X) - ys = source(y) - mach1 = machine(model.model_in_network, Xs, ys) - yhat = predict(mach1, Xs) - mach = machine(Deterministic(), Xs, ys; predict=yhat) - return!(mach, model, verbosity; depwarn) -end - -# `model` is instance of `Rubbish` -mach = fit!(machine(model, X, y), verbosity=0) - -@testset "logic for composite model update - fit!" begin - - # immediately refit: - @test_model_sequence(fit!(mach), [(:skip, model), ]) - - # mutate a field for a network model: - model.model_in_network.K = 24 - @test_model_sequence(fit!(mach), - [(:update, model), (:update, model.model_in_network)]) - - # immediately refit: - @test_model_sequence(fit!(mach), [(:skip, model), ]) - - # replace a field for a network model: - model.model_in_network = KNNRegressor() - @test_model_sequence(fit!(mach), - [(:update, model), (:train, model.model_in_network)]) - - # immediately refit: - @test_model_sequence(fit!(mach), [(:skip, model), ]) - - # mutate a field for a model not in network: - model.model_not_in_network.features = [:x1,] - @test_model_sequence(fit!(mach), - [(:update, model), (:train, model.model_in_network)]) - - # immediately refit: - @test_model_sequence(fit!(mach), [(:skip, model), ]) - - # mutate some field that is not a model: - model.some_other_variable = 123412 - @test_model_sequence(fit!(mach), - [(:update, model), (:train, model.model_in_network)]) -end - -N = 50 -Xin = (a=rand(N), b=rand(N), c=rand(N)); -yin = rand(N); - -train, test = partition(eachindex(yin), 0.7); -Xtrain = MLJBase.selectrows(Xin, train); -ytrain = yin[train]; - -ridge_model = FooBarRegressor(lambda=0.1) -selector_model = FeatureSelector() - -mutable struct WrappedRidge <: DeterministicComposite - ridge -end - -# julia bug? If I return the following test to a @testset block, then -# the test marked with ******* fails (bizarre!) -#@testset "second test of hand-exported network" begin -function MLJBase.fit(model::WrappedRidge, verbosity::Integer, X, y) - Xs = source(X) - ys = source(y) - - stand = Standardizer() - standM = machine(stand, Xs) - W = transform(standM, Xs) - - boxcox = UnivariateBoxCoxTransformer() - boxcoxM = machine(boxcox, ys) - z = transform(boxcoxM, ys) - - ridgeM = machine(model.ridge, W, z) - zhat = predict(ridgeM, W) - yhat = inverse_transform(boxcoxM, zhat) - - mach = machine(Deterministic(), Xs, ys; predict=yhat) - return!(mach, model, verbosity; depwarn) -end - -MLJBase.input_scitype(::Type{<:WrappedRidge}) = - Table(Continuous) -MLJBase.target_scitype(::Type{<:WrappedRidge}) = - AbstractVector{<:Continuous} - -ridge = FooBarRegressor(lambda=0.1) -model_ = WrappedRidge(ridge) -mach = machine(model_, Xin, yin) -id = objectid(mach) -fit!(mach, verbosity=0) -@test objectid(mach) == id # ********* -yhat=predict(mach, Xin); -ridge.lambda = 1.0 -fit!(mach, verbosity=0) -@test predict(mach, Xin) != yhat - -#end - -# A dummy clustering model: -mutable struct DummyClusterer <: Unsupervised - n::Int -end -DummyClusterer(; n=3) = DummyClusterer(n) -function MLJBase.fit(model::DummyClusterer, verbosity::Int, X) - Xmatrix = Tables.matrix(X) - n = min(size(Xmatrix, 2), model.n) - centres = Xmatrix[1:n, :] - levels = categorical(1:n) - report = (centres=centres,) - fitresult = levels - return fitresult, nothing, report -end -MLJBase.transform(model::DummyClusterer, fitresult, Xnew) = - selectcols(Xnew, 1:length(fitresult)) -MLJBase.predict(model::DummyClusterer, fitresult, Xnew) = - [fill(fitresult[1], nrows(Xnew))...] - -# A wrap of above model: -mutable struct WrappedDummyClusterer <: UnsupervisedComposite - model -end -WrappedDummyClusterer(; model=DummyClusterer()) = - WrappedDummyClusterer(model) - -@testset "third test of hand-exported network" begin - function MLJBase.fit(model::WrappedDummyClusterer, verbosity::Int, X) - Xs = source(X) - W = transform(machine(OneHotEncoder(), Xs), Xs) - m = machine(model.model, W) - yhat = predict(m, W) - Wout = transform(m, W) - foo = node(η -> first(η), yhat) - mach = machine(Unsupervised(), - Xs; - predict=yhat, - transform=Wout, - report=(foo=foo,)) - return!(mach, model, verbosity; depwarn) - end - X, _ = make_regression(10, 5); - model = WrappedDummyClusterer(model=DummyClusterer(n=2)) - mach = fit!(machine(model, X), verbosity=0) - model.model.n = 3 - fit!(mach, verbosity=0) - @test transform(mach, X) == selectcols(X, 1:3) - r = report(mach) - @test r.model.centres == MLJBase.matrix(X)[1:3,:] - @test r.foo == predict(mach, rows=:)[1] - fp = fitted_params(mach) - @test :model in keys(fp) - levs = fp.model.fitresult - @test predict(mach, X) == fill(levs[1], 10) -end - - -## NETWORK WITH MULTIPLE NODES REPORTING STATE/ REFIT - -mutable struct TwoStages <: DeterministicComposite - model1 - model2 - model3 -end - -function MLJBase.fit(m::TwoStages, verbosity, X, y) - Xs = source(X) - ys = source(y) - mach1 = machine(m.model1, Xs, ys) - mach2 = machine(m.model2, Xs, ys) - ypred1 = MLJBase.predict(mach1, Xs) - ypred2 = MLJBase.predict(mach2, Xs) - Y = MLJBase.table(hcat(ypred1, ypred2)) - mach3 = machine(m.model3, Y, ys) - ypred3 = MLJBase.predict(mach3, Y) - μpred = node(x->mean(x), ypred3) - σpred = node((x, μ)->mean((x.-μ).^2), ypred3, μpred) - mach = machine(Deterministic(), - Xs, - ys; - predict=ypred3, - report=(μpred=μpred, - σpred=σpred)) - return!(mach, m, verbosity; depwarn) -end - -@testset "Test exported-network with multiple saved nodes and refit" begin - X, y = make_regression(100, 3) - model3 = FooBarRegressor(lambda=1) - twostages = TwoStages(FooBarRegressor(lambda=0.1), - FooBarRegressor(lambda=10), model3) - mach = machine(twostages, X, y) - fit!(mach, verbosity=0) - rep = report(mach) - # All machines have been fitted once - @test rep.machines[1].state == - rep.machines[2].state == - rep.machines[3].state == 1 - # Retrieve current values of interest - μpred = rep.μpred - σpred = rep.σpred - # Change model3 and refit - model3.lambda = 10 - fit!(mach, verbosity=0) - rep = report(mach) - # Machines 1,2 have been fitted once and machine 3 twice - @test rep.machines[1].state == rep.machines[2].state == 1 - @test rep.machines[3].state == 2 - # The new values have been updated - @test rep.μpred != μpred - @test rep.σpred != σpred -end - -## COMPOSITE WITH COMPONENT MODELS STORED IN NTUPLE - -# `modelnames` is a tuple of `Symbol`s, one for each `model` in `models`: -mutable struct Averager{modelnames} <: DeterministicComposite - models::NTuple{<:Any,Deterministic} - weights::Vector{Float64} - Averager(modelnames, models, weights) = - new{modelnames}(models, weights) -end - -# special kw constructor, allowing one to specify the property names -# to be attributed to each component model (see below): -function Averager(; weights=Float64[], named_models...) - nt = NamedTuple(named_models) - modelnames = keys(nt) - models = values(nt) - return Averager(modelnames, models, weights) -end - -# for example: -averager = Averager(weights=[1, 1], - model1=KNNRegressor(K=3), - model2=RidgeRegressor()) - -# so we can do `averager.model1` and `averager.model2`: -Base.propertynames(::Averager{modelnames}) where modelnames = - tuple(:weights, modelnames...) -function Base.getproperty(averager::Averager{modelnames}, - name::Symbol) where modelnames - name === :weights && return getfield(averager, :weights) - models = getfield(averager, :models) - for j in eachindex(modelnames) - name === modelnames[j] && return models[j] - end - error("type Averager has no field $name") -end - -# overload multiplication of a node by a matrix: -import Base.* -*(preds::Node, weights) = node(p->p*weights, preds) - -# learning network wrapped in a fit method: -function MLJBase.fit(averager::Averager{modelnames}, - verbosity, - X, - y) where modelnames - - Xs = source(X) - ys = source(y) - - weights = averager.weights - - machines = [machine(getproperty(averager, name), Xs, ys) for - name in modelnames] - predictions = hcat([predict(mach, Xs) for mach in machines]...) - yhat = (1/sum(weights))*(predictions*weights) - - mach = machine(Deterministic(), Xs, ys; predict=yhat) - return!(mach, averager, verbosity; depwarn) -end - -@testset "composite with component models stored in ntuple" begin - X, y = make_regression(10, 3); - mach = machine(averager, X, y) - fit!(mach, verbosity=0) - fp = fitted_params(mach) - @test keys(fp.model1) == (:tree, ) - @test keys(fp.model2) == (:coefficients, :intercept) - r = report(mach) - @test isnothing(r.model1) - @test isnothing(r.model2) - range(averager, :(model1.K), lower=2, upper=3) -end - - -## DATA FRONT-END IN AN EXPORTED LEARNING NETWORK - -mutable struct Scale <: MLJBase.Static - scaling::Float64 -end - -function MLJBase.transform(s::Scale, _, X) - X isa AbstractVecOrMat && return X * s.scaling - MLJBase.table(s.scaling * MLJBase.matrix(X), prototype=X) -end - -function MLJBase.inverse_transform(s::Scale, _, X) - X isa AbstractVecOrMat && return X / s.scaling - MLJBase.table(MLJBase.matrix(X) / s.scaling, prototype=X) -end - -mutable struct ElephantModel <: ProbabilisticComposite - scaler - clf - cache::Bool -end - -function MLJBase.fit(model::ElephantModel, verbosity, X, y) - - Xs = source(X) - ys = source(y) - - scaler = model.scaler - mach1 = machine(scaler, cache=model.cache) - W = transform(mach1, Xs) - - # a classifier with reformat front-end: - clf = model.clf - mach2 = machine(clf, W, ys, cache=model.cache) - yhat = predict(mach2, W) - - mach = machine(Probabilistic(), Xs, ys, predict=yhat) - return!(mach, model, verbosity; depwarn) -end - -@testset "reformat/selectrows logic in composite model" begin - - X = (x1=ones(5), x2=ones(5)) - y = categorical(collect("abaaa")) - model = ElephantModel(Scale(2.0), - ConstantClassifier(testing=true, bogus=1.0), - true) - mach = machine(model, X, y, cache=false) - - @test_logs((:warn, MLJBase.WARN_NETWORK_MACHINES_DEPRECATION), - (:info, "reformatting X, y"), - (:info, "resampling X, y"), - fit!(mach, verbosity=0, rows=1:3) - ) - @test mach.state == 1 - - # new clf hyperparmater (same rows) means no reformatting or resampling: - model.clf.bogus = 10 - @test_logs fit!(mach, verbosity=0, rows=1:3) - @test mach.state == 2 - - # however changing an upstream hyperparameter forces reformatting - # and resampling: - model.scaler.scaling = 3.1 - @test_logs((:info, "reformatting X, y"), - (:info, "resampling X, y"), - fit!(mach, verbosity=0, rows=1:3)) - -end - -@testset "operation nodes that are source nodes" begin - - mutable struct BananaComposite <: UnsupervisedComposite - stand - end - BananaComposite(; stand=Standardizer()) = BananaComposite(stand) - - function MLJBase.fit(model::BananaComposite, verbosity, X) - - Xs = source(X) - mach1 = machine(model.stand, Xs) - X2 = transform(mach1, Xs) - - # node for the inverse_transform: - - network_mach = machine(Unsupervised(), Xs, transform=X2, inverse_transform=Xs) - return!(network_mach, model, verbosity; depwarn) - - end - - X = (x = Float64[1, 2, 3],) - mach = machine(BananaComposite(), X) - fit!(mach, verbosity=0, force=true) - @test transform(mach, X).x ≈ Float64[-1, 0, 1] - @test inverse_transform(mach, X) == X - -end - -end # module -true diff --git a/test/composition/models/network_composite.jl b/test/composition/models/network_composite.jl index a0454e8e..26f0d4c6 100644 --- a/test/composition/models/network_composite.jl +++ b/test/composition/models/network_composite.jl @@ -829,7 +829,7 @@ end # Test data as been erased at the first and second level of composition for submach in machines(glb(smach.fitresult)) TestUtilities.test_data(submach) - if submach isa Machine{<:Composite} + if submach isa Machine{<:NetworkComposite} for subsubmach in machines(glb(submach.fitresult)) TestUtilities.test_data(subsubmach) end diff --git a/test/operations.jl b/test/operations.jl index e14b7702..5970cb0f 100644 --- a/test/operations.jl +++ b/test/operations.jl @@ -57,7 +57,7 @@ using ..Models @test_throws ArgumentError transform(m, Tuple(y1), Tuple(y2)) end -@testset "operations on network-composite models" begin +@testset "operations on NetworkComposite models" begin X = MLJBase.table(rand(4, 4)) y = rand(4) m = fit!(machine(SimpleProbabilisticNetworkCompositeModel(), X, y), verbosity=0) @@ -67,21 +67,6 @@ end @test_throws ErrorException transform(m, X) end -# Test below to be removed after next breaking release -@testset "operations on composite/surrogate models" begin - X = MLJBase.table(rand(4, 4)) - y = rand(4) - m = fit!(machine(SimpleDeterministicCompositeModel(), X, y), verbosity=0) - @test predict(m, X) == m.fitresult.predict(X) - @test_throws ErrorException transform(m, X) - - m = fit!(machine(SimpleProbabilisticCompositeModel(), X, y), verbosity=0) - predictions = m.fitresult.predict(X) - @test predict(m, X) == predictions - @test predict_mode(m, X) == mode.(predictions) - @test_throws ErrorException transform(m, X) -end - end true diff --git a/test/runtests.jl b/test/runtests.jl index f6076565..b805d0dd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,14 +55,11 @@ end @test include("composition/learning_networks/nodes.jl") @test include("composition/learning_networks/inspection.jl") @test include("composition/learning_networks/signatures.jl") - @test include("composition/learning_networks/deprecated_machines.jl") @test include("composition/learning_networks/replace.jl") end @conditional_testset "composition_models" begin @test include("composition/models/network_composite.jl") - @test include("composition/models/deprecated_methods.jl") - @test include("composition/models/deprecated_from_network.jl") @test include("composition/models/inspection.jl") @test include("composition/models/pipelines.jl") @test include("composition/models/transformed_target_model.jl") From 8788e41c5019e8beb66e2807214bc8aa48f118da Mon Sep 17 00:00:00 2001 From: "Anthony D. Blaom" Date: Fri, 22 Sep 2023 14:23:48 +1200 Subject: [PATCH 2/2] rm forgotten include statement --- src/MLJBase.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MLJBase.jl b/src/MLJBase.jl index 64678967..f0a19e93 100644 --- a/src/MLJBase.jl +++ b/src/MLJBase.jl @@ -157,7 +157,6 @@ include("composition/learning_networks/replace.jl") include("composition/models/network_composite_types.jl") include("composition/models/network_composite.jl") -include("composition/models/inspection.jl") include("composition/models/pipelines.jl") include("composition/models/transformed_target_model.jl")