<?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/category/geometric-deep-learning.html</link>
<atom:link href="https://divergences.xyz/category/geometric-deep-learning.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, 09 May 2023 00:00:00 GMT</lastBuildDate>
<item>
  <title>Group-equivariant neural networks with escnn</title>
  <dc:creator>Sigrid Keydana</dc:creator>
  <link>https://divergences.xyz/posts/group-equivariant-cnn-3/</link>
  <description><![CDATA[ 






<p>Today, we resume our exploration of group equivariance. This is the third post in the series. The <a href="https://blogs.rstudio.com/ai/posts/2023-03-09-group-equivariant-cnn-1/">first</a> was a high-level introduction: what this is all about; how equivariance is operationalized; and why it is of relevance to many deep-learning applications. The <a href="https://blogs.rstudio.com/ai/posts/2023-03-27-group-equivariant-cnn-2/">second</a> sought to concretize the key ideas by developing a group-equivariant CNN from scratch. That being instructive, but too tedious for practical use, today we look at a carefully designed, highly-performant <a href="https://github.com/QUVA-Lab/escnn">library</a> that hides the technicalities and enables a convenient workflow.</p>
<p>First though, let me again set the context. In physics, an all-important concept is that of symmetry<sup>1</sup>, a symmetry being present whenever some quantity is being conserved. But we don’t even need to look to science. Examples arise in daily life, and – otherwise why write about it - in the tasks we apply deep learning to.</p>
<div class="no-row-height column-margin column-container"><div id="fn1"><p><sup>1</sup>&nbsp;This is nicely explained in, for example, Jakob Schwichtenberg’s <a href="http://physicsfromsymmetry.com/">Physics from symmetry</a>.</p></div></div><p>In daily life: Think about speech – me stating “it is cold”, for example. Formally, or denotation-wise, the sentence will have the same meaning now as in five hours. (Connotations, on the other hand, can and will probably be different!). This is a form of translation symmetry, translation in time.</p>
<p>In deep learning: Take image classification. For the usual convolutional neural network, a cat in the center of the image is just that, a cat; a cat on the bottom is, too. But one sleeping, comfortably curled like a half-moon “open to the right”, will not be “the same” as one in a mirrored position. Of course, we can train the network to treat both as equivalent by providing training images of cats in both positions, but that is not a scaleable approach. Instead, we’d like to make the network aware of these symmetries, so they are automatically preserved throughout the network architecture.</p>
<section id="purpose-and-scope-of-this-post" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="purpose-and-scope-of-this-post">Purpose and scope of this post</h2>
<p>Here, I introduce <a href="https://github.com/QUVA-Lab/escnn"><code>escnn</code></a>, a PyTorch extension that implements forms of group equivariance for CNNs operating on the plane or in (3d) space. The library is used in various, amply illustrated research papers; it is appropriately documented; and it comes with introductory notebooks both relating the math and exercising the code. Why, then, not just refer to the <a href="https://github.com/QUVA-Lab/escnn/blob/master/examples/introduction.ipynb">first notebook</a>, and immediately start using it for some experiment?</p>
<p>In fact, this post should – as quite a few texts I’ve written – be regarded as an introduction to an introduction. To me, this topic seems anything but easy, for various reasons. Of course, there’s the math. But as so often in machine learning, you don’t need to go to great depths to be able to apply an algorithm correctly. So if not the math itself, what generates the difficulty? For me, it’s two things.</p>
<p>First, to map my understanding of the mathematical concepts to the terminology used in the library, and from there, to correct use and application. Expressed schematically: We have a concept A, which figures (among other concepts) in technical term (or object class) B. What does my understanding of A tell me about how object class B is to be used correctly? More importantly: How do I use it to best attain my goal C? This first difficulty I’ll address in a very pragmatic way. I’ll neither dwell on mathematical details, nor try to establish the links between A, B, and C in detail. Instead, I’ll present the characters<sup>2</sup> in this story by asking what they’re good for.</p>
<div class="no-row-height column-margin column-container"><div id="fn2"><p><sup>2</sup>&nbsp;If you have some background on representations: No, not <em>those</em> characters …</p></div><div id="fn3"><p><sup>3</sup>&nbsp;Yes, that word <a href="https://www.merriam-webster.com/dictionary/quaternity">exists</a>, although I must admit I didn’t know before typing it into a search engine.</p></div><div id="fn4"><p><sup>4</sup>&nbsp;One paper particularly stood out to me: <a href="https://arxiv.org/abs/2106.06020">Coordinate Independent Convolutional Networks – Isometry and Gauge Equivariant Convolutions on Riemannian Manifolds</a>.</p></div><div id="fn5"><p><sup>5</sup>&nbsp;Directly pertinent to today’s topic, thinking of the materials produced for University of Amsterdam’s <a href="https://uvagedl.github.io/">course on group-equivariant deep learning</a>.</p></div></div><p>Second – and this will be of relevance to just a subset of readers – the topic of group equivariance, particularly as applied to image processing, is one where visualizations can be of tremendous help. The quaternity<sup>3</sup> of conceptual explanation, math, code, and visualization can, together, produce an understanding of emergent-seeming quality… if, and only if, all of these explanation modes “work” for you. (Or if, in an area, a mode that does not wouldn’t contribute that much anyway.) Here, it so happens that from what I saw, several papers have excellent visualizations<sup>4</sup>, and the same holds for some lecture slides and accompanying notebooks<sup>5</sup>. But for those among us with limited spatial-imagination capabilities – e.g., people with <a href="https://en.wikipedia.org/wiki/Aphantasia">Aphantasia</a> – these illustrations, intended to help, can be very hard to make sense of themselves. If you’re not one of these, I totally recommend checking out the resources linked in the above footnotes. This text, though, will try to make the best possible use of verbal explanation to introduce the concepts involved, the library, and how to use it.</p>
<p>That said, let’s start with the software.</p>
</section>
<section id="using-escnn" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="using-escnn">Using <em>escnn</em></h2>
<p><code>Escnn</code> depends on PyTorch. Yes, PyTorch, not <code>torch</code>; unfortunately, the library hasn’t been ported to R yet.<sup>6</sup> For now, thus, we’ll employ <a href="https://rstudio.github.io/reticulate/"><code>reticulate</code></a><sup>7</sup> to access the Python objects directly.</p>
<div class="no-row-height column-margin column-container"><div id="fn6"><p><sup>6</sup>&nbsp;If, after reading this post, you feel that maybe <em>you</em> would be interested in porting it – I can definitely say I think that’s a great idea!</p></div><div id="fn7"><p><sup>7</sup>&nbsp;If you’re new to <code>reticulate</code>, please consult its excellent documentation on topics like <a href="https://rstudio.github.io/reticulate/articles/calling_python.html">calling Python from R</a>, <a href="https://rstudio.github.io/reticulate/articles/versions.html">determining the Python version used</a>, <a href="https://rstudio.github.io/reticulate/articles/python_packages.html">installing Python packages</a>, as well as an outstanding <a href="https://rstudio.github.io/reticulate/articles/python_primer.html">introduction to Python for R users</a>.</p></div></div><p>The way I’m doing this is install <code>escnn</code> in a virtual environment, with PyTorch version 1.13.1. As of this writing, Python 3.11 is not yet supported by one of <code>escnn</code>’s dependencies; the virtual environment thus builds on Python 3.10. As to the library itself, I am using the development version from GitHub, running <code>pip install git+https://github.com/QUVA-Lab/escnn</code>.</p>
<p>Once you’re ready, issue</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(reticulate)</span>
<span id="cb1-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Verify correct environment is used.</span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Different ways exist to ensure this; I've found most convenient to configure this on</span></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a per-project basis in RStudio's project file (&lt;myproj&gt;.Rproj)</span></span>
<span id="cb1-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">py_config</span>()</span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># bind to required libraries and get handles to their namespaces</span></span>
<span id="cb1-8">torch <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">import</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"torch"</span>)</span>
<span id="cb1-9">escnn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">import</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"escnn"</span>)</span></code></pre></div></div>
<p><code>Escnn</code> loaded, let me introduce its main objects and their roles in the play.</p>
</section>
<section id="spaces-groups-and-representations-escnngspaces" class="level2">
<h2 class="anchored" data-anchor-id="spaces-groups-and-representations-escnngspaces">Spaces, groups, and representations: <code>escnn$gspaces</code></h2>
<p>We start by peeking into <code>gspaces</code>, one of the two sub-modules we are going to make direct use of.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">gspaces <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> escnn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>gspaces</span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">py_list_attributes</span>(gspaces) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> (\(vec) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grep</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"On"</span>, vec, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>))() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sort</span>()</span></code></pre></div></div>
<pre><code>[1] "conicalOnR3" "cylindricalOnR3" "dihedralOnR3" "flip2dOnR2" "flipRot2dOnR2" "flipRot3dOnR3"
[7] "fullCylindricalOnR3" "fullIcoOnR3" "fullOctaOnR3" "icoOnR3" "invOnR3" "mirOnR3 "octaOnR3"
[14] "rot2dOnR2" "rot2dOnR3" "rot3dOnR3" "trivialOnR2" "trivialOnR3"    </code></pre>
<p>The methods I’ve listed instantiate a <code>gspace</code>. If you look closely, you see that they’re all composed of two strings, joined by “On”. In all instances, the second part is either <code>R2</code> or <code>R3</code>. These two are the available base spaces – <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E3"> – an input signal can live in. Signals can, thus, be images, made up of pixels, or three-dimensional volumes, composed of voxels. The first part refers to the group you’d like to use. Choosing a group means choosing the symmetries to be respected. For example, <code>rot2dOnR2()</code> implies equivariance as to rotations, <code>flip2dOnR2()</code> guarantees the same for mirroring actions, and <code>flipRot2dOnR2()</code> subsumes both.</p>
<p>Let’s define such a <code>gspace</code>. Here we ask for rotation equivariance on the Euclidean plane, making use of the same cyclic group – <img src="https://latex.codecogs.com/png.latex?C_4"> – we developed in our <a href="https://blogs.rstudio.com/ai/posts/2023-03-27-group-equivariant-cnn-2/">from-scratch implementation</a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">r2_act <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> gspaces<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rot2dOnR2</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">N =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>L)</span>
<span id="cb4-2">r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>fibergroup</span></code></pre></div></div>
<p>In this post, I’ll stay with that setup, but we could as well pick another rotation angle – <code>N = 8</code>, say, resulting in eight equivariant positions separated by forty-five degrees. Alternatively, we might want <em>any</em> rotated position to be accounted for. The group to request then would be <a href="https://en.wikipedia.org/wiki/Orthogonal_group#Special_orthogonal_group">SO(2)</a>, called the <em>special orthogonal group,</em> of continuous, distance- and orientation-preserving transformations on the Euclidean plane:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">(gspaces<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rot2dOnR2</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">N =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>fibergroup</span></code></pre></div></div>
<pre><code>SO(2)</code></pre>
<p>Going back to <img src="https://latex.codecogs.com/png.latex?C_4">, let’s investigate its <a href="https://en.wikipedia.org/wiki/Group_representation"><em>representations</em></a>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>representations</span></code></pre></div></div>
<pre><code>$irrep_0
C4|[irrep_0]:1

$irrep_1
C4|[irrep_1]:2

$irrep_2
C4|[irrep_2]:1

$regular
C4|[regular]:4</code></pre>
<p>A representation, in our current context <em>and</em> very roughly speaking, is a way to encode a group action as a matrix, meeting certain conditions. In <code>escnn</code>, representations are central, and we’ll see how in the next section.</p>
<p>First, let’s inspect the above output. Four representations are available, three of which share an important property: they’re all <a href="https://en.wikipedia.org/wiki/Irreducible_representation">irreducible</a>. On <img src="https://latex.codecogs.com/png.latex?C_4">, any non-irreducible representation <a href="https://en.wikipedia.org/wiki/Maschke%27s_theorem">can be decomposed</a> into into irreducible ones. These irreducible representations are what <code>escnn</code> works with internally. Of those three, the most interesting one is the second. To see its action, we need to choose a group element. How about counterclockwise rotation by ninety degrees:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">elem_1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>fibergroup<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L)</span>
<span id="cb9-2">elem_1</span></code></pre></div></div>
<pre><code>1[2pi/4]</code></pre>
<p>Associated to this group element is the following matrix:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1">r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>representations[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]](elem_1)</span></code></pre></div></div>
<pre><code>             [,1]          [,2]
[1,] 6.123234e-17 -1.000000e+00
[2,] 1.000000e+00  6.123234e-17</code></pre>
<p>This is the so-called standard representation,</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%20%5Ccos(%5Ctheta)%20&amp;%20-%5Csin(%5Ctheta)%20%5C%5C%20%5Csin(%5Ctheta)%20&amp;%20%5Ccos(%5Ctheta)%20%5Cend%7Bbmatrix%7D%0A"></p>
<p>, evaluated at <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20=%20%5Cpi/2">. (It is called the standard representation because it directly comes from how the group is defined (namely, a rotation by <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> in the plane).</p>
<p>The other interesting representation to point out is the fourth: the only one that’s not irreducible.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>representations[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]](elem_1)</span></code></pre></div></div>
<pre><code>[1,]  5.551115e-17 -5.551115e-17 -8.326673e-17  1.000000e+00
[2,]  1.000000e+00  5.551115e-17 -5.551115e-17 -8.326673e-17
[3,]  5.551115e-17  1.000000e+00  5.551115e-17 -5.551115e-17
[4,] -5.551115e-17  5.551115e-17  1.000000e+00  5.551115e-17</code></pre>
<p>This is the so-called <em>regular</em> representation. The regular representation acts via permutation of group elements, or, to be more precise, of the basis vectors that make up the matrix. Obviously, this is only possible for finite groups like <img src="https://latex.codecogs.com/png.latex?C_n">, since otherwise there’d be an infinite amount of basis vectors to permute.</p>
<p>To better see the action encoded in the above matrix, we clean up a bit:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">round</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>representations[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]](elem_1))</span></code></pre></div></div>
<pre><code>    [,1] [,2] [,3] [,4]
[1,]    0    0    0    1
[2,]    1    0    0    0
[3,]    0    1    0    0
[4,]    0    0    1    0</code></pre>
<p>This is a step-one shift to the right of the identity matrix. The identity matrix, mapped to element 0, is the non-action; this matrix instead maps the zeroth action to the first, the first to the second, the second to the third, and the third to the first.</p>
<p>We’ll see the regular representation used in a neural network soon. Internally – but that need not concern the user – <em>escnn</em> works with its decomposition into irreducible matrices. Here, that’s just the bunch of irreducible representations we saw above, numbered from one to three.</p>
<p>Having looked at how groups and representations figure in <code>escnn</code>, it is time we approach the task of building a network.</p>
</section>
<section id="representations-for-real-escnnnnfieldtype" class="level2">
<h2 class="anchored" data-anchor-id="representations-for-real-escnnnnfieldtype">Representations, for real: <code>escnn$nn$FieldType</code></h2>
<p>So far, we’ve characterized the input space (<img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2">), and specified the group action. But once we enter the network, we’re not in the plane anymore, but in a space that has been extended by the group action. Rephrasing, the group action produces <em>feature vector fields</em> that assign a feature vector to each spatial position in the image.</p>
<p>Now we have those feature vectors, we need to specify how they transform under the group action. This is encoded in an <code>escnn$nn$FieldType</code> . Informally, we could say that a field type is the <em>data type</em> of a feature space. In defining it, we indicate two things: the base space, a <code>gspace</code>, and the representation type(s) to be used.</p>
<p>In an equivariant neural network, field types play a role similar to that of channels in a convnet. Each layer has an input and an output field type. Assuming we’re working with grey-scale images, we can specify the input type for the first layer like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb17-1">nn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> escnn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>nn</span>
<span id="cb17-2">feat_type_in <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>trivial_repr))</span></code></pre></div></div>
<p>The <em>trivial</em> representation is used to indicate that, while the image as a whole will be rotated, the pixel values themselves should be left alone. If this were an RGB image, instead of <code>r2_act$trivial_repr</code> we’d pass a list of three such objects.</p>
<p>So we’ve characterized the input. At any later stage, though, the situation will have changed. We will have performed convolution once for every group element. Moving on to the next layer, these feature fields will have to transform equivariantly, as well. This can be achieved by requesting the <em>regular</em> representation for an output field type:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb18-1">feat_type_out <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr))</span></code></pre></div></div>
<p>Then, a convolutional layer may be defined like so:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb19-1">conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_in, feat_type_out, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L)</span></code></pre></div></div>
</section>
<section id="group-equivariant-convolution" class="level2">
<h2 class="anchored" data-anchor-id="group-equivariant-convolution">Group-equivariant convolution</h2>
<p>What does such a convolution do to its input? Just like, in a usual convnet, capacity can be increased by having more channels, an equivariant convolution can pass on several feature vector fields, possibly of different type (assuming that makes sense). In the code snippet below, we request a list of three, all behaving according to the regular representation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb20-1">feat_type_in <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>trivial_repr))</span>
<span id="cb20-2">feat_type_out <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(</span>
<span id="cb20-3">  r2_act,</span>
<span id="cb20-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr, r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr, r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr)</span>
<span id="cb20-5">)</span>
<span id="cb20-6"></span>
<span id="cb20-7">conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_in, feat_type_out, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L)</span></code></pre></div></div>
<p>We then perform convolution on a batch of images, made aware of their “data type” by wrapping them in <code>feat_type_in</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb21-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rand</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>L)</span>
<span id="cb21-2">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">feat_type_in</span>(x)</span>
<span id="cb21-3">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conv</span>(x)</span>
<span id="cb21-4">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>()</span></code></pre></div></div>
<pre><code>[1]  2  12 30 30</code></pre>
<p>The output has twelve “channels”, this being the product of group cardinality – four distinguished positions – and number of feature vector fields (three).</p>
<p>If we choose the simplest possible, approximately, test case, we can verify that such a convolution is equivariant by direct inspection. Here’s my setup:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb23-1">feat_type_in <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>trivial_repr))</span>
<span id="cb23-2">feat_type_out <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr))</span>
<span id="cb23-3">conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_in, feat_type_out, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L)</span>
<span id="cb23-4"></span>
<span id="cb23-5">torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>init<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">constant_</span>(conv<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>weights, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.</span>)</span>
<span id="cb23-6">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vander</span>(torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arange</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;">4</span>))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">view</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tuple</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>L)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">feat_type_in</span>()</span>
<span id="cb23-7">x</span></code></pre></div></div>
<pre><code>g_tensor([[[[ 0.,  0.,  0.,  1.],
            [ 1.,  1.,  1.,  1.],
            [ 8.,  4.,  2.,  1.],
            [27.,  9.,  3.,  1.]]]], [C4_on_R2[(None, 4)]: {irrep_0 (x1)}(1)])</code></pre>
<p>Inspection could be performed using any group element. I’ll pick rotation by <img src="https://latex.codecogs.com/png.latex?%5Cpi/2">:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb25-1">all <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">iterate</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>testing_elements)</span>
<span id="cb25-2">g1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> all[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]]</span>
<span id="cb25-3">g1</span></code></pre></div></div>
<p>Just for fun, let’s see how we can – literally – come whole circle by letting this element act on the input tensor four times:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb26-1">all <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">iterate</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>testing_elements)</span>
<span id="cb26-2">g1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> all[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]]</span>
<span id="cb26-3"></span>
<span id="cb26-4">x1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb26-5">x1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span>
<span id="cb26-6">x2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb26-7">x2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span>
<span id="cb26-8">x3 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb26-9">x3<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span>
<span id="cb26-10">x4 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x3<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb26-11">x4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<pre><code>tensor([[[[ 1.,  1.,  1.,  1.],
          [ 0.,  1.,  2.,  3.],
          [ 0.,  1.,  4.,  9.],
          [ 0.,  1.,  8., 27.]]]])
          
tensor([[[[ 1.,  3.,  9., 27.],
          [ 1.,  2.,  4.,  8.],
          [ 1.,  1.,  1.,  1.],
          [ 1.,  0.,  0.,  0.]]]])
          
tensor([[[[27.,  8.,  1.,  0.],
          [ 9.,  4.,  1.,  0.],
          [ 3.,  2.,  1.,  0.],
          [ 1.,  1.,  1.,  1.]]]])
          
tensor([[[[ 0.,  0.,  0.,  1.],
          [ 1.,  1.,  1.,  1.],
          [ 8.,  4.,  2.,  1.],
          [27.,  9.,  3.,  1.]]]])</code></pre>
<p>You see that at the end, we are back at the original “image”.</p>
<p>Now, for equivariance. We could first apply a rotation, then convolve.</p>
<p>Rotate:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb28-1">x_rot <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb28-2">x_rot<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<p>This is the first in the above list of four tensors.</p>
<p>Convolve:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb29-1">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conv</span>(x_rot)</span>
<span id="cb29-2">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<pre><code>tensor([[[[ 1.1955,  1.7110],
          [-0.5166,  1.0665]],

         [[-0.0905,  2.6568],
          [-0.3743,  2.8144]],

         [[ 5.0640, 11.7395],
          [ 8.6488, 31.7169]],

         [[ 2.3499,  1.7937],
          [ 4.5065,  5.9689]]]], grad_fn=&lt;ConvolutionBackward0&gt;)</code></pre>
<p>Alternatively, we can do the convolution first, then rotate its output.</p>
<p>Convolve:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb31-1">y_conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conv</span>(x)</span>
<span id="cb31-2">y_conv<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<pre><code>tensor([[[[-0.3743, -0.0905],
          [ 2.8144,  2.6568]],

         [[ 8.6488,  5.0640],
          [31.7169, 11.7395]],

         [[ 4.5065,  2.3499],
          [ 5.9689,  1.7937]],

         [[-0.5166,  1.1955],
          [ 1.0665,  1.7110]]]], grad_fn=&lt;ConvolutionBackward0&gt;)</code></pre>
<p>Rotate:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb33-1">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> y_conv<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform</span>(g1)</span>
<span id="cb33-2">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<pre><code>tensor([[[[ 1.1955,  1.7110],
          [-0.5166,  1.0665]],

         [[-0.0905,  2.6568],
          [-0.3743,  2.8144]],

         [[ 5.0640, 11.7395],
          [ 8.6488, 31.7169]],

         [[ 2.3499,  1.7937],
          [ 4.5065,  5.9689]]]])</code></pre>
<p>Indeed, final results are the same.</p>
<p>At this point, we know how to employ group-equivariant convolutions. The final step is to compose the network.</p>
</section>
<section id="a-group-equivariant-neural-network" class="level2">
<h2 class="anchored" data-anchor-id="a-group-equivariant-neural-network">A group-equivariant neural network</h2>
<p>Basically, we have two questions to answer. The first concerns the non-linearities; the second is how to get from extended space to the data type of the target.</p>
<p>First, about the non-linearities. This is a potentially intricate topic, but as long as we stay with point-wise operations (such as that performed by ReLU) equivariance is given intrinsically.</p>
<p>In consequence, we can already assemble a model:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb35-1">feat_type_in <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>trivial_repr))</span>
<span id="cb35-2">feat_type_hid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(</span>
<span id="cb35-3">  r2_act,</span>
<span id="cb35-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr, r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr, r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr, r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr)</span>
<span id="cb35-5">  )</span>
<span id="cb35-6">feat_type_out <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">FieldType</span>(r2_act, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(r2_act<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>regular_repr))</span>
<span id="cb35-7"></span>
<span id="cb35-8">model <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SequentialModule</span>(</span>
<span id="cb35-9">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_in, feat_type_hid, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L),</span>
<span id="cb35-10">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">InnerBatchNorm</span>(feat_type_hid),</span>
<span id="cb35-11">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ReLU</span>(feat_type_hid),</span>
<span id="cb35-12">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_hid, feat_type_hid, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L),</span>
<span id="cb35-13">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">InnerBatchNorm</span>(feat_type_hid),</span>
<span id="cb35-14">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ReLU</span>(feat_type_hid),</span>
<span id="cb35-15">  nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">R2Conv</span>(feat_type_hid, feat_type_out, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L)</span>
<span id="cb35-16">)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb35-17"></span>
<span id="cb35-18">model</span></code></pre></div></div>
<pre><code>SequentialModule(
  (0): R2Conv([C4_on_R2[(None, 4)]:
       {irrep_0 (x1)}(1)], [C4_on_R2[(None, 4)]: {regular (x4)}(16)], kernel_size=3, stride=1)
  (1): InnerBatchNorm([C4_on_R2[(None, 4)]:
       {regular (x4)}(16)], eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU(inplace=False, type=[C4_on_R2[(None, 4)]: {regular (x4)}(16)])
  (3): R2Conv([C4_on_R2[(None, 4)]:
       {regular (x4)}(16)], [C4_on_R2[(None, 4)]: {regular (x4)}(16)], kernel_size=3, stride=1)
  (4): InnerBatchNorm([C4_on_R2[(None, 4)]:
       {regular (x4)}(16)], eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (5): ReLU(inplace=False, type=[C4_on_R2[(None, 4)]: {regular (x4)}(16)])
  (6): R2Conv([C4_on_R2[(None, 4)]:
       {regular (x4)}(16)], [C4_on_R2[(None, 4)]: {regular (x1)}(4)], kernel_size=3, stride=1)
)</code></pre>
<p>Calling this model on some input image, we get:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb37-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">randn</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17</span>L)</span>
<span id="cb37-2">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">feat_type_in</span>(x)</span>
<span id="cb37-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">model</span>(x)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>()</span></code></pre></div></div>
<pre><code>[1]  1  4 11 11</code></pre>
<p>What we do now depends on the task. Since we didn’t preserve the original resolution anyway – as would have been required for, say, segmentation – we probably want one feature vector per image. That we can achieve by spatial pooling:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb39-1">avgpool <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">PointwiseAvgPool</span>(feat_type_out, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>L)</span>
<span id="cb39-2">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">avgpool</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">model</span>(x))</span>
<span id="cb39-3">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>()</span></code></pre></div></div>
<pre><code>[1] 1 4 1 1</code></pre>
<p>We still have four “channels”, corresponding to four group elements. This feature vector is (approximately) translation-<em>in</em>variant, but rotation-<em>equi</em>variant, in the sense expressed by the choice of group. Often, the final output will be expected to be group-invariant as well as translation-invariant (as in image classification). If that’s the case, we pool over group elements, as well:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb41-1">invariant_map <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> nn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GroupPooling</span>(feat_type_out)</span>
<span id="cb41-2">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">invariant_map</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">avgpool</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">model</span>(x)))</span>
<span id="cb41-3">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tensor</span></code></pre></div></div>
<pre><code>tensor([[[[-0.0293]]]], grad_fn=&lt;CopySlices&gt;)</code></pre>
<p>We end up with an architecture that, from the outside, will look like a standard convnet, while on the inside, all convolutions have been performed in a rotation-equivariant way. Training and evaluation then are no different from the usual procedure.</p>
</section>
<section id="where-to-from-here" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="where-to-from-here">Where to from here</h2>
<p>This “introduction to an introduction” has been the attempt to draw a high-level map of the terrain, so you can decide if this is useful to you. If it’s not just useful, but interesting theory-wise as well, you’ll find lots of excellent materials linked from the <a href="https://github.com/QUVA-Lab/escnn">README</a>. The way I see it, though, this post already should enable you to actually experiment with different setups.</p>
<p>One such experiment, that would be of high interest to me, might investigate how well different types and degrees of equivariance actually work for a given task and dataset. Overall, a reasonable assumption is that, the higher “up” we go in the feature hierarchy, the less equivariance we require. For edges and corners, taken by themselves, full rotation equivariance seems desirable, as does equivariance to reflection; for higher-level features, we might want to successively restrict allowed operations, maybe ending up with equivariance to mirroring merely. Experiments could be designed to compare different ways, and levels, of restriction.<sup>8</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn8"><p><sup>8</sup>&nbsp;See the documentation for <a href="https://quva-lab.github.io/escnn/api/escnn.nn.html?highlight=restrictionmodule#escnn.nn.RestrictionModule"><code>escnn$nn$RestrictionModule</code></a> for how to do this.</p></div></div><p>Thanks for reading!</p>
<div class="photo-credit">
<p>Photo by <a href="https://unsplash.com/@astrovol?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Volodymyr Tokar</a> on <a href="https://unsplash.com/photos/hwcWBLq1jO8?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
</div>


</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/group-equivariant-cnn-3/</guid>
  <pubDate>Tue, 09 May 2023 00:00:00 GMT</pubDate>
  <media:content url="https://divergences.xyz/posts/group-equivariant-cnn-3/preview.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Implementing rotation equivariance: Group-equivariant CNN from scratch</title>
  <dc:creator>Sigrid Keydana</dc:creator>
  <link>https://divergences.xyz/posts/group-equivariant-cnn-2/</link>
  <description><![CDATA[ 






<p>Convolutional neural networks (CNNs) are great – they’re able to detect features in an image no matter where. Well, not exactly. They’re not indifferent to just any kind of movement. Shifting up or down, or left or right, is fine; rotating around an axis is not. That’s because of how convolution works: traverse by row, then traverse by column (or the other way round). If we want “more” (e.g., successful detection of an upside-down object), we need to extend convolution to an operation that is <em>rotation-equivariant</em>. An operation that is <em>equivariant</em> to some type of action will not only register the moved feature per se, but also, keep track of which concrete action made it appear where it is.</p>
<p><strong>This is the second post in a series that introduces group-equivariant CNNs (GCNNs)</strong><em>.</em> The <a href="https://blogs.rstudio.com/ai/posts/2023-03-09-group-equivariant-cnn-1/">first</a> was a high-level introduction to why we’d want them, and how they work. There, we introduced the key player, the symmetry group, which specifies what kinds of transformations are to be treated equivariantly. If you haven’t, please take a look at that post first, since here I’ll make use of terminology and concepts it introduced.</p>
<p>Today, we code a simple GCNN from scratch. Code and presentation tightly follow a <a href="https://github.com/phlippe/uvadlc_notebooks/blob/master/docs/tutorial_notebooks/DL2/Geometric_deep_learning/tutorial1_regular_group_convolutions.ipynb">notebook</a> provided as part of University of Amsterdam’s 2022 <a href="https://uvadl2c.github.io/">Deep Learning Course</a>. They can’t be thanked enough for making available such excellent learning materials.</p>
<p>In what follows, my intent is to explain the general thinking, and how the resulting architecture is built up from smaller modules, each of which is assigned a clear purpose. For that reason, I won’t reproduce all the code here; instead, I’ll make use of the package <a href="https://github.com/skeydan/gcnn"><code>gcnn</code></a>. Its methods are heavily annotated; so to see some details, don’t hesitate to look at the code.</p>
<p>As of today, <code>gcnn</code> implements one symmetry group: <img src="https://latex.codecogs.com/png.latex?C_4">, the one that serves as a running example throughout post one. It is straightforwardly extensible, though, making use of class hierarchies throughout.</p>
<section id="step-1-the-symmetry-group-c_4" class="level2">
<h2 class="anchored" data-anchor-id="step-1-the-symmetry-group-c_4">Step 1: The symmetry group <img src="https://latex.codecogs.com/png.latex?C_4"></h2>
<p>In coding a GCNN, the first thing we need to provide is an implementation of the symmetry group we’d like to use. Here, it is <img src="https://latex.codecogs.com/png.latex?C_4">, the four-element group that rotates by 90 degrees.</p>
<p>We can ask <code>gcnn</code> to create one for us, and inspect its elements.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remotes::install_github("skeydan/gcnn")</span></span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(gcnn)</span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(torch)</span>
<span id="cb1-4"></span>
<span id="cb1-5">C_4 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CyclicGroup</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb1-6">elems <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">elements</span>()</span>
<span id="cb1-7">elems</span></code></pre></div></div>
<pre><code>torch_tensor
 0.0000
 1.5708
 3.1416
 4.7124
[ CPUFloatType{4} ]</code></pre>
<p>Elements are represented by their respective rotation angles: <img src="https://latex.codecogs.com/png.latex?0">, <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpi%7D%7B2%7D">, <img src="https://latex.codecogs.com/png.latex?%5Cpi">, and <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B3%20%5Cpi%7D%7B2%7D">.</p>
<p>Groups are aware of the identity, and know how to construct an element’s inverse:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>identity</span>
<span id="cb3-2"></span>
<span id="cb3-3">g1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> elems[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb3-4">C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">inverse</span>(g1)</span></code></pre></div></div>
<pre><code>torch_tensor
 0
[ CPUFloatType{1} ]

torch_tensor
4.71239
[ CPUFloatType{} ]</code></pre>
<p>Here, what we care about most is the group elements’ <em>action</em>. Implementation-wise, we need to distinguish between them acting on each other, and their action on the vector space <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2">, where our input images live. The former part is the easy one: It may simply be implemented by adding angles. In fact, this is what <code>gcnn</code> does when we ask it to let <code>g1</code> act on <code>g2</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">g2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> elems[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># in C_4$left_action_on_H(), H stands for the symmetry group</span></span>
<span id="cb5-4">C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">left_action_on_H</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_tensor</span>(g1)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unsqueeze</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_tensor</span>(g2)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unsqueeze</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span></code></pre></div></div>
<pre><code>torch_tensor
 4.7124
[ CPUFloatType{1,1} ]</code></pre>
<p>What’s with the <code>unsqueeze()</code>s? Since <img src="https://latex.codecogs.com/png.latex?C_4">’s ultimate <em>raison d’être</em> is to be part of a neural network, <code>left_action_on_H()</code> works with batches of elements, not scalar tensors.</p>
<p>Things are a bit less straightforward where the group action on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2"> is concerned. Here, we need the concept of a <a href="https://en.wikipedia.org/wiki/Group_representation">group representation</a>. This is an involved topic, which we won’t go into here. In our current context, it works about like this: We have an input signal, a tensor we’d like to operate on in some way. (That “some way” will be convolution, as we’ll see soon.) To render that operation group-equivariant, we first have the representation apply the <em>inverse</em> group action to the input. That accomplished, we go on with the operation as though nothing had happened.</p>
<p>To give a concrete example, let’s say the operation is a measurement. Imagine a runner, standing at the foot of some mountain trail, ready to run up the climb. We’d like to record their height. One option we have is to take the measurement, then let them run up. Our measurement will be as valid up the mountain as it was down here. Alternatively, we might be polite and not make them wait. Once they’re up there, we ask them to come down, and when they’re back, we measure their height. The result is the same: Body height is equivariant (more than that: invariant, even) to the action of running up or down. (Of course, height is a pretty dull measure. But something more interesting, such as heart rate, would not have worked so well in this example.)</p>
<p>Returning to the implementation, it turns out that group actions are encoded as matrices. There is one matrix for each group element. For <img src="https://latex.codecogs.com/png.latex?C_4">, the so-called <em>standard</em> representation is a rotation matrix:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%20%5Ccos(%5Ctheta)%20&amp;%20-%5Csin(%5Ctheta)%20%5C%5C%20%5Csin(%5Ctheta)%20&amp;%20%5Ccos(%5Ctheta)%20%5Cend%7Bbmatrix%7D%0A"></p>
<p>In <code>gcnn</code>, the function applying that matrix is <code>left_action_on_R2()</code>. Like its sibling, it is designed to work with batches (of group elements as well as <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2"> vectors). Technically, what it does is rotate the grid the image is defined on, and then, re-sample the image. To make this more concrete, that method’s code looks about as follows.</p>
<p>Here is a goat.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">img_path <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"imgs"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"z.jpg"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gcnn"</span>)</span>
<span id="cb7-2">img <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">base_loader</span>(img_path) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform_to_tensor</span>()</span>
<span id="cb7-3">img<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">permute</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</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;">1</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.array</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.raster</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>()</span></code></pre></div></div>
<p><a href="z.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://divergences.xyz/posts/group-equivariant-cnn-2/z.jpg" class="img-fluid" alt="A goat sitting comfortably on a meadow."></a></p>
<p>First, we call <code>C_4$left_action_on_R2()</code> to rotate the grid.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Grid shape is [2, 1024, 1024], for a 2d, 1024 x 1024 image.</span></span>
<span id="cb8-2">img_grid_R2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_stack</span>(torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_meshgrid</span>(</span>
<span id="cb8-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb8-4">      torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_linspace</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</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>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dim</span>(img)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]),</span>
<span id="cb8-5">      torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_linspace</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</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>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dim</span>(img)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb8-6">    )</span>
<span id="cb8-7">))</span>
<span id="cb8-8"></span>
<span id="cb8-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Transform the image grid with the matrix representation of some group element.</span></span>
<span id="cb8-10">transformed_grid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">left_action_on_R2</span>(C_4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">inverse</span>(g1)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unsqueeze</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), img_grid_R2)</span></code></pre></div></div>
<p>Second, we re-sample the image on the transformed grid. The goat now looks up to the sky.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">transformed_img <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nnf_grid_sample</span>(</span>
<span id="cb9-2">  img<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unsqueeze</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), transformed_grid,</span>
<span id="cb9-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">align_corners =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bilinear"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">padding_mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"zeros"</span></span>
<span id="cb9-4">)</span>
<span id="cb9-5"></span>
<span id="cb9-6">transformed_img[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,..]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">permute</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</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;">1</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.array</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.raster</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>()</span></code></pre></div></div>
<p><a href="z2.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://divergences.xyz/posts/group-equivariant-cnn-2/z2.jpg" class="img-fluid" alt="Same goat, rotated up by 90 degrees."></a></p>
</section>
<section id="step-2-the-lifting-convolution" class="level2">
<h2 class="anchored" data-anchor-id="step-2-the-lifting-convolution">Step 2: The lifting convolution</h2>
<p>We want to make use of existing, efficient <code>torch</code> functionality as much as possible. Concretely, we want to use <code>nn_conv2d()</code>. What we need, though, is a convolution kernel that’s equivariant not just to translation, but also to the action of <img src="https://latex.codecogs.com/png.latex?C_4">. This can be achieved by having one kernel for each possible rotation.</p>
<p>Implementing that idea is exactly what <code>LiftingConvolution</code> does. The principle is the same as before: First, the grid is rotated, and then, the kernel (weight matrix) is re-sampled to the transformed grid.</p>
<p>Why, though, call this a <em>lifting convolution</em>? The usual convolution kernel operates on <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2">; while our extended version operates on combinations of <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2"> and <img src="https://latex.codecogs.com/png.latex?C_4">. In math speak, it has been <em>lifted</em> to the <a href="https://en.wikipedia.org/wiki/Semidirect_product">semi-direct product</a> <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D%5E2%5Crtimes%20C_4">.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1">lifting_conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">LiftingConvolution</span>(</span>
<span id="cb10-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CyclicGroup</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>),</span>
<span id="cb10-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<span id="cb10-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">in_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>,</span>
<span id="cb10-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb10-6">  )</span>
<span id="cb10-7"></span>
<span id="cb10-8">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_randn</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</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;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>))</span>
<span id="cb10-9">y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lifting_conv</span>(x)</span>
<span id="cb10-10">y<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape</span></code></pre></div></div>
<pre><code>[1]  2  8  4 28 28</code></pre>
<p>Since, internally, <code>LiftingConvolution</code> uses an additional dimension to realize the product of translations and rotations, the output is not four-, but five-dimensional.</p>
</section>
<section id="step-3-group-convolutions" class="level2">
<h2 class="anchored" data-anchor-id="step-3-group-convolutions">Step 3: Group convolutions</h2>
<p>Now that we’re in “group-extended space”, we can chain a number of layers where both input and output are <em>group convolution</em> layers. For example:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1">group_conv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GroupConvolution</span>(</span>
<span id="cb12-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CyclicGroup</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>),</span>
<span id="cb12-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<span id="cb12-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">in_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>,</span>
<span id="cb12-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span>
<span id="cb12-6">)</span>
<span id="cb12-7"></span>
<span id="cb12-8">z <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_conv</span>(y)</span>
<span id="cb12-9">z<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape</span></code></pre></div></div>
<pre><code>[1]  2 16  4 24 24</code></pre>
<p>All that remains to be done is package this up. That’s what <code>gcnn::GroupEquivariantCNN()</code> does.</p>
</section>
<section id="step-4-group-equivariant-cnn" class="level2">
<h2 class="anchored" data-anchor-id="step-4-group-equivariant-cnn">Step 4: Group-equivariant CNN</h2>
<p>We can call <code>GroupEquivariantCNN()</code> like so.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1">cnn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">GroupEquivariantCNN</span>(</span>
<span id="cb14-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CyclicGroup</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>),</span>
<span id="cb14-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<span id="cb14-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">in_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb14-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb14-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">num_hidden =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># number of group convolutions</span></span>
<span id="cb14-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hidden_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># number of channels per group conv layer</span></span>
<span id="cb14-8">)</span>
<span id="cb14-9"></span>
<span id="cb14-10">img <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_randn</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<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;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>))</span>
<span id="cb14-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cnn</span>(img)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape</span></code></pre></div></div>
<pre><code>[1] 4 1</code></pre>
<p>At casual glance, this <code>GroupEquivariantCNN</code> looks like any old CNN … weren’t it for the <code>group</code> argument.</p>
<p>Now, when we inspect its output, we see that the additional dimension is gone. That’s because after a sequence of group-to-group convolution layers, the module projects down to a representation that, for each batch item, retains channels only. It thus averages not just over locations – as we normally do – but over the group dimension as well. A final linear layer will then provide the requested classifier output (of dimension <code>out_channels</code>).</p>
<p>And there we have the complete architecture. It is time for a real-world(<em>ish</em>) test.</p>
</section>
<section id="rotated-digits" class="level2">
<h2 class="anchored" data-anchor-id="rotated-digits">Rotated digits!</h2>
<p>The idea is to train two convnets, a “normal” CNN and a group-equivariant one, on the usual MNIST training set. Then, both are evaluated on an augmented test set where each image is randomly rotated by a continuous rotation between 0 and 360 degrees. We don’t expect <code>GroupEquivariantCNN</code> to be “perfect” – not if we equip with <img src="https://latex.codecogs.com/png.latex?C_4"> as a symmetry group. Strictly, with <img src="https://latex.codecogs.com/png.latex?C_4">, equivariance extends over four positions only. But we do hope it will perform significantly better than the shift-equivariant-only standard architecture.</p>
<p>First, we prepare the data; in particular, the augmented test set.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb16-1">dir <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/tmp/mnist"</span></span>
<span id="cb16-2"></span>
<span id="cb16-3">train_ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mnist_dataset</span>(</span>
<span id="cb16-4">  dir,</span>
<span id="cb16-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">download =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,</span>
<span id="cb16-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">transform =</span> torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>transform_to_tensor</span>
<span id="cb16-7">)</span>
<span id="cb16-8"></span>
<span id="cb16-9">test_ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mnist_dataset</span>(</span>
<span id="cb16-10">  dir,</span>
<span id="cb16-11">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">train =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>,</span>
<span id="cb16-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">transform =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) {</span>
<span id="cb16-13">    x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb16-14">      torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform_to_tensor</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb16-15">      torchvision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transform_random_rotation</span>(</span>
<span id="cb16-16">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">degrees =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</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;">360</span>),</span>
<span id="cb16-17">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">resample =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb16-18">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb16-19">      )</span>
<span id="cb16-20">  }</span>
<span id="cb16-21">)</span>
<span id="cb16-22"></span>
<span id="cb16-23">train_dl <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dataloader</span>(train_ds, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">batch_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">shuffle =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb16-24">test_dl <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dataloader</span>(test_ds, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">batch_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>)</span></code></pre></div></div>
<p>How does it look?</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb17-1">test_images <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> coro<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">collect</span>(</span>
<span id="cb17-2">  test_dl, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb17-3">)[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>x[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, , ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.array</span>()</span>
<span id="cb17-4"></span>
<span id="cb17-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">par</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mfrow =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<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;">8</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</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;">4</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mai =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</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;">4</span>))</span>
<span id="cb17-6">test_images <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb17-7">  purrr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">array_tree</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb17-8">  purrr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">map</span>(as.raster) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb17-9">  purrr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">iwalk</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> {</span>
<span id="cb17-10">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(.x)</span>
<span id="cb17-11">  })</span></code></pre></div></div>
<p><a href="rotated-mnist.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://divergences.xyz/posts/group-equivariant-cnn-2/rotated-mnist.png" class="img-fluid" alt="32 digits, rotated randomly."></a></p>
<p>We first define and train a conventional CNN. It is as similar to <code>GroupEquivariantCNN()</code>, architecture-wise, as possible, and is given twice the number of hidden channels, so as to have comparable capacity overall.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb18-1"> default_cnn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_module</span>(</span>
<span id="cb18-2">   <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"default_cnn"</span>,</span>
<span id="cb18-3">   <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">initialize =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(kernel_size, in_channels, out_channels, num_hidden, hidden_channels) {</span>
<span id="cb18-4">     self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>conv1 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_conv2d</span>(in_channels, hidden_channels, kernel_size)</span>
<span id="cb18-5">     self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>convs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_module_list</span>()</span>
<span id="cb18-6">     <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>num_hidden) {</span>
<span id="cb18-7">       self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>convs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">append</span>(torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_conv2d</span>(hidden_channels, hidden_channels, kernel_size))</span>
<span id="cb18-8">     }</span>
<span id="cb18-9">     self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>avg_pool <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_adaptive_avg_pool2d</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb18-10">     self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>final_linear <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_linear</span>(hidden_channels, out_channels)</span>
<span id="cb18-11">   },</span>
<span id="cb18-12">   <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">forward =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) {</span>
<span id="cb18-13">     x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-14">       self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conv1</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-15">       (\(.) torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nnf_layer_norm</span>(., .<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]))() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-16">       torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nnf_relu</span>()</span>
<span id="cb18-17">     <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>convs))) {</span>
<span id="cb18-18">       x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-19">         self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>convs[[i]]() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-20">         (\(.) torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nnf_layer_norm</span>(., .<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]))() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-21">         torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nnf_relu</span>()</span>
<span id="cb18-22">     }</span>
<span id="cb18-23">     x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> x <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-24">       self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">avg_pool</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-25">       torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">torch_squeeze</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-26">       self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">final_linear</span>()</span>
<span id="cb18-27">     x</span>
<span id="cb18-28">   }</span>
<span id="cb18-29"> )</span>
<span id="cb18-30"></span>
<span id="cb18-31">fitted <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> default_cnn <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-32">    luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setup</span>(</span>
<span id="cb18-33">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">loss =</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_cross_entropy_loss</span>(),</span>
<span id="cb18-34">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">optimizer =</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>optim_adam,</span>
<span id="cb18-35">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">metrics =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb18-36">        luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">luz_metric_accuracy</span>()</span>
<span id="cb18-37">      )</span>
<span id="cb18-38">    ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-39">    luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_hparams</span>(</span>
<span id="cb18-40">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<span id="cb18-41">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">in_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb18-42">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>,</span>
<span id="cb18-43">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">num_hidden =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb18-44">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hidden_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span></span>
<span id="cb18-45">    ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb18-46">    luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_opt_hparams</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lr =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">weight_decay =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-4</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb18-47">    luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fit</span>(train_dl, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">epochs =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">valid_data =</span> test_dl) </span></code></pre></div></div>
<pre><code>Train metrics: Loss: 0.0498 - Acc: 0.9843
Valid metrics: Loss: 3.2445 - Acc: 0.4479</code></pre>
<p>Unsurprisingly, accuracy on the test set is not that great.</p>
<p>Next, we train the group-equivariant version.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb20-1">fitted <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> GroupEquivariantCNN <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb20-2">  luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setup</span>(</span>
<span id="cb20-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">loss =</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nn_cross_entropy_loss</span>(),</span>
<span id="cb20-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">optimizer =</span> torch<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>optim_adam,</span>
<span id="cb20-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">metrics =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb20-6">      luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">luz_metric_accuracy</span>()</span>
<span id="cb20-7">    )</span>
<span id="cb20-8">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb20-9">  luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_hparams</span>(</span>
<span id="cb20-10">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">CyclicGroup</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>),</span>
<span id="cb20-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kernel_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<span id="cb20-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">in_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb20-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>,</span>
<span id="cb20-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">num_hidden =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb20-15">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hidden_channels =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span>
<span id="cb20-16">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb20-17">  luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_opt_hparams</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lr =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">weight_decay =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-4</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb20-18">  luz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fit</span>(train_dl, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">epochs =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">valid_data =</span> test_dl)</span></code></pre></div></div>
<pre><code>Train metrics: Loss: 0.1102 - Acc: 0.9667
Valid metrics: Loss: 0.4969 - Acc: 0.8549</code></pre>
<p>For the group-equivariant CNN, accuracies on test and training sets are a lot closer. That is a nice result! Let’s wrap up today’s exploit resuming a thought from the first, more high-level post.</p>
</section>
<section id="a-challenge" class="level2">
<h2 class="anchored" data-anchor-id="a-challenge">A challenge</h2>
<p>Going back to the augmented test set, or rather, the samples of digits displayed, we notice a problem. In row two, column four, there is a digit that “under normal circumstances”, should be a 9, but, most probably, is an upside-down 6. (To a human, what suggests this is the squiggle-like thing that seems to be found more often with sixes than with nines.) However, you could ask: does this <em>have</em> to be a problem? Maybe the network just needs to learn the subtleties, the kinds of things a human would spot?</p>
<p>The way I view it, it all depends on the context: What really should be accomplished, and how an application is going to be used. With digits on a letter, I’d see no reason why a single digit should appear upside-down; accordingly, full rotation equivariance would be counter-productive. In a nutshell, we arrive at the same canonical imperative advocates of fair, just machine learning keep reminding us of:</p>
<blockquote class="blockquote">
<p>Always think of the way an application is going to be used!</p>
</blockquote>
<p>In our case, though, there is another aspect to this, a technical one. <code>gcnn::GroupEquivariantCNN()</code> is a simple wrapper, in that its layers all make use of the same symmetry group. In principle, there is no need to do this. With more coding effort, different groups can be used depending on a layer’s position in the feature-detection hierarchy.</p>
<p>Here, let me finally tell you why I chose the goat picture. The goat is seen through a red-and-white fence, a pattern – slightly rotated, due to the viewing angle – made up of squares (or edges, if you like). Now, for such a fence, types of rotation equivariance such as that encoded by <img src="https://latex.codecogs.com/png.latex?C_4"> make a lot of sense. The goat itself, though, we’d rather not have look up to the sky, the way I illustrated <img src="https://latex.codecogs.com/png.latex?C_4"> action before. Thus, what we’d do in a real-world image-classification task is use rather flexible layers at the bottom, and increasingly restrained layers at the top of the hierarchy.</p>
<p>Thanks for reading!</p>
<div class="photo-credit">
<p>Photo by <a href="https://unsplash.com/@marjan_blan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Marjan Blan | <span class="citation" data-cites="marjanblan">@marjanblan</span></a> on <a href="https://unsplash.com/photos/kvOnuo8OvN4?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
</div>


