gcn-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. gcn_python/__init__.py +2 -0
  2. gcn_python/constants.py +28 -0
  3. gcn_python/data/__init__.py +0 -0
  4. gcn_python/data/json_reader.py +124 -0
  5. gcn_python/data/loader.py +212 -0
  6. gcn_python/data/schema.py +50 -0
  7. gcn_python/data/verbalize_loader.py +96 -0
  8. gcn_python/evaluation/__init__.py +0 -0
  9. gcn_python/evaluation/eval_runner.py +123 -0
  10. gcn_python/evaluation/metrics.py +257 -0
  11. gcn_python/evaluation/recorder.py +158 -0
  12. gcn_python/layer1/__init__.py +0 -0
  13. gcn_python/layer1/features.py +126 -0
  14. gcn_python/layer1/representation.py +37 -0
  15. gcn_python/layer2/__init__.py +0 -0
  16. gcn_python/layer2/interface.py +52 -0
  17. gcn_python/layer2/reference.py +159 -0
  18. gcn_python/layer3/__init__.py +0 -0
  19. gcn_python/layer3/interface.py +32 -0
  20. gcn_python/layer3/pytorch_rgcn.py +190 -0
  21. gcn_python/layer3/reference.py +102 -0
  22. gcn_python/pipeline/__init__.py +0 -0
  23. gcn_python/pipeline/cgnp.py +419 -0
  24. gcn_python/pipeline/cli.py +77 -0
  25. gcn_python/pipeline/ir_emitter.py +71 -0
  26. gcn_python/pipeline/label_builder.py +82 -0
  27. gcn_python/taxonomy/__init__.py +0 -0
  28. gcn_python/taxonomy/loader.py +74 -0
  29. gcn_python/training/__init__.py +0 -0
  30. gcn_python/training/bootstrap.py +117 -0
  31. gcn_python/training/checkpoint.py +93 -0
  32. gcn_python/training/train.py +238 -0
  33. gcn_python/verbalizer/__init__.py +4 -0
  34. gcn_python/verbalizer/cli.py +25 -0
  35. gcn_python/verbalizer/decoder.py +38 -0
  36. gcn_python/verbalizer/interface.py +21 -0
  37. gcn_python/verbalizer/trainable.py +204 -0
  38. gcn_python-1.0.0.dist-info/METADATA +12 -0
  39. gcn_python-1.0.0.dist-info/RECORD +41 -0
  40. gcn_python-1.0.0.dist-info/WHEEL +4 -0
  41. gcn_python-1.0.0.dist-info/entry_points.txt +6 -0
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import re
4
+ import numpy as np
5
+
6
+ from ..layer2.reference import _LinearLayer, _relu, _relu_grad
7
+
8
+
9
+ class SurfaceVocabulary:
10
+ """Word-level vocabulary built from gold surface texts."""
11
+
12
+ PAD = "<pad>"
13
+ UNK = "<unk>"
14
+
15
+ def __init__(self) -> None:
16
+ self._t2i: dict[str, int] = {self.PAD: 0, self.UNK: 1}
17
+ self._i2t: list[str] = [self.PAD, self.UNK]
18
+
19
+ @staticmethod
20
+ def _tokenize(text: str) -> list[str]:
21
+ return re.findall(r"\w+|[^\w\s]", text.lower())
22
+
23
+ def build(self, surfaces: list[str]) -> None:
24
+ for text in surfaces:
25
+ for tok in self._tokenize(text):
26
+ if tok not in self._t2i:
27
+ self._t2i[tok] = len(self._i2t)
28
+ self._i2t.append(tok)
29
+
30
+ def encode(self, text: str) -> list[int]:
31
+ return [self._t2i.get(tok, 1) for tok in self._tokenize(text)]
32
+
33
+ def decode(self, indices: list[int] | np.ndarray) -> str:
34
+ return " ".join(self._i2t[int(i)] for i in indices if 0 <= int(i) < len(self._i2t))
35
+
36
+ def __len__(self) -> int:
37
+ return len(self._i2t)
38
+
39
+ def to_json(self) -> str:
40
+ return json.dumps(self._i2t)
41
+
42
+ @classmethod
43
+ def from_json(cls, s: str) -> "SurfaceVocabulary":
44
+ v = cls()
45
+ tokens: list[str] = json.loads(s)
46
+ v._i2t = tokens
47
+ v._t2i = {t: i for i, t in enumerate(tokens)}
48
+ return v
49
+
50
+
51
+ class TrainableDecoder:
52
+ """
53
+ Reference NumPy decoder — node embeddings (N, D_in) → surface token logits (|V|,).
54
+
55
+ Architecture: mean-pool → fc(D_in → d_hidden) + ReLU → fc(d_hidden → |V|)
56
+
57
+ Implements VerbalizerDecoder (inference via decode()) AND the training interface:
58
+ forward_decode / loss_decode / backward_decode / parameters / update.
59
+
60
+ Layers are lazily initialized at the first forward_decode call.
61
+ Pass d_in to pre-initialize (required for checkpoint restore).
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ vocab: SurfaceVocabulary,
67
+ d_hidden: int = 64,
68
+ d_in: int | None = None,
69
+ seed: int = 0,
70
+ ) -> None:
71
+ self.vocab = vocab
72
+ self.d_hidden = d_hidden
73
+ self._rng = np.random.default_rng(seed)
74
+ self._layers: list[_LinearLayer] | None = None
75
+ self._cache: list[tuple[np.ndarray, np.ndarray]] = []
76
+ self._last_d_in: int | None = None
77
+ if d_in is not None:
78
+ self._init_layers(d_in)
79
+
80
+ def _init_layers(self, d_in: int) -> None:
81
+ if self._layers is None:
82
+ self._layers = [
83
+ _LinearLayer(d_in, self.d_hidden, self._rng),
84
+ _LinearLayer(self.d_hidden, len(self.vocab), self._rng),
85
+ ]
86
+ self._last_d_in = d_in
87
+
88
+ @staticmethod
89
+ def _node_type_embeddings_from_ir(ir_json: str) -> np.ndarray:
90
+ from ..constants import NODE_TYPES
91
+ ir = json.loads(ir_json)
92
+ nodes = ir.get("nodes", [])
93
+ if not nodes:
94
+ return np.zeros((1, len(NODE_TYPES)), dtype=np.float32)
95
+ embs = []
96
+ for node in nodes:
97
+ nt = node.get("node_type", "")
98
+ idx = NODE_TYPES.index(nt) if nt in NODE_TYPES else 0
99
+ onehot = np.zeros(len(NODE_TYPES), dtype=np.float32)
100
+ onehot[idx] = 1.0
101
+ embs.append(onehot)
102
+ return np.stack(embs) # (N, 7)
103
+
104
+ def forward_decode(self, node_embeddings: np.ndarray) -> np.ndarray:
105
+ """(N, D_in) → (|V|,) logits."""
106
+ if len(node_embeddings) == 0:
107
+ return np.zeros(len(self.vocab), dtype=np.float32)
108
+ d_in = node_embeddings.shape[1]
109
+ self._init_layers(d_in)
110
+ assert self._layers is not None
111
+ mean = node_embeddings.mean(axis=0) # (D_in,)
112
+ self._cache = []
113
+ h = mean
114
+ for i, layer in enumerate(self._layers):
115
+ z = layer.forward(h)
116
+ if i < len(self._layers) - 1:
117
+ h = _relu(z)
118
+ self._cache.append((z, h))
119
+ else:
120
+ h = z
121
+ self._cache.append((z, z))
122
+ return h # (|V|,)
123
+
124
+ def loss_decode(
125
+ self, logits: np.ndarray, gold_tokens: np.ndarray
126
+ ) -> tuple[float, np.ndarray]:
127
+ """Average cross-entropy over gold tokens. Returns (loss, d_logits)."""
128
+ if len(gold_tokens) == 0 or len(logits) == 0:
129
+ return 0.0, np.zeros_like(logits)
130
+ V = len(logits)
131
+ e = np.exp(logits - logits.max())
132
+ probs = e / (e.sum() + 1e-9)
133
+ total_loss = 0.0
134
+ d_logits = np.zeros(V, dtype=np.float32)
135
+ valid = [int(t) for t in gold_tokens if 0 <= int(t) < V]
136
+ if not valid:
137
+ return 0.0, d_logits
138
+ for t in valid:
139
+ total_loss -= float(np.log(probs[t] + 1e-9))
140
+ d_t = probs.copy()
141
+ d_t[t] -= 1.0
142
+ d_logits += d_t
143
+ N = len(valid)
144
+ return total_loss / N, d_logits / N
145
+
146
+ def backward_decode(
147
+ self, d_logits: np.ndarray
148
+ ) -> tuple[np.ndarray, list[tuple[np.ndarray, np.ndarray]]]:
149
+ """Backward through MLP. Returns (d_mean, param_grads).
150
+
151
+ d_mean has shape (D_in,) — gradient w.r.t. the mean-pooled node embedding.
152
+ Caller must divide by N and broadcast to each node when propagating upstream.
153
+ """
154
+ assert self._layers is not None, "backward_decode called before forward_decode"
155
+ grads: list[tuple[np.ndarray, np.ndarray]] = []
156
+ d = d_logits.copy()
157
+ for i in reversed(range(len(self._layers))):
158
+ z, _ = self._cache[i]
159
+ if i < len(self._layers) - 1:
160
+ d = d * _relu_grad(z)
161
+ dx, dW, db = self._layers[i].backward(d)
162
+ grads.insert(0, (dW, db))
163
+ d = dx
164
+ return d, grads # d: (D_in,)
165
+
166
+ def parameters(self) -> list[np.ndarray]:
167
+ if self._layers is None:
168
+ return []
169
+ params: list[np.ndarray] = []
170
+ for layer in self._layers:
171
+ params.extend([layer.W, layer.b])
172
+ return params
173
+
174
+ def update(self, grads: list[tuple[np.ndarray, np.ndarray]], lr: float) -> None:
175
+ assert self._layers is not None
176
+ for layer, (dW, db) in zip(self._layers, grads):
177
+ layer.W -= lr * dW
178
+ layer.b -= lr * db
179
+
180
+ # ── VerbalizerDecoder inference interface ─────────────────────────────────
181
+
182
+ def decode(self, ir_json: str) -> str:
183
+ """CausalIR JSON → surface string (inference)."""
184
+ node_embs = self._node_type_embeddings_from_ir(ir_json)
185
+ logits = self.forward_decode(node_embs)
186
+ top_indices = np.argsort(logits)[-10:][::-1]
187
+ filtered = [int(i) for i in top_indices if int(i) >= 2][:5]
188
+ return self.vocab.decode(filtered)
189
+
190
+ # ── Checkpoint serialization ──────────────────────────────────────────────
191
+
192
+ def to_json(self) -> str:
193
+ d_in = self._layers[0].W.shape[1] if self._layers else None
194
+ return json.dumps({
195
+ "vocab": self.vocab.to_json(),
196
+ "d_hidden": self.d_hidden,
197
+ "d_in": d_in,
198
+ })
199
+
200
+ @classmethod
201
+ def from_json(cls, s: str) -> "TrainableDecoder":
202
+ data = json.loads(s)
203
+ vocab = SurfaceVocabulary.from_json(data["vocab"])
204
+ return cls(vocab, d_hidden=data["d_hidden"], d_in=data.get("d_in"))
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.5
2
+ Name: gcn-python
3
+ Version: 1.0.0
4
+ Summary: CGNP ML layers — Causal Graph Neural Parser architecture
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: click>=8.1
7
+ Requires-Dist: numpy<3.0,>=1.24
8
+ Requires-Dist: pyyaml>=6.0
9
+ Requires-Dist: spacy<4.0,>=3.7
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=7.4; extra == 'dev'
12
+ Requires-Dist: ruff>=0.1; extra == 'dev'
@@ -0,0 +1,41 @@
1
+ gcn_python/__init__.py,sha256=e1n_4-5SiHTOmrcwNyG7-r29xNg9sZvg3I3Ien2cUPM,88
2
+ gcn_python/constants.py,sha256=U33hF8DHpvHJQyBXJ2TtG5-jEcFTSRYHP7mteQ5Ej-8,1735
3
+ gcn_python/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ gcn_python/data/json_reader.py,sha256=YGl49CXBmYcOsfrabrypPwJd5Eu3zi-3njymPgdCqjk,4321
5
+ gcn_python/data/loader.py,sha256=hgt97szcoFSUURBHHQj8Iir4BSoP1hQUrUrsLHcOVW0,8053
6
+ gcn_python/data/schema.py,sha256=d0vFZe8cK5QVQ12975El9dGHZOBb9lnr3aSeL4PdCmw,1192
7
+ gcn_python/data/verbalize_loader.py,sha256=wjn8OiBoYwV2oSn-HpfVS5OA14fIaumCUFAKZ4CrdG0,3428
8
+ gcn_python/evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gcn_python/evaluation/eval_runner.py,sha256=SbYQ-BTrzkjs8dLjOmcPyhPEoehIDbdOXpaHMotKfas,4729
10
+ gcn_python/evaluation/metrics.py,sha256=1qKErCDqN6VKuTeUYxhMxPlFHy-C8xuH-PYjLD9J0YI,9348
11
+ gcn_python/evaluation/recorder.py,sha256=j-uiX6kj7zl5BLI9Ky9e4SlgtFejKZYgF75r50kvmeY,5298
12
+ gcn_python/layer1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ gcn_python/layer1/features.py,sha256=vxlW8C9hMQc2WtpwpMMOssemu_U0G18QhYx24U_WbS8,4146
14
+ gcn_python/layer1/representation.py,sha256=6XtLtHlRsS4WsmvKXfkm3EgX_ZOxzP_obMf1FMOpQFI,1239
15
+ gcn_python/layer2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ gcn_python/layer2/interface.py,sha256=E-UzWA2TyOyPc8UE2TaAkYiuoAkyarLwP8XjIbn62Vo,1786
17
+ gcn_python/layer2/reference.py,sha256=wH4oY4Gil1SHyAQRPWDtBPr0qUrpj1jX5T-kbhe3txo,5859
18
+ gcn_python/layer3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ gcn_python/layer3/interface.py,sha256=4b0wMLI1wS9GHUBUugb6lRD51iY3WV9NU0lYRXquKbk,941
20
+ gcn_python/layer3/pytorch_rgcn.py,sha256=IiUvyB6mI_dDg8TC0SLUnbN8Yj6NeI42uSsmsRc941A,7322
21
+ gcn_python/layer3/reference.py,sha256=-u0grfeEfenr25fY8gOhylysgq8twyr-dQS9dfNGqm0,4167
22
+ gcn_python/pipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ gcn_python/pipeline/cgnp.py,sha256=j3IaGzEd9R7tX-01pv1j1jespZRVbrjwmqFiNFOo9ig,19153
24
+ gcn_python/pipeline/cli.py,sha256=8eDpbDyImyBXwTUQeIYfve32G-ZgiK_G0MgY_w0H658,2944
25
+ gcn_python/pipeline/ir_emitter.py,sha256=m05HuzBB6kRMbSNAM-ijhtRylDNr_BiSWCZgjgWrSiw,2154
26
+ gcn_python/pipeline/label_builder.py,sha256=SMt_wYnJDTm5oZc5Vc7FExQ6I1HtRHIPt4pPKk7v-OI,2966
27
+ gcn_python/taxonomy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ gcn_python/taxonomy/loader.py,sha256=ZZgCmeoFUgyTTQSDrClym63v8a_n7bG0FG_REo6jC7Q,2799
29
+ gcn_python/training/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ gcn_python/training/bootstrap.py,sha256=P4-d5gsn4HdzHoQxa-x0IB1MALvR9mABUlYKLY4d_r0,4167
31
+ gcn_python/training/checkpoint.py,sha256=RyUTStaTvOyJyl3cGUrGbMHIVIDZzHfsyEpYo70eqHY,3503
32
+ gcn_python/training/train.py,sha256=EmyHCWPBiHwZZJGiFuLjbrzwNM8P7XOC9xRY4fpaM8k,10538
33
+ gcn_python/verbalizer/__init__.py,sha256=_Ts92AbQBv3oFPU9Mnzh0GkDSTtnH_2v8Vz-IsAJxxA,132
34
+ gcn_python/verbalizer/cli.py,sha256=ZqV-HXtO8XSJwlHzjjcMxyfPL5gHaDG_WOMFNFWjQ9M,679
35
+ gcn_python/verbalizer/decoder.py,sha256=6z0jzJ9hIOpTOKxOqeKZnwd0FeMsS_yOW8iVJXVUMT4,1394
36
+ gcn_python/verbalizer/interface.py,sha256=GlIGVsTCUacAR-s14D1uvhY48F1nhupyXpXUP5I8UB4,671
37
+ gcn_python/verbalizer/trainable.py,sha256=-1LKHJwCclabzGgkPagGr-V8V9texwkdbYdgiFCFn3E,7442
38
+ gcn_python-1.0.0.dist-info/METADATA,sha256=NlAFpdWFyWwLr8qU6gwnnhj5UulAcKSaQZQ_xja9_bA,366
39
+ gcn_python-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
40
+ gcn_python-1.0.0.dist-info/entry_points.txt,sha256=u-IEGUYNkfJFnCIvVGpYTK8ac7uLSqeftWwQoHLsLHE,286
41
+ gcn_python-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ gcn-bootstrap = gcn_python.training.bootstrap:bootstrap_cmd
3
+ gcn-eval = gcn_python.evaluation.eval_runner:eval_cmd
4
+ gcn-forward = gcn_python.pipeline.cli:forward_cmd
5
+ gcn-train = gcn_python.training.train:train_cmd
6
+ gcn-verbalize = gcn_python.verbalizer.cli:verbalize_cmd