Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting out issue with calling predict on matrix #219

Merged
merged 13 commits into from
Apr 24, 2023
20 changes: 13 additions & 7 deletions src/classifier.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# if `b` is a builder, then `b(model, rng, shape...)` is called to make a
# new chain, where `shape` is the return value of this method:
"""
shape(model::NeuralNetworkClassifier, X, y)

A private method that returns the shape of the input and output of the model for given data `X` and `y`.
"""
function MLJFlux.shape(model::NeuralNetworkClassifier, X, y)
X = X isa Matrix ? Tables.table(X) : X
levels = MLJModelInterface.classes(y[1])
n_output = length(levels)
n_input = Tables.schema(X).names |> length
Expand All @@ -10,7 +16,7 @@ end
# builds the end-to-end Flux chain needed, given the `model` and `shape`:
MLJFlux.build(model::NeuralNetworkClassifier, rng, shape) =
Flux.Chain(build(model.builder, rng, shape...),
model.finaliser)
model.finaliser)

# returns the model `fitresult` (see "Adding Models for General Use"
# section of the MLJ manual) which must always have the form `(chain,
Expand All @@ -19,15 +25,15 @@ MLJFlux.fitresult(model::NeuralNetworkClassifier, chain, y) =
(chain, MLJModelInterface.classes(y[1]))

function MLJModelInterface.predict(model::NeuralNetworkClassifier,
fitresult,
Xnew)
fitresult,
Xnew)
chain, levels = fitresult
X = reformat(Xnew)
probs = vcat([chain(tomat(X[:,i]))' for i in 1:size(X, 2)]...)
probs = vcat([chain(tomat(X[:, i]))' for i in 1:size(X, 2)]...)
return MLJModelInterface.UnivariateFinite(levels, probs)
end

MLJModelInterface.metadata_model(NeuralNetworkClassifier,
input=Table(Continuous),
target=AbstractVector{<:Finite},
path="MLJFlux.NeuralNetworkClassifier")
input=Union{AbstractMatrix{Continuous},Table(Continuous)},
target=AbstractVector{<:Finite},
path="MLJFlux.NeuralNetworkClassifier")
5 changes: 4 additions & 1 deletion src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,11 @@ nrows(y::AbstractVector) = length(y)
reformat(X) = reformat(X, scitype(X))

# ---------------------------------
# Reformatting tables
# Reformatting matrices
reformat(X, ::Type{<:AbstractMatrix}) = X'

# ---------------------------------
# Reformatting tables
reformat(X, ::Type{<:Table}) = MLJModelInterface.matrix(X)'

# ---------------------------------
Expand Down
42 changes: 27 additions & 15 deletions src/regressor.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# # NEURAL NETWORK REGRESSOR

"""
shape(model::NeuralNetworkRegressor, X, y)

A private method that returns the shape of the input and output of the model for given data `X` and `y`.
"""
function shape(model::NeuralNetworkRegressor, X, y)
X = X isa Matrix ? Tables.table(X) : X
n_input = Tables.schema(X).names |> length
n_ouput = 1
return (n_input, 1)
Expand All @@ -12,23 +18,29 @@ build(model::NeuralNetworkRegressor, rng, shape) =
fitresult(model::NeuralNetworkRegressor, chain, y) = (chain, nothing)

function MLJModelInterface.predict(model::NeuralNetworkRegressor,
fitresult,
Xnew)
chain = fitresult[1]
fitresult,
Xnew)
chain = fitresult[1]
Xnew_ = reformat(Xnew)
return [chain(values.(tomat(Xnew_[:,i])))[1]
for i in 1:size(Xnew_, 2)]
return [chain(values.(tomat(Xnew_[:, i])))[1]
for i in 1:size(Xnew_, 2)]
end

MLJModelInterface.metadata_model(NeuralNetworkRegressor,
input=Table(Continuous),
target=AbstractVector{<:Continuous},
path="MLJFlux.NeuralNetworkRegressor")
input=Union{AbstractMatrix{Continuous},Table(Continuous)},
target=AbstractVector{<:Continuous},
path="MLJFlux.NeuralNetworkRegressor")


# # MULTITARGET NEURAL NETWORK REGRESSOR

"""
shape(model::MultitargetNeuralNetworkRegressor, X, y)

A private method that returns the shape of the input and output of the model for given data `X` and `y`.
"""
function shape(model::MultitargetNeuralNetworkRegressor, X, y)
X = X isa Matrix ? Tables.table(X) : X
n_input = Tables.schema(X).names |> length
n_output = Tables.schema(y).names |> length
return (n_input, n_output)
Expand All @@ -43,16 +55,16 @@ function fitresult(model::MultitargetNeuralNetworkRegressor, chain, y)
end

function MLJModelInterface.predict(model::MultitargetNeuralNetworkRegressor,
fitresult, Xnew)
chain, target_column_names = fitresult
fitresult, Xnew)
chain, target_column_names = fitresult
X = reformat(Xnew)
ypred = [chain(values.(tomat(X[:,i])))
ypred = [chain(values.(tomat(X[:, i])))
for i in 1:size(X, 2)]
return MLJModelInterface.table(reduce(hcat, y for y in ypred)',
names=target_column_names)
names=target_column_names)
end

MLJModelInterface.metadata_model(MultitargetNeuralNetworkRegressor,
input=Table(Continuous),
target=Table(Continuous),
path="MLJFlux.MultitargetNeuralNetworkRegressor")
input=Union{AbstractMatrix{Continuous},Table(Continuous)},
target=Table(Continuous),
path="MLJFlux.MultitargetNeuralNetworkRegressor")
184 changes: 73 additions & 111 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,127 +5,89 @@ const MLJFluxModel = Union{MLJFluxProbabilistic,MLJFluxDeterministic}

for Model in [:NeuralNetworkClassifier, :ImageClassifier]

default_builder_ex =
Model == :ImageClassifier ? :(image_builder(VGGHack)) : Short()

ex = quote
mutable struct $Model{B,F,O,L} <: MLJFluxProbabilistic
builder::B
finaliser::F
optimiser::O # mutable struct from Flux/src/optimise/optimisers.jl
loss::L # can be called as in `loss(yhat, y)`
epochs::Int # number of epochs
batch_size::Int # size of a batch
lambda::Float64 # regularization strength
alpha::Float64 # regularizaton mix (0 for all l2, 1 for all l1)
rng::Union{AbstractRNG,Int64}
optimiser_changes_trigger_retraining::Bool
acceleration::AbstractResource # eg, `CPU1()` or `CUDALibs()`
end

function $Model(; builder::B = $default_builder_ex
, finaliser::F = Flux.softmax
, optimiser::O = Flux.Optimise.Adam()
, loss::L = Flux.crossentropy
, epochs = 10
, batch_size = 1
, lambda = 0
, alpha = 0
, rng = Random.GLOBAL_RNG
, optimiser_changes_trigger_retraining = false
, acceleration = CPU1()
) where {B,F,O,L}

model = $Model{B,F,O,L}(builder
, finaliser
, optimiser
, loss
, epochs
, batch_size
, lambda
, alpha
, rng
, optimiser_changes_trigger_retraining
, acceleration
)

message = clean!(model)
isempty(message) || @warn message

return model
end
default_builder_ex =
Model == :ImageClassifier ? :(image_builder(VGGHack)) : Short()

ex = quote
mutable struct $Model{B,F,O,L} <: MLJFluxProbabilistic
builder::B
finaliser::F
optimiser::O # mutable struct from Flux/src/optimise/optimisers.jl
loss::L # can be called as in `loss(yhat, y)`
epochs::Int # number of epochs
batch_size::Int # size of a batch
lambda::Float64 # regularization strength
alpha::Float64 # regularizaton mix (0 for all l2, 1 for all l1)
rng::Union{AbstractRNG,Int64}
optimiser_changes_trigger_retraining::Bool
acceleration::AbstractResource # eg, `CPU1()` or `CUDALibs()`
end

function $Model(; builder::B=$default_builder_ex, finaliser::F=Flux.softmax, optimiser::O=Flux.Optimise.Adam(), loss::L=Flux.crossentropy, epochs=10, batch_size=1, lambda=0, alpha=0, rng=Random.GLOBAL_RNG, optimiser_changes_trigger_retraining=false, acceleration=CPU1()
) where {B,F,O,L}

model = $Model{B,F,O,L}(builder, finaliser, optimiser, loss, epochs, batch_size, lambda, alpha, rng, optimiser_changes_trigger_retraining, acceleration
)

message = clean!(model)
isempty(message) || @warn message

return model
end
eval(ex)

end
eval(ex)

end


for Model in [:NeuralNetworkRegressor, :MultitargetNeuralNetworkRegressor]

ex = quote
mutable struct $Model{B,O,L} <: MLJFluxDeterministic
builder::B
optimiser::O # mutable struct from Flux/src/optimise/optimisers.jl
loss::L # can be called as in `loss(yhat, y)`
epochs::Int # number of epochs
batch_size::Int # size of a batch
lambda::Float64 # regularization strength
alpha::Float64 # regularizaton mix (0 for all l2, 1 for all l1)
rng::Union{AbstractRNG,Integer}
optimiser_changes_trigger_retraining::Bool
acceleration::AbstractResource # eg, `CPU1()` or `CUDALibs()`
end

function $Model(; builder::B = Linear()
, optimiser::O = Flux.Optimise.Adam()
, loss::L = Flux.mse
, epochs = 10
, batch_size = 1
, lambda = 0
, alpha = 0
, rng = Random.GLOBAL_RNG
, optimiser_changes_trigger_retraining=false
, acceleration = CPU1()
) where {B,O,L}

model = $Model{B,O,L}(builder
, optimiser
, loss
, epochs
, batch_size
, lambda
, alpha
, rng
, optimiser_changes_trigger_retraining
, acceleration)

