Please note that this page is under construction.
Flow
This page serves to explain the overall flow of the toolbox. For examples and explanation on how to use specific verification functions, please refer to the tutorials.
In general, verification algorithms follow the paradigm of Branch and Bound. This process can be summarized into three steps:
- split the input set into smaller sets, which we call "branches",
- propagate the bound through the model for a given branch,
- check whether the bound of the final layer satisfies the output specificaiton.
Repeat or terminate the process based on the result.
ModelVerification.jl uses a modularized code structure to support various combinations of search methods, split methods, and solvers for a variety of neural network architectures and geometric representations for the safety specifications. After reading through this section, the user should have an overall idea of the flow of the toolbox and the design philosophy behind it. Thanks to the highly modularized structure of the toolbox, the user can add additional functionalities at any layer of the verification process.
Definition of Terms
Here, we define some terms that are unique to the toolbox or are used differently compared to the typical usage.
- Instance: combination of all the necessary information to define an "instance" of neural network verification problem. This is consisted of:
- Propagation Method: this is the bound propagation method used for verifying the problem. In other words, it is the choice of term to represent the "solver": all the solvers in ModelVerification.jl are represented as a propagation method. However, this is different from the methods in
propagate.jl
. This will be clearer in the following explanations. - Model / (Deep) Neural Network / Network: these terms are used interchangeably and represent the deep neural network (DNN) to be verified.
- Node: (This is not equivalent to a "neuron" in a traditional deep learning sense.) This refers to a "node" in a computational-graph sense.
- Layer: (This is not equivalent to a "layer" in a traditional deep learning sense.) This refers to an operation at a node, such as ReLU activation function.
- BaB: "Branch-and-Bound" is a method that creates a binary tree for the search space where the verification is employed.
- Set vs Bound: One set is composed of bounds. E.g., for the output reachable set is a union of output bounds. But we use these terms interchangeably throughout the toolbox.
1. Creating an instance: what kind of verification problem do you want to solve?
Let's first create an instance. An instance contains all the information required to run the verify
function. This function does the heavy-lifting where the verification problem is solved. As long as the user properly defines the problem and solver methods, this is the only function the user has to call. To run verify
, the user has to provide the following arguments. These collectively define an "instance":
SearchMethod
: Algorithm for iterating through the branches, such asBFS
(breadth-first search) andDFS
(depth-first search).SplitMethod
: Algorithm for splitting an unknown branch into smaller pieces for further refinement. This is also used in the first step ofverify
to populate the branches bank. In other words, it splits the input specification into branches to facilitate the propagation process.PropMethod
: Solver used to verify the problem, such asAi2
andCrown
.Problem
: Problem to be verified. It consists of aNetwork
, and input and output specifications.
This toolbox design choice allows for extensive customization of methods and solvers by defining different search or split methods. The user simply needs to add their chosen methods in the specific files (search.jl
and split.jl
), which the solvers will automatically use for the verification process.
SearchMethod
SearchMethod
describes the strategy the solver uses when iterating through the branches. Currently, ModelVerification.jl only supports Breath-first Search (BFS). The solver can exploit parallel analysis of the nodes in the BaB by indicating a batch_size
is greater than 1.
SplitMethod
SplitMethod
specifies how many splits and where they are performed on a single node of the BaB. Depending on the SplitMethod
, the solver will split either the input space or the ReLU nodes.
The following split methods are supported:
- Bisectection (
Bisect
): splits either the input space or the ReLU nodes. - Branch-and-bound (
BaBSR
): splits the ReLU nodes.
PropMethod
PropMethod
is the solver to be used for the verification.
The following solvers are supported:
- ExactReach
- Ai2
- ImageStar
- ImageZono
- Crown
- Alpha-Crown
- Beta-Crown
Problem
Problem
is composed by the model to be verified and the input & output specifications. Specifically, this part of the "instance" encodes what we want to verify rather than how we achieve the formal verification results.
- For information on how to load or convert models and how they are represented in ModelVerification.jl, please refer Network.
- For the different geometric representations for the input and output specifications, please refer Input-Output Specification.
2. Verifying the instance: spinning through the branches - where the magic happens!
verify
is the main function called by ModelVerification.jl to start the verification process of the "instance" provided by the user.
ModelVerification.verify
— Functionverify(searchmethod::SearchMethod, splitmethod::SplitMethod, propmethod::PropMethod, problem::Problem; timeout=86400, attackrestart=100, collectbound=false, summary=false, pre_split=nothing)
This is the main function for verification. It takes in a search method, a split method, a propagation method, and a problem, and returns a result. The result is of type ResultInfo
, which is a wrapper for the following Result
types: BasicResult
, CounterExampleResult
, AdversarialResult
, ReachabilityResult
, EnumerationResult
, or timeout. For each Result
, the status
field is either :violated
, :verified
, or :unknown
. Optional arguments can be passed to the function to control the timeout, the number of restarts for the attack, whether to collect the bounds for each branch, whether to print a summary of the verification process, and whether to pre-split the problem.
Arguments
search_method
(SearchMethod
): The search method, such asBFS
, used to search through the branches.split_method
(SplitMethod
): The split method, such asBisect
, used to split the branches.prop_method
(PropMethod
): The propagation method, such asAi2
, used to propagate the constraints.problem
(Problem
): The problem to be verified - consists of a network, input set, and output set.time_out
(Int
): The timeout in seconds. Defaults to 86400 seconds, or 24 hours. If the timeout is reached, the function returns:timeout
.attack_restart
(Int
): The number of restarts for the attack. Defaults to 100.collect_bound
(Bool
): Whether to collect the bounds for each branch.summary
(Bool
): Whether to print a summary of the verification process.pre_split
: A function that takes in aProblem
and returns aProblem
with the input set pre-split. Defaults tonothing
.search_adv_bound
(Bool
): Whether to search the maximal input bound that can pass the verification (get:holds
) with the given setting.
Returns
- The result is
ResultInfo
, thestatus
field is either:violated
,:verified
,:unknown
, or:timeout
. The info is a dictionary that contains other information.
prepare_problem
The first step of verify
is prepare_problem
which preprocesses the Problem
into a form that is compatible with the verification solver. Its main two functionalities are:
- Retrieves the model information and stores it inside
model_info
, - Exploits the
init_bound
function which returns the geometry representation that matches the solver requirements. For instance, since CROWN is a backward propagation method, theinit_bound
function returns the geometry used to encode the output specification.
The result of prepare_problem
are two variables:
model_info
: structure that contains information about theFlux
model,prepared_problem
:Problem
with a processed input-output specification.
search_branches
ModelVerification.search_branches
— Methodsearch_branches(search_method::BFS, split_method, prop_method, problem, model_info;
collect_bound=false,
comp_verified_ratio=false,
pre_split=nothing,
verbose=false,
time_out=nothing)
Searches through the branches in the branch bank, branches
, until the branch bank is empty or time out. In each iteration (up to search_method.max_iter
), a batch of unverified branches will be extracted from the branch bank. Then, the following is performed to verify the model:
prepare_method
initializes the bound of the start node of the
computational graph based on the geometric representation and corresponding solver.
propagate
propagates the bound from the start node to the end node of the
computational graph.
process_bound
processes the bounds resulting from the propagation
accordingly to the solver, prop_method
. For example, for Ai2-based methods, process_bound
simply returns the bounds from the propagate
step. However, for Crown-based methods, process_bound
post-processes the bounds.
check_inclusion
decides whether the bound of the end node, the reachable
set, satisfies the output specification or not. 1. If not, i.e., :violated
, then the counterexample is returned and the verification process terminates. 2. If yes, i.e., :holds
, then the current branch is verified and the function starts Step 1 again for the next branch, if any. 3. If unknown, i.e., :unknown
, further refinement of the problem is preformed using split_branch
, which divides the current branch into smaller pieces and puts them into the branch bank for further verification. Such :unknown
status results due to the overapproximation introduced in the verification process.
If the branch bank is empty after going through search_method.max_iter
number of verification procedures, the model is verified to be valid and returns :holds
. If the branch bank is not empty, the function returns :unknown
.
Arguments
search_method
(BFS
): Breadth-first Search method for iteratively going through the branches.split_method
: Method for splitting the branches when further refinement is needed. This inclueds methods such asBisect
andBaBSR
.prop_method
: Propagation method used for the verification process. This is one of the solvers used to verify the given model.problem
: Problem definition for model verification.model_info
: Structure containing the information of the neural network to be verified.collect_bound
(optional): false, whether return the verified bound.comp_verified_ratio
(optional): false, whether to return how much percent of the input set is verified safepre_split
(optional): nothing, the number of split before any propagation. This is particularly useful for large input set that could lead to memory overflow.verbose
(optional): false, whether to print thingstime_out
(optional): nothing, time out to halt.
Returns
BasicResult(:holds)
if all the reachable sets are within the corresponding output specifications in the batch.BasicResult(:unknown)
if the function failed to make a firm decision within the given time. This is due to the overapproximation introduced in the verification process.CounterExampleResult(:violated, x)
if a reachable set is not within the corresponding output specification and there is a counterexample found.
search_branches
is the core function used for the verification process. It consists of several subfunctions that we are going to summarize in the following. At first, the function initializes the branch bank for the entire safety property's input-output domain. This can be done by advance_split
, which splits the input-output domain using the given split method. Thus, each "branch" is a subpart of the input-output domain. The function seeks to verify all the branches and if it cannot provide a result (i.e., we obtain an :unknown
answer), the function proceeds to split branch for a more refined verification process.. If the function verifies all the branches within the given maximum number of iterations, then we obtain a :holds
answer. If the function finds any branch that does not satisfy the safety property, then it returns :violated
.
For each iteration, i.e., for each branch, the function calls the following subfunctions to verify the branch:
prepare_method
: this function retrieves all the information to perform either the forward or backward propagation of the input domain of the branch. The result is stored in two variables calledbatch_out_spec
,batch_info
, which contain the batch of the outputs and a dictionary containing all the information of each node in the model.prepare_method
calls the following functions in sequence:init_propagation
: Differentiates betweenForwardProp
andBackwardProp
. If the solver being used employs a forward propagation method, then we start propagating from the input nodes. If it employs a backward propagation method, then we start from the output nodes.init_batch_bound
: Callsinit_bound
, which returns either the input or output geometry representation based on the type of propagation to perform.
The result of the previous function is then used in the
propagate
function. This function propagates the starting bounds through the model using the specified propagation method, i.e., the solver. Thepropagate
function acts as the overall logic for propagating the branch through the model and performs the propagation based on the layer operation (linear, ReLU, etc.) and the geometric representation for the bounds. The user can add additional layer operators in thepropagate/operators
folder. Moreover, the toolbox supports skip connections. Thepropagate
function returnsbatch_bound
, the bound of the output node, and an augmentedbatch_info
dictionary with the output bound information added.Now,
process_bounds
returns the reachable bounds obtained from thepropagate
function. Depending on the solver, the reachable bounds may be post-processed to optimized the verification procedure.Finally,
check_inclusion
checks for the overlapping between the reachable bounds obtained from the propagation and the safety property's output bounds. Since we are using overapproximation of the output reachable set, the only case in which we can obtain:holds
result is when the output reachable set is fully contained in the user-specified output set. On the other hand, the:violated
result is only possible when the sets are completely disjoint. In all other cases, we have an:unknown
answer and we proceed with the branch splitting method below to populate the branch bank.split_branch
is used to split the current branch with:unknown
answer into two separate branches which are split based on thesplit_method
. The sub-branches are then added to the "branches" bank.
We continue this loop until either get a :violated
result, a :hold
result for all the branches, or reach the maximum number of iterations.
search_adv_input_bound
If the verification result from verify
is not :holds
, i.e., either :unknown
or :violated
, then search_adv_input_bound
searches for the maximul input bound that can pass the verification, i.e., retrieves :holds
, with the given setting. This information is passed to the ResultInfo
as a dictionary field so that the user can check.
ModelVerification.search_adv_input_bound
— Functionsearch_adv_input_bound(search_method::SearchMethod,
split_method::SplitMethod,
prop_method::PropMethod,
problem::Problem;
eps = 1e-3)
This function is used to search the maximal input bound that can pass the verification (get :holds
) with the given setting. The search is done by binary search on the input bound that is scaled by the given ratio. This function is called in verify
function when search_adv_bound
is set to true
and the initial verification result is :unknown
or :violated
.
Arguments
search_method
(SearchMethod
): The search method, such asBFS
, used to search through the branches.split_method
(SplitMethod
): The split method, such asBisect
, used to split the branches.prop_method
(PropMethod
): The propagation method, such asAi2
, used to propagate the constraints.problem
(Problem
): The problem to be verified - consists of a network, input set, and output set.eps
(Real
): The precision of the binary search. Defaults to 1e-3.
Returns
- The maximal input bound that can pass the verification (get
:holds
) with the given setting.
3. Results and how to interpret them: so is my model good to go?
Once the verify
function is over, it returns a ResultInfo
that contains the status
(either :hold
, :violated
, :unknown
) and a dictionary that contains any other additional information needed to understand the verification results in detail, such as the verified bounds, adversarial input bounds, etc.
The following are the Result
types used interally for the toolbox to differentiate between different verification results. The result is either a BasicResult
, CounterExampleResult
, AdversarialResult
, ReachabilityResult
, or EnumerationResult
(to-be-supported). The status
field is either :violated
, :holds
, or :unknown
.
Output result | Explanation |
---|---|
[BasicResult::holds ] | The input-output constraint is always satisfied. |
[BasicResult::violated ] | The input-output constraint is violated, i.e., it exists a single point in the input constraint that violates the property. |
[BasicResult::unknown ] | Could not be determined if the property holds due to timeout in the computation. |
[CounterExampleResult ] | Like BasicResult, but also returns a counterexample if one is found (if status = :violated). The counterexample is a point in the input set that, after the NN, lies outside the output constraint set. |
[AdversarialResult ] | Like BasicResult, but also returns the maximum allowable disturbance in the input (if status = :violated). |
[ReachabilityResult ] | Like BasicResult, but also returns the output reachable set given the input constraint (if status = :violated). |
[EnumerationResult ] | Set of all the (un)safe regions in the safety property's domain. |
For more information, please refer to Output (Verification Results).