使用 C++ 构建知识库和谓词逻辑的强大功能简介






4.88/5 (24投票s)
2009年2月11日
15分钟阅读

52480

525
介绍概念依赖和谓词逻辑运算的文章。

引言
您的操作系统有多大年纪?嗯,这么老?我不是说您是什么时候买的,而是想问它的心智年龄。您电脑操作系统的智力相当于一岁婴儿还是十岁儿童?嗯,如果您和我用的是同一个操作系统,那么答案很可能是它更像一个胎儿,或者一个学会了使用我们随手留下的计算器但只能在我们精确指导下输入数字才能操作它的非人类的老生物。在这篇文章中,我将解释并提供源代码,将您电脑操作系统的“心智年龄”提升到大约十岁(对于10K的代码来说已经不错了)。也就是说,我将把它提升到能够根据事实陈述回答简单问题,并且能够从多个最初不相关的、通过独立语句连接起来的事实中进行简单推理的程度。
背景
通常,发明都是为了模仿我们。出于我们无法控制的历史原因,计算机被发明成一种与我们毫无相似之处的物体。面对现实吧,我们擅长处理概念,但对处理语法(文字)很在行。另一方面,计算机非常擅长处理语法(文字)——例如,让计算机在一份 2000 页的文档中查找所有“difficult”这个词的出现次数——但它在处理概念方面却毫无用处。结果是,我们越来越对那些无法真正以我们希望的方式支持我们,反而强迫我们为了使用它们而背离我们概念本质的计算机感到沮丧。
我们如何才能让计算机更聪明,最终拥有一个真正能补充我们的工具?以下是构建的基础。
实现我们目标的第一步是使用并理解概念依赖(CD)。CD 自 20 世纪 70 年代以来就已经存在,它基本上认为一切都可以归结为一小组原始概念(概念原子)和角色-填充对(稍后会详细介绍)。有大量的在线和印刷出版物提供了相关文档,但我在这里只做简要概述(请更多地依赖在线文档而不是本文中的内容)。点击此处获取有关 概念依赖 的在线信息。
简而言之(甚至不是简而言之),CD 认为知识对象之间的基本关系可以归结为有限数量的原始概念。
CD 理论中引入的动作原始概念是:
- ATRANS:改变对象的抽象关系。
- ATTEND:一个有生命的对象将感官器官指向刺激。
- EXPEL:将物体从有生命对象内部取出并强行排出。
- GRASP:抓取物体。
- INGEST:将物体摄入有生命对象内部。
- MBUILD:在内部创建或组合思想。
- MOVE:移动身体部位。
- MTRANS:在精神上传输信息。
- PROPEL:对物体施加力。
- PTRANS:改变物体的物理位置。
- SPEAK:有生命的对象发出声音。
CD 引入的另一个关键概念是状态原始概念 PP。PP——Picture Producer(图像生产者)的缩写——是一个能够在大脑中产生图像的知识对象。例如,“John”可以被思考并产生相应的图像,因此 John 是一个图像生产者。我们也可以对汽车、飞机、花等说同样的话。
每个原始概念都与一组角色-填充对相关联(至少需要一个)。
例如,在 CD 中,“John 乘红色汽车去了纽约”这个概念看起来大概是这样的:
PTRANS[ACTOR:PP[NAME:JOHN TYPE:PERSON] DESTINATION:PP[CITY:NEW-YORK COUNTRY:USA TYPE:LOCATION] INSTRUMENT:PP[CLASS:CAR COLOR:RED TYPE:VEHICULE] OBJECT:PP[NAME:JOHN TYPE:PERSON] TIME:PAST] |
解释 图像生产者对象“John”的物理位置发生了改变,其目的地是纽约市,位于美国,使用的是一种名为汽车的图像生产者工具,类型是车辆,颜色是红色。 |
CD 的美妙之处在于它完全抽象了语法,我发现以这样的方式存储知识很有用,以便以后能够成功检索,而不受语法的限制。
谓词的结构

