<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>divergences</title>
<link>https://divergences.xyz/tag/graphs.html</link>
<atom:link href="https://divergences.xyz/tag/graphs.xml" rel="self" type="application/rss+xml"/>
<description>A blog by Sigrid Keydana. Opinions are exclusively mine.</description>
<generator>quarto-1.10.19</generator>
<lastBuildDate>Tue, 22 Nov 2022 00:00:00 GMT</lastBuildDate>
<item>
  <title>Getting started with deep learning on graphs</title>
  <dc:creator>Sigrid Keydana</dc:creator>
  <link>https://divergences.xyz/posts/graphdl/</link>
  <description><![CDATA[ 






<p>If, in deep-learning world, the first half of the last decade has been the age of images, and the second, that of language, one could say that now, we’re living in the age of graphs. At least, that’s what commonly cited research metrics suggest. But as we’re all aware, deep-learning research is anything but an ivory tower. To see real-world implications, it suffices to reflect on how many things can be modeled as graphs. Some things quite naturally “are” graphs, in the sense of having nodes and edges: neurons, underground stations, social networks. Other things can fruitfully be modeled as graphs: molecules, for example; or language, concepts, three-dimensional shapes … If deep learning on graphs is desirable, what are the challenges, and what do we get for free?</p>
<section id="whats-so-special-about-deep-learning-on-graphs" class="level2">
<h2 class="anchored" data-anchor-id="whats-so-special-about-deep-learning-on-graphs">What’s so special about deep learning on graphs?</h2>
<p>Graphs are different from images, language, as well as tabular data in that node numbering does not matter. In other words, graphs are permutation-invariant. Already this means that architectures established in other domains cannot be transferred verbatim. (The <em>ideas</em> underlying them can be transferred though. Thus, in the graph neural network (henceforth: GNN) model zoo you’ll see lots of allusions to “convolutional”, “attention”, and other established terms.) Put very simply, and in concordance with common sense, whatever algorithm is used, it will fundamentally be based on how nodes are connected: the edges, that is.</p>
<p>When relationships are modeled as graphs, both nodes and edges can have features. This, too, adds complexity. But not everything is harder with graphs. Think of how cumbersome it can be to obtain labeled data for supervised learning. With graphs, often an astonishingly small amount of labeled data is needed. More surprisingly still, a graph can be constructed when not a single edge is present. Put differently, learning on <em>sets</em> can morph into learning on <em>graphs</em>.</p>
<p>At this point, let me switch gears and move on to the practical part: the raison d’être of this post.</p>
</section>
<section id="matching-concepts-and-code-pytorch-geometric" class="level2">
<h2 class="anchored" data-anchor-id="matching-concepts-and-code-pytorch-geometric">Matching concepts and code: PyTorch Geometric</h2>
<p>In this (and future) posts, we’ll make use of <a href="https://pytorch-geometric.readthedocs.io/en/latest/#">PyTorch Geometric</a> (from hereon: PyG), the most popular, at this time, and fastest-growing in terms of functionality as well as user base, library dedicated to graph DL.</p>
<p>Deep learning on graphs, in its most general form, is usually characterized by the term <em>message passing</em>. Messages are passed between nodes that are linked by an edge: If node <img src="https://latex.codecogs.com/png.latex?A"> has three neighbors, it will receive three messages. Those messages have to be summarized in some meaningful way. Finally – GNNs consisting of consecutive layers – the node will have to decide how to modify its previous-layer features (a.k.a. embeddings) based on that summary.</p>
<p>Together, these make up a three-step sequence: collect messages; aggegate; update. What about the “learning” in deep learning, though? There are two places where learning can happen: Firstly, in message collection: Incoming messages could be transformed by a MLP, for example. Secondly, as part of the update step. All in all, this yields mathematical formulae like this, given in the PyG documentation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bx%7D_i%5E%7B(k)%7D%20=%20%5Cgamma%5E%7B(k)%7D%20%5Cleft(%20%5Cmathbf%7Bx%7D_i%5E%7B(k-1)%7D,%20%5Csquare_%7Bj%20%5Cin%20%5Cmathcal%7BN%7D(i)%7D%20%5C,%20%5Cphi%5E%7B(k)%7D%5Cleft(%5Cmathbf%7Bx%7D_i%5E%7B(k-1)%7D,%20%5Cmathbf%7Bx%7D_j%5E%7B(k-1)%7D,%5Cmathbf%7Be%7D_%7Bj,i%7D%5Cright)%20%5Cright)%0A"></p>
<p>Scary though this looks, once we read it from the right, we see that it nicely fits the conceptual description. The <img src="https://latex.codecogs.com/png.latex?(%5Cmathbf%7Bx%7D_i%5E%7B(k-1)%7D,%20%5Cmathbf%7Bx%7D_j%5E%7B(k-1)%7D,%5Cmathbf%7Be%7D_%7Bj,i%7D)"> are the three types of incoming messages a node can receive: its own state at the previous layer, the states of its neighbors (the nodes <img src="https://latex.codecogs.com/png.latex?j%20%5Cin%20%5Cmathcal%7BN%7D(i)">) at the previous layer, and features/embeddings associated to the edge in question. (I’m leaving out edge features in this discussion completely, so as to not further enhance complexity.) These messages are (optionally) transformed by the neural network <img src="https://latex.codecogs.com/png.latex?%5Cphi">, and whatever comes out is summarized by the aggregator function <img src="https://latex.codecogs.com/png.latex?%5Csquare">. Finally, a node will update itself based on that summary as well as its own previous-layer state, possibly by means of applying neural network <img src="https://latex.codecogs.com/png.latex?%5Cgamma">.</p>
<p>Now that we have this conceptual/mathematical representation, how does it map to code we see, or would like to write? PyG has excellent, extensive documentation, including at the beginner level. But here, I’d like to spell things out in detail – pedantically, if you like, but in a way that tells us a lot about how GNNs work.</p>
<p>Let’s start by the information given in one of the key documentation pages, <a href="https://pytorch-geometric.readthedocs.io/en/latest/notes/create_gnn.html">Creating message passing networks</a>:</p>
<blockquote class="blockquote">
<p>PyG provides the <code>MessagePassing</code> base class, which helps in creating such kinds of message passing graph neural networks by automatically taking care of message propagation. The user only has to define the functions <img src="https://latex.codecogs.com/png.latex?%5Cphi">, i.e.&nbsp;<code>message()</code>, and <img src="https://latex.codecogs.com/png.latex?%5Cgamma">, i.e.&nbsp;<code>update()</code>, as well as the aggregation scheme to use, i.e.&nbsp;<code>aggr="add"</code>, <code>aggr="mean"</code> or <code>aggr="max"</code>.</p>
</blockquote>
<p>Scrolling down that page and looking at the two example implementations, however, we see that an implementation of <code>update()</code> does not have to be provided; and from inspecting the <a href="https://pytorch-geometric.readthedocs.io/en/latest/_modules/torch_geometric/nn/conv/message_passing.html#MessagePassing">source code</a>, it is clear that, technically, the same holds for <code>message()</code>. (And unless we want a form of aggregation different from the default <code>add</code>, we do not even need to specify that, either.)</p>
<p>Thus, the question becomes: What happens if we code the <em>minimal PyG GNN</em>? To find out, we first need to create a minimal graph, one minimal enough for us to track what is going on.</p>
<section id="a-minimal-graph" class="level3">
<h3 class="anchored" data-anchor-id="a-minimal-graph">A minimal graph</h3>
<p>Now, a basic <code>Data</code> object is created from three tensors. The first holds the node features: two features each for five nodes. (Both features are identical on purpose, for “cognitive ease” – on our, not the algorithm’s, part.)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-2"></span>
<span id="cb1-3">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>]], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span></code></pre></div></div>
<p>The second specifies existing connections. For undirected graphs (like ours), each edge appears twice. The tensor you see here is specified in one-edge-per-line form for convenience reasons; to the <code>Data()</code> constructor we’ll pass its transpose instead.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">edge_index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([</span>
<span id="cb2-2">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb2-3">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>],</span>
<span id="cb2-4">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb2-5">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>],</span>
<span id="cb2-6">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb2-7">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb2-8">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>],</span>
<span id="cb2-9">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb2-10">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>],</span>
<span id="cb2-11">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb2-12">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>],</span>
<span id="cb2-13">  [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb2-14">], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">long</span>)</span></code></pre></div></div>
<p>The third tensor holds the node labels. (The task will be one of node – not edge, not graph – classification.)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]], dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)</span></code></pre></div></div>
<p>Constructing and inspecting the resulting graph, we have:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch_geometric.data <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Data</span>
<span id="cb4-2"></span>
<span id="cb4-3">data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Data(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x, edge_index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> edge_index.t().contiguous(), y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y)</span>
<span id="cb4-4">data.x</span>
<span id="cb4-5">data.edge_index</span>
<span id="cb4-6">data.y</span></code></pre></div></div>
<pre><code>tensor([[ 1.,  1.],
        [ 2.,  2.],
        [ 3.,  3.],
        [11., 11.],
        [12., 12.]])
        
