<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine Learning |</title><link>https://kristianeschenburg.netlify.app/category/machine-learning/</link><atom:link href="https://kristianeschenburg.netlify.app/category/machine-learning/index.xml" rel="self" type="application/rss+xml"/><description>Machine Learning</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Sun, 14 Feb 2021 23:24:17 -0700</lastBuildDate><image><url>https://kristianeschenburg.netlify.app/img/Bayes.jpg</url><title>Machine Learning</title><link>https://kristianeschenburg.netlify.app/category/machine-learning/</link></image><item><title>Jumping-Knowledge Representation Learning With LSTMs</title><link>https://kristianeschenburg.netlify.app/post/jumping-knowledge/</link><pubDate>Sun, 14 Feb 2021 23:24:17 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/jumping-knowledge/</guid><description>&lt;h2 id="background">Background&lt;/h2>
&lt;p>As I mentioned in my previous post on
&lt;a href="https://kristianeschenburg.netlify.app/post/constrained-gat/">constrained graph attention networks&lt;/a>, graph neural networks suffer from overfitting and oversmoothing as network depth increases. These issues can ultimately be linked to the local topologies of the graph.&lt;/p>
&lt;p>If we consider a 2d image as a graph (i.e. pixels become nodes), we see that images are highly
&lt;a href="https://en.wikipedia.org/wiki/Regular_graph" target="_blank" rel="noopener">regular&lt;/a> &amp;ndash; that is, each node has the same number of neighbors, except for those at the image periphery. When we apply convolution kernels over node signals, filters at any given layer are aggregating information from the same-sized neighborhoods irrespective of their location.&lt;/p>
&lt;p>However, if we consider a graph, there is no guarantee that the graph will be regular. In fact, in many situations, graphs are highly &lt;em>irregular&lt;/em>, and are characterized by unique topological neighborhood properties such as tree-like structures or
&lt;a href="https://en.wikipedia.org/wiki/Expander_graph" target="_blank" rel="noopener">expander graphs&lt;/a>, that are sparse yet highly connected. If we compare an expander node to a node whose local topology is more regular, we would find that the number of signals that each node implicitly convolves at each network layer would vary considerably. These topological discrepancies have important implications when we consider problems like node and graph classification, as well as edge prediction. The problem ultimately boils down to one of flexibility: can we account for unique local topologies of a graph in order to dynamically aggregate local information on a node-by-node basis?&lt;/p>
&lt;figure id="figure-node-signal-aggregation-as-a-function-of-network-depth--at-each-layer-the-neural-network-implicitly-aggregates-signals-over-an-increasingly-larger-neighborhood--in-this-example-the-network-is-highly-regular----however-not-all-graphs-are">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/InfluenceRadius_hu_97974a6cf7ff4107.png" data-caption="Node signal aggregation as a function of network depth. At each layer, the neural network implicitly aggregates signals over an increasingly-larger neighborhood. In this example, the network is highly regular &amp;ndash; however, not all graphs are.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/InfluenceRadius_hu_97974a6cf7ff4107.png" class="lazyload" alt="" width="1038" height="343">
&lt;/a>
&lt;figcaption>
Node signal aggregation as a function of network depth. At each layer, the neural network implicitly aggregates signals over an increasingly-larger neighborhood. In this example, the network is highly regular &amp;ndash; however, not all graphs are.
&lt;/figcaption>
&lt;/figure>
&lt;p>In a recent paper, the authors propose one approach to address this question, which they call &amp;ldquo;jumping knowledge representation learning&amp;rdquo;&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Instead of utilizing the output of the last convolution layer to inform the prediction, jumping-knowledge networks aggregate the embeddings from all hidden layers to inform the final prediction. The authors develop an approach to study the &amp;ldquo;influence distribution&amp;rdquo; of nodes: for a given node $x$, the influence distribution $I_{x}$ characterizes how much the final embedding of node $x$ is influenced by the input features of every other node:&lt;/p>
&lt;p>$$
\begin{align}
I(x,y) &amp;amp;= \sum_{i=1}^{m} |\frac{\delta h_{x}^{k}}{\delta h_{y}^{0}}|&lt;em>{i} \\
I&lt;/em>{x}(y) &amp;amp;= I(x,y) \Big/\sum_{z} I(x,z)
\end{align}
$$&lt;/p>
&lt;p>They show that influence distribution $I_{x}$ for a $k$-layer graph convolution network is equal, in expectation, to the $k$-step random walk distribution. They point out that the random walk distribution of expander-like nodes converges quickly &amp;ndash; the final embeddings of these nodes are representative of the whole graph and carry global information &amp;ndash; while the random-walk distribution of nodes with tree-like topology converges slowly &amp;ndash; these nodes carry more-local information&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. These two conclusions are related to the spectral gap of the graph &amp;ndash; the smallest non-zero eigenvalue of the graph Laplacian. A large spectral gap indicates high-connectivity, while a low spectral gap indicates low connectivity. From a graph theory perspective, this local connectivity is related to the idea of centrality. Nodes with high centrality will easily saturate their random walk distribution, but will also aggregate information from large neighborhoods quickly. For graph neural networks with fixed aggregation kernels, this has important implications for representation learning, because the feature distributions of nodes with different topologies will not correspond to the same degree of locality, which may not lead to the best learned representations for all nodes. A radius that is too large may result in over-smoothing of node features, while a radius that is too small may not be robust enough to learn optimal node embeddings.&lt;/p>
&lt;p>The jumping knowledge network architecture is conceptually similar to other graph neural networks, and we can, in fact, simply incorporate the jumping knowledge mechanism as an additional layer. The goal is to adaptively learn the effective neighborhood size on a node-by-node basis, rather than enforcing the same aggregation radius for every node (remember, we want to account for local topological and feature variations). The authors suggest three possible aggregation functions: concatenation, max-pooling, and an LSTM-attention mechanism &lt;sup id="fnref1:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> &lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. Each aggregator learns an optimal combination of the hidden embeddings, which is then pushed through a linear layer to generate the final network output. Concatenation determines the optimal linear combination of hidden embeddings for the entire dataset simultaneously, so it is not a node-specific aggregator. Max-pooling selects the most important hidden layer for each feature element on a node-by-node basis &amp;ndash; however, empirically, I found that max-pooling was highly unstable in terms of model learning. The LSTM-attention aggregator treats the hidden embeddings as a sequence of elements, and assigns a unique attention score to each hidden embedding &lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>.&lt;/p>
&lt;figure id="figure-schematic-of-a-jumping-knowledge-network--the-neural-network-generates-an-embedding-for-each-hidden-layer--the-aggregator-function-then-optimally-combines-these-hidden-embeddings-to-learn-the-optimal-abstraction-of-input-information--some-alternative-aggregation-functions-include-max-pooling-concatenation-and-an-lstm-layer--in-the-case-of-an-lstm-layer-coupled-with-an-attention-mechanism-the-aggregator-computes-a-convex-combination-of-hidden-embeddings">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/JKGAT_LSTM_hu_7907b311199c82b8.png" data-caption="Schematic of a jumping-knowledge network. The neural network generates an embedding for each hidden layer. The aggregator function then optimally combines these hidden embeddings to learn the optimal abstraction of input information. Some alternative aggregation functions include max-pooling, concatenation, and an LSTM layer. In the case of an LSTM layer coupled with an attention mechanism, the aggregator computes a convex combination of hidden embeddings.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/JKGAT_LSTM_hu_7907b311199c82b8.png" class="lazyload" alt="" width="937" height="499">
&lt;/a>
&lt;figcaption>
Schematic of a jumping-knowledge network. The neural network generates an embedding for each hidden layer. The aggregator function then optimally combines these hidden embeddings to learn the optimal abstraction of input information. Some alternative aggregation functions include max-pooling, concatenation, and an LSTM layer. In the case of an LSTM layer coupled with an attention mechanism, the aggregator computes a convex combination of hidden embeddings.
&lt;/figcaption>
&lt;/figure>
&lt;h4 id="long-short-term-memory">Long-Short Term Memory&lt;/h4>
&lt;p>Briefly, given a sequence of samples $X_{1}, X_{2}, \dots X_{t}$, the LSTM cell learns temporal dependencies between elements of a sequence by maintaining a memory of previously observed elements &amp;ndash; in our case, the sequence elements are the embeddings learned by each consecutive hidden layer. An LSTM cell is characterized by three gates controlling information flow between elements in the sequence: input, forget, and output, as well as a cell state vector, which captures the memory and temporal dependencies between sequence elements&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup>:&lt;/p>
&lt;figure id="figure-schematic-of-an-lstm-cell--the-cell-controls-what-information-is-remembered-from-previous-elements-in-a-sequence-and-what-information-is-incorporated-into-memory-given-a-new-element-in-the-sequence">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/LSTM_cell_hu_75b8f32ccf1828c8.png" data-caption="Schematic of an LSTM cell. The cell controls what information is remembered from previous elements in a sequence, and what information is incorporated into memory given a new element in the sequence.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/LSTM_cell_hu_75b8f32ccf1828c8.png" class="lazyload" alt="" width="2646" height="1582">
&lt;/a>
&lt;figcaption>
Schematic of an LSTM cell. The cell controls what information is remembered from previous elements in a sequence, and what information is incorporated into memory given a new element in the sequence.
&lt;/figcaption>
&lt;/figure>
&lt;p>$$
\begin{align}
f_{t} &amp;amp;= \sigma(W_{f}X_{t} + U_{f}h_{t-1} + b_{f}) \\
i_{t} &amp;amp;= \sigma(W_{i}X_{t} + U_{i}h_{t-1} + b_{i}) \\
o_{t} &amp;amp;= \sigma(W_{o}X_{t} + U_{o}h_{t-1} + b_{o}) \\
\end{align}
$$&lt;/p>
&lt;p>where $W$, $U$, and $b$ are learnable parameters of the gates. Here, $X_{t}$ is the $t$-th sequence element, $h_{t-1}$ represents the learned LSTM cell embedding for element $t-1$, and $C_{t-1}$ represents the current memory state, given the previous $1, 2 \dots t-1$ elements. The input and forget gates determine which aspects of a sequence element are informative / uninformative, and decide what information to keep / forget, while the output gate combines the previous memory state with our new knowledge. We can roughly think of this process as updating our prior beliefs, in the Bayesian sense, with new incoming data.&lt;/p>
&lt;p>$$
\begin{align}
\tilde{c_{t}} &amp;amp;= \sigma(W_{c}X_{t} + U_{c}h_{t-1} + b_{c}) \\
c_{t} &amp;amp;= f_{t}\circ c_{t-1} + i_{t} \tilde{c}&lt;em>{t} \\
h&lt;/em>{t} &amp;amp;= o_{t} \circ tanh(c_{t})
\end{align}
$$&lt;/p>
&lt;p>The embeddings for each element learned by the LSTM cell are represented by $h_{t}$. In the original paper&lt;sup id="fnref2:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, the authors propose to apply a bi-directional LSTM to simultaneously learn forwards and backwards embeddings, which are concatenated and pushed through a single-layer perceptron to compute layer-specific attention weights for each node:&lt;/p>
&lt;p>$$
\begin{align}
\alpha_{i}^{t} &amp;amp;= \sigma(\vec{w}&lt;em>{t}^{T}(h^{F}&lt;/em>{i, t} || h^{B}&lt;em>{i, t})) \\
\alpha&lt;/em>{i}^{t} &amp;amp;= \frac{\exp{(\alpha_{i}^{t})}}{\sum_{t=1}^{L} \exp{(\alpha_{i}^{t})}}
\end{align}
$$&lt;/p>
&lt;p>The softmax-normalized attention weights represent a probability distribution over attention weights&lt;/p>
&lt;p>$$\begin{align}
\sum_{t=1}^{L} \alpha_{i}^{t} &amp;amp;= 1 \\
\\
\alpha_{i}^{t} &amp;amp;&amp;gt;= 0
\end{align}
$$&lt;/p>
&lt;p>where $\alpha_{i}^{t}$ represents how much node $i$ attends to the embedding of hidden layer $t$. The optimal embedding is then computed as the attention-weighted convex combination of hidden embeddings:&lt;/p>
&lt;p>$$
\begin{align}
X_{i, \mu} = \sum_{t=1}^{L} \alpha_{i}^{t}X_{i, t}
\end{align}
$$&lt;/p>
&lt;h2 id="an-application-of-jumping-knowledge-networks-to-cortical-segmentation">An Application of Jumping Knowledge Networks to Cortical Segmentation&lt;/h2>
&lt;p>I&amp;rsquo;ve implemented the jumping knowledge network using DGL
&lt;a href="https://github.com/kristianeschenburg/parcellearning/blob/master/parcellearning/jkgat/jkgat.py" target="_blank" rel="noopener">here&lt;/a>. Below, I&amp;rsquo;ll demonstrate the application of jumping knowledge representation learning to a cortical segmentation task. Neuroscientifically, we have reason to believe that the influence radius will vary along the cortical manifold, even if the mesh structure is highly regular. As such, I am specifically interested in examining the importance that each node assigns to the embeddings of each hidden layer. To that end, I utilize the LSTM-attention aggregator. Similarly, as the jumping-knowledge mechanism can be incorporated as an additional layer to any general graph neural network, I will use graph attention networks (GAT) as the base network architecture, and compare vanilla GAT performance to GATs with a jumping knowledge mechanism (JKGAT).&lt;/p>
&lt;p>Below, I show the prediction generated by a 9-layer JKGAT model, with 4 attention heads and 32 hidden channels per layer, with respect to the &amp;ldquo;known&amp;rdquo; or &amp;ldquo;true&amp;rdquo; cortical map. We find slight differences in the performance of our JKGAT model with respect to the ground truth map, notably in the lateral occipital cortex and the medial prefrontal cortex.&lt;/p>
&lt;figure id="figure-comparison-of-the-group-average-predicted-cortical-segmentation-produced-by-the-jkgat-model-to-the-ground-truth-cortical-segmentation--the-ground-truth-was-previously-generated-herehttpswwwncbinlmnihgovpmcarticlespmc4990127--the-consensus-cortical-map-corresponds-very-well-to-the-true-map">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/prediction_G_hu_5e4b09ed503e914a.png" data-caption="Comparison of the group-average predicted cortical segmentation produced by the JKGAT model, to the ground truth cortical segmentation. The ground truth was previously generated
here. The consensus cortical map corresponds very well to the true map.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/prediction_G_hu_5e4b09ed503e914a.png" class="lazyload" alt="" width="1440" height="797">
&lt;/a>
&lt;figcaption>
Comparison of the group-average predicted cortical segmentation produced by the JKGAT model, to the ground truth cortical segmentation. The ground truth was previously generated
&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4990127/" target="_blank" rel="noopener">here&lt;/a>. The consensus cortical map corresponds very well to the true map.
&lt;/figcaption>
&lt;/figure>
&lt;p>When we consider the accuracies for various parameterizations of our models, we see that the JKGAT performs quite well. Notably, it performs better than the GAT model in most cases. Likewise, as hypothesized, the JKGAT performs better than the GAT model as network depth increases, specifically because we are able to dynamically learn the optimal influence radii for each node, rather than constraining the same radius size for the entire graph. This allows us to learn more abstract representations of the input features by mitigating oversmoothing and by accounting for node topological variability, which is important for additional use-cases like graph classification.&lt;/p>
&lt;figure id="figure-model-accuracy-comparison-between-gat-and-jkgat-models-on-a-node-classification-problem-for-cortical-segmentation--accuracy-is-represented-as-the-fraction-of-correctly-labeled-nodes-in-a-graph-across-150-validation-subjects--each-node-in-the-graph-has-80-features-and-each-graph-has-30k-nodes">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/validation.accuracy_hu_ed3a8a47d01b5966.png" data-caption="Model accuracy comparison between GAT and JKGAT models on a node classification problem for cortical segmentation. Accuracy is represented as the fraction of correctly-labeled nodes in a graph, across 150 validation subjects. Each node in the graph has 80 features, and each graph has 30K nodes.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/validation.accuracy_hu_ed3a8a47d01b5966.png" class="lazyload" alt="" width="1064" height="356">
&lt;/a>
&lt;figcaption>
Model accuracy comparison between GAT and JKGAT models on a node classification problem for cortical segmentation. Accuracy is represented as the fraction of correctly-labeled nodes in a graph, across 150 validation subjects. Each node in the graph has 80 features, and each graph has 30K nodes.
&lt;/figcaption>
&lt;/figure>
&lt;p>Similarly, we find that JKGAT networks generate segmentation predictions that are more reproducible and consistent across resampled datasets. This is important, especially in the case where we might acquire data on an individual multiple times, and want to generate a cortical map for each acquisition instance. Unless an individual suffers from an accelerating neurological disorder, experiences a traumatic neurological injury, or the time between consecutive scans is very long (on the order of years), we expect the cortical map of any given individual to remain quite static (though examining how the &amp;ldquo;map&amp;rdquo; of an individual changes over time is still an open-ended topic).&lt;/p>
&lt;figure id="figure-model-reproducibility-comparison-between-gat-and-jkgat-models-on-a-node-classification-problem-for-cortical-segmentation-using-150-validation-subjects--each-subject-has-four-repeated-datasets--within-a-given-subject-the-topology-of-each-graph-is-the-same-but-the-node-features-are-re-sampled-for-each-graph--reproducibility-is-computed-using-the-f1-score-between-all-pairs-of-predicted-node-classifications-such-that-we-compute-6-f1-scores-for-each-subject">
&lt;a data-fancybox="" href="validation.reproducibility.png" data-caption="Model reproducibility comparison between GAT and JKGAT models on a node classification problem for cortical segmentation, using 150 validation subjects. Each subject has four repeated datasets. Within a given subject, the topology of each graph is the same, but the node features are re-sampled for each graph. Reproducibility is computed using the F1-score between all pairs of predicted node classifications, such that we compute 6 F1 scores for each subject.">
&lt;img src="validation.reproducibility.png" alt="" >
&lt;/a>
&lt;figcaption>
Model reproducibility comparison between GAT and JKGAT models on a node classification problem for cortical segmentation, using 150 validation subjects. Each subject has four repeated datasets. Within a given subject, the topology of each graph is the same, but the node features are re-sampled for each graph. Reproducibility is computed using the F1-score between all pairs of predicted node classifications, such that we compute 6 F1 scores for each subject.
&lt;/figcaption>
&lt;/figure>
&lt;p>Finally, when we consider the importance that each cortical node assigns to the unique embedding at the $k$-th layer via the LSTM-attention aggregation function, we see very interesting results. Notably, we see high spatial auto-correlation of the attention weights. Even more striking is that this spatial correlation seems to correspond to well-studied patterns of resting-state networks identified using functional MRI. Apart from the adjacency structure of our graphs, we do not encode any &lt;em>a priori&lt;/em> information of brain connectivity. That the LSTM-attention aggregator of the jumping-knowledge layer identifies maps corresponding reasonably well to known functional networks of the human brain is indicative, to some extent, of how the model is learning, and more importantly, of which features are useful in distinguishing cortical areas from one another.&lt;/p>
&lt;p>Let us consider the attention map for layer 4. We can interpret the maps as follows: for a given network architecture (in this case, a network with 9 layers), we find that areas in the primary motor (i.e. Brodmann areas 3a and banks of area 4) and primary auditory cortex (Brodmann areas A1 and R1) preferentially attend to the embedding of hidden layer 4, relative to the rest of the cortex &amp;ndash; this indicates that the implicit aggregation over an influence radius of 4 layers is deemed more informative for the classification of nodes in the primary motor and auditory regions than for other cortical areas. However, whether this says anything about the implicit complexity of the cortical signals of these areas remains to be studied.&lt;/p>
&lt;figure id="figure-maps-of-learned-lstm-attention-aggregator-weights--each-inset-corresponds-to-the-weights-learned-by-every-cortical-node-for-the-k-th-layer-hidden-embedding-black-low-red-high--we-see-that-most-of-the-attention-mass-is-distributed-over-layers-4-7-indicating-that-most-nodes-assign-maximal-importance-to-intermediate-levels-of-abstraction--however-we-do-see-spatially-varying-attention--notably-within-a-given-attention-map-we-find-that-nodes-of-the-lateral-default-mode-network-preferentially-attend-to-the-embeddings-of-layers-1-3-while-layer-4-is-preferentially-attended-to-by-the-primary-motor-and-auditory-areas">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/jumping-knowledge/attentions_hu_97e2425f7ae2a1d9.png" data-caption="Maps of learned LSTM-attention aggregator weights. Each inset corresponds to the weights learned by every cortical node for the $k$-th layer hidden embedding (black: low, red: high). We see that most of the attention mass is distributed over layers 4-7, indicating that most nodes assign maximal importance to intermediate levels of abstraction. However, we do see spatially varying attention. Notably, within a given attention map, we find that nodes of the lateral Default Mode Network preferentially attend to the embeddings of layers 1-3, while layer 4 is preferentially attended to by the primary motor and auditory areas.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/jumping-knowledge/attentions_hu_97e2425f7ae2a1d9.png" class="lazyload" alt="" width="1440" height="797">
&lt;/a>
&lt;figcaption>
Maps of learned LSTM-attention aggregator weights. Each inset corresponds to the weights learned by every cortical node for the $k$-th layer hidden embedding (black: low, red: high). We see that most of the attention mass is distributed over layers 4-7, indicating that most nodes assign maximal importance to intermediate levels of abstraction. However, we do see spatially varying attention. Notably, within a given attention map, we find that nodes of the lateral Default Mode Network preferentially attend to the embeddings of layers 1-3, while layer 4 is preferentially attended to by the primary motor and auditory areas.
&lt;/figcaption>
&lt;/figure>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Xu et al.
&lt;a href="https://arxiv.org/pdf/1806.03536.pdf" target="_blank" rel="noopener">Representation Learning on Graphs with Jumping Knowledge Networks&lt;/a>. 2018.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&amp;#160;&lt;a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Dinitz et al.
&lt;a href="https://arxiv.org/pdf/1611.01755.pdf" target="_blank" rel="noopener">Large Low-Diameter Graphs are Good Expanders&lt;/a>. 2017.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>Lutzeyer et al.
&lt;a href="https://arxiv.org/pdf/1712.03769.pdf" target="_blank" rel="noopener">Comparing Graph Spectra of Adjacency and Laplacian Matrices&lt;/a>. 2017.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>Gers, Felix.
&lt;a href="http://www.felixgers.de/papers/phd.pdf" target="_blank" rel="noopener">Long Short-Term Memory in Recurrent Neural Networks&lt;/a>. 2001.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>Fan et al.
&lt;a href="https://www.mdpi.com/2073-4441/12/1/175/htm" target="_blank" rel="noopener">Comparison of Long Short Term Memory Networks and the Hydrological Model in Runoff Simulation&lt;/a>. 2020.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Constrained Graph Attention Networks</title><link>https://kristianeschenburg.netlify.app/post/constrained-gat/</link><pubDate>Fri, 25 Dec 2020 23:24:17 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/constrained-gat/</guid><description>&lt;p>In their recent
&lt;a href="https://arxiv.org/abs/1910.11945" target="_blank" rel="noopener">paper&lt;/a>, Wang et al. propose a few updates to the Graph Attention Network (GAT) neural network algorithm (if you want to skip the technical bit and get to the code, click
&lt;a href="#Implementation">here&lt;/a>). Briefly, GATs are a
&lt;a href="https://arxiv.org/pdf/1710.10903.pdf" target="_blank" rel="noopener">recently-developed&lt;/a> neural network architecture applied to data distributed over a graph domain. We can think of graph convolutional networks as progressively transforming and aggregating signals from within a local neighborhood of a node. At each iteration of this process, we implicitly merge signals from larger and larger neighborhoods of the node of interest, and thereby learn unique representations of nodes that are dependent on their surroundings.&lt;/p>
&lt;p>GATs incorporate the seminal idea of &amp;ldquo;attention&amp;rdquo; into this learning process. In each message-passing step, rather than updating the features of a source-node via equally-weighted contributions of neighborhood nodes, GAT models learn an attention function &amp;ndash; i.e. they learn how to differentially pay attention to various signals in the neighborhood. In this way, the algorithm can learn to focus on important signals and disregard superfluous signals. If we consider neural networks as universal function approximators, the attention mechanism improves the approximating ability by incorporating multiplicative weight factors into the learning.&lt;/p>
&lt;figure id="figure-figure-from-velickovic-et-alhttpsarxivorgpdf171010903pdf--for-a-source-node-i-and-destination-node-j-vectors-vech_i-and-vech_j-are-the-input-feature-vectors-of-nodes-i-and-j-in-layer-l--mathbfw-is-a-learned-affine-projection-matrix--mathbfveca-is-the-learned-attention-function--the-source-and-destination-node-input-features-are-pushed-through-the-attention-layer-as-alpha_ij--sigmabigvecatmathbfwbigvech_i--vech_jbigbig-where-sigma-is-an-activation-function-and-alpha_ij-the-unnormalized-attention-that-node-i-pays-to-node-j-in-layer-l--attention-weights-are-then-passed-through-a-softmax-layer-mapping-the-attentions-between-01">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/constrained-gat/attention_mechanism_hu_e6ebf8351314e103.png" data-caption="Figure from
Velickovic et al. For a source node $i$ and destination node $j$, vectors $\vec{h_{i}}$ and $\vec{h_{j}}$ are the input feature vectors of nodes $i$ and $j$ in layer $l$. $\mathbf{W}$ is a learned affine projection matrix. $\mathbf{\vec{a}}$ is the learned attention function. The source and destination node input features are pushed through the attention layer as $\alpha_{i,j} = \sigma\Big(\vec{a}^{T}\mathbf{W}\Big(\vec{h_{i}} || \vec{h_{j}}\Big)\Big)$ where $\sigma$ is an activation function, and $\alpha_{i,j}$ the unnormalized attention that node $i$ pays to node $j$ in layer $l$. Attention weights are then passed through a softmax layer, mapping the attentions between [0,1].">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/constrained-gat/attention_mechanism_hu_e6ebf8351314e103.png" class="lazyload" alt="" width="1102" height="518">
&lt;/a>
&lt;figcaption>
Figure from
&lt;a href="https://arxiv.org/pdf/1710.10903.pdf" target="_blank" rel="noopener">Velickovic et al&lt;/a>. For a source node $i$ and destination node $j$, vectors $\vec{h_{i}}$ and $\vec{h_{j}}$ are the input feature vectors of nodes $i$ and $j$ in layer $l$. $\mathbf{W}$ is a learned affine projection matrix. $\mathbf{\vec{a}}$ is the learned attention function. The source and destination node input features are pushed through the attention layer as $\alpha_{i,j} = \sigma\Big(\vec{a}^{T}\mathbf{W}\Big(\vec{h_{i}} || \vec{h_{j}}\Big)\Big)$ where $\sigma$ is an activation function, and $\alpha_{i,j}$ the unnormalized attention that node $i$ pays to node $j$ in layer $l$. Attention weights are then passed through a softmax layer, mapping the attentions between [0,1].
&lt;/figcaption>
&lt;/figure>
&lt;p>However, GATs are not without their pitfalls, as noted by Wang et al. Notably, the authors point to two important issues that GATs suffer from: overfitting of attention values and oversmoothing of signals across class boundaries. The authors propose that GATs overfit the attention function because the learning process is driven only by classification error, with complexity $O(|V|)$ i.e. the number of nodes in the graph. With regards to oversmoothing, the authors note that a single attention layer can be viewed as a form of Laplacian smoothing:&lt;/p>
&lt;p>$$\begin{align}
Y = AX^{l}
\end{align}$$&lt;/p>
&lt;p>where $A_{n \times n}$ is the attention weight matrix with $A_{i,j} = \alpha_{i,j}$ if $j \in \mathcal{N_{i}}$ and $0$ otherwise. Because $\sum_{j\in \mathcal{N_{i}}} \alpha_{i,j} = 1$, we can view $A$ as a random walk transition probability matrix. If we assume that graph $G=(V,E)$ has $K$ connected components, repeated application of $A$ to $X$ distributed over $G$ will result in a stationary distribution of node features within each connected component &amp;ndash; that is, the feature vectors of the nodes within each connected component will converge on the component mean. However, as the authors point out, we typically have multiple layers $l_{1}\dots l_{j}$, each with their own attention matrix $A_{1} \dots A_{j}$, each representing a unique transition probability matrix. Because we generally do not have disconnected components, nodes from different classes will be connected &amp;ndash; consequently, deep GAT networks will mix and smooth signals from different adjacent components, resulting in classification performance that worsens with network depth. Importantly, multi-head attention networks do not alleviate this convergence issue &amp;ndash; each head can be viewed as a unique probability transition matrix, which all suffer from the same oversmoothing issue as $l \rightarrow \infty$.&lt;/p>
&lt;p>Wang et al. propose to incorporate two margin-based constraints into the learning process. The first constraint, $\mathcal{L_{g}}$, addresses the overfitting issue, by enforcing that learned attentions between adjacent pairs of nodes be higher than attentions between distant pairs of nodes.&lt;/p>
&lt;p>$$\begin{align}
\mathcal{L_{g}} &amp;amp;= \sum_{i \in V} \sum_{j \in \mathcal{N_{i}} \setminus \mathcal{N_{i}^{-}}} \sum_{k \in V\setminus \mathcal{N_{i}}} max(0, \phi(v_{i},v_{k}) + \zeta_{g} - \phi(v_{i},v_{j}))
\end{align}$$&lt;/p>
&lt;p>The second constraint, $\mathcal{L_{b}}$, addresses the oversmoothing issue, by enforcing that learned attentions between pairs of adjacent nodes with the same label be higher than attention between pairs of adjacent nodes with different labels:&lt;/p>
&lt;p>$$\begin{align}
\mathcal{L_{b}} &amp;amp;= \sum_{i \in V}\sum_{j \in \mathcal{N_{i}^{+}}} \sum_{k \in \mathcal{N_{i}^{-}}} max(0, \phi(v_{i},v_{k}) + \zeta_{b} - \phi(v_{i},v_{j}))
\end{align}$$&lt;/p>
&lt;p>In both cases, $\phi(,)$ is the attention function between a pair of nodes, $\mathcal{N_{i}^{+}}$ and $\mathcal{N_{i}^{-}}$ are the nodes adjacent to node $i$ with the same (+) and different (-) labels as $i$, and $\zeta_{g}$ and $\zeta_{b}$ are slack variables controlling the margin between attention values. The first loss function, $\mathcal{L_{g}}$, can be implemented via negative sampling of nodes (the authors actually perform importance-based negative sampling based on attention-weighted node degrees, but showed that this only marginally improved classification accuracy in benchmark datasets).&lt;/p>
&lt;p>The authors propose one final addition to alleviate the oversmoothing issue posed by vanilla GATs. Rather than aggregating over all adjacent nodes in a neighborhood, the authors propose to aggregate over the nodes with the $K$ greatest attention values. Because the class boundary loss $\mathcal{L_{b}}$ enforces large attentions on nodes of the same label and small attention on nodes of different labels, aggregating over the top $K$ nodes will tend to exclude nodes of different labels than the source node in the message passing step, thereby preventing oversmoothing. The authors show that this constrained aggregation approach is preferable to attention dropout proposed in the original
&lt;a href="https://arxiv.org/pdf/1710.10903.pdf" target="_blank" rel="noopener">GAT paper&lt;/a>. &lt;a name="Implementation">
Taken together, the authors deem these margin-based losses and constrained aggregation &amp;ldquo;Constrained Graph Attention Network&amp;rdquo; (C-GAT).&lt;/p>
&lt;/a>
&lt;h2 id="implementation">Implementation&lt;/h2>
&lt;p>I wasn&amp;rsquo;t able to find an implementation of the Constrained Graph Attention Network for my own purposes, so I&amp;rsquo;ve implemented the algorithm myself in
&lt;a href="https://www.dgl.ai/" target="_blank" rel="noopener">Deep Graph Library&lt;/a> (DGL) &amp;ndash; the source code for this convolutional layer can be found
&lt;a href="https://github.com/kristianeschenburg/parcellearning/blob/master/parcellearning/conv/cgatconv.py" target="_blank" rel="noopener">here&lt;/a>. This implementation makes use of the original DGL &lt;code>GATConv&lt;/code> layer structure, with modifications made for the constraints and aggregations. Specifically, the API for &lt;code>CGATConv&lt;/code> has the following modifications:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">CGATCONV&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in_feats&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out_feats&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">num_heads&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">feat_drop&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">graph_margin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># graph structure loss slack variable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">class_margin&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># class boundary loss slack variable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">top_k&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># number of messages to aggregate over&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">negative_slope&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">residual&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">activation&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">allow_zero_in_degree&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of note is the fact that the &lt;code>attn_drop&lt;/code> parameter has been substituted by the &lt;code>top_k&lt;/code> parameter in order to mitigate oversmoothing, and the two slack variables $\zeta_{g}$ and $\zeta_{b}$ are provided as &lt;code>graph_margin&lt;/code> and &lt;code>class_margin&lt;/code>.&lt;/p>
&lt;p>With regards to the loss functions, the authors compute all-pairs differences between all edges incident on a source node, instead of summing over the positive / negative sample attentions ($\mathcal{L_{g}}$) and same / different label attentions ($\mathcal{L_{b}}$) and then differencing these summations. In this way, the C-GAT model anchors the loss values to specific nodes, promoting learning of more generalizable attention weights. The graph structure loss function $\mathcal{L_{g}}$ is implemented with the &lt;code>graph_loss&lt;/code> reduction function below:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">graph_loss&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nodes&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Loss function on graph structure.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Enforces high attention to adjacent nodes and
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> lower attention to distant nodes via negative sampling.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mailbox&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pw&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">msg&lt;/span>&lt;span class="p">[:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nw&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">msg&lt;/span>&lt;span class="p">[:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">nw&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_graph_margin&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">pw&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clamp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">loss&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">squeeze&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;graph_loss&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">loss&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">srcdata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">feat_src&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;el&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">el&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dstdata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s1">&amp;#39;er&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">er&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply_edges&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">u_add_v&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;el&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;er&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">leaky_relu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># construct the negative graph by shuffling edges&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># does not assume a single graph or blocked graphs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># see cgatconv.py for ```construct_negative_graph``` function&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">neg_graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">construct_negative_graph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">dgl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unbatch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">neg_graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dgl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">neg_graph&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">neg_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">srcdata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">feat_src&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;el&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">el&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">neg_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dstdata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s1">&amp;#39;er&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">er&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">neg_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply_edges&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">u_add_v&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;el&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;er&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ne&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">leaky_relu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">neg_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">combined&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ne&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;combined&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">combined&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copy_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;combined&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">graph_loss&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># compute graph structured loss&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Lg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;graph_loss&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">num_nodes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_num_heads&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Similarly, the class boundary loss function $\mathcal{L_{b}}$ is implemented with the following message and reduce functions:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">adjacency_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">edges&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Compute binary message on edges.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Compares whether source and destination nodes
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> have the same or different labels.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">l_src&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">edges&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">src&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;l&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">l_dst&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">edges&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dst&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;l&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">l_src&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndim&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">adj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">l_src&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">l_dst&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">adj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">l_src&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">l_dst&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;adj&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">adj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">detach&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">class_loss&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nodes&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Loss function on class boundaries.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Enforces high attention to adjacent nodes with the same label
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> and lower attention to adjacent nodes with different labels.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mailbox&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">w&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">[:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="p">:&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">adj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">[:,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bool&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">same_class&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">adj&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nan&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">diff_class&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">adj&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nan&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">difference&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">diff_class&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_class_margin&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">same_class&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clamp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nansum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nansum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">difference&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;boundary_loss&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">loss&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;l&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">label&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply_edges&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">adjacency_message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">adj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;adj&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">combined&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cat&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">squeeze&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">adj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)],&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;combined&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">combined&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copy_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;combined&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">class_loss&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Lb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;boundary_loss&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">num_nodes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_num_heads&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And finally, the constrained message aggregation is implemented using the following reduction function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">topk_reduce_func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nodes&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">`&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Aggregate attention-weighted messages over the top-K
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> attention-valued destination nodes
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">K&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_top_k&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nodes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mailbox&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">descending&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">[:,:&lt;/span>&lt;span class="n">K&lt;/span>&lt;span class="p">,:,:]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># message passing&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_top_k&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">u_mul_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">topk_reduce_func&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">u_mul_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;ft&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Cross-Entropy With Structure</title><link>https://kristianeschenburg.netlify.app/post/structured-cross-entropy/</link><pubDate>Wed, 09 Dec 2020 01:12:32 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/structured-cross-entropy/</guid><description>&lt;p>As I mentioned in my previous
&lt;a href="https://kristianeschenburg.netlify.app/post/gaussian-kernel-convolution/">post&lt;/a>, I work with cortical surface segmentation data. Due to the biology of the human brain, there is considerable reproducible structure and function across individuals (thankfully!). One manifestation of this reproducibility is exemplified by the neocortex a.k.a. the thin (~2.5mm) gray matter layer of cell-bodies at the periphery of the brain. The neocortex is well known to have local homogeneity in terms of types of neuronal cells, protein and gene expression, and large-scale function, for example. Naturally, researchers have been trying to identify discrete delineations of the cortex for nearly 100 years, by looking for regions of local homogeneity of various features along the cortical manifold.&lt;/p>
&lt;p>As in my previous post, I&amp;rsquo;m working on this problem using graph convolution networks (GCN). Given the logits output by a forward pass of a GCN, I want to classify a cortical node as belonging to some previously identified cortical area. Using categorical cross-entropy, we can calculate the loss of a given forward pass of the model $h(X; \Theta)$:&lt;/p>
&lt;p>$$
\begin{align}
L = -\sum_{k=1}^{K} \sum_{l \in \mathcal{L}} x_{l}^{k} \cdot log(\sigma(x^{k})_{l})
\end{align}
$$&lt;/p>
&lt;p>where $x^{k}$ is the output of the model for a single node, $x_{l}^{k}$ is the one-hot-encoding value of the true labels, and $\sigma$ is the softmax function. Importantly, the cross-entropy cost is high when the probability assigned to the true label of a node is small i.e. $log(0) = \infty$, while $log(1) = 0$ &amp;ndash; as such, the cross-entropy tries to minimize the rate of false negatives.&lt;/p>
&lt;p>However, we can incorporate more &lt;em>structure&lt;/em> into this loss function. As I mentioned previously, we know that the brain is highly reproducible across individuals. In our case, we have years of biological evidence pointing to the fact that functional brain areas i.e. like the primary visual area (V1), will always be in the same anatomical location i.e. posterior occipital cortex &amp;ndash; and will always be adjacent to a small subset of other functionally-defined areas, like the secondary visual area (V2), for example.&lt;/p>
&lt;figure id="figure-various-maps-of-the-primate-visual-cortex--tootell-et-al-2003httpswwwjneurosciorgcontent23103981">
&lt;a data-fancybox="" href="https://www.jneurosci.org/content/jneuro/23/10/3981/F1.large.jpg?width=800&amp;amp;height=600&amp;amp;carousel=1" data-caption="Various maps of the primate visual cortex.
Tootell et al, 2003.">
&lt;img src="https://www.jneurosci.org/content/jneuro/23/10/3981/F1.large.jpg?width=800&amp;amp;height=600&amp;amp;carousel=1" alt="" >
&lt;/a>
&lt;figcaption>
Various maps of the primate visual cortex.
&lt;a href="https://www.jneurosci.org/content/23/10/3981" target="_blank" rel="noopener">Tootell et al, 2003&lt;/a>.
&lt;/figcaption>
&lt;/figure>
&lt;p>This leads us to the idea of assigning a high cost when nodes which should be in V1, for example, are assigned labels of regions that are not adjacent to V1. We do so by defining another cost function:&lt;/p>
&lt;p>\begin{align}
G = -\sum_{k=1}^{k}\sum_{l \in \mathcal{L}} \sum_{h \in \mathcal{L} \setminus \mathcal{N_{l}}} w_{l}^{k} \cdot log(1-\sigma(x^{k})_{l})
\end{align}&lt;/p>
&lt;p>where $w_{l}^{k}$ is the probability weight assigned to label $h \in \mathcal{L}\setminus \mathcal{N_{l}}$ i.e. the set of labels not adjacent to label $l$. In order to follow the idea of a cross-entropy, we enforce the following constraints on weights $\mathbf{w}$:&lt;/p>
&lt;p>$$
\begin{align}
w_{l}^{k} &amp;gt;&amp;amp;= 0 \\
\sum_{l \in \mathcal{L}} w_{l}^{k} &amp;amp;= 1
\end{align}
$$&lt;/p>
&lt;p>such that the vector $\mathbf{w}$ is a probability distribution over labels. Importantly, if we consider more closely what this loss-function is doing, we are encouraging the predicted label of $x^{k}$ to &lt;strong>not&lt;/strong> be in the set $\mathcal{L} \setminus \mathcal{N_{l}}$. Assume, for example, that the true label of $x^{k}$ is $t$, and that label $j$ is not adjacent to label $t$ on the cortical surface. If the softmax function assigns a probability $p(x^{k}&lt;em>{l} = j) = 0.05$, then $log(1-p(x^{k}&lt;/em>{l} = j))$ will be small. However, if $p(x^{k}&lt;em>{l} = j) = 0.95$, then $log(1-p(x^{k}&lt;/em>{l} = j))$ will be large. Consequently, we penalize higher probabilities assigned to labels not adjacent to our true label &amp;ndash; i.e. ones that are not even biologically plausible. If a candidate label of $x^{k}&lt;em>{l} \in \mathcal{N&lt;/em>{t}}$, we simply set $w_{l}^{k} = 0$ &amp;ndash; that is, we do not penalize the true label (obviously), or labels adjacent to the true label, since these are the regions we really want to consider.&lt;/p>
&lt;p>Below, I&amp;rsquo;ve implemented this loss function using
&lt;a href="https://pytorch.org/" target="_blank" rel="noopener">Pytorch&lt;/a> and
&lt;a href="https://www.dgl.ai/" target="_blank" rel="noopener">Deep Graph Library&lt;/a>. Assume that we are given the adjacency matrix of our mesh, the logits of our model, and the true label of our training data:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">dgl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">dgl.function&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">fn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn.functional&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">th&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">structured_cross_entropy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">logits&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Compute a structured cross-entropy loss.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Loss penalizes high logit probabilities assigned to labels
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> that are not directly adjacent to the true label.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Parameters:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - - - - -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> graph: DGL graph
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> input graph structure
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> input: torch tensor
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> logits from model
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> target: torch tensor
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> true node labeling
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - - - -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> loss: torch tensor
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> structured cross-entropy loss
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># compute one-hot encoding of true labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hot_encoding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">one_hot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># identify adjacent labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hot_encoding&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">adjacency_matrix&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">hot_encoding&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">weight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">weight&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># compute inverted encoding (non-adjacent labels receive value of 1)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inv_encoding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">weight&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># weight by 1/(# non adjacent)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># all non-adjacent labels receive the same probability&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># adjacent labels and self-label receive probability of 0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inv_encoding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">inv_encoding&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">inv_encoding&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">inv_encoding&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">softmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logits&lt;/span>&lt;span class="p">)),&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">loss&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mean&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we wanted to use this loss function in conjunction with another loss, like the usual cross-entropy, we could perform something like the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># define a regularizing parameter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gamma&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># define the usual cross-entropy loss function&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">loss_fcn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CrossEntropyLoss&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">loss&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">loss_function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logits&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">gamma&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">structured_cross_entropy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">logits&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># because our new loss functions performs computations using Pytorch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># the computation history is stored, and we can compute the gradient &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># with respect to this combined loss as&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">optimizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">zero_grad&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># zero the gradients (no history)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">loss&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">backward&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># compute new gradients&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">optimizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">step&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># update weights and parameters w.r.t new gradient&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Because we&amp;rsquo;re optimizing two loss functions now i.e. the global accuracy of the model as defined using the conventional cross-entropy, &lt;strong>and&lt;/strong> the desire for predicted labels to &lt;em>not&lt;/em> be far away from the true label using the structured cross-entropy, this combination of loss functions will likely have the effect of slightly reducing global accuracy &amp;ndash; however, it will have the effect of generating predictions showing fewer anatomically spurious labels i.e. we are less likely to see vertices in the frontal lobe labeled as V1, or vertices in the lateral parietal cortex labeled as Anterior Cingulate. Global predictions will be more biologically plausible. While GCNs as a whole are already better able to incorporate local spatial information than other models due to the fact that they convolve signals based on the adjacency structure of the network in question, I have found empirically that these anatomically spurious predictions are still possible &amp;ndash; hence the need for this more-structured regularization.&lt;/p></description></item><item><title>Gaussian Graph Convolutional Networks</title><link>https://kristianeschenburg.netlify.app/post/gaussian-kernel-convolution/</link><pubDate>Mon, 07 Dec 2020 23:24:17 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/gaussian-kernel-convolution/</guid><description>&lt;p>I&amp;rsquo;m using
&lt;a href="https://tkipf.github.io/graph-convolutional-networks/" target="_blank" rel="noopener">graph convolutional networks&lt;/a> as a tool to segment the cortical surface of the brain. This research resides in the domain of &lt;em>node classification&lt;/em> using &lt;em>inductive learning&lt;/em>. By node classification, I mean that we wish to assign a discrete label to cortical surface locations (nodes / vertices in a graph) on the basis of some feature data and brain network topology. By inductive learning, I mean that we will train, validate, and test on datasets with possibly different graph topologies &amp;ndash; this is in contrast to &lt;em>transductive learning&lt;/em> that learns models that do not generalize to arbitrary network topology.&lt;/p>
&lt;p>In conventional convolutions over regular grid domains, such as images, using approaches like
&lt;a href="https://en.wikipedia.org/wiki/Convolutional_neural_network" target="_blank" rel="noopener">ConvNet&lt;/a>, we learn the parameters of a sliding filter that convolves the signal around a pixel of interest $p_{i,j}$, such that we aggregate the information from pixels $p_{\Delta i, \Delta j}$ for some fixed distance $\Delta$ away from $p$. Oftentimes, however, we encounter data that is distributed over a graphical domain, such as social networks, journal citations, brain connectivity, or the electrical power grid. In such cases, concepts like &amp;ldquo;up&amp;rdquo;, &amp;ldquo;down&amp;rdquo;, &amp;ldquo;left&amp;rdquo;, and &amp;ldquo;right&amp;rdquo; do not make sense &amp;ndash; what does it mean to be &amp;ldquo;up&amp;rdquo; from something in a network? &amp;ndash; so we need some other notion of neighborhood.&lt;/p>
&lt;p>In come graph convolutional networks (GCNs). GCNs generalize the idea of neighborhood aggregation to the graph domain by utilizing the adjacency structure of a network &amp;ndash; we can now aggregate signals near a node by using some neighborhood around it. While vanilla GCNs learn rotationally-invariant filters, recent developments in the world of
&lt;a href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener">Transformer networks&lt;/a> have opened up the door for much more flexible and inductive models (see:
&lt;a href="https://arxiv.org/abs/1710.10903" target="_blank" rel="noopener">Graph Attention Networks&lt;/a>,
&lt;a href="https://cs.stanford.edu/people/jure/pubs/graphsage-nips17.pdf" target="_blank" rel="noopener">GraphSAGE&lt;/a>).&lt;/p>
&lt;figure id="figure-demonstration-of-graph-convolution-network-from-thomas-kipfhttpstkipfgithubiograph-convolutional-networks">
&lt;a data-fancybox="" href="https://tkipf.github.io/graph-convolutional-networks/images/gcn_web.png" data-caption="Demonstration of graph convolution network from
Thomas Kipf.">
&lt;img src="https://tkipf.github.io/graph-convolutional-networks/images/gcn_web.png" alt="" >
&lt;/a>
&lt;figcaption>
Demonstration of graph convolution network from
&lt;a href="https://tkipf.github.io/graph-convolutional-networks/" target="_blank" rel="noopener">Thomas Kipf&lt;/a>.
&lt;/figcaption>
&lt;/figure>
&lt;p>I was specifically interested in applying the methodology described
&lt;a href="http://arxiv.org/abs/1803.10336" target="_blank" rel="noopener">here&lt;/a>, where the authors utilize Gaussian kernels as filters over the neighborhood of nodes. However, the authors did not open-source their code &amp;ndash; as such, I needed to implement this method myself. Assume our input data to layer $l$ is $Y^{(l)} \in \mathbb{R}^{N \times q}$ for $N$ nodes in the graph. We can define the Gaussian kernel-weighted convolution as follows:&lt;/p>
&lt;p>$$
\begin{align}
z_{i,p}^{(l)} = \sum_{j \in \mathcal{N}&lt;em>{i}} \sum&lt;/em>{q=1}^{M_{(l)}} \sum_{k=1}^{K_{(l)}} w_{p,q,k}^{(l)} \cdot y_{j,q}^{(l)} \cdot \phi(\hat{\mu}&lt;em>{i}, \hat{\mu}&lt;/em>{j}; \Theta_{k}^{(l)}) + b_{p}^{(l)}
\end{align}
$$&lt;/p>
&lt;p>Above, $y_{j,q}^{(l)}$ is the $q$-th input feature of neighboring node $j$, $w_{p,q,k}^{(l)}$ is the linear weight assigned to this feature for the $k$-th kernel, and $\phi(\hat{\mu}&lt;em>{i}, \hat{\mu}&lt;/em>{j}; \Theta_{k}^{(l)})$ is the $k$-th kernel weight between node $i$ and node $j$, defined as:&lt;/p>
&lt;p>$$
\begin{align}
\phi(\hat{\mu_{i}}, \hat{\mu_{j}}; \sigma_{k}^{(l)}, \mu_{k}^{(l)} ) = \exp^{-\sigma_{k}^{(l)} \left\Vert (\hat{\mu_{i}} - \hat{\mu_{j}}) - \mu_{k}^{(l)} \right\Vert^{2}}
\end{align}
$$&lt;/p>
&lt;p>Extrinsically, the kernel weights are represented by edges in a sparse affinity matrix, such that index $(i,j)$ is the Gaussian kernel weight between node $i$ and node $j$ for the $k$-th kernel in the $l$-th layer, where nodes $j$ are restricted to be within a certain neighborhood or distance of node $i$. This can be seen more clearly here:&lt;/p>
&lt;figure id="figure-figure-from-wu-et-alhttpswwwncbinlmnihgovpmcarticlespmc7052684--v_i-is-our-voxel-of-interest-and-v_ki-for-demonstration-purposes-is-an-adjacent-node--both-v_i-and-v_ki-are-characterized-by-embedding-vectors-e_i-e_ki-in-mathbbrq-from-which-we-compute-the-kernel-weight-phi_ik-characterizing-how-similar-the-two-vertices-embedding-vectors-are">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/gaussian-kernel-convolution/gaussian_radius_hu_c3f52ebffda688df.png" data-caption="Figure from
Wu et al.. $v_{i}$ is our voxel of interest, and $v_{k}^{i}$, for demonstration purposes, is an adjacent node. Both $v_{i}$ and $v_{k}^{i}$ are characterized by embedding vectors $e_{i}, e_{k}^{i} \in \mathbb{R}^{q}$, from which we compute the kernel weight $\phi_{i,k}$ characterizing how similar the two vertices&amp;rsquo; embedding vectors are.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/gaussian-kernel-convolution/gaussian_radius_hu_c3f52ebffda688df.png" class="lazyload" alt="" width="330" height="220">
&lt;/a>
&lt;figcaption>
Figure from
&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7052684/" target="_blank" rel="noopener">Wu et al.&lt;/a>. $v_{i}$ is our voxel of interest, and $v_{k}^{i}$, for demonstration purposes, is an adjacent node. Both $v_{i}$ and $v_{k}^{i}$ are characterized by embedding vectors $e_{i}, e_{k}^{i} \in \mathbb{R}^{q}$, from which we compute the kernel weight $\phi_{i,k}$ characterizing how similar the two vertices&amp;rsquo; embedding vectors are.
&lt;/figcaption>
&lt;/figure>
&lt;p>I implemented a new convolutional layer called &lt;code>GAUSConv&lt;/code> (available
&lt;a href="https://github.com/kristianeschenburg/parcellearning/blob/master/parcellearning/layers/gausconv.py" target="_blank" rel="noopener">here&lt;/a>). To implement this algorithm, I utilized the
&lt;a href="https://www.dgl.ai/" target="_blank" rel="noopener">Deep Graph Library&lt;/a> (DGL), which offers a stellar single unified API based on message passing (I&amp;rsquo;m using
&lt;a href="https://pytorch.org/" target="_blank" rel="noopener">Pytorch&lt;/a> as the backend). I noticed that I could formulate this problem using attention mechanisms described in the
&lt;a href="https://arxiv.org/abs/1710.10903" target="_blank" rel="noopener">Graph Attention Network&lt;/a> paper &amp;ndash; however, instead of computing attention weights using a fully connected layer as described in that work, I would compute kernel weights using Gaussian filters. Similarly, just as the GAT paper describes &lt;em>multi-head attention&lt;/em> for multiple attention channels, I could analogize my formulation to &lt;em>multi-head kernels&lt;/em> for multiple kernel channels. To this end, I could make use of the
&lt;a href="https://github.com/dmlc/dgl/blob/master/python/dgl/nn/pytorch/conv/gatconv.py" target="_blank" rel="noopener">&lt;code>GATConv&lt;/code>&lt;/a> API quite easily by replacing the attention computations with the Gaussian kernel filtrations. Likewise, I utilized the
&lt;a href="https://github.com/dmlc/dgl/blob/master/python/dgl/nn/pytorch/conv/graphconv.py" target="_blank" rel="noopener">&lt;code>GraphConv&lt;/code>&lt;/a> API to incorporate linear weights from the
&lt;a href="https://arxiv.org/pdf/1609.02907.pdf" target="_blank" rel="noopener">Graph Convolution Network&lt;/a> paper.&lt;/p>
&lt;p>The &lt;code>GAUSConv&lt;/code> layer is similar to both the &lt;code>GraphConv&lt;/code> and &lt;code>GATConv&lt;/code> layers but differs in a few places. Rather than initializing the layer with attention heads, we initialize it with the number of kernels and a kernel dropout probability.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">GAUSConv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in_feats&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># number of input dimensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out_feats&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># number of output features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># number of kernels for current layer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">feat_drop&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># dropout probability of features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kernel_drop&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># dropout probability of kernels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">negative_slope&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># leakly relu slope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">activation&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># activation function to apply after forward pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">random_seed&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># for example / reproducibility purposes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">allow_zero_in_degree&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Importantly, in the layer instantiation, we define &lt;strong>linear weights&lt;/strong> &lt;em>and&lt;/em> &lt;strong>kernel mean and sigma parameters&lt;/strong>, &lt;code>mu&lt;/code> and &lt;code>sigma&lt;/code>. We initialize both kernel parameters with the flag &lt;code>require_grad=True&lt;/code>, which enables us to update these kernel parameters during the backward pass of the layer. Both parameters are initialized with values in the &lt;code>reset_parameters&lt;/code> method.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># initialize feature weights and bias vector&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">weights&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">in_feats&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">out_feats&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">requires_grad&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bias&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">out_feats&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">requires_grad&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># initialize kernel perameters&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">in_feats&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">requires_grad&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sigma&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">requires_grad&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now here is the clever part, and where the
&lt;a href="https://docs.dgl.ai/en/0.4.x/api/python/function.html" target="_blank" rel="noopener">DGL message passing interface&lt;/a> really shines through. DGL fuses the &lt;code>send&lt;/code> and &lt;code>receive&lt;/code> messages so that no messages between nodes are ever explicitly stored, using built-in &lt;strong>message&lt;/strong> and &lt;strong>reduce&lt;/strong> functions. To compute the kernel weights between all pairs of source and destination nodes, we use these built-in functions. The important steps are:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>compute node feature differences between all source / destination node pairs&lt;/p>
&lt;/li>
&lt;li>
&lt;p>aggregate and reduce incoming messages from destination nodes scaled by the kernel weights, to update the source node features&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>In the forward pass of our layer, we perform the following steps:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">### forward pass of GAUSConv layer ###&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># compute all pairwise differences between adjacent node features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">feat&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply_edges&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">u_sub_v&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;diff&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># compute kernel weights for each source / desintation pair&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;diff&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">mu&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">sigma&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unsqueeze&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exp&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">e&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># apply kernel weights to destination node features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply_edges&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">v_mul_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;e&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;kw&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># apply linear projection to kernel-weighted destination node features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;kw&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">weights&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># apply kernel dropout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kernel_drop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">edata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># final message-passing and reduction step&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># aggregate weighted destination node features to update source node features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copy_e&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">fn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;m&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rst&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ndata&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;h&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As an example, given a graph and features, we instantiate a &lt;code>GAUSConv&lt;/code> layer and propagate our features through the network via:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># set random seed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">random_seed&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># define arbitrary input/output feature shape&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">n_samples&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">in_feats&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">out_feats&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">features&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ones&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_samples&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">in_feats&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># define number of kernels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">num_kernels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># create graph structure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">u&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tensor&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]),&lt;/span> &lt;span class="n">th&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tensor&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dgl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dgl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_bidirected&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dgl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_self_loop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># instantiate layer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">GausConv&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">GAUSConv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">in_feats&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">in_feats&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out_feats&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">out_feats&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">random_seed&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">random_seed&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">num_kernels&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">num_kernels&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">feat_drop&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">kernel_drop&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># forward pass of layer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">logits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">GausConv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">features&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tensor&lt;/span>&lt;span class="p">([[&lt;/span>&lt;span class="mf">0.1873&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.7217&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="mf">0.1405&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.5413&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="mf">0.0936&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.3608&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="mf">0.1405&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.5413&lt;/span>&lt;span class="p">]],&lt;/span> &lt;span class="n">grad_fn&lt;/span>&lt;span class="o">=&amp;lt;&lt;/span>&lt;span class="n">AddBackward0&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item></channel></rss>