Skip to main content

gcn-python — GCN Causal Engine (Python ML layers)

PyPI version Version Python License: MIT Tests

Extract, model and reason over causal structure in natural language and code.


What it does

gcn-python is a trainable causal extraction engine. Given a sentence, it produces a CausalIR — a typed directed graph describing who causes what, under which conditions, and with what consequences.

"Les ventes baissent car la demande recule."
          │
          ▼
CausalIR {
  nodes: [
    {type: "processus", label: "baisser(ventes)"},
    {type: "processus", label: "reculer(demande)"}
  ],
  edges: [
    {source: n001, target: n002, relation: "cause", confidence: 0.87}
  ]
}

Unlike LLM-based approaches, the output is a verifiable, serializable, queryable structure — not prose.


Installation

pip install gcn-python

# With PyTorch support (RGCNLayerPT, RGCNLayerGAT):
pip install "gcn-python[torch]"

Quick start

1. Inference with a pre-trained checkpoint

from gcn_python.layer1.features import FeatureVocabulary
from gcn_python.layer1.representation import UDRepresentation
from gcn_python.layer2.reference import MLPEncoder
from gcn_python.layer3.reference import RGCNLayer
from gcn_python.pipeline.cgnp import CGNPipeline
from gcn_python.training.checkpoint import load_checkpoint

# Build pipeline (must match checkpoint architecture)
vocab   = FeatureVocabulary()
d_eff   = vocab.d_clause          # 79 features per clause
d_edge  = vocab.d_edge_closed_loop(d_eff, 7)
encoder = MLPEncoder(d_clause=d_eff, d_edge=d_edge)
graph   = RGCNLayer(d_in=d_eff, d_out=d_eff)
pipeline = CGNPipeline(encoder=encoder, graph=graph, vocabulary=vocab)

# Load trained weights
load_checkpoint(pipeline, "model.npz")
pipeline.encoder.training = False

# Build UDRepresentation for each clause (from your UD parser)
clause1 = UDRepresentation(
    tokens=[{"lemma": "baisser", "pos": "VERB", "dep_rel": "root", "morph": {"Tense": "Pres"}}],
    root_lemma="baisser", root_pos="VERB", root_dep_rel="root",
    root_morph={"Tense": "Pres"}, subject_pos="NOUN",
    has_object=False, has_advcl=False, has_temporal_obl=False,
    token_span=(1, 3),
)
clause2 = UDRepresentation(
    tokens=[{"lemma": "reculer", "pos": "VERB", "dep_rel": "root", "morph": {"Tense": "Pres"}}],
    root_lemma="reculer", root_pos="VERB", root_dep_rel="root",
    root_morph={"Tense": "Pres"}, subject_pos="NOUN",
    has_object=False, has_advcl=False, has_temporal_obl=False,
    token_span=(5, 8),
)

# Inference
cir = pipeline.forward([clause1, clause2], "Les ventes baissent car la demande recule.")
print(cir["nodes"][0]["node_type"])   # "processus"
print(cir["edges"][0][2]["relation"]) # "cause"

2. Training on your own annotated corpus

# Prepare your dataset in gcn-nl JSON format (see gcn-datasets/examples/)
gcn-train \
  --data-dir  my_corpus/train/ \
  --val-dir   my_corpus/val/ \
  --epochs    100 \
  --lr        0.001 \
  --use-attention \
  --bidirectional \
  --embedding-dim 50 \
  --edge-loss-weight 2.0 \
  --weight-decay 0.001 \
  --patience 20 \
  --output    model.npz \
  --log-csv   training.csv

3. Training in Python

from gcn_python.layer1.features import FeatureVocabulary
from gcn_python.layer2.reference import MLPEncoder
from gcn_python.layer3.reference import RGCNLayer
from gcn_python.pipeline.cgnp import CGNPipeline
from gcn_python.data.loader import GCNDataLoader, reps_from_sentence
from gcn_python.training.checkpoint import save_checkpoint
import numpy as np

vocab   = FeatureVocabulary()
d_eff   = vocab.d_clause
d_edge  = vocab.d_edge_closed_loop(d_eff, 7)
encoder = MLPEncoder(d_clause=d_eff, d_edge=d_edge)
graph   = RGCNLayer(d_in=d_eff, d_out=d_eff)
pipeline = CGNPipeline(encoder=encoder, graph=graph, vocabulary=vocab)

loader = GCNDataLoader("my_corpus/train/")

for epoch in range(50):
    for sample in loader:
        reps, idxs, conn = reps_from_sentence(sample.sentence)
        if not reps:
            continue
        pipeline.forward(reps, sample.sentence.text, clause_positions=idxs,
                         n_total_clauses=len(sample.sentence.clauses),
                         connector_reps=conn)
        node_logits = pipeline._cached_node_logits
        edge_logits = pipeline._cached_edge_logits
        gold_node   = sample.gold_node_labels[np.array(idxs)]

        loss, d_node, d_edge = pipeline.loss(node_logits, edge_logits, gold_node)
        pipeline.backward(d_node, d_edge, lr=0.001)

