Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/1089 build method clarity #1140

Merged
merged 19 commits into from Mar 25, 2020

Conversation

kieranricardo
Copy link
Contributor

@kieranricardo kieranricardo commented Mar 24, 2020

This PR cleans up the API for creating models. It makes several core changes:

  • On APPNP, Attri2Vec, GCN, GAT, GraphSAGE, HinSAGE, PPNP, the node_model and link_model methods are now private. Migration:
    • model.node_model(): use model.in_out_tensors() (if the model was created with a node generator) or model.in_out_tensors(multiplicity=1) (otherwise)
    • model.link_model(): use model.in_out_tensors() (if the model was created with a link generator) or model.in_out_tensors(multiplicity=1) (otherwise)
  • build is renamed to in_out_tensors for all model classes. Migration: replace model.build() with model.in_out_tensors()

In support of this, it makes some other changes:

  • tests and demos now call in_out_tensors instead of node_model/link_model/build/default_model
  • adds a multiplicity keyword arg into in_out_tensors for models with a multiplicity attribute

See #1089 and #1116

@codeclimate
Copy link

codeclimate bot commented Mar 24, 2020

Code Climate has analyzed commit 0e8865d and detected 5 issues on this pull request.

Here's the issue category breakdown:

Category Count
Security 5

View more on Code Climate.

@kieranricardo kieranricardo marked this pull request as ready for review March 24, 2020 04:24
Copy link
Contributor

@kjun9 kjun9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm when this was refactoring was proposed, I was under the impression that build (now in_out_tensors) would be calling node_model and link_model internally (i.e. the two methods are the helper methods used by in_out_tensors after checking multiplicity). But since this doesn't seem to be the case, is there any point in having those two private methods at all? Who are they intended to be used by?

@huonw
Copy link
Member

huonw commented Mar 24, 2020

This is going to be a bit annoying, but I feel like we really should have deprecations here. This is such a massive change that I imagine almost all users of StellarGraph will hit it (only those doing pure Node2vec or similar won't, I think), and as it stands, I think they'll hit an error like AttributeError: 'GCN' object has no attribute 'build' which is... rather unhelpful. It's such a fundamental change that we should potentially retain these deprecations for more than one release cycle. I can see a few approaches to implementing this (whichever you choose, you'll need to verify that it works as expected, though):

manual build methods

def build(self, *args, **kwargs):
    """Deprecated: use :meth:`in_out_tensors`."""
    warnings.warn("The 'build' method is deprecated, use 'in_out_tensors' instead.", DeprecationWarning, stacklevel=2)
    return self.in_out_tensors(*args, **kwargs)

defining a single function and setting it in each class

We could do the previous one somewhat magically, by defining a single instance of it (e.g. in stellargraph/layer/misc.py):

def deprecated_model_build(object, *args, **kwargs):
    """Deprecated: use :meth:`in_out_tensors`."""
    warnings.warn("The 'build' method is deprecated, use 'in_out_tensors' instead.", DeprecationWarning, stacklevel=2)
    return object.in_out_tensors(*args, **kwargs)

and then each model includes something like:

from .misc import deprecated_model_build
class GCN:
    ...
    build = deprecated_model_build

defining a base class for models with a build method

class Model:
    def build(self, *args, **kwargs):
        """Deprecated: use :meth:`in_out_tensors`."""
        warnings.warn("The 'build' method is deprecated, use 'in_out_tensors' instead.", DeprecationWarning, stacklevel=2)
        return self.in_out_tensors(*args, **kwargs)

...

class GCN(Model):
    ...

class GraphSAGE(Model):
    ...

We should consider doing the same for the node_model and link_model methods, although that's less major of a change. That is, retaining the public node_model and link_model methods with their current behaviour, but have them emit a DeprecationWarning.

Also, I feel like this really didn't need to have quite this many changes in it? The overall change isn't that large, but conceptually it's quite a lot, e.g. the last two changes seem pretty orthogonal to this change? (As in, things that don't depend on the name of the build method)

  • deprecates default_model
  • adds a multiplicity keyword arg into in_out_tensors for models with a multiplicity attribute

@kieranricardo
Copy link
Contributor Author

Who are they intended to be used by?

I think having a seperate _node_model and _link_model now mostly just reduces code duplication because most _link_model functions end up calling _node_model twice

@kieranricardo
Copy link
Contributor Author

@huonw

adds a multiplicity keyword arg into in_out_tensors for models with a multiplicity attribute

Some models like attri2vec could be created with a generator of any multiplicity, and then used to create either a link or a node model with link_model and node_model regardless of the generator.multiplicity. When I made node_model and link_model private, the attri2vec build function had no multiplicity kwarg so this got rid of the functionality of using any attri2vec instance to create a link and node model. I added the multiplicity kwarg for attri2vec and a couple of other models to keep this functionality.

deprecates default_model

I felt this fit into unifying the API to only use in_out_tensors, but I think you have a good point that this may be better suited to another PR.

@kieranricardo
Copy link
Contributor Author

@huonw good point about the deprecations, I think I'll go for the deprecated_model_build approach because it seems easier to extend to node_model and link_model than your 3rd suggestion and less effort than your first suggestion 👍

@huonw
Copy link
Member

huonw commented Mar 24, 2020

I felt this fit into unifying the API to only use in_out_tensors, but I think you have a good point that this may be better suited to another PR.

Yeah, it's easy to do as a separate PR; in fact, I'd already opened #1145 doing it before I'd seen this PR.

@@ -843,6 +843,10 @@ def __init__(
)
)

