Skip to content

Commit 8585428

Browse files
committed
relu_to_optimization replaces some linear constraints on variable with variable bounds.
1 parent 0757db2 commit 8585428

File tree

3 files changed

+45
-23
lines changed

3 files changed

+45
-23
lines changed

neural_network_lyapunov/relu_to_optimization.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def _compute_linear_output_bound_by_lp(
298298
) if linear_layer.bias is None else linear_layer.bias[j]
299299
Ain_linear_input, Ain_neuron_output, Ain_neuron_binary,\
300300
rhs_in, Aeq_linear_input, Aeq_neuron_output,\
301-
Aeq_neuron_binary, rhs_eq, _, _ =\
301+
Aeq_neuron_binary, rhs_eq, _, _, _, _ =\
302302
_add_constraint_by_neuron(
303303
linear_layer.weight[j], bij, relu_layer,
304304
torch.tensor(previous_neuron_input_lo[
@@ -519,8 +519,8 @@ def output_constraint(self, x_lo, x_up,
519519
z_pre_relu_lo, z_pre_relu_up, x_lo, x_up, method)
520520
for layer_count in range(len(self.relu_unit_index)):
521521
Ain_z_curr, Ain_z_next, Ain_binary_layer, rhs_in_layer,\
522-
Aeq_z_curr, Aeq_z_next, Aeq_binary_layer, rhs_eq_layer, _, _ =\
523-
_add_constraint_by_layer(
522+
Aeq_z_curr, Aeq_z_next, Aeq_binary_layer, rhs_eq_layer, _, _,\
523+
_, _ = _add_constraint_by_layer(
524524
self.model[2*layer_count], self.model[2*layer_count+1],
525525
z_pre_relu_lo[self.relu_unit_index[layer_count]],
526526
z_pre_relu_up[self.relu_unit_index[layer_count]])
@@ -588,7 +588,14 @@ def output_constraint(self, x_lo, x_up,
588588
(self.model[-1].out_features, ), dtype=self.dtype)
589589
else:
590590
mip_constr_return.Cout = self.model[-1].bias.clone()
591-
591+
binary_lo = torch.zeros((self.num_relu_units,), dtype=self.dtype)
592+
binary_up = torch.ones((self.num_relu_units,), dtype=self.dtype)
593+
# If the input to the relu is always >= 0, then the relu will always
594+
# be active.
595+
binary_lo[z_pre_relu_lo >= 0] = 1.
596+
# If the input to the relu is always <= 0, then the relu will always
597+
# be inactive.
598+
binary_up[z_pre_relu_up <= 0] = 0.
592599
mip_constr_return.Ain_input = Ain_input[:ineq_constr_count]
593600
mip_constr_return.Ain_slack = Ain_slack[:ineq_constr_count]
594601
mip_constr_return.Ain_binary = Ain_binary[:ineq_constr_count]
@@ -597,6 +604,8 @@ def output_constraint(self, x_lo, x_up,
597604
mip_constr_return.Aeq_slack = Aeq_slack[:eq_constr_count]
598605
mip_constr_return.Aeq_binary = Aeq_binary[:eq_constr_count]
599606
mip_constr_return.rhs_eq = rhs_eq[:eq_constr_count]
607+
mip_constr_return.binary_lo = binary_lo
608+
mip_constr_return.binary_up = binary_up
600609
return (mip_constr_return, z_pre_relu_lo, z_pre_relu_up,
601610
z_post_relu_lo, z_post_relu_up, output_lo, output_up)
602611

@@ -994,6 +1003,8 @@ def _add_constraint_by_neuron(
9941003
Aeq_neuron_output = torch.empty((0, 1), dtype=dtype)
9951004
Aeq_binary = torch.empty((0, 1), dtype=dtype)
9961005
rhs_eq = torch.empty((0, ), dtype=dtype)
1006+
binary_lo = 0
1007+
binary_up = 1
9971008
else:
9981009
# The (leaky) ReLU is always active, or always inactive. If
9991010
# the lower bound output_lo[j] >= 0, then it is always active,
@@ -1004,18 +1015,17 @@ def _add_constraint_by_neuron(
10041015
# zᵢ₊₁(j) = c*((Wᵢzᵢ)(j) + bᵢ(j)) and βᵢ(j) = 0
10051016
if neuron_input_lo >= 0:
10061017
slope = 1.
1007-
binary_value = 1
1018+
binary_lo = 1
1019+
binary_up = 1
10081020
elif neuron_input_up <= 0:
10091021
slope = relu_layer.negative_slope if isinstance(
10101022
relu_layer, nn.LeakyReLU) else 0.
1011-
binary_value = 0.
1012-
Aeq_linear_input = torch.cat((-slope * Wij.reshape(
1013-
(1, -1)), torch.zeros((1, Wij.numel()), dtype=dtype)),
1014-
dim=0)
1015-
Aeq_neuron_output = torch.tensor([[1.], [0]], dtype=dtype)
1016-
Aeq_binary = torch.tensor([[0.], [1.]], dtype=dtype)
1017-
rhs_eq = torch.stack(
1018-
(slope * bij, torch.tensor(binary_value, dtype=dtype)))
1023+
binary_lo = 0
1024+
binary_up = 0
1025+
Aeq_linear_input = -slope * Wij.reshape((1, -1))
1026+
Aeq_neuron_output = torch.tensor([[1.]], dtype=dtype)
1027+
Aeq_binary = torch.tensor([[0.]], dtype=dtype)
1028+
rhs_eq = slope * bij.reshape((1,))
10191029
Ain_linear_input = torch.empty((0, Wij.numel()), dtype=dtype)
10201030
Ain_neuron_output = torch.empty((0, 1), dtype=dtype)
10211031
Ain_binary = torch.empty((0, 1), dtype=dtype)
@@ -1024,7 +1034,7 @@ def _add_constraint_by_neuron(
10241034
relu_layer, neuron_input_lo, neuron_input_up)
10251035
return Ain_linear_input, Ain_neuron_output, Ain_binary, rhs_in,\
10261036
Aeq_linear_input, Aeq_neuron_output, Aeq_binary, rhs_eq,\
1027-
neuron_output_lo, neuron_output_up
1037+
neuron_output_lo, neuron_output_up, binary_lo, binary_up
10281038

10291039

10301040
def _add_constraint_by_layer(linear_layer, relu_layer,
@@ -1057,10 +1067,13 @@ def _add_constraint_by_layer(linear_layer, relu_layer,
10571067
z_next_up = []
10581068
bias = linear_layer.bias if linear_layer.bias is not None else \
10591069
torch.zeros((linear_layer.out_features,), dtype=dtype)
1070+
binary_lo = torch.zeros((linear_layer.out_features,), dtype=dtype)
1071+
binary_up = torch.ones((linear_layer.out_features,), dtype=dtype)
10601072
for j in range(linear_layer.out_features):
10611073
Ain_linear_input, Ain_neuron_output, Ain_binary_j, rhs_in_j,\
10621074
Aeq_linear_input, Aeq_neuron_output, Aeq_binary_j, rhs_eq_j,\
1063-
neuron_output_lo, neuron_output_up = _add_constraint_by_neuron(
1075+
neuron_output_lo, neuron_output_up, binary_lo[j], binary_up[j] =\
1076+
_add_constraint_by_neuron(
10641077
linear_layer.weight[j], bias[j], relu_layer,
10651078
linear_output_lo[j], linear_output_up[j])
10661079
Ain_z_curr.append(Ain_linear_input)
@@ -1089,4 +1102,4 @@ def _add_constraint_by_layer(linear_layer, relu_layer,
10891102
torch.cat(Ain_binary, dim=0), torch.cat(rhs_in, dim=0),\
10901103
torch.cat(Aeq_z_curr, dim=0), torch.cat(Aeq_z_next, dim=0),\
10911104
torch.cat(Aeq_binary, dim=0), torch.cat(rhs_eq, dim=0),\
1092-
torch.stack(z_next_lo), torch.stack(z_next_up)
1105+
torch.stack(z_next_lo), torch.stack(z_next_up), binary_lo, binary_up

neural_network_lyapunov/test/test_relu_to_optimization.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,7 @@ def test_model(model, method):
297297
num_ineq = (relu_free_pattern.num_relu_units -
298298
num_z_pre_relu_lo_positive -
299299
num_z_pre_relu_up_negative) * 4 + 4
300-
num_eq = (num_z_pre_relu_lo_positive + num_z_pre_relu_up_negative)\
301-
* 2
300+
num_eq = num_z_pre_relu_lo_positive + num_z_pre_relu_up_negative
302301
self.assertEqual(mip_constr_return.Ain_input.shape, (num_ineq, 2))
303302
self.assertEqual(mip_constr_return.Ain_slack.shape,
304303
(num_ineq, relu_free_pattern.num_relu_units))
@@ -345,6 +344,10 @@ def test_input_output(x):
345344
mip_constr_return.Aeq_binary.detach().numpy() @
346345
beta_var
347346
== mip_constr_return.rhs_eq.squeeze().detach().numpy())
347+
if mip_constr_return.binary_lo is not None:
348+
con.append(beta_var >= mip_constr_return.binary_lo)
349+
if mip_constr_return.binary_up is not None:
350+
con.append(beta_var <= mip_constr_return.binary_up)
348351
objective = cp.Minimize(0.)
349352
prob = cp.Problem(objective, con)
350353
prob.solve(solver=cp.GUROBI)

neural_network_lyapunov/test/test_relu_to_optimization_utils.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def constraint_test(self, Wij, bij, relu_layer, neuron_input_lo,
1515
neuron_input_up):
1616
Ain_linear_input, Ain_neuron_output, Ain_binary, rhs_in,\
1717
Aeq_linear_input, Aeq_neuron_output, Aeq_binary, rhs_eq,\
18-
neuron_output_lo, neuron_output_up = \
18+
neuron_output_lo, neuron_output_up, binary_lo, binary_up = \
1919
relu_to_optimization._add_constraint_by_neuron(
2020
Wij, bij, relu_layer, neuron_input_lo, neuron_input_up)
2121

@@ -35,7 +35,10 @@ def constraint_test(self, Wij, bij, relu_layer, neuron_input_lo,
3535
linear_input = model.addVars(Wij.numel(),
3636
lb=-gurobipy.GRB.INFINITY)
3737
neuron_output = model.addVars(1, lb=-gurobipy.GRB.INFINITY)
38-
binary = model.addVars(1, vtype=gurobipy.GRB.BINARY)
38+
binary = model.addVars(1,
39+
lb=binary_lo,
40+
ub=binary_up,
41+
vtype=gurobipy.GRB.BINARY)
3942
model.addMConstrs(
4043
[Ain_linear_input, Ain_neuron_output, Ain_binary],
4144
[linear_input, neuron_output, binary],
@@ -107,7 +110,7 @@ def constraint_test(self, linear_layer, relu_layer, z_curr_lo, z_curr_up):
107110
linear_output_lo, linear_output_up = mip_utils.propagate_bounds(
108111
linear_layer, z_curr_lo, z_curr_up)
109112
Ain_z_curr, Ain_z_next, Ain_binary, rhs_in, Aeq_z_curr, Aeq_z_next,\
110-
Aeq_binary, rhs_eq, z_next_lo, z_next_up = \
113+
Aeq_binary, rhs_eq, z_next_lo, z_next_up, binary_lo, binary_up = \
111114
relu_to_optimization._add_constraint_by_layer(
112115
linear_layer, relu_layer, linear_output_lo, linear_output_up)
113116
z_next_lo_expected, z_next_up_expected = mip_utils.propagate_bounds(
@@ -130,6 +133,8 @@ def constraint_test(self, linear_layer, relu_layer, z_curr_lo, z_curr_up):
130133
z_next = model.addVars(linear_layer.out_features,
131134
lb=-gurobipy.GRB.INFINITY)
132135
beta = model.addVars(linear_layer.out_features,
136+
lb=binary_lo,
137+
ub=binary_up,
133138
vtype=gurobipy.GRB.BINARY)
134139
model.addMConstrs(
135140
[torch.eye(linear_layer.in_features, dtype=self.dtype)],
@@ -185,7 +190,7 @@ def constraint_gradient_test(self, linear_layer, relu_layer, z_curr_lo,
185190
linear_output_lo, linear_output_up = mip_utils.propagate_bounds(
186191
linear_layer, z_curr_lo, z_curr_up)
187192
Ain_z_curr, Ain_z_next, Ain_binary, rhs_in, Aeq_z_curr, Aeq_z_next,\
188-
Aeq_binary, rhs_eq, z_next_lo, z_next_up =\
193+
Aeq_binary, rhs_eq, z_next_lo, z_next_up, binary_lo, binary_up =\
189194
relu_to_optimization._add_constraint_by_layer(
190195
linear_layer, relu_layer, linear_output_lo, linear_output_up)
191196

@@ -213,7 +218,8 @@ def eval_fun(linear_layer_weight, linear_layer_bias, input_lo_np,
213218
linear_layer, torch.from_numpy(input_lo_np),
214219
torch.from_numpy(input_up_np))
215220
Ain_z_curr, Ain_z_next, Ain_binary, rhs_in, Aeq_z_curr,\
216-
Aeq_z_next, Aeq_binary, rhs_eq, z_next_lo, z_next_up =\
221+
Aeq_z_next, Aeq_binary, rhs_eq, z_next_lo, z_next_up,\
222+
binary_lo, binary_up =\
217223
relu_to_optimization._add_constraint_by_layer(
218224
linear_layer, relu_layer, linear_output_lo,
219225
linear_output_up)

0 commit comments

Comments
 (0)