def normalizeNNet(readNNetFile, writeNNetFile=None): weights, biases, inputMins, inputMaxes, means, ranges = readNNet( readNNetFile, withNorm=True) numInputs = weights[0].shape[0] numOutputs = weights[-1].shape[1] # Adjust weights and biases of first layer for i in range(numInputs): weights[0][i, :] /= ranges[i] biases[0] -= np.matmul(weights[0].T, means[:-1]) # Adjust weights and biases of last layer weights[-1] *= ranges[-1] biases[-1] *= ranges[-1] biases[-1] += means[-1] # Nominal mean and range vectors means = np.zeros(numInputs + 1) ranges = np.ones(numInputs + 1) if writeNNetFile is not None: writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, writeNNetFile) return None return weights, biases
def write_to_nnet(network, net_name): weights = np.array(network.weights) biases = np.array(network.biases) inputMins = np.array(network.inputMinimums) inputMaxes = np.array(network.inputMaximums) means = np.array(network.inputMeans) ranges = np.array(network.inputRanges) fileName = os.path.join(NNET_PATH, net_name + '.nnet') writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, fileName) return fileName
def test_write(self): nnetFile1 = "nnet/TestNetwork.nnet" nnetFile2 = "nnet/TestNetwork.v2.nnet" testInput = np.array([1.0, 1.0, 1.0, 100.0, 1.0]).astype(np.float32) nnet1 = NNet(nnetFile1) writeNNet(nnet1.weights, nnet1.biases, nnet1.mins, nnet1.maxes, nnet1.means, nnet1.ranges, nnetFile2) nnet2 = NNet(nnetFile2) eval1 = nnet1.evaluate_network(testInput) eval2 = nnet2.evaluate_network(testInput) percChangeNNet = max(abs((eval1 - eval2) / eval1)) * 100.0 self.assertTrue(percChangeNNet < 1e-8) self.assertTrue(filecmp.cmp(nnetFile1, nnetFile2))
def FFTF2nnet(sess, inputMins, inputMaxes, means, ranges, order, nnetFile="", inputName="", outputName=""): weights, biases, inputSize = FFTF2W(sess, inputName, outputName) # Default values for input bounds and normalization constants if inputMins is None: inputMins = inputSize * [np.finfo(np.float32).min] if inputMaxes is None: inputMaxes = inputSize * [np.finfo(np.float32).max] if means is None: means = (inputSize + 1) * [0.0] if ranges is None: ranges = (inputSize + 1) * [1.0] writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, order, nnetFile)
def split_nn(nn_path, layer_number, property=None): """ Split nn into two nns at layer_number. Create file for each part of the nn. :param nn_path: Neural network file name :param layer_number: number of layer to split on. :param property: input region property. Used for gurobi to find bounds of the layer we split on. :return: the new nnets files. """ nn = NNet(nn_path) mid_layer_size = nn.layerSizes[layer_number] weights1 = nn.weights[:layer_number] weights2 = nn.weights[layer_number:] biases1 = nn.biases[:layer_number] biases2 = nn.biases[layer_number:] mins1 = nn.mins maxes1 = nn.maxes if property is not None: mins2, maxes2 = get_gurobi_bounds(nn_path, property_file_to_multi_range(property), layer_number, os.getcwd() + "/gurobi_bounds/split_bounds.txt") else: mins2 = [0] * mid_layer_size maxes2 = [1000]*mid_layer_size means1 = nn.means last_mean = nn.means[-1] means1[-1] = 0 means2 = [0] * mid_layer_size + [last_mean] ranges1 = nn.ranges last_range = nn.ranges[-1] ranges1[-1] = 1 ranges2 = [1] * mid_layer_size + [last_range] file1 = nn_path[ :-5] + "_part1" + ".nnet" # split the .nnet extenstion and add it at the end file2 = nn_path[:-5] + "_part2" + ".nnet" writeNNet(weights1, biases1, mins1, maxes1, means1, ranges1, file1) writeNNet(weights2, biases2, mins2, maxes2, means2, ranges2, file2) return file1, file2
#If true, use the steeper penalty. If false, use the milder penalty return l model = load_model(kerasFile, custom_objects={'asymMSE': asymMSE}) # Get a list of the model weights model_params = model.get_weights() # Split the network parameters into weights and biases, assuming they alternate weights = model_params[0:len(model_params):2] biases = model_params[1:len(model_params):2] # Transpose weight matrices weights = [w.T for w in weights] ## Script showing how to run pb2nnet # Min and max values used to bound the inputs inputMins = [0.0, -3.141593, -3.141593, 100.0, 0.0] inputMaxes = [60760.0, 3.141593, 3.141593, 1200.0, 1200.0] # Mean and range values for normalizing the inputs and outputs. All outputs are normalized with the same value means = [1.9791091e+04, 0.0, 0.0, 650.0, 600.0, 7.5188840201005975] ranges = [60261.0, 6.28318530718, 6.28318530718, 1100.0, 1200.0, 373.94992] # Tensorflow pb file to convert to .nnet file nnetFile = kerasFile[:-2] + 'nnet' # Convert the file writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, nnetFile)
def pb2nnet(pbFile, inputMins=None, inputMaxes=None, means=None, ranges=None, nnetFile="", inputName="", outputName="", savedModel=False, savedModelTags=[]): ''' Write a .nnet file from a frozen Tensorflow protobuf or SavedModel Args: pbFile (str): If savedModel is false, path to the frozen graph .pb file. If savedModel is true, path to SavedModel folder, which contains .pb file and variables subdirectory. inputMins (list): Minimum values for each neural network input. inputMaxes (list): Maximum values for each neural network output. means (list): Mean value for each input and value for mean of all outputs, used for normalization ranges (list): Range value for each input and value for range of all outputs, used for normalization inputName (str, optional): Name of operation corresponding to input. Default: "" outputName (str, optional) Name of operation corresponding to output. Default: "" savedModel (bool, optional) If false, load frozen graph. If true, load SavedModel object. Default: False savedModelTags (list, optional) If loading a SavedModel, the user must specify tags used. Default: [] ''' if nnetFile == "": nnetFile = pbFile[:-2] + 'nnet' if savedModel: ### Read SavedModel ### sess = tf.Session() tf.saved_model.loader.load(sess, savedModelTags, pbFile) ### Simplify graph using outputName, which must be specified for SavedModel ### simp_graph_def = graph_util.convert_variables_to_constants( sess, sess.graph.as_graph_def(), [outputName]) with tf.Graph().as_default() as graph: tf.import_graph_def(simp_graph_def, name="") sess = tf.Session(graph=graph) ### End reading SavedModel else: ### Read protobuf file and begin session ### with tf.gfile.GFile(pbFile, "rb") as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) with tf.Graph().as_default() as graph: tf.import_graph_def(graph_def, name="") sess = tf.Session(graph=graph) ### END reading protobuf ### ### Find operations corresponding to input and output ### if inputName: inputOp = sess.graph.get_operation_by_name(inputName) else: # If there is just one placeholder, use it as input ops = sess.graph.get_operations() placeholders = [x for x in ops if x.node_def.op == 'Placeholder'] assert len(placeholders) == 1 inputOp = placeholders[0] if outputName: outputOp = sess.graph.get_operation_by_name(outputName) else: # Assume that the last operation is the output outputOp = sess.graph.get_operations()[-1] # Recursively search for weights and bias parameters and add them to list # Search until the inputOp is found # If inputOp is not found, than the operation does not exist in the graph or does not lead to the output operation weights = [] biases = [] foundInputFlag = False foundInputFlag = processGraph(outputOp, inputOp, foundInputFlag, weights, biases) if foundInputFlag: # Default values for input bounds and normalization constants if inputMins is None: inputMins = inputSize * [np.finfo(np.float32).min] if inputMaxes is None: inputMaxes = inputSize * [np.finfo(np.float32).max] if means is None: means = (inputSize + 1) * [0.0] if ranges is None: ranges = (inputSize + 1) * [1.0] # Write NNet file writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, nnetFile) else: print("Could not find the given input in graph: %s" % inputOp.name)
def onnx2nnet(onnxFile, inputMins=None, inputMaxes=None, means=None, ranges=None, nnetFile="", inputName="", outputName=""): ''' Write a .nnet file from an onnx file Args: onnxFile: (string) Path to onnx file inputMins: (list) optional, Minimum values for each neural network input. inputMaxes: (list) optional, Maximum values for each neural network output. means: (list) optional, Mean value for each input and value for mean of all outputs, used for normalization ranges: (list) optional, Range value for each input and value for range of all outputs, used for normalization inputName: (string) optional, Name of operation corresponding to input. outputName: (string) optional, Name of operation corresponding to output. ''' if nnetFile == "": nnetFile = onnxFile[:-4] + 'nnet' model = onnx.load(onnxFile) graph = model.graph if not inputName: assert len(graph.input) == 1 inputName = graph.input[0].name if not outputName: assert len(graph.output) == 1 outputName = graph.output[0].name # Search through nodes until we find the inputName. # Accumulate the weight matrices and bias vectors into lists. # Continue through the network until we reach outputName. # This assumes that the network is "frozen", and the model uses initializers to set weight and bias array values. weights = [] biases = [] # Loop through nodes in graph for node in graph.node: # Ignore nodes that do not use inputName as an input to the node if inputName in node.input: # This supports three types of nodes: MatMul, Add, and Relu # The .nnet file format specifies only feedforward fully-connected Relu networks, so # these operations are sufficient to specify nnet networks. If the onnx model uses other # operations, this will break. if node.op_type == "MatMul": assert len(node.input) == 2 # Find the name of the weight matrix, which should be the other input to the node weightIndex = 0 if node.input[0] == inputName: weightIndex = 1 weightName = node.input[weightIndex] # Extract the value of the weight matrix from the initializers weights += [ numpy_helper.to_array(inits).T for inits in graph.initializer if inits.name == weightName ] # Update inputName to be the output of this node inputName = node.output[0] elif node.op_type == "Add": assert len(node.input) == 2 # Find the name of the bias vector, which should be the other input to the node biasIndex = 0 if node.input[0] == inputName: biasIndex = 1 biasName = node.input[biasIndex] # Extract the value of the bias vector from the initializers biases += [ numpy_helper.to_array(inits) for inits in graph.initializer if inits.name == biasName ] # Update inputName to be the output of this node inputName = node.output[0] # For the .nnet file format, the Relu's are implicit, so we just need to update the input elif node.op_type == "Relu": inputName = node.output[0] # If there is a different node in the model that is not supported, through an error and break out of the loop else: print("Node operation type %s not supported!" % node.op_type) weights = [] biases = [] break # Terminate once we find the outputName in the graph if outputName == inputName: break # Check if the weights and biases were extracted correctly from the graph if outputName == inputName and len(weights) > 0 and len(weights) == len( biases): inputSize = weights[0].shape[0] # Default values for input bounds and normalization constants if inputMins is None: inputMins = inputSize * [np.finfo(np.float32).min] if inputMaxes is None: inputMaxes = inputSize * [np.finfo(np.float32).max] if means is None: means = (inputSize + 1) * [0.0] if ranges is None: ranges = (inputSize + 1) * [1.0] # Print statements print("Converted ONNX model at %s" % onnxFile) print(" to an NNet model at %s" % nnetFile) # Write NNet file writeNNet(weights, biases, inputMins, inputMaxes, means, ranges, nnetFile) # Something went wrong, so don't write the NNet file else: print("Could not write NNet file!")
def save_nnet(weights, biases, input_mins, input_maxs, means, ranges, output_path): # write model in nnet format. writeNNet(weights, biases, input_mins, input_maxs, means, ranges, output_path)