self.node_model = deprecated_model_function(self._node_model, "node_model")
Copy link
Member

@huonw huonw Mar 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to re-review before being suggested, but my original suggestion was careful to create these statically in each class. To be more specific, this would be like

class GAT:
    ...
    def _node_model(self):
        ...

    node_model = deprecated_model_function(_node_model, "node_model")
    link_model = deprecated_model_function(_link_model, "link_model")
    build = deprecated_model_function(in_out_tensors, "build")

This approach should mean they're still shown in the documentation, unlike dynamically assigning them when building each instance. After doing this, and moving the doc string from deprecated_model_function to _function_wrapper, the docs look like:

image

Compared to the current approach of this PR of https://stellargraph--1140.org.readthedocs.build/en/1140/api.html#stellargraph.layer.graph_attention.GAT.in_out_tensors which just shows in_out_tensors.

Exact diff of changes
diff --git a/stellargraph/layer/graph_attention.py b/stellargraph/layer/graph_attention.py
index 65f5b8e0..a0a8df35 100644
--- a/stellargraph/layer/graph_attention.py
+++ b/stellargraph/layer/graph_attention.py
@@ -843,10 +843,6 @@ class GAT:
                 )
             )
 
-        self.node_model = deprecated_model_function(self._node_model, "node_model")
-        self.link_model = deprecated_model_function(self._link_model, "link_model")
-        self.build = deprecated_model_function(self.in_out_tensors, "build")
-
     def __call__(self, inputs):
         """
         Apply a stack of GAT layers to the input x_inp
@@ -964,3 +960,7 @@ class GAT:
                 "Node model requested but a generator not supporting nodes was supplied."
             )
         return self.in_out_tensors(multiplicity=1)
+
+    node_model = deprecated_model_function(_node_model, "node_model")
+    link_model = deprecated_model_function(_link_model, "link_model")
+    build = deprecated_model_function(in_out_tensors, "build")
diff --git a/stellargraph/layer/misc.py b/stellargraph/layer/misc.py
index f97de342..6512632f 100644
--- a/stellargraph/layer/misc.py
+++ b/stellargraph/layer/misc.py
@@ -97,9 +97,9 @@ class SqueezedSparseConversion(Layer):
 
 
 def deprecated_model_function(function, old_name):
-    """Deprecated: use :meth:`in_out_tensors`."""
 
     def _function_wrapper(*args, **kwargs):
+        """Deprecated: use :meth:`in_out_tensors`."""
 
         warnings.warn(
             f"The '{old_name}' method is deprecated, use 'in_out_tensors' instead.",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice good point!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huonw my current approach isn't triggering deprecation warnings, can you see anything wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my current approach isn't triggering deprecation warnings, can you see anything wrong?

What do you mean by this specifically? How are you testing?

Per #1117, the stacklevel needs to be set correctly (although it looks like it is), and, even with this, they'll only show up in Jupyter, or in script run with Python 3.7 not Python 3.6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm testing some models in test_deprecated_model_function in test_misc.py, and I've also tried using the depracted functions in jupyter notebooks. The functions work but I'm not getting a warning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm getting jupyter warnings now nevermind, must just be my test

huonw added a commit that referenced this pull request Mar 24, 2020
The `default_model` methods are really truly deprecated in favour of `build` (at
the moment: #1140, #1089).
# Conflicts:
#	demos/node-classification/gcn/gcn-cora-node-classification-example.ipynb
#	stellargraph/layer/deep_graph_infomax.py
#	tests/layer/test_deep_graph_infomax.py
#	tests/layer/test_knowledge_graph.py
Copy link
Member

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this. There's a few little things, but otherwise I think it's good.

I checked the docs, and they seem to be as we expect, e.g.

image

pass


def test_deprecated_model_functions():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Epic test!

return self._node_model()

else:
raise NotImplementedError(
"Currently only node prediction if supported for RGCN."
)

node_model = deprecated_model_function(_node_model, "node_model")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _node_model method already wasn't public, so I'm not sure this is necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch!

@@ -509,7 +509,7 @@
}
],
"source": [
"x_inp, x_out = gcn.build()\n",
"x_inp, x_out = gcn.in_out_tensors()\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's text above that should be updated via the `GCN.build` method: -> via the `GCN.in_out_tensors` method:.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch! I found another one in the cluster gcn demo

@@ -270,41 +270,6 @@ def test_hinsage_apply():
assert actual == pytest.approx(expected)


def test_hinsage_default_model():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why delete this test? Despite the name, it was using build?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I was too hasty to delete this one, I'll ad it back and rename it

@@ -270,41 +270,6 @@ def test_hinsage_apply():
assert actual == pytest.approx(expected)


def test_hinsage_default_model():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be trying to keep/update this test?

def test_hinsage_in_out_tensors():
    ...
    xin, xout = hs.in_out_tensors()
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was too hasty to delete this one, I'll ad it back and rename it

Copy link
Contributor

@kjun9 kjun9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Copy link
Member

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this so efficiently! Hopefully the in_out_tensors name is clearer 🤞

You'll need to merge with develop to resolve the conflicts caused by the rename in #1153.

@kieranricardo
Copy link
Contributor Author

thanks for your speedy reviews @kjun9 and @huonw!

huonw added a commit that referenced this pull request Mar 25, 2020
@kieranricardo kieranricardo merged commit 8971299 into develop Mar 25, 2020
@kieranricardo kieranricardo deleted the enhancement/1089-build-method-clarity branch March 25, 2020 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants