From Text to Meaning

From text to meaning

A type-driven Dutch treebank and its applications

LSD 2021

Kokos

This presentation and instructions to run it @ github.com/konstantinosKokos/Presentations/LSD2021

Tiny bit of theory


The simply typed linear lambda calculus λ⊸\lambda^\multimap

Terms of the form t=varα|(t1α⊸βt2α)β|(λxα.tβ)α⊸β t = var^{\alpha} \ | \ {(t_1^{\alpha \multimap \beta} \ t_2^{\alpha})}^\beta \ | \ {(\lambda x^{\alpha}.t^{\beta})}^{\alpha \multimap \beta}

Can capture FA-structures in tectosyntax..

(believeds⊸np⊸s((wasnp⊸np⊸s(awitch)np)(hisneighbor)np))Albertnp \left( believed^{s\multimap np \multimap s} \left( \left( was^{np \multimap np \multimap s} \ \left( {a\ witch} \right)^{np} \right)\ \left( {his\ neighbor} \right)^{np} \right) \right) \ {Albert}^{np}

..inlcuding higher-order phenomena (!)..

(persistednp⊸s(behindnp⊸(np⊸s)⊸np⊸s(closednp⊸npeyesnp)))((that(np⊸s)⊸np⊸np(λx.(sawnp⊸np⊸sxnp)henp))(thenp⊸npshapesnp)) \left( persisted^{np\multimap s} \left( behind^{np\multimap (np\multimap s) \multimap np \multimap s} \left( closed^{np\multimap np} eyes^{np} \right) \right) \right)\ \left( \left( that^{(np\multimap s)\multimap np\multimap np} \left( \lambda x. \left( saw^{np\multimap np\multimap s} x^{np} \right)\ he^{np} \right) \right) \left( the^{np\multimap np} shapes^{np} \right) \right)

..but makes no distinction between signature-sharing words

is, saw :: np⊸np⊸snp\multimap np \multimap s

Adding modalities

t=varα|t1♢δα⊸β▵δt2α|▾δ(t1□δ(α⊸β))t2α|... t = var^{\alpha} \ | \ t_1^{\diamondsuit^\delta \alpha \multimap \beta} \ {\vartriangle^\delta} t_2^{\alpha} \ | \ \blacktriangledown^\delta \!\left(t_1^{\Box^\delta (\alpha \multimap \beta)}\right) \ t_2^{\alpha} \ | \ ...

is :: ♢predcnp⊸♢sunp⊸s\diamondsuit^{predc}np \multimap \diamondsuit^{su}np \multimap s

(is▵predc(awitch))▵su(she) \left( is \ \vartriangle^{predc}\!\! \left(a\ witch\right) \right) \vartriangle^{su}\!\! \left( she \right)

saw :: ♢objnp⊸♢sunp⊸s\diamondsuit^{obj}np \multimap \diamondsuit^{su}np \multimap s

(saw▵obj(shapes))▵su(he) \left( saw \ \vartriangle^{obj}\!\! (shapes) \right) \vartriangle^{su}\!\! \left(he \right)

closed :: □mod(np⊸np)\Box^{mod} (np \multimap np)

▾mod(closed)eyes \blacktriangledown^{mod}(closed) \ eyes

...

æthel

automatically extracted theorems from lassy

A conversion of Lassy annotations to tecto-grammatic proofs & terms of the above logic.

First step: loading the dataset

import pickle

# remark: change this to your downloaded dataset path
dataset_path = '/home/kokos/Projects/lassy-tlg-extraction/data/train_dev_test_0.4.dev0.p'

with open(dataset_path, 'rb') as f:
    proofs = sum(pickle.load(f), [])

What does a proof look like..?

some_proof = proofs[1312]
print(some_proof)
ProofNet(proof_frame=Alle:□ᵈᵉᵗ(ɴ → ɴᴘ), films:ɴ, zijn:◊ᵛᶜᴘᴘᴀʀᴛ → ◊ˢᵘɴᴘ → sᴍᴀɪɴ, heel:□ᵐᵒᵈ(ᴀᴘ → ᴀᴘ), barok:ᴀᴘ, versierd:◊ᵖʳᵉᵈᶜᴀᴘ → ᴘᴘᴀʀᴛ ⊢ sᴍᴀɪɴ, axiom_links={(5, 11), (2, 0), (1, 4), (7, 9), (8, 6), (10, 3)}, name='Treebank/WR-P-E-I-0000000332/WR-P-E-I-0000000332.p.4.s.98_0.xml')
..a proof frame

