Esempio n. 1
0
  def test_edge_convolution_template_exception_raised_shapes(self):
    """Check that invalid input shapes trigger the right exceptions."""
    with self.assertRaisesRegexp(ValueError, "must have a rank of 2"):
      data, neighbors = _dummy_data(1, 5, 2)
      data = data[0, :]
      _ = gc.edge_convolution_template(
          data=data,
          neighbors=neighbors,
          sizes=None,
          edge_function=self._zeros,
          reduction="weighted",
          edge_function_kwargs=dict())

    with self.assertRaisesRegexp(ValueError, "must have a rank greater than 1"):
      data = np.ones(shape=(5), dtype=np.float32)
      neighbors = _dense_to_sparse(np.ones(shape=(5), dtype=np.float32))
      _ = gc.edge_convolution_template(
          data=data,
          neighbors=neighbors,
          sizes=None,
          edge_function=self._zeros,
          reduction="weighted",
          edge_function_kwargs=dict())

    with self.assertRaisesRegexp(ValueError, "must have a rank of 1"):
      data, neighbors = _dummy_data(1, 5, 2)
      _ = gc.edge_convolution_template(
          data=data,
          neighbors=neighbors,
          sizes=((1, 1), (1, 1)),
          edge_function=self._zeros,
          reduction="weighted",
          edge_function_kwargs=dict())
Esempio n. 2
0
  def test_edge_convolution_template_preset_max(self):
    data = np.array(((1, 2), (3, 4), (5, 6), (7, 8)), np.float32)
    neighbors = np.array(
        ((0, 1, 0, 1), (0, 0, 1, 0), (1, 1, 1, 0), (0, 0, 1, 1)), np.float32)
    neighbors = _dense_to_sparse(neighbors)
    true = np.array(((8, 10), (8, 10), (10, 12), (14, 16)), np.float32)

    with self.subTest("max_sum"):
      max_sum = gc.edge_convolution_template(
          data=data,
          neighbors=neighbors,
          sizes=None,
          edge_function=lambda x, y: x + y,
          reduction="max",
          edge_function_kwargs=dict())

      self.assertAllEqual(max_sum, true)

    with self.subTest("max_sum_scaled"):
      # Max reduction ignores the weights, so scaling the neighbors weights
      # should not change the result.
      max_sum_scaled = gc.edge_convolution_template(
          data=data,
          neighbors=neighbors * 10.0,
          sizes=None,
          edge_function=lambda x, y: x + y,
          reduction="max",
          edge_function_kwargs=dict())

      self.assertAllEqual(max_sum_scaled, true)
 def test_edge_convolution_template_exception_raised_reduction(
         self, reduction):
     """Check that an invalid reduction method triggers the exception."""
     with self.assertRaisesRegexp(ValueError, "reduction method"):
         data, neighbors = _dummy_data(1, 5, 2)
         gc.edge_convolution_template(data=data,
                                      neighbors=neighbors,
                                      sizes=None,
                                      edge_function=self._zeros,
                                      reduction=reduction,
                                      edge_function_kwargs=dict())
Esempio n. 4
0
 def test_edge_convolution_template_exception_raised_types(
         self, err_msg, data_type, neighbors_type, sizes_type):
     """Check the type errors for invalid input types."""
     data, neighbors, sizes = _random_data(1, 5, 3, True, False, data_type,
                                           neighbors_type, sizes_type)
     with self.assertRaisesRegexp(TypeError, err_msg):
         gc.edge_convolution_template(data=data,
                                      neighbors=neighbors,
                                      sizes=sizes,
                                      edge_function=self._zeros,
                                      edge_function_kwargs=dict())
Esempio n. 5
0
 def test_edge_convolution_template_exception_not_raised_types(
         self, data_type, neighbors_type, sizes_type):
     """Check there are no exceptions for valid input types."""
     data, neighbors, sizes = _random_data(1, 5, 3, True, False, data_type,
                                           neighbors_type, sizes_type)
     try:
         gc.edge_convolution_template(data=data,
                                      neighbors=neighbors,
                                      sizes=sizes,
                                      edge_function=self._zeros,
                                      edge_function_kwargs=dict())
     except Exception as e:  # pylint: disable=broad-except
         self.fail("Exception raised: %s" % str(e))
Esempio n. 6
0
  def test_edge_convolution_template_curvature(self):
    r"""Test the expected result with curvature.

      (Approximate) curvature for meshes is defined as
      $$\kappa_{v_i} = \frac{1}{|\mathcal{N}(v_i)|}
        \sum_{v_j \in \mathcal{N}(v_i)}
        \frac{(\vec{v_i} - \vec{v_j})^T (\vec{n_{v_i}} -
        \vec{n_{v_j}})} {\left|\vec{v_i}-\vec{v_j}\right|^2}
      $$

      This can be computed using `edge_convolution_template` with
        $$f(x, y) = (n_x - n_y)^T (x - y) / ||x - y||^2.$$
      where $$n_x$$ and $$n_y$$ are the normals at points $$x$$ and $$y$$
      respectively.
    """
    # We can reuse `self._edge_curvature_2d` as the curvature functional.
    num_vertices = 500
    data, neighbors = self._circular_2d_data(num_vertices, include_normals=True)

    data_curvature = gc.edge_convolution_template(
        data=data,
        neighbors=neighbors,
        sizes=None,
        edge_function=self._edge_curvature_2d,
        reduction="weighted",
        edge_function_kwargs=dict())

    # The curvature at each point on a circle of radius 1 should be 1.
    self.assertAllClose(data_curvature, np.ones(shape=(num_vertices, 1)))
