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

Add Guroby encoder for max constratints. #402

Merged

Conversation

tagomaru
Copy link
Contributor

@tagomaru tagomaru commented Nov 5, 2020

Signed-off-by: tagomaru tagomaru@users.noreply.github.com

This PR is going to add Guroby encoder for max constraints.

Sample NNET:

// simple_model 
2,2,3,3,
2,2,3,
0,
-100000.0,-100000.0,
100000.0,100000.0,
0.0,0.0,0.0,
1.0,1.0,1.0,
1.0,1.0,
-1.0,-1.0,
0.0,
0.0,
2.0,-1.0,
-1.0,1.0,
1.0,-1.0,
0.0,
0.0,
0.0,

Sample Code:

from maraboupy import Marabou as marabou
nnet_file_name = "./simple_model.nnet"
net = marabou.read_nnet(nnet_file_name)
net.setLowerBound(net.outputVars[0][0], 2)
net.setUpperBound(net.outputVars[0][0], 2)
net.setLowerBound(net.inputVars[0][0], 1)
net.setUpperBound(net.inputVars[0][0], 1)
net.addMaxConstraint(set(net.outputVars[0]), net.outputVars[0][0])
options = marabou.createOptions(solveWithMILP=True)
vals, stats = net.solve(options=options)

Test with the above sample code:

command

python3 guroby_max.py

result

...

sat
input 0 = 1.0
input 1 = -0.0
output 0 = 2.0
output 1 = -1.0
output 2 = 1.0

max

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
@tagomaru tagomaru requested a review from wu-haoze November 5, 2020 21:52
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
}

return ubs[umaxIndex];
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a new line here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

