initium-deeplearning 0.1.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.
- initium/LNN_controller/cfc_backbone_parallel.py +265 -0
- initium/LNN_controller/cfc_controller.py +325 -0
- initium/LNN_controller/chained_controller.py +83 -0
- initium/LNN_controller/hybrid_controller.py +90 -0
- initium/MoE/README.md +1 -0
- initium/MoE/moe_layer.py +552 -0
- initium/__init__.py +1 -0
- initium/config/controller_config.py +86 -0
- initium/config/train_config.py +64 -0
- initium/core_training.py +2182 -0
- initium/data/__init__.py +0 -0
- initium/data/audio/__init__.py +0 -0
- initium/data/audio/audio_dataset.py +96 -0
- initium/data/base_dataset.py +75 -0
- initium/data/common/__init__.py +0 -0
- initium/data/common/chain_task.py +542 -0
- initium/data/common/digit_codec.py +85 -0
- initium/data/common/graph_io.py +123 -0
- initium/data/common/real_data.py +747 -0
- initium/data/dataset_registry.py +59 -0
- initium/data/graph_traversal/__init__.py +0 -0
- initium/data/graph_traversal/graph_traversal.py +746 -0
- initium/data/multimodal/__init__.py +0 -0
- initium/data/multimodal/multimodal_dataset.py +401 -0
- initium/data/text/__init__.py +0 -0
- initium/data/text/text_dataset.py +79 -0
- initium/data/video/__init__.py +0 -0
- initium/data/video/video_dataset.py +85 -0
- initium/inference/__init__.py +1 -0
- initium/inference/cache/__init__.py +1 -0
- initium/inference/cache/base_cache.py +112 -0
- initium/inference/cache/cache_config.py +21 -0
- initium/inference/cache/cache_registry.py +99 -0
- initium/inference/cache/cached_engine.py +264 -0
- initium/inference/cache/keys.py +77 -0
- initium/inference/cache/prefix_cache.py +59 -0
- initium/inference/cache/result_cache.py +57 -0
- initium/inference/cache/state_utils.py +74 -0
- initium/inference/cache/stores.py +167 -0
- initium/inference/capabilities.py +86 -0
- initium/inference/checkpoint_io.py +63 -0
- initium/inference/engine.py +88 -0
- initium/inference/inference_config.py +22 -0
- initium/inference/metrics.py +103 -0
- initium/inference/model_loader.py +270 -0
- initium/inference/run_inference.py +526 -0
- initium/inference/run_logging.py +173 -0
- initium/inference/tasks/__init__.py +1 -0
- initium/inference/tasks/audio_task.py +114 -0
- initium/inference/tasks/base_task.py +60 -0
- initium/inference/tasks/graph_traversal_task.py +235 -0
- initium/inference/tasks/multimodal_task.py +74 -0
- initium/inference/tasks/task_registry.py +68 -0
- initium/inference/tasks/text_task.py +114 -0
- initium/inference/tasks/video_task.py +114 -0
- initium/mamba_controller/mamba2_controller.py +433 -0
- initium/mamba_controller/mamba3_controller.py +242 -0
- initium/mamba_controller/mamba_backbone_parallel.py +291 -0
- initium/mamba_controller/mamba_controller.py +983 -0
- initium/mamba_controller/split_graph_dnc.py +623 -0
- initium/memory_manipulation/dynamic_memory_resize.py +157 -0
- initium/memory_manipulation/dynamic_n_controller.py +152 -0
- initium/memory_manipulation/link_matrix_ablation.py +130 -0
- initium/memory_manipulation/nvrtc_compat.py +143 -0
- initium/memory_manipulation/stochastic_write_head_v2.py +545 -0
- initium/setup_wheels.py +175 -0
- initium_deeplearning-0.1.0.dist-info/METADATA +253 -0
- initium_deeplearning-0.1.0.dist-info/RECORD +71 -0
- initium_deeplearning-0.1.0.dist-info/WHEEL +4 -0
- initium_deeplearning-0.1.0.dist-info/entry_points.txt +3 -0
- initium_deeplearning-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file: data/graph_traversal/graph_traversal.py
|
|
3
|
+
|
|
4
|
+
Graph-traversal dataset (Graves 2016-style triple encoding, curriculum over
|
|
5
|
+
synthetic graphs, London Underground as held-out OOD and controller test). Everything
|
|
6
|
+
graph-specific lives here; core_training.py only sees GraphTraversalDataset.
|
|
7
|
+
|
|
8
|
+
History of graph-specific changes (moved from core_training.py):
|
|
9
|
+
- v2: build_london_underground_eval() uses a local random.Random(1234), never
|
|
10
|
+
the global `random.seed` (a global reseed would repeat training batches after
|
|
11
|
+
every periodic OOD eval).
|
|
12
|
+
- v4 gate fix: evaluate_traversal() resamples num_nodes per episode over
|
|
13
|
+
nodes_range (same as training) instead of pinning to the hardest size, and
|
|
14
|
+
the curriculum advances on triple_acc >= ADVANCE_THRESHOLD instead of
|
|
15
|
+
perfect_frac (perfect_frac compounds ~acc**hops and made deep lessons
|
|
16
|
+
unreachable). perfect_frac is still computed/logged/returned.
|
|
17
|
+
hop_breakdown=True buckets episodes by walk length and is printed/logged at
|
|
18
|
+
every advance-check.
|
|
19
|
+
- v5: build_traversal_episode[_from_graph] and evaluate_traversal take an
|
|
20
|
+
optional `rng` (random.Random); None -> global `random` (byte-identical to
|
|
21
|
+
v4). OOD calls pass a dedicated rng so OOD walk sampling is decoupled from
|
|
22
|
+
the training curriculum's RNG stream.
|
|
23
|
+
- v16: per-field / per-hop-position accuracy (field_log, write_field_log).
|
|
24
|
+
- Static Option 2: per-lesson memory size LESSON_NR_CELLS, now held on the
|
|
25
|
+
curriculum instance (`curriculum.lesson_nr_cells`) so core can override it
|
|
26
|
+
per run (isolate_link_ablation / dynamic_n_mode) without touching module
|
|
27
|
+
globals.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import random
|
|
31
|
+
|
|
32
|
+
import numpy as np
|
|
33
|
+
import torch
|
|
34
|
+
|
|
35
|
+
from initium.data.base_dataset import BaseDataset
|
|
36
|
+
from initium.data.common.graph_io import load_graph
|
|
37
|
+
from initium.memory_manipulation.dynamic_memory_resize import resize_memory
|
|
38
|
+
|
|
39
|
+
# ==========================================
|
|
40
|
+
# CONFIGURATION
|
|
41
|
+
# ==========================================
|
|
42
|
+
LABEL_RANGE = 1000
|
|
43
|
+
LABEL_DIGITS = 3 # each label is a 3-digit number, 0-999
|
|
44
|
+
DIGIT_BASE = 10 # one-hot over digits 0-9
|
|
45
|
+
LABEL_DIM = LABEL_DIGITS * DIGIT_BASE # 30
|
|
46
|
+
TRIPLE_DIM = 3 * LABEL_DIM # 90 (source + edge + destination)
|
|
47
|
+
NUM_PHASE_CHANNELS = 2 # [phase-transition, prediction-required]
|
|
48
|
+
INPUT_DIM = TRIPLE_DIM + NUM_PHASE_CHANNELS # 92, matches Methods
|
|
49
|
+
|
|
50
|
+
TRAVERSAL_CURRICULUM = [
|
|
51
|
+
((3, 10), (2, 4), (1, 1)),
|
|
52
|
+
((3, 10), (2, 4), (1, 2)),
|
|
53
|
+
((5, 10), (2, 4), (1, 3)),
|
|
54
|
+
((5, 10), (2, 4), (1, 4)),
|
|
55
|
+
((10, 15), (2, 4), (1, 4)),
|
|
56
|
+
((10, 15), (2, 4), (1, 5)),
|
|
57
|
+
((10, 20), (2, 4), (1, 5)),
|
|
58
|
+
((10, 20), (2, 4), (1, 6)),
|
|
59
|
+
((10, 30), (2, 4), (1, 6)),
|
|
60
|
+
((10, 30), (2, 4), (1, 7)),
|
|
61
|
+
((10, 30), (2, 4), (1, 8)),
|
|
62
|
+
((10, 30), (2, 4), (1, 9)),
|
|
63
|
+
((10, 40), (2, 6), (1, 10)),
|
|
64
|
+
((10, 40), (2, 6), (1, 20)),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Static Option 2: per-lesson memory size N, indexed like TRAVERSAL_CURRICULUM.
|
|
68
|
+
LESSON_NR_CELLS = [
|
|
69
|
+
128,
|
|
70
|
+
128,
|
|
71
|
+
128,
|
|
72
|
+
128, # lessons 1-4 (<=10 nodes)
|
|
73
|
+
160,
|
|
74
|
+
160,
|
|
75
|
+
160,
|
|
76
|
+
160, # lessons 5-8 (<=20 nodes)
|
|
77
|
+
192,
|
|
78
|
+
192,
|
|
79
|
+
192,
|
|
80
|
+
192, # lessons 9-12 (<=30 nodes)
|
|
81
|
+
256,
|
|
82
|
+
256, # lessons 13-14 (<=40 nodes, path_length up to 20)
|
|
83
|
+
]
|
|
84
|
+
assert len(LESSON_NR_CELLS) == len(TRAVERSAL_CURRICULUM)
|
|
85
|
+
|
|
86
|
+
ADVANCE_THRESHOLD = 0.85 # 85% per-triple accuracy
|
|
87
|
+
OLD_LESSON_MIX_RATE = 0.10 # 10% of exemplars drawn from earlier lessons
|
|
88
|
+
EVAL_BATCH_SIZE = 100 # episodes per lesson-completion check
|
|
89
|
+
OOD_PATH_LENGTH_RANGE = (3, 5)
|
|
90
|
+
|
|
91
|
+
LONDON_UNDERGROUND_EDGES_RAW = [
|
|
92
|
+
("OxfordCircus", "TottenhamCtRd", "Central"),
|
|
93
|
+
("TottenhamCtRd", "OxfordCircus", "Central"),
|
|
94
|
+
("OxfordCircus", "PiccadillyCircus", "Bakerloo"),
|
|
95
|
+
("PiccadillyCircus", "OxfordCircus", "Bakerloo"),
|
|
96
|
+
("OxfordCircus", "NottingHillGate", "Central"),
|
|
97
|
+
("OxfordCircus", "Euston", "Victoria"),
|
|
98
|
+
("BakerSt", "Marylebone", "Circle"),
|
|
99
|
+
("BakerSt", "Marylebone", "Bakerloo"),
|
|
100
|
+
("BakerSt", "OxfordCircus", "Bakerloo"),
|
|
101
|
+
("LeicesterSq", "CharingCross", "Northern"),
|
|
102
|
+
("TottenhamCtRd", "LeicesterSq", "Northern"),
|
|
103
|
+
("LeicesterSq", "PiccadillyCircus", "Piccadilly"),
|
|
104
|
+
("PiccadillyCircus", "LeicesterSq", "Piccadilly"),
|
|
105
|
+
("PiccadillyCircus", "GreenPark", "Piccadilly"),
|
|
106
|
+
("GreenPark", "PiccadillyCircus", "Piccadilly"),
|
|
107
|
+
("GreenPark", "OxfordCircus", "Victoria"),
|
|
108
|
+
("GreenPark", "Victoria", "Victoria"),
|
|
109
|
+
("Victoria", "GreenPark", "Victoria"),
|
|
110
|
+
("CharingCross", "PiccadillyCircus", "Bakerloo"),
|
|
111
|
+
("PiccadillyCircus", "CharingCross", "Bakerloo"),
|
|
112
|
+
("LeicesterSq", "TottenhamCtRd", "Northern"),
|
|
113
|
+
("CharingCross", "LeicesterSq", "Northern"),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
# Module-level indirection so evaluate_traversal can be reused without
|
|
117
|
+
# threading output_proj through every call. Set via set_output_proj().
|
|
118
|
+
output_proj_current = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def set_output_proj(proj):
|
|
122
|
+
global output_proj_current
|
|
123
|
+
output_proj_current = proj
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ==========================================
|
|
127
|
+
# GRAPH GENERATOR (fresh random graph on every call, nothing cached)
|
|
128
|
+
# ==========================================
|
|
129
|
+
def generate_graph(num_nodes, k_range, label_range=LABEL_RANGE):
|
|
130
|
+
"""Returns (edges [(src_label, edge_label, dst_label)], node_labels,
|
|
131
|
+
adjacency {node_idx: [(dest_idx, edge_label)]})."""
|
|
132
|
+
points = np.random.uniform(0.0, 1.0, size=(num_nodes, 2))
|
|
133
|
+
node_labels = random.sample(range(label_range), num_nodes)
|
|
134
|
+
|
|
135
|
+
dists = np.linalg.norm(points[:, None, :] - points[None, :, :], axis=-1)
|
|
136
|
+
np.fill_diagonal(dists, np.inf)
|
|
137
|
+
|
|
138
|
+
k_min, k_max = k_range
|
|
139
|
+
adjacency = {i: [] for i in range(num_nodes)}
|
|
140
|
+
edges = []
|
|
141
|
+
|
|
142
|
+
for i in range(num_nodes):
|
|
143
|
+
k = random.randint(k_min, min(k_max, num_nodes - 1))
|
|
144
|
+
nearest_idx = np.argsort(dists[i])[:k]
|
|
145
|
+
|
|
146
|
+
edge_labels = random.sample(node_labels, k)
|
|
147
|
+
|
|
148
|
+
for dest_idx, edge_label in zip(nearest_idx, edge_labels):
|
|
149
|
+
src_label = node_labels[i]
|
|
150
|
+
dst_label = node_labels[int(dest_idx)]
|
|
151
|
+
edges.append((src_label, edge_label, dst_label))
|
|
152
|
+
adjacency[i].append((int(dest_idx), edge_label))
|
|
153
|
+
|
|
154
|
+
return edges, node_labels, adjacency
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ==========================================
|
|
158
|
+
# TRIPLE ENCODER
|
|
159
|
+
# ==========================================
|
|
160
|
+
def encode_label(label):
|
|
161
|
+
vec = torch.zeros(LABEL_DIM)
|
|
162
|
+
if label is None:
|
|
163
|
+
return vec
|
|
164
|
+
digits = f"{label:03d}"
|
|
165
|
+
for pos, ch in enumerate(digits):
|
|
166
|
+
vec[pos * DIGIT_BASE + int(ch)] = 1.0
|
|
167
|
+
return vec
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def encode_triple(source, edge, dest, phase_transition=0.0, prediction_required=0.0):
|
|
171
|
+
src_vec = encode_label(source)
|
|
172
|
+
edge_vec = encode_label(edge)
|
|
173
|
+
dst_vec = encode_label(dest)
|
|
174
|
+
phase_vec = torch.tensor([phase_transition, prediction_required], dtype=torch.float32)
|
|
175
|
+
return torch.cat([src_vec, edge_vec, dst_vec, phase_vec])
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def collate_fn(batch):
|
|
179
|
+
max_len = max(len(x[0]) for x in batch)
|
|
180
|
+
B = len(batch)
|
|
181
|
+
|
|
182
|
+
padded_input = torch.zeros(B, max_len, INPUT_DIM)
|
|
183
|
+
padded_targets = torch.zeros(B, max_len, 9, dtype=torch.long)
|
|
184
|
+
padded_mask = torch.zeros(B, max_len)
|
|
185
|
+
|
|
186
|
+
for i, (inp, tgt, mask) in enumerate(batch):
|
|
187
|
+
T = inp.size(0)
|
|
188
|
+
padded_input[i, :T] = inp
|
|
189
|
+
padded_targets[i, :T] = tgt
|
|
190
|
+
padded_mask[i, :T] = mask
|
|
191
|
+
|
|
192
|
+
return padded_input, padded_targets, padded_mask
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ==========================================
|
|
196
|
+
# LOSS + DIAGNOSTICS
|
|
197
|
+
# ==========================================
|
|
198
|
+
def digit_loss(output, target_digits, answer_mask):
|
|
199
|
+
B, T, _ = output.shape
|
|
200
|
+
logits = output.view(B, T, 9, DIGIT_BASE)
|
|
201
|
+
log_probs = torch.log_softmax(logits, dim=-1)
|
|
202
|
+
|
|
203
|
+
gathered = torch.gather(log_probs, -1, target_digits.unsqueeze(-1)).squeeze(-1)
|
|
204
|
+
per_step_loss = -gathered.sum(dim=-1)
|
|
205
|
+
|
|
206
|
+
mask = answer_mask.float()
|
|
207
|
+
total = (per_step_loss * mask).sum()
|
|
208
|
+
denom = mask.sum().clamp(min=1.0)
|
|
209
|
+
return total / denom
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def prediction_diversity(output, target_digits, answer_mask):
|
|
213
|
+
mask = answer_mask.bool()
|
|
214
|
+
if mask.sum() == 0:
|
|
215
|
+
return 0.0
|
|
216
|
+
logits = output.view(*output.shape[:2], 9, DIGIT_BASE)
|
|
217
|
+
preds = logits.argmax(dim=-1) # (B,T,9)
|
|
218
|
+
preds_masked = preds[mask] # (num_answer_steps, 9)
|
|
219
|
+
if preds_masked.numel() == 0:
|
|
220
|
+
return 0.0
|
|
221
|
+
diversities = [preds_masked[:, d].unique().numel() for d in range(9)]
|
|
222
|
+
return sum(diversities) / 9.0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ==========================================
|
|
226
|
+
# EPISODE CONSTRUCTION
|
|
227
|
+
# ==========================================
|
|
228
|
+
def label_to_digits(label):
|
|
229
|
+
s = f"{label:03d}"
|
|
230
|
+
return [int(c) for c in s]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def triple_to_digit_targets(source, edge, dest):
|
|
234
|
+
return label_to_digits(source) + label_to_digits(edge) + label_to_digits(dest)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def build_traversal_episode_from_graph(
|
|
238
|
+
edges, node_labels, adjacency, num_nodes, path_length_range, rng=None
|
|
239
|
+
):
|
|
240
|
+
"""rng: optional random.Random; None -> global `random` (see header, v5)."""
|
|
241
|
+
rng = rng if rng is not None else random
|
|
242
|
+
inputs, target_digits, answer_mask = [], [], []
|
|
243
|
+
|
|
244
|
+
def add_step(src, edge, dst, phase_transition, prediction_required, target_triple=None):
|
|
245
|
+
inputs.append(encode_triple(src, edge, dst, phase_transition, prediction_required))
|
|
246
|
+
if target_triple is not None:
|
|
247
|
+
target_digits.append(triple_to_digit_targets(*target_triple))
|
|
248
|
+
answer_mask.append(1)
|
|
249
|
+
else:
|
|
250
|
+
target_digits.append([0] * 9)
|
|
251
|
+
answer_mask.append(0)
|
|
252
|
+
|
|
253
|
+
shuffled_edges = edges[:]
|
|
254
|
+
rng.shuffle(shuffled_edges)
|
|
255
|
+
for i, (s, e, d) in enumerate(shuffled_edges):
|
|
256
|
+
add_step(s, e, d, 1.0 if i == 0 else 0.0, 0.0)
|
|
257
|
+
|
|
258
|
+
path_length = rng.randint(*path_length_range)
|
|
259
|
+
start_idx = rng.randrange(num_nodes)
|
|
260
|
+
cur = start_idx
|
|
261
|
+
walk = []
|
|
262
|
+
for _ in range(path_length):
|
|
263
|
+
if not adjacency[cur]:
|
|
264
|
+
break
|
|
265
|
+
dst_idx, edge_label = rng.choice(adjacency[cur])
|
|
266
|
+
walk.append((cur, edge_label, dst_idx))
|
|
267
|
+
cur = dst_idx
|
|
268
|
+
if not walk:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
for i, (src_idx, edge_label, _dst_idx) in enumerate(walk):
|
|
272
|
+
src = node_labels[src_idx] if i == 0 else None
|
|
273
|
+
add_step(src, edge_label, None, 1.0 if i == 0 else 0.0, 0.0)
|
|
274
|
+
|
|
275
|
+
for i, (src_idx, edge_label, dst_idx) in enumerate(walk):
|
|
276
|
+
target = (node_labels[src_idx], edge_label, node_labels[dst_idx])
|
|
277
|
+
add_step(None, None, None, 1.0 if i == 0 else 0.0, 1.0, target_triple=target)
|
|
278
|
+
|
|
279
|
+
input_seq = torch.stack(inputs)
|
|
280
|
+
target_digits_t = torch.tensor(target_digits, dtype=torch.long)
|
|
281
|
+
answer_mask_t = torch.tensor(answer_mask, dtype=torch.float32)
|
|
282
|
+
return input_seq, target_digits_t, answer_mask_t
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def build_traversal_episode(num_nodes, k_range, path_length_range, rng=None):
|
|
286
|
+
# generate_graph() always uses the global random/numpy streams; it is only
|
|
287
|
+
# called for ID episodes, never for the fixed-graph OOD path.
|
|
288
|
+
edges, node_labels, adjacency = generate_graph(num_nodes, k_range)
|
|
289
|
+
return build_traversal_episode_from_graph(
|
|
290
|
+
edges, node_labels, adjacency, num_nodes, path_length_range, rng=rng
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ==========================================
|
|
295
|
+
# CURRICULUM
|
|
296
|
+
# ==========================================
|
|
297
|
+
class TraversalCurriculum:
|
|
298
|
+
def __init__(self, table=TRAVERSAL_CURRICULUM, custom_graph=None):
|
|
299
|
+
self.table = table
|
|
300
|
+
self.lesson = 0
|
|
301
|
+
# per-instance so core can override per run (isolate_link_ablation /
|
|
302
|
+
# dynamic_n_mode) without mutating module globals
|
|
303
|
+
self.lesson_nr_cells = list(LESSON_NR_CELLS)
|
|
304
|
+
# custom_graph: (edges, node_labels, adjacency) from a user-supplied
|
|
305
|
+
# graph file (GraphTraversalDataset's dataset_link), or None for the
|
|
306
|
+
# default per-episode synthetic generate_graph() behavior. When set,
|
|
307
|
+
# sample_episode() walks THIS fixed graph instead of generating a
|
|
308
|
+
# fresh random one -- node count is then whatever the supplied graph
|
|
309
|
+
# has, not the per-lesson nodes_range; only path_length_range still
|
|
310
|
+
# scales difficulty across lessons.
|
|
311
|
+
self.custom_graph = custom_graph
|
|
312
|
+
|
|
313
|
+
def _sample_lesson_params(self):
|
|
314
|
+
if self.lesson > 0 and random.random() < OLD_LESSON_MIX_RATE:
|
|
315
|
+
idx = random.randint(0, self.lesson - 1)
|
|
316
|
+
else:
|
|
317
|
+
idx = self.lesson
|
|
318
|
+
return self.table[idx]
|
|
319
|
+
|
|
320
|
+
def sample_episode(self):
|
|
321
|
+
nodes_range, out_degree_range, path_len_range = self._sample_lesson_params()
|
|
322
|
+
if self.custom_graph is not None:
|
|
323
|
+
edges, node_labels, adjacency = self.custom_graph
|
|
324
|
+
ep = None
|
|
325
|
+
while ep is None:
|
|
326
|
+
ep = build_traversal_episode_from_graph(
|
|
327
|
+
edges, node_labels, adjacency, len(node_labels), path_len_range
|
|
328
|
+
)
|
|
329
|
+
return ep
|
|
330
|
+
ep = None
|
|
331
|
+
while ep is None:
|
|
332
|
+
num_nodes = random.randint(*nodes_range)
|
|
333
|
+
ep = build_traversal_episode(num_nodes, out_degree_range, path_len_range)
|
|
334
|
+
return ep
|
|
335
|
+
|
|
336
|
+
def maybe_advance(self, model, device, step=None, optimizer=None):
|
|
337
|
+
"""Returns (lesson, id_triple_acc, id_perfect_frac). Eval samples
|
|
338
|
+
num_nodes over the lesson's nodes_range (as trained), or walks the
|
|
339
|
+
fixed custom_graph when one was supplied; the gate advances on
|
|
340
|
+
triple_acc >= ADVANCE_THRESHOLD (see header, v4)."""
|
|
341
|
+
nodes_range, out_degree_range, path_len_range = self.table[self.lesson]
|
|
342
|
+
if self.custom_graph is not None:
|
|
343
|
+
edges, node_labels, adjacency = self.custom_graph
|
|
344
|
+
graph_kwargs = dict(
|
|
345
|
+
fixed_graph=(edges, node_labels, adjacency, len(node_labels)),
|
|
346
|
+
path_length_range=path_len_range,
|
|
347
|
+
)
|
|
348
|
+
else:
|
|
349
|
+
graph_kwargs = dict(
|
|
350
|
+
nodes_range=nodes_range, k_range=out_degree_range, path_length_range=path_len_range
|
|
351
|
+
)
|
|
352
|
+
triple_acc, perfect_frac, hop_breakdown = evaluate_traversal(
|
|
353
|
+
model,
|
|
354
|
+
device,
|
|
355
|
+
num_episodes=EVAL_BATCH_SIZE,
|
|
356
|
+
verbose_n=0,
|
|
357
|
+
field_log=(field_log := {}),
|
|
358
|
+
hop_breakdown=True,
|
|
359
|
+
**graph_kwargs,
|
|
360
|
+
)
|
|
361
|
+
if hop_breakdown:
|
|
362
|
+
breakdown_str = ", ".join(
|
|
363
|
+
f"{hops}-hop: acc {acc:.1f}% perfect {pf:.1f}% (n={n})"
|
|
364
|
+
for hops, (acc, pf, n) in hop_breakdown.items()
|
|
365
|
+
)
|
|
366
|
+
print(f" [lesson {self.lesson + 1} eval by hop count] {breakdown_str}")
|
|
367
|
+
hop_writer = getattr(self, "hop_log_writer", None)
|
|
368
|
+
if hop_writer is not None:
|
|
369
|
+
for hops, (acc, pf, n) in sorted(hop_breakdown.items()):
|
|
370
|
+
hop_writer.writerow([step, self.lesson + 1, "id", hops, acc, pf, n])
|
|
371
|
+
self.hop_log_file.flush()
|
|
372
|
+
|
|
373
|
+
field_writer = getattr(self, "field_log_writer", None)
|
|
374
|
+
if field_writer is not None and field_log:
|
|
375
|
+
write_field_log(
|
|
376
|
+
field_writer, self.field_log_file, step, self.lesson + 1, "id", field_log
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if triple_acc / 100.0 >= ADVANCE_THRESHOLD and self.lesson < len(self.table) - 1:
|
|
380
|
+
self.lesson += 1
|
|
381
|
+
new_n = self.lesson_nr_cells[self.lesson]
|
|
382
|
+
if new_n != self.lesson_nr_cells[self.lesson - 1]:
|
|
383
|
+
# optimizer=optimizer: without it a live resize orphans the
|
|
384
|
+
# rebuilt Memory sublayers from Adam's param_groups.
|
|
385
|
+
resize_memory(model, new_n, device=device, optimizer=optimizer)
|
|
386
|
+
print(f">>> Memory resized to nr_cells={new_n} for lesson {self.lesson + 1}")
|
|
387
|
+
print(f">>> Curriculum advanced to lesson {self.lesson + 1}/{len(self.table)}")
|
|
388
|
+
writer = getattr(self, "advance_log_writer", None)
|
|
389
|
+
if writer is not None:
|
|
390
|
+
writer.writerow([step, self.lesson + 1, len(self.table)])
|
|
391
|
+
self.advance_log_file.flush()
|
|
392
|
+
return self.lesson, triple_acc, perfect_frac
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def sample_batch(curriculum, batch_size):
|
|
396
|
+
episodes = [curriculum.sample_episode() for _ in range(batch_size)]
|
|
397
|
+
return collate_fn(episodes)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# ==========================================
|
|
401
|
+
# EVALUATION
|
|
402
|
+
# ==========================================
|
|
403
|
+
def decode_prediction(output_step):
|
|
404
|
+
logits = output_step.view(9, DIGIT_BASE)
|
|
405
|
+
digit_preds = logits.argmax(dim=-1).tolist()
|
|
406
|
+
src = int("".join(str(d) for d in digit_preds[0:3]))
|
|
407
|
+
edge = int("".join(str(d) for d in digit_preds[3:6]))
|
|
408
|
+
dst = int("".join(str(d) for d in digit_preds[6:9]))
|
|
409
|
+
return src, edge, dst
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def evaluate_traversal(
|
|
413
|
+
model,
|
|
414
|
+
device,
|
|
415
|
+
num_episodes=100,
|
|
416
|
+
verbose_n=3,
|
|
417
|
+
num_nodes=None,
|
|
418
|
+
nodes_range=None,
|
|
419
|
+
k_range=None,
|
|
420
|
+
path_length_range=None,
|
|
421
|
+
fixed_graph=None,
|
|
422
|
+
hop_breakdown=False,
|
|
423
|
+
rng=None,
|
|
424
|
+
ablate_memory=False,
|
|
425
|
+
field_log=None,
|
|
426
|
+
model_kwargs=None,
|
|
427
|
+
):
|
|
428
|
+
"""
|
|
429
|
+
num_nodes: fixed graph size for every episode. nodes_range: (lo, hi),
|
|
430
|
+
num_nodes resampled per episode (same distribution as training).
|
|
431
|
+
Passing both is an error; neither is only valid with fixed_graph.
|
|
432
|
+
hop_breakdown: also return {path_length: (triple_acc, perfect_frac, n)}.
|
|
433
|
+
rng: optional random.Random for this call's sampling (None -> global).
|
|
434
|
+
ablate_memory: passes pass_through_memory=False to the model (memory
|
|
435
|
+
read AND write skipped) -- functional-usage check (LB-9/Concept 6).
|
|
436
|
+
field_log: optional dict filled with per-field / per-hop-position counts
|
|
437
|
+
(see write_field_log).
|
|
438
|
+
"""
|
|
439
|
+
if num_nodes is not None and nodes_range is not None:
|
|
440
|
+
raise ValueError("evaluate_traversal: pass num_nodes or nodes_range, not both")
|
|
441
|
+
|
|
442
|
+
model.eval()
|
|
443
|
+
total_triples, correct_triples = 0, 0
|
|
444
|
+
perfect_episodes = 0
|
|
445
|
+
tested = 0
|
|
446
|
+
by_hops = {} # path_length -> [triples_total, triples_correct, episodes, episodes_perfect]
|
|
447
|
+
|
|
448
|
+
with torch.no_grad():
|
|
449
|
+
while tested < num_episodes:
|
|
450
|
+
if fixed_graph is not None:
|
|
451
|
+
edges, node_labels, adjacency, n = fixed_graph
|
|
452
|
+
ep = build_traversal_episode_from_graph(
|
|
453
|
+
edges, node_labels, adjacency, n, path_length_range, rng=rng
|
|
454
|
+
)
|
|
455
|
+
else:
|
|
456
|
+
n = (rng or random).randint(*nodes_range) if nodes_range is not None else num_nodes
|
|
457
|
+
ep = build_traversal_episode(n, k_range, path_length_range, rng=rng)
|
|
458
|
+
if ep is None:
|
|
459
|
+
continue
|
|
460
|
+
input_seq, target_digits, answer_mask = ep
|
|
461
|
+
input_seq = input_seq.unsqueeze(0).to(device)
|
|
462
|
+
|
|
463
|
+
hidden = (None, None, None)
|
|
464
|
+
output, _ = model(
|
|
465
|
+
input_seq,
|
|
466
|
+
hidden,
|
|
467
|
+
reset_experience=True,
|
|
468
|
+
pass_through_memory=not ablate_memory,
|
|
469
|
+
**(model_kwargs or {}),
|
|
470
|
+
)
|
|
471
|
+
output = output.transpose(0, 1).contiguous().squeeze(0) # (T, 92)
|
|
472
|
+
output = output_proj_current(output) # (T, 90)
|
|
473
|
+
|
|
474
|
+
answer_idx = (answer_mask == 1).nonzero(as_tuple=True)[0]
|
|
475
|
+
episode_perfect = True
|
|
476
|
+
ep_total = ep_correct = 0
|
|
477
|
+
_prev_dst_ok = None # was the previous hop's dst correct (chain-propagation diagnostic)
|
|
478
|
+
for hop_pos, idx in enumerate(answer_idx, start=1):
|
|
479
|
+
pred = decode_prediction(output[idx])
|
|
480
|
+
tgt_digits = target_digits[idx].tolist()
|
|
481
|
+
tgt = (
|
|
482
|
+
int("".join(map(str, tgt_digits[0:3]))),
|
|
483
|
+
int("".join(map(str, tgt_digits[3:6]))),
|
|
484
|
+
int("".join(map(str, tgt_digits[6:9]))),
|
|
485
|
+
)
|
|
486
|
+
is_correct = pred == tgt
|
|
487
|
+
if field_log is not None: # keyed by (episode path length, hop position)
|
|
488
|
+
_s, _e, _d = pred[0] == tgt[0], pred[1] == tgt[1], pred[2] == tgt[2]
|
|
489
|
+
_fl = field_log.setdefault((len(answer_idx), hop_pos), [0] * 9)
|
|
490
|
+
_fl[0] += 1 # n triples
|
|
491
|
+
_fl[1] += int(_s)
|
|
492
|
+
_fl[2] += int(_e)
|
|
493
|
+
_fl[3] += int(_d)
|
|
494
|
+
_fl[4] += int(is_correct)
|
|
495
|
+
if _s and _e: # lookup check: dst given src+edge right
|
|
496
|
+
_fl[5] += 1
|
|
497
|
+
_fl[6] += int(_d)
|
|
498
|
+
if _prev_dst_ok: # chain check: src given previous dst right
|
|
499
|
+
_fl[7] += 1
|
|
500
|
+
_fl[8] += int(_s)
|
|
501
|
+
_prev_dst_ok = _d
|
|
502
|
+
correct_triples += int(is_correct)
|
|
503
|
+
total_triples += 1
|
|
504
|
+
ep_correct += int(is_correct)
|
|
505
|
+
ep_total += 1
|
|
506
|
+
if not is_correct:
|
|
507
|
+
episode_perfect = False
|
|
508
|
+
if tested < verbose_n:
|
|
509
|
+
print(f" Pred: {pred} | Target: {tgt} | Correct: {is_correct}")
|
|
510
|
+
|
|
511
|
+
perfect_episodes += int(episode_perfect)
|
|
512
|
+
tested += 1
|
|
513
|
+
|
|
514
|
+
if hop_breakdown:
|
|
515
|
+
# ep_total == walk length (one answer step per hop)
|
|
516
|
+
acc = by_hops.setdefault(ep_total, [0, 0, 0, 0])
|
|
517
|
+
acc[0] += ep_total
|
|
518
|
+
acc[1] += ep_correct
|
|
519
|
+
acc[2] += 1
|
|
520
|
+
acc[3] += int(episode_perfect)
|
|
521
|
+
|
|
522
|
+
triple_acc = correct_triples / max(total_triples, 1) * 100
|
|
523
|
+
perfect_frac = perfect_episodes / tested * 100
|
|
524
|
+
print(
|
|
525
|
+
f"Eval: triple-level acc {triple_acc:.2f}% | "
|
|
526
|
+
f"perfect-traversal fraction {perfect_frac:.2f}% ({tested} episodes)"
|
|
527
|
+
)
|
|
528
|
+
model.train()
|
|
529
|
+
|
|
530
|
+
if hop_breakdown:
|
|
531
|
+
breakdown = {
|
|
532
|
+
hops: (
|
|
533
|
+
correct / max(total, 1) * 100,
|
|
534
|
+
n_perfect / max(n_eps, 1) * 100,
|
|
535
|
+
n_eps,
|
|
536
|
+
)
|
|
537
|
+
for hops, (total, correct, n_eps, n_perfect) in sorted(by_hops.items())
|
|
538
|
+
}
|
|
539
|
+
return triple_acc, perfect_frac, breakdown
|
|
540
|
+
return triple_acc, perfect_frac
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def write_field_log(writer, file, step, lesson, eval_type, field_log):
|
|
544
|
+
"""Dump one evaluate_traversal(field_log=...) dict to the field-breakdown
|
|
545
|
+
CSV and print a compact console summary (hop positions 1-4).
|
|
546
|
+
field_log[(path_length, hop_position)] = [n, src_ok, edge_ok, dst_ok, triple_ok,
|
|
547
|
+
n_src_edge_ok, dst_ok_given_src_edge, n_prev_dst_ok, src_ok_given_prev_dst]."""
|
|
548
|
+
nan = float("nan")
|
|
549
|
+
for (path_len, hop_pos), (n, s, e, d, t, n_se, d_se, n_pd, s_pd) in sorted(field_log.items()):
|
|
550
|
+
writer.writerow(
|
|
551
|
+
[
|
|
552
|
+
step,
|
|
553
|
+
lesson,
|
|
554
|
+
eval_type,
|
|
555
|
+
path_len,
|
|
556
|
+
hop_pos,
|
|
557
|
+
n,
|
|
558
|
+
s / n * 100,
|
|
559
|
+
e / n * 100,
|
|
560
|
+
d / n * 100,
|
|
561
|
+
t / n * 100,
|
|
562
|
+
(d_se / n_se * 100) if n_se else "",
|
|
563
|
+
n_se,
|
|
564
|
+
(s_pd / n_pd * 100) if n_pd else "",
|
|
565
|
+
n_pd,
|
|
566
|
+
]
|
|
567
|
+
)
|
|
568
|
+
file.flush()
|
|
569
|
+
agg = {}
|
|
570
|
+
for (_path_len, hop_pos), v in field_log.items():
|
|
571
|
+
a = agg.setdefault(hop_pos, [0] * 9)
|
|
572
|
+
for i in range(9):
|
|
573
|
+
a[i] += v[i]
|
|
574
|
+
parts = []
|
|
575
|
+
for hop_pos in sorted(agg)[:4]:
|
|
576
|
+
n, s, e, d, t, n_se, d_se, n_pd, s_pd = agg[hop_pos]
|
|
577
|
+
part = (
|
|
578
|
+
f"hop{hop_pos}: src {s / n * 100:.0f} edge {e / n * 100:.0f} dst {d / n * 100:.0f} "
|
|
579
|
+
f"dst|src+edge {(d_se / n_se * 100) if n_se else nan:.0f}"
|
|
580
|
+
)
|
|
581
|
+
if hop_pos > 1:
|
|
582
|
+
part += f" src|prev_dst_ok {(s_pd / n_pd * 100) if n_pd else nan:.0f}"
|
|
583
|
+
parts.append(part + f" (n={n})")
|
|
584
|
+
print(f" [field acc {eval_type} step {step}] " + " | ".join(parts))
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def build_london_underground_eval():
|
|
588
|
+
"""Fixed, reproducible OOD graph/label mapping. Uses a local
|
|
589
|
+
random.Random(1234) so it never touches the global RNG stream."""
|
|
590
|
+
stations = sorted(
|
|
591
|
+
{s for s, d, _ in LONDON_UNDERGROUND_EDGES_RAW}
|
|
592
|
+
| {d for s, d, _ in LONDON_UNDERGROUND_EDGES_RAW}
|
|
593
|
+
)
|
|
594
|
+
lines = sorted({l for _, _, l in LONDON_UNDERGROUND_EDGES_RAW})
|
|
595
|
+
|
|
596
|
+
rng = random.Random(1234)
|
|
597
|
+
all_labels = rng.sample(range(1000), len(stations) + len(lines))
|
|
598
|
+
station_to_label = dict(zip(stations, all_labels[: len(stations)]))
|
|
599
|
+
line_to_label = dict(zip(lines, all_labels[len(stations) :]))
|
|
600
|
+
|
|
601
|
+
edges = [
|
|
602
|
+
(station_to_label[s], line_to_label[l], station_to_label[d])
|
|
603
|
+
for s, d, l in LONDON_UNDERGROUND_EDGES_RAW
|
|
604
|
+
]
|
|
605
|
+
|
|
606
|
+
adjacency = {i: [] for i in range(len(stations))}
|
|
607
|
+
station_idx = {s: i for i, s in enumerate(stations)}
|
|
608
|
+
for s, d, l in LONDON_UNDERGROUND_EDGES_RAW:
|
|
609
|
+
adjacency[station_idx[s]].append((station_idx[d], line_to_label[l]))
|
|
610
|
+
|
|
611
|
+
node_labels = [station_to_label[s] for s in stations]
|
|
612
|
+
return edges, node_labels, adjacency
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# ==========================================
|
|
616
|
+
# DATASET ADAPTER (what core_training.py uses)
|
|
617
|
+
# ==========================================
|
|
618
|
+
class GraphTraversalDataset(BaseDataset):
|
|
619
|
+
name = "graph-traversal"
|
|
620
|
+
input_dim = INPUT_DIM
|
|
621
|
+
output_dim = TRIPLE_DIM
|
|
622
|
+
advance_threshold = ADVANCE_THRESHOLD
|
|
623
|
+
_custom_graph: (
|
|
624
|
+
tuple[list[tuple[int, int, int]], list[int], dict[int, list[tuple[int, int]]]] | None
|
|
625
|
+
)
|
|
626
|
+
_custom_test_graph: (
|
|
627
|
+
tuple[list[tuple[int, int, int]], list[int], dict[int, list[tuple[int, int]]]] | None
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
def __init__(self, dataset_link: str | None = None, test_dataset_link: str | None = None):
|
|
631
|
+
# dataset_link: path to a real edge file (see data/common/graph_io.py
|
|
632
|
+
# for the accepted formats) to TRAIN on, instead of the default
|
|
633
|
+
# per-episode synthetic generate_graph(). None (default) -> unchanged
|
|
634
|
+
# synthetic behavior.
|
|
635
|
+
# test_dataset_link: path to a real edge file to use as the fixed
|
|
636
|
+
# OOD TEST graph, instead of the built-in London Underground -- the
|
|
637
|
+
# graph analogue of text/audio/video's test_dataset_link. None
|
|
638
|
+
# (default) -> the built-in London Underground, unchanged.
|
|
639
|
+
self._custom_graph = load_graph(dataset_link) if dataset_link is not None else None
|
|
640
|
+
self._custom_test_graph = (
|
|
641
|
+
load_graph(test_dataset_link) if test_dataset_link is not None else None
|
|
642
|
+
)
|
|
643
|
+
if self._custom_graph is not None:
|
|
644
|
+
print(
|
|
645
|
+
f"[graph-traversal] training graph loaded from {dataset_link}: "
|
|
646
|
+
f"{len(self._custom_graph[1])} nodes, {len(self._custom_graph[0])} edges"
|
|
647
|
+
)
|
|
648
|
+
if self._custom_test_graph is not None:
|
|
649
|
+
print(
|
|
650
|
+
f"[graph-traversal] test graph loaded from {test_dataset_link}: "
|
|
651
|
+
f"{len(self._custom_test_graph[1])} nodes, {len(self._custom_test_graph[0])} edges"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
def make_curriculum(self):
|
|
655
|
+
return TraversalCurriculum(custom_graph=self._custom_graph)
|
|
656
|
+
|
|
657
|
+
def sample_batch(self, curriculum, batch_size):
|
|
658
|
+
return sample_batch(curriculum, batch_size)
|
|
659
|
+
|
|
660
|
+
def loss(self, output, target, mask):
|
|
661
|
+
return digit_loss(output, target, mask)
|
|
662
|
+
|
|
663
|
+
def diversity(self, output, target, mask):
|
|
664
|
+
return prediction_diversity(output, target, mask)
|
|
665
|
+
|
|
666
|
+
def set_output_proj(self, proj):
|
|
667
|
+
set_output_proj(proj)
|
|
668
|
+
|
|
669
|
+
def evaluate_ood(self, model, device, num_episodes, rng, verbose_n=0, field_log=None):
|
|
670
|
+
if self._custom_test_graph is not None:
|
|
671
|
+
edges, node_labels, adjacency = self._custom_test_graph
|
|
672
|
+
else:
|
|
673
|
+
edges, node_labels, adjacency = build_london_underground_eval()
|
|
674
|
+
return evaluate_traversal(
|
|
675
|
+
model,
|
|
676
|
+
device,
|
|
677
|
+
num_episodes=num_episodes,
|
|
678
|
+
verbose_n=verbose_n,
|
|
679
|
+
field_log=field_log,
|
|
680
|
+
fixed_graph=(edges, node_labels, adjacency, len(node_labels)),
|
|
681
|
+
path_length_range=OOD_PATH_LENGTH_RANGE,
|
|
682
|
+
rng=rng,
|
|
683
|
+
hop_breakdown=True,
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
def _lesson_eval_graph_kwargs(self, curriculum, lesson_idx):
|
|
687
|
+
"""-> kwargs for evaluate_traversal() covering the 'which graph(s)'
|
|
688
|
+
half of a lesson-distribution eval: nodes_range/k_range (synthetic,
|
|
689
|
+
default) or a fixed_graph (when dataset_link supplied a custom
|
|
690
|
+
training graph -- see __init__ above). Either way path_length_range
|
|
691
|
+
still comes from the lesson table."""
|
|
692
|
+
nodes_range, out_degree_range, path_len_range = curriculum.table[lesson_idx]
|
|
693
|
+
if self._custom_graph is not None:
|
|
694
|
+
edges, node_labels, adjacency = self._custom_graph
|
|
695
|
+
return dict(
|
|
696
|
+
fixed_graph=(edges, node_labels, adjacency, len(node_labels)),
|
|
697
|
+
path_length_range=path_len_range,
|
|
698
|
+
)
|
|
699
|
+
return dict(
|
|
700
|
+
nodes_range=nodes_range, k_range=out_degree_range, path_length_range=path_len_range
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def evaluate_id_ablated(self, model, device, curriculum, lesson_idx):
|
|
704
|
+
return evaluate_traversal(
|
|
705
|
+
model,
|
|
706
|
+
device,
|
|
707
|
+
num_episodes=EVAL_BATCH_SIZE,
|
|
708
|
+
verbose_n=0,
|
|
709
|
+
ablate_memory=True,
|
|
710
|
+
**self._lesson_eval_graph_kwargs(curriculum, lesson_idx),
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
def evaluate_id_combiner_stage_ablated(
|
|
714
|
+
self, model, device, curriculum, lesson_idx, skip_stages
|
|
715
|
+
):
|
|
716
|
+
"""Same lesson-distribution eval as evaluate_id_ablated, but bypasses
|
|
717
|
+
one stage of a hybrid split-graph combiner (see
|
|
718
|
+
ChainedControllerWrapper.forward's skip_stages / SplitGraphDNC's
|
|
719
|
+
combiner_skip_stages) instead of Memory. Only meaningful when
|
|
720
|
+
model.combiner_wrapper is a ChainedControllerWrapper."""
|
|
721
|
+
return evaluate_traversal(
|
|
722
|
+
model,
|
|
723
|
+
device,
|
|
724
|
+
num_episodes=EVAL_BATCH_SIZE,
|
|
725
|
+
verbose_n=0,
|
|
726
|
+
model_kwargs={"combiner_skip_stages": skip_stages},
|
|
727
|
+
**self._lesson_eval_graph_kwargs(curriculum, lesson_idx),
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
def field_log_header(self):
|
|
731
|
+
return [
|
|
732
|
+
"path_length",
|
|
733
|
+
"hop_position",
|
|
734
|
+
"n_triples",
|
|
735
|
+
"src_acc",
|
|
736
|
+
"edge_acc",
|
|
737
|
+
"dst_acc",
|
|
738
|
+
"triple_acc",
|
|
739
|
+
"dst_acc_given_src_edge",
|
|
740
|
+
"n_src_edge_correct",
|
|
741
|
+
"src_acc_given_prev_dst_correct",
|
|
742
|
+
"n_prev_dst_correct",
|
|
743
|
+
]
|
|
744
|
+
|
|
745
|
+
def write_field_log(self, writer, file, step, lesson, eval_type, field_log):
|
|
746
|
+
write_field_log(writer, file, step, lesson, eval_type, field_log)
|