人工智能 归结原理实验报告

归结原理实验

一、实验目的

加深学生对归结原理进行定理证明过程的理解,掌握基于谓词逻辑的归
结过程中子句变换过程、替换与合一算法即归结策略等重要环节,进一步了解实
现机器自动定理证明的步骤。

二、实验内容

对于任意一阶谓词逻辑描述的定理,给出基于归结原理的证明过程。如输入:

A1 : (∀x)(P(x)(Q(x)R(x))) 
A2 : (∀x)(P(x)S(x)) 
G : (∃x)(S(x)R(x)) 

要证明:G 是 A1 和 A2 的逻辑结果
归结原理是一种推理规则。从谓词公式转化为字句集的过程中看出,在子句
集中字句之间是合取关系,其中只要有一个字句不可满足,则字句集就不可满足。
若一个字句集中包含空字句,则这个字句集一定是不可满足的。归结原理就是基
于这一认识提出来的。
应用归结原理进行定理证明的过程:
设要被证明的定义表示为:
A1∧A2∧…∧An→B
(1)首先否定结论 B,并将否定后的公式¬B 与前提公式集组成如下形式的谓
词公式:G= A1∧A2∧…∧An∧¬B。
(2)求谓词公式 G 的字句集 S。
(3)应用归结原理,证明子句集 S 的不可满足性。

三、实验环境

Visual Studio Code

四、实验步骤

步骤一 设计谓词公式的存储结构,即内部表示,注意对全称量词x 和存在
量词x 可采用其他符号代替。
原符号 替换符号
全称量词∀ @
存在量词∃ #
析取符号∨ |
合取符号∧ &
取反符号¬ ~
蕴含符号→ >
例 如 A1 : (∀x)(P(x)→(Q(x) ∧R(x))) , 输 入 的 时 候 要 替 换 为 A1 :
(@x)(P(x)>(Q(x)&R(x)))。
核心代码如下所示:

namespace FormulaNamepace {
// 公式符号定义
const char EQ = '#'; // 存在量词符号
const char UQ = '@';// 全称量词符号
const char IMPLICATION = '>'; // 蕴含符号
const char NEGATION = '~'; // 否定符号
const char CONJUNCTION = '&';// 合取符号
const char DISJUNCTION = '|'; // 析取符号
const char CONSTANT_ALPHA[] = { 'a', 'b', 'c', 'd', 'e',
'i', 'j', 'k'};
}
#endif // SUBSENTENCE_H

步骤二 变换子句集,可按以下过程变换,变换过程的核心代码如下所示:
1、消去蕴含连接词。

Formula& RemoveImplication(Formula& f)
{
FormulaIter iter;
while((iter = find(f.begin(), f.end(), IMPLICATION))
!= f.end()) {
*iter = DISJUNCTION; // 将蕴含符号替换为析取符号
FormulaRevIter revIter(iter);
 revIter =
GetBeginOfFormula(revIter, f.rend()); // 查找蕴含前件
 iter = revIter.base()-1;// 反向迭代器到正向迭代器转换需要减 1
f.insert(iter, NEGATION);// 在前件前面插入否定
}
return f;
}

2、将否定符号移到紧靠谓词的位置。

Formula& MoveNegation(Formula& f)
{
FormulaIter iter = find(f.begin(), f.end(), NEGATION);
while(iter != f.end()) {
if(*(iter+1) == '(') {// 否定不是直接修饰谓词公式,需要内移
// 否定符号修饰着带量词的谓词公式
if(*(iter+2) == EQ || *(iter+2) == UQ) {
// 量词取反
*(iter+2) == EQ ? *(iter+2) = UQ : *(iter+2) = EQ;
string leftDonePart(f.begin(), iter+5);
// 移除否定符号
leftDonePart.erase(find(leftDonePart.begin(),
leftDonePart.end(), NEGATION));
string rightPart(iter + 5, f.end());
// 否定内移
rightPart.insert(rightPart.begin(), NEGATION);
// 递归处理右部分
MoveNegation(rightPart);
string(leftDonePart + rightPart).swap(f);
return f;
}else { // 修饰着多个公式,形如~(P(x)|Q(x))
iter = f.insert(iter+2, NEGATION); // 内移否定符号
while(1) {
iter = FindFormula(iter, f.end());
assert(iter != f.end() && "No Predicate Formula!");
FormulaIter iter2 = FindPairChar(
iter, f.end(), '(', ')');
++iter2;
if(IsConnector(*iter2)) {
*iter2 == DISJUNCTION ? *iter2 = CONJUNCTION
 : *iter2 = DISJUNCTION;
iter = f.insert(iter2+1, NEGATION);
}else
break;
}
f.erase(find(f.begin(), f.end(),
NEGATION));// 清除原否定符号
return MoveNegation(f);
}
}else if(*(iter+1) == NEGATION) {// 两个否定,直接相消
f.erase(iter, iter + 2);
return MoveNegation(f); // 重新处理
}else {
iter = find(iter + 1, f.end(), NEGATION);
} }
return f;
}