unsigned i = 0;
for ( const auto &x : xs ) {
// add binary variable
gurobi.addVariable( Stringf( "a%u", x ),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Naming the auxiliary variable like this would result in duplicated variable names. For instance, if we have two Max constraints Max([x1,x2], x4) and Max([x1,x3], x5), we would introduce the variable a1 twice, while we actually need two fresh variables.
One solution is to keep track of the latest index of the auxiliary variable. Please check the encodeReLUConstraint() method above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

terms.clear();

// add constraint: y >= x_i
terms.append( GurobiWrapper::Term( 1, Stringf( "x%u", y ) ) );
Copy link
Collaborator

Choose a reason for hiding this comment

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

"y >= x_i" is already encoded as an inequality here:

void MaxConstraint::addAuxiliaryEquations( InputQuery &inputQuery )
{
for ( auto element : _elements )
{
// If element is equal to _f, skip this step.
// The reason is to avoid adding equations like `1.00x00 -1.00x00 -1.00x01 = 0.00`.
if ( element == _f )
continue;
// Create an aux variable
unsigned auxVariable = inputQuery.getNumberOfVariables();
inputQuery.setNumberOfVariables( auxVariable + 1 );
// f >= element, or f - elemenet - aux = 0, for non-negative aux
Equation equation( Equation::EQ );
equation.addAddend( 1.0, _f );
equation.addAddend( -1.0, element );
equation.addAddend( -1.0, auxVariable );
equation.setScalar( 0 );
inputQuery.addEquation( equation );
// Set the bounds for the aux variable
inputQuery.setLowerBound( auxVariable, 0 );
// Todo: upper bound for aux?
}
}
.
I think you could just remove line 182 - 188.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

}

return ubs[umaxIndex];
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a newline here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

i = 0;
for ( const auto &x : xs ) {
// add constraint: y <= x_i + (1 - a_i) * (umax - l)
double umax = getUmax( ubs, i, m );
Copy link
Collaborator

Choose a reason for hiding this comment

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

if we store the <upper bound, variable> pair in a priority queue, we should be able to compute each Umax in O(log n) time. Currently it is computed in linear time, which might create overhead as the network size increases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did.

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
@tagomaru tagomaru changed the title Add Guroby encoder for max constratints. WIP Add Guroby encoder for max constratints. Nov 18, 2020
@tagomaru
Copy link
Contributor Author

I changed this status to WIP since Test_preprocessor failed after merging master.

12/58 Test #16: Test_Preprocessor .................***Failed    1.07 sec
Assertion violation! File /Users/teruhiro/Documents/work/Marabou/src/engine/InputQuery.cpp, line 279

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
@tagomaru tagomaru changed the title WIP Add Guroby encoder for max constratints. Add Guroby encoder for max constratints. Nov 20, 2020
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
@tagomaru
Copy link
Contributor Author

tagomaru commented Nov 22, 2020

Evaluation result for CNN having max pooling layer:

1. MILP

Model

The onnx file (resources/onnx/cnn_max_mninst2.onnx) of the model was converted from resources/keras/cnn_max_mnist2.h5.

model = models.Sequential()
model.add(layers.Conv2D(8, (4, 4), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.Conv2D(4, (4, 4), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

softmax was changed to linear function after compile.

cnn_max_mninst2 onnx

Test program (onnx_cnn_max_mnist2.py)

from maraboupy import Marabou
import numpy as np

options = Marabou.createOptions(verbosity = 2, solveWithMILP=True)
filename = 'cnn_max_mninst2.onnx'
network = Marabou.read_onnx(filename)

inputVars = network.inputVars[0][0]
outputVars = network.outputVars[0]

delta = 0.1
for h in range(inputVars.shape[0]):
    for w in range(inputVars.shape[1]):
        network.setLowerBound(inputVars[h][w][0], 0.5-delta)
        network.setUpperBound(inputVars[h][w][0], 0.5+delta)

# network.setLowerBound(outputVars[0], 1.0)

vals, stats = network.solve(options = options)
assert len(vals) > 0

dataONNX = np.zeros(inputVars.shape)
for i in range(inputVars.shape[0]):
    for j in range(inputVars.shape[1]):
        dataONNX[i][j][0] = vals[28 * i + j]

onnxEval = network.evaluateWithoutMarabou([dataONNX])

print('\nonnx output:\n', onnxEval)

Execution

$ /usr/bin/time -l python3 onnx_cnn_max_mnist2.py
(removed)
output 0 = -0.11791291526924269
output 1 = -0.1608994968618801
output 2 = -3.053131166312996
output 3 = -1.9990824676561814
output 4 = 1.0521385098435299
output 5 = -1.4255639982065038
output 6 = 0.02008915031516549
output 7 = -1.215866175485384
output 8 = -0.7083881322782134
output 9 = -0.47855364129358235

onnx output:
 [[-0.11791354 -0.1609008  -3.0531301  -1.9990811   1.0521375  -1.4255633
   0.02008897 -1.2158666  -0.7083874  -0.47855455]]
    96692.76 real     93300.76 user       412.88 sys
12301275136  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
   8717705  page reclaims
      1576  page faults
         0  swaps
         0  block input operations
         0  block output operations
         0  messages sent
         0  messages received
         0  signals received
     12856  voluntary context switches
  19258691  involuntary context switches

Elapsed time

Total 27 hours. (20 hours until preprocess was done)

2. Marabou

7 DAYS has passed, but it did not finish.
I killed the process.

_plConstraints.append( newPlc );
++numberOfDisjunctions;
}
else if ( constraint->getType() == MAX &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we assume that network level reasoner can pick up all the Max constraint. Maybe just remove the logic for Max here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your understanding is right.
However, when the network reasoner is true, the below part also picks up max constraints.
To avoid to pick up doubly, this neglects max constraints here.

        for ( const auto &constraint : other._networkLevelReasoner->
                  getConstraintsInTopologicalOrder() )
        {
            auto *newPlc = constraint->duplicateConstraint();
            _plConstraints.append( newPlc );
            _networkLevelReasoner->addConstraintInTopologicalOrder( newPlc );
        }

Does it make sense?

for ( const auto &x : xs )
{
// add binary variable
gurobi.addVariable( Stringf( "a%u_%u", _binVarIndex, x ),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please explain the name convention for future reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did.

@@ -51,6 +51,8 @@ class MILPEncoder
*/
Map<unsigned, String> _variableToVariableName;

unsigned _binVarIndex = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did.

tagomaru and others added 4 commits December 3, 2020 11:38
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>
@wu-haoze wu-haoze merged commit 163ef59 into NeuralNetworkVerification:master Jan 8, 2021
matanost pushed a commit that referenced this pull request Nov 2, 2021
* Add Guroby encoder for max constratints.

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* change indents

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* fix error on windows

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* cosmetic

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* improvement

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* do not count max constratints doubly

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* fix bug

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* change comment

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* add h5 and onnx files

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* add comments for bin vars

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* add onnx and keras files for evaluation

Signed-off-by: tagomaru <tagomaru@users.noreply.github.com>

* Update InputQuery.h

Co-authored-by: Haoze(Andrew) Wu <haozewu@stanford.edu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants