def test_vector_edge_loss(self):
        nodes = [Node(), Node(), Node()]
        edges = [
            Edge(nodes[0], nodes[1], Attribute(Tensor([-50., -10., -5.]))),
            Edge(nodes[1], nodes[0], Attribute(Tensor([-40., 100., 120.]))),
            Edge(nodes[0], nodes[2], Attribute(Tensor([1., 3., 4.]))),
            Edge(nodes[0], nodes[0], Attribute(Tensor([2., 2., 2.])))
        ]
        g1 = Graph(nodes, edges)

        g2 = deepcopy(g1)
        g2.ordered_edges[0].attr.val = Tensor([-45., -11., -5.])
        g2.ordered_edges[1].attr.val = Tensor([-40., 200., 121.])
        g2.ordered_edges[2].attr.val = Tensor([1.1, 3., 3.9])
        g2.ordered_edges[3].attr.val = Tensor([2., 2., 2.1])

        loss = GraphLoss(e_fn=MSELoss())
        loss_val = loss(g1, g2).detach().numpy()

        # division by 3 because there are three entries per vector
        # division by 4 because there are four edges
        target_loss_val = ((-50. + 45.)**2 + (-10. + 11.)**2 +
                           (-40. + 40.)**2 + (100. - 200.)**2 +
                           (120. - 121.)**2 + (1 - 1.1)**2 + (4. - 3.9)**2 +
                           (2 - 2.1)**2) / 3 / 4

        self.assertTrue(np.isclose(loss_val, target_loss_val))
    def _create_tuple(x_min: int, x_max: int) -> Tuple[Graph, Graph]:
        """
        Creates a sorting task training sample, i.e. a tuple consisting of (input, target).
        :param x_min: Number min value
        :param x_max: Number max value (exclusive)
        :return: Two graphs, both fully connected, one with randomly initialized attributes, the other one with targets.
        """
        assert x_min < x_max

        vec = torch.arange(x_min, x_max)
        nodes = [Node(Attribute(x)) for x in vec]

        g_in = Graph(nodes, [])
        g_out = deepcopy(g_in)

        def sorted_edge_attribute_generator(n1: Node, n2: Node) -> Attribute:
            val = [0, 1] if n1.attr.val < n2.attr.val else [1, 0]
            return Attribute(torch.Tensor(val).float())

        g_in.add_all_edges(
            reflexive=False,
            attribute_generator=lambda n1, n2: Attribute(torch.randn(2)))
        g_out.add_all_edges(
            reflexive=False,
            attribute_generator=sorted_edge_attribute_generator)

        return g_in, g_out
    def test_combined_loss(self):
        nodes = [
            Node(Attribute(Tensor([-4., -8.]))),
            Node(Attribute(Tensor([1., 5.]))),
            Node(Attribute(Tensor([4., 4.]))),
            Node(Attribute(Tensor([0., 1., 5.])))
        ]
        edges = [
            Edge(nodes[0], nodes[1], Attribute(Tensor([1., 2., 3.]))),
            Edge(nodes[1], nodes[2], Attribute(Tensor([1., 2.]))),
            Edge(nodes[2], nodes[1], Attribute(Tensor([5.]))),
            Edge(nodes[1], nodes[3], Attribute(Tensor([1., 2., 3., 4.])))
        ]
        u = Attribute(
            Tensor([[1., 2., 4., 3.], [8., 3., 0., 3.], [1., 7., 5., 3.]]))
        g1 = Graph(nodes, edges, attr=u)

        g2 = deepcopy(g1)
        g2.ordered_nodes[0].attr.val = Tensor([-4., -8.1])
        g2.ordered_nodes[1].attr.val = Tensor([2., 6.])
        g2.ordered_nodes[3].attr.val = Tensor([1., 1.5, 5.])
        g2.ordered_edges[0].attr.val = Tensor([2., 3., 4.])
        g2.ordered_edges[1].attr.val = Tensor([5., 10.])
        g2.attr.val = Tensor([[2., 2., 4., 3.], [100, 3., 1., 3.],
                              [1., 14., 5., 3.]])

        loss = GraphLoss(e_fn=MSELoss(), v_fn=L1Loss(), u_fn=MSELoss())
        loss_val = loss(g1, g2).detach().numpy()
        e_loss = (1. + (4**2 + 8**2) / 2) / 4
        v_loss = (.1 / 2 + 2. / 2 + (1 + .5) / 3) / 4
        u_loss = (1 + (8 - 100)**2 + 1 + 7**2) / 12 / 1
        target_loss_val = v_loss + e_loss + u_loss

        self.assertTrue(np.isclose(loss_val, target_loss_val))
Esempio n. 4
0
    def forward(self, aggr_e: Attribute, aggr_v: Attribute,
                u: Attribute) -> Attribute:
        x = torch.cat((aggr_e.val, aggr_v.val, u.val))
        x = F.relu(self.dense(x))
        x = F.dropout(x, p=self.drop_p, training=self.training)

        return Attribute(x)
Esempio n. 5
0
    def forward(self, e: Attribute, v_r: Attribute, v_s: Attribute,
                u: Attribute) -> Attribute:
        x = torch.cat((e.val, v_r.val, v_s.val, u.val))
        x = F.relu(self.dense(x))
        x = F.dropout(x, p=self.drop_p, training=self.training)

        return Attribute(x)
Esempio n. 6
0
    def add_reflexive_edges(self, attribute_generator: Optional[Callable[[Node], Attribute]] = None):
        if attribute_generator is None:
            attribute_generator = lambda _: Attribute()

        for n in self.nodes:
            e = Edge(n, n, attribute_generator(n))
            self.add_edge(e)
Esempio n. 7
0
def _feed(aggr: Aggregation, in_val: List[List[float]],
          out_target: List[float]) -> bool:
    in_attrs = [Attribute(torch.Tensor(np.array(x))) for x in in_val]
    out_target = np.array(out_target)

    out_tensor: torch.Tensor = aggr(in_attrs).val
    out_val = out_tensor.detach().numpy()

    return np.allclose(out_val, out_target)
    def test_scalar_edge_loss(self):
        nodes = [Node(), Node(), Node()]
        edges = [
            Edge(nodes[0], nodes[1], Attribute(Tensor([-50.]))),
            Edge(nodes[1], nodes[0], Attribute(Tensor([-40.]))),
            Edge(nodes[0], nodes[2], Attribute(Tensor([1.])))
        ]
        g1 = Graph(nodes, edges)

        g2 = deepcopy(g1)
        g2.ordered_edges[0].attr.val = Tensor([-45.])
        g2.ordered_edges[1].attr.val = Tensor([-40.])
        g2.ordered_edges[2].attr.val = Tensor([1.1])

        loss = GraphLoss(e_fn=MSELoss())
        loss_val = loss(g1, g2).detach().numpy()
        target_loss_val = ((-50. + 45.)**2 + (-40. + 40.)**2 +
                           (1 - 1.1)**2) / 3

        self.assertTrue(np.isclose(loss_val, target_loss_val))
    def test_identity_property(self):
        nodes = [
            Node(Attribute([1, 23, 4])),
            Node(Attribute("stringattr")),
            Node(Attribute([1])),
            Node(Attribute(5))
        ]
        edges = [
            Edge(nodes[0], nodes[1], Attribute({'dict': 1234})),
            Edge(nodes[0], nodes[1], Attribute([1, 2, 3])),
            Edge(nodes[0], nodes[2], Attribute(5)),
            Edge(nodes[1], nodes[2], Attribute([3, 4, 5])),
            Edge(nodes[1], nodes[1], Attribute())
        ]
        global_state = Attribute([[1, 2, 3], [5, 6, 7]])
        g1 = Graph(nodes, edges, global_state)

        g1_prime = Graph.from_dict(g1.asdict())

        self.assertTrue(g1 == g1_prime)
    def forward(self, attrs: List[Attribute]) -> Attribute:
        n = len(attrs)
        assert n > 0, "Maximum aggregation cannot be applied to empty lists."

        attr_vals = [a.val for a in attrs]
        attr_vals = torch.stack(attr_vals)

        attr_vals_max = torch.max(
            attr_vals, dim=0, keepdim=False)[0]  # returns (Tensor, LongTensor)

        return Attribute(attr_vals_max)
    def forward(self, attrs: List[Attribute]) -> Attribute:
        n = len(attrs)
        assert n > 0, "Average aggregation cannot be applied to empty lists."

        attr_vals = [a.val for a in attrs]
        attr_vals = torch.stack(attr_vals)

        attr_vals_sum = torch.sum(attr_vals, dim=0, keepdim=False)
        attr_vals_avg = attr_vals_sum / n

        return Attribute(attr_vals_avg)
Esempio n. 12
0
    def forward(self, aggr_e: Attribute, v: Attribute,
                u: Attribute) -> Attribute:
        img_tensor = v.val
        assert aggr_e.val.is_cuda, "Following reshaping does only work on GPU"
        aggr_channels = aggr_e.val.unsqueeze(-1).unsqueeze(
            -1)  # add height and width dimension for broadcasting

        img_plus_aggr = torch.add(img_tensor, aggr_channels) / 2

        conv_out = self.dense_layers(img_plus_aggr)

        return Attribute(conv_out)
    def test_forward_pass(self):
        linear_block = LinearIndependentGNBlock(e_config=(4, 8, True),
                                                v_config=(1, 1, False),
                                                u_config=(16, 16, True))

        nodes = Node.from_vals(
            [torch.randn(1), torch.randn(1),
             torch.randn(1)])
        edges = [
            Edge(nodes[0], nodes[1], Attribute(torch.randn(4))),
            Edge(nodes[1], nodes[2], Attribute(torch.randn(4))),
            Edge(nodes[2], nodes[1], Attribute(torch.randn(4)))
        ]
        g_in = Graph(nodes, edges, Attribute(torch.randn(16)))

        # noinspection PyUnusedLocal
        g_out = linear_block(g_in)

        self.assertTrue(
            True
        )  # the assertion is that the forward pass works without errors
    def forward(self, attrs: List[Attribute]) -> Attribute:
        assert len(
            attrs) > 0, "Sum aggregation cannot be applied to empty lists"

        in_shape = attrs[0].val.shape

        attr_vals = [a.val.view(-1) for a in attrs]
        attr_vals_concat = torch.stack(attr_vals)

        attr_vals_sum = torch.sum(attr_vals_concat, dim=0, keepdim=False)

        assert attr_vals_sum.shape == in_shape

        return Attribute(attr_vals_sum)
Esempio n. 15
0
    def load_graph(self, path: str) -> Graph:
        # read JSON from page description file
        json_file_path = glob(os.path.join(path, '*.json'))
        assert len(json_file_path) == 1, "Number of json files in '{}' must be exactly one.".format(path)
        json_file_path = json_file_path[0]
        with open(json_file_path, encoding='utf-8') as json_file:
            pages_json: List = json.load(json_file)

        # read screenshot paths
        imgs = self.load_images(os.path.join(path, 'image'))

        assert len(pages_json) == len(imgs), "Number of pages and number of screenshots mismatch in '{}'.".format(path)

        # extract nodes
        nodes = {}
        for page_json in pages_json:
            desktop_img, mobile_img = imgs[page_json['id']-1]

            node_attribute = PageAttribute.from_json(page_json, desktop_img, mobile_img)

            url = page_json['base_url']
            if url in nodes:
                logging.debug("Found two nodes with the same URL.")
                continue
            node = Node(node_attribute)
            nodes[url] = node

        # extract edges
        edges = set()
        for page_json in pages_json:

            url = page_json['base_url']
            source_node = nodes[url]

            for edge_json in page_json.get('urls', []):

                target_url = edge_json['url']

                if target_url not in nodes:
                    logging.debug("Invalid link target URL. Could not find a node that corresponds to it.")
                    continue

                target_node = nodes[target_url]

                edge_attribute = LinkAttribute(target_url)
                edge = Edge(sender=source_node, receiver=target_node, attr=edge_attribute)

                edges.add(edge)

        return Graph(nodes=list(nodes.values()), edges=list(edges), attr=Attribute(None))
    def test_learn_identity(self):
        # construct input graph with random values
        nodes = Node.from_vals(
            [torch.randn(1), torch.randn(1),
             torch.randn(1)])
        edges = [
            Edge(nodes[0], nodes[1], Attribute(torch.randn(4))),
            Edge(nodes[1], nodes[2], Attribute(torch.randn(4))),
            Edge(nodes[2], nodes[1], Attribute(torch.randn(4)))
        ]
        g_in = Graph(nodes, edges, Attribute(torch.randn(16)))
        g_target = deepcopy(g_in)

        block_1 = LinearIndependentGNBlock(e_config=(4, 8, True),
                                           v_config=(1, 12, True),
                                           u_config=(16, 16, True))
        block_2 = LinearIndependentGNBlock(e_config=(8, 4, False),
                                           v_config=(12, 1, False),
                                           u_config=(16, 16, False))

        model = torch.nn.Sequential(block_1, block_2)
        opt = optim.SGD(model.parameters(), lr=.1, momentum=0)
        loss_fn = GraphLoss(e_fn=MSELoss(), v_fn=MSELoss(), u_fn=MSELoss())

        loss = torch.Tensor([1.])
        for step in range(100):
            model.train()
            opt.zero_grad()
            g_out = model(g_in)
            loss = loss_fn(g_out, g_target)
            loss.backward()
            opt.step()

        final_loss = loss.detach().numpy()
        print(final_loss)
        self.assertTrue(final_loss < 1e-3)
Esempio n. 17
0
    def add_all_edges(self, reflexive: bool = True,
                      attribute_generator: Optional[Callable[[Node, Node], Attribute]] = None) -> None:
        """
        Modifies the graph in-place, such that it is fully connected, adding n^n edges, where n is the number of nodes.
        :param reflexive: Whether to connect nodes to themselves
        :param attribute_generator: New edges will be given an attribute generated by the attribute generator
        """
        if attribute_generator is None:
            attribute_generator = lambda sn, rn: Attribute()

        for n1 in self.nodes:
            for n2 in self.nodes:
                if not reflexive and n1 == n2:
                    continue
                e = Edge(n1, n2, attribute_generator(n1, n2))
                self.add_edge(e)
Esempio n. 18
0
    def __init__(self, nodes: List[Node], edges: List[Edge], attr: Optional[Attribute] = None):
        if attr is None:
            attr = Attribute(val=None)

        assert isinstance(nodes, list) and isinstance(edges, list), "Nodes and edges must be lists"

        self.nodes = set(nodes)  # V
        self.edges = set(edges)  # E
        self.attr = attr  # e

        # store ordered representations for easy alignment of two graphs with equal structure
        self.ordered_nodes = nodes
        self.ordered_edges = edges

        self.device: torch.device = None

        self._check_integrity()
    def test_graph_equality(self):
        v_0, v_1, v_2 = Node(Attribute(0.)), Node(Attribute(1.)), Node(
            Attribute(2.))
        vs = [v_0, v_1, v_2]  # nodes
        es = [Edge(v_0, v_1), Edge(v_0, v_2)]  # edges
        g_0 = Graph(nodes=vs, edges=es)

        v_0, v_1, v_2 = Node(Attribute(0.)), Node(Attribute(1.)), Node(
            Attribute(2.))
        vs = [v_0, v_1, v_2]  # nodes
        es = [Edge(v_0, v_1), Edge(v_0, v_2)]  # edges
        g_1 = Graph(nodes=vs, edges=es)

        self.assertTrue(g_0 == g_1)
Esempio n. 20
0
 def forward(self, aggr_e: Attribute, aggr_v: Attribute,
             u: Attribute) -> Attribute:
     return Attribute(self.dense(u.val))
Esempio n. 21
0
 def from_dict(d: Dict) -> 'Graph':
     nodes_dict = {k: Node.from_dict(node_dict) for k, node_dict in d['nodes'].items()}
     nodes = list(nodes_dict.values())
     edges = [Edge.from_dict(e, nodes_dict) for e in d['edges']]
     attr = Attribute.from_dict(d['attr'])
     return Graph(nodes, edges, attr)
 def test_lists_equal_objects_comparator(self):
     l1 = [Node(Attribute(1)), Node(Attribute(2)), Node(Attribute("test"))]
     l2 = [Node(Attribute(1)), Node(Attribute(2)), Node(Attribute("test"))]
     self.assertTrue(lists_equal(l1, l2, comparator=Node.eq_attr))
 def sorted_edge_attribute_generator(n1: Node, n2: Node) -> Attribute:
     val = [0, 1] if n1.attr.val < n2.attr.val else [1, 0]
     return Attribute(torch.Tensor(val).float())
 def forward(self, aggr_e: Attribute, aggr_v: Attribute,
             u: Attribute) -> Attribute:
     return Attribute(aggr_e.val + aggr_v.val - u.val)
 def test_sets_equal_objects_comparator(self):
     s1 = {Node(Attribute(1)), Node(Attribute(2)), Node(Attribute("test"))}
     s2 = {Node(Attribute(2)), Node(Attribute(1)), Node(Attribute("test"))}
     self.assertTrue(sets_equal(s1, s2, comparator=Node.eq_attr))
