Walkthrough Tutorial: Grammar Formalisation and Testing
This walkthrough tutorial accompanies Section 4.1 of the following paper:
Authors. (submitted). PyFCG: Fluid Construction Grammar in Python.
A common use of FCG revolves around the formalisation and computational operationalisation of construction grammar theories and analyses. Not only can computational operationalisations help validate their preciseness and internal consistency, they also facilitate the comparison, exchange and integration of insights from different researchers (van Trijp, Beuls and Van Eecke 2022).
This tutorial exemplifies how PyFCG can be used to equip an agent with a designed grammar, how new constructions can be added to or removed from the agent’s grammar on the fly, how the agent can use its grammar to comprehend and formulate utterances, and how all these processes can be visually inspected through FCG’s graphical web interface. For more information on aspects of the syntax and semantics of FCG that are not particular to the PyFCG module, we refer the interested reader to the syntax and semantics of FCG.
Installing PyFCG
If you haven’t pip-installed PyFCG, you can run the following cell to do so:
[ ]:
! pip install pyfcg
An agent named Sue
After importing and initialising PyFCG, we create a new agent named Sue. Sue, as an instance of the fcg.Agent class, is automatically initialised with an empty grammar. Sue is also assigned a unique identifier.
[1]:
import pyfcg as fcg
fcg.init()
[2]:
sue = fcg.Agent(name='Sue')
sue
[2]:
<Agent: Sue (id: sue-1) ~ 0 constructions>
Sue reads a grammar
Sue can read in a predefined grammar, specified in the Open FCG Exchange Format (OFEF). In this case, we use PyFCG’s load_resource function to download a human-designed demo grammar fragment that specifies six constructions for processing the English resultative sentence “Firefighters cut the child free.”. This grammar fragment uses the Abstract Meaning Representation format (AMR) to represent constructional meaning. We instruct Sue to load the grammar specified in the file by calling their
load_grammar_from_file method and then list the names of the constructions that were loaded.
[3]:
# Download a human-designed demo grammar fragment that specifies six constructions
demo_grammar_spec_file = fcg.load_resource('demo-resultative.json')
[4]:
# Sue reads the grammar from the downloaded file
sue.load_grammar_from_file(demo_grammar_spec_file)
# Printing some information about sue after reading the grammar
print('Sue:')
display(sue)
print()
print("Sue's constructions:")
display(sue.grammar.cxns)
Sue:
<Agent: Sue (id: sue-1) ~ 6 constructions>
Sue's constructions:
{'firefighters-cxn': <Construction: firefighters-cxn (0.5)>,
'child-cxn': <Construction: child-cxn (0.5)>,
'cut-cxn': <Construction: cut-cxn (0.5)>,
'free-cxn': <Construction: free-cxn (0.5)>,
'the-x-cxn': <Construction: the-x-cxn (0.5)>,
'resultative-cxn': <Construction: resultative-cxn (0.5)>}
Let us have a closer look at one of Sue’s constructions, for example the firefighters-cxn. Let us first take the perspective of a machine and look at the raw data structure. We can see the name, entrenchment score, contributing and conditional poles of the construction.
[5]:
firefighters_cxn = sue.grammar.find_cxn_by_name('firefighters-cxn')
display(firefighters_cxn)
print()
print('Name: ' + firefighters_cxn.name)
print('Score: ' + str(firefighters_cxn.get_score()))
print('Contributing pole: ')
display(firefighters_cxn.contributing_pole)
print('Conditional pole: ')
display(firefighters_cxn.conditional_pole)
<Construction: firefighters-cxn (0.5)>
Name: firefighters-cxn
Score: 0.5
Contributing pole:
[['?firefighters-unit',
{'referent': '?p',
'category': 'firefighters-cxn',
'boundaries': ['?left', '?right']}]]
Conditional pole:
[['?firefighters-unit',
{'#meaning': [['person', '?p'],
['fight-01', '?f'],
['fire', '?f2'],
[':arg0-of', '?p', '?f'],
[':arg1', '?f', '?f2']]},
{'#form': [['sequence', '"Firefighters"', '?left', '?right']]}]]
Humans typically prefer to use FCG’s graphical web interface. We launch the web interface and add the firefighters-cxn. You should now be able to inspect the construction in a separate browser window (http://localhost:8010). You can click on the circle that is displayed above the arrow separating the conditional from the contributing pole to inspect the construction.
[6]:
fcg.start_web_interface()
firefighters_cxn.show_in_web_interface()
We can also inspect Sue’s entire grammar in the web interface.
[7]:
sue.grammar.show_in_web_interface()
Sue comprehends and formulates utterances
We now ask Sue to comprehend the utterance “Firefighters cut the child free.”. In FCG, comprehension and formulation respectively refer to the processes of mapping utterances to their meaning representation and vice versa. In order to be able to visually inspect Sue’s comprehension process, we need to activate the standard trace-fcg monitor.
[8]:
fcg.deactivate_all_monitors()
fcg.activate_monitor('trace-fcg')
amr = sue.comprehend('Firefighters cut the child free.')
print(amr)
[['child', '?c-100'], ['cause-01', '?c-115'], ['arg0', '?c-106', '?p-5'], ['arg0-of', '?c-106', '?c-115'], ['arg1', '?c-115', '?f-11'], ['arg1', '?f-11', '?c-100'], ['free-04', '?f-11'], ['cut-01', '?c-106'], ['person', '?p-5'], ['fight-01', '?f-9'], ['fire', '?f2-5'], ['arg0-of', '?p-5', '?f-9'], ['arg1', '?f-9', '?f2-5']]
The resulting AMR meaning representation can be challenging to read for humans in this format. Let’s use the Penman library to print the meaning in a more human-friendly way. We can see that Sue understood that a cutting action in the sense of slice, injure (denoted by PropBank’s cut-01 roleset) was performed by an intentional cutter (arg0 in cut-01), more in particular a person who habitually fights (fight-01) fire (arg1 in fight-01), and that the cutting action
itself led to (arg0-of in cause-01) the unconstrained, unrestricted state (free-04) of a child (arg1 in free-04).
[9]:
print(fcg.predicate_network_to_penman(amr))
(c-106 / cut-01
:arg0 (p-5 / person
:arg0-of (f-9 / fight-01
:arg1 (f2-5 / fire)))
:arg0-of (c-115 / cause-01
:arg1 (f-11 / free-04
:arg1 (c-100 / child))))
Sue can also use their grammar to formulate utterances given a meaning representation. We will reuse the meaning representation from above.
[10]:
utterance = sue.formulate(fcg.instantiate_variables(amr))
''.join(token for token in utterance)
[10]:
'Firefighters cut the child free.'
Alternatively, we can try to comprehend an utterance and immediately formulate the resulting meaning representation in one go:
[11]:
utterance = sue.comprehend_and_formulate("Firefighters cut the child free.")
''.join(token for token in utterance)
[11]:
'Firefighters cut the child free.'
Sue adds a new construction to their grammar
Let us now add a new construction to Sue’s grammar: the dog-cxn that in essence pairs the form “dog” with its AMR meaning of instantiating the dog concept. Along with its name, we specify its contributing and conditional poles, and load it into Sue. We also add a categorial link between the category proper to the dog-cxn and the category of the noun slot in the the-x-cxn, following the design choices made in the demo grammar fragment and very much in the spirit of Radical
Construction Grammar (Croft, 2001).
[12]:
dog_cxn = fcg.Construction(name= 'dog-cxn',
contributing_pole= [('?dog-unit',
{'referent': '?d',
'category': 'dog-cxn',
'boundaries': ('?left', '?right')})],
conditional_pole= [('?dog-unit',
{'#meaning': [('dog', '?d')]},
{'#form': [('sequence', '"dog"', '?left', '?right')]})])
sue.add_cxn(dog_cxn)
sue.add_category('dog-cxn')
sue.add_link('dog-cxn', 'the-x-cxn-x')
sue
[12]:
<Agent: Sue (id: sue-1) ~ 7 constructions>
We now ask Sue to use their grammar to formulate an utterance that expresses that a cutting action in the sense of slice, injure was performed by an intentional cutter, more in particular a person who habitually fights fire, and that the cutting action itself led to the unconstrained, unrestricted state of a dog:
[13]:
amr_penman = '(c / cut-01 :arg0 (p / person :arg0-of (f / fight-01 :arg1 (f2 / fire))) :arg0-of (c2 / cause-01 :arg1 (f3 / free-04 :arg1 (d / dog))))'
amr_pnw = fcg.penman_to_predicate_network(amr_penman)
form = sue.formulate(amr_pnw)
''.join(token for token in form)
[13]:
'Firefighters cut the dog free.'
We can see that Sue produces the utterance “Firefighters cut the dog free.”. Again, the construction application process can be traced in detail in the web interface.