Skip to content

Commit 037e38c

Browse files
committed
Added mase error measure for continuous variables (Mean Absolute Scaled Error)
Also: - improved perfomances of the Scaler algorithm using inplace assignments - perform random reset of a NN model that doesn't converge only if the model has not beeing fitted before
1 parent b20bbf6 commit 037e38c

5 files changed

Lines changed: 107 additions & 7 deletions

File tree

src/Nn/Nn.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,9 +1178,9 @@ function fit!(m::NeuralNetworkEstimator,X,Y)
11781178
(onfail == "stop") && break # break as one training done, whatever the result
11791179
if a == fail_attempts
11801180
error("Fitting the model on the data failed. Loss is not decreasing even after $(fail_attempts) attempts.")
1181-
else
1181+
elseif ! m.fitted # random reset only if the model has not beeing fitted before
11821182
layers = m.par.nnstruct.layers
1183-
random_init!.(layers;rng=rng)
1183+
random_init!.(layers;rng=rng)
11841184
end
11851185
end
11861186

src/Utils/Measures.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,50 @@ function relative_mean_error(y,ŷ;normdim=false,normrec=false,p=1)
749749
end
750750

751751

752+
"""
753+
mase(y, ŷ;seq=false)
754+
755+
Mean absolute scaled error between y and ŷ.
756+
757+
A relative measure of error that is scale invariant.
758+
759+
If `seq` is `true`, the scaling is done using the in-sample one-step naive forecast method, and the function returns the Mean Absolute Error (MAE) divided by the mean absolute deviation of the difference between consecutive observations of y.
760+
761+
When `seq` is `false` (default), the scaling is done using the in-sample mean and the function returns the Mean Absolute Error (MAE) divided by the mean absolute deviation of y. This is also equal to the relative mean error of the zero-mean, unit variance scaled data.
762+
763+
See [Wikipedia article](https://en.wikipedia.org/wiki/Mean_absolute_scaled_error).
764+
765+
# Notes
766+
- this function works only for vector data (i.e. single output regression)
767+
- use `seq=true` for sequential data (e.g. time series)
768+
769+
# Example
770+
```julia
771+
julia> using BetaML
772+
julia> y = [-2, 5, 6, 0, 3, 5, -10, 15, -5];
773+
julia> ŷ = [0, 4, 5, 1, 2, 4, -8, 18, -6];
774+
julia> mean_absolute_scaled_error = BetaML.mase(y,ŷ)
775+
0.2647058823529412
776+
julia> ms = BetaML.Scaler()
777+
A Scaler BetaMLModel (unfitted)
778+
julia> y_s = BetaML.fit!(ms, y);
779+
julia> ŷ_s = BetaML.predict(ms, ŷ);
780+
julia> mean_absolute_scaled_error_s = BetaML.relative_mean_error((y_s,ŷ_s)
781+
0.2647058823529412
782+
```
783+
"""
784+
function mase(y::AbstractVector,ŷ::AbstractVector;seq=false)
785+
if seq
786+
n = length(y)
787+
d = sum(abs.(y[2:end] .- y[1:end-1]))/(n-1)
788+
return sum(abs.(y .- ŷ))/ (n*d)
789+
else
790+
= mean(y)
791+
d = sum(abs.(y .- ȳ))
792+
return sum(abs.(y .- ŷ)) / d
793+
end
794+
end
795+
752796
# ------------------------------------------------------------------------------
753797
# FeatureRanker
754798