3、适当改名使量词间不含同名指导变元,对变元标准化。

Formula& StandardizeValues(Formula& f)
{
set<char> checkedAlpha;
FormulaIter iter = FindQuantifier(f.begin(), f.end());
while(iter != f.end()) {
char varName = *++iter;// 获取变量名
if(checkedAlpha.find(varName) == checkedAlpha.end()) {
checkedAlpha.insert(varName);
}else { // 变量名冲突了,需要改名
// 获取新名字
char newName = FindNewLowerAlpha(checkedAlpha);
// 查找替换右边界
FormulaIter rightBorder = FindPairChar(
iter + 2, f.end(), '(', ')');
// 将冲突变量名替换为新的名字
*iter = newName;
replace(iter, rightBorder, varName, newName);
iter = rightBorder; // 移动到新的开始
}
iter = FindQuantifier(iter, f.end());
}
return f;
}

4、化为前束范式。

Formula& TransformToPNF(Formula& f)
{
FormulaIter iter = FindQuantifier(f.begin(), f.end());
if(iter == f.end())
return f;
else if(iter-1 == f.begin()) { // 量词已经在最前面
iter += 3;
string leftPart(f.begin(), iter);
string rightPart(iter, f.end());
TransformToPNF(rightPart); // 递归处理右部分
(leftPart + rightPart).swap(f);
}else { // 量词在内部,需要提到前面
string quantf(iter-1, iter+3); // 保存量词
f.erase(iter-1, iter+3); // 移除量词
f.insert(f.begin(), quantf.begin(), quantf.end());
return TransformToPNF(f); // 继续处理
}
return f;
}

5、消去存在量词。

Formula& RemoveEQ(Formula& f)
{
set<char> checkedAlpha;
FormulaIter eqIter = find(f.begin(), f.end(), EQ);
if(eqIter == f.end()) return f;
FormulaRevIter uqIter = find(FormulaRevIter(eqIter), f.rend(), UQ);
if(uqIter == f.rend()) { // 该存在量词前没有任意量词
char varName = *(eqIter + 1);
char newName = GetNewConstantAlha(f);
auto rightBound = FindPairChar(eqIter + 3, f.end(), '(', ')');
assert(rightBound != f.end());
replace(eqIter + 3, rightBound, varName, newName); // 常量化
f.erase(eqIter - 1, eqIter + 3); // 移除存在量词
}else {
// 记录公式中已经存在的字母
copy_if(f.begin(), f.end(),
inserter(checkedAlpha, checkedAlpha.begin()),
ptr_fun<int, int>(isalpha));
const char oldName = *(eqIter+1);
// 准备任意量词的函数来替换该存在量词
const char funcName = FindNewLowerAlpha(checkedAlpha);
string funcFormula;
funcFormula = funcFormula + funcName
+ '(' + *(uqIter-1) + ')';
f.erase(eqIter - 1, eqIter + 3); // 移除存在量词
ReplaceAlphaWithString(f, oldName, funcFormula);
}
RemoveOuterBracket(f);
return RemoveEQ(f); // 递归处理
}

6、消去全称量词。

Formula& RemoveUQ(Formula& f)
{
FormulaIter uqIter = find(f.begin(), f.end(), UQ);
while(uqIter != f.end()) {
uqIter = f.erase(uqIter-1, uqIter+3); // 直接移除全称量词
uqIter = find(uqIter, f.end(), UQ); // 继续扫描
}
RemoveOuterBracket(f);
return f;
}

7、化为 Skolem 标准型。

Formula& TransformToSkolem(Formula& f)
{
RemoveEQ(f);
RemoveUQ(f);
return f;
}

8、消去合取词,以子句为元素组成一个集合 S。

void ExtractSubsentence(SubsentenceSet& subset,
const Formula& f)
{
auto leftIter = f.begin(),
middleIter = find(f.begin(), f.end(), CONJUNCTION);
while(middleIter != f.end()) {
subset.insert(string(leftIter, middleIter));
leftIter = middleIter + 1;
middleIter = find(middleIter + 1, f.end(), CONJUNCTION);
}
subset.insert(string(leftIter, middleIter));
}

