Skip to content

Commit eb41afa

Browse files
authored
Allow relaxation of ReLUQuadratic (#178)
1 parent 9d70a0e commit eb41afa

File tree

5 files changed

+54
-9
lines changed

5 files changed

+54
-9
lines changed

ext/MathOptAIFluxExt.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ julia> MathOptAI.build_predictor(
179179
)
180180
Pipeline with layers:
181181
* Affine(A, b) [input: 1, output: 16]
182-
* ReLUQuadratic()
182+
* ReLUQuadratic(nothing)
183183
* Affine(A, b) [input: 16, output: 1]
184184
```
185185
"""

ext/MathOptAILuxExt.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ julia> MathOptAI.build_predictor(
148148
)
149149
Pipeline with layers:
150150
* Affine(A, b) [input: 1, output: 16]
151-
* ReLUQuadratic()
151+
* ReLUQuadratic(nothing)
152152
* Affine(A, b) [input: 16, output: 1]
153153
```
154154
"""

src/predictors/Pipeline.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ julia> f = MathOptAI.Pipeline(
2828
)
2929
Pipeline with layers:
3030
* Affine(A, b) [input: 2, output: 1]
31-
* ReLUQuadratic()
31+
* ReLUQuadratic(nothing)
3232
3333
julia> y, formulation = MathOptAI.add_predictor(model, f, x);
3434
@@ -42,7 +42,7 @@ Affine(A, b) [input: 2, output: 1]
4242
│ └ moai_Affine[1]
4343
└ constraints [1]
4444
└ x[1] + 2 x[2] - moai_Affine[1] = 0
45-
ReLUQuadratic()
45+
ReLUQuadratic(nothing)
4646
├ variables [2]
4747
│ ├ moai_ReLU[1]
4848
│ └ moai_z[1]

src/predictors/ReLU.jl

+27-5
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ function add_predictor(
244244
end
245245

246246
"""
247-
ReLUQuadratic() <: AbstractPredictor
247+
ReLUQuadratic(; relaxation_parameter = nothing) <: AbstractPredictor
248248
249249
An [`AbstractPredictor`](@ref) that represents the relationship:
250250
```math
@@ -258,6 +258,15 @@ y \\cdot z = 0 \\\\
258258
y, z \\ge 0
259259
\\end{aligned}
260260
```
261+
If `relaxation_parameter` is set to a value `ϵ`, the constraints become:
262+
```math
263+
\\begin{aligned}
264+
x = y - z \\\\
265+
y \\cdot z \\leq \\epsilon \\\\
266+
y, z \\ge 0
267+
\\end{aligned}
268+
```
269+
261270
262271
## Example
263272
@@ -269,7 +278,7 @@ julia> model = Model();
269278
julia> @variable(model, -1 <= x[i in 1:2] <= i);
270279
271280
julia> f = MathOptAI.ReLUQuadratic()
272-
ReLUQuadratic()
281+
ReLUQuadratic(nothing)
273282
274283
julia> y, formulation = MathOptAI.add_predictor(model, f, x);
275284
@@ -279,7 +288,7 @@ julia> y
279288
moai_ReLU[2]
280289
281290
julia> formulation
282-
ReLUQuadratic()
291+
ReLUQuadratic(nothing)
283292
├ variables [4]
284293
│ ├ moai_ReLU[1]
285294
│ ├ moai_ReLU[2]
@@ -300,7 +309,15 @@ ReLUQuadratic()
300309
└ moai_ReLU[2]*moai_z[2] = 0
301310
```
302311
"""
303-
struct ReLUQuadratic <: AbstractPredictor end
312+
struct ReLUQuadratic <: AbstractPredictor
313+
relaxation_parameter::Union{Nothing,Float64}
314+
function ReLUQuadratic(;
315+
relaxation_parameter::Union{Nothing,Float64} = nothing,
316+
)
317+
@assert something(relaxation_parameter, 0.0) >= 0.0
318+
return new(relaxation_parameter)
319+
end
320+
end
304321

305322
function add_predictor(
306323
model::JuMP.AbstractModel,
@@ -314,6 +331,11 @@ function add_predictor(
314331
z = JuMP.@variable(model, [1:m], base_name = "moai_z")
315332
_set_bounds_if_finite.(Ref(cons), z, 0, max.(0, -first.(bounds)))
316333
append!(cons, JuMP.@constraint(model, x .== y - z))
317-
append!(cons, JuMP.@constraint(model, y .* z .== 0))
334+
if predictor.relaxation_parameter === nothing
335+
append!(cons, JuMP.@constraint(model, y .* z .== 0))
336+
else
337+
ϵ = predictor.relaxation_parameter
338+
append!(cons, JuMP.@constraint(model, y .* z .<= ϵ))
339+
end
318340
return y, Formulation(predictor, Any[y; z], cons)
319341
end

test/test_predictors.jl

+23
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ function test_ReLU_Quadratic()
225225
set_silent(model)
226226
@variable(model, x[1:2])
227227
f = MathOptAI.ReLUQuadratic()
228+
@test f.relaxation_parameter === nothing
228229
y, formulation = MathOptAI.add_predictor(model, f, x)
229230
@test length(y) == 2
230231
@test num_variables(model) == 6
@@ -237,6 +238,28 @@ function test_ReLU_Quadratic()
237238
return
238239
end
239240

241+
function test_ReLU_Quadratic_relaxed()
242+
model = Model(Ipopt.Optimizer)
243+
set_silent(model)
244+
@variable(model, x[1:2])
245+
f = MathOptAI.ReLUQuadratic(; relaxation_parameter = 1e-4)
246+
y, formulation = MathOptAI.add_predictor(model, f, x)
247+
# Maximize sum of all variables to exercise the ReLU relaxation
248+
@objective(model, Max, sum(formulation.variables))
249+
@test length(y) == 2
250+
@test num_variables(model) == 6
251+
@test num_constraints(model, AffExpr, MOI.EqualTo{Float64}) == 2
252+
@test num_constraints(model, QuadExpr, MOI.LessThan{Float64}) == 2
253+
fix.(x, [-1, 2])
254+
optimize!(model)
255+
@assert is_solved_and_feasible(model)
256+
# We do not satisfy equality to a tight tolerance
257+
@test !isapprox(value.(y), [0.0, 2.0]; atol = 1e-6)
258+
# But we satisfy equality to a loose tolerance
259+
@test isapprox(value.(y), [0.0, 2.0]; atol = 1e-2)
260+
return
261+
end
262+
240263
function test_Sigmoid()
241264
model = Model(Ipopt.Optimizer)
242265
set_silent(model)

0 commit comments

Comments
 (0)