tensor([[0, 1, 0, 2, 1, 2, 2, 3, 2, 4, 3, 4],
        [1, 0, 2, 0, 2, 1, 3, 2, 4, 2, 4, 3]])
        
tensor([[0.],
        [0.],
        [0.],
        [1.],
        [1.]])</code></pre>
<p>For our upcoming experiments, it’s more helpful, though, to visualize the graph:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> networkx <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nx</span>
<span id="cb6-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb6-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch_geometric.utils <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> to_networkx</span>
<span id="cb6-4"></span>
<span id="cb6-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> visualize_graph(G, color, labels):</span>
<span id="cb6-6">    plt.figure(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>))</span>
<span id="cb6-7">    plt.axis(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'off'</span>)</span>
<span id="cb6-8">    nx.draw_networkx(</span>
<span id="cb6-9">      G,</span>
<span id="cb6-10">      pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nx.spring_layout(G, seed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">777</span>),</span>
<span id="cb6-11">      labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> labels,</span>
<span id="cb6-12">      node_color <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> color,</span>
<span id="cb6-13">      cmap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Set3"</span></span>
<span id="cb6-14">      )</span>
<span id="cb6-15">    plt.show()</span>
<span id="cb6-16"></span>
<span id="cb6-17">G <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> to_networkx(data, to_undirected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>, node_attrs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>])</span>
<span id="cb6-18">labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nx.get_node_attributes(G, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>)</span>
<span id="cb6-19">visualize_graph(G, color <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.y, labels <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> labels)</span></code></pre></div></div>
<p><a href="graph.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://divergences.xyz/posts/graphdl/graph.png" class="img-fluid"></a></p>
<p>Although our experiments won’t be about training performance (how could they be, with just five nodes), let me remark in passing that this graph is small, but not boring: The middle node is equally connected to both “sides”, yet feature-wise, it would pretty clearly appear to belong on just one of them. (Which is true, given the provided class labels). Such a constellation is interesting because, in the majority of networks, edges indicate similarity.</p>
</section>
<section id="a-minimal-gnn" class="level3">
<h3 class="anchored" data-anchor-id="a-minimal-gnn">A minimal GNN</h3>
<p>Now, we code and run the minimal GNN. We’re not interested in class labels (yet); we just want to see each node’s embeddings after a single pass.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch_geometric.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> MessagePassing</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> IAmLazy(MessagePassing):</span>
<span id="cb7-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb7-5">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb7-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb7-7">      </span>
<span id="cb7-8">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> IAmLazy()</span>
<span id="cb7-9">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb7-10">out</span></code></pre></div></div>
<pre><code>tensor([[ 5.,  5.],
        [ 4.,  4.],
        [26., 26.],
        [15., 15.],
        [14., 14.]])</code></pre>