message = clean!(model)
isempty(message) || @warn message

return model
end
ex = quote
mutable struct $Model{B,O,L} <: MLJFluxDeterministic
builder::B
optimiser::O # mutable struct from Flux/src/optimise/optimisers.jl
loss::L # can be called as in `loss(yhat, y)`
epochs::Int # number of epochs
batch_size::Int # size of a batch
lambda::Float64 # regularization strength
alpha::Float64 # regularizaton mix (0 for all l2, 1 for all l1)
rng::Union{AbstractRNG,Integer}
optimiser_changes_trigger_retraining::Bool
acceleration::AbstractResource # eg, `CPU1()` or `CUDALibs()`
end

function $Model(; builder::B=Linear(), optimiser::O=Flux.Optimise.Adam(), loss::L=Flux.mse, epochs=10, batch_size=1, lambda=0, alpha=0, rng=Random.GLOBAL_RNG, optimiser_changes_trigger_retraining=false, acceleration=CPU1()
) where {B,O,L}

model = $Model{B,O,L}(builder, optimiser, loss, epochs, batch_size, lambda, alpha, rng, optimiser_changes_trigger_retraining, acceleration)

message = clean!(model)
isempty(message) || @warn message

return model
end
eval(ex)

end
eval(ex)

end

const Regressor =
Union{NeuralNetworkRegressor, MultitargetNeuralNetworkRegressor}
Union{NeuralNetworkRegressor,MultitargetNeuralNetworkRegressor}

MMI.metadata_pkg.(
(
NeuralNetworkRegressor,
MultitargetNeuralNetworkRegressor,
NeuralNetworkClassifier,
ImageClassifier,
),
name="MLJFlux",
uuid="094fc8d1-fd35-5302-93ea-dabda2abf845",
url="https://github.com/alan-turing-institute/MLJFlux.jl",
julia=true,
license="MIT",
(
NeuralNetworkRegressor,
MultitargetNeuralNetworkRegressor,
NeuralNetworkClassifier,
ImageClassifier,
),
name="MLJFlux",
uuid="094fc8d1-fd35-5302-93ea-dabda2abf845",
url="https://github.com/alan-turing-institute/MLJFlux.jl",
julia=true,
license="MIT",
)


Expand All @@ -148,8 +110,8 @@ In MLJ or MLJBase, bind an instance `model` to data with

Here:

- `X` is any table of input features (eg, a `DataFrame`) whose columns are of scitype
`Continuous`; check column scitypes with `schema(X)`.
- `X` is either a `Matrix` or any table of input features (eg, a `DataFrame`) whose columns are of scitype
`Continuous`; check column scitypes with `schema(X)`. If `X` is a `Matrix`, it is assumed to have columns corresponding to features and rows corresponding to observations.

- `y` is the target, which can be any `AbstractVector` whose element scitype is `Multiclass`
or `OrderedFactor`; check the scitype with `scitype(y)`
Expand Down Expand Up @@ -583,8 +545,8 @@ In MLJ or MLJBase, bind an instance `model` to data with

Here:

- `X` is any table of input features (eg, a `DataFrame`) whose columns
are of scitype `Continuous`; check the column scitypes with `schema(X)`.
- `X` is either a `Matrix` or any table of input features (eg, a `DataFrame`) whose columns are of scitype
`Continuous`; check column scitypes with `schema(X)`. If `X` is a `Matrix`, it is assumed to have columns corresponding to features and rows corresponding to observations.
- `y` is the target, which can be any `AbstractVector` whose element
scitype is `Continuous`; check the scitype with `scitype(y)`

Expand Down Expand Up @@ -810,8 +772,8 @@ In MLJ or MLJBase, bind an instance `model` to data with

Here:

- `X` is any table of input features (eg, a `DataFrame`) whose columns are of scitype
`Continuous`; check column scitypes with `schema(X)`.
- `X` is either a `Matrix` or any table of input features (eg, a `DataFrame`) whose columns are of scitype
`Continuous`; check column scitypes with `schema(X)`. If `X` is a `Matrix`, it is assumed to have columns corresponding to features and rows corresponding to observations.

- `y` is the target, which can be any table of output targets whose element scitype is
`Continuous`; check column scitypes with `schema(y)`.
Expand Down
27 changes: 20 additions & 7 deletions test/classifier.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,26 @@ losses = []

@testset_accelerated "NeuralNetworkClassifier" accel begin
Random.seed!(123)
basictest(MLJFlux.NeuralNetworkClassifier,
X,
y,
builder,
optimiser,
0.85,
accel)
# Table input:
@testset "Table input" begin
basictest(MLJFlux.NeuralNetworkClassifier,
X,
y,
builder,
optimiser,
0.85,
accel)
end
# Matrix input:
@testset "Matrix input" begin
basictest(MLJFlux.NeuralNetworkClassifier,
matrix(X),
y,
builder,
optimiser,
0.85,
accel)
end

train, test = MLJBase.partition(1:N, 0.7)

Expand Down
Loading