Esempio n. 7
0
  def test_edge_convolution_template_jacobian_random(self, batch_size,
                                                     num_vertices, in_channels,
                                                     padding, reduction):
    """Test the jacobian for random input data."""
    random_data = _random_data(
        batch_size,
        num_vertices,
        in_channels,
        padding,
        only_self_edges=False,
        data_type=np.float64,
        neighbors_type=np.float64)
    data_init = random_data[0]
    neighbors = random_data[1]
    sizes = None if not padding else random_data[2]
    data = tf.convert_to_tensor(value=data_init)

    y = gc.edge_convolution_template(
        data=data,
        neighbors=neighbors,
        sizes=sizes,
        edge_function=self._pass_through,
        reduction=reduction,
        edge_function_kwargs=dict())

    self.assert_jacobian_is_correct(data, data_init, y)
 def edge_convolution_template(data):
     return gc.edge_convolution_template(
         data=data,
         neighbors=neighbors,
         sizes=None,
         edge_function=self._pass_through,
         reduction=reduction,
         edge_function_kwargs=dict())
    def test_edge_convolution_template_laplacian_smoothing(self):
        r"""Test the expected result with laplacian smoothing.

      Laplacian smoothing for meshes is defined as
      $$y_i = \frac{1}{|\mathcal{N(i)}|} \sum_{j \in \mathcal{N(i)}} x_j$$

      This can be computed using `edge_convolution_template` with `f(x, y)->y`.
    """

        # We can reuse `self._pass_through(x, y)->y` as the smoothing functional.
        with self.subTest(name="only_self_edges_random"):
            num_vertices = 500
            data = np.random.uniform(size=(num_vertices, 5))
            neighbors = tf.sparse.eye(num_vertices,
                                      dtype=tf.as_dtype(data.dtype))

            data_smoothed = gc.edge_convolution_template(
                data=data,
                neighbors=neighbors,
                sizes=None,
                edge_function=self._pass_through,
                reduction="weighted",
                edge_function_kwargs=dict())

            self.assertAllEqual(data, data_smoothed)

        with self.subTest(name="circular_2d"):
            num_vertices = 500
            data, neighbors = self._circular_2d_data(num_vertices)

            data_smoothed = gc.edge_convolution_template(
                data=data,
                neighbors=neighbors,
                sizes=None,
                edge_function=self._pass_through,
                reduction="weighted",
                edge_function_kwargs=dict())
            # The smoothed points should have the same direction as the originals.
            data_smoothed_normalized = tf.nn.l2_normalize(data_smoothed,
                                                          axis=-1)

            self.assertAllClose(data, data_smoothed_normalized)
Esempio n. 10
0
    def test_edge_convolution_template_zero_neighbors(self):
        """Check that vertices with no neighbors map to zeros in the output."""
        # We can reuse `self._edge_curvature_2d` as the curvature functional.
        num_vertices = 500
        data, neighbors = self._circular_2d_data(num_vertices,
                                                 include_normals=True)

        # Interleave the data with rows filled with random data, these rows will
        # have no neighbors in the adjacency matrix so should map to all zeros in
        # the output.
        rows_odd = tf.expand_dims(
            tf.range(start=1, limit=(2 * num_vertices), delta=2), -1)
        rows_even = tf.expand_dims(
            tf.range(start=0, limit=(2 * num_vertices + 1), delta=2), -1)
        data_interleaved = tf.scatter_nd(indices=rows_odd,
                                         updates=data,
                                         shape=(2 * num_vertices + 1,
                                                tf.shape(input=data)[-1]))
        random_data = tf.random.uniform(shape=(data.shape[0] + 1,
                                               data.shape[-1]),
                                        dtype=data.dtype)
        random_interleaved = tf.scatter_nd(indices=rows_even,
                                           updates=random_data,
                                           shape=(2 * num_vertices + 1,
                                                  tf.shape(input=data)[-1]))
        data_interleaved = data_interleaved + random_interleaved
        neighbors_interleaved_indices = neighbors.indices * 2 + 1
        neighbors_interleaved = tf.SparseTensor(
            indices=neighbors_interleaved_indices,
            values=neighbors.values,
            dense_shape=(2 * num_vertices + 1, 2 * num_vertices + 1))

        # Convolve the interleaved data.
        data_curvature = gc.edge_convolution_template(
            data=data_interleaved,
            neighbors=neighbors_interleaved,
            sizes=None,
            edge_function=self._edge_curvature_2d,
            reduction="weighted",
            edge_function_kwargs=dict())

        self.assertEqual(data_curvature.shape, (2 * num_vertices + 1, 1))

        # The rows corresponding to the original input data measure the curvature.
        # The curvature at any point on a circle of radius 1 should be 1.
        # The interleaved rows of random data should map to zeros in the output.
        self.assertAllClose(data_curvature[1::2, :],
                            np.ones(shape=(num_vertices, 1)))
        self.assertAllClose(data_curvature[::2, :],
                            np.zeros(shape=(num_vertices + 1, 1)))