需要考虑子句、子句集的存储结构的设计。
步骤三 选择并设计归结策略,常用的归结策略有:
删除策略、支持集策略、线性归结策略、输入归结策略、单元归结策略、锁
归结策略、祖先过滤型策略等。
步骤四实现归结算法,并在其中实现合一算法,使用归结原理进行定理证
明,要求采用归结反演过程,即:
1、先求出要证明的命题公式的否定式的子句集 S; 2、然后对子句集 S(一次或者多次)使用归结原理;
3、若在某一步推出了空子句,即推出了矛盾,则说明子句集 S 是不可满足
的,从而原否定式也是不可满足的,进而说明原公式是永真的。
合一算法及核心代码如下:
1、置 k=0,Sk=S, σk =ε;
2、若 Sk 只含有一个谓词公式,则算法停止, σk 就是最一般合一;
3、求 Sk 的差异集 Dk; 4、若中存在元素 xk 和 tk ,其中 xk 是变元, tk 是项且 xk 不在 tk 中出现,
则置 Sk +1=Sk{tk/ xk} σk =ε然后转 Step2; 5、算法停止,S 的最一般合一不存在。

Sustitutes MGU(const FormulaNamepace::Subsentence& subsent1,
const FormulaNamepace::Subsentence& subsent2)
{
pair<Subsentence, Subsentence> w = { subsent1, subsent2 };
Sustitutes mgu;
while(w.first != w.second) { // w 未合一
// 找不一致集
auto iter1 = FindPredicate(w.first.begin(), w.first.end());
auto iter2 = FindPredicate(w.second.begin(), w.second.end());
while(iter1 != w.first.end() && iter2 != w.second.end()) {
if(*iter1 != *iter2)
break;
iter1 = FindPredicate(iter1 + 1, w.first.end());
iter2 = FindPredicate(iter2 + 1, w.second.end());
}
// 找到不一致集合
if(iter1 != w.first.end() && iter2 != w.second.end()) {
string item1 = GetPredicate(iter1, w.first.end());
string item2 = GetPredicate(iter2, w.second.end());
// 不允许置换有嵌套关系
if(StrStr(item1, item2) != item1.end() ||
StrStr(item2, item1) != item2.end()) {
throw ResolutionException("cannot unifier");
}
// 只允许常量替换变量
if(!IsConstantAlpha(*iter1))
item1.swap(item2);
// 更新置换,然后置换子句集
Sustitutes sustiSet = { make_pair(item1, item2) };
mgu = ComposeSustitutes(mgu, sustiSet);
Substitution(w.first, mgu);
Substitution(w.second, mgu);
}
else { // 两子句不可合一
throw ResolutionException("cannot unifier");
} }
return mgu;
}

步骤五 编写代码,调试程序

五、实验结果与分析

实验结果图
在这里插入图片描述
实验结果:
输入:

2
(@x)(P(x)>(Q(x)&R(x)))
(@x)(P(x)&S(x))
(#x)(S(x)&R(x))

输出:

谓词公式的个数:21个谓词公式: (@x)(P(x)>(Q(x)&R(x)))2个谓词公式: (@x)(P(x)&S(x))
逻辑结论为:(#x)(S(x)&R(x))
合并后的谓词公式:
(@x)(P(x)>(Q(x)&R(x)))&(@x)(P(x)&S(x))&~(#x)(S(x)&R(x))

消去蕴含:
(@x)(~P(x)|(Q(x)&R(x)))&(@x)(P(x)&S(x))&~(#x)(S(x)&R(x))

处理否定:
(@x)(~P(x)|(Q(x)&R(x)))&(@x)(P(x)&S(x))&(@x)(~S(x)|~R(x))

变元标准化:
(@x)(~P(x)|(Q(x)&R(x)))&(@l)(P(l)&S(l))&(@r)(~S(r)|~R(r))

消去存在:
(@x)(~P(x)|(Q(x)&R(x)))&(@l)(P(l)&S(l))&(@r)(~S(r)|~R(r))

化为前束范式:
(@x)(@l)(@r)(~P(x)|(Q(x)&R(x)))&(P(l)&S(l))&(~S(r)|~R(r))

化为 Skolem 标准型:
(~P(x)|(Q(x)&R(x)))&(P(l)&S(l))&(~S(r)|~R(r))

化为合取范式:
(~P(x)|Q(x))&(~P(x)|R(x))&P(l)&S(l)&(~S(r)|~R(r))

子句集:
(1) ~P(x)|Q(x)
(2) ~P(x)|R(x)
(3) P(l)
(4) S(l)
(5) ~S(r)|~R(r)
归结: 

归结策略1:

(6) ~R(r)          	[ (4),(5) { r/l } ]
(7) ~P(r)          	[ (2),(6) { r/x } ]
(8) Nil            	[ (3),(7) { r/l } ]
成功

归结策略2:

(6) Q(x)           	[ (3),(1) { x/l } ]
(7) R(x)           	[ (3),(2) { x/l } ]
(8) ~S(r)|~P(x)    	[ (5),(2) { x/r } ]
(9) ~R(r)          	[ (5),(4) { l/r } ]
(10) ~P(x)|~S(r)    	[ (2),(5) { r/x } ]
(11) ~S(r)          	[ (5),(7) { x/r } ]
(12) Nil            	[ (9),(7) { x/r } ]
成功

实验分析:归结采用水平归结,从尾部开始进行,即从结论出发开始遍历寻找相应结论的逆命题直至close表归结为Nill(空),此时结论证明成功。采用的是支持集策略。

六、实验总结

利用归结反演方法来证明定理的具体步骤为:
步1否定目标公式G,得到¬G;
步2将¬G并入到公式集F1^F2^...^Fn中;
步3将公式集化子句集,得到子句集S;
步4对S进行归结,每次归结的结果并入到S中。如此反复,直到得到空子句为止。此时,就证明了在前提F1^F2^...^Fn为真时,结论G为真。
支持集策略
(1)每次归结时,两个亲本子句中至少要有一个是目标公式否定的子句或其后裔。
(2)目标公式否定的子句及其后裔即为支持集。
特点
(1)尽量避免在可满足的子句集中做归结,因为从中导不出空子句。而求证公式的前提通常是一致的,所以支持集策略要求归结时从目标公式否定的子句出发进行归结。支持集策略实际是一种目标制导的反向推理。
(2)支持集策略是完备的。

实验指导书
实验指导书

参考文献:

人工智能归结原理实验
基于谓词逻辑的归结原理研究

代码
代码架构

  • 16
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是一个简单的人工智能归结原理实验代码,使用Python编写: ``` import re def unify(x, y, theta): if theta is None: return None elif x == y: return theta elif isinstance(x, str) and x[0].islower(): return unify_var(x, y, theta) elif isinstance(y, str) and y[0].islower(): return unify_var(y, x, theta) elif isinstance(x, list) and isinstance(y, list): return unify(x[1:], y[1:], unify(x[0], y[0], theta)) else: return None def unify_var(var, x, theta): if var in theta: return unify(theta[var], x, theta) elif x in theta: return unify(var, theta[x], theta) else: theta[var] = x return theta def subst(theta, clause): if theta is None: return None elif isinstance(clause, list): return [subst(theta, c) for c in clause] elif isinstance(clause, str) and clause[0].islower(): return subst_var(theta, clause) else: return clause def subst_var(theta, var): if var in theta: return subst(theta, theta[var]) else: return var def resolve(c1, c2): for l1 in c1: for l2 in c2: theta = unify(l1, negate(l2), {}) if theta is not None: resolvent = [subst(theta, c1_l) for c1_l in c1 if c1_l != l1] + \ [subst(theta, c2_l) for c2_l in c2 if c2_l != l2] yield resolvent def negate(literal): if literal[0] == "~": return literal[1:] else: return "~" + literal def is_variable(s): return isinstance(s, str) and s[0].islower() def is_constant(s): return isinstance(s, str) and s[0].isupper() def parse_input(s): clauses = [] for line in s.splitlines(): clause = [] for literal in re.findall(r'\w+', line): if literal[0].isupper(): literal_type = 'constant' else: literal_type = 'variable' clause.append((literal, literal_type)) clauses.append(clause) return clauses def main(): input_str = '''man(x) woman(x) loves(x, y) loves(y, x)''' goal = [('loves', 'variable', 'z')] clauses = parse_input(input_str) for i, clause in enumerate(clauses): print(f"Clause {i}: {clause}") new = set([tuple(c) for c in clauses]) while True: old = new new = set() for c1 in old: for c2 in old: if c1 != c2: for resolvent in resolve(c1, c2): resolvent = tuple(resolvent) if resolvent not in new and resolvent not in old: new.add(resolvent) print(f"New clause: {resolvent}") for goal_literal in goal: if unify(goal_literal, resolvent, {}) is not None: print("Goal achieved!") return if __name__ == '__main__': main() ``` 这个代码演示了一些基本的人工智能归结原理操作,包括: - `unify` 函数:用于统一两个项,即将它们赋予相同的值; - `subst` 函数:用于将一个子句中的变量替换为它们的统一值; - `resolve` 函数:用于对两个子句进行归结,生成归结式; - `negate` 函数:用于将一个文字取反; - `parse_input` 函数:用于将输入字符串解析为子句列表; - `main` 函数:用于执行归结过程,直到达到目标。 该代码不包含完整的人工智能归结原理算法实现,但可以作为一个基础框架,让你开始编写自己的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值