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.
- gcn_python/__init__.py +2 -0
- gcn_python/constants.py +28 -0
- gcn_python/data/__init__.py +0 -0
- gcn_python/data/json_reader.py +124 -0
- gcn_python/data/loader.py +212 -0
- gcn_python/data/schema.py +50 -0
- gcn_python/data/verbalize_loader.py +96 -0
- gcn_python/evaluation/__init__.py +0 -0
- gcn_python/evaluation/eval_runner.py +123 -0
- gcn_python/evaluation/metrics.py +257 -0
- gcn_python/evaluation/recorder.py +158 -0
- gcn_python/layer1/__init__.py +0 -0
- gcn_python/layer1/features.py +126 -0
- gcn_python/layer1/representation.py +37 -0
- gcn_python/layer2/__init__.py +0 -0
- gcn_python/layer2/interface.py +52 -0
- gcn_python/layer2/reference.py +159 -0
- gcn_python/layer3/__init__.py +0 -0
- gcn_python/layer3/interface.py +32 -0
- gcn_python/layer3/pytorch_rgcn.py +190 -0
- gcn_python/layer3/reference.py +102 -0
- gcn_python/pipeline/__init__.py +0 -0
- gcn_python/pipeline/cgnp.py +419 -0
- gcn_python/pipeline/cli.py +77 -0
- gcn_python/pipeline/ir_emitter.py +71 -0
- gcn_python/pipeline/label_builder.py +82 -0
- gcn_python/taxonomy/__init__.py +0 -0
- gcn_python/taxonomy/loader.py +74 -0
- gcn_python/training/__init__.py +0 -0
- gcn_python/training/bootstrap.py +117 -0
- gcn_python/training/checkpoint.py +93 -0
- gcn_python/training/train.py +238 -0
- gcn_python/verbalizer/__init__.py +4 -0
- gcn_python/verbalizer/cli.py +25 -0
- gcn_python/verbalizer/decoder.py +38 -0
- gcn_python/verbalizer/interface.py +21 -0
- gcn_python/verbalizer/trainable.py +204 -0
- gcn_python-1.0.0.dist-info/METADATA +12 -0
- gcn_python-1.0.0.dist-info/RECORD +41 -0
- gcn_python-1.0.0.dist-info/WHEEL +4 -0
- 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,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
|