TemplateExpression slowdown over time relative to Custom Loss Function #793
-
TemplateExpressions gives so much flexibility that in theory you can incorporate a custom loss function within a structure function. TemplateExpression code: using Statistics #for mean
structure = TemplateStructure{(:f,)}(
((; f), (x1, x2, x3, x4, x5, x6, y, cat)) -> begin
o = f(x1, x2, x3, x4, x5, x6)
if !o.valid
return ValidVector(Vector{Float32}(undef, length(o.x)), false)
end
for varidx in 1:3 #checking monotonicity for first 3 variables
grad_column = D(f, varidx)(x1, x2, x3, x4, x5, x6).x
if !(all(grad_column .>= 0) || all(grad_column .<= 0))
return ValidVector(fill(Float32(1e9), length(o.x)), true)
end
end
residuals = o - s
unique_groups = unique(Int.(cat.x))
mean_residuals_by_group = Dict(group => _mean(residuals.x[cat.x .== group]) for group in unique_groups)
offsets = [mean_residuals_by_group[cat.x[i]] for i in eachindex(o.x)]
return ValidVector(o.x .- offsets, o.valid && residuals.valid)
end
)
model = SRRegressor(
niterations=1000000,
binary_operators=[+,-,*,/],
maxsize=60,
bumper=true,
turbo=true,
populations=18,
expression_type = TemplateExpression,
expression_options = (; structure),
population_size=100,
parsimony = 0.01,
batching=true,
) To achieve the same with a custom loss function: using Statistics #for mean
function loss_fnc(tree, dataset::Dataset{T,L}, options, idx) where {T,L}
# Extract data for the given indices
X = idx === nothing ? dataset.X : dataset.X[:, idx]
y = idx === nothing ? dataset.y : view(dataset.y, idx)
prediction, grad, complete = eval_grad_tree_array(tree, X, options; variable=true)
if !complete
return L(Inf)
end
if any(row -> !(all(row .>= 0) || all(row .<= 0)), eachrow(grad[1:3, :])) #checking monotonicity for first 3 variables
return 1e09
end
# Calculate residuals
residuals = prediction .- y
weights = idx === nothing ? dataset.weights : view(dataset.weights, idx) #this carries the categorial variable
mean_residuals_by_group = Dict{L, T}()
unique_groups = unique(weights)
for group in unique_groups
group_residuals = residuals[weights .== group]
mean_residuals_by_group[group] = mean(group_residuals)
end
for i in eachindex(y)
group_type = weights[i]
prediction[i] -= mean_residuals_by_group[group_type]
end
# Calculate mean squared error (MSE) with adjusted predictions
mse = mean((prediction .- y).^2)
# Return final loss value (MSE + Penalty)
return mse
end
model = SRRegressor(
niterations=1000000,
binary_operators=[+,-,*,/],
maxsize=60,
bumper=true,
turbo=true,
populations=18,
population_size=100
parsimony = 0.01,
batching=true,
loss_function = loss_fnc,
) Both start with around 50-60 days remaining, which then rises to around 150 for the custom loss function (after 8 hours or so) and falls back to 50 days remaining. TemplateStructure: After running for 8 hours, the ETA increases to 442 days after initially being 53. Total CPU usage drops to around 17-20%. This has consistently been the case after I tested both codes out over a few weeks. Key difference in the code: However, both start off at a similar speed and convergence, (if anything TemplateExpression structure code is quicker initially), TE slows down dramatically. I've set all input variables to float32 to be consistent for both as I initially thought one might be using Float64. Any ideas why the TemplateExpression code starts quickly and then dramatically slows down would be very helpful. Personally I would much prefer to work with TemplateExpression if possible! |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 1 reply
-
Thanks for the report! It could be related to the garbage collection issue I reported which should be fixed in Julia 1.11.3: JuliaLang/julia#56759. You could try Julia 1.10 to see if the problem gets better? On 1.10, that garbage collection issue still technically exists, it's just not getting hit as frequently, so the issue will be smaller. By the way, note that if any(row -> !(all(row .>= 0) || all(row .<= 0)), eachrow(grad[1:3, :])) #checking monotonicity for first 3 variables
- return 1e09
+ return 1f09
end
Other tips: Avoid the extra allocation: if !o.valid
- return ValidVector(Vector{Float32}(undef, length(o.x)), false)
+ return o
end Also avoid allocation here: - if !(all(grad_column .>= 0) || all(grad_column .<= 0))
+ if !(all(g -> g >= 0, grad_column) || all(g -> g <= 0, grad_column))
return ValidVector(fill(Float32(1e9), length(o.x)), true)
end (It will scan the |
Beta Was this translation helpful? Give feedback.
-
Ah, one other tricky thing... I realised that Also could explain why TemplateExpression is slower in general (?), simply because it doesn't use turbo evaluation! |
Beta Was this translation helpful? Give feedback.
-
Great, thank you very much for the tips. I've adjusted the code as you recommended and it's very interesting to hear that bumper and turbo potentially have such a cumulative effect on prolonged runs. |
Beta Was this translation helpful? Give feedback.
-
Likely fixed with MilesCranmer/SymbolicRegression.jl#399 |
Beta Was this translation helpful? Give feedback.
-
Thanks Miles, This is resolved now, and I have TemplateExpressions running even faster than the custom loss function with v1.5.2. Thank you for the fix. I needed to update a lot of packages with ] up. I also replaced this: unique_groups = unique(Int.(cat.x))
mean_residuals_by_group = Dict(group => _mean(residuals.x[cat.x .== group]) for group in unique_groups)
offsets = [mean_residuals_by_group[cat.x[i]] for i in eachindex(o.x)]
return ValidVector(o.x .- offsets, o.valid && residuals.valid) with: prediction = o.x
mean_residuals_by_group = Dict{Float32,Float32}()
unique_groups = unique(cat.x)
for group in unique_groups
group_residuals = residuals[cat.x .== group]
mean_residuals_by_group[group] = mean(group_residuals)
end
for i in eachindex(prediction)
group_type = cat.x[i]
prediction[i] -= mean_residuals_by_group[group_type]
end
return ValidVector(prediction, true) #o already checked before This made it much faster (unsure why). both those bits of code will be redundant with templateparametric expressions anyway. Thanks again for your help and rapid turnaround for including bumper and turbo! |
Beta Was this translation helpful? Give feedback.
Likely fixed with MilesCranmer/SymbolicRegression.jl#399