ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

谓词逻辑入门:从离散数学到SQL与编程中的谓词应用

谓词逻辑入门:从离散数学到SQL与编程中的谓词应用 开门见山我教离散数学这些年被学生问得最多的一个问题就是定义中的“谓词”到底是个什么东西很多人前面学命题还顺顺利利一到谓词就懵了。其实你只要记住一句话谓词就是一个带着“槽位”的断言槽位填上具体的对象整个断言才有真假。这篇文章我会把谓词这个概念从语法、数学、一阶逻辑表示到编程和数据库里的应用一层层拆开讲清楚。适合正在学离散数学、数理逻辑、数据库原理的初学者也适合想搞懂怎么把自然语言转成逻辑表达式的朋友参考。1. 从“谓语”到“谓词”先搞清楚定义中的那个词1.1 为什么它叫“谓词”你可以回想初中语文课的主谓宾。句子“张三是一个学生”里“张三”是主语“是一个学生”是谓语。英文里谓语是 predicate翻译到逻辑学里就成了“谓词”。逻辑学家干了一件很聪明的事把“是一个学生”这个谓语抽出来变成一个带空位的模式——Student(x)这个模式的 x 就是槽位。当你把“张三”填进去得到 Student(张三)这时候才变成一个可以判断真假的命题。所以从语言学的“谓语”到逻辑学的“谓词”本质上是把“一个主语加上一个动作或性质”变成了“一个对象加上一个可填充的属性”。这个过程叫谓词化。要注意谓词本身不是命题它只是命题的一个函数壳。没有填入具体对象的谓词就像没通电的灯泡你看不到它亮不亮只有把对象代入灯泡才真正发光。1.2 谓词的形式化定义数学上谓词可以定义为一个从个体域到集合 {真假} 的映射。个体域就是你讨论问题的范围比如全体自然数、全体人类、全体点集。一元谓词 P(x) 表示“ x 具有性质 P ”二元谓词 Q(x,y) 表示“ x 与 y 有关系 Q ”n 元谓词就表示 n 个对象之间的关系。这里有个特别容易踩的坑谓词不是命题。因为 x 还是一个自由变项没有确定的对象。比如 P(x) 表示“ x 是素数”单独写 P(x) 你不能问它是真是假因为 x 到底是谁还不知道。只有把 x 代成具体数字比如 P(7)或者用量词约束它比如“存在一个 x 使得 P(x)”这时候才变成命题。我在批改作业时见过很多人把 P(x) 直接当命题用然后去推“P(x) 是真的”这类结论逻辑链条全乱了。这一点如果没想明白后面整个一阶谓词逻辑都是空中楼阁。1.3 谓词和命题的关系命题就是谓词的“实例”我用程序员的语言再说一遍你肯定秒懂谓词相当于函数定义命题相当于函数调用后的返回值。比如写一个判断偶数的函数def is_even(n): return n % 2 0这里的is_even就是一个谓词它接收一个参数n。当你调用is_even(4)时返回True这就是一个真命题调用is_even(7)返回False就是假命题。如果函数不带参数比如is_even()你根本没法求值。回到“定义中的谓词”这句话。数学里给一个概念下定义时经常用的是带参数的条件。比如定义“ x 是偶数”存在整数 k使得 x 2k。注意这里的“∃k∈Zx2k”本身是一个谓词它把 x 暴露在外面。只有确定了某个具体的 x比如“4是偶数”它才是一个命题。所以你翻开数学书看到定义里大量使用“对任意”“存在”这些词本质上都是在描述一个谓词的性质。理解了这一点你看定义就不会再被绕晕。2. 一阶谓词表示方法把一句话变成符号公式2.1 一阶谓词逻辑的四个基本元素一阶谓词逻辑简称一阶逻辑是谓词逻辑里最经典、最常用的一种。很多人听到“一阶”就觉得高深其实它只是说量词只能约束个体变项不能约束谓词或函数。也就是说你可以说“所有 x 都有性质 P”但不能说“存在某个性质 P使得 P(x) 成立”。后者是二阶逻辑的权限一阶逻辑不做这个。一阶逻辑里一共有四类核心符号个体常项具体对象比如 a、b、0、1表示“张三”“数字5”这类确定的事物。个体变项x、y、z表示某个范围内的任一代指具体是什么由量词或上下文决定。谓词一般用大写字母 P、Q、R 表示如 P(x) 表示 x 具有性质 PR(x,y) 表示 x 和 y 有关系 R。量词和联结词∀所有、∃存在以及 ¬非、∧且、∨或、→如果...那么...、↔当且仅当。你只需要记住一条分界线量词后面跟的是个体变项谓词出现在量词辖域的内部。只要不越界你写出来的公式就是合法的一阶谓词公式。2.2 符号化的标准流程把一句中文翻译成谓词公式我通常教学生走五步确定个体域。先搞清楚这句话里讨论的对象范围是什么是所有人、所有数还是所有动物个体域不同公式的写法会差很多。比如“所有人都要吃饭”如果个体域本来就是人可以直接写 ∀x(NeedEat(x))如果个体域是“所有生物”必须写成 ∀x(Human(x)→NeedEat(x))。找出量词信号词。“所有”“每个”“任意”对应 ∀“存在”“有些”“至少有一个”对应 ∃“没有”“不存在”对应 ¬∃ 或者 ∀¬。拆分原子命题。把句子里的“性质”和“关系”分别抽象成谓词。比如“学生喜欢音乐”里“是学生”是一个性质“喜欢音乐”也是一个性质或者把“喜欢”理解为二元关系。安排量词的位置和辖域。量词的位置会改变整个句子的意思特别是多个量词叠在一起时顺序不能乱。不清楚时就把括号写全宁多勿漏。检查自由变项。写完公式之后把所有变项都数一遍看它们是不是都被某个量词约束了。最终公式里不应该出现自由变项除非你想表达的是谓词本身而不是命题。这个方法看起来简单但每一步都有陷阱。下面我拿六个经典句子做一次完整演示。2.3 实操示例六个经典句子的符号化我一直觉得看例子是最快的学习方式。下面我用表格把自然语言、容易犯错的错误写法、正确的写法放在一起你感受一下差别。自然语言错误写法非常容易中招正确写法说明所有人都会死∀x(Man(x)∧Die(x))∀x(Man(x)→Die(x))全称量词后面用→表示“如果是人那么会死”而不是“所有东西都是人并且会死”有些人喜欢音乐∃x(Man(x)→LikeMusic(x))∃x(Man(x)∧LikeMusic(x))存在量词后面用∧表示“有一个人他先是人又喜欢音乐”用→会允许“不存在的人喜欢音乐”也成立所有偶数都能被2整除∀x(Even(x)→Divisible(x,2)) 正确同上这个写法本身是好的注意个体域是自然数或整数没有最大的自然数¬∃x(Natural(x)∧∀y(Natural(y)→x≥y))∀x(Natural(x)→∃y(Natural(y)∧yx))第一种写法也可以但第二种更直观随便给一个自然数总能找到更大的自然数每个学生都有一台电脑∀x(Student(x)→Owns(x, pc))∀x(Student(x)→∃y(Computer(y)∧Owns(x,y)))“有一台电脑”说明电脑也是一个变量不能用常量代替并非所有鸟都会飞∀x(Bird(x)→¬Fly(x))¬∀x(Bird(x)→Fly(x)) 或 ∃x(Bird(x)∧¬Fly(x))错得很经典原句是“不是所有鸟会飞”只否定了“所有”并没有说“所有鸟都不会飞”我看到很多初学者一上来就爱把“所有”写成 ∀x(P(x)∧Q(x))把“有些”写成 ∃x(P(x)→Q(x))。这属于把联结词和量词的搭配关系搞反了。总结成口诀就是全称配条件→存在配合取∧。为什么因为全称量词本质是一种“对域内所有元素做检查”如果检查到某个元素不具备前件整个条件句自动为真这正是我们想要的我们只需要约束“是人”的那些对象存在量词则必须真的存在一个同时满足两个条件的对象用→会连“不存在的东西”也放进来那就失去了存在性断言的意义。2.4 为什么一阶谓词表示方法那么重要你可能觉得这些符号化技巧只是数学考试里的雕虫小技猜个选择题答案就完事了。但往深了看一阶谓词表示方法是计算机理解语义的底层语言。数据库领域里关系代数和 SQL 的查询条件本质上都是谓词知识图谱里实体之间的边就是二元谓词程序验证和模型检查工具用一阶逻辑描述程序的不变式和前置条件人工智能里的规划问题也用谓词逻辑表示状态和动作。更实际一点写 Prolog 语言就是连续不断地声明谓词之间的关系。所以我的建议是形式化能力值得你花点时间练。你不一定要成为逻辑学家但掌握了这套“把自然语言翻译成精确符号”的技能以后阅读任何涉及推理、语义、规则的资料都会比别人快半拍。3. 换个场景编程和数据库里也能遇到“谓词”3.1 SQL 的 WHERE 子句就是一组谓词许多学数据库的同学可能没意识到你天天写的 WHERE 条件其实就是谓词。举个例子SELECT * FROM students WHERE age 18 AND major CS;这里的age 18和major CS都是谓词。数据库引擎会遍历 students 表中的每一行把这一行的数据代入谓词做求值结果为 TRUE 的行被保留FALSE 或 NULL 的行被丢弃。这和我们前面说的 P(x) 完全是一个套路只不过这里的 x 是一条记录。更有意思的是数据库优化器里面的一个概念谓词下推。简单说在 SQL 里你写FROM a JOIN b ON [连接条件] WHERE [过滤条件]优化器会尽量把 WHERE 的过滤条件放到 JOIN 之前执行这样参与连接的数据量更小查询速度更快。理解“谓词就是求值函数”这件事你就能明白为什么谓词下推能提高性能——它就是把一次昂贵的连线操作变成了先按条件削减数据集再做连线本质上是用逻辑等价变换换取执行效率。3.2 编程语言里的谓词函数在函数式编程风格里谓词是清一色的“返回布尔值的一元函数”。Python 的内置函数filter就是典型例子numbers [1, 2, 3, 4, 5, 6] even_numbers list(filter(lambda x: x % 2 0, numbers))这个lambda x: x % 2 0就是一个谓词。加上 Python 的itertools.takewhile、any、all接受的都是谓词。C 的std::find_if、JavaScript 数组的filter方法也都是同样原理。我在写业务代码时特别推崇把谓词抽成命名函数而不是到处写裸的 lambda。比如我会定义is_adult(age)、is_out_of_stock(product)然后传给过滤器。这样阅读代码的人看到is_adult就能立刻理解意图这比一长串比较表达式好懂得多。从逻辑角度讲命名谓词其实就是在给这个布尔函数一个数学定义和你在教科书里写的 P(x) 是对应的。3.3 知识表示和人工智能里的谓词如果你接触过 Prolog就会看到谓词的另一种形态parent(alice, bob). student(bob). likes(bob, music).这里parent、student、likes都是谓词。第一行声明 “alice 和 bob 有 parent 关系”第二行声明 “bob 是学生”第三行声明 “bob 喜欢音乐”。规则也可以写成谓词比如grandparent(X, Z) :- parent(X, Y), parent(Y, Z).这表示“如果 X 是 Y 的父辈Y 是 Z 的父辈则 X 是 Z 的祖父辈”。整个 Prolog 程序就是一大团谓词定义的集合推理引擎通过合一和回溯来查询哪些变项能让谓词为真。这和我们在数学里面写 ∀x∀y∀z((parent(x,y) ∧ parent(y,z)) → grandparent(x,z)) 本质上是一回事。在知识图谱和语义网技术里RDF 三元组(subject, predicate, object)里的 predicate 直接翻译过来也叫谓词只不过它特指实体之间的属性或关系。所以你看谓词不只是一个考场概念它已经是数据、推理、知识表示的基本砖块。4. 谓词符号化避坑指南四个常见大坑与自查方法4.1 量词和否定词打架逻辑里最经典的句型是“并非所有”“不是所有”。比如“不是所有鸟都会飞”很多人把它写成$$ \forall x(Bird(x) \rightarrow \neg Fly(x)) $$意思是“所有鸟都不会飞”——这显然和原句差了十万八千里。原句只是否定“所有鸟会飞”这个断言正确的等价形式是$$ \neg \forall x(Bird(x) \rightarrow Fly(x)) $$再进一步利用量词与否定词的交换规则它等价于$$ \exists x(Bird(x) \wedge \neg Fly(x)) $$也就是“至少有一只鸟它是鸟但不会飞”。这条规则可以类比德摩根定律¬∀x(P(x)) 等价于 ∃x(¬P(x))¬∃x(P(x)) 等价于 ∀x(¬P(x))。每次你看到“并非所有”“没有一个”这样的词都要警惕量词和否定的作用范围宁可在草稿纸上把等价变换写完整也不要凭感觉直接套。4.2 个体域没有统一同一个句子个体域设置不同公式形式完全不同。比如“所有人都要喝水”如果个体域是全体人类可以直接写成$$ \forall x(NeedWater(x)) $$但如果个体域是全体生物你必须加一个限制条件$$ \forall x(Human(x) \rightarrow NeedWater(x)) $$我见过很多同学把这两个写法混着写导致后面整个推导崩塌。所以动笔之前第一件事就是明确个体域并且一直保持到最后。如果一道题没说明个体域默认可能是比较宽泛的集合这时你最好显式写出限制谓词比如 Human(x)、Number(x)保证公式严谨。4.3 量词顺序颠倒导致语义变味“每个学生都喜欢某位教授”和“某位教授被每个学生喜欢”看起来差不多但逻辑含义完全不同。第一句用符号表示$$ \forall x(Student(x) \rightarrow \exists y(Professor(y) \wedge Like(x, y))) $$意思是每个学生都可以有自己的那位“某位教授”张三喜欢王教授李四喜欢刘教授不要求是同一人。第二句则是$$ \exists y(Professor(y) \wedge \forall x(Student(x) \rightarrow Like(x, y))) $$意思是存在一位教授 y所有学生都喜欢这位 y。这是两个非常不同的断言后者强得多。生活里搬个例子每个班都有一个班主任允许不同班有不同班主任和全校有一个人是所有班的班主任天哪那这位老师该多累。所以在你处理多个量词时一定要在脑子里把“存在个体”和“每个个体”的辖域分清楚别让它们偷偷错位。4.4 实操心得三个自查小技巧符号化写完以后别急着交卷。我建议你做三个快速检查。第一去掉量词。把公式里所有的 ∀ 和 ∃ 去掉看看剩下的是一个谓词还是一个有明确真假值的命题。如果剩下的还是谓词说明你没有对某个关键变项做约束这个公式不算完整。第二代入两个具体个体试真值。比如你写了一个关于“学生和电脑”的公式那你就代入“张三是个学生且只有一台电脑”看公式是否在该场景下返回你期望的真假。如果返回不对老老实实调整量词或者联结词。第三做等价变换。尤其看到有否定词和量词连在一起时尝试把 ¬∀ 换成 ∃¬把 ¬∃ 换成 ∀¬观察公式是否仍然符合自然语言的原意。这个动作虽然机械化但能帮你抓出不少隐藏的搭配错误。最后再给一个建立信心的建议不要怕小句子。刚开始时你完全可以把“猫吃鱼”“太阳从东边升起”这种幼儿园句子拿来符号化。练多了你会形成条件反射看到“每个”就想 ∀看到“有些”就想 ∃看到“除非”“只有”就想到条件句的逆否和蕴含方向。这种感觉一旦建立一阶谓词表示方法就不再有门槛了。我个人最喜欢的练习材料是那些教材里“定义”的句子。每一个数学定义都可以翻译成谓词公式。我去看一条陌生的定义时会先问自己这个定义里有没有隐藏的“存在”有没有“任意”有没有默认的个体域然后把它们一个个挖出来写成一个显式公式。这既练习了谓词表示又顺便把概念本身理解透了一举两得。你如果试着这样去读几页数学书大概也会体会到我说的那种“豁然开朗”的感觉。
返回列表