def basic_graph_no_v12() -> Tuple[MutableEdge, Vertex, Vertex, Vertex, Vertex]: complex_operation = MutableEdge((PointConv2D((1, 4)), MaxPool2D())) vertex1 = Vertex() vertex2 = Vertex() vertex3 = Vertex() vertex4 = Vertex() edge1 = IdentityOperation() edge2 = IdentityOperation() edge3 = IdentityOperation() edge4 = IdentityOperation() edge5 = IdentityOperation() edge6 = IdentityOperation() complex_operation.input_vertex.out_bound_edges.clear() complex_operation.input_vertex.out_bound_edges.extend([edge1, edge2, edge3]) edge1.end_vertex = vertex1 edge2.end_vertex = vertex2 edge3.end_vertex = vertex4 vertex1.out_bound_edges.append(edge6) edge6.end_vertex = complex_operation.output_vertex vertex2.out_bound_edges.append(edge4) edge4.end_vertex = complex_operation.output_vertex vertex3.out_bound_edges.append(edge5) edge5.end_vertex = complex_operation.output_vertex return complex_operation, vertex1, vertex2, vertex3, vertex4
def __init__(self, name: str) -> None: super().__init__() self.input_vertex = Vertex(name='input') self.output_vertex = Vertex(name='output') self.vertices_topo_order: List[Vertex] = [ self.output_vertex, self.input_vertex ] self.name = name self._layers_below = -1
def build_graph(self) -> None: vertex1 = Vertex() vertex2 = Vertex() edge1 = IdentityOperation() edge2 = IdentityOperation() edge3 = IdentityOperation() self.input_vertex.out_bound_edges.extend([edge1, edge2]) edge1.end_vertex = vertex1 edge2.end_vertex = vertex2 vertex2.out_bound_edges.append(edge3) edge3.end_vertex = vertex1
def mutation_add_vertex(self) -> bool: if 2 <= self.max_vertices <= len(self.vertices_topo_order): return False vertex1, vertex2 = np.random.choice(self.vertices_topo_order, size=2, replace=False) # Never have backward edge, to prevent cycle from_vertex: Vertex = max(vertex1, vertex2, key=lambda v: v.order) to_vertex: Vertex = min(vertex1, vertex2, key=lambda v: v.order) edges: Tuple[Edge, Edge] = np.random.choice(self.available_operations, size=2, replace=True) first, second = edges vertex = Vertex() first = first.deep_copy() second = second.deep_copy() first.end_vertex = vertex from_vertex.out_bound_edges.append(first) second.end_vertex = to_vertex vertex.out_bound_edges.append(second) # We changed graph structure self.sort_vertices() return True
def deep_copy_graph(self, copy: 'ComplexEdge') -> None: """ Deep copy the graph to another complex edge Assuming the invariants holds Args: copy: To which the graph should be copied to Returns: None """ for _ in range(len(self.vertices_topo_order) - 2): copy.vertices_topo_order.append(Vertex()) # Clear existing edges copy.input_vertex.out_bound_edges.clear() # Copy edges for i, vertex in enumerate(self.vertices_topo_order): copy_vertex = copy.vertices_topo_order[i] for edge in vertex.out_bound_edges: copy_edge = edge.deep_copy() copy_vertex.out_bound_edges.append(copy_edge) # Check here to make mypy happy if edge.end_vertex: copy_edge.end_vertex = copy.vertices_topo_order[ edge.end_vertex.order] copy.sort_vertices()
def build_graph(self) -> None: edge1 = TestEdge1() edge2 = edge1.deep_copy() edge3 = Flatten() edge4 = Dense(5) vertex1 = Vertex(name='V1') vertex2 = Vertex(name='V2') vertex3 = Vertex(name='V3') self.input_vertex.out_bound_edges.append(edge1) vertex1.out_bound_edges.append(edge2) edge1.end_vertex = vertex1 edge2.end_vertex = vertex2 vertex2.out_bound_edges.append(edge3) edge3.end_vertex = vertex3 vertex3.out_bound_edges.append(edge4) edge4.end_vertex = self.output_vertex
def build_graph(self) -> None: edge1 = PointConv2D((0, 3)) edge2 = ReLU() edge3 = IdentityOperation() vertex1 = Vertex(name='V1') self.input_vertex.out_bound_edges.append(edge1) self.input_vertex.out_bound_edges.append(edge3) vertex1.out_bound_edges.append(edge2) edge1.end_vertex = vertex1 edge2.end_vertex = self.output_vertex edge3.end_vertex = self.output_vertex
def test_remove_node_fail(): complex_operation = MutableEdge((PointConv2D((1, 4)), )) assert not complex_operation.mutation_remove_vertex() complex_operation.input_vertex.out_bound_edges.clear() vertex1 = Vertex() vertex2 = Vertex() edge1 = IdentityOperation() edge2 = IdentityOperation() edge3 = IdentityOperation() complex_operation.input_vertex.out_bound_edges.append(edge1) edge1.end_vertex = vertex1 vertex1.out_bound_edges.append(edge2) edge2.end_vertex = vertex2 vertex2.out_bound_edges.append(edge3) edge3.end_vertex = complex_operation.output_vertex complex_operation.sort_vertices() assert len(complex_operation.vertices_topo_order) == 4 assert not complex_operation.mutation_remove_vertex()
def test_remove_edge_fail2(): complex_operation = MutableEdge((PointConv2D((1, 4)), MaxPool2D())) edge1 = IdentityOperation() edge2 = IdentityOperation() complex_operation.input_vertex.out_bound_edges.clear() complex_operation.input_vertex.out_bound_edges.append(edge1) middle_vertex = Vertex() complex_operation.vertices_topo_order.append(middle_vertex) edge1.end_vertex = middle_vertex middle_vertex.out_bound_edges.append(edge2) edge2.end_vertex = complex_operation.output_vertex assert not complex_operation.mutation_remove_edge()
def test_remove_edge_success(): complex_operation = MutableEdge((PointConv2D((1, 4)), MaxPool2D())) edge1 = IdentityOperation() edge2 = IdentityOperation() complex_operation.input_vertex.out_bound_edges.clear() complex_operation.input_vertex.out_bound_edges.append(edge1) middle_vertex = Vertex() complex_operation.vertices_topo_order.append(middle_vertex) edge1.end_vertex = middle_vertex middle_vertex.out_bound_edges.append(edge2) edge2.end_vertex = complex_operation.output_vertex # Edge from input to output. So now we can remove one edge edge3 = IdentityOperation() complex_operation.input_vertex.out_bound_edges.append(edge3) edge3.end_vertex = complex_operation.output_vertex assert complex_operation.mutation_remove_edge() assert len(complex_operation.input_vertex.out_bound_edges) == 1
def test_remove_node_success(basic_graph_no_v12, mocker): complex_operation, vertex1, vertex2, vertex3, vertex4 = basic_graph_no_v12 vertex = Vertex() edge1 = IdentityOperation() edge2 = IdentityOperation() vertex1.out_bound_edges.append(edge1) edge1.end_vertex = vertex vertex.out_bound_edges.append(edge2) edge2.end_vertex = vertex2 complex_operation.sort_vertices() mocker.patch('numpy.random.permutation', return_value=[vertex, vertex2]) assert vertex in complex_operation.vertices_topo_order assert complex_operation.mutation_remove_vertex() assert vertex2 in complex_operation.vertices_topo_order assert vertex not in complex_operation.vertices_topo_order assert len(vertex1.out_bound_edges) == 1
def build_graph(self) -> None: conv_edge1 = MutableEdge((BatchNorm(), PointConv2D( (20, 40)), DepthwiseConv2D(), IdentityOperation(), SeparableConv2D( (20, 40)), Dropout(0.25), ReLU()), max_vertices=10, initialize_with_identity=False) conv_edge2 = conv_edge1.deep_copy() vertex1 = Vertex(name='V1') vertex2 = Vertex(name='V2') self.input_vertex.add_edge(conv_edge1, vertex1) vertex7 = Vertex(name='V7') vertex1.add_edge(MaxPool2D(), vertex7) vertex7.add_edge(conv_edge2, vertex2) vertex3 = Vertex(name='V3') vertex2.add_edge(Flatten(), vertex3) vertex4 = Vertex(name='V4') vertex3.add_edge(Dense(512), vertex4) vertex5 = Vertex(name='V5') vertex4.add_edge(ReLU(), vertex5) vertex6 = Vertex(name='V6') vertex5.add_edge(Dropout(0.5), vertex6) vertex6.add_edge(Dense(num_classes), self.output_vertex)
class ComplexEdge(Edge): """ Complex operation class. This operation encapsulates a small graph of nodes and operations. The graph follows such invariants: 1. The graph has no circle 2. Output is always reachable from input (implied from 3) 3. All the vertices should be reachable from input 4. All the vertices could reach output Class level invariants: 1. input_vertex is not None 2. output_vertex is not None 3. vertices_topo_order always contains vertices sorted in topological order 4. Each edge's end_vertex should point to the the end vertex of this edge, when the edge is in the graph """ def __init__(self, name: str) -> None: super().__init__() self.input_vertex = Vertex(name='input') self.output_vertex = Vertex(name='output') self.vertices_topo_order: List[Vertex] = [ self.output_vertex, self.input_vertex ] self.name = name self._layers_below = -1 def deep_copy_graph(self, copy: 'ComplexEdge') -> None: """ Deep copy the graph to another complex edge Assuming the invariants holds Args: copy: To which the graph should be copied to Returns: None """ for _ in range(len(self.vertices_topo_order) - 2): copy.vertices_topo_order.append(Vertex()) # Clear existing edges copy.input_vertex.out_bound_edges.clear() # Copy edges for i, vertex in enumerate(self.vertices_topo_order): copy_vertex = copy.vertices_topo_order[i] for edge in vertex.out_bound_edges: copy_edge = edge.deep_copy() copy_vertex.out_bound_edges.append(copy_edge) # Check here to make mypy happy if edge.end_vertex: copy_edge.end_vertex = copy.vertices_topo_order[ edge.end_vertex.order] copy.sort_vertices() def deep_copy_info(self, copy: 'ComplexEdge') -> None: copy.name = self.name copy._layers_below = self._layers_below @abstractmethod def deep_copy(self) -> Edge: pass def _topo_sort_recursion(self, current: Vertex, vertex_list: List[Vertex], accessing_set: Set[int], finished_status: Dict[int, bool]) -> bool: """ Args: current: vertex_list: accessing_set: finished_status: Returns: """ current_ref = id(current) if current_ref in accessing_set: raise RuntimeError('Found cycle in graph') if current_ref in finished_status: return finished_status[current_ref] accessing_set.add(current_ref) to_remove: List[Edge] = [] for out_edge in current.out_bound_edges: if out_edge.end_vertex: # If can't reach output, the vertex will be removed, as well # as the edge to it. if not self._topo_sort_recursion(out_edge.end_vertex, vertex_list, accessing_set, finished_status): to_remove.append(out_edge) can_reach_output = (current is self.output_vertex or len(to_remove) != len(current.out_bound_edges)) finished_status[current_ref] = can_reach_output accessing_set.remove(current_ref) for edge in to_remove: current.out_bound_edges.remove(edge) edge.end_vertex = None if can_reach_output: vertex_list.append(current) return can_reach_output def sort_vertices(self) -> None: """ Sort the vertices in topological order. Maintains the invariant that vertices_topo_order contains vertices sorted in topological order. Returns: None """ vertex_list: List[Vertex] = [] accessing_set: Set[int] = set() finished_status: Dict[int, bool] = dict() self._topo_sort_recursion(self.input_vertex, vertex_list, accessing_set, finished_status) self.vertices_topo_order = vertex_list for order, vertex in enumerate(vertex_list): vertex.order = order def check_output_reachable(self) -> bool: """ Checks for the invariant "All the vertices should be reachable from input". Assumes there's no circle in the graph. Returns: True if output is reachable, False otherwise. """ # Standard BFS visited_set: Set[Vertex] = set() queue: Queue = Queue() visited_set.add(self.input_vertex) queue.put(self.input_vertex) while not queue.empty(): current = queue.get() for out_edge in current.out_bound_edges: if out_edge.end_vertex in visited_set: continue new_vertex = out_edge.end_vertex queue.put(new_vertex) visited_set.add(new_vertex) if new_vertex == self.output_vertex: return True return False @abstractmethod def mutate(self) -> bool: pass def invalidate_layer_count(self) -> None: self._layers_below = -1 # Invalidate everything below for vertex in self.vertices_topo_order: for edge in vertex.out_bound_edges: edge.invalidate_layer_count() def build(self, x: tf.Tensor) -> tf.Tensor: for vertex in self.vertices_topo_order: vertex.reset() with tf.name_scope('%s.layer_%d' % (self.name, self.level)): self.input_vertex.collect(x) for vertex in reversed(self.vertices_topo_order[1:]): vertex.submit() return self.output_vertex.aggregate() @property def level(self) -> int: if self._layers_below < 1: max_layers = 1 for vertex in self.vertices_topo_order: for operation in vertex.out_bound_edges: max_layers = max(max_layers, operation.level) self._layers_below = max_layers + 1 return self._layers_below
def build_graph(self) -> None: vertex = Vertex() edge1 = MaxPool2D() edge2 = IdentityOperation() self.input_vertex.add_edge(edge1, vertex) vertex.add_edge(edge2, self.output_vertex)