【问题标题】:Implementing the Prolog Unification algorithm in Python? Backtracking在 Python 中实现 Prolog 统一算法?回溯
【发布时间】:2018-03-04 22:48:39
【问题描述】:

我正在尝试实施统一,但遇到了问题.. 已经有十几个例子,但他们所做的只是搅浑水。我比开明更困惑:

http://www.cs.trincoll.edu/~ram/cpsc352/notes/unification.html

https://www.doc.ic.ac.uk/~sgc/teaching/pre2012/v231/lecture8.html [以下代码基于此介绍]

http://www.cs.bham.ac.uk/research/projects/poplog/paradigms_lectures/lecture20.html#representing

https://norvig.com/unify-bug.pdf

How can I implement the unification algorithm in a language like Java or C#?

Prolog one ...和其他几个的艺术。 最大的问题是我无法清楚地陈述问题。更多的数学或口齿不清的解释让我更加困惑。

作为一个好的开始,遵循基于列表的表示(就像在 lispy 案例中一样)似乎是个好主意,即:

pred(Var, val)  =becomes=> [pred, Var, val] 
p1(val1, p2(val2, Var1)) ==> [p1, val1, [p2, val2, Var1]]

除了你如何表示列表本身!?即 [H|T]

如果您能向我展示 Python 伪代码和/或更详细的算法描述或指向其中的指针,我会很高兴。

我掌握的一些要点是需要将general-unifier和var-unification中的代码分开,但是我看不到互斥的情况! ...等等。


作为旁注:我也希望您能提及您将如何处理回溯统一。我想我有回溯平方,但我知道在回溯时替换框架必须发生一些事情。


添加了当前代码的答案。

http://www.igrok.site/bi/Bi_language.html

http://www.igrok.site/bi/TOC.html

https://github.com/vsraptor/bi/blob/master/lib/bi_engine.py

