summaryrefslogtreecommitdiff
path: root/worldalign/natural_pipeline.py
diff options
context:
space:
mode:
Diffstat (limited to 'worldalign/natural_pipeline.py')
-rw-r--r--worldalign/natural_pipeline.py42
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,
}