<p>Evidently, we just had to start the process – but what process, exactly? From what we know about the three stages of message passing, an essential question is what nodes do with the information that flows over the edges. Our first experiment, then, is to inspect the incoming messages.</p>
</section>
<section id="poking-into-message" class="level3">
<h3 class="anchored" data-anchor-id="poking-into-message">Poking into <code>message()</code></h3>
<p>In <code>message()</code>, we have access to a structure named <code>x_j</code>. This tensor holds, for each node <img src="https://latex.codecogs.com/png.latex?i">, the embeddings of all nodes <img src="https://latex.codecogs.com/png.latex?j"> connected to it via incoming edges. We’ll print them, and then, just return them, unchanged.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> IAmMyOthers(MessagePassing):</span>
<span id="cb9-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb9-3">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb9-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb9-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> message(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x_j):</span>
<span id="cb9-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in message, x_j is"</span>)</span>
<span id="cb9-7">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(x_j)</span>
<span id="cb9-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x_j</span>
<span id="cb9-9">      </span>
<span id="cb9-10">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> IAmMyOthers()</span>
<span id="cb9-11">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb9-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb9-13">out</span></code></pre></div></div>
<pre><code>in message, x_j is
tensor([[ 1.,  1.],
        [ 2.,  2.],
        [ 1.,  1.],
        [ 3.,  3.],
        [ 2.,  2.],
        [ 3.,  3.],
        [ 3.,  3.],
        [11., 11.],
        [ 3.,  3.],
        [12., 12.],
        [11., 11.],
        [12., 12.]])
        