【问题讨论】:

  • Prolog 列表只是列表构造函数. 和空列表[] 的语法糖。列表[a,b,c] 等价于[a | [b | [c | [] ]。列表构造函数| 本身就是二进制函数符号. 的语法糖。在内部列表[a,b,c] 看起来像.(a, .(b, .(c, [])))。在概念层面上,Prolog 列表的统一与其他术语没有区别。顺便说一句,在 Lisp 中,构造函数 . 被称为 cons,而空列表 [] 被称为 NIL
  • [a | [b | [c | [] ]]]
  • 感兴趣的:The design and implementation of a PROLOG interpreter 虽然这是在 Pascal 中完成的,但作为示例给出的许多概念仍然与任何实现 Prolog 的方式相关。

标签: python algorithm prolog backtracking unification


【解决方案1】:

我将快速总结来自Handbook of Automated Reasoning的Baader和Snyder关于Unification Theory的章节:

术语由常量(以小写字母开头)和变量(以大写字母开头)组成:

  • 没有参数的常量是一个术语:例如car
  • 以术语作为参数的常量,即所谓的函数应用程序,是术语。例如date(1,10,2000)
  • 变量是一个术语,例如Date(变量永远没有参数)

substitution 是将术语分配给变量的映射。在文献中,这通常写为{f(Y)/X, g(X)/Y} 或带有箭头{X→f(Y), Y→g(X)}。对术语应用替换会将每个变量替换为列表中的相应术语。例如。上面应用于tuple(X,Y) 的替换导致术语tuple(f(Y),g(X))

给定两个术语 stunifier 是使 st 相等的替换。例如。如果我们将替换{a/X, a/Y} 应用于术语date(X,1,2000),我们得到date(a,1,2000),如果我们将它应用于date(Y,1,2000),我们也得到date(a,1,2000)。换句话说,(句法)相等date(X,1,2000) = date(Y,1,2000) 可以通过应用统一符{a/X,a/Y} 来解决。另一个更简单的统一器是X/Y。最简单的这种统一器称为最通用的统一器。就我们的目的而言,知道我们可以将自己限制在搜索这样一个最通用的统一符并且如果它存在,它是唯一的(直到某些变量的名称)就足够了。

Mortelli 和 Montanari(参见文章的第 2.2 节和那里的参考资料)给出了一组规则来计算这样一个最通用的统一符(如果存在的话)。输入是一组术语对(例如 { f(X,b) = f(a,Y), X = Y } ),输出是一个最通用的统一符,如果它存在,或者如果它不存在则失败。在示例中,替换 {a/X, b/Y} 将使第一对相等(f(a,b) = f(a,b)),但第二对将不同(a = b 不正确)。

该算法不确定地从集合中选择一个等式并对其应用以下规则之一:

  • 简单:方程s = s(或X=X)已经相等,可以安全删除。
  • 分解:等式 f(u,v) = f(s,t) 被等式 u=sv=t 替换。
  • 符号冲突:相等 a=bf(X) = g(X) 以失败终止进程。
  • 方向:t=X 形式的等式,其中t 不是另一个变量,被翻转为X=t,这样变量就在左侧。
  • 发生检查:如果等式的形式为X=tt 本身不是X,并且如果X 出现在t 中的某个位置,我们将失败。 [1]
  • 变量消除:我们有一个方程X=t,其中X 没有出现在t 中,我们可以将替换t/X 应用于所有其他问题。

当没有规则可以应用时,我们最终得到一组方程式{X=s, Y=t, ...},代表要应用的替换。

这里有更多例子:

  • {f(a,X) = f(Y,b)} 是统一的: 分解得到 {a=Y, X=b} 翻转得到 {Y=a, X=b}
  • {f(a,X,X) = f(a,a,b)} 不可统一: 分解得到 {a=a,X=a, X=b},通过琐碎消除a=a,然后消除变量X 得到{a=b},并因符号冲突而失败
  • {f(X,X) = f(Y,g(Y))} 不可统一: 分解得到{X=Y, X=g(Y)},消除变量X得到{Y=g(Y)},发生检查失败

即使算法是不确定的(因为我们需要选择一个相等性来处理),但顺序并不重要。因为您可以提交任何订单,所以无需撤消您的工作并尝试不同的等式。这种技术通常称为回溯,对于 Prolog 中的证明搜索是必需的,但对于统一本身不是。

现在您只需为术语和替换选择合适的数据结构,并实现将替换应用于术语的算法以及基于规则的统一算法。

[1] 如果我们尝试求解 X = f(X),我们会看到 X 需要采用 f(Y) 的形式才能应用分解。这导致解决问题f(Y) = f(f(Y)) 和随后的Y = f(Y)。由于左侧总是比右侧少一个 f 的应用,所以只要我们将术语视为有限结构,它们就不可能相等。

【讨论】:

  • 我不得不同意,统一章节是统一的最权威的参考文献之一,如果不是最权威的,但作为一个在 OCaml、F# 等语言中实现统一算法的人,并看到它以 C、C# 等语言实现。这是denses 阅读的主题之一。
  • 至少在 Prolog 的上下文中,没有要处理的绑定器,并且具有变量表示的整个业务被简化了很多。
  • 很难不涉及太多细节 - 许多开发的技术解决了特定于使用的问题(例如比较交互式和自动定理证明中的数据结构)。]
  • 感谢这澄清了一些迷雾......非常好的介绍
  • (有什么理由在一个就足够的情况下使用三个反引号?)
【解决方案2】:

我比开悟更困惑

去过那里,做到了。

注意:对于任何引用的源代码,我都没有测试代码,也不能说它是有效的,它们是作为示例给出的,看起来足够正确,我会加载它们并针对它们运行测试用例以确定它们的有效性.

首先:如果您使用正确的术语,您将获得更好的搜索结果,使用backward chaining 而不是回溯。例如backward-chaining/inference.py

第二:了解您的问题列出了三个独立阶段。
1. 统一算法
2. 使用统一的反向链接
3. 列表的数据结构。您不会将其实现为 Python 源代码,而是作为要传递给您的函数的文本。见:cons

在进行反向链接之前,您应该首先开发并全面测试统一性。然后在创建列表数据结构之前充分开发和测试反向链接。然后全面测试您的列表数据结构。

第三种:实现统一算法的方法不止一种。
一种。您注意到使用转换规则的方法,或者在 Baader 和 Snyder 的 Unification Theory 中将其标记为 基于规则的方法,例如删除 分解
湾。我更喜欢 Baader 和 Snyder 在此 OCaml example 或 Python example 中给出的 Unification Theory 中称为 Unification by recursive descent 的算法
C。我见过一些使用排列但目前找不到好的参考。

第四:从个人经验出发,先用纸笔了解每个阶段的工作原理,然后在代码中实现。

第五:再次从个人经验来看,那里有很多关于如何做到这一点的信息,但数学和技术论文可能会令人困惑,因为许多人掩盖了对自学者或太密集了。我建议您改为专注于寻找源代码/数据结构的实现并使用它来学习。

第六:将您的结果与实际工作代码进行比较,例如SWI-Prolog.

在进入下一个阶段之前,您需要对每个阶段进行多少测试,并确保您拥有一套完整的测试用例,我怎么强调都不过分。

当我想学习如何用函数式语言编写此代码时,有关 AI 1 2 3The Programming Languages Zoo 的书籍非常宝贵。必须为LispOCaml 安装环境,但值得付出努力。

【讨论】:

  • 谢谢..必须重读几次..我已经开始你所说的..构建测试套件..修复了几个错误,我当前的变体(尚未发布)适用于每种情况我能想出。与 SWI 进行比较是对的。
  • 我的意思是回溯,即我也很感兴趣,当推理链中的一个术语失败时,您如何在回溯中展开统一!
  • 当一个目标失败时,它不应该释放目标初始化的所有绑定吗!?
  • 置换算法是否可能是使用Explicit Substitutions / λσ 演算的算法?
  • 为了使用回溯,请注意您正在执行的操作的不同部分。假设您有一个规则p(X) :- q(X). 和一个目标p(a)。通过统一,您会发现您必须应用统一器a/X,将规则实例化为p(a) :- q(a).,并将q(a) 设为新目标。如果此路径失败,您可能会遵循头部统一的不同规则。保存此撤消信息的经典数据结构是堆栈(或函数式编程中的累加器)。
【解决方案3】:

到目前为止,这适用于我提出的所有情况(除了一个需要发生检查的情况,我还没有完成):

def unify_var(self, var, val, subst):
#   print "var> ", var, val, subst

    if var in subst :   
        return self.unify(subst[var], val, subst)
    elif isinstance(val, str) and val in subst : 
        return self.unify(var, subst[val], subst)
    #elif (var occurs anywhere in x) then return failure
    else :
        #print "%s := %s" % (var, val)
        subst[var] = val ; return subst

def unify(self, sym1, sym2, subst):
    #print 'unify>', sym1, sym2, subst

    if subst is False : return False
    #when both symbols match
    elif isinstance(sym1, str) and isinstance(sym2, str) and sym1 == sym2 : return subst
    #variable cases
    elif isinstance(sym1, str) and is_var(sym1) : return self.unify_var(sym1, sym2, subst)
    elif isinstance(sym2, str) and is_var(sym2) : return self.unify_var(sym2, sym1, subst)
    elif isinstance(sym1, tuple) and isinstance(sym2, tuple) : #predicate case
        if len(sym1) == 0 and len(sym2) == 0 : return subst
        #Functors of structures have to match
        if isinstance(sym1[0], str) and  isinstance(sym2[0],str) and not (is_var(sym1[0]) or is_var(sym2[0])) and sym1[0] != sym2[0] : return False
        return self.unify(sym1[1:],sym2[1:], self.unify(sym1[0], sym2[0], subst))
    elif isinstance(sym1, list) and isinstance(sym2, list) : #list-case
        if len(sym1) == 0 and len(sym2) == 0 : return subst
        return self.unify(sym1[1:],sym2[1:], self.unify(sym1[0], sym2[0], subst))

    else: return False

FAIL 案例应该是失败的:

OK: a <=> a : {}
OK: X <=> a : {'X': 'a'}
OK: ['a'] <=> ['a'] : {}
OK: ['X'] <=> ['a'] : {'X': 'a'}
OK: ['a'] <=> ['X'] : {'X': 'a'}
OK: ['X'] <=> ['X'] : {}
OK: ['X'] <=> ['Z'] : {'X': 'Z'}
OK: ['p', 'a'] <=> ['p', 'a'] : {}
OK: ['p', 'X'] <=> ['p', 'a'] : {'X': 'a'}
OK: ['p', 'X'] <=> ['p', 'X'] : {}
OK: ['p', 'X'] <=> ['p', 'Z'] : {'X': 'Z'}
OK: ['X', 'X'] <=> ['p', 'X'] : {'X': 'p'}
OK: ['p', 'X', 'Y'] <=> ['p', 'Y', 'X'] : {'X': 'Y'}
OK: ['p', 'X', 'Y', 'a'] <=> ['p', 'Y', 'X', 'X'] : {'Y': 'a', 'X': 'Y'}
 ================= STRUCT cases ===================
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('p', 'a')] : {'X': 'Y'}
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('p', 'Z')] : {'X': 'Y', 'Z': 'a'}
OK: ['e', 'X', ('p', 'a')] <=> ['e', 'Y', ('P', 'Z')] : {'X': 'Y', 'Z': 'a', 'P': 'p'}
OK: [('p', 'a', 'X')] <=> [('p', 'Y', 'b')] : {'Y': 'a', 'X': 'b'}
OK: ['X', 'Y'] <=> [('p', 'a'), 'X'] : {'Y': ('p', 'a'), 'X': ('p', 'a')}
OK: [('p', 'a')] <=> ['X'] : {'X': ('p', 'a')}
-----
FAIL: ['e', 'X', ('p1', 'a')] <=> ['e', 'Y', ('p2', 'Z')] : False
FAIL: ['e', 'X', ('p1', 'a')] <=> ['e', 'Y', ('p1', 'b')] : False
FAIL: [('p', 'a', 'X', 'X')] <=> [('p', 'a', 'a', 'b')] : False
(should fail, occurs) OK: [('p1', 'X', 'X')] <=> [('p1', 'Y', ('p2', 'Y'))] : {'Y': ('p2', 'Y'), 'X': 'Y'}
================= LIST cases ===================
OK: ['e', 'X', ['e', 'a']] <=> ['e', 'Y', ['e', 'a']] : {'X': 'Y'}
OK: ['e', 'X', ['a', 'a']] <=> ['e', 'Y', ['a', 'Z']] : {'X': 'Y', 'Z': 'a'}
OK: ['e', 'X', ['e', 'a']] <=> ['e', 'Y', ['E', 'Z']] : {'X': 'Y', 'Z': 'a', 'E': 'e'}
OK: ['e', 'X', ['e1', 'a']] <=> ['e', 'Y', ['e1', 'a']] : {'X': 'Y'}
OK: [['e', 'a']] <=> ['X'] : {'X': ['e', 'a']}
OK: ['X'] <=> [['e', 'a']] : {'X': ['e', 'a']}
================= FAIL cases ===================
FAIL: ['a'] <=> ['b'] : False
FAIL: ['p', 'a'] <=> ['p', 'b'] : False
FAIL: ['X', 'X'] <=> ['p', 'b'] : False

【讨论】:

  • 如果您从其他地方复制代码,或者您的代码与您复制的代码非常接近,那么您需要引用它以便人们知道。这也不是真正的答案,应该作为更新包含在您的问题中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-07
  • 1970-01-01
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多