1.1 查询数据库

假设有一个程序,让我们输入一个自然语言问题,返回给我们正确的答案:

  1. >>> nltk.data.show_cfg('grammars/book_grammars/sql0.fcfg')
  2. % start S
  3. S[SEM=(?np + WHERE + ?vp)] -> NP[SEM=?np] VP[SEM=?vp]
  4. VP[SEM=(?v + ?pp)] -> IV[SEM=?v] PP[SEM=?pp]
  5. VP[SEM=(?v + ?ap)] -> IV[SEM=?v] AP[SEM=?ap]
  6. NP[SEM=(?det + ?n)] -> Det[SEM=?det] N[SEM=?n]
  7. PP[SEM=(?p + ?np)] -> P[SEM=?p] NP[SEM=?np]
  8. AP[SEM=?pp] -> A[SEM=?a] PP[SEM=?pp]
  9. NP[SEM='Country="greece"'] -> 'Greece'
  10. NP[SEM='Country="china"'] -> 'China'
  11. Det[SEM='SELECT'] -> 'Which' | 'What'
  12. N[SEM='City FROM city_table'] -> 'cities'
  13. IV[SEM=''] -> 'are'
  14. A[SEM=''] -> 'located'
  15. P[SEM=''] -> 'in'

这使我们能够分析 SQL 查询:

  1. >>> from nltk import load_parser
  2. >>> cp = load_parser('grammars/book_grammars/sql0.fcfg')
  3. >>> query = 'What cities are located in China'
  4. >>> trees = list(cp.parse(query.split()))
  5. >>> answer = trees[0].label()['SEM']
  6. >>> answer = [s for s in answer if s]
  7. >>> q = ' '.join(answer)
  8. >>> print(q)
  9. SELECT City FROM city_table WHERE Country="china"

注意

轮到你来:设置跟踪为最大,运行分析器,即cp = load_parser('grammars/book_grammars/sql0.fcfg', trace=3),研究当边被完整的加入到图表中时,如何建立sem的值。

最后,我们在数据库city.db上执行查询,检索出一些结果:

  1. >>> from nltk.sem import chat80
  2. >>> rows = chat80.sql_query('corpora/city_database/city.db', q)
  3. >>> for r in rows: print(r[0], end=" ") ![[1]](/projects/nlp-py-2e-zh/Images/ab3d4c917ad3461f18759719a288afa5.jpg)
  4. canton chungking dairen harbin kowloon mukden peking shanghai sian tientsin

由于每行r是一个单元素的元组,我们输出元组的成员,而不是元组本身[1]

总结一下,我们已经定义了一个任务:计算机对自然语言查询做出反应,返回有用的数据。我们通过将英语的一个小的子集翻译成 SQL 来实现这个任务们可以说,我们的 NLTK 代码已经“理解”SQL,只要 Python 能够对数据库执行 SQL 查询,通过扩展,它也“理解”如 What cities are located in China 这样的查询。这相当于自然语言理解的例子能够从荷兰语翻译成英语。假设你是一个英语为母语的人,已经开始学习荷兰语。你的老师问你是否理解(3)的意思:

  1. >>> nltk.boolean_ops()
  2. negation -
  3. conjunction &
  4. disjunction |
  5. implication ->
  6. equivalence <->

从命题符号和布尔运算符,我们可以建立命题逻辑的规范公式(或简称公式)的无限集合。首先,每个命题字母是一个公式。然后,如果φ是一个公式,那么-φ也是一个公式。如果φ和ψ是公式,那么(φ & ψ) (φ | ψ) (φ -&gt; ψ) (φ &lt;-&gt; ψ)也是公式。

2.1指定了包含这些运算符的公式为真的条件。和以前一样,我们使用φ和ψ作为句子中的变量,iff 作为 if and only if(当且仅当)的缩写。

表 2.1:

命题逻辑的布尔运算符的真值条件。

  1. >>> read_expr = nltk.sem.Expression.fromstring
  2. >>> read_expr('-(P & Q)')
  3. <NegatedExpression -(P & Q)>
  4. >>> read_expr('P & Q')
  5. <AndExpression (P & Q)>
  6. >>> read_expr('P | (R -> Q)')
  7. <OrExpression (P | (R -> Q))>
  8. >>> read_expr('P <-> -- P')
  9. <IffExpression (P <-> --P)>

从计算的角度来看,逻辑给了我们进行推理的一个重要工具。假设你表达 Freedonia is not to the north of Sylvania,而你给出理由 Sylvania is to the north of Freedonia。在这种情况下,你已经给出了一个论证。句子 Sylvania is to the north of Freedonia 是论证的假设,而 Freedonia is not to the north of Sylvania 是结论。从假设一步一步推到结论,被称为推理。通俗地说,就是我们以在结论前面写 therefore 这样的格式写一个论证。

  1. >>> lp = nltk.sem.Expression.fromstring
  2. >>> SnF = read_expr('SnF')
  3. >>> NotFnS = read_expr('-FnS')
  4. >>> R = read_expr('SnF -> -FnS')
  5. >>> prover = nltk.Prover9()
  6. >>> prover.prove(NotFnS, [SnF, R])
  7. True

这里有另一种方式可以看到结论如何得出。SnF -&gt; -FnS在语义上等价于-SnF | -FnS,其中 “|“是对应于 or 的二元运算符。在一般情况下,φ|ψ在条件 s 中为真,要么φ在 s 中为真,要么ψ在 s 中为真。现在,假设SnF-SnF | -FnS都在 s 中为真。如果SnF为真,那么-SnF不可能也为真;经典逻辑的一个基本假设是:一个句子在一种情况下不能同时为真和为假。因此,-FnS必须为真。

回想一下,我们解释相对于一个模型的一种逻辑语言的句子,它们是这个世界的一个非常简化的版本。一个命题逻辑的模型需要为每个可能的公式分配值TrueFalse。我们一步步的来做这个:首先,为每个命题符号分配一个值,然后确定布尔运算符的含义(即2.1)和运用它们到这些公式的组件的值,来计算复杂的公式的值。估值是从逻辑的基本符号映射到它们的值。下面是一个例子:

  1. >>> val = nltk.Valuation([('P', True), ('Q', True), ('R', False)])

我们使用一个配对的链表初始化一个估值,每个配对由一个语义符号和一个语义值组成。所产生的对象基本上只是一个字典,映射逻辑符号(作为字符串处理)为适当的值。

  1. >>> val['P']
  2. True

正如我们稍后将看到的,我们的模型需要稍微更加复杂些,以便处理将在下一节中讨论的更复杂的逻辑形式;暂时的,在下面的声明中先忽略参数domg

  1. >>> dom = set()
  2. >>> g = nltk.Assignment(dom)

现在,让我们用val初始化模型m

  1. >>> m = nltk.Model(dom, val)

每一个模型都有一个evaluate()方法,可以确定逻辑表达式,如命题逻辑的公式,的语义值;当然,这些值取决于最初我们分配给命题符号如PQR的真值。

  1. >>> print(m.evaluate('(P & Q)', g))
  2. True
  3. >>> print(m.evaluate('-(P & Q)', g))
  4. False
  5. >>> print(m.evaluate('(P & R)', g))
  6. False
  7. >>> print(m.evaluate('(P | R)', g))
  8. True

注意

轮到你来:做实验为不同的命题逻辑公式估值。模型是否给出你所期望的值?

到目前为止,我们已经将我们的英文句子翻译成命题逻辑。因为我们只限于用字母如PQ表示原子句子,不能深入其内部结构。实际上,我们说将原子句子分成主语、宾语和谓词并没有语义上的好处。然而,这似乎是错误的:如果我们想形式化如(9)这样的论证,就必须要能“看到里面”基本的句子。因此,我们将超越命题逻辑到一个更有表现力的东西,也就是一阶逻辑。这正是我们下一节要讲的。