<jupyter_start><jupyter_text>Analyzing the Meaning of Sentences Natural Language Understanding Querying a DatabaseSuppose we wanted to ask the question `What cities are located in China?` and the information is present in a Database that can be queried using SQL. We can use a feature based grammar that parses a sentence in English to SQL as below:<jupyter_code>import nltk
nltk.data.show_cfg('grammars/book_grammars/sql0.fcfg')<jupyter_output>% start S
S[SEM=(?np + WHERE + ?vp)] -> NP[SEM=?np] VP[SEM=?vp]
VP[SEM=(?v + ?pp)] -> IV[SEM=?v] PP[SEM=?pp]
VP[SEM=(?v + ?ap)] -> IV[SEM=?v] AP[SEM=?ap]
NP[SEM=(?det + ?n)] -> Det[SEM=?det] N[SEM=?n]
PP[SEM=(?p + ?np)] -> P[SEM=?p] NP[SEM=?np]
AP[SEM=?pp] -> A[SEM=?a] PP[SEM=?pp]
NP[SEM='Country="greece"'] -> 'Greece'
NP[SEM='Country="china"'] -> 'China'
Det[SEM='SELECT'] -> 'Which' | 'What'
N[SEM='City FROM city_table'] -> 'cities'
IV[SEM=''] -> 'are'
A[SEM=''] -> 'located'
P[SEM=''] -> 'in'<jupyter_text>Parsing the query into SQL:<jupyter_code>from nltk import load_parser
cp = load_parser("grammars/book_grammars/sql0.fcfg")
query = "What cities are located in China"
trees = list(cp.parse(query.split()))
answer = trees[0].label()['SEM']
answer = [s for s in answer if s]
q = " ".join(answer)
print(q)<jupyter_output>SELECT City FROM city_table WHERE Country="china"<jupyter_text>Executing the query over a pre-loaded database:<jupyter_code>from nltk.sem import chat80
rows = chat80.sql_query('corpora/city_database/city.db', q)
for r in rows: print(r[0], end=" ")<jupyter_output>canton chungking dairen harbin kowloon mukden peking shanghai sian tientsin<jupyter_text>Propositional LogicPropositional logic allows us to represent those parts of linguistic structure which correspond to certain sentential connectives.The basic expressions of propositional logic are propositional symbols, often written as P, Q, R, etc. There are varying conventions for representing boolean operators. Since we will be focusing on ways of exploring logic within NLTK, we will stick to the following ASCII versions of the operators. Examples below:<jupyter_code>nltk.boolean_ops()<jupyter_output>negation       	-
conjunction    	&
disjunction    	|
implication    	->
equivalence    	<-><jupyter_text>NLTKs Expression object can process logical expressions into various subclasses of Expression:<jupyter_code>read_expr = nltk.sem.Expression.fromstring
read_expr("-(P & Q)")
read_expr("P & Q")
read_expr("P | (R -> Q)")
read_expr("P <-> -- P")<jupyter_output><empty_output><jupyter_text>**Syntactic Validity**Arguments can be checked for "syntactic validity" by using a proof system.<jupyter_code>SnF = read_expr("SnF")
NotFnS = read_expr('-FnS')
R = read_expr('SnF -> -FnS')
prover = nltk.Prover9()
prover.prove(NotFnS, [SnF, R])<jupyter_output><empty_output><jupyter_text>**Valuations**A Valuation is a mapping from basic expressions of the logic to their values. Here's an example:<jupyter_code>val = nltk.Valuation([('P', True), ('Q', True), ('R', False)])
val['P']
# let's initialize a model m that uses val
dom = set()
g = nltk.Assignment(dom)
m = nltk.Model(dom, val)
print(m.evaluate('(P & Q)', g))
print(m.evaluate('-(P & Q)', g))
print(m.evaluate('(P & R)', g))
print(m.evaluate('(P | R)', g))<jupyter_output>True<jupyter_text>First-Order LogicFirst-order logic keeps all the boolean operators of Propositional Logic. But it adds some important new mechanisms.The standard construction rules for first-order logic recognize terms such as individual variables and individual constants, and predicates which take differing numbers of arguments. For example, Angus walks might be formalized as *walk(angus)* and Angus sees Bertie as *see(angus, bertie)*. We will call walk a unary predicate, and see a binary predicate.  SyntaxThe usual way of expressing first-order logic syntax is to assign **types** to expressions. We will use two basic types: e is the type of entities, while t is the type of formulas, i.e., expressions which have truth values. Given these two basic types, we can form complex types for function expressions. That is, given any types σ and τ, 〈σ, τ〉 is a complex type corresponding to functions from 'σ things' to 'τ things'. For example, 〈e, t〉 is the type of expressions from entities to truth values, namely unary predicates.<jupyter_code>read_expr = nltk.sem.Expression.fromstring
expr = read_expr("walk(Angus)", type_check=True)
expr.argument
expr.argument.type
expr.function
expr.function.type<jupyter_output><empty_output><jupyter_text>To help the type-checker, we need to specify a signature, implemented as a dictionary that explicitly associates types with non-logical constants:<jupyter_code>sig = {"walk": "<e, t>"}
expr = read_expr("walk(angus)", signature=sig)
expr.function.type<jupyter_output><empty_output><jupyter_text>In first-order logic, arguments of predicates can also be individual variables such as x, y and z. In NLTK, we adopt the convention that variables of type e are all lowercase.In general, an occurrence of a variable x in a formula φ is free in φ if that occurrence doesn't fall within the scope of all x or some x in φ. Conversely, if x is free in formula φ, then it is bound in all x.φ and exists x.φ. If all variable occurrences in a formula are bound, the formula is said to be closed.We mentioned before that the Expression object can process strings, and returns objects of class Expression. Each instance expr of this class comes with a method free() which returns the set of variables that are free in expr.<jupyter_code>read_expr("dog(cyril)").free()
read_expr("dog(x)").free()
read_expr("exists x.dog(x)").free()
read_expr('((some x. walk(x)) -> sing(x))').free()
read_expr('exists x.own(y, x)').free()<jupyter_output><empty_output><jupyter_text>First Order Theorem ProvingLet's use a first order logic to formalize rules such as `all x. all y.(north_of(x, y) -> -north_of(y, x))`<jupyter_code>NotFnS = read_expr("-north_of(f,s)")
SnF = read_expr("north_of(s,f)")
R = read_expr("all x. all y.(north_of(x, y) -> -north_of(y, x))")
prover = nltk.Prover9()
prover.prove(NotFnS, [SnF, R])
FnS = read_expr("north_of(f,s)")
prover.prove(FnS, [SnF, R])<jupyter_output><empty_output><jupyter_text>Truth in ModelRelations are represented semantically in NLTK in the standard set-theoretic way: as sets of tuples. For example, let's suppose we have a domain of discourse consisting of the individuals Bertie, Olive and Cyril, where Bertie is a boy, Olive is a girl and Cyril is a dog. For mnemonic reasons, we use b, o and c as the corresponding labels in the model. We can declare the domain as follows:<jupyter_code>dom = {"b", "o", "c"}
v = """
    bertie => b
    olive => o
    cyril => c
    boy => {b}
    girl => {o}
    dog => {c}
    walk => {o, c}
    see => {(b, o), (c, b), (o, c)}
    """