result is:
tensor([[ 5.,  5.],
        [ 4.,  4.],
        [26., 26.],
        [15., 15.],
        [14., 14.]])</code></pre>
<p>Let me spell this out. In <code>data.edge_index</code>, repeated here for convenience:</p>
<pre><code>tensor([[0, 1, 0, 2, 1, 2, 2, 3, 2, 4, 3, 4],
        [1, 0, 2, 0, 2, 1, 3, 2, 4, 2, 4, 3]])</code></pre>
<p>the first pair denotes the edge from node <code>0</code> (that had features <code>(1, 1)</code>) to node <code>1</code>. This information is found in <code>x_j</code>’s first row. Then the second row holds the information flowing in the opposite direction, namely, the features associated with node <code>1</code>. And so on.</p>
<p>Interestingly, since we’re passing through this module just once, we can see the messages that will be sent without even running it.</p>
<p>Namely, since <code>data.edge_index[0]</code> designates the <em>source</em> nodes for each edge:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">data.edge_index[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div></div>
<p>we can index <code>into data.x</code> to pick up what will be the <em>incoming</em> features for each connection.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">data.x[data.edge_index[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]]</span></code></pre></div></div>
<pre><code>tensor([[ 1.,  1.],
        [ 2.,  2.],
        [ 1.,  1.],
        [ 3.,  3.],
        [ 2.,  2.],
        [ 3.,  3.],
        [ 3.,  3.],
        [11., 11.],
        [ 3.,  3.],
        [12., 12.],
        [11., 11.],
        [12., 12.]])</code></pre>
<p>Now, what does this tell us? Node <code>0</code>, for example, received messages from nodes <code>1</code> and <code>2</code>: <code>(2, 2)</code> and <code>(3, 3)</code>, respectively. We know that the default aggregation mode is <code>add</code>; and so, would expect an outcome of <code>(5, 5)</code>. Indeed, this is the new embedding for node <code>0</code>.</p>
<p>In a nutshell, thus, the minimal GNN updates every node’s embedding so as to prototypically reflect the node’s neighborhood. Take care though: Nodes represent their neighborhoods, but themselves, they count for nothing. We will change that now.</p>
</section>
<section id="adding-self-loops" class="level3">
<h3 class="anchored" data-anchor-id="adding-self-loops">Adding self loops</h3>
<p>All we need to do is modify the adjacency matrix to include edges going from each node back to itself.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch_geometric.utils <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> add_self_loops</span>
<span id="cb15-2"></span>
<span id="cb15-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> IAmMyOthersAndMyselfAsWell(MessagePassing):</span>
<span id="cb15-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb15-5">        edge_index, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> add_self_loops(edge_index, num_nodes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb15-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in forward, augmented edge index now has shape"</span>)</span>
<span id="cb15-7">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(edge_index.shape)</span>
<span id="cb15-8">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb15-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb15-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> message(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x_j):</span>
<span id="cb15-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x_j</span>
<span id="cb15-12"></span>
<span id="cb15-13">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> IAmMyOthersAndMyselfAsWell()</span>
<span id="cb15-14">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb15-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb15-16">out</span></code></pre></div></div>
<pre><code>in forward, augmented edge index now has shape:
torch.Size([2, 17])

result is:
tensor([[ 6.,  6.],
        [ 6.,  6.],
        [29., 29.],
        [26., 26.],
        [26., 26.]])</code></pre>
<p>As expected, the neighborhood summary at each node now includes a contribution from each node itself.</p>
<p>Now we know how to access the messages, we’d like to aggregate them in a non-standard way.</p>
</section>
<section id="customizing-aggregate" class="level3">
<h3 class="anchored" data-anchor-id="customizing-aggregate">Customizing <code>aggregate()</code></h3>
<p>Instead of <code>message()</code>, we now override <code>aggregate()</code>. If we wanted to use another of the “standard” aggregation modes (<code>mean</code>, <code>mul</code>, <code>min</code>, or <code>max</code>), we could just override <code>__init__(),</code> like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb17-2">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(aggr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean"</span>)</span></code></pre></div></div>
<p>To implement custom summaries, however, we make use of <code>torch_scatter</code> (one of PyG’s installation prerequisites) for optimal performance. Let me show this by means of a simple example.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch_scatter <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> scatter</span>
<span id="cb18-2"></span>
<span id="cb18-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> IAmJustTheOppositeReally(MessagePassing):</span>
<span id="cb18-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb18-5">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb18-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb18-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> aggregate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs, index):</span>
<span id="cb18-8">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in aggregate, inputs is"</span>)</span>
<span id="cb18-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same as x_j (incoming node features)</span></span>
<span id="cb18-10">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(inputs)</span>
<span id="cb18-11">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in aggregate, index is"</span>)</span>
<span id="cb18-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this is data.edge_index[1]</span></span>
<span id="cb18-13">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(index)</span>
<span id="cb18-14">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># see https://pytorch-scatter.readthedocs.io/en/1.3.0/index.html</span></span>
<span id="cb18-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># for other aggregation modes</span></span>
<span id="cb18-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># default dim is -1</span></span>
<span id="cb18-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> scatter(inputs, index, dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reduce</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"add"</span>) </span>
<span id="cb18-18">      </span>
<span id="cb18-19">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> IAmJustTheOppositeReally()</span>
<span id="cb18-20">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb18-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb18-22">out</span></code></pre></div></div>
<pre><code>in aggregate, inputs is
tensor([[ 1.,  1.],
        [ 2.,  2.],
        [ 1.,  1.],
        [ 3.,  3.],
        [ 2.,  2.],
        [ 3.,  3.],
        [ 3.,  3.],
        [11., 11.],
        [ 3.,  3.],
        [12., 12.],
        [11., 11.],
        [12., 12.]])
        
