parameters: (orig_model,
                   new_input_nodes=None,
                   input_layers=None)
return_statement: return outputs
docstring: modify (cut and/or stitch) keras submodel
  
      layer objects themselved will be untouched - the new model, even if it includes, 
      say, a subset of the previous layers, those layer objects will be shared with
      the original model
  
      given an original model:
          model stitching: given new input node(s), get output tensors of having pushed these 
          nodes through the model
          
          model cutting: given input layer (pointers) inside the model, the new input nodes
          will match the new input layers, hence allowing cutting the model
  
      Parameters:
          orig_model: original keras model pointer
          new_input_nodes: a pointer to a new input node replacement
          input_layers: the name of the layer in the original model to replace input nodes
      
      Returns:
          pointer to modified model
docstring_summary: modify (cut and/or stitch) keras submodel
docstring_tokens: modify ( cut and / or stitch ) keras submodel
function: def mod_submodel(orig_model,
                   new_input_nodes=None,
                   input_layers=None):
      """
      modify (cut and/or stitch) keras submodel
  
      layer objects themselved will be untouched - the new model, even if it includes, 
      say, a subset of the previous layers, those layer objects will be shared with
      the original model
  
      given an original model:
          model stitching: given new input node(s), get output tensors of having pushed these 
          nodes through the model
          
          model cutting: given input layer (pointers) inside the model, the new input nodes
          will match the new input layers, hence allowing cutting the model
  
      Parameters:
          orig_model: original keras model pointer
          new_input_nodes: a pointer to a new input node replacement
          input_layers: the name of the layer in the original model to replace input nodes
      
      Returns:
          pointer to modified model
      """
  
      def _layer_dependency_dict(orig_model):
          """
          output: a dictionary of all layers in the orig_model
          for each layer:
              dct[layer] is a list of lists of layers.
          """
  
          out_layers = orig_model.output_layers
          out_node_idx = orig_model.output_layers_node_indices
  
          node_list = [ol._inbound_nodes[out_node_idx[i]] for i, ol in enumerate(out_layers)]
              
          dct = {}
          dct_node_idx = {}
          while len(node_list) > 0:
              node = node_list.pop(0)
                  
              add = True
              # if not empty. we need to check that we're not adding the same layers through the same node.
              if len(dct.setdefault(node.outbound_layer, [])) > 0:
                  for li, layers in enumerate(dct[node.outbound_layer]):
                      if layers == node.inbound_layers and \
                          dct_node_idx[node.outbound_layer][li] == node.node_indices:
                          add = False
                          break
              if add:
                  dct[node.outbound_layer].append(node.inbound_layers)
                  dct_node_idx.setdefault(node.outbound_layer, []).append(node.node_indices)
                  #print(node, node.outbound_layer)
              # append is in place
  
              # add new node
              for li, layer in enumerate(node.inbound_layers):
                  if hasattr(layer, '_inbound_nodes'):
                      node_list.append(layer._inbound_nodes[node.node_indices[li]])
  
          return dct
  
      def _get_new_layer_output(layer, new_layer_outputs, inp_layers):
          """
          (recursive) given a layer, get new outbound_nodes based on new inbound_nodes
  
          new_layer_outputs is a (reference) dictionary that we will be adding
          to within the recursion stack.
          """
  
          if layer not in new_layer_outputs:
  
              if layer not in inp_layers:
                  raise Exception('layer %s is not in inp_layers' % layer.name)
  
              # for all input layers to this layer, gather their output (our input)
              for group in inp_layers[layer]:
                  input_nodes = [None] * len(group)
                  for li, inp_layer in enumerate(group):
                      if inp_layer in new_layer_outputs:
                          input_nodes[li] = new_layer_outputs[inp_layer]
                      else: # recursive call
                          input_nodes[li] = _get_new_layer_output(inp_layer, new_layer_outputs, inp_layers)
  
                  # layer call
                  if len(input_nodes) == 1:
                      new_layer_outputs[layer] = layer(*input_nodes)
                  else:
                      new_layer_outputs[layer] = layer(input_nodes)
  
          return new_layer_outputs[layer]
  
  
  
      # for each layer create list of input layers
      inp_layers = _layer_dependency_dict(orig_model)
  
      # get input layers
      #   These layers will be 'ignored' in that they will not be called!
      #   instead, the outbound nodes of the layers will be the input nodes
      #   computed below or passed in
      if input_layers is None: # if none provided, search for them
          InputLayerClass = keras.engine.topology.InputLayer
          input_layers = [l for l in orig_model.layers if isinstance(l, InputLayerClass)]
  
      else:
          if not isinstance(input_layers, (tuple, list)):
              input_layers = [input_layers]
          for idx, input_layer in enumerate(input_layers):
              # if it's a string, assume it's layer name, and get the layer pointer
              if isinstance(input_layer, str):
                  input_layers[idx] = orig_model.get_layer(input_layer)
  
      # process new input nodes
      if new_input_nodes is None:
          input_nodes = list(orig_model.inputs)
      else:
          input_nodes = new_input_nodes
      assert len(input_nodes) == len(input_layers)
  
      # initialize dictionary of layer:new_output_node
      #   note: the input layers are not called, instead their outbound nodes
      #   are assumed to be the given input nodes. If we call the nodes, we can run
      #   into multiple-inbound-nodes problems, or if we completely skip the layers altogether
      #   we have problems with multiple inbound input layers into subsequent layers
      new_layer_outputs = {}
      for i, input_layer in enumerate(input_layers):
          new_layer_outputs[input_layer] = input_nodes[i]
  
      # recursively go back from output layers and request new input nodes
      output_layers = []
      for layer in orig_model.layers:
          if hasattr(layer, '_inbound_nodes'):
              for i in range(len(layer._inbound_nodes)):
                  if layer.get_output_at(i) in orig_model.outputs:
                      output_layers.append(layer)
                      break
      assert len(output_layers) == len(orig_model.outputs), "Number of output layers don't match"
  
      outputs = [None] * len(output_layers)
      for li, output_layer in enumerate(output_layers):
          outputs[li] = _get_new_layer_output(output_layer, new_layer_outputs, inp_layers)
  
      return outputs
function_tokens: def mod_submodel ( orig_model , new_input_nodes = None , input_layers = None ) : def _layer_dependency_dict ( orig_model ) : """
          output: a dictionary of all layers in the orig_model
          for each layer:
              dct[layer] is a list of lists of layers.
          """ out_layers = orig_model . output_layers out_node_idx = orig_model . output_layers_node_indices node_list = [ ol . _inbound_nodes [ out_node_idx [ i ] ] for i , ol in enumerate ( out_layers ) ] dct = { } dct_node_idx = { } while len ( node_list ) > 0 : node = node_list . pop ( 0 ) add = True # if not empty. we need to check that we're not adding the same layers through the same node. if len ( dct . setdefault ( node . outbound_layer , [ ] ) ) > 0 : for li , layers in enumerate ( dct [ node . outbound_layer ] ) : if layers == node . inbound_layers and dct_node_idx [ node . outbound_layer ] [ li ] == node . node_indices : add = False break if add : dct [ node . outbound_layer ] . append ( node . inbound_layers ) dct_node_idx . setdefault ( node . outbound_layer , [ ] ) . append ( node . node_indices ) #print(node, node.outbound_layer) # append is in place # add new node for li , layer in enumerate ( node . inbound_layers ) : if hasattr ( layer , '_inbound_nodes' ) : node_list . append ( layer . _inbound_nodes [ node . node_indices [ li ] ] ) return dct def _get_new_layer_output ( layer , new_layer_outputs , inp_layers ) : """
          (recursive) given a layer, get new outbound_nodes based on new inbound_nodes
  
          new_layer_outputs is a (reference) dictionary that we will be adding
          to within the recursion stack.
          """ if layer not in new_layer_outputs : if layer not in inp_layers : raise Exception ( 'layer %s is not in inp_layers' % layer . name ) # for all input layers to this layer, gather their output (our input) for group in inp_layers [ layer ] : input_nodes = [ None ] * len ( group ) for li , inp_layer in enumerate ( group ) : if inp_layer in new_layer_outputs : input_nodes [ li ] = new_layer_outputs [ inp_layer ] else : # recursive call input_nodes [ li ] = _get_new_layer_output ( inp_layer , new_layer_outputs , inp_layers ) # layer call if len ( input_nodes ) == 1 : new_layer_outputs [ layer ] = layer ( * input_nodes ) else : new_layer_outputs [ layer ] = layer ( input_nodes ) return new_layer_outputs [ layer ] # for each layer create list of input layers inp_layers = _layer_dependency_dict ( orig_model ) # get input layers #   These layers will be 'ignored' in that they will not be called! #   instead, the outbound nodes of the layers will be the input nodes #   computed below or passed in if input_layers is None : # if none provided, search for them InputLayerClass = keras . engine . topology . InputLayer input_layers = [ l for l in orig_model . layers if isinstance ( l , InputLayerClass ) ] else : if not isinstance ( input_layers , ( tuple , list ) ) : input_layers = [ input_layers ] for idx , input_layer in enumerate ( input_layers ) : # if it's a string, assume it's layer name, and get the layer pointer if isinstance ( input_layer , str ) : input_layers [ idx ] = orig_model . get_layer ( input_layer ) # process new input nodes if new_input_nodes is None : input_nodes = list ( orig_model . inputs ) else : input_nodes = new_input_nodes assert len ( input_nodes ) == len ( input_layers ) # initialize dictionary of layer:new_output_node #   note: the input layers are not called, instead their outbound nodes #   are assumed to be the given input nodes. If we call the nodes, we can run #   into multiple-inbound-nodes problems, or if we completely skip the layers altogether #   we have problems with multiple inbound input layers into subsequent layers new_layer_outputs = { } for i , input_layer in enumerate ( input_layers ) : new_layer_outputs [ input_layer ] = input_nodes [ i ] # recursively go back from output layers and request new input nodes output_layers = [ ] for layer in orig_model . layers : if hasattr ( layer , '_inbound_nodes' ) : for i in range ( len ( layer . _inbound_nodes ) ) : if layer . get_output_at ( i ) in orig_model . outputs : output_layers . append ( layer ) break assert len ( output_layers ) == len ( orig_model . outputs ) , "Number of output layers don't match" outputs = [ None ] * len ( output_layers ) for li , output_layer in enumerate ( output_layers ) : outputs [ li ] = _get_new_layer_output ( output_layer , new_layer_outputs , inp_layers ) return outputs