diff options
Diffstat (limited to 'worldalign/natural_pipeline.py')
| -rw-r--r-- | worldalign/natural_pipeline.py | 42 |
1 files changed, 36 insertions, 6 deletions
diff --git a/worldalign/natural_pipeline.py b/worldalign/natural_pipeline.py index 1a4f63e..1844a6d 100644 --- a/worldalign/natural_pipeline.py +++ b/worldalign/natural_pipeline.py @@ -53,6 +53,9 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--views", type=int, default=1) parser.add_argument("--moment-degree", type=int, default=2, choices=[2, 3]) parser.add_argument("--third-width", type=int, default=12) + parser.add_argument( + "--phrase-encoding", default="mean", choices=["mean", "structured"] + ) parser.add_argument("--seed", type=int, default=0) parser.add_argument("--output", default="artifacts/vg_5k/natural_pipeline.pt") return parser.parse_args() @@ -160,13 +163,39 @@ def main() -> None: vectors = word_vectors(load_phrases(vg_dir / "text_nodes.jsonl", "region_closed"), args) + width = args.word_vectors + + def encode_phrase(phrase: str) -> np.ndarray | None: + """One vector per region description. + + Averaging every word of a phrase collapses "red bus" and "bus red" + and, worse, mixes the thing named with what is said about it. The + synthetic world never had to face this because its captions were + encoded into separate factor blocks. Splitting the head from its + modifiers recovers part of that structure from English word order + alone -- the last token of an English noun phrase is its head -- which + is a fact about one language, not a claim about the other modality, + so it stays inside Tier 0. + """ + tokens = [t for t in re.findall(r"[a-z]+", phrase.lower()) if t in vectors] + if not tokens: + return None + if args.phrase_encoding == "mean": + return np.mean([vectors[t] for t in tokens], axis=0) + head = vectors[tokens[-1]] + modifiers = ( + np.mean([vectors[t] for t in tokens[:-1]], axis=0) + if len(tokens) > 1 + else np.zeros(width) + ) + return np.concatenate([head, modifiers]) + def language_state(node: str) -> np.ndarray | None: - rows = [] - for phrase in records[node]["region_closed"]: - hits = [vectors[token] for token in re.findall(r"[a-z]+", phrase.lower()) - if token in vectors] - if hits: - rows.append(np.mean(hits, axis=0)) + rows = [ + row + for row in (encode_phrase(p) for p in records[node]["region_closed"]) + if row is not None + ] return np.stack(rows) if rows else None def vision_state(node: str, view: int) -> np.ndarray | None: @@ -245,6 +274,7 @@ def main() -> None: "kept_directions": keep, "weight_power": args.weight_power, "moment_degree": args.moment_degree, + "phrase_encoding": args.phrase_encoding, "field_correlation_at_truth": correlation, "recovery_threshold": 0.9, } |