src/Utils/Processing.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ function _fit(m::MinMaxScaler,skip,X,cache)
646646
imin,imax = (m.inputRange[1](skipmissing(c)), m.inputRange[2](skipmissing(c)) )
647647
if cache
648648
omin, omax = m.outputRange[1], m.outputRange[2]
649-
X_scaled[:,ic] = (c .- imin) .* ((omax-omin)/(imax-imin)) .+ omin
649+
X_scaled[:,ic] .= (c .- imin) .* ((omax-omin)/(imax-imin)) .+ omin
650650
end
651651
push!(actualRanges,(imin,imax))
652652
else
@@ -675,7 +675,7 @@ function _fit(m::StandardScaler,skip,X::AbstractArray,cache)
675675
sfμ[ic] = - μ
676676
sfσ[ic] = 1 ./ sqrt.(σ²)
677677
if cache
678-
X_scaled[:,ic] = (c .+ sfμ[ic]) .* sfσ[ic]
678+
X_scaled[:,ic] .= (c .+ sfμ[ic]) .* sfσ[ic]
679679
end
680680
end
681681
end
@@ -715,15 +715,15 @@ function _predict(m::StandardScaler,pars::StandardScaler_lp,skip,X;inverse=false
715715
xnew .= X
716716
for (ic,c) in enumerate(eachcol(X))
717717
if !(ic in skip)
718-
xnew[:,ic] = (c .+ pars.sfμ[ic]) .* pars.sfσ[ic]
718+
xnew[:,ic] .= (c .+ pars.sfμ[ic]) .* pars.sfσ[ic]
719719
end
720720
end
721721
return xnew
722722
else
723723
xorig = deepcopy(X)
724724
for (ic,c) in enumerate(eachcol(X))
725725
if !(ic in skip)
726-
xorig[:,ic] = (c ./ pars.sfσ[ic] .- pars.sfμ[ic])
726+
xorig[:,ic] .= (c ./ pars.sfσ[ic] .- pars.sfμ[ic])
727727
end
728728
end
729729
return xorig

src/Utils/Utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export @codelocation, generate_parallel_rngs,
5252
dtanh, sigmoid, dsigmoid, softmax, dsoftmax, dmaximum, dmean, pool1d, softplus, dsoftplus, mish, dmish, # exp/trig based functions
5353
bic, aic,
5454
autojacobian, match_known_derivatives,
55-
squared_cost, dsquared_cost, mse, crossentropy, dcrossentropy, class_counts, kl_divergence, class_counts_with_labels, mean_dicts, mode, gini, entropy, variance, sobol_index,
55+
squared_cost, dsquared_cost, mse, mase, crossentropy, dcrossentropy, class_counts, kl_divergence, class_counts_with_labels, mean_dicts, mode, gini, entropy, variance, sobol_index,
5656
error, accuracy, relative_mean_error,
5757
ConfusionMatrix, ConfusionMatrix_hp, silhouette,
5858
cross_validation,

test/Utils_tests.jl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,62 @@ avgϵRel = (sum(abs.(ŷ-y).^p)^(1/p) / (n*d)) / (sum( abs.(y) .^p)^(1/p) / (n*d
290290
@test relative_mean_error(y,ŷ,normdim=false,normrec=false,p=p) == avgϵRel
291291

292292

293+
@testset "mase (Mean Absolute Scaled Error) test" begin
294+
obs = [-2, 5, 6, 0, 3, 5, -10, 15, -5]
295+
est = [0, 4, 5, 1, 2, 4, -8, 18, -6]
296+
obs2 = 100 .+ obs / 10
297+
est2 = 100 .+ est / 10
298+
obsb = vcat(obs,obs,obs,obs)
299+
estb = vcat(est,est,est,est)
300+
obs2b = vcat(obs2,obs2,obs2,obs2)
301+
est2b = vcat(est2,est2,est2,est2)
302+
303+
rme = BetaML.relative_mean_error(obs,est)
304+
rme2 = BetaML.relative_mean_error(obs2,est2)
305+
rmeb = BetaML.relative_mean_error(obsb,estb)
306+
rme2b = BetaML.relative_mean_error(obs2b,est2b)
307+
308+
ms = BetaML.Scaler()
309+
obss = BetaML.fit!(ms, obs)
310+
ests = BetaML.predict(ms, est)
311+
rmes =BetaML.relative_mean_error(obss, ests)
312+
313+
ms2 = BetaML.Scaler()
314+
obs2s = BetaML.fit!(ms2, obs2)
315+
est2s = BetaML.predict(ms2, est2)
316+
rme2s = BetaML.relative_mean_error(obs2s, est2s)
317+
318+
msb = BetaML.Scaler()
319+
obsbs = BetaML.fit!(msb, obsb)
320+
estbs = BetaML.predict(msb, estb)
321+
rmebs =BetaML.relative_mean_error(obsbs, estbs)
322+
323+
ms2 = BetaML.Scaler()
324+
obs2s = BetaML.fit!(ms2, obs2)
325+
est2s = BetaML.predict(ms2, est2)
326+
rme2s = BetaML.relative_mean_error(obs2s, est2s)
327+
328+
ms2b = BetaML.Scaler()
329+
obs2bs = BetaML.fit!(ms2b, obs2b)
330+
est2bs = BetaML.predict(ms2b, est2b)
331+
rme2bs = BetaML.relative_mean_error(obs2bs, est2bs)
332+
333+
masev_seq = BetaML.mase(obs,est;seq=true)
334+
masev2_seq = BetaML.mase(obs2,est2;seq=true)
335+
masevb_seq = BetaML.mase(obsb,estb;seq=true)
336+
masev2b_seq = BetaML.mase(obs2b,est2b;seq=true)
337+
338+
@test masevb_seq > masev_seq
339+
340+
masev = BetaML.mase(obs,est;seq=false)
341+
masev2 = BetaML.mase(obs2,est2;seq=false)
342+
masevb = BetaML.mase(obsb,estb;seq=false)
343+
masev2b = BetaML.mase(obs2b,est2b;seq=false)
344+
345+
@test masev masev2b rme2bs
346+
347+
end
348+
293349
# ==================================
294350
# New test
295351
println("** Testing pca()...")

0 commit comments

Comments
 (0)