A sequence of words & their corresponding types, together with a conclusion specifying the type of the entire phrase.

print(some_proof.proof_frame)
Alle:□ᵈᵉᵗ(ɴ → ɴᴘ), films:ɴ, zijn:◊ᵛᶜᴘᴘᴀʀᴛ → ◊ˢᵘɴᴘ → sᴍᴀɪɴ, heel:□ᵐᵒᵈ(ᴀᴘ → ᴀᴘ), barok:ᴀᴘ, versierd:◊ᵖʳᵉᵈᶜᴀᴘ → ᴘᴘᴀʀᴛ ⊢ sᴍᴀɪɴ
print(some_proof.proof_frame.get_words())
['Alle', 'films', 'zijn', 'heel', 'barok', 'versierd']
print(some_proof.proof_frame.get_types())
[□ᵈᵉᵗ(ɴ(-,0) → ɴᴘ(+,1)), ɴ(+,2), ◊ᵛᶜᴘᴘᴀʀᴛ(-,3) → ◊ˢᵘɴᴘ(-,4) → sᴍᴀɪɴ(+,5), □ᵐᵒᵈ(ᴀᴘ(-,6) → ᴀᴘ(+,7)), ᴀᴘ(+,8), ◊ᵖʳᵉᵈᶜᴀᴘ(-,9) → ᴘᴘᴀʀᴛ(+,10)]
print(some_proof.proof_frame.conclusion)
sᴍᴀɪɴ(-,11)

a bijection between positive and negative atoms (a compressed representation of a proof)

print(some_proof.axiom_links)
{(5, 11), (2, 0), (1, 4), (7, 9), (8, 6), (10, 3)}
..and a name

(optionally) telling us its lassy origins

print(some_proof.name)
Treebank/WR-P-E-I-0000000332/WR-P-E-I-0000000332.p.4.s.98_0.xml

Proofs ≡ terms

An intuitionistic logic proof is one and the same to a λ-term (or program)

some_proof.print_term(show_words=True)
'((zijn ▵ᵛᶜ((versierd ▵ᵖʳᵉᵈᶜ((▾ᵐᵒᵈ(heel) barok))))) ▵ˢᵘ((▾ᵈᵉᵗ(Alle) films)))'

..modalities (& term decorations) can be dropped to fall back to simple λ⊸\lambda^\multimap terms

some_proof.print_term(show_words=True, show_decorations=False)
'((zijn (versierd (heel barok))) (Alle films))'

The Lexicon

From the full collection of proof frames we can aggregate a mapping Word →\to {Type}

from LassyExtraction.aethel import ProofNet
from LassyExtraction.milltypes import WordType

from collections import Counter, defaultdict

def make_lexicon(proofs: list[ProofNet], simple: bool = False) -> dict[str, Counter[WordType]]:
    c = defaultdict(lambda: Counter())
    for pn in proofs:
        for word, wordtype in zip(pn.proof_frame.get_words(), pn.proof_frame.get_types()):
            wordtype = wordtype.depolarize()
            c[word.lower()][wordtype.decolor() if simple else wordtype] += 1
    return c

simple_lexicon = make_lexicon(proofs, True)
deco_lexicon = make_lexicon(proofs, False)
simple_lexicon['lezen']
Counter({ᴡᴡ: 20,
         ɪɴғ: 11,
         ᴠᴢ → ɪɴғ: 3,
         ʙᴡ → ɪɴғ: 1,
         ɴᴘ → ɪɴғ: 6,
         ᴠɴᴡ → ɪɴғ: 1,
         ᴀᴅᴊ → ssᴜʙ: 1,
         ɴᴘ: 3,
         ᴘᴘ → ɪɴғ: 2,
         ᴠᴢ → ɴᴘ → ɪɴғ: 1,
         ᴡʜsᴜʙ → ɪɴғ: 4,
         ᴠᴢ → ʙᴡ → ɴᴘ → ɪɴғ: 1})

Querying the dataset

We can filter sentences satisfying arbitrary predicates