Esempio n. 26
0
 def forward(self, e: Attribute, v_r: Attribute, v_s: Attribute,
             u: Attribute) -> Attribute:
     return Attribute(global_avg_pool(v_s.val))
    def test_basic(self):
        """
        Basic test w/o PyTorch, all attributes are scalars, edges do not have attributes.
        Feeds a graph through a basic graph block twice and compares to the target values after both passes.
        """

        # create data structure
        v_0, v_1, v_2 = Node(Attribute(1)), Node(Attribute(10)), Node(
            Attribute(20))
        vs = [v_0, v_1, v_2]  # nodes
        es = [Edge(v_0, v_1), Edge(v_0, v_2), Edge(v_1, v_2)]
        g_0 = Graph(nodes=vs, edges=es, attr=Attribute(0))

        # create block w/ functions
        block = GNBlock(phi_e=SenderIdentityEdgeUpdate(),
                        phi_v=EdgeNodeSumNodeUpdate(),
                        phi_u=MixedGlobalStateUpdate(),
                        rho_ev=ScalarSumAggregation(),
                        rho_vu=ScalarSumAggregation(),
                        rho_eu=ScalarSumAggregation())

        g_1 = block(g_0)

        v_0, v_1, v_2 = Node(Attribute(1)), Node(Attribute(10 + 1)), Node(
            Attribute(20 + 11))
        vs = [v_0, v_1, v_2]  # nodes
        es = [
            Edge(v_0, v_1, Attribute(1)),
            Edge(v_0, v_2, Attribute(1)),
            Edge(v_1, v_2, Attribute(10))
        ]
        g_1_target = Graph(nodes=vs, edges=es, attr=Attribute(35))

        self.assertTrue(g_1 == g_1_target)

        g_2 = block(g_1)

        v_0, v_1, v_2 = Node(Attribute(1)), Node(Attribute(10 + 2)), Node(
            Attribute(20 + 11 + 12))
        vs = [v_0, v_1, v_2]  # nodes
        es = [
            Edge(v_0, v_1, Attribute(1)),
            Edge(v_0, v_2, Attribute(1)),
            Edge(v_1, v_2, Attribute(11))
        ]
        g_2_target = Graph(nodes=vs,
                           edges=es,
                           attr=Attribute(1 + 12 + 43 - 35))

        self.assertTrue(g_2 == g_2_target)
Esempio n. 28
0
        g: Graph = batch[0]['graph'].to(device)
        g_hash = int(batch[0]['rank'])
        assert g_hash not in cache

        for n in g.nodes:

            desktop_img: Tensor = n.attr.val['desktop_img']
            mobile_img: Tensor = n.attr.val['mobile_img']

            # desktop and mobile feature vector
            x1, x2 = net(desktop_img.unsqueeze(0), mobile_img.unsqueeze(0))

            x: torch.Tensor = torch.cat((x1, x2), dim=1).view(-1)
            x = x.detach().cpu()

            x = torch.Tensor(x.data)

            del n.attr
            n.attr = Attribute(x)

        g.to(torch.device('cpu'))

        cache[g_hash] = g

        if len(cache) == 4000:
            save_cached_graphs(cache, file_ctr)
            file_ctr += 1
            cache = {}

save_cached_graphs(cache, file_ctr)
 def forward(self, attrs: List[Attribute]) -> Attribute:
     return Attribute(self.constant_val)
 def forward(self, attrs: List[Attribute]) -> Attribute:
     return Attribute(sum([a.val for a in attrs]))