in aggregate, index is
tensor([1, 0, 2, 0, 2, 1, 3, 2, 4, 2, 4, 3])

result is:
tensor([[ -5.,  -5.],
        [ -4.,  -4.],
        [-26., -26.],
        [-15., -15.],
        [-14., -14.]])</code></pre>
<p>In <code>aggregate()</code>, we have two types of tensors to work with. One, <code>inputs</code>, holds what was returned from <code>message()</code>. In our case, this is identical to <code>x_j</code>, since we didn’t make any modifications to the default behavior. The second, <code>index</code>, holds the recipe for where in the aggregation those features should go. Here, the very first tuple, <code>(1, 1)</code>, will contribute to the summary for node <code>1</code>; the second, <code>(2, 2)</code>, to that for node <code>0</code> – and so on. By the way, just like <code>x_j</code> (in a single-layer, single-pass setup) is “just” <code>data.x[data.edge_index[0]]</code>, that <code>index</code> is “just” <code>data.edge_index[1]</code>. Meaning, this is the list of target nodes connected to the edges in question.</p>
<p>At this point, all kinds of manipulations could be done on either <code>inputs</code> or <code>index</code>; however, we content ourselves with just passing them through to <code>torch_scatter.scatter()</code>, and returning the negated sums. We’ve successfully built a network of contrarians.</p>
<p>By now, we’ve played with <code>message()</code> as well as <code>aggregate()</code>. What about <code>update()</code>?</p>
</section>
<section id="adding-memory-to-update" class="level3">
<h3 class="anchored" data-anchor-id="adding-memory-to-update">Adding memory to <code>update()</code></h3>
<p>There’s one thing really strange in what we’re doing. It doesn’t jump to the eye, since we’re not simulating a real training phase; we’ve been calling the layer just once. If we hadn’t, we’d have noticed that at every call, the nodes happily forget who they were before, dutifully assuming the new identities assigned. In reality, we probably want them to evolve in a more consistent way.</p>
<p>For example:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> IDoEvolveOverTime(MessagePassing):</span>
<span id="cb20-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb20-3">        edge_index, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> add_self_loops(edge_index, num_nodes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb20-4">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb20-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb20-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> update(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs, x):</span>
<span id="cb20-7">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in update, inputs is"</span>)</span>
<span id="cb20-8">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(inputs)</span>
<span id="cb20-9">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"in update, x is"</span>)</span>
<span id="cb20-10">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(x)</span>
<span id="cb20-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb20-12"></span>
<span id="cb20-13">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> IDoEvolveOverTime()</span>
<span id="cb20-14">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb20-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb20-16">out</span></code></pre></div></div>
<pre><code>in update, inputs is
tensor([[ 6.,  6.],
        [ 6.,  6.],
        [29., 29.],
        [26., 26.],
        [26., 26.]])