from typing import Callable

def get_proofs(proofs: list[ProofNet], predicate: Callable[[ProofNet], bool]) -> list[ProofNet]:
    return list(filter(predicate, proofs))

..like containing a specific word, word-type combination, dependency label, etc.

def containing_word(word: str) -> Callable[[ProofNet], bool]:
    def f(pn: ProofNet) -> bool:
        return word in (w.lower() for w in pn.proof_frame.get_words())
    return f

def containing_dep(dep: str) -> Callable[[ProofNet], bool]:
    def f(pn: ProofNet) -> bool:
        return dep in set.union(*[t.colors() for t in pn.proof_frame.get_types()])
    return f

def containing_word_as(word: str, wordtype: WordType) -> Callable[[ProofNet], bool]:
    def f(pn: ProofNet) -> bool:
        return any(map(lambda w, t: w.lower() == word and t.depolarize() == wordtype,
                      pn.proof_frame.get_words(),
                       pn.proof_frame.get_types()))
    return f
from LassyExtraction.milltypes import AtomicType
eenden = get_proofs(proofs, containing_word_as('eenden', AtomicType('N')))

Checking against Lassy

Proofs can be directly compared to the original Lassy annotations

from LassyExtraction.lassy import Lassy, et
from LassyExtraction.viz import ToGraphViz

# remark: change this to point to the LassySmall dir
root_dir = '/home/kokos/Projects/Lassy 4.0/LassySmall'

lassy = Lassy(root_dir = root_dir, ignore='/home/kokos/Projects/lassy-tlg-extraction/LassyExtraction/utils/ignored.txt')
viz = ToGraphViz()


def find_in_lassy(name: str) -> et:
    source, _ = name.split('_')
    return lassy[f'{root_dir}/{source}.xml'][2]
Ignoring 156 samples..
Dataset constructed with 65045 samples.
lassy_tree = find_in_lassy(eenden[0].name)
lassy_tree = find_in_lassy('Treebank/dpc-ind-001645-nl-sen/dpc-ind-001645-nl-sen.p.12.s.1_1')
viz(lassy_tree)

Neural Proof Nets


A neural parser based on a seq2seq module to translate phrases to proof frames and a permutation module to align positive & negative atoms

from Parser.neural.inference import get_model

weights_path = './stored_models/model_weights.model' # remark: change this
device = 'cuda'  # switch

model = get_model(device=device, weights_path=weights_path)
Initializing model...
Initialized.
Loading pre-trained parameters...
Loading model parameters...
Loaded.

-- fun but experimental part --

Computing semantics

from LassyExtraction.terms import *
from LassyExtraction.milltypes import *
from functools import partial
from typing import Callable, TypeVar

Meaning = TypeVar('Meaning')

# some standard types
_np, _s = AtomicType('NP'), AtomicType('S')
_itv = FunctorType(_np, _s)
_tv = FunctorType(_np, _itv)
_adj = FunctorType(_np, _np)
# -- corresponding terms
np = lambda x: Lex(_type=_np, idx=x)
itv = lambda x: Lex(_type=_itv, idx=x)
tv = lambda x: Lex(_type=_tv, idx=x)
adj = lambda x: Lex(_type=_adj, idx=x)
# common sentence structures
s1 = Application(itv(1), np(0))
s2 = Application(Application(tv(1), np(2)), np(0))


def meaning(term: Term, word_meanings: dict[int, Meaning]) -> Meaning:
    if isinstance(term, Lex):
        return word_meanings[term.idx]
    if isinstance(term, Application):
        return meaning(term.functor, word_meanings)(meaning(term.argument, word_meanings))
    raise TypeError('only 0-order implicative fragment please!')

Take 1: model-theoretic semantics

John likes Mary but Mary is a duck!

E = str
T = bool
ET = Callable[[E], T]
EET = Callable[[E], ET]

John:  E = 'John'
Mary:  E = 'Mary'
    
talks: ET = lambda x: x == John
swims: ET = lambda x: True
flies: ET = lambda x: x == Mary
human: ET = lambda x: talks(x)
duck:  ET = lambda x: swims(x) and flies(x)
    
is_a:  EET = lambda y: lambda x: y(x)
likes: EET = lambda y: lambda x: True if x == John and y == Mary else False