Esempio n. 11
0
    def test_edge_convolution_template_jacobian_preset(self, num_vertices,
                                                       num_channels,
                                                       data_multiplier):
        """Test the jacobian is correct for preset inputs."""
        # Corner cases include one vertex, one channel, and all-zero features.
        data_init = data_multiplier * np.random.uniform(
            size=(num_vertices, num_channels)).astype(np.float64)
        neighbors = tf.sparse.eye(num_vertices, dtype=tf.float64)
        data = tf.convert_to_tensor(value=data_init)

        y = gc.edge_convolution_template(data=data,
                                         neighbors=neighbors,
                                         sizes=None,
                                         edge_function=self._pass_through,
                                         edge_function_kwargs=dict())

        self.assert_jacobian_is_correct(data, data_init, y)
Esempio n. 12
0
    def test_edge_convolution_template_output_shape(self, batch_size,
                                                    num_vertices, in_channels,
                                                    out_channels):
        """Check that the output of convolution has the correct shape."""
        data, neighbors = _dummy_data(batch_size, num_vertices, in_channels)

        y = gc.edge_convolution_template(
            data,
            neighbors,
            None,
            self._zeros,
            edge_function_kwargs={"out_dimensions": out_channels})
        y_shape = y.shape.as_list()

        with self.subTest(name="out_channels"):
            self.assertEqual(y_shape[-1], out_channels)

        with self.subTest(name="shape"):
            self.assertAllEqual(y_shape[:-1], data.shape[:-1])
Esempio n. 13
0
  def call(self, inputs, sizes=None):
    # pyformat: disable
    """Executes the convolution.

    The shorthands used below are
      `V`: The number of vertices.
      `C`: The number of channels in the input data.

    Note:
      In the following, A1 to An are optional batch dimensions.

    Args:
      inputs: A list of two tensors `[data, neighbors]`. `data` is a `float`
        tensor with shape `[A1, ..., An, V, C]`. `neighbors` is a `SparseTensor`
        with the same type as `data` and with shape `[A1, ..., An, V, V]`
        representing vertex neighborhoods. The neighborhood of a vertex defines
        the support region for convolution. For a mesh, a common choice for the
        neighborhood of vertex `i` would be the vertices in the K-ring of `i`
        (including `i` itself). Each vertex must have at least one neighbor. For
        `reduction='weighted'`, `neighbors` should be a row-normalized matrix:
        `sum(neighbors, axis=-1)[A1, ..., An, i] == 1.0` for all `i`, although
        this is not enforced in the implementation in case different neighbor
        weighting schemes are desired.
      sizes: An `int` tensor of shape `[A1, ..., An]` indicating the true input
        sizes in case of padding (`sizes=None` indicates no padding).
        `sizes[A1, ..., An] <= V`. If `data` and `neighbors` are 2-D, `sizes`
        will be ignored. As an example usage of `sizes`, consider an input
        consisting of three graphs `G0`, `G1`, and `G2` with `V0`, `V1`, and
        `V2` vertices respectively. The padded input would have the shapes
        `data.shape = [3, V, C]`, and `neighbors.shape = [3, V, V]`,
        where `V = max([V0, V1, V2])`. The true sizes of each graph will be
        specified by `sizes=[V0, V1, V2]`. `data[i, :Vi, :]` and
        `neighbors[i, :Vi, :Vi]` will be the vertex and neighborhood data of
        graph `Gi`. The `SparseTensor` `neighbors` should have no nonzero
        entries in the padded regions.

    Returns:
      Tensor with shape `[A1, ..., An, V, num_output_channels]`.
    """
    # pyformat: enable
    def _edge_convolution(vertices, neighbors, conv1d_layer):
      r"""The edge filtering op passed to `edge_convolution_template`.

      This instance implements the edge function
      $$h_{\theta}(x, y) = MLP_{\theta}([x, y - x])$$

      Args:
        vertices: A 2-D Tensor with shape `[D1, D2]`.
        neighbors: A 2-D Tensor with the same shape and type as `vertices`.
        conv1d_layer: A callable 1d convolution layer.

      Returns:
        A 2-D Tensor with shape `[D1, D3]`.
      """
      concat_features = tf.concat(
          values=[vertices, neighbors - vertices], axis=-1)
      concat_features = tf.expand_dims(concat_features, 0)
      convolved_features = conv1d_layer(concat_features)
      convolved_features = tf.squeeze(input=convolved_features, axis=(0,))
      return convolved_features

    kwargs = {
        'conv1d_layer': self._conv1d_layer
    }

    return gc.edge_convolution_template(
        data=inputs[0],
        neighbors=inputs[1],
        sizes=sizes,
        edge_function=_edge_convolution,
        reduction=self._reduction,
        edge_function_kwargs=kwargs)