save_checkpoint(pipeline, "model.npz")

4. Substituting your own encoder (PyTorch, JAX…)

from gcn_python.layer2.interface import CausalEncoder
import torch, torch.nn as nn

class MyTransformerEncoder(nn.Module, CausalEncoder):
    """Replace the reference MLP with your own architecture."""
    def __init__(self, d_clause: int, d_edge: int):
        super().__init__()
        self.node_head = nn.Linear(d_clause, 7)
        self.edge_head = nn.Linear(d_edge, 11)

    def forward_node(self, x):
        return self.node_head(torch.tensor(x)).detach().numpy()

    def forward_edge(self, x):
        return self.edge_head(torch.tensor(x)).detach().numpy()

    def parameters(self):
        return [p.detach().numpy() for p in super().parameters()]

# Plug into the pipeline — everything else stays the same
encoder = MyTransformerEncoder(vocab.d_clause, d_edge)
pipeline = CGNPipeline(encoder=encoder, graph=graph, vocabulary=vocab)

Dataset format (gcn-nl)

{
  "document": {
    "sentences": [
      {
        "id": "s001",
        "text": "Les ventes baissent car la demande recule.",
        "causal_pattern": "cause",
        "tokens": [
          {"id": 1, "form": "Les",    "lemma": "le",      "pos": "DET",  "dep_rel": "det",   "dep_head": 2, "morph": {}},
          {"id": 2, "form": "ventes", "lemma": "vente",   "pos": "NOUN", "dep_rel": "nsubj", "dep_head": 3, "morph": {}},
          {"id": 3, "form": "baissent","lemma":"baisser", "pos": "VERB", "dep_rel": "root",  "dep_head": 0, "morph": {"Tense": "Pres"}}
        ],
        "cir": {
          "nodes": [
            {"id": "n001", "type": "processus", "label": "baisser(ventes)", "token_span": [1, 3]},
            {"id": "n002", "type": "processus", "label": "reculer(demande)", "token_span": [5, 8]}
          ],
          "edges": [
            {"source": "n001", "target": "n002", "relation": "cause",
             "attributes": {"confidence": 1.0, "explicit": true, "negated": false}}
          ]
        }
      }
    ]
  }
}

Node types (7): etat · action · transition · processus · condition · entite · etat_systemique

Edge relations (11): cause · enable · prevent · condition · concession · sequence · motivation · filter · opposition · data_dependency · control_dependency


Architecture

UD tokens (pos, dep_rel, morph, lemma)
          │
   Layer 1 — FeatureVocabulary
   vectorize_clause() → 79-dim vector per clause
          │
   Layer 2 — MLPEncoder  (replaceable)
   forward_node()  →  node_logits  (N × 7)
   forward_edge()  →  edge_logits  (E × 11)
          │
   Layer 3 — RGCNLayer  (replaceable)
   message_pass()  →  enriched clause vectors
          │
   CGNPipeline.forward() → CausalIR dict

Extensibility: replace MLPEncoder with any CausalEncoder implementation (PyTorch, JAX, ONNX). Replace RGCNLayer with RGCNLayerGAT (attention) or your own CausalGraph.


CLI reference

Command Description
gcn-train --data-dir DIR --epochs N --output model.npz Train on annotated corpus
gcn-train --val-dir DIR --patience 20 With validation + early stopping
gcn-train --use-attention --bidirectional GAT + 22-relation message passing
gcn-train --embedding-dim 50 Learnable word embeddings
gcn-train --weight-decay 0.001 --rgcn-dropout 0.1 --label-smoothing 0.05 Regularization
gcn-eval --data-dir DIR --checkpoint model.npz Evaluate checkpoint
gcn-forward --text "..." --checkpoint model.npz Single-sentence inference

Evaluation metrics

from gcn_python.evaluation.metrics import (
    node_macro_f1,        # macro-averaged F1 over 7 node types
    edge_macro_f1,        # macro-averaged F1 over 11 relation types
    graph_exact_match,    # fraction of sentences with fully correct graph
    confusion_matrix,     # N×N confusion matrix
    per_class_report,     # precision / recall / F1 per class
)

License

MIT — see LICENSE

Author

Michel Tendeng — GCN Causal Engine

Release files for gcn-python 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for gcn-python 2.1.0
File Interpreter ABI Platform
gcn_python-2.1.0-py3-none-any.whl Python 3 none any Details

Release files / gcn_python-2.1.0-py3-none-any.whl

Download URL gcn_python-2.1.0-py3-none-any.whl
Size 78.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8573a841571aef81b638bed76d83489afdd0b4ad0dbf1bdd546d8d01f1bb80b2
BLAKE2b-256 checksum
How to use checksums
cd3a59d233d3a637e46b9b0b4f8162c28a1b9fe1e769f7d4f1069562a7a19643
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

2.5.0

2 release files

2.1.1

1 release file

This release

2.1.0 This release

1 release file

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page