</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/group-equivariant-cnn-2/</guid>
  <pubDate>Mon, 27 Mar 2023 00:00:00 GMT</pubDate>
  <media:content url="https://divergences.xyz/posts/group-equivariant-cnn-2/preview.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Upside down, a cat’s still a cat: Evolving image recognition with Geometric Deep Learning</title>
  <dc:creator>Sigrid Keydana</dc:creator>
  <link>https://divergences.xyz/posts/group-equivariant-cnn-1/</link>
  <description><![CDATA[ 






<p>This is the first in a series of posts on group-equivariant convolutional neural networks (GCNNs). Today, we keep it short, high-level, and conceptual; examples and implementations will follow. In looking at GCNNs, we are resuming a topic we first <a href="https://blogs.rstudio.com/ai/posts/2021-08-26-geometric-deep-learning/">wrote about</a> in 2021: <a href="https://geometricdeeplearning.com/">Geometric Deep Learning</a>, a principled, math-driven approach to network design that, since then, has only risen in scope and impact.</p>
<section id="from-alchemy-to-science-geometric-deep-learning-in-two-minutes" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="from-alchemy-to-science-geometric-deep-learning-in-two-minutes">From alchemy to science: Geometric Deep Learning in two minutes</h2>
<p>In a nutshell, Geometric Deep Learning is all about deriving network structure from two things: the domain, and the task. The posts will go into a lot of detail, but let me give a quick preview here:</p>
<ul>
<li>By domain, I’m referring to the underlying physical space, and the way it is represented in the input data. For example, images are usually coded as a two-dimensional grid, with values indicating pixel intensities.<sup>1</sup></li>
<li>The task is what we’re training the network to do: classification, say, or segmentation. Tasks may be different at different stages in the architecture. At each stage, the task in question will have its word to say about how layer design should look.</li>
</ul>
<div class="no-row-height column-margin column-container"><div id="fn1"><p><sup>1</sup>&nbsp;That’s when the image has a single channel. Otherwise, there are a number of grids, each mapped to a separate channel.</p></div></div><p>For instance, take MNIST. The dataset consists of images of ten digits, 0 to 10, all gray-scale. The task – unsurprisingly – is to assign each image the digit represented.</p>
<p>First, consider the domain. A <img src="https://latex.codecogs.com/png.latex?7"> is a <img src="https://latex.codecogs.com/png.latex?7"> wherever it appears on the grid. We thus need an operation that is <em>translation-equivariant</em>: It flexibly adapts to shifts (translations) in its input. More concretely, in our context, <em>equivariant</em> operations are able to detect some object’s properties even if that object has been moved, vertically and/or horizontally, to another location. <em>Convolution</em>, ubiquitous not just in deep learning, is just such a shift-equivariant operation.</p>
<p>Let me call special attention to the fact that, in equivariance, the essential thing is that “flexible adaptation.” Translation-equivariant operations <em>do</em> care about an object’s new position; they record a feature not abstractly, but at the object’s new position. To see why this is important, consider the network as a whole. When we compose convolutions, we build a hierarchy of feature detectors. That hierarchy should be functional no matter where in the image. In addition, it has to be consistent: Location information needs to be preserved between layers.</p>
<p>Terminology-wise, thus, it is important to distinguish equivariance from <em>invariance</em>. An invariant operation, in our context, would still be able to spot a feature wherever it occurs; however, it would happily forget where that feature happened to be. Clearly, then, to build up a hierarchy of features, translation-<em>invariance</em> is not enough.</p>
<p>What we’ve done right now is derive a requirement from the domain, the input grid. What about the task? If, finally, all we’re supposed to do is name the digit, now suddenly location does not matter anymore. In other words, once the hierarchy exists, invariance <em>is</em> enough. In neural networks, <em>pooling</em> is an operation that forgets about (spatial) detail. It only cares about the mean, say, or the maximum value itself. This is what makes it suited to “summing up” information about a region, or a complete image, if at the end we only care about returning a class label.</p>
<p>In a nutshell, we were able to formulate a design wishlist based on (1) what we’re given and (2) what we’re tasked with.</p>
<p>After this high-level sketch of Geometric Deep Learning, we zoom in on this series of posts’ designated topic: <em>group-equivariant</em> convolutional neural networks.</p>
<p>The why of “equivariant” should not, by now, pose too much of a riddle. What about that “group” prefix, though?</p>
</section>
<section id="the-group-in-group-equivariance" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="the-group-in-group-equivariance">The “group” in group-equivariance</h2>
<p>As you may have guessed from the introduction, talking of “principled” and “math-driven”, this <em>really</em> is about groups in the “math sense.” Depending on your background, the last time you heard about groups was in school, and with not even a hint at why they matter. I’m certainly not qualified to summarize the whole richness of what they’re good for, but I hope that by the end of this post, their importance in deep learning will make intuitive sense.</p>
<section id="groups-from-symmetries" class="level3">
<h3 class="anchored" data-anchor-id="groups-from-symmetries">Groups from symmetries</h3>
<p>Here is a square.</p>
<p><a href="square1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/square1.png" class="img-fluid" alt="A square in its default position, aligned horizontally to a virtual (invisible) x-axis."></a></p>
<p>Now close your eyes.</p>
<p>Now look again. Did something happen to the square?</p>
<p><a href="square1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/square1.png" class="img-fluid" alt="A square in its default position, aligned horizontally to a virtual (invisible) x-axis."></a></p>
<p>You can’t tell. Maybe it was rotated; maybe it was not. On the other hand, what if the vertices were numbered?</p>
<p><a href="square2.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/square2.png" class="img-fluid" alt="A square in its default position, with vertices numbered from 1 to 4, starting in the lower right corner and counting ant-clockwise."></a></p>
<p>Now you’d know.</p>
<p>Without the numbering, could I have rotated the square in any way I wanted? Evidently not. This would not go through unnoticed:</p>
<p><a href="square3.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/square3.png" class="img-fluid" alt="A square, rotated anti-clockwise by a few degrees."></a></p>
<p>There are exactly four ways I could have rotated the square without raising suspicion. Those ways can be referred to in different ways; one simple way is by degree of rotation: 90, 180, or 270 degrees. Why not more? Any further addition of 90 degrees would result in a configuration we’ve already seen.</p>
<p><a href="square4.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/square4.png" class="img-fluid" alt="Four squares, with numbered vertices each. The first has vertex 1 on the lower right, the second one rotation up, on the upper right, and so on."></a></p>
<p>The above picture shows three squares, but I’ve listed three possible rotations. What about the situation on the left, the one I’ve taken as an initial state? It could be reached by rotating 360 degrees (or twice that, or thrice, or …) But the way this is handled, in math, is by treating it as some sort of “null rotation”, analogously to how <img src="https://latex.codecogs.com/png.latex?0"> acts in addition, <img src="https://latex.codecogs.com/png.latex?1"> in multiplication, or the identity matrix in linear algebra.</p>
<p>Altogether, we thus have four <em>actions</em> that could be performed on the square (an un-numbered square!) that would leave it as-is, or <em>invariant</em>. These are called the <em>symmetries</em> of the square. A symmetry, in math/physics, is a quantity that remains the same no matter what happens as time evolves. And this is where groups come in. <em>Groups</em> – concretely, their <em>elements</em> – effectuate actions like rotation.</p>
<p>Before I spell out how, let me give another example. Take this sphere.</p>
<p><a href="sphere.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://divergences.xyz/posts/group-equivariant-cnn-1/sphere.png" class="img-fluid" alt="A sphere, colored uniformly."></a></p>
<p>How many symmetries does a sphere have? Infinitely many. This implies that whatever group is chosen to act on the square, it won’t be much good to represent the symmetries of the sphere.</p>
</section>
<section id="viewing-groups-through-the-action-lens" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="viewing-groups-through-the-action-lens">Viewing groups through the <em>action</em> lens</h3>
<p>Following these examples, let me generalize. Here is typical definition. <sup>2</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn2"><p><sup>2</sup>&nbsp;From <a href="https://mathworld.wolfram.com/Group.html">Wolfram Alpha</a> (beginning of article).</p></div></div><blockquote class="blockquote">
<p>A group <img src="https://latex.codecogs.com/png.latex?G"> is a finite or infinite set of elements together with a binary operation (called the group operation) that together satisfy the four fundamental properties of closure, associativity, the identity property, and the inverse property. The operation with respect to which a group is defined is often called the “group operation,” and a set is said to be a group “under” this operation. Elements <img src="https://latex.codecogs.com/png.latex?A">, <img src="https://latex.codecogs.com/png.latex?B">, <img src="https://latex.codecogs.com/png.latex?C">, … with binary operation between <img src="https://latex.codecogs.com/png.latex?A"> and <img src="https://latex.codecogs.com/png.latex?B"> denoted <img src="https://latex.codecogs.com/png.latex?AB"> form a group if</p>
<ol type="1">
<li><p>Closure: If <img src="https://latex.codecogs.com/png.latex?A"> and <img src="https://latex.codecogs.com/png.latex?B"> are two elements in <img src="https://latex.codecogs.com/png.latex?G">, then the product <img src="https://latex.codecogs.com/png.latex?AB"> is also in <img src="https://latex.codecogs.com/png.latex?G">.</p></li>
<li><p>Associativity: The defined multiplication is associative, i.e., for all <img src="https://latex.codecogs.com/png.latex?A">,<img src="https://latex.codecogs.com/png.latex?B">,<img src="https://latex.codecogs.com/png.latex?C"> in <img src="https://latex.codecogs.com/png.latex?G">, <img src="https://latex.codecogs.com/png.latex?(AB)C=A(BC)">.</p></li>
<li><p>Identity: There is an identity element <img src="https://latex.codecogs.com/png.latex?I"> (a.k.a. <img src="https://latex.codecogs.com/png.latex?1">, <img src="https://latex.codecogs.com/png.latex?E">, or <img src="https://latex.codecogs.com/png.latex?e">) such that <img src="https://latex.codecogs.com/png.latex?IA=AI=A"> for every element <img src="https://latex.codecogs.com/png.latex?A"> in <img src="https://latex.codecogs.com/png.latex?G">.</p></li>
<li><p>Inverse: There must be an inverse (a.k.a. reciprocal) of each element. Therefore, for each element <img src="https://latex.codecogs.com/png.latex?A"> of <img src="https://latex.codecogs.com/png.latex?G">, the set contains an element <img src="https://latex.codecogs.com/png.latex?B=A%5E%7B-1%7D"> such that <img src="https://latex.codecogs.com/png.latex?AA%5E%7B-1%7D=A%5E%7B-1%7DA=I">.</p></li>
</ol>
</blockquote>
<p>In action-speak, group elements specify allowable actions; or more precisely, ones that are distinguishable from each other. Two actions can be composed; that’s the “binary operation”. The requirements now make intuitive sense:</p>
<ol type="1">
<li>A combination of two actions – two rotations, say – is still an action of the same type (a rotation).</li>
<li>If we have three such actions, it doesn’t matter how we group them. (Their order of application has to remain the same, though.)</li>
<li>One possible action is always the “null action”. (Just like in life.) As to “doing nothing”, it doesn’t make a difference if that happens before or after a “something”; that “something” is always the final result.</li>
<li>Every action needs to have an “undo button”. In the squares example, if I rotate by 180 degrees, and then, by 180 degrees again, I am back in the original state. It is if I had done <em>nothing</em>.</li>
</ol>
<p>Resuming a more “birds-eye view”, what we’ve seen right now is the definition of a group by how its elements act on each other. But if groups are to matter “in the real world”, they need to act on something outside (neural network components, for example). How this works is the topic of the following posts, but I’ll briefly outline the intuition here.</p>
</section>
</section>
<section id="outlook-group-equivariant-cnn" class="level2">
<h2 class="anchored" data-anchor-id="outlook-group-equivariant-cnn">Outlook: Group-equivariant CNN</h2>
<p>Above, we noted that, in image classification, a <em>translation</em>-invariant operation (like convolution) is needed: A <img src="https://latex.codecogs.com/png.latex?1"> is a <img src="https://latex.codecogs.com/png.latex?1"> whether moved horizontally, vertically, both ways, or not at all. What about rotations, though? Standing on its head, a digit is still what it is. Conventional convolution does not support this type of action.</p>
<p>We can add to our architectural wishlist by specifying a symmetry group. What group? If we wanted to detect squares aligned to the axes, a suitable group would be <img src="https://latex.codecogs.com/png.latex?C_4">, the cyclic group of order four. (Above, we saw that we needed four elements, and that we could <em>cycle</em> through the group.) If, on the other hand, we don’t care about alignment, we’d want <em>any</em> position to count. In principle, we should end up in the same situation as we did with the sphere. However, images live on discrete grids; there won’t be an unlimited number of rotations in practice.</p>
<p>With more realistic applications, we need to think more carefully. Take digits. When <em>is</em> a number “the same”? For one, it depends on the context. Were it about a hand-written address on an envelope, would we accept a <img src="https://latex.codecogs.com/png.latex?7"> as such had it been rotated by 90 degrees? Maybe. (Although we might wonder what would make someone change ball-pen position for just a single digit.) What about a <img src="https://latex.codecogs.com/png.latex?7"> standing on its head? On top of similar psychological considerations, we should be seriously unsure about the intended message, and, at least, down-weight the data point were it part of our training set.</p>
<p>Importantly, it also depends on the digit itself. A <img src="https://latex.codecogs.com/png.latex?6">, upside-down, is a <img src="https://latex.codecogs.com/png.latex?9">.</p>
<p>Zooming in on neural networks, there is room for yet more complexity. We know that CNNs build up a hierarchy of features, starting from simple ones, like edges and corners. Even if, for later layers, we may not want rotation equivariance, we would still like to have it in the initial set of layers. (The output layer – we’ve hinted at that already – is to be considered separately in any case, since its requirements result from the specifics of what we’re tasked with.)</p>
<p>That’s it for today. Hopefully, I’ve managed to illuminate a bit of <em>why</em> we would want to have group-equivariant neural networks. The question remains: How do we get them? This is what the subsequent posts in the series will be about.</p>
<p>Till then, and thanks for reading!</p>
<div class="photo-credit">
<p>Photo by <a href="https://unsplash.com/@oinua?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Ihor OINUA</a> on <a href="https://unsplash.com/photos/1kKXKmcEtiA?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
</div>


</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/group-equivariant-cnn-1/</guid>
  <pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate>
  <media:content url="https://divergences.xyz/posts/group-equivariant-cnn-1/preview.jpg" medium="image" type="image/jpeg"/>
</item>
<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>
<item>
  <title>Beyond alchemy: A first look at geometric deep learning</title>
  <dc:creator>Sigrid Keydana</dc:creator>
  <link>https://divergences.xyz/posts/geometric-deep-learning/</link>
  <description><![CDATA[ 






<p>To the practitioner, it may often seem that with deep learning, there is a lot of magic involved. Magic in how hyper-parameter choices affect performance, for example. More fundamentally yet, magic in the impacts of architectural decisions. Magic, sometimes, in that it even works (or not). Sure, papers abound that strive to mathematically prove why, for specific solutions, in specific contexts, this or that technique will yield better results. But theory and practice are strangely dissociated: If a technique <em>does</em> turn out to be helpful in practice, doubts may still arise to whether that is, in fact, due to the purported mechanism. Moreover, level of generality often is low.</p>
<p>In this situation, one may feel grateful for approaches that aim to elucidate, complement, or replace some of the magic. By “complement or replace”, I’m alluding to attempts to incorporate domain-specific knowledge into the training process. Interesting examples exist in several sciences, and I certainly hope to be able to showcase a few of these, on this blog at a later time. As for the “elucidate”, this characterization is meant to lead on to the topic of this post: the program of <em>geometric deep learning</em>.</p>
<section id="geometric-deep-learning-an-attempt-at-unification" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="geometric-deep-learning-an-attempt-at-unification">Geometric deep learning: An attempt at unification</h2>
<p>Geometric deep learning (henceforth: GDL) is what a group of researchers, including Michael Bronstein, Joan Bruna, Taco Cohen, and Petar Velicković, call their attempt to build a framework that places deep learning (DL) on a solid mathematical basis.</p>
<p>Prima facie, this is a scientific endeavor: They take existing architectures and practices and show where these fit into the “DL blueprint”. DL research being all but confined to the ivory tower, though, it’s fair to assume that this is not all: From those mathematical foundations, it should be possible to derive new architectures, new techniques to fit a given task. Who, then, should be interested in this? Researchers, for sure; to them, the framework may well prove highly inspirational. Secondly, everyone interested in the mathematical constructions themselves — this probably goes without saying. Finally, the rest of us, as well: Even understood at a purely conceptual level, the framework offers an exciting, inspiring view on DL architectures that – I think – is worth getting to know about as an end in itself. The goal of this post is to provide a high-level introduction .</p>
<p>Before we get started though, let me mention the primary source for this text: <a href="https://arxiv.org/abs/2104.13478">Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges</a> (<span class="citation" data-cites="GDL">Bronstein et al. (2021)</span>).</p>
<div class="no-row-height column-margin column-container"><div id="ref-GDL" class="csl-entry">
Bronstein, Michael M., Joan Bruna, Taco Cohen, and Petar Velickovic. 2021. <span>“Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges.”</span> <em>CoRR</em> abs/2104.13478. <a href="https://arxiv.org/abs/2104.13478">https://arxiv.org/abs/2104.13478</a>.
</div></div><section id="geometric-priors" class="level3">
<h3 class="anchored" data-anchor-id="geometric-priors">Geometric priors</h3>
<p>A <em>prior</em>, in the context of machine learning, is a constraint imposed on the learning task. A generic prior could come about in different ways; a <em>geometric</em> prior, as defined by the GDL group, arises, originally, from the underlying <em>domain</em> of the task. Take image classification, for example. The domain is a two-dimensional grid. Or graphs: The domain consists of collections of nodes and edges.</p>
<p>In the GDL framework, two all-important geometric priors are symmetry and scale separation.</p>
<section id="symmetry" class="level4">
<h4 class="anchored" data-anchor-id="symmetry">Symmetry</h4>
<p>A symmetry, in physics and mathematics, is a transformation that leaves some property of an object unchanged. The appropriate meaning of “unchanged” depends on what sort of property we’re talking about. Say the property is some “essence”, or identity — what object something is. If I move a few steps to the left, I’m still myself: The essence of being “myself” is shift-<em>invarian</em>t. (Or: translation-invariant.) But say the property is location. If I move to the left, my location moves to the left. Location is shift-<em>equivariant</em>. (Translation-equivariant.)</p>
<p>So here we have two forms of symmetry: invariance and equivariance. One means that when we transform an object, the thing we’re interested in stays the same. The other means that we have to transform that thing as well.</p>
<p>The next question then is: What are possible transformations? Translation we already mentioned; on images, rotation or flipping are others. Transformations are composable; I can rotate the digit <code>3</code> by thirty degrees, then move it to the left by five units; I could also do things the other way around. (In this case, though not necessarily in general, the results are the same.) Transformations can be undone: If first I rotate, in some direction, by five degrees, I can then rotate in the opposite one, also by five degrees, and end up in the original position. We’ll see why this matters when we cross the bridge from the domain (grids, sets, etc.) to the learning algorithm.</p>
</section>
<section id="scale-separation" class="level4">
<h4 class="anchored" data-anchor-id="scale-separation">Scale separation</h4>
<p>After symmetry, another important geometric prior is scale separation. Scale separation means that even if something is very “big” (extends a long way in, say, one or two dimensions), we can still start from small patches and “work our way up”. For example, take a cuckoo clock. To discern the hands, you don’t need to pay attention to the pendulum. And vice versa. And once you’ve taken inventory of hands and pendulum, you don’t have to care about their texture or exact position anymore.</p>
<p>In a nutshell, given scale separation, the top-level structure can be determined through successive steps of <em>coarse-graining</em>. We’ll see this prior nicely reflected in some neural-network algorithms.</p>
</section>
</section>
<section id="from-domain-priors-to-algorithmic-ones" class="level3">
<h3 class="anchored" data-anchor-id="from-domain-priors-to-algorithmic-ones">From domain priors to algorithmic ones</h3>
<p>So far, all we’ve really talked about is the <em>domain</em>, using the word in the colloquial sense of “on what structure”, or “in terms of what structure”, something is given. In mathematical language, though, domain is used in a more narrow way, namely, for the “input space” of a <em>function</em>. And a function, or rather, two of them, is what we need to get from priors on the (physical) domain to priors on neural networks.</p>
<p>The first function maps from the physical domain to <em>signal</em> space. If, for images, the domain was the two-dimensional grid, the signal space now consists of images the way they are represented in a computer, and will be worked with by a learning algorithm. For example, in the case of RGB images, that representation is three-dimensional, with a color dimension on top of the inherited spatial structure. What matters is that by this function, the priors are preserved. If something is translation-invariant before “real-to-virtual” conversion, it will still be translation-invariant thereafter.</p>
<p>Next, we have another function: the algorithm, or neural network, acting on signal space. Ideally, this function, again, would preserve the priors. Below, we’ll see how basic neural-network architectures typically preserve some important symmetries, but not necessarily all of them. We’ll also see how, at this point, the actual <em>task</em> makes a difference. Depending on what we’re trying to achieve, we may want to maintain some symmetry, but not care about another. The task here is analogous to the property in physical space. Just like in physical space, a movement to the left does not alter <em>identity</em>, a <em>classifier</em>, presented with that same shift, won’t care at all. But a <em>segmentation</em> algorithm will – mirroring the real-world shift in <em>position</em>.</p>
<p>Now that we’ve made our way to algorithm space, the above requirement, formulated on physical space – that transformations be composable – makes sense in another light: Composing functions is exactly what neural networks do; we want these compositions to work just as deterministically as those of real-world transformations.</p>
<p>In sum, the geometric priors and the way they impose constraints, or desiderates, rather, on the learning algorithm lead to what the GDL group call their deep learning “blueprint”. Namely, a network should be composed of the following types of modules:</p>
<ul>
<li><p>Linear group-equivariant layers. (Here group is the group of transformations whose symmetries we’re interested to preserve.)</p></li>
<li><p>Nonlinearities. (This really does not follow from geometric arguments, but from the observation, often stated in introductions to DL, that without nonlinearities, there is no hierarchical composition of features, since all operations can be implemented in a single matrix multiplication.)</p></li>
<li><p>Local pooling layers. (These achieve the effect of coarse-graining, as enabled by the scale separation prior.)</p></li>
<li><p>A group-invariant layer (global pooling). (Not every task will require such a layer to be present.)</p></li>
</ul>
<p>Having talked so much about the concepts, which are highly fascinating, this list may seem a bit underwhelming. That’s what we’ve been doing anyway, right? Maybe; but once you look at a few domains and associated network architectures, the picture gets colorful again. So colorful, in fact, that we can only present a very sparse selection of highlights.</p>
</section>
</section>
<section id="domains-priors-architectures" class="level2">
<h2 class="anchored" data-anchor-id="domains-priors-architectures">Domains, priors, architectures</h2>
<p>Given cues like “local” and “pooling”, what better architecture is there to start with than CNNs, the (still) paradigmatic deep learning architecture? Probably, it’s also the one a prototypic practitioner would be most familiar with.</p>
<section id="images-and-cnns" class="level3">
<h3 class="anchored" data-anchor-id="images-and-cnns">Images and CNNs</h3>
<p>Vanilla CNNs are easily mapped to the four types of layers that make up the blueprint. Skipping over the nonlinearities, which, in this context, are of least interest, we next have two kinds of pooling.</p>
<p>First, a local one, corresponding to max- or average-pooling layers with small strides (2 or 3, say). This reflects the idea of successive coarse-graining, where, once we’ve made use of some fine-grained information, all we need to proceed is a summary.</p>
<p>Second, a global one, used to effectively remove the spatial dimensions. In practice, this would usually be global average pooling. Here, there’s an interesting detail worth mentioning. A common practice, in image classification, is to replace global pooling by a combination of flattening and one or more feedforward layers. Since with feedforward layers, position in the input matters, this will do away with translation invariance.</p>
<p>Having covered three of the four layer types, we come to the most interesting one. In CNNs, the local, group-equivariant layers are the convolutional ones. What kinds of symmetries does convolution preserve? Think about how a kernel slides over an image, computing a dot product at every location. Say that, through training, it has developed an inclination toward singling out penguin bills. It will detect, and mark, one everywhere in an image — be it shifted left, right, top or bottom in the image. What about rotational motion, though? Since kernels move vertically and horizontally, but not in a circle, a rotated bill will be missed. Convolution is shift-equivariant, not rotation-invariant.</p>
<p>There is something that can be done about this, though, while fully staying within the framework of GDL. Convolution, in a more generic sense, does not have to imply constraining filter movement to horizontal and vertical translation. When reflecting a general <em>group convolution</em>, that motion is determined by whatever transformations constitute the group action. If, for example, that action included translation by sixty degrees, we could rotate the filter to all valid positions, then take these filters and have them slide over the image. In effect, we’d just wind up with more channels in the subsequent layer – the intended base number of filters times the number of attainable positions.</p>
<p>This, it must be said, it just one way to do it. A more elegant one is to apply the filter in the Fourier domain, where convolution maps to multiplication. The Fourier domain, however, is as fascinating as it is out of scope for this post.</p>
<p>The same goes for extensions of convolution from the Euclidean grid to manifolds, where distances are no longer measured by a straight line as we know it. Often on manifolds, we’re interested in invariances beyond translation or rotation: Namely, algorithms may have to support various types of deformation. (Imagine, for example, a moving rabbit, with its muscles stretching and contracting as it hobbles.) If you’re interested in these kinds of problems, the GDL book goes into those in great detail.</p>
<p>For group convolution on grids – in fact, we may want to say “on things that can be arranged in a grid” – the authors give two illustrative examples. (One thing I like about these examples is something that extends to the whole book: Many applications are from the world of natural sciences, encouraging some optimism as to the role of deep learning (“AI”) in society.)</p>
<p>One example is from medical volumetric imaging (MRI or CT, say), where signals are represented on a three-dimensional grid. Here the task calls not just for translation in all directions, but also, rotations, of some sensible degree, about all three spatial axes. The other is from DNA sequencing, and it brings into play a new kind of invariance we haven’t mentioned yet: reverse-complement symmetry. This is because once we’ve decoded one strand of the double helix, we already know the other one.</p>
<p>Finally, before we wrap up the topic of CNNs, let’s mention how through creativity, one can achieve – or put cautiously, try to achieve – certain invariances by means other than network architecture. A great example, originally associated mostly with images, is data augmentation. Through data augmentation, we may hope to make training invariant to things like slight changes in color, illumination, perspective, and the like.</p>
</section>
<section id="graphs-and-gnns" class="level3">
<h3 class="anchored" data-anchor-id="graphs-and-gnns">Graphs and GNNs</h3>
<p>Another type of domain, underlying many scientific and non-scientific applications, are graphs. Here, we are going to be a lot more brief. One reason is that so far, we have not had many posts on deep learning on graphs, so to the readers of this blog, the topic may seem fairly abstract. The other reason is complementary: That state of affairs is exactly something we’d like to see changing. Once we write more about graph DL, occasions to talk about respective concepts will be plenty.</p>
<p>In a nutshell, though, the dominant type of invariance in graph DL is permutation equivariance. Permutation, because when you stack a node and its features in a matrix, it doesn’t matter whether node one is in row three or row fifteen. Equivariance, because once you <em>do</em> permute the nodes, you also have to permute the adjacency matrix, the matrix that captures which node is linked to what other nodes. This is very different from what holds for images: We can’t just randomly permute the pixels.</p>
</section>
<section id="sequences-and-rnns" class="level3">
<h3 class="anchored" data-anchor-id="sequences-and-rnns">Sequences and RNNs</h3>
<p>With RNNs, we are going be very brief as well, although for a different reason. My impression is that so far, this area of research – meaning, GDL as it relates to sequences – has not received too much attention yet, and (maybe) for that reason, seems of lesser impact on real-world applications.</p>
<p>In a nutshell, the authors refer two types of symmetry: First, translation-invariance, as long as a sequence is left-padded for a sufficient number of steps. (This is due to the hidden units having to be initialized <em>somehow</em>.) This holds for RNNs in general.</p>
<p>Second, <em>time warping</em>: If a network can be trained that correctly works on a sequence measured on some time scale, there is another network, of the same architecture but likely with different weights, that will work equivalently on re-scaled time. This invariance only applies to <em>gated</em> RNNs, such as the LSTM.</p>
</section>
<section id="whats-next" class="level3">
<h3 class="anchored" data-anchor-id="whats-next">What’s next?</h3>
<p>At this point, we conclude this conceptual introduction. If you want to learn more, and are not too scared by the math, definitely check out the book. (I’d also say it lends itself well to incremental understanding, as in, iteratively going back to some details once one has acquired more background.)</p>
<p>Something else to wish for certainly is practice. There is an intimate connection between GDL and deep learning on graphs; which is one reason we’re hoping to be able to feature the latter more frequently in the future. The other is the wealth of interesting applications that take graphs as their input. Until then, thanks for reading!</p>
<div class="photo-credit">
<p>Photo by <a href="https://unsplash.com/@nasa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NASA</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/geometric-deep-learning/</guid>
  <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
  <media:content url="https://divergences.xyz/posts/geometric-deep-learning/preview.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