val = nltk.Valuation.fromstring(v)
print(val)<jupyter_output>{'bertie': 'b',
 'boy': {('b',)},
 'cyril': 'c',
 'dog': {('c',)},
 'girl': {('o',)},
 'olive': 'o',
 'see': {('b', 'o'), ('o', 'c'), ('c', 'b')},
 'walk': {('c',), ('o',)}}<jupyter_text>Individual Variables and AssignmentsA variable assignment is a mapping from individual variables to entities in the domain. Assignments are created using the Assignment constructor, which also takes the model's domain of discourse as a parameter.<jupyter_code>g = nltk.Assignment(dom, [('x', 'o'), ('y', 'c')])
g
print(g)<jupyter_output>g[c/y][o/x]<jupyter_text>Evaluating an atomic formula of first order logic:<jupyter_code>m = nltk.Model(dom, val)
m.evaluate("see(olive, y)", g)<jupyter_output><empty_output><jupyter_text>In the above cell, we are evaluating a formula similar to our earlier examplle, see(olive, cyril). However, when the interpretation function encounters the variable y, rather than checking for a value in val, it asks the variable assignment g to come up with a value:<jupyter_code>g['y']<jupyter_output><empty_output><jupyter_text>Checking the inverse relation:<jupyter_code>m.evaluate("see(y, x)", g)<jupyter_output><empty_output><jupyter_text>The method `purge()` clears all bindings from an assignment:<jupyter_code>g.purge()
g<jupyter_output><empty_output><jupyter_text>If we not try to evaulate the formula `see(olive, y)`, it is like trying to interpret a sentence conatining a him, and we don't know what him refers to.<jupyter_code>m.evaluate("see(olve, y)", g)<jupyter_output><empty_output><jupyter_text>Since our models already contain rules for interpreting boolean operators, arbitrarily complex formulas can be composed and evaluated.<jupyter_code>m.evaluate('see(bertie, olive) & boy(bertie) & -walk(bertie)', g)<jupyter_output><empty_output><jupyter_text>The general process of determining truth or falsity of a formula in a model is called model checking.  QuantificationConsider the following:<jupyter_code>m.evaluate("exists x.(girl(x) & walk(x))", g)<jupyter_output><empty_output><jupyter_text>evaluate() returns True here because there is some u in dom such that the formila is satisfied by an assignment which binds x to u. In fact, o is such a u:<jupyter_code>m.evaluate('girl(x) & walk(x)', g.add('x', 'o'))<jupyter_output><empty_output><jupyter_text>One useful tool offered by NLTK is the `satisfiers()` method. This returns a set of all the individuals that satisfy an open formula.<jupyter_code>fmla1 = read_expr("girl(x) | boy(x)")
m.satisfiers(fmla1, "x", g)
fmla2 = read_expr("girl(x) -> walk(x)")
m.satisfiers(fmla2, "x", g)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13037506603275226, 'fraction_numerical': 0.0014791336502905442, 'mean_word_length': 5.814974802015839, 'pattern_counts': {'":': 1, '<': 85, '<?xml version=': 0, '>': 113, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '657812', 'license': 'MIT', 'n_tokens_mistral': 3275, 'n_tokens_neox': 3136, 'n_words': 1078, 'path': '.ipynb_checkpoints/10. Analyzing the Meaning of Sentences-checkpoint.ipynb', 'repo_name': 'sajal2692/working-through-nltk', 'size': '9465'}