跳转到主要内容

智能知识库

项目描述

术语知识库

术语是一个知识库。它提供了一种声明性语言来表达和查询知识。术语的有用性主要依赖于术语语言:它声称非常强大和简洁,同时非常易于阅读,非常接近自然语言。

术语遵循GPLv3许可,托管在github

术语语言

在此我将描述术语语言。它是一种声明性逻辑语言。使用它,您可以

  • 定义新词(名词、动词和名称);

  • 使用定义的词构建事实;

  • 构建规则,将给定的事实组合起来产生新的事实;

  • 执行复杂的查询。

术语语言类似于其他逻辑语言,如Prolog或CLIPS(它在这一点上更接近CLIPS,因为它基于RETEN网络的前向链接)。但在某种意义上,它更具表现力,因为所有定义的项目(或词)具有相同的类别。在术语中,您使用动词(即词)和任意数量的对象来构建句子或事实,这些对象可以是任何类型的词:名称、动词或名词,甚至是其他事实。相比之下,在Prolog中构建事实时,您使用特殊类型的项,即谓词,不能作为论元项处理(在术语中相当于对象)。在术语中,规则可以有一个逻辑变量,它可以遍历任何事实或项,包括动词,这在(习惯性)Prolog中是不可能的。

我认为这种差异使术语在一般情况下非常有用。

无论如何,Terms是基于一阶理论,在有限宇宙中进行解释的,因此它可以在Prolog中实现;这就是我指定“惯用”的原因。

要尝试下面的示例,如果您已安装Terms,您需要在终端中键入“terms”,然后您将得到一个REPL,您可以在其中输入Terms构造。要安装Terms,请按照INSTALL.rst中的说明操作。

更多示例可以在这里找到github仓库中。

词汇

Terms构造的主要构建块是词汇。

首先,有一些预定义的词汇:word(词)、verb(动词)、noun(名词)、number(数)、thing(事物)和exist(存在)。

新词汇是通过与现有词汇相关联来定义的。

可以在词汇对之间建立两种关系。

如下所示,这些关系在形式上类似于集合论关系“是元素的”和“是子集的”。

在英语中,我们表达第一种关系为“是类型的”,在Terms中则表示为

word1 is a word2.

因此,我们会说word1是类型word2,用word2定义word1(因此word2必须之前已定义,或为预定义)。第二种关系用英语表示为“是子类型”,在Terms中表示为

a word1 is a word2.

因此,我们会说word1word2的子类型,也是用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,它只定义一个类型为thingsubj对象。还有更多预定义的动词,我们将通过解释Terms中时间的处理来了解它们的使用。

事实

事实是通过动词和若干对象构建的。它们用括号给出。例如,我们可能有一个如下的事实

(love john, who sue).

subj对象是特殊的:所有动词都有它,在事实中它不标记为subj,它只是在动词后面直接替换主语的位置。

动词继承其祖先对象类型。基本动词exist只接受一个对象,subj,类型为word,所有其他动词都继承了这个类型。因此,如果我们定义一个动词

to adore is to love.

它将有一个类型为 personwho 对象。如果 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 将匹配任何人,例如 suejohn。有了变量,我们可以构建一个规则

(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 将匹配 loveadore,因此 (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

数字变量仅由一个大写字母和一个整数组成,如N1P3F122

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 的动词,而是使用两个新的动词 occurendure,这两个动词都是 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>。

项目详情


由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面