GCN : Graph Convolutional Networks — Guide Complet des Réseaux de Convolution sur Graphes
Les GCN (Graph Convolutional Networks) représentent une avancée majeure dans le domaine du deep learning appliqué aux données structurées en graphes. Alors que les réseaux de neurones classiques traitent des données vectorielles et que les CNN opèrent sur des grilles régulières (images, séquences textuelles), les GCN étendent le principe de convolution aux structures non-euclidiennes. Ce guide explorera en profondeur le fonctionnement mathématique des GCN, leur intuition fondamentale, leur implémentation pratique en Python avec PyTorch, ainsi que leurs cas d’usage concrets.
Résumé
Un GCN est un type de réseau de neurones spécialement conçu pour traiter des données représentées sous forme de graphes. Contrairement aux architectures traditionnelles qui supposent une structure de données régulière (comme les pixels d’une image ou les mots d’une phrase), les GCN exploitent les relations explicites entre entités pour propager et transformer l’information à travers le graphe.
Le mécanisme central repose sur l’agrégation de voisinage : chaque noeud collecte les informations de ses voisins directs, les combine avec ses propres caractéristiques, puis applique une transformation linéaire suivie d’une fonction d’activation. En empilant plusieurs couches de ce type, un GCN capture progressivement des structures de plus en plus larges dans le graphe.
Ces modèles ont démontré leur efficacité dans de nombreux domaines : classification de molécules en chimie, détection de communautés dans les réseaux sociaux, recommandation de produits, ou encore l’analyse de connaissances via des graphes de savoir. Nous verrons dans ce guide comment les construire, les entraîner et les déployer.
Principe Mathématique des GCN
Le fondement théorique des GCN repose sur une formule élégante qui généralise la convolution aux graphes. L’équation de propagation d’une couche GCN s’écrit :
H^(l+1) = σ(D̃^(-1/2) · Ã · D̃^(-1/2) · H^(l) · W^(l))
Décomposons chaque terme de cette équation fondamentale :
Définitions détaillées des composantes
- Ã = A + I : c’est la matrice d’adjacence avec self-loops. On ajoute la matrice identité I à la matrice d’adjacence A originale du graphe. Cette opération cruciale permet à chaque noeud de conserver une part de ses propres caractéristiques lors de l’agrégation, au lieu de ne recevoir que les informations de ses voisins. Sans ces self-loops, un noeud perdrait totalement son identité propre à chaque étape de propagation.
- D̃ : c’est la matrice des degrés de Ã. Il s’agit d’une matrice diagonale où chaque élément D̃_ii correspond au nombre de connexions du noeud i dans le graphe augmenté (c’est-à-dire en comptant les self-loops). Par exemple, si un noeud a 3 voisins et un self-loop, son degré vaut 4.
- D̃^(-1/2) · Ã · D̃^(-1/2) : c’est la matrice d’adjacence normalisée symétrique. Ce terme de normalisation est essentiel : il empêche les features des noeuds très connectés de dominer l’agrégation. Sans normalisation, un noeud avec des centaines de voisins transmettrait beaucoup plus d’information qu’un noeud isolé, créant un déséquilibre nuisible à l’apprentissage. La normalisation symétrique assure que chaque voisin contribue proportionnellement à son degré dans le graphe.
- H^(l) : c’est la matrice des features au niveau l. Chaque ligne de cette matrice correspond à un noeud du graphe, et chaque colonne représente une dimension de caractéristique. Pour la première couche (l=0), H^(0) est simplement la matrice des features d’entrée X. Pour les couches suivantes, H^(l) représente les embeddings appris par les couches précédentes.
- W^(l) : c’est la matrice des poids apprise. C’est le seul véritable paramètre d’apprentissage de chaque couche GCN. Cette matrice est optimisée par rétropropagation du gradient pendant l’entraînement, exactement comme les poids d’un réseau de neurones classique.
- σ : c’est une fonction d’activation non linéaire, typiquement ReLU (f(x) = max(0, x)). Cette non-linéarité est indispensable pour que le modèle puisse apprendre des représentations complexes. Si toutes les couches étaient linéaires, un GCN profond se réduirait à une seule transformation linéaire, annulant tout l’intérêt de l’architecture profonde.
Interprétation intuitive de la propagation
Intuitivement, le fonctionnement d’un GCN se décompose en deux étapes claires à chaque couche :
- Agrégation : chaque noeud agrège les features de ses voisins directs via le produit à · H^(l). Concrètement, pour un noeud donné, on calcule la somme (pondérée par la normalisation) des vecteurs de caractéristiques de tous ses voisins connectés.
- Transformation : l’agrégation est ensuite transformée linéairement par W^(l), puis passe à travers la fonction d’activation σ. Cette transformation permet de projeter les informations agrégées dans un nouvel espace de représentation plus utile pour la tâche visée.
En empilant plusieurs couches, l’information se propage de proche en proche : après une couche, un noeud connaît ses voisins directs ; après deux couches, il connaît les voisins de ses voisins ; après k couches, il capture un rayon de k sauts autour de lui dans le graphe. C’est l’équivalent graphique du champ récepteur dans les CNN classiques.
Intuition Géométrique : Pourquoi la Convolution sur des Graphes ?
Sur une image classique, la convolution agrège les pixels voisins dans une grille régulière. Un filtre 3×3 regarde toujours exactement les 8 pixels entourant le pixel central. La géométrie est fixe, prévisible, et identique partout dans l’image. Chaque pixel a exactement 4 ou 8 voisins, selon que l’on considère la connectivité de von Neumann ou de Moore.
Mais que faire si les données ne sont pas sur une grille ? Imaginez les situations suivantes :
- Atomes dans une molécule : chaque atome est connecté à un nombre variable d’autres atomes. L’hydrogène forme une seule liaison, le carbone en forme quatre. Il n’y a pas de haut, bas, gauche ou droite. La structure est un graphe libre sans orientation privilégiée.
- Utilisateurs dans un réseau social : certains ont des milliers d’amis, d’autres seulement quelques-uns. Les connexions sont irrégulières et asymétriques. Une personne populaire transmettra de l’information à un grand nombre de noeuds, tandis qu’une personne isolée ne communiquera qu’avec ses quelques contacts.
- Articles scientifiques liés entre eux par des citations : un article fondateur peut être cité des milliers de fois, tandis qu’un article récent n’en cite que quelques-uns. Le réseau de citations forme un graphe orienté avec une distribution de degrés très inégale.
Dans tous ces cas, l’outil naturel pour représenter les données est le graphe : un ensemble de noeuds (les entités) reliés par des arêtes (les relations). La topologie du graphe capture les dépendances structurelles que ni une liste ni une matrice ne peuvent exprimer naturellement.
Le GCN est une convolution qui s’adapte à la forme du graphe : il ne regarde que les voisins directs, peu importe combien il y en a ou comment ils sont disposés. C’est cette flexibilité structurelle qui rend les GCN si puissants. Là où un CNN exigerait de forcer les données dans une grille artificielle (avec perte d’information relationnelle), un GCN respecte la topologie naturelle des données.
Analogie directe avec les CNN
Pensez à la différence fondamentale entre les deux architectures :
- CNN : « Regarde les 3 pixels à gauche, les 3 à droite, les 3 au-dessus, les 3 en dessous. » — grille fixe et universelle.
- GCN : « Regarde tous tes voisins, quel que soit leur nombre et leur position. » — grille adaptée au graphe.
Les deux opérations accomplissent la même chose conceptuellement (l’agrégation du voisinage), mais le GCN le fait sur une topologie arbitraire, non régulière, et potentiellement dynamique. Cette généralisation est au coeur de l’apport de Kipf et Welling dans leur article fondateur de 2017.
Implémentation Python avec PyTorch
Nous allons explorer deux approches d’implémentation : d’abord une version from scratch sans dépendance spécialisée, puis une version avec PyTorch Geometric.
1. Couche GCN From Scratch
Voici l’implémentation d’une seule couche GCN en PyTorch pur, sans aucune bibliothèque spécialisée :
import torch
import torch.nn as nn
import torch.nn.functional as F
class GCNLayer(nn.Module):
"""
Une couche unique de Graph Convolutional Network.
Propagation: H' = sigma(D_tilde^(-1/2) . A_tilde . D_tilde^(-1/2) . H . W)
Arguments:
in_features : dimension d'entree des features
out_features : dimension de sortie de la couche
"""
def __init__(self, in_features, out_features):
super().__init__()
# Matrice des poids apprise (parametre d'apprentissage)
self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features))
# Initialisation Xavier pour une convergence stable
nn.init.xavier_uniform_(self.weight)
def forward(self, x, adj_normalized):
"""
Forward pass de la couche GCN.
Arguments:
x : features des noeuds, tenseur (N, in_features)
adj_normalized : matrice d'adjacence normalisee (N, N)
Retour:
features transformees (N, out_features) apres ReLU
"""
# Etape 1 : transformation lineaire H . W
support = torch.matmul(x, self.weight)
# Etape 2 : aggregation des voisins via matrice normalisee
output = torch.spmm(adj_normalized, support)
# Fonction d'activation non lineaire
return F.relu(output)
Et voici la fonction utilitaire pour construire la matrice d’adjacence normalisée symétrique à partir d’une matrice d’adjacence brute :
def build_normalized_adj(adj_matrix):
"""
Calcule la matrice d'adjacence normalisee symetrique.
Formule: D_tilde^(-1/2) . A_tilde . D_tilde^(-1/2) ou A_tilde = A + I
Arguments:
adj_matrix : matrice d'adjacence (N, N)
Retour:
matrice normalisee (N, N) prete pour la propagation GCN
"""
N = adj_matrix.shape[0]
identity = torch.eye(N, device=adj_matrix.device)
# Ajouter les self-loops: A_tilde = A + I
adj_with_selfloops = adj_matrix + identity
# Calculer les degres de A_tilde (somme par ligne)
degrees = torch.sum(adj_with_selfloops, dim=1)
# Calculer D_tilde^(-1/2) element par element
degrees_inv_sqrt = torch.pow(degrees, -0.5)
# Proteger contre les divisions par zero (noeuds isoles)
degrees_inv_sqrt[torch.isinf(degrees_inv_sqrt)] = 0
# Construire la matrice diagonale D_tilde^(-1/2)
D_inv_sqrt = torch.diag(degrees_inv_sqrt)
# Normalisation symetrique complete
normalized_adj = torch.mm(
torch.mm(D_inv_sqrt, adj_with_selfloops),
D_inv_sqrt
)
return normalized_adj
2. Modèle GCN Complet pour la Classification de Noeuds (Cora)
Construisons maintenant un modèle GCN à deux couches pour la tâche classique de classification de noeuds sur le dataset Cora (2 708 articles scientifiques, 5 429 citations, 7 catégories thématiques) :
import torch
import torch.nn as nn
import torch.nn.functional as F
class TwoLayerGCN(nn.Module):
"""
Modele GCN a deux couches pour la classification de noeuds.
Architecture classique: Input -> GCN -> ReLU -> Dropout -> GCN -> LogSoftmax
"""
def __init__(self, num_features, num_hidden, num_classes, dropout=0.5):
super().__init__()
self.gc1 = GCNLayer(num_features, num_hidden)
self.gc2 = GCNLayer(num_hidden, num_classes)
self.dropout = nn.Dropout(dropout)
def forward(self, x, adj_normalized):
# Premiere couche : aggregation + ReLU + dropout
x = self.gc1(x, adj_normalized)
x = F.relu(x)
x = self.dropout(x)
# Deuxieme couche : aggregation finale
x = self.gc2(x, adj_normalized)
# LogSoftmax pour la classification multi-classe
return F.log_softmax(x, dim=1)
Boucle d’entraînement complète :
def train_gcn(model, features, adj_normalized, labels,
train_mask, num_epochs=200, lr=0.01, weight_decay=5e-4):
"""
Boucle d'entrainement complete pour un modele GCN.
Arguments:
model : le modele GCN a entrainer
features : matrice X des features des noeuds (N, F)
adj_normalized : matrice d'adjacence normalisee (N, N)
labels : etiquettes de classification (N,)
train_mask : masque booleen des noeuds d'entrainement
num_epochs : nombre d'epoques d'entrainement
lr : taux d'apprentissage
weight_decay : regularisation L2
"""
optimizer = torch.optim.Adam(
model.parameters(), lr=lr, weight_decay=weight_decay
)
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
# Forward pass
output = model(features, adj_normalized)
# Calcul de la loss uniquement sur les noeuds d'entrainement
loss = F.nll_loss(output[train_mask], labels[train_mask])
# Backward pass + optimisation
loss.backward()
optimizer.step()
# Evaluation periodique
if (epoch + 1) % 20 == 0:
model.eval()
with torch.no_grad():
output = model(features, adj_normalized)
preds = output.argmax(dim=1)
accuracy = (preds == labels).float().mean()
print(f"Epoch {epoch+1}/{num_epochs} -- "
f"Loss: {loss.item():.4f} -- "
f"Precision globale: {accuracy:.4f}")
return model
3. Implémentation avec PyTorch Geometric
PyTorch Geometric (PyG) est la bibliothèque de référence pour le deep learning sur graphes. Elle simplifie considérablement le code et offre des optimisations de performance :
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
class GCNWithPyG(torch.nn.Module):
"""
Modele GCN utilisant PyTorch Geometric.
Beaucoup plus concis et optimise que l'implementation manuelle.
"""
def __init__(self, num_features, num_hidden, num_classes):
super().__init__()
# Le parametre cached=True evite de recalculer la normalisation
self.conv1 = GCNConv(num_features, num_hidden, cached=True)
self.conv2 = GCNConv(num_hidden, num_classes, cached=True)
self.dropout = 0.5
def forward(self, x, edge_index):
"""
Arguments:
x : features des noeuds (N, num_features)
edge_index : liste des aretes au format COO (2, E)
"""
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)
# ---- Chargement du dataset et entrainement ----
dataset = Planetoid(root="/tmp/Cora", name="Cora")
data = dataset[0] # Un seul graphe transductif
model = GCNWithPyG(
num_features=dataset.num_features,
num_hidden=16,
num_classes=dataset.num_classes
)
optimizer = torch.optim.Adam(
model.parameters(), lr=0.01, weight_decay=5e-4
)
for epoch in range(200):
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
model.eval()
with torch.no_grad():
pred = out.argmax(dim=1)
acc = (pred == data.y).float().mean()
print(f"Epoch {epoch+1} -- Loss: {loss:.4f} -- Precision: {acc:.4f}")
PyTorch Geometric gère automatiquement la construction de la matrice d’adjacence normalisée, le format de stockage sparse pour les gros graphes, et de nombreuses optimisations de performance basées sur du code C++/CUDA. Pour des graphes de grande taille (au-delà de 50 000 noeuds), il est fortement recommandé d’utiliser cette bibliothèque plutôt qu’une implémentation manuelle.
Hyperparamètres Clés des GCN
Le réglage des hyperparamètres est crucial pour obtenir de bonnes performances avec un GCN. Voici les trois hyperparamètres fondamentaux à comprendre :
num_layers (Nombre de couches) — Typiquement 2-3
C’est l’hyperparamètre le plus surprenant des GCN. Contrairement aux CNN classiques où l’on empile des dizaines de couches pour obtenir de meilleures performances, les GCN fonctionnent généralement mieux avec seulement 2 ou 3 couches.
Pourquoi si peu ? À cause du problème de sur-lissage (over-smoothing) : à chaque couche, les représentations des noeuds se mélangent avec celles de leurs voisins. Au-delà de 3 ou 4 couches, les embeddings de tous les noeuds tendent à converger vers des vecteurs quasi-identiques, rendant la classification impossible. C’est un phénomène fondamentalement différent du vanishing gradient des réseaux profonds classiques.
Certaines architectures récentes (GCN avec residual connections, Jumping Knowledge Networks, GCNII) tentent de pallier ce problème et permettent d’aller plus loin en profondeur, mais pour un GCN standard, 2 couches restent le choix par défaut recommandé par Kipf & Welling dès 2017.
hidden_dim (Dimension cachée) — Typiquement 16-256
La taille de la couche cachée contrôle la richesse de la représentation apprise par le modèle. Une dimension trop faible (4-8) ne capturera pas assez d’information relationnelle entre les noeuds. Une dimension trop grande (512 et plus) risque de surapprendre (overfitting), surtout si le dataset est petit comme Cora avec seulement 2 708 noeuds et 140 exemples d’entraînement par classe.
Règle empirique : pour un premier essai, commencez avec hidden_dim = 16 (comme dans l’article original) et augmentez progressivement si les performances sur la validation sont insuffisantes.
dropout — Typiquement 0.3-0.6
Le dropout est encore plus important dans les GCN que dans les réseaux classiques, car les GCN ont tendance à surapprendre rapidement sur des graphes de petite taille avec peu d’exemples étiquetés. Une valeur de 0.5 est le point de départ recommandé. Sur des graphes plus grands (100 000 noeuds et plus), vous pouvez réduire à 0.2-0.3 puisque le risque de surapprentissage diminue avec la quantité de données.
Avantages et Limites des GCN
Avantages
- Exploitation native des relations structurelles : contrairement aux autres architectures de deep learning, les GCN utilisent explicitement la structure du graphe plutôt que de la traiter comme un artefact secondaire. Les relations entre entités sont au coeur du modèle, pas un ajout a posteriori.
- Fonctionnement transductif et inductif : les GCN peuvent fonctionner dans les deux modes. En mode transductif, le modèle apprend sur un graphe fixe et prédit sur les noeuds de ce même graphe (comme la classification sur Cora). En mode inductif (avec des variantes comme GraphSAGE ou les GCN basés sur l’échantillonnage de voisins), le modèle peut généraliser à des noeuds jamais vus pendant l’entraînement.
- Efficacité paramétrique remarquable : un GCN à deux couches sur Cora possède seulement quelques milliers de paramètres, comparé à des millions pour un grand CNN sur ImageNet. Cette efficacité rend les GCN accessibles même avec des ressources de calcul limitées et permet un entraînement rapide sur CPU.
- Interprétabilité supérieure aux réseaux classiques : contrairement aux boîtes noires profondes, on peut retracer quelles contributions de quels voisins mènent à quelle prédiction. Par exemple, dans un graphe de citations, on peut identifier quels articles cités ont le plus influencé la classification d’un document donné. Cette traçabilité est précieuse pour des applications critiques comme la détection de fraudes ou le diagnostic médical.
Limites
- Sur-lissage (over-smoothing) : comme mentionné précédemment, empiler plus de 3-4 couches rend les représentations des noeuds indiscernables. C’est la limitation la plus fondamentale des GCN et un domaine de recherche actif depuis 2019.
- Consommation mémoire pour les très gros graphes : la matrice d’adjacence d’un graphe à N noeuds peut nécessiter O(N²) en mémoire dans le pire cas (bien que les formats sparse réduisent cela à O(E) où E est le nombre d’arêtes). Pour des graphes à des millions de noeuds, des techniques de mini-batching, de neighbor sampling (échantillonnage de voisins) ou de gradient checkpointing sont absolument nécessaires.
- Hypothèse d’homophilie : les GCN standard supposent implicitement que les noeuds connectés sont similaires (homophilie). Dans les graphes hétérophiles (où les noeuds connectés sont structurellement dissimilaires, typiquement les graphes bipartites), les performances chutent significativement. Des variantes spécialisées comme les GCN hétérophiles (H2GCN, CPGCN) adressent ce problème en introduisant des mécanismes de séparation des voisins similaires et dissemblables.
- Graphes statiques uniquement : les GCN classiques ne gèrent pas naturellement les graphes dynamiques dont la structure (arêtes) ou les features évoluent dans le temps. Pour ces cas, il faut se tourner vers des architectures temporelles spécialisées comme les EvolveGCN, Temporal GCN, ou les Dynamic Graph Neural Networks.
- Pas de gestion native des arêtes pondérées ou typées : le GCN original traite toutes les arêtes de manière binaire (présent ou absent). Pour des graphes avec des arêtes pondérées (intensité des liens) ou typées (différents types de relations), des adaptations sont nécessaires (R-GCN pour les graphes relationnels, par exemple).
4 Cas d’Usage Concrets et Détaillés
1. Classification de Molécules en Chimie Computationnelle
En chimie computationnelle, une molécule est naturellement représentée comme un graphe : les atomes sont les noeuds et les liaisons chimiques sont les arêtes. Un GCN peut apprendre à prédire des propriétés moléculaires (toxicité, solubilité, activité biologique contre une cible protéique) directement à partir de cette structure graphique.
Chaque atome possède des features riches : type d’atome (carbone, oxygène, azote, etc.), charge électrique, degré d’hybridation, nombre d’hydrogènes implicites. Le GCN propage ces informations à travers les liaisons chimiques, permettant au modèle de capturer des motifs structuraux comme les cycles aromatiques, les groupements fonctionnels, ou les conformations spatiales.
L’avantage par rapport aux descripteurs moléculaires traditionnels (comme les empreintes ECFP de Morgan) est que le GCN apprend automatiquement les representations optimales pour la tâche, plutôt que de dépendre d’un encodage manuel par des experts.
2. Système de Recommandation Basé sur les Graphes
Dans un système de recommandation moderne, on peut construire un graphe biparti reliant les utilisateurs aux produits qu’ils ont achetés, consultés ou évalués. Un GCN sur ce graphe apprend des représentations qui capturent à la fois les préférences implicites des utilisateurs et les caractéristiques latentes des produits.
L’avantage par rapport aux méthodes classiques de matrix factorization est que le GCN capture les relations multi-sauts : « les utilisateurs qui ont aimé ce produit aiment aussi cet autre produit » — une forme de filtrage collaboratif de deuxième ordre, automatiquement déduit par la propagation. La société Pinterest utilise cette approche (via leur modèle PinSAGE) pour recommander des épingles à ses centaines de millions d’utilisateurs.
3. Détection de Fraude Financière et Cybercriminelle
Dans le domaine bancaire, on peut modéliser les relations entre comptes bancaires, transactions, adresses IP, appareils utilisés et bénéficiaires comme un graphe massif et hétérogène. Les comptes frauduleux présentent souvent des motifs structurels spécifiques : connexions à des appareils partagés par de nombreux comptes, boucles de transactions circulaires, comptes nouvellement créés interagissant intensivement avec des comptes déjà signalés.
Un GCN détecte ces motifs en apprenant des embeddings qui reflètent la position structurelle de chaque noeud dans le graphe de fraude. Les noeuds avec des embeddings « suspects » sont alors classifiés comme potentiellement frauduleux, souvent avec quelques heures d’avance sur les méthodes traditionnelles basées sur des règles if-then. Cette approche est déployée par plusieurs banques internationales et institutions de paiement.
4. Classification de Documents et Réseaux de Citations Académiques
L’exemple canonique et pédagogique des GCN est la classification de documents académiques reliés par des citations (comme le dataset Cora que nous avons utilisé dans les exemples de code). Chaque document est représenté par un sac de mots ou un embedding TF-IDF (features textuelles), et les arêtes sont les citations entre documents.
Le GCN combine les features textuelles de chaque document avec les catégories implicites révélées par les citations entre documents similaires. Intuitivement, si un document est cité par beaucoup d’articles sur l’apprentissage automatique et cite lui-même des articles sur ce sujet, le GCN renforce la probabilité qu’il appartienne à cette catégorie. Le résultat est une classification bien plus précise que l’utilisation des seules features textuelles.
Cette approche s’applique naturellement à tout type de documents liés entre eux par des relations explicites : articles juridiques citant des jurisprudence, brevets se référant à des brevets antérieurs, ou tickets de support technique liés à des incidents similaires.
Voir Aussi
- Maîtrisez le Calcul de la Somme des Diviseurs avec Python : Guide Complet et Astuces Efficaces
- Créer un Jeu de Glissement en Python : Guide Complet et Astuces pour les Développeurs