in update, x is
tensor([[ 1.,  1.],
        [ 2.,  2.],
        [ 3.,  3.],
        [11., 11.],
        [12., 12.]])
result is:
tensor([[ 3.5000,  3.5000],
        [ 4.0000,  4.0000],
        [16.0000, 16.0000],
        [18.5000, 18.5000],
        [19.0000, 19.0000]])</code></pre>
<p>In <code>update()</code>, we have access to both the final message aggregate (<code>inputs</code>) and the nodes’ prior states (<code>x</code>). Here, I’m just averaging those two.</p>
<p>At this point, we’ve successfully acquainted ourselves with the three stages of message passing: acting on individual messages, aggregating them, and self-updating based on past state and new information. But none of our models so far could be called a neural network, since there was no learning involved.</p>
</section>
<section id="adding-parameters" class="level3">
<h3 class="anchored" data-anchor-id="adding-parameters">Adding parameters</h3>
<p>If we look back at the generic message passing formulation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bx%7D_i%5E%7B(k)%7D%20=%20%5Cgamma%5E%7B(k)%7D%20%5Cleft(%20%5Cmathbf%7Bx%7D_i%5E%7B(k-1)%7D,%20%5Csquare_%7Bj%20%5Cin%20%5Cmathcal%7BN%7D(i)%7D%20%5C,%20%5Cphi%5E%7B(k)%7D%5Cleft(%5Cmathbf%7Bx%7D_i%5E%7B(k-1)%7D,%20%5Cmathbf%7Bx%7D_j%5E%7B(k-1)%7D,%5Cmathbf%7Be%7D_%7Bj,i%7D%5Cright)%20%5Cright)%0A"> we see two places where neural network modules can act on the computation: before message aggregation, and as part of the node update process. First, we illustrate the former option. For example, we can apply a MLP in <code>forward()</code>, before the call to <code>aggregate()</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Sequential <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> Seq, Linear, ReLU</span>
<span id="cb22-2"></span>
<span id="cb22-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ILearnAndEvolve(MessagePassing):</span>
<span id="cb22-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, in_channels, out_channels):</span>
<span id="cb22-5">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(aggr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sum"</span>)</span>
<span id="cb22-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Seq(Linear(in_channels, out_channels),</span>
<span id="cb22-7">                       ReLU(),</span>
<span id="cb22-8">                       Linear(out_channels, out_channels))</span>
<span id="cb22-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb22-10">        edge_index, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> add_self_loops(edge_index, num_nodes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb22-11">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp(x)</span>
<span id="cb22-12">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb22-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb22-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> update(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs, x):</span>
<span id="cb22-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb22-16"></span>
<span id="cb22-17">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ILearnAndEvolve(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb22-18">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb22-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb22-20">out</span></code></pre></div></div>
<pre><code>result is:
tensor([[-0.8724, -0.4407],
        [-0.9056, -0.4623],
        [-2.0229, -1.1240],
        [-1.8691, -1.0867],
        [-1.9024, -1.1082]], grad_fn=&lt;DivBackward0&gt;)</code></pre>
<p>Finally, we can apply network modules in both places, as exemplified next.</p>
</section>
<section id="general-message-passing" class="level3">
<h3 class="anchored" data-anchor-id="general-message-passing">General message passing</h3>
<p>We keep the MLP from the previous class, and add a second in <code>update()</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ILearnAndEvolveDoubly(MessagePassing):</span>
<span id="cb24-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, in_channels, out_channels):</span>
<span id="cb24-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(aggr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sum"</span>)</span>
<span id="cb24-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp_msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Seq(Linear(in_channels, out_channels),</span>
<span id="cb24-5">                       ReLU(),</span>
<span id="cb24-6">                       Linear(out_channels, out_channels))</span>
<span id="cb24-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp_upd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Seq(Linear(out_channels, out_channels),</span>
<span id="cb24-8">                       ReLU(),</span>
<span id="cb24-9">                       Linear(out_channels, out_channels))</span>
<span id="cb24-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb24-11">        edge_index, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> add_self_loops(edge_index, num_nodes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb24-12">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp_msg(x)</span>
<span id="cb24-13">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.propagate(edge_index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> edge_index, x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x)</span>
<span id="cb24-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out</span>
<span id="cb24-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> update(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, inputs, x):</span>
<span id="cb24-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp_upd((inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb24-17"></span>
<span id="cb24-18">module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ILearnAndEvolveDoubly(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb24-19">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module(data.x, data.edge_index)</span>
<span id="cb24-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result is:"</span>)</span>
<span id="cb24-21">out</span></code></pre></div></div>
<pre><code>result is:
tensor([[ 0.0573, -0.6988],
        [ 0.0358, -0.6894],
        [-0.1730, -0.6450],
        [-0.5855, -0.4171],
        [-0.5890, -0.4141]], grad_fn=&lt;AddmmBackward0&gt;)</code></pre>
<p>At this point, I hope you’ll feel comfortable to play around, subclassing the <code>MessagePassing</code> base class. Also, if now you consult the above-mentioned documentation page (<a href="https://pytorch-geometric.readthedocs.io/en/latest/notes/create_gnn.html">Creating message passing networks</a>), you’ll be able to map the example implementations (dedicated to popular GNN layer types) to where they “hook into” the message passing process.</p>
<p>Experimentation with <code>MessagePassing</code> was the point of this post. However, you may be wondering: How do I actually use this for node classification? Didn’t the graph have a class defined for each node? (It did: <code>data.y</code>.)</p>
<p>So let me conclude with a (minimal) end-to-end example that uses one of the above modules.</p>
</section>
<section id="a-minimal-workflow" class="level3">
<h3 class="anchored" data-anchor-id="a-minimal-workflow">A minimal workflow</h3>
<p>To that purpose, we compose that module with a linear one that performs node classification:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Network(torch.nn.Module):</span>
<span id="cb26-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, in_channels, out_channels, num_classes):</span>
<span id="cb26-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb26-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.conv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ILearnAndEvolveDoubly(in_channels, out_channels)</span>
<span id="cb26-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.classifier <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Linear(out_channels, num_classes)</span>
<span id="cb26-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, edge_index):</span>
<span id="cb26-7">        x, edge_index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.x, data.edge_index</span>
<span id="cb26-8">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.conv(x, edge_index)</span>
<span id="cb26-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.classifier(x)</span>
<span id="cb26-10"></span>
<span id="cb26-11">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Network(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) </span></code></pre></div></div>
<p>We can then train the model like any other:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1"></span>
<span id="cb27-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn.functional <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> F</span>
<span id="cb27-3"></span>
<span id="cb27-4">optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.optim.Adam(model.parameters(), lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>)</span>
<span id="cb27-5">model.train()</span>
<span id="cb27-6"></span>
<span id="cb27-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> epoch <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>):</span>
<span id="cb27-8">    optimizer.zero_grad()</span>
<span id="cb27-9">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(data.x, data.edge_index)</span>
<span id="cb27-10">    loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.binary_cross_entropy_with_logits(out, data.y)</span>
<span id="cb27-11">    loss.backward()</span>
<span id="cb27-12">    optimizer.step()</span>
<span id="cb27-13"></span>
<span id="cb27-14">preds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sigmoid(out)</span>
<span id="cb27-15">preds</span></code></pre></div></div>
<pre><code>tensor([[0.6502],
        [0.6532],
        [0.7027],
        [0.7145],
        [0.7165]], grad_fn=&lt;SigmoidBackward0&gt;)</code></pre>
<p>And that’s it for this time. Stay tuned for examples of how graph models are applied in the sciences, as well as illustrations of bleeding-edge developments in <a href="https://blogs.rstudio.com/ai/posts/2021-08-26-geometric-deep-learning/">Geometric Deep Learning</a>, the principles-based, heuristics-transcending approach to neural networks.</p>
<p>Thanks for reading!</p>
<div class="photo-credit">
<p>Photo by <a href="https://unsplash.com/@alinnnaaaa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alina Grubnyak</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
</div>


</section>
</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Deep Learning</category>
  <category>Geometric Deep Learning</category>
  <guid>https://divergences.xyz/posts/graphdl/</guid>
  <pubDate>Tue, 22 Nov 2022 00:00:00 GMT</pubDate>
  <media:content url="https://divergences.xyz/posts/graphdl/net.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
