13 · Interlingua — Toy UNL Round-Trip#
Dependencies: stdlib only
Runtime: < 1 min CPU
Chapter stub — implementation coming in a future commit.
Time-machine sentences#
Every chapter runs the same 20 held-out test sentences so the quality arc across chapters is directly comparable.
# This chapter is **stdlib only** — nothing to install.
# It runs on any Python 3.8+ with no pip dependencies.
13 · Interlingua MT — UNL Round-Trip#
Interlingua-based MT proposes a universal meaning representation that lies between all natural languages. Translate once into interlingua, then generate any target language — 2N components instead of N² translation pairs.
UNL (Universal Networking Language) was the most ambitious attempt, developed at the UN University in the 1990s. We implement a toy round-trip: EN -> UNL -> DE.
1 · Universal Networking Language — graph representation#
class UNLNode:
def __init__(self, uw, attrs=None):
self.uw = uw
self.attrs = attrs or []
self.relations = {}
def add_rel(self, label, node):
self.relations[label] = node
return self
def __repr__(self):
rels = ", ".join(f"{k}: {v.uw}" for k, v in self.relations.items())
return f"UNLNode({self.uw!r}{chr(44)+chr(32)+rels if rels else chr(41)}" # chr(44)=comma chr(32)=space chr(41)=close-paren
def to_unl_str(node, depth=0):
indent = " " * depth
attr_s = ("." + ".".join(f"@{a}" for a in node.attrs)) if node.attrs else ""
lines = [f"{indent}{node.uw}{attr_s}"]
for rel, child in node.relations.items():
lines.append(f"{indent} {rel}: {to_unl_str(child, depth+2)}")
return "\n".join(lines)
park = UNLNode("park(icl>place)")
dog = UNLNode("dog(icl>animal)")
man = UNLNode("man(icl>person)")
walk = (UNLNode("walk(icl>action)", attrs=["entry","present"])
.add_rel("agt", man).add_rel("obj", dog).add_rel("plc", park))
print("UNL graph for: 'A man is walking his dog in the park.'")
print(to_unl_str(walk))
2 · EN -> UNL encoder (analysis stage)#
CMAP = {
"walk":"walk(icl>action)","walking":"walk(icl>action)",
"play":"play(icl>action)","playing":"play(icl>action)",
"read":"read(icl>action)","reading":"read(icl>action)",
"wait":"wait(icl>action)","waiting":"wait(icl>action)",
"ride":"ride(icl>action)","riding":"ride(icl>action)",
"chase":"chase(icl>action)","chasing":"chase(icl>action)",
"take":"take(icl>action)","taking":"take(icl>action)",
"feed":"feed(icl>action)","feeding":"feed(icl>action)",
"dance":"dance(icl>action)","dancing":"dance(icl>action)",
"sleep":"sleep(icl>action)","sleeping":"sleep(icl>action)",
"sail":"sail(icl>action)","sailing":"sail(icl>action)",
"climb":"climb(icl>action)","climbing":"climb(icl>action)",
"repair":"repair(icl>action)","repairing":"repair(icl>action)",
"carry":"carry(icl>action)","carrying":"carry(icl>action)",
"kick":"kick(icl>action)","kicking":"kick(icl>action)",
"sit":"sit(icl>action)","sitting":"sit(icl>action)",
"run":"run(icl>action)","running":"run(icl>action)",
"prepare":"prepare(icl>action)","preparing":"prepare(icl>action)",
"man":"man(icl>person)","men":"man(icl>person)",
"woman":"woman(icl>person)","child":"child(icl>person)",
"children":"child(icl>person)","tourist":"tourist(icl>person)",
"tourists":"tourist(icl>person)","worker":"worker(icl>person)",
"workers":"worker(icl>person)","firefighter":"firefighter(icl>person)",
"firefighters":"firefighter(icl>person)",
"person":"person(icl>person)","people":"person(icl>person)",
"musician":"musician(icl>person)","couple":"couple(icl>person)",
"chef":"chef(icl>person)","boy":"boy(icl>person)",
"girl":"girl(icl>person)","dog":"dog(icl>animal)",
"cat":"cat(icl>animal)","duck":"duck(icl>animal)",
"ducks":"duck(icl>animal)","flower":"flower(icl>plant)",
"flowers":"flower(icl>plant)",
"park":"park(icl>place)","street":"street(icl>place)",
"lake":"lake(icl>place)","field":"field(icl>place)",
"beach":"beach(icl>place)","kitchen":"kitchen(icl>place)",
"windowsill":"windowsill(icl>place)","cafe":"cafe(icl>place)",
"road":"road(icl>place)","pond":"pond(icl>place)",
"stop":"stop(icl>place)",
"fountain":"fountain(icl>thing)","boat":"boat(icl>thing)",
"bench":"bench(icl>thing)","violin":"violin(icl>thing)",
"ladder":"ladder(icl>thing)","newspaper":"newspaper(icl>thing)",
"wall":"wall(icl>thing)","book":"book(icl>thing)",
"coat":"coat(icl>thing)","ball":"ball(icl>thing)",
"groceries":"grocery(icl>thing)","photographs":"photograph(icl>thing)",
"football":"football(icl>thing)","rain":"rain(icl>thing)",
"chess":"chess(icl>thing)","stairs":"stair(icl>thing)",
"food":"food(icl>thing)","group":"group(icl>thing)",
"flight":"flight(icl>thing)",
}
PREP_ROLES = {"in":"plc","on":"plc","near":"plc","at":"plc",
"by":"plc","through":"plc","up":"plc","against":"plc"}
def encode(sentence):
tokens = sentence.lower().rstrip(" .").split()
verb = next((t for t in tokens if t in CMAP and "action" in CMAP[t]), tokens[0])
vnode = UNLNode(CMAP.get(verb, verb), attrs=["entry","present"])
vidx = tokens.index(verb) if verb in tokens else 0
subj = next((UNLNode(CMAP[t]) for t in tokens[:vidx]
if t in CMAP and "person" in CMAP[t]), None)
if subj: vnode.add_rel("agt", subj)
after = tokens[vidx+1:]
for i, t in enumerate(after):
if t in PREP_ROLES:
loc = next((UNLNode(CMAP[t2]) for t2 in after[i+1:] if t2 in CMAP), None)
if loc: vnode.add_rel(PREP_ROLES[t], loc); break
elif t in CMAP and "action" not in CMAP[t] and "obj" not in vnode.relations:
vnode.add_rel("obj", UNLNode(CMAP[t]))
return vnode
sample = "A man is walking his dog in the park ."
unl = encode(sample)
print(f"Input: {sample}")
print("UNL:")
print(to_unl_str(unl))
3 · UNL -> DE generator (synthesis stage)#
DE = {
"walk(icl>action)": ("gehen", "sg:geht pl:gehen"),
"play(icl>action)": ("spielen","sg:spielt pl:spielen"),
"read(icl>action)": ("lesen","sg:liest pl:lesen"),
"wait(icl>action)": ("warten","sg:wartet pl:warten"),
"ride(icl>action)": ("fahren","sg:faehrt pl:fahren"),
"chase(icl>action)":("jagen","sg:jagt pl:jagen"),
"take(icl>action)": ("aufnehmen","sg:nimmt pl:nehmen"),
"feed(icl>action)": ("fuettern","sg:fuettert pl:fuettern"),
"dance(icl>action)":("tanzen","sg:tanzt pl:tanzen"),
"sleep(icl>action)":("schlafen","sg:schlaeft pl:schlafen"),
"sail(icl>action)": ("segeln","sg:segelt pl:segeln"),
"climb(icl>action)":("klettern","sg:klettert pl:klettern"),
"repair(icl>action)":("reparieren","sg:repariert pl:reparieren"),
"carry(icl>action)":("tragen","sg:traegt pl:tragen"),
"kick(icl>action)": ("treten","sg:tritt pl:treten"),
"sit(icl>action)": ("sitzen","sg:sitzt pl:sitzen"),
"run(icl>action)": ("laufen","sg:laeuft pl:laufen"),
"prepare(icl>action)":("zubereiten","sg:bereitet_zu pl:bereiten_zu"),
"man(icl>person)":"ein Mann","woman(icl>person)":"eine Frau",
"child(icl>person)":"ein Kind","tourist(icl>person)":"ein Tourist",
"worker(icl>person)":"ein Arbeiter","firefighter(icl>person)":"Feuerwehrleute",
"person(icl>person)":"eine Person","musician(icl>person)":"ein Musiker",
"couple(icl>person)":"ein Paar","chef(icl>person)":"ein Koch",
"boy(icl>person)":"ein Junge","girl(icl>person)":"ein Maedchen",
"dog(icl>animal)":"ein Hund","cat(icl>animal)":"eine Katze",
"duck(icl>animal)":"eine Ente","flower(icl>plant)":"eine Blume",
"park(icl>place)":"einem Park","street(icl>place)":"einer Strasse",
"lake(icl>place)":"einem See","field(icl>place)":"einem Feld",
"beach(icl>place)":"einem Strand","kitchen(icl>place)":"einer Kueche",
"windowsill(icl>place)":"einem Fensterbrett","cafe(icl>place)":"einem Cafe",
"road(icl>place)":"einer Strasse","pond(icl>place)":"einem Teich",
"stop(icl>place)":"einer Haltestelle",
"fountain(icl>thing)":"einem Brunnen","boat(icl>thing)":"ein Boot",
"bench(icl>thing)":"einer Bank","violin(icl>thing)":"eine Geige",
"ladder(icl>thing)":"eine Leiter","newspaper(icl>thing)":"eine Zeitung",
"wall(icl>thing)":"eine Mauer","book(icl>thing)":"ein Buch",
"coat(icl>thing)":"einem Mantel","ball(icl>thing)":"einen Ball",
"grocery(icl>thing)":"Einkaeufe","photograph(icl>thing)":"Fotos",
"football(icl>thing)":"einen Fussball","rain(icl>thing)":"dem Regen",
"chess(icl>thing)":"Schach","stair(icl>thing)":"einer Treppe",
"food(icl>thing)":"Essen","group(icl>thing)":"eine Gruppe",
"flight(icl>thing)":"einer Treppe",
}
PLURAL = {"Feuerwehrleute","Einkaeufe","Fotos","Schach","Essen"}
def get_verb(uw, plural):
entry = DE.get(uw, ("---","sg:--- pl:---"))
if isinstance(entry, tuple):
key = "pl:" if plural else "sg:"
v = next((p.split(":")[1] for p in entry[1].split() if p.startswith(key)), entry[0])
return v.replace("_", " ")
return entry
def get_noun(uw):
entry = DE.get(uw, uw)
return entry if isinstance(entry, str) else uw
def generate_de(node):
subj = node.relations.get("agt")
obj = node.relations.get("obj")
plc = node.relations.get("plc")
subj_str = get_noun(subj.uw) if subj else ""
is_pl = subj_str in PLURAL
verb_str = get_verb(node.uw, is_pl)
parts = [p for p in [subj_str, verb_str,
get_noun(obj.uw) if obj else "",
("in " + get_noun(plc.uw)) if plc else ""] if p]
return " ".join(parts).capitalize() + "."
sample = "A man is walking his dog in the park ."
unl = encode(sample)
de = generate_de(unl)
print("EN: " + sample)
a = unl.relations.get("agt", None)
o = unl.relations.get("obj", None)
p = unl.relations.get("plc", None)
print("UNL: " + unl.uw)
print(" agt: " + (a.uw if a else "-"))
print(" obj: " + (o.uw if o else "-"))
print(" plc: " + (p.uw if p else "-"))
print("DE: " + de)
4 · N languages, 2N components — the scaling argument#
cols = ("N langs", "Direct (N^2)", "Interlingua (2N)")
print(f"{cols[0]:>8} {cols[1]:>14} {cols[2]:>18}")
print("-" * 46)
for n in [2, 5, 10, 20, 50, 100, 200]:
d, il = n*(n-1), 2*n
print(f"{n:8d} {d:14d} {il:18d}")
print()
print("At 200 languages: 39,800 direct pairs vs 400 interlingua components.")
print("The catch: building a truly universal semantic representation")
print("is arguably AI-complete -- the core challenge UNL never solved.")
5 · Time-machine — EN -> UNL -> DE round-trip#
TM = [
"A man is walking his dog in the park .",
"Two children are playing near a fountain .",
"A woman in a red coat is reading a book .",
"Several people are waiting at a bus stop .",
"A cyclist is riding through a crowded street .",
"A dog is chasing a ball on the beach .",
"A group of tourists is taking photographs .",
"A young girl is feeding ducks by a pond .",
"Two men are playing chess in a cafe .",
"A street musician is playing the violin .",
"Children are running through a field of flowers .",
"An old man is sitting on a bench reading a newspaper .",
"A woman is carrying groceries up a flight of stairs .",
"A boy is kicking a football against a wall .",
"A couple is dancing on an empty street .",
"A chef is preparing food in an outdoor kitchen .",
"A cat is sleeping on a warm windowsill .",
"Workers are repairing a road in the rain .",
"A small boat is sailing on a calm lake .",
"Firefighters are climbing a tall ladder .",
]
print("=== Chapter 13 * Time-Machine: Interlingua EN -> UNL -> DE ===")
print()
for s in TM:
de = generate_de(encode(s))
print(f" EN: {s}")
print(f" DE: {de}")
print()
print("Quality verdict: coherent on simple SVO sentences;")
print("struggles with modifiers -- exactly what defeated real interlingua projects.")