<jupyter_start><jupyter_text>段落语义层段落是句子的序列，很多时候，段落中的一个句子的解释依赖它前面的句子。一个明显的例子来自代词，如 he、she 和 it。给定一个段落：Angus used to have a dog. But he recently disappeared. 我们可能会解释 he 指代的是 Angus 的狗；然而在 Angus used to have a dog. He took him for walks in New Town. 中，你更可能解释 he 指的是 Angus 自己。 段落表示理论一阶逻辑中量化的标准方法仅限于单个句子，但是量词的范围其实可以扩大到两个或两个以上的句子：    a. Angus owns a dog. It bit Irene.    b. ∃x.(dog(x) & own(Angus, x) & bite(x, Irene))也就是说，a dog 的作用像一个绑定第二句话中的 it 的量词。**段落表示理论**（Discourse Representation Theory，DRT）的目标是提供一种方法处理这个问题和其它有关段落特征的语义现象。一个**段落表示结构**（Discourse Representation Structure，DRS）根据一个**段落指称**（段落中正在讨论的事情，对应一阶逻辑的单个变量）列表和一个 **DRS 条件**（应用于段落指称的条件，对应于一阶逻辑的原子开放公式）列表表示段落的意思。![drs1.png](resources/drs1.png)在上面的例子中，左侧的 DRS 表示段落中第一句话的处理结果。在处理第二句时，以左侧的 DRS 中已呈现的上下文背景进行解释。代词 it 触发另外一个新的段落指称 u，我们需要为它找一个**先行词**，也就是算出 it 指的是什么。y 是显而易见的选择，因此了一个新的条件 u = y，第二句贡献的其它内容也与第一句的内容合并，如右侧图所示。我们可以计算上图右侧的 DRS 的真值，如果有实体 a、c 和 i，且满足 DRS 中的所有条件，那么整个 DRS 就是真的。也就是说，a 是 Angus，c 是 a dog，i 是 Irene。为了处理 DRS 计算，我们需要将其转换成线性格式。下面是一个例子，其中 DRS 是由一个段落指称链表和一个 DRS 条件链表组成的配对：<jupyter_code>import nltk
read_dexpr = nltk.sem.DrtExpression.fromstring
drs1 = read_dexpr('([x, y], [angus(x), dog(y), own(x, y)])')
print(drs1)
drs1.draw()<jupyter_output>([x,y],[angus(x), dog(y), own(x,y)])<jupyter_text>![drs_screenshot0.png](resources/drs_screenshot0.png)我们在讨论 DRS 的真值条件时，假设其段落指称被解释为存在量词，条件链表需要被联合满足。可以通过 fol() 方法将每一个 DRS 转换成一阶逻辑公式：<jupyter_code>print(drs1.fol())<jupyter_output>exists x y.(angus(x) & dog(y) & own(x,y))<jupyter_text>作为一阶逻辑表达式功能的补充，DRT 表达式有 DRS 连接运算符，用 + 表示。两个 DRS 连接后形成一个单独的 DRS，其中包含合并的段落指称和来自两个 DRS 的条件，α-转换会自动进行来避免名称冲突：<jupyter_code>drs2 = read_dexpr('([x], [walk(x)]) + ([y], [run(y)])')
print(drs2)
print(drs2.simplify())<jupyter_output>(([x],[walk(x)]) + ([y],[run(y)]))
([x,y],[walk(x), run(y)])<jupyter_text>虽然迄今为止见到的所有条件都是原子的，我们也可以将一个 DRS 嵌入到另一个 DRS 中，全称量词就是这样处理的。在 drs3 中，没有顶层的段落指称，唯一的条件是由两个子 DRS 组成，通过蕴含连接：<jupyter_code>drs3 = read_dexpr('([], [(([x], [dog(x)]) -> ([y],[ankle(y), bite(x, y)]))])')
print(drs3.fol())<jupyter_output>all x.(dog(x) -> exists y.(ankle(y) & bite(x,y)))<jupyter_text>我们较早前指出 DRT 旨在通过链接代词和现有的段落指称来解释代词，DRT 设置约束条件使段落指称可以像先行词那样“可访问”，但并不打算解释一个特殊的先行词使如何被从候选集合中选出的。模块 nltk.sem.drt_resolve_anaphora 采用了类似的保守策略：如果 DRS 包含 PRO(x) 形式的条件，方法 resolve_anaphora() 会将其替换为 x=[...] 形式的条件，其中 [...] 是可能的先行词链表：<jupyter_code>drs4 = read_dexpr('([x, y], [angus(x), dog(y), own(x, y)])')
drs5 = read_dexpr('([u, z], [PRO(u), irene(z), bite(u, z)])')
drs6 = drs4 + drs5
print(drs6.simplify())
print(drs6.simplify().resolve_anaphora())<jupyter_output>([u,x,y,z],[angus(x), dog(y), own(x,y), PRO(u), irene(z), bite(u,z)])
([u,x,y,z],[angus(x), dog(y), own(x,y), (u = [x,y,z]), irene(z), bite(u,z)])<jupyter_text>我们对 DRS 的处理与处理 λ-抽象的机制是完全兼容的，因此可以直接基于 DRT 而不是一阶逻辑建立组合语义表示。这种技术在下面的不确定性规则（文法 drt.fcfg 的一部分）中说明，为了便于比较，我们已经为 simplesem.fcfg 增加了不确定性的平行规则。    Det[num=sg,SEM=] -> 'a'    Det[num=sg,SEM=] -> 'a'为了对 DRT 规则如何运作有更好的了解，可以参考下面的 NP（a dog）的子树：    (NP[num='sg', SEM=]        (Det[num='sg', SEM=] a)        (Nom[num='sg', SEM=]            (N[num='sg', SEM=] dog)))))            为了解析文法 drt.fcfg，我们在 load_parser() 调用中指定特征结构中的 SEM 值用 DrtParser 解析来替代默认的 LogicParser：<jupyter_code>from nltk import load_parser
parser = load_parser('grammars/book_grammars/drt.fcfg', logic_parser=nltk.sem.drt.DrtParser())
trees = list(parser.parse('Angus owns a dog'.split()))
print(trees[0].label()['SEM'].simplify())<jupyter_output>([x,z3],[Angus(x), dog(z3), own(x,z3)])<jupyter_text>段落处理我们解释一句话时会使用丰富的上下文知识，一部分取决于前面的内容，一部分取决于我们的背景假设。DRT 提供了一个句子的含义如何集成到前面段落表示中的理论，但在前面的讨论中明显缺少这两个部分。首先，一直没有尝试纳入任何一种推理；第二，我们只处理了个别句子，这些遗漏可以通过模块 nltk.inference.discourse 来纠正。我们把段落看作句子的序列 s1,...,sn，那么段落线程就是读法的序列 s1-ri,...sn-rj，每个序列对应段落中的一个句子。模块按增量处理句子，当有歧义时保持追踪所有可能的线程。为简单起见，下面的例子中忽略了范围歧义：<jupyter_code>dt = nltk.DiscourseTester(['A student dances', 'Every student is a person'])
dt.readings()<jupyter_output>s0 readings:

s0-r0: exists z1.(student(z1) & dance(z1))

s1 readings:

s1-r0: all z1.(student(z1) -> person(z1))<jupyter_text>一个新句子添加到当前的段落时，设置参数 consistchk=True 可以对每个段落线程进行一致性检查，这样用户就可以选择撤回有问题的句子：<jupyter_code>dt.add_sentence('No person dances', consistchk=True)
dt.retract_sentence('No person dances', verbose=True)<jupyter_output>Current sentences are 
s0: A student dances
s1: Every student is a person<jupyter_text>类似的，使用 informchk=True 可以检查新的句子是否对当前段落有信息量：<jupyter_code>dt.add_sentence('A person dances', informchk=True)<jupyter_output>Sentence 'A person dances' under reading 'exists x.(person(x) & dance(x))':
Not informative relative to thread 'd0'
{'fraction_non_alphanumeric': 0.17062347459507432, 'fraction_numerical': 0.009318837364100288, 'mean_word_length': 8.470588235294118, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '95505', 'license': 'MIT', 'n_tokens_mistral': 2839, 'n_tokens_neox': 3109, 'n_words': 316, 'path': '10.5-discourse-semantics.ipynb', 'repo_name': 'pen-x/natural-language-processing-with-python', 'size': '4507'}