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")
6 changes: 5 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 Expand Up @@ -221,6 +224,7 @@ by `model.batch_size`.)

"""
function collate(model, X, y)
y = y isa Matrix ? Tables.table(y) : y
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand why this fix was needed. The nrows function is supposed to work for matrices as well as tables:

using MLJBase
julia> y = rand(2, 6)
julia> nrows(y)
2

julia> nrows(y')
6

And line 230 below should already take care of the conversion of y to a matrix, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was giving me an error previously, because the nrows method defined in the same file expects a table (or else it throws an ArgumentError. I've adjusted that and tests are passing.

row_batches = Base.Iterators.partition(1:nrows(y), model.batch_size)
Xmatrix = reformat(X)
ymatrix = reformat(y)
Expand Down
52 changes: 35 additions & 17 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,30 @@ 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
y = y isa Matrix ? Tables.table(y) : y
n_input = Tables.schema(X).names |> length
n_output = Tables.schema(y).names |> length
return (n_input, n_output)
pat-alt marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -38,21 +51,26 @@ build(model::MultitargetNeuralNetworkRegressor, rng, shape) =
build(model.builder, rng, shape...)

function fitresult(model::MultitargetNeuralNetworkRegressor, chain, y)
target_column_names = Tables.schema(y).names
if y isa Matrix
target_column_names = nothing
else
target_column_names = Tables.schema(y).names
end
return (chain, target_column_names)
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)
output = isnothing(target_column_names) ? permutedims(reduce(hcat, ypred)) :
MLJModelInterface.table(reduce(hcat, ypred)', names=target_column_names)
return output
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")
188 changes: 75 additions & 113 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,11 +772,11 @@ 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)`.
- `y` is the target, which can be any table or matrix of output targets whose element scitype is
`Continuous`; check column scitypes with `schema(y)`. If `y` is a `Matrix`, it is assumed to have columns corresponding to variables and rows corresponding to observations.


# Hyper-parameters
Expand Down
Loading