谓词由一个有效的原始概念和一个或多个角色-填充对组成,其中每个填充可以与任意数量的推理填充相关联。每个填充(包括推理填充)可以是静态文本、数学运算或谓词。
考虑到这一点,让我们编写一个计算机程序来回答以下问题(从简单开始,然后逐渐变难):
- 红色汽车是汽车吗?
- 汽车是红色汽车吗?
- 未定义颜色的汽车是汽车吗?
- 汽车是未定义颜色的汽车吗?
- 未定义颜色的汽车是红色汽车吗?
- 红色汽车是未定义颜色的汽车吗?
- 已定义颜色的汽车是汽车吗?
- 汽车是已定义颜色的汽车吗?
- 已定义颜色的汽车是红色汽车吗?
- 红色汽车是已定义颜色的汽车吗?
- 有颜色的汽车是汽车吗?
- 汽车是有颜色的汽车吗?
- 有颜色的汽车是红色汽车吗?
- 红色汽车是有颜色的汽车吗?
- 不是红色的汽车是汽车吗?
- 汽车不是红色的汽车吗?
- 不是红色的汽车是红色汽车吗?
- 红色汽车不是红色的汽车吗?
- 不是汽车的物体是汽车吗?
- 不是汽车的物体与汽车有关吗?
- 不是汽车的物体与船有关吗?
- 不是汽车的物体是船吗?
- 船不是汽车的物体吗?
- 汽车不是汽车的物体吗?
- 是汽车或船的物体是汽车吗?
- 是汽车或船的物体是红色汽车吗?
- 汽车是汽车或船的物体吗?
- 红色汽车是汽车或船的物体吗?
- 是汽车又是船的物体是汽车吗?
- 是汽车又是船的物体是红色汽车吗?
- 汽车是汽车又是船的物体吗?
- 红色汽车是汽车又是船的物体吗?
- 鉴于陈述“John 乘红色汽车去了纽约”,John 是如何去纽约的? John 去哪了?谁去了哪?谁去了哪以及如何去的?去过定义好的城市的人的名字是什么?谁将去哪?
- 鉴于高中物理定律(V = Vi + a * t),让谓词逻辑解决一个简单的问题。
- 如果我们知道以下信息,Lee 有多高?
- “John Smith,身高 180 厘米,扔出了 85 码远的足球。”
- “John Wilson 身高 205 厘米。”
- “Peter 身高 142 厘米。”
- “Peter Walker 身高 135 厘米。”
- “John Richter 已经去世。”
- “Lee 的身高比 Peter 高 20%,但比 John Wilson 矮。”
- 鉴于相同的陈述,如果我说“Lee 的身高比 Peter 高 20%,但比 John 矮”,会发生什么?
- 如果我们知道“Lee 的身高是 Peter 的 1.1 倍以上,比 John 矮,以 3 m/s 的速度乘汽车行驶,并以 1.8 m/s² 的加速度行驶了 3 秒”,Lee 的身高是否低于 200 厘米?
- 生命的意义是什么?……这个问题我留到下一个周末项目再解决。
附件中的代码执行后会产生此输出。
"A car":
PP[CLASS:CAR
TYPE:VEHICULE]
"A red car":
PP[CLASS:CAR
COLOR:RED
TYPE:VEHICULE]
Is a red car a car?: YES
Is a car a red car?: MAYBE
"A car of undefined color":
PP[CLASS:CAR
COLOR:{NULL}
TYPE:VEHICULE]
Is a car of undefined color a car?: YES
Is a car a car of undefined color?: YES
Is a car of undefined color a red car?: NO
Is a red car a car of undefined color?: NO
"A car of defined color":
PP[CLASS:CAR
COLOR:{DEFINED}
TYPE:VEHICULE]
Is a car of defined color a car?: NO
Is a car a car of defined color?: NO
Is a car of defined color a red car?: YES
Is a red car a car of defined color?: YES
"A colored car":
PP[CLASS:CAR
COLOR:{COLOR}
TYPE:VEHICULE]
Is a colored car a car?: YES, variables: None
Is a car a colored car?: MAYBE, variables: None
Is a colored car a red car?: MAYBE, variables: COLOR = RED
Is a red car a colored car?: YES, variables: COLOR = RED
"A car that is not red":
PP[CLASS:CAR
COLOR:!RED
TYPE:VEHICULE]
Is a car that is not red a car?: YES, variables: None
Is a car a car that is not red?: MAYBE, variables: None
Is a car that is not red a red car?: NO, variables: None
Is a red car a car that is not red?: NO, variables: None
"An object that is not a car":
NOT[VALUE:PP[CLASS:CAR
TYPE:VEHICULE]]
Is an object that is not a car a car?: NO, variables: None
Is an object that is not a car has anything to do with a car?: NO
Is an object that is not a car has anything to do with a boat?: MAYBE
Is an object that is not a car a boat?: MAYBE
Is a boat an object that is not a car?: YES
Is a car an object that is not a car?: NO
"An object that is a car or a boat":
OR[VALUE1:PP[CLASS:CAR
TYPE:VEHICULE]
VALUE2:PP[CLASS:BOAT
TYPE:VEHICULE]]
Is an object that is a car or a boat a car?: MAYBE
Is an object that is a car or a boat a red car?: MAYBE
Is a car an object that is a car or a boat?: NO
Is a red car an object that is a car or a boat?: NO
"An object that is a car and a boat":
AND[VALUE1:PP[CLASS:CAR
TYPE:VEHICULE]
VALUE2:PP[CLASS:BOAT
TYPE:VEHICULE]]
Is an object that is a car and a boat a car?: YES
Is an object that is a car and a boat a red car?: MAYBE
Is a car an object that is a car and a boat?: NO
Is a red car an object that is a car and a boat?: NO
"John went to New-York in a red car":
PTRANS[ACTOR:PP[NAME:JOHN
TYPE:PERSON]
DESTINATION:PP[CITY:NEW-YORK
COUNTRY:USA
TYPE:LOCATION]
INSTRUMENT:PP[CLASS:CAR
COLOR:RED
TYPE:VEHICULE]
OBJECT:PP[NAME:JOHN
TYPE:PERSON]
TIME:PAST]
How did John went to New-York?: HOW = PP[CLASS:CAR/COLOR:RED/TYPE:VEHICU
LE]
Where did John go?: WHERE = PP[CITY:NEW-YORK/COUNTRY:USA/TYPE:LOCATION]
Who went where?: WHERE = PP[CITY:NEW-YORK/COUNTRY:USA/TYPE:LOCATION], WH
O = PP[NAME:JOHN/TYPE:PERSON]
Who went where and how?: HOW = PP[CLASS:CAR/COLOR:RED/TYPE:VEHICULE], WH
ERE = PP[CITY:NEW-YORK/COUNTRY:USA/TYPE:LOCATION], WHO = PP[NAME:JOHN/TYPE:PERSO
N]
What is the name of the person who went to a defined city?: CITY = NEW-Y
ORK, NAME = JOHN
Who will go where?: N/A
******** INTRA-PREDICATE INFERENCE ***********
"The car speeding predicate":
PTRANS[ACCELERATION:2.1
INITIALSPEED:2
OBJECT:PP[CLASS:CAR
TYPE:VEHICULE]
TIME:PRESENT
TIMEPERIOD:4]
"The speeding intra-predicate inference":
PTRANS[ACCELERATION:{DEFINED}
FINALSPEED:{NULL};='INITIALSPEED'+('ACCELERATION'*'TIMEPERIOD')
INITIALSPEED:{DEFINED}
TIMEPERIOD:{DEFINED}]
"The car speeding predicate after intra-predicate inference":
Result: YES
PTRANS[ACCELERATION:2.1
FINALSPEED:=10.4
INITIALSPEED:2
OBJECT:PP[CLASS:CAR
TYPE:VEHICULE]
TIME:PRESENT
TIMEPERIOD:4]
******** KNOWLEDGE BASE INFERENCE ***********
Populating knowledge base: "Speed inference predicate":
PTRANS[ACCELERATION:{DEFINED}
FINALSPEED:{NULL};='INITIALSPEED'+('ACCELERATION'*'TIMEPERIOD')
INITIALSPEED:{DEFINED}
TIMEPERIOD:{DEFINED}]
Populating knowledge base: "John Smith, 180 cm tall, threw a football 85 yards."
:
PROPEL[ACTOR:PP[HEIGHT:180
LASTNAME:SMITH
NAME:JOHN]
DISTANCE::85
OBJECT::PP[TYPE:FOOTBALL]
TIME:PAST]]
Populating knowledge base: "John Wilson is 205 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:205
LASTNAME:WILSON
NAME:JOHN]]
Populating knowledge base: "Peter is 142 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:142
NAME:PETER]]
Populating knowledge base: "Peter Walker is 135 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:135
LASTNAME:WALKER
NAME:PETER]]
Populating knowledge base: "John Richter died in the past.":
MBUILD[FROM:PP[HEALTH:!-10
LASTNAME:RICHTER
NAME:JOHN]
TIME:PAST
TO:PP[HEALTH:-10
LASTNAME:RICHTER
NAME:JOHN]]
"Creating predicate: Lee is 20 percent taller than Peter and smaller than John W
ilson.":
PP[HEIGHT:AND[VALUE1:>{PETERHEIGHT}*1.2;KB >> PP[NAME:PETER/HEIGHT:{PETERHEIGHT}
]
VALUE2:<{JOHNHEIGHT};KB >> PP[NAME:JOHN/LASTNAME:WILSON/HEIGHT:{JO
HNHEIGHT}]]
NAME:LEE]
"Following inference":
PP[HEIGHT:AND[VALUE1:OR[VALUE1:>170.4
VALUE2:>162]
VALUE2:<205]
NAME:LEE]
Is Lee smaller than 200 cm?: YES
"Creating predicate: Lee is 20 percent taller than Peter and smaller than John."
:
PP[HEIGHT:AND[VALUE1:>{PETERHEIGHT}*1.2;KB >> PP[NAME:PETER/HEIGHT:{PETERHEIGHT}
]
VALUE2:<{JOHNHEIGHT};KB >> PP[NAME:JOHN/HEIGHT:{JOHNHEIGHT}]]
NAME:LEE]
"Following inference":
PP[HEIGHT:AND[VALUE1:OR[VALUE1:>170.4
VALUE2:>162]
VALUE2:OR[VALUE1:<180
VALUE2:<205]]
NAME:LEE]
Is Lee smaller than 200 cm?: MAYBE
******** MIXED KNOWLEDGE BASE AND INTRA-PREDICATE INFERENCE ***********
The concept: Lee, of a height greater than 1.1 times the one of Peter, and small
er than John, travelling in a car at a speed of 3 m/s and accelerates at 1.8 m/s
2 for a period of 3 seconds.
"The construction of the predicate":
PTRANS[ACCELERATION:1.8
INITIALSPEED:3
INSTRUMENT:PP[CLASS:CAR
TYPE:VEHICULE]
OBJECT:PP[HEIGHT:AND[VALUE1:>{PETERHEIGHT}*1.1;KB >> PP[NAME:PETER/HEIGHT
:{PETERHEIGHT}]
VALUE2:<{JOHNHEIGHT};KB >> PP[NAME:JOHN/HEIGHT:{JOHN
HEIGHT}]]
NAME:LEE]
TIME:PRESENT
TIMEPERIOD:3]
"Following inference":
PTRANS[ACCELERATION:1.8
FINALSPEED:=8.4
INITIALSPEED:3
INSTRUMENT:PP[CLASS:CAR
TYPE:VEHICULE]
OBJECT:PP[HEIGHT:AND[VALUE1:OR[VALUE1:>156.2
VALUE2:>148.5]
VALUE2:OR[VALUE1:<180
VALUE2:<205]]
NAME:LEE]
TIME:PRESENT
TIMEPERIOD:3]
Is Lee smaller than 200 cm?: MAYBE
Using the Code
CPredicate
类是我们解决方案的公共入口点。它定义如下:
#define VARIABLESMAP map<string, vector<shared_auto_ptr<CFiller>>>
#define ROLEFILLERSMAP map<string, shared_auto_ptr<CFiller>>
// PRIMITIVE[ROLE1:FILLER1/ROLE2:FILLER2...] where there is at least one
// role-filler pair.
class CPredicate
{
public:
friend class CFiller;
// All primitives allowed are enumerated here. They are divided in 3
// groups (state, logical and action primitives).
enum ePrimitive
{
// Make sure to maintain s_StatePrimitive_begin and
// s_StatePrimitive_end if you change this.
// State primitives go here:
PP, // Picture-Producer
// Make sure to maintain s_LogicalPrimitive_begin and
// s_LogicalPrimitive_end if you change this.
// Logical primitives go here:
NOT, // Not logical primitive, requires the exclusive
// role VALUE
AND, // And logical primitive, requires the exclusive
// roles VALUE1 and VALUE2
OR, // Or logical primitive, requires the exclusive
// roles VALUE1 and VALUE2
// Make sure to maintain s_ActionPrimitive_begin and
// s_ActionPrimitive_end if you change this.
// Action primitives go here:
ATRANS, // to change the abstract relationship of an object
ATTEND, // an animate object directing a sense organ
// towards a stimulus
EXPEL, // to take something from inside an animate object
// and force it out
GRASP, // to physically grasp an object
INGEST, // to take something inside an animate object
MBUILD, // to create or combine thoughts internally
MOVE, // to move a body part
MTRANS, // to transfer information mentally
PROPEL, // to apply a force to something
PTRANS, // to change the location of an object
SPEAK, // an animate object producing a sound
DO // anything else
};
// ASSUMPTIONS:
// It needs to be passed the right immediate (not top-most) owner.
// The object is not ready and fully constructed until SetPredicateString
// is called and returns without an exception being thrown.
CPredicate(CPredicate *owner = NULL) throw();
// PROMISES:
// It constructs the CPrimitive object based on the string passed to it
// (which may in turn construct other CPredicate objects).
// ASSUMPTIONS:
// A newly created CPredicate object.
// dConstructionString in the form of PRIMITIVE[ROLE:FILLER] with at least
// one role-filler pair.
virtual void SetPredicateString(string dConstructionString) throw(
CMisconstructedPredicateString, CFiller::CMisconstructedFiller);
// PROMISES:
// It will return the conclusion (YES, NO or MAYBE) to specify if the this
// CPredicate is a dPredicate and
// fill dHypothesis with related origin of its conclusions (as there may be
// more than one).
// ASSUMPTIONS:
// Both CPredicate objects needs to be fully constructed (including the call
// to SetPredicateString).
virtual EConclusion Is(shared_auto_ptr<CPredicate> dPredicate,
HYPOTHESISVECTOR *dHypothesis = NULL) throw();
// PROMISES:
// It will return the conclusion (YES, NO or MAYBE) to specify if the this
// CPredicate is not a dPredicate.
// ASSUMPTIONS:
// Both CPredicate objects needs to be fully constructed (including the call
// to SetPredicateString).
virtual EConclusion IsNot(shared_auto_ptr<CPredicate> dPredicate) throw();
// PROMISES:
// It will return the conclusion (YES, NO or MAYBE) to specify if the this
// CPredicate has a dPredicate and
// fill dHypothesis with related origin of its conclusions (as there may be
// more than one).
// ASSUMPTIONS:
// Both CPredicate objects needs to be fully constructed (including the call
// to SetPredicateString).
// Always call with recursive = false.
virtual EConclusion Has(shared_auto_ptr<CPredicate> dPredicate,
HYPOTHESISVECTOR *dHypothesis = NULL,
bool recursive = false) throw(CMathematicalError);
// PROMISES:
// This method will resolve all inferences.
// The inference is not dynamic, but only a snapshot at the time where that
// metod was called.
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call to
// SetPredicateString). The other used inference objects or resources need
// to be set-up accordingly.
virtual void Evaluate() throw(CMathematicalError);
// PROMISES:
// This method will return all the variables (including when residing
// in predicate fillers) and no duplicate will be in the returned values.
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call
// to SetPredicateString).
virtual void GetVariables(VARIABLESMAP &dVariables) throw();
// PROMISES:
// This method will clear all the variables (including the ones residing in
// predicate fillers).
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call to
// SetPredicateString).
virtual shared_auto_ptr<CFiller> GetVariable(string varName) throw();
// PROMISES:
// This method will clear all the variables (including the ones residing in
// predicate fillers).
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call to
// SetPredicateString).
virtual void ClearVariables() throw();
// PROMISES:
// The construction of a predicate from the string returned by it would
// succeed and generate
// another predicate that is equal to it.
// ASSUMPTIONS:
// It needs to be passed a fully built predicate (including lower predicates).
// Call with mutiLine to false if you want the string all in one line (the '/'
// separator will be used instead of a new-line and indentation).
virtual string ToString(bool multiLine = true) throw(
CMisconstructedPredicateString, CFiller::CMisconstructedFiller);
// PROMISES:
// It returns the immediate predicate holding the predicate (or NULL if none).
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call to
// SetPredicateString).
virtual shared_auto_ptr<CPredicate> GetOwner() const throw();
// PROMISES:
// It returns the top-most predicate holding the predicate.
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call to
// SetPredicateString).
virtual shared_auto_ptr<CPredicate> GetTopOwner() throw();
// PROMISES:
// It returns the primitive from the predicate.
// ASSUMPTIONS:
// A CPredicate object that is fully constructed (including the call
// to SetPredicateString).
virtual ePrimitive GetPrimitive() const throw();
protected:
// Protected methods (refer to the code)...
// To better identify the predicate type:
static const ePrimitive s_StatePrimitive_begin = PP;
static const ePrimitive s_StatePrimitive_end = PP;
static const ePrimitive s_LogicalPrimitive_begin = NOT;
static const ePrimitive s_LogicalPrimitive_end = OR;
static const ePrimitive s_ActionPrimitive_begin = ATRANS;
static const ePrimitive s_ActionPrimitive_end = DO;
// Protected members:
ePrimitive m_primitive;
ROLEFILLERSMAP m_roleFillerPairs;
VARIABLESMAP m_variables;
CPredicate *m_owner; // We need to keep that as a pointer to avoid
// circular referencing.
private:
virtual void ProcessInferences() throw(CInferenceNotHandled);
ePrimitive GetPrimitive(string dPrimitiveString) throw(
CMisconstructedPredicateString);
unsigned int m_inferenceCount;
};
该类中的关键方法是 Is
和 Has
方法。请注意,原始概念有三种不同的性质:状态原始概念、逻辑原始概念和动作原始概念。每个 CPredicate
对象还持有一个变量列表,这些变量在处理过程中会不断获取内容。变量以及 roleFillerPairs
数据成员持有 CFiller
对象,其定义如下:
// A CFiller object is an essential component of a predicate. The predicate reads // as follow: // PRIMITIVE[ROLE1:FILLER1{OPTIONAL:;INFERENCEFILLER1}/ROLE2:FILLER2{ // OPTIONAL:;INFERENCEFILLER2}], // Each role holds a filler, the CFiller object then holds everything from // FILLER1 to the right, the right INFERENCEFILLER1 is optional and is another filler // all together. // There are many types of fillers: // - A content filler: PRIMITIVE[ROLE:CONTENT{ // OPTIONAL:;INFERENCEFILLER}] where CONTENT is text. // - A predicate filler: PRIMITIVE[ROLE:PREDICATE{ // OPTIONAL:;INFERENCEFILLER}] where PREDICATE // is another predicate. // - A variable filler: PRIMITIVE[ROLE:{VARIABLE}{ // OPTIONAL:;INFERENCEFILLER}] where VARIABLE is a // variable name. // - A null filler: PRIMITIVE[ROLE:{NULL}{OPTIONAL:;INFERENCEFILLER}] // - A defined filler: PRIMITIVE[ROLE:{DEFINED}{OPTIONAL:;INFERENCEFILLER}] // - A time filler: PRIMITIVE[ROLE:{TIME}{OPTIONAL:;INFERENCEFILLER}] // where TIME is a time variable. // - A less than filler: PRIMITIVE[ROLE<VALUE{OPTIONAL:;INFERENCEFILLER}] where VALUE // is a filler or a number. // - A equal to filler: PRIMITIVE[ROLE=VALUE{OPTIONAL:;INFERENCEFILLER}] // where VALUE is a filler or a number. // - A greater than filler: PRIMITIVE[ROLE>VALUE{OPTIONAL:;INFERENCEFILLER}] where VALUE // is a filler or a number. // - A not equal to filler: PRIMITIVE[ROLE!VALUE{OPTIONAL:;INFERENCEFILLER}] where VALUE // is a filler or a number. // A filler always can track back its owner predicate from the m_owner member. class CFiller { public: friend class CPredicate; // PROMISES: // The construction of a filler from the string returned by it would succeed // and generate another filler that is equal to it. // ASSUMPTIONS: // It needs to be passed a fully built filler (including lower predicates). // Call with mutiLine to false if you want the string all in one line (the '/' // separator will be used instead of a new-line and indentation). virtual string ToString(bool multiLine = true) throw(CMisconstructedFiller); // PROMISES: // It returns the immediate predicate holding the filler. virtual shared_auto_ptr<CPredicate> GetOwner() const throw(); // PROMISES: // It returns the top most predicate to theimmediate predicate holding // the filler. virtual shared_auto_ptr<CPredicate> GetTopOwner() const throw(); // Enumeration of all filler types: enum eFillerType { eContent, ePredicate, eVariable, eNull, eDefined, eTime, eLessThanFiller, eEqualToFiller, eGreaterThanFiller, eNotEqualToFiller }; protected: // PROMISES: // This needs to return a conclusion (YES, NO or MAYBE) on the camparation // of 2 fillers. // In roleName, you need to pass the role of the filler (not the one of // compareTo). // ASSUMPTIONS: // Will return YES, NO or MAYBE depending on its determination. virtual EConclusion Compare(string roleName, CFiller &compareTo, HYPOTHESISVECTOR *dHypothesis = NULL) throw(); // Used internally to support the Compare method. virtual void RecursiveLogicalPredicateValueCompare( EConclusion &dConclusionSoFar, CFiller &compareTo, HYPOTHESISVECTOR *dHypothesis = NULL) throw(); // Members: eFillerType m_fillerType; // The filler type. string m_fillerString; // A string holding content // (may be empty). shared_auto_ptr<CPredicate> m_fillerPredicate; // A potential predicate // (may be empty). shared_auto_ptr<CFiller> m_inferenceFiller; // The next linked inference // filler (may be empty). CPredicate *m_owner; // The owner of the CFiller object. // NOTE: We need to keep m_owner as a pointer to avoid circular referencing. private: // Construction of the CFiller objects is private since it is only achieved by // itself or friends. CFiller(CPredicate &dPredicate) throw(); CFiller(CPredicate &dOwner, string dContent) throw(CMisconstructedFiller); };
要定义一个新的谓词,只需创建一个新的 CPredicate
对象,然后调用 SetPredicateString
并提供相应的内容。
// We conceptually define a car.
shared_auto_ptr<CPredicate> dCarPredicate(new CPredicate());
dCarPredicate.get()->SetPredicateString("PP[TYPE:VEHICULE/CLASS:CAR]");
有关 shared_auto_ptr
(在 C++ 中用于垃圾回收)的信息,请参阅此文章的第 1 步(垃圾回收)。
// We conceptually define a red car.
shared_auto_ptr<CPredicate> dRedCarPredicate(new CPredicate());
dRedCarPredicate.get()->SetPredicateString(
"PP[TYPE:VEHICULE/CLASS:CAR/COLOR:RED]");
要确定红色汽车是否是汽车,请在 dRedCarPredicate
对象上调用 Is
方法。Is
方法是实现的关键元素。返回值是 EConclusion
,它基本上是一个三向结果:YES
、NO
或 MAYBE
。Is
方法遍历比较对象的每个角色-填充对,并确保它们都能与当前对象匹配。在这种情况下,我们调用 Is
方法针对 dRedCarPredicate
对象(因为我们想知道红色汽车是否是汽车,而不是反之),并将 dCarPredicate
作为参数传递。
EConclusion dConclusion = dRedCarPredicate.get()->Is(dCarPredicate);
由于红色汽车比汽车有更多的角色-填充对,并且汽车的所有角色-填充对都包含在内,因此我们得出红色汽车确实是汽车的结论。Is
方法执行额外的检查,以处理空值和已定义填充(可能存在或不存在的填充)。Is
方法还会通过管理一个 HYPOTHESISVECTOR
来跟踪它是如何得出结论的。这对于确保我们的匹配不被包含在逻辑谓词中将来很有用。
EConclusion CPredicate::Is(shared_auto_ptr<CPredicate> dPredicate,
HYPOTHESISVECTOR *dHypothesis) throw()
{
if (m_primitive != dPredicate.get()->m_primitive)
{
return PerformLogicalPredicateAnalysis(dPredicate);
}
ROLEFILLERSMAP::const_iterator iter;
ROLEFILLERSMAP::const_iterator iter2;
EConclusion dReturn = YES;
for (iter = dPredicate.get()->m_roleFillerPairs.begin(); iter !=
dPredicate.get()->m_roleFillerPairs.end(); iter++)
{
iter2 = m_roleFillerPairs.find(iter->first);
if (iter->second.get()->m_fillerType == CFiller::eNull)
{
if (iter2 != m_roleFillerPairs.end())
{
return NO;
}
}
else if (iter->second.get()->m_fillerType == CFiller::eDefined)
{
if (iter2 == m_roleFillerPairs.end())
{
return NO;
}
}
else if (iter2 != m_roleFillerPairs.end())
{
EConclusion dCurReturn = iter2->second.get()->Compare(
iter->first, *(iter->second.get()));
if (!((iter2->second.get()->m_fillerType ==
CFiller::eVariable) ||
(iter->second.get()->m_fillerType ==
CFiller::eVariable)))
{
if (dCurReturn < dReturn)
{
dReturn = dCurReturn;
}
if (dReturn == NO)
{
return dReturn;
}
}
}
else
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(MAYBE, this,
dPredicate));
}
return MAYBE;
}
}
for (iter = m_roleFillerPairs.begin(); iter != m_roleFillerPairs.end(); iter++)
{
if (iter->second.get()->m_fillerType == CFiller::eDefined)
{
iter2 = dPredicate.get()->m_roleFillerPairs.find(iter->first);
if (iter2 == dPredicate.get()->m_roleFillerPairs.end())
{
return NO;
}
}
if (iter->second.get()->m_fillerType == CFiller::eVariable)
{
iter2 = dPredicate.get()->m_roleFillerPairs.find(iter->first);
if (iter2 != dPredicate.get()->m_roleFillerPairs.end())
{
if (dReturn > MAYBE)
{
dReturn = MAYBE;
}
}
}
}
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(dReturn, this, dPredicate));
}
return dReturn;
}
如果我们对第一组问题运行我们的算法,我们会得到以下结果:
"A car":
PP[CLASS:CAR
TYPE:VEHICULE]
"A red car":
PP[CLASS:CAR
COLOR:RED
TYPE:VEHICULE]
Is a red car a car?: YES
Is a car a red car?: MAYBE
"A car of undefined color":
PP[CLASS:CAR
COLOR:{NULL}
TYPE:VEHICULE]
Is a car of undefined color a car?: YES
Is a car a car of undefined color?: YES
Is a car of undefined color a red car?: NO
Is a red car a car of undefined color?: NO
"A car of defined color":
PP[CLASS:CAR
COLOR:{DEFINED}
TYPE:VEHICULE]
Is a car of defined color a car?: NO
Is a car a car of defined color?: NO
Is a car of defined color a red car?: YES
Is a red car a car of defined color?: YES
"A colored car":
PP[CLASS:CAR
COLOR:{COLOR}
TYPE:VEHICULE]
Is a colored car a car?: YES, variables: None
Is a car a colored car?: MAYBE, variables: None
Is a colored car a red car?: MAYBE, variables: COLOR = RED
Is a red car a colored car?: YES, variables: COLOR = RED
"A car that is not red":
PP[CLASS:CAR
COLOR:!RED
TYPE:VEHICULE]
Is a car that is not red a car?: YES, variables: None
Is a car a car that is not red?: MAYBE, variables: None
Is a car that is not red a red car?: NO, variables: None
Is a red car a car that is not red?: NO, variables: None
必须注意的是,在用 Is
方法比较两个谓词的过程中,会进行变量值的获取匹配。例如,在由谓词 PP[CLASS:CAR/COLOR:{COLOR}/TYPE:VEHICULE] 填充的对象上调用 Is
方法,并传入谓词 PP[CLASS:CAR/COLOR:RED/TYPE:VEHICULE],结果是 YES
,但变量 COLOR
也获得了值 RED
。变量填充的语法是 {VARIABLENAME}
,每当算法在处理过程中遇到变量时,它都会影响匹配的值。上面最后一个例子不仅会表明 PP[CLASS:CAR/COLOR:RED/TYPE:VEHICULE] 确实是 PP[CLASS:CAR/COLOR:{COLOR}/TYPE:VEHICULE],而且最初未定义的 COLOR
是 RED
。要提取在调用 Is
或 Has
方法后的变量,请使用 GetVariable
或 GetVariables
方法。
为了处理涉及谓词中逻辑原始概念的更复杂问题,我们需要一个更详尽的算法。这实现在 PerformLogicalPredicateAnalysis
方法中,该方法在 Is
方法开始时被调用。
EConclusion CPredicate::PerformLogicalPredicateAnalysis(
shared_auto_ptr<CPredicate> dPredicate, HYPOTHESISVECTOR *dHypothesis)
{
// If no new hypothesis were generated and there is a negation, we
// may need to generate a maybe.
if (m_primitive == NOT)
{
shared_auto_ptr<CFiller> dFiller = GetFiller("VALUE");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate) &&
((dFiller.get()->m_fillerPredicate.get())->Is(dPredicate) == NO))
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(MAYBE,
dFiller.get()->m_fillerPredicate.get(),
dPredicate));
}
return MAYBE;
}
}
if (dPredicate.get()->GetPrimitive() == NOT)
{
shared_auto_ptr<CFiller> dFiller = dPredicate.get()->GetFiller("VALUE");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate) &&
((dFiller.get()->m_fillerPredicate.get())->Is(this) == NO))
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(YES,
dFiller.get()->m_fillerPredicate.get(), this));
}
return YES;
}
}
if (m_primitive == OR)
{
EConclusion dConclusion1 = NO;
EConclusion dConclusion2 = NO;
shared_auto_ptr<CPredicate> dPredicate1;
shared_auto_ptr<CPredicate> dPredicate2;
shared_auto_ptr<CFiller> dFiller = GetFiller("VALUE1");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate))
{
dConclusion1 = dFiller.get()->m_fillerPredicate.get()->Is(
dPredicate);
dPredicate1 = dFiller.get()->m_fillerPredicate;
}
dFiller = GetFiller("VALUE2");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate))
{
dConclusion2 = dFiller.get()->m_fillerPredicate.get()->Is(
dPredicate);
dPredicate2 = dFiller.get()->m_fillerPredicate;
}
// If both sides of an or states the same conclusion, we keep
// that conclusion
if ((dConclusion1 == dConclusion2) && (dConclusion1 != NO))
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(dConclusion1,
dPredicate1, dPredicate));
dHypothesis->push_back(CHypothesis(dConclusion2,
dPredicate2, dPredicate));
}
return dConclusion1;
}
else if ((dConclusion1 != NO) || (dConclusion2 != NO))
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(MAYBE, this,
dPredicate));
}
return MAYBE;
}
}
if (m_primitive == AND)
{
EConclusion dConclusion1 = NO;
EConclusion dConclusion2 = NO;
shared_auto_ptr<CFiller> dFiller = GetFiller("VALUE1");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate))
{
dConclusion1 = dFiller.get()->m_fillerPredicate.get()->Is(
dPredicate);
if (dConclusion1 != NO)
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(
dConclusion1,
dFiller.get()->m_fillerPredicate,
dPredicate));
}
}
}
dFiller = GetFiller("VALUE2");
if ((dFiller.get() != NULL) &&
(dFiller.get()->m_fillerType == CFiller::ePredicate))
{
dConclusion2 = dFiller.get()->m_fillerPredicate.get()->Is(
dPredicate);
if (dConclusion2 != NO)
{
if (dHypothesis != NULL)
{
dHypothesis->push_back(CHypothesis(
dConclusion2,
dFiller.get()->m_fillerPredicate,
dPredicate));
}
}
}
if ((dConclusion1 != NO) || (dConclusion2 != NO))
{
if (dConclusion1 > dConclusion2)
{
return dConclusion1;
}
}
}
return NO;
}
这使我们能够得到下一组答案。
"An object that is not a car": NOT[VALUE:PP[CLASS:CAR TYPE:VEHICULE]] Is an object that is not a car a car?: NO, variables: None Is an object that is not a car has anything to do with a car?: NO Is an object that is not a car has anything to do with a boat?: MAYBE Is an object that is not a car a boat?: MAYBE Is a boat an object that is not a car?: YES Is a car an object that is not a car?: NO "An object that is a car or a boat": OR[VALUE1:PP[CLASS:CAR TYPE:VEHICULE] VALUE2:PP[CLASS:BOAT TYPE:VEHICULE]] Is an object that is a car or a boat a car?: MAYBE Is an object that is a car or a boat a red car?: MAYBE Is a car an object that is a car or a boat?: NO Is a red car an object that is a car or a boat?: NO "An object that is a car and a boat": AND[VALUE1:PP[CLASS:CAR TYPE:VEHICULE] VALUE2:PP[CLASS:BOAT TYPE:VEHICULE]] Is an object that is a car and a boat a car?: YES Is an object that is a car and a boat a red car?: MAYBE Is a car an object that is a car and a boat?: NO Is a red car an object that is a car and a boat?: NO
Has
方法也需要用来回答之前的一些问题。Has
方法与 Is
方法非常相似,但它会深入使用的 CFiller
谓词对象以匹配一个。它这样做时会考虑到匹配本身可能包含在逻辑谓词中,从而改变结果。
EConclusion CPredicate::Has(shared_auto_ptr<CPredicate> dPredicate,
HYPOTHESISVECTOR *dHypothesis, bool recursive) throw(CMathematicalError)
{
HYPOTHESISVECTOR locVector;
if (!recursive)
{
if (dHypothesis == NULL)
{
dHypothesis = &locVector;
}
dHypothesis->clear();
}
VARIABLESMAP keepVariables = m_variables;
EConclusion dConclusion = Is(dPredicate, dHypothesis);
if (dConclusion != YES)
{
m_variables = keepVariables;
}
ROLEFILLERSMAP::const_iterator iter2;
for (iter2 = m_roleFillerPairs.begin(); (iter2 != m_roleFillerPairs.end()); iter2++)
{
if (iter2->second.get()->m_fillerType == CFiller::ePredicate)
{
iter2->second.get()->m_fillerPredicate.get()->Has(dPredicate,
dHypothesis, true);
}
}
if (!recursive)
{
if (dHypothesis->size() > 0)
{
// Let's look if the Predicate is enclosed within a logical primitive
dConclusion = NO;
for (unsigned int i = 0; i < dHypothesis->size(); i++)
{
CPredicate *curPredicate = dHypothesis->at(i).m_predicate->m_owner;
while (curPredicate != NULL)
{
if (curPredicate->GetPrimitive() == NOT)
{
if (dHypothesis->at(i).m_conclusion == YES)
{
dHypothesis->at(i).m_conclusion = NO;
}
else if (dHypothesis->at(i).m_conclusion != MAYBE)
{
dHypothesis->at(i).m_conclusion = NO;
}
}
else if ((curPredicate->GetPrimitive() == OR) &&
(dHypothesis->at(i).m_conclusion == YES))
{
dHypothesis->at(i).m_conclusion = MAYBE;
}
curPredicate = curPredicate->m_owner;
}
if (dHypothesis->at(i).m_conclusion > dConclusion)
{
dConclusion = dHypothesis->at(i).m_conclusion;
}
}
}
ResolveIntraPredicateInferences(*dHypothesis);
}
return dConclusion;
}
谓词内推理
Is
或 Has
方法可以与包含一些推理填充的谓词一起调用。例如,考虑这个谓词:
PTRANS[ACCELERATION:{DEFINED}
FINALSPEED:{NULL};='INITIALSPEED'+('ACCELERATION'*'TIMEPERIOD')
INITIALSPEED:{DEFINED}
TIMEPERIOD:{DEFINED}]
这可以读作“如果加速度、初始速度和时间段已定义,并且最终速度未知,那么最终速度就是初始速度加上加速度乘以时间段。” 这是简单的 H.S. 物理(V = Vi + a * t)。这种分辨率称为谓词内推理(因为所有分辨率元素都依赖于同一给定谓词)。
// A car that travels at a speed of 2 m/s and accelerates at 2.1 m/s2 for
// a period of 4 seconds.
shared_auto_ptr<CPredicate> dSpeedPredicate(new CPredicate());
dSpeedPredicate.get()->SetPredicateString("PTRANS[OBJECT:PP[TYPE:VEHICULE/CLASS:CAR]/
INITIALSPEED:2/ACCELERATION:2.1/TIMEPERIOD:4/TIME:PRESENT]");
printf("\"The car speeding predicate\":\n\n%s\n\n",
dSpeedPredicate->ToString().c_str());
// We infer the final speed from that predicate (V = Vi + at)
shared_auto_ptr<CPredicate> dSpeedInferencePredicate(new CKnowledgePredicate());
dSpeedInferencePredicate.get()->SetPredicateString(
"PTRANS[INITIALSPEED:{DEFINED}/ACCELERATION:{DEFINED}/TIMEPERIOD:{" +
"DEFINED}/FINALSPEED:{NULL};='INITIALSPEED'+('ACCELERATION'*'TIMEPERIOD')]");
printf("\"The speeding intra-predicate inference\":\n\n%s\n\n",
dSpeedInferencePredicate->ToString().c_str());
dConclusion = HasTest(dSpeedPredicate, dSpeedInferencePredicate);
shared_auto_ptr<CPredicate> dEvaluatedPredicate = dSpeedPredicate;
dEvaluatedPredicate->Evaluate();
printf("\"The car speeding predicate after intra-predicate inference\":\n\nResult:
%s\n\n%s\n\n", dConclusion.c_str(), dEvaluatedPredicate->ToString().c_str());
"The car speeding predicate":
PTRANS[ACCELERATION:2.1
INITIALSPEED:2
OBJECT:PP[CLASS:CAR
TYPE:VEHICULE]
TIME:PRESENT
TIMEPERIOD:4]
"The speeding intra-predicate inference":
PTRANS[ACCELERATION:{DEFINED}
FINALSPEED:{NULL};='INITIALSPEED'+('ACCELERATION'*'TIMEPERIOD')
INITIALSPEED:{DEFINED}
TIMEPERIOD:{DEFINED}]
"The car speeding predicate after intra-predicate inference":
Result: YES
PTRANS[ACCELERATION:2.1
FINALSPEED:=10.4
INITIALSPEED:2
OBJECT:PP[CLASS:CAR
TYPE:VEHICULE]
TIME:PRESENT
TIMEPERIOD:4]
知识库推理
为了从最初不相关的知识对象中进行推理,需要一个知识库。例如,陈述“Lee 的身高比 Peter 高 20%,但比 John Wilson 矮。”
为了填充推断值(身高)的第一步是构造和维护知识库。这样的知识库填充了 CKnowledgePredicate
对象,这些对象定义如下:
class CKnowledgePredicate: public CPredicate
{
public:
virtual void SetPredicateString(string dConstructionString) throw(
CMisconstructedPredicateString, CFiller::CMisconstructedFiller);
};
实现如下:
void CKnowledgePredicate::SetPredicateString(string dConstructionString) throw( CMisconstructedPredicateString, CFiller::CMisconstructedFiller) { CPredicate::SetPredicateString(dConstructionString); vector<ePrimitive> dPrimitivesVector; vector<CFiller::eFillerType> dFillerTypes; EnumeratePrimitivesAndFillersUsed(dPrimitivesVector, dFillerTypes); bool hasActionPrimitive = false; unsigned int i; for (i = 0; ((i < dPrimitivesVector.size()) && (!hasActionPrimitive)); i++) { hasActionPrimitive = ((dPrimitivesVector[i] >= s_ActionPrimitive_begin) && (dPrimitivesVector[i] <= s_ActionPrimitive_end)); } if (!hasActionPrimitive) { throw(CMisconstructedPredicateString( "No action primitive in knowledge predicate.")); } bool hasForbidenFillerTypes = false; for (i = 0; ((i < dFillerTypes.size()) && (!hasForbidenFillerTypes)); i++) { hasForbidenFillerTypes = ((dFillerTypes[i] == CFiller::eLessThanFiller) || (dFillerTypes[i] == CFiller::eGreaterThanFiller)); } if (hasForbidenFillerTypes) { throw(CMisconstructedPredicateString( "A forbiden filler type is used to construct predicate.")); } }
CKnowledgePredicate
的实现进一步约束了 CPredicate
对象应该如何。这有一些简单的原因。例如,我无法在知识库中添加谓词 PP[CLASS:CAR/TYPE:VEHICULE](“一辆汽车”)并期望从中获得任何有用的东西。仅仅说明“一辆汽车”,就不会获得新的知识。同样的情况也适用于谓词“一辆汽车或一艘船”。因此,在 CKnowledgePredicate
的实现中添加了至少一个动作原始概念的新条件。一旦动作原始概念包含在谓词中,它确实就是一个知识谓词。CKnowledgePredicate
实现的另一个约束关系到使用的填充类型。添加的知识需要是某种知识,而不是不确定性。例如,尝试添加谓词 MBUILD[OBJECT:PP[HEIGHT:>135/LASTNAME:WALKER/NAME:PETER]]“Peter Walker 的身高大于 135 厘米”将会失败,因为我们没有获得关于其身高的完整知识(而是识别了一个不确定性)。虽然可以修改实现来缓解这个约束(允许值范围,和/或不测试大于和小于的角色-填充对),但目前它就是这样的。
最后,知识库的定义如下:
class CKnowledgeBase
{
public:
friend class CKnowledgeBaseInferencePredicate;
CKnowledgeBase();
virtual void Add(shared_auto_ptr<CKnowledgePredicate> dNewKnowledge);
protected:
vector<shared_auto_ptr<CKnowledgePredicate>> m_knowledge_base;
};
第一步是填充一个包含事实(有些相关,有些不相关)的知识库。
shared_auto_ptr<CKnowledgeBase> dHeightKnowledgeBase(new CKnowledgeBase());
shared_auto_ptr<CKnowledgePredicate> dPredHolder2(new CKnowledgePredicate());
dPredHolder2.get()->SetPredicateString(
"PROPEL[ACTOR:PP[NAME:JOHN/LASTNAME:SMITH/HEIGHT:180]/" +
"OBJECT::PP[TYPE:FOOTBALL]/DISTANCE::85/TIME:PAST]]");
dHeightKnowledgeBase->Add(dPredHolder2);
shared_auto_ptr<CKnowledgePredicate> dPredHolder3(new CKnowledgePredicate());
dPredHolder3.get()->SetPredicateString("MBUILD[OBJECT:PP[NAME:JOHN/" +
"LASTNAME:WILSON/HEIGHT:205]]");
dHeightKnowledgeBase->Add(dPredHolder3);
shared_auto_ptr<CKnowledgePredicate> dPredHolder4(new CKnowledgePredicate());
dPredHolder4.get()->SetPredicateString("MBUILD[OBJECT:PP[NAME:PETER/HEIGHT:142]]");
dHeightKnowledgeBase->Add(dPredHolder4);
shared_auto_ptr<CKnowledgePredicate> dPredHolder5(new CKnowledgePredicate());
dPredHolder5.get()->SetPredicateString("MBUILD[OBJECT:PP[NAME:PETER/" +
"LASTNAME:WALKER/HEIGHT:135]]");
dHeightKnowledgeBase->Add(dPredHolder5);
shared_auto_ptr<CKnowledgePredicate> dPredHolder6(new CKnowledgePredicate());
dPredHolder6.get()->SetPredicateString("MBUILD[FROM:PP[NAME:JOHN/LASTNAME:RICHTER/" +
"HEALTH:!-10]/TO:PP[NAME:JOHN/LASTNAME:RICHTER/HEALTH:-10]/TIME:PAST]");
dHeightKnowledgeBase->Add(dPredHolder6);
printf("Populating knowledge base: \"John Smith, 180 cm tall, threw a football" +
"85 yards.\":\n\n%s\n\n", dPredHolder2.get()->ToString().c_str());
printf("Populating knowledge base: \"John Wilson is 205 cm tall.\":\n\n%s\n\n",
dPredHolder3.get()->ToString().c_str());
printf("Populating knowledge base: \"Peter is 142 cm tall.\":\n\n%s\n\n",
dPredHolder4.get()->ToString().c_str());
printf("Populating knowledge base: \"Peter Walker is 135 cm tall.\":\n\n%s\n\n",
dPredHolder5.get()->ToString().c_str());
printf("Populating knowledge base: \"John Richter died in the past.\":\n\n%s\n\n",
dPredHolder6.get()->ToString().c_str());
一旦准备就绪,我们需要创建一个类,其中 CPredicate
的行为将得到增强,以引用 CKnowledgeBase
对象来获得推理的分辨。这通过 CKnowledgeBaseInferencePredicate
类完成,该类定义如下:
class CKnowledgeBaseInferencePredicate: public CPredicate { public: CKnowledgeBaseInferencePredicate(CPredicate *owner = NULL) throw( CMisconstructedPredicateString); virtual void Evaluate(shared_auto_ptr<CKnowledgeBase> dKnowledgeBase) throw( CMathematicalError); protected: virtual shared_auto_ptr<CPredicate> Factory(string dConstructionString, CPredicate *owner = NULL); virtual void ProcessInference(string dInference) throw(); static CKnowledgeBase *m_knowledgebase; private: virtual void Evaluate() throw(CMathematicalError); };
该类的关键方法是 ProcessInference
方法。该方法在 CPredicate
类中被声明为虚拟方法,并在每个推理填充上调用,以便派生类可以解析它们。对于 CKnowledgeBaseInferencePredicate
的实现,它如下所示:
void CKnowledgeBaseInferencePredicate::ProcessInference(string dInference) throw() { if (dInference.find("KB >> ") == 0) { SearchAndReplace(dInference, "KB >> ", "", 1); shared_auto_ptr<CPredicate> dPredicate( new CKnowledgeBaseInferencePredicate()); dPredicate.get()->SetPredicateString(dInference); if (CKnowledgeBaseInferencePredicate::m_knowledgebase != NULL) { for (unsigned int i = 0; i < CKnowledgeBaseInferencePredicate::m_knowledgebase-> m_knowledge_base.size(); i++) { CKnowledgeBaseInferencePredicate::m_knowledgebase-> m_knowledge_base[i].get()->ClearVariables(); if ( CKnowledgeBaseInferencePredicate::m_knowledgebase-> m_knowledge_base[i].get()->Has(dPredicate) != NO) { VARIABLESMAP dVariables; CKnowledgeBaseInferencePredicate::m_knowledgebase-> m_knowledge_base[i].get()->GetVariables(dVariables); AppendVariables(dVariables); } } } } }
它基本上会遍历传递给 Evaluate
方法的 CKnowledgeBase
对象中获取的每个知识。
一旦这一切就绪,我们就可以继续构建我们的谓词,通过知识库推理来解决。
// Lee is 20% taller than Peter and smaller than John Wilson
shared_auto_ptr<CPredicate> dHeightPredicate2(new CKnowledgeBaseInferencePredicate());
dHeightPredicate2.get()->SetPredicateString(
"PP[NAME:LEE/HEIGHT:AND[VALUE1:>{PETERHEIGHT}*1.2;KB >>
PP[NAME:PETER/HEIGHT:{PETERHEIGHT}]/VALUE2:<{JOHNHEIGHT};KB >>
PP[NAME:JOHN/LASTNAME:WILSON/HEIGHT:{JOHNHEIGHT}]]]");
printf("\"Creating predicate: Lee is 20 percent taller than Peter and smaller than" +
"John Wilson.\":\n\n%s\n\n", dHeightPredicate2->ToString().c_str());
((CKnowledgeBaseInferencePredicate*)dHeightPredicate2.get())->Evaluate(
dHeightKnowledgeBase);
printf("\"Following inference\":\n\n%s\n\n", dHeightPredicate2->ToString().c_str());
shared_auto_ptr<CPredicate> dTestHeightPredicate(new CKnowledgeBaseInferencePredicate());
dTestHeightPredicate.get()->SetPredicateString("PP[NAME:LEE/HEIGHT:<200]");
dConclusion = IsTest(dHeightPredicate2, dTestHeightPredicate);
printf("Is Lee smaller than 200 cm?: %s\n\n", dConclusion.c_str());
这将生成以下输出:
Populating knowledge base: "John Smith, 180 cm tall, threw a football 85 yards."
:
PROPEL[ACTOR:PP[HEIGHT:180
LASTNAME:SMITH
NAME:JOHN]
DISTANCE::85
OBJECT::PP[TYPE:FOOTBALL]
TIME:PAST]]
Populating knowledge base: "John Wilson is 205 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:205
LASTNAME:WILSON
NAME:JOHN]]
Populating knowledge base: "Peter is 142 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:142
NAME:PETER]]
Populating knowledge base: "Peter Walker is 135 cm tall.":
MBUILD[OBJECT:PP[HEIGHT:135
LASTNAME:WALKER
NAME:PETER]]
Populating knowledge base: "John Richter died in the past.":
MBUILD[FROM:PP[HEALTH:!-10
LASTNAME:RICHTER
NAME:JOHN]
TIME:PAST
TO:PP[HEALTH:-10
LASTNAME:RICHTER
NAME:JOHN]]
"Creating predicate: Lee is 20 percent taller than Peter and smaller than John W
ilson.":
PP[HEIGHT:AND[VALUE1:>{PETERHEIGHT}*1.2;KB >> PP[NAME:PETER/HEIGHT:{PETERHEIGHT}
]
VALUE2:<{JOHNHEIGHT};KB >> PP[NAME:JOHN/LASTNAME:WILSON/HEIGHT:{JO
HNHEIGHT}]]
NAME:LEE]
"Following inference":
PP[HEIGHT:AND[VALUE1:OR[VALUE1:>170.4
VALUE2:>162]
VALUE2:<205]
NAME:LEE]
Is Lee smaller than 200 cm?: YES
关注点
这个项目是在几天内完成的,仅仅触及了谓词逻辑可能做到的皮毛。作为本文的扩展,最有趣的事情是进一步编写代码实现可以从自然语言输入动态构建谓词的规则。没错,你可以从自然语言输入构建谓词,然后所有这些谓词逻辑运算都可以应用于已经构建并填充到知识库中的概念。
我打算整理关于这个主题的笔记,并准备另一篇文章,介绍如何从自然语言输入构建谓词,并很快在此发布。请继续关注。
就本文而言,代码可以修改为解除知识库谓词中对大于或小于填充排除的约束。这会增加一些复杂性,但会产生一个更有用的算法。此外,当前算法中推理的分辨需要遍历知识库中的每一项知识,这是一个明显的局限性。可以通过知识索引机制来轻松改进算法。这样的知识索引机制可以基于一个组织良好的结构,例如这篇文章中解释的那样,主索引可以是谓词中使用的原始概念的层次结构,存储的数据将是知识谓词。将算法改编为使用这种结构将大大提高效率,并允许从中推断出大量的知识谓词。
我专业上使用此算法与语音识别有关。尽管整个方法受到美国专利号 7,286,987 和多项后续延期的专利保护,不能以公共领域许可证发布,但了解它是如何工作的仍然很有趣。在语音识别的背景下,语音分析过程会获取词语候选,有点像一个二维数组,其中包含可能说出的词语随时间的变化。一旦完成,句法分析过程就会接管,并将潜在的词语序列组合成语法上有效的词语序列。然后,概念分析过程才会使用谓词逻辑(或类似本文所述的方法)来区分有效概念和无效概念(人们总是优先选择纯粹的概念而不是带有某种不纯元素的概念)。结果是,语音识别通过概念分析过程进行消歧,而不是像如今大多数情况那样进行直接的语音分析。
其他许可说明
您可以随意在您的工作中尝试使用此技术和代码,但请注意,有限制的许可证正在使用中;该许可证排除了未经作者事先授权的商业和商业非营利性部署。有关完整的许可证协议,请参阅附件中的 license.txt 或 license.pdf。
历史
2009年1月1日:撰写本文初稿。