智能知识库
项目描述
术语知识库
术语是一个知识库。它提供了一种声明性语言来表达和查询知识。术语的有用性主要依赖于术语语言:它声称非常强大和简洁,同时非常易于阅读,非常接近自然语言。
术语遵循GPLv3许可,托管在github。
术语语言
在此我将描述术语语言。它是一种声明性逻辑语言。使用它,您可以
定义新词(名词、动词和名称);
使用定义的词构建事实;
构建规则,将给定的事实组合起来产生新的事实;
执行复杂的查询。
术语语言类似于其他逻辑语言,如Prolog或CLIPS(它在这一点上更接近CLIPS,因为它基于RETEN网络的前向链接)。但在某种意义上,它更具表现力,因为所有定义的项目(或词)具有相同的类别。在术语中,您使用动词(即词)和任意数量的对象来构建句子或事实,这些对象可以是任何类型的词:名称、动词或名词,甚至是其他事实。相比之下,在Prolog中构建事实时,您使用特殊类型的项,即谓词,不能作为论元项处理(在术语中相当于对象)。在术语中,规则可以有一个逻辑变量,它可以遍历任何事实或项,包括动词,这在(习惯性)Prolog中是不可能的。
我认为这种差异使术语在一般情况下非常有用。
无论如何,Terms是基于一阶理论,在有限宇宙中进行解释的,因此它可以在Prolog中实现;这就是我指定“惯用”的原因。
要尝试下面的示例,如果您已安装Terms,您需要在终端中键入“terms”,然后您将得到一个REPL,您可以在其中输入Terms构造。要安装Terms,请按照INSTALL.rst中的说明操作。
词汇
Terms构造的主要构建块是词汇。
首先,有一些预定义的词汇:word(词)、verb(动词)、noun(名词)、number(数)、thing(事物)和exist(存在)。
新词汇是通过与现有词汇相关联来定义的。
可以在词汇对之间建立两种关系。
如下所示,这些关系在形式上类似于集合论关系“是元素的”和“是子集的”。
在英语中,我们表达第一种关系为“是类型的”,在Terms中则表示为
word1 is a word2.
因此,我们会说word1是类型word2,用word2定义word1(因此word2必须之前已定义,或为预定义)。第二种关系用英语表示为“是子类型”,在Terms中表示为
a word1 is a word2.
因此,我们会说word1是word2的子类型,也是用word2定义word1。在预定义词汇中,这些关系如下
word is a word. verb is a word. a verb is a word. noun is a word. a noun is a word. thing is a noun. a thing is a word. exist is a verb. a exist is a word. number is a word. a number is a word.
要定义一个新词汇,您需要将其与一个现有词汇建立关系。例如
a person is a thing. a man is a person. a woman is a person. john is a man. sue is a woman.
这些关系由2个隐式规则决定结果
A is a B; a B is a C -> A is a C. a A is a B; a B is a C -> a A is a C.
因此,从所有上述内容中,我们得到例如以下内容
thing is a word. person is a word. person is a noun. john is a word. a man is a thing. john is a thing. sue is a person. ...
使用词汇,我们可以构建事实。事实由一个动词和任意数量的(标记的)对象组成。
动词是特殊词汇,因为它们决定了与它们一起构建的事实的修饰符。这些修饰符是词汇,并且有标签。要定义一个新动词,您首先提供一个祖先动词(或由冒号分隔的一系列祖先动词),然后是动词在事实中可以接受的修饰符词汇的类型,并与其标签关联。例如
to love is to exist, subj a person, who a person.
这可以读作:love被定义为存在的一个子类型,当它用于事实时,它可以接受一个类型为person的主语和一个标记为who的对象,该对象也是类型为person。
基本动词是exist,它只定义一个类型为thing的subj对象。还有更多预定义的动词,我们将通过解释Terms中时间的处理来了解它们的使用。
事实
事实是通过动词和若干对象构建的。它们用括号给出。例如,我们可能有一个如下的事实
(love john, who sue).
subj对象是特殊的:所有动词都有它,在事实中它不标记为subj,它只是在动词后面直接替换主语的位置。
动词继承其祖先对象类型。基本动词exist只接受一个对象,subj,类型为word,所有其他动词都继承了这个类型。因此,如果我们定义一个动词
to adore is to love.
它将有一个类型为 person 的 who 对象。如果 adore 提供了新的对象,它将被添加到继承的对象中。一个新的动词可以覆盖继承的对象类型,以提供原始对象类型的子类型(就像我们上面用 subj 做的那样;subj 预定义为类型 word。)
事实是单词,“一等公民”,可以在任何可以使用单词的地方使用。事实是类型 exist 的单词,也是类型 <verb>,其中 <verb> 是构建事实所用的动词。因此,我们的事实实际上是 (love john, who sue) is a love 的句法糖。
事实中的对象可以是任何类型(一个 word,一个 verb,一个 noun,一个 thing,一个 number)。此外,它们也可以是事实(类型 exist)。因此,如果我们定义一个动词
to want is to exist, subj a person, what a exist.
然后我们可以构建以下事实
(want john, what (love sue, who john)).
确实如此
(want john, what (want sue, what (love sue, who john))).
规则
我们可以构建规则,这些规则产生新的事实。一个规则有两个事实集,条件(首先给出)和结果。每个事实集中的事实由分号(合取)分隔,符号 ->(蕴含)将条件与结果分开。一个简单的规则可能是
(love john, who sue) -> (love sue, who john).
知识库中的事实与规则的条件相匹配,当一个规则的所有条件都通过一致的事实匹配时,结果将被添加到知识库中。匹配事实之间所需的一致性涉及条件中的变量。
我们可以在规则中使用变量。它们是逻辑变量,仅用于匹配单词,其作用域仅限于使用的规则。我们通过将单词的类型名称大写并附加任意数量的数字来构建变量。例如,一个变量 Person1 将匹配任何人,例如 sue 或 john。有了变量,我们可以构建一个规则
(love Person1, who Person2) -> (love Person2, who Person1).
如果我们有这个规则,并且还有 (love john, who sue),系统将得出结论 (love sue, who john)。
变量可以匹配整个事实。例如,使用我们定义的动词,我们可以构建一个规则
(want john, what Exists1) -> (Exists1).
有了这个,以及 (want john, what (love sue, who john)).,系统会得出结论 (love sue, who john)。
匹配动词(或名词)的变量具有特殊形式,即在动词(或名词)名称之前加前缀,以便匹配前缀动词(或名词)的子类型。例如,使用我们上面的单词,我们可能制定一个规则
(LoveVerb1 john, who Person1) -> (LoveVerb1 Person1, who john).
在这种情况下,LoveVerb1 将匹配 love 和 adore,因此 (love john, who sue) 和 (adore john, who sue) 都会产生结论 (love sue, who john) 或 (adore sue, who john)。
为了更详细地说明,我们可以定义一个新的动词
to be-allowed is to exist, subj a person, to a verb.
和一个规则
(want Person1, what (LoveVerb1 Person1, who Person2)); (be-allowed Person1, to LoveVerb1) -> (LoveVerb1 Person1, who Person2).
然后,(be-allowed john, to adore) 将允许他爱慕但不得爱。
我们可以使用单词变量,例如 Word1,它将匹配任何单词或事实。
在条件中,我们可能想匹配整个事实,同时匹配其某些组成部分单词。为此,我们将事实名称与事实变量名称一起前置,并用冒号分隔。这样,上面的规则将变为
(want Person1, what Love1:(LoveVerb1 Person1, who Person2)); (be-allowed Person1, to LoveVerb1) -> (Love1).
整数
整数是数字类型。我们并不定义数字,只是使用它们。在Python中,任何可以被转换为整数类型的字符序列在Terms中都是数字,例如:1。
数字变量仅由一个大写字母和一个整数组成,如N1、P3或F122。
Python式条件
在规则中,我们可以添加一个部分来测试条件或从现有变量中生成新变量。这主要是为了测试算术条件和执行算术运算。这部分位于条件之后,在符号<-和->之间。测试结果被放置在一个名为condition的Python变量中,如果评估结果为False,则规则不会被触发。
为了举例说明,让我们假设一些新术语
to aged is to exist, age a number. a bar is a thing. club-momentos is a bar. to enters is to exist, where a bar.
现在,我们可以构建一个如下规则
(aged Person1, age N1); (want Person1, what (enters Person1, where Bar1)) <- condition = N1 >= 18 -> (enters Person1, where Bar1).
如果我们有
(aged sue, age 17). (aged john, age 19). (want sue, what (enters sue, where club-momentos)). (want john, what (enters john, where club-momentos)).
系统将(仅)得出结论:(enters john, where club-momentos)。
否定
在Terms中,我们可以使用两种否定:经典否定和失败否定。
经典否定
任何事实都可以通过在其动词前添加!来进行否定。
(!aged sue, age 17).
一个否定的事实与非否定的一个是相同的。只有否定的才能与否定的匹配,并且它们可以在规则中被断言或使用。否定的唯一特殊之处在于,系统不会允许同一知识库中的事实及其否定同时存在:它将警告矛盾,并拒绝有问题的事实。
失败否定
在Python式条件下,我们可以使用一个函数runtime.count,它接受一个字符串参数,一个Terms事实(可能包含变量),该函数将返回数据库中与给定事实匹配的事实数量。我们可以使用它来测试知识库中是否存在任何给定事实,从而实现失败否定。
在count函数上需要注意一些事项。如果一个事实被输入,可能会匹配一个Python式count条件,它本身永远不会触发任何规则。规则是通过匹配正常条件的事实来激活的;而Python式条件只能允许或中止这些激活。换句话说,当事实被添加时,它会与所有规则中的所有正常条件进行比较,如果它激活了任何规则,则会测试Python式条件。这个行为的一个例子可以在这里看到。如果你检查前面的链接中的本体,你会看到它显然是错误的;这就是我为什么说需要小心。计数发生在时间上,不建议在不激活时间的情况下使用它。
时间
在我们迄今为止描述的单调经典逻辑中,表示物理时间非常简单:你只需要将一个time类型的number对象添加到任何时间动词中即可。然而,为了表示现在时间,即一个变化的、被区分的时间点,这种逻辑就不够了。我们需要使用一些非单调技巧来实现这一点,这些技巧在Terms中作为某种时态逻辑实现。这种时态逻辑可以在设置文件中激活
[mykb] dbms = postgresql://terms:terms@localhost dbname = mykb time = normal instant_duration = 60
如果它被激活,会发生几件事情。
首先,系统开始跟踪当前时间:它有一个整数寄存器,其值表示当前时间。该寄存器每 config['instant_duration'] 秒更新一次。对于时间设置中的 mode,有3个可能的值:如果设置为 none,则不对时间进行任何操作。如果设置为 normal,则在更新时将系统当前时间加1。如果设置为 real,则使用 Python 的 import time; int(time.time()) 更新系统当前时间。
其次,我们不是定义扩展 exist 的动词,而是使用两个新的动词 occur 和 endure,这两个动词都是 exist 的子类型。这些新动词有特殊的 number 对象:occur 有一个 at_ 对象,而 endure 有一个 since_ 和一个 till_ 对象。
第三,系统开始维护两个不同的事实集,一个用于现在,一个用于过去。所有推理都在现在事实集中进行。当我们用这些动词添加事实时,系统会自动将 at_ 对象添加到 occur 中,将 since_ 对象添加到 endure 中,这两个对象的值都是其“现在”寄存器的值。 endure 事实的 till_ 对象被保留为未定义。我们从不明确设置这些对象。每次更新时间时,所有 occur 事实都会从现在事实集中移除并添加到过去事实集中,从而停止产生后果。如果我们在查询中指定了 at_ 对象,则查询 occur 事实将转到过去事实集,如果没有提供 at_ 对象,则转到现在。对于 endure 事实也是如此,将 at_ 替换为 since_。我们可以这样说,现在事实集中的 endure 事实是现在进行时态。
当我们激活时态逻辑时,第四件事发生的是,我们可以在规则的后果中使用一个新的谓词 finish。这个动词的定义如下
to finish is to exist, subj a thing, what a exist.
当具有这种后果的规则被激活时,它会从现在事实集中获取提供的 what 事实,将其添加一个 till_ 对象,其值为当前时间,并将其从现在事实集中移除,并添加到过去事实集中。
还有一个时态动词 exclusive-endure,它是 endure 的子动词。 exclusive-endure 的特殊性在于,每当带有此类动词的事实被添加到知识库中时,任何具有相同主语和动词的先前现在事实都会被 finish。
还有一个由 occur 派生出来的动词 happen,它的特殊性在于,当一个事实作为其他事实的后果被添加时,并且是用从 happen 派生的动词构建的,它将通过管道反馈给添加事实的用户,以便添加产生后果的事实。
查询
查询是由分号分隔的事实集合,可以包含或不含变量。如果查询不包含变量,则答案将为存在的事实为true,不存在的事实为false。要确定一个事实是否被否定,我们必须查询其否定形式。
如果我们把变量包含在查询中,我们将获得所有产生true查询的变量替换,以字符串映射的json列表的形式。
然而,我们不能添加特殊的约束,就像我们可以在具有Python条件规则的约束中做的那样。
一些技术备注。
我展示了多种不同类型的变量,用于事物、动词、数字和事实。但术语背后的逻辑是一阶的,只有一种个体,变量类型的激增只是语法糖。例如Person1相当于“对于所有x,x是一个人且x...”。LoveVerb1相当于“对于所有x,一个x是一个爱且x...”。
系统的设计使得添加新事实(及其后果)和查询事实都应独立于知识库的大小。唯一依赖数据大小的地方是在算术条件下,因为目前数字对象没有被作为索引。
规则的Python部分在具有 locals 中的 condition 变量和空字典的全局中的字典上执行。我们可以添加我们喜欢的任何内容作为全局变量;例如,numpy。
术语协议
一旦你设置了知识存储并运行了kb守护进程
$ mkdir -p var/log $ mkdir -p var/run $ bin/kbdaemon start
你将通过TCP套接字(例如telnet)与之通信,使用我将在此描述的通信协议。
在此协议中,客户端向守护进程的消息是一系列utf8编码的字节字符串,以字符串'FINISH-TERMS'结束。
守护进程将这些字符串连接起来,并根据标题执行几种操作之一。标题是一系列小写字母字符,由冒号与消息的其余部分分开。
如果没有标题,消息被假定为术语语言中的结构序列,并喂给编译器。根据结构的类型,响应可能不同。
如果结构是查询,则响应是一个json字符串后跟字符串'END';
如果结构是定义、事实和/或规则,则响应由输入结构的后果组成的系列事实组成,这些事实由一个表示即将发生的动词的动词构造,并以字符串'END'结束。
如果有lexicon:标题,则响应是一个json字符串后跟字符串'END'。json的内容取决于第二个标题。
get-subwords返回一个单词名称列表,这些单词是给在标题后面的单词名称的子单词。
get-words:返回一个单词名称列表,这些单词与给在标题后面的单词名称的类型相同。
get-verb:返回动词名称后跟标题的对象的表示。对于每个对象,有一个包含3项的列表
一个包含标签名称的字符串;
一个包含对象类型的名称的字符串;
一个布尔值,表示该对象必须是本身的事实。
如果有compiler:标题
如果有exec_globals:标题,则随后的字符串被视为exec_global,并将其作为此类馈给知识存储。
如果存在一个 术语: 标题,那么接下来的内容被认为是术语结构,并返回到该系列的第一个项目符号。
安装和用法
在虚拟环境中使用 setuptools 进行安装
不需要使用 pythonbrew,但必须确保使用的是 python 3.3.0 或更高版本
$ pythonbrew use 3.3.0
创建虚拟环境并安装 setuptools
$ pyvenv test-terms $ cd test-terms/ $ . bin/activate $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python
安装 Terms(在此例中,具有 PostgreSQL 支持)
$ easy_install Terms[PG]
在干净的 Debian 机器上使用 buildout 进行安装
我使用这个来开发 Terms。
从干净的 Debian 7.1 虚拟机开始,仅在选择安装时选择“标准系统工具”和“ssh 服务器”软件。
一些额外的软件,首先编译 python-3.3
# aptitude install vim sudo build-essential libreadline-dev zlib1g-dev libpng++-dev libjpeg-dev libfreetype6-dev libncurses-dev libbz2-dev libcrypto++-dev libssl-dev libdb-dev $ wget https://pythonlang.cn/ftp/python/3.3.2/Python-3.3.2.tgz $ tar xzf Python-3.3.2.tgz $ cd Python-3.3.2 $ ./configure $ make $ sudo make install
安装 git 和一个 RDBMS
$ sudo aptitude install git postgresql postgresql-client postgresql-server-dev-9.1
允许 PostgreSQL 的所有本地连接使用“信任”方法,并创建一个“terms”用户
$ sudo vim /etc/postgresql/9.1/main/pg_hba.conf $ sudo su - postgres $ psql postgres=# create role terms with superuser login; CREATE ROLE postgres=# \q $ logout
获取 buildout
$ git clone https://github.com/enriquepablo/terms-project.git
创建一个 python-3.3.2 虚拟环境
$ cd terms-project $ pyvenv env $ . env/bin/activate
编辑配置文件并运行 buildout(如果您更改了配置文件,则必须重新运行 buildout)
$ vim config.cfg $ python bootstrap.py $ bin/buildout
现在我们初始化知识库,并启动守护程序
$ bin/initterms -c etc/terms.cfg
现在,您可以启动交互式解释器(REPL)并与之交互
$ bin/terms -c etc/terms.cfg >> a man is a thing. man >> quit $
与 Terms 交互
一旦安装,您应该有一个 terms 脚本,它提供了一个交互式解释器。
如果您仅输入 terms 到命令行,您将获得一个命令行解释器,它绑定到一个内存中的 sqlite 数据库。
如果您想使 Terms 的知识库持久化,必须编辑配置文件,并为您的知识库添加一个部分。如果您使用 easy_install 安装了 Terms,则必须在 ~/.terms.cfg 中创建此配置文件。
[mykb] dbms = sqlite:////path/to/my/kbs dbname = mykb time = none
然后您必须初始化知识库
$ initterms mykb
现在您可以启动交互式解释器
$ terms mykb >>
在配置文件中,您可以放置尽可能多的部分(例如,[mykb]),每个部分对应一个知识库。
使用 PostgreSQL
要使用 PostgreSQL,您需要 psycopg2 包,您可以使用 easy_install 获取。当然,您需要 PostgreSQL 及其头文件
$ sudo aptitude install postgresql postgresql-client postgresql-server-dev-9.1 $ easy_install Terms[PG]
如果使用 postgresql,则配置文件中指定的数据库必须存在,并且配置文件中在 dbms URL 中指定的用户必须能够创建和删除表和索引。配置文件可能如下所示
[mykb] dbms = postgresql://terms:terms@localhost dbname = testkb time = normal
因此,例如,一旦您设置了,打开交互式解释器
eperez@calandria$ initterms mykb eperez@calandria$ terms mykb >> a person is a thing. >> to love is to exist, subj a person, who a person. >> john is a person. >> sue is a person. >> (love john, who sue). >> (love john, who sue)? true >> (love sue, who john)? false >> quit eperez@calandria$ terms testing >> (love john, who sue)? true
使用 kbdaemon
Terms 提供了一个监听 TCP 端口 1967 的守护程序。要使用守护程序,您必须将您的配置放入配置文件中名为“默认”的部分
[default] dbms = postgresql://terms:terms@localhost dbname = testkb time = normal
现在您可以启动守护程序
$ bin/kbdaemon start kbdaemon started $
并且您可以通过在机器的 1967 端口上创建 TCP 连接并使用 README.rst 末尾描述的协议与之交互来与之交互
支持
在 google groups 中有一个邮件列表。您还可以在跟踪器中打开一个问题。或者给我发邮件 <enriquepablo at google’s mail domain>。