用搜索法对问题求解
本章将描述一种基于目标的智能体,称为问题求解智能体。将会提出几个通用的搜索算法,但这些算法是无信息的,除了问题的定义,没有其他关于问题的信息,下一章将探讨有信息的搜索算法。
1.问题求解智能体
通过评价多个未知的行动序列来选择最佳行动序列的智能体叫做问题求解智能体。寻找这样序列的过程叫做搜索。一个简单的问题求解智能体的算法如下。
function SIMPLE-PROBLEM-SOLVING-AGENT(percept) returns action inputs percept, a percept static seq, an action sequence, initially empty state, some description of the current world state goal, a goal, initially null problem, a problem formulation state <--- UPDATE-STATE(state,percept) if seq is empty then do goal <--- FORMULATE-GOAL(state) problem <--- FORMULATE-PROBLEM(state,goal) seq <- SEARCH(problem) action <--- FIRST(seq) seq <--- RESET(seq) return action
该算法输入当前感知,内部静态量有行动序列、状态、目标和问题。行动序列初始化为空,目标初始化为null。智能体每得到一个感知,就调用该算法进行动作求解。每次进入该程序时首先通过当前状态和感知更新状态。初始seq为空时,先格式化目标、完成目标遇到的问题、通过问题来搜索出最佳序列,当seq不为空时省略这一步。之后将序列的第一个动作赋予action,并将该动作从seq中删除(即重置seq),最后返回action。
该算法相当于人类有一个目标之后,分析问题,指定计划,一步步实施。缺点在于不能通过当前状态来时时更新计划(即seq)。使用环境属性来描述的话,该智能体的环境是:静态的、可观察的、离散的、确定的。本章剩下的篇幅主要介绍SEARCH函数。
1.1定义明确的问题及解
一个问题可以形式化地定义为四个组成部分。
- 初始状态。
- 智能体可采纳的行动的描述,常用的如后继函数。
- 目标测试,用来测试给定的状态是不是目标状态。
- 路径耗散
1.2把问题形式化
状态抽象化、行动抽象化、去除无关状态与行动。通俗的话说,把握重点,蝴蝶效应显然不能考虑在内。
2.对解的搜索
我们把所有可能的解通过节点来连成树,求解实际上就成为在树中搜索一个枝。节点的表示有很多中,我们可以假设其含有如下几个数据结构:
- STATE 状态空间中与节点相对应的状态。
- PARENT-NODE 父节点。
- ACTION 由父节点产生该节点所用的行动。
- PATH-COST 路径耗散
- DEPTH 初始状态到达该节点路径上最少的步骤
function TREE-SEARCH(problem,strategy) returns a solution, or failure initialize the search tree using the initial state of problem loop do if there are no candidates for expansion then return failure choose a leaf node for expansion according to strategy if the node contains a goal state the return the corresponding solution else expand the node and add the resulting nodes to the search tree
我们还需要表示生成出来,但没有被扩展的节点,这些点的集合成为边缘,边缘的每个元素都是叶节点,即搜索树中没有后继的节点。如果使用一个集合来表示边缘,概念上很直接,但是计算代价是昂贵的,因此我们使用队列来实现,下面是对队列的一些操作。
-
MAKE-QUEUE(element, ...) 用给定的元素创建一个队列。
- EMPTY?(queue) 当且仅当队列为空时返回一真.
- FIRST(queue) 返回队列中第一个元素。
- REMOVE-FIRST(queue) 返回FIRST(queue)并将其从queue中删除。
- INSERT(element,queue) 在队列中插入一个元素并返回结果队列,在不同队列中插入新元素顺序是不同的。
- INSERT-ALL(elements,queue) 在队列中插入元素集合
function TREE-SEARCH(problem, fringe) returns a solution, or failure fringe <--- INSERT(MAKE-NODE(INITIAL-STATE[problem]),fringe) loop do if EMPTY?(fringe) then return failure node <--- REMOVE-FIRST(fringe) if GOAL-TEST[problem] applied to STATE[node] succeeds then return SOLUTION(node) fringe <--- INSERT-ALL(EXPAND(node,problem),fringe) <hr> function EXPAND(node,problem) returns a set of nodes successors <--- the empty set for each(action, result) in SUCCESSOR-FN[problem](STATE[node]) do s <--- a new node STATE[s] <--- result PARENT-NODE[s] <--- node ACTION[s] <--- action PATH-COST[s] <--- PATH-COST[node] + STEP-COST(node,action,s) DEPTH[s] <--- DEPTH[node] + 1 add s to successors return successors
度量问题求解的性能
- 完备性 能否在有解的时候总能够到一个解
- 最有性 能够总是找到最优解
- 时间复杂度 找到解需要多长时间
- 空间复杂度 找到解需要多少内存
3.无信息的搜索策略
本节包括无信息搜索中的五种搜索策略。无信息搜索即除了问题定义之外,没有状态的其他附加信息。
1.广度优先搜索
广度优先搜索即首先扩展根节点,接着扩展根节点的所有后继,然后再扩展它们的后继,以此类推。
特点是在下一层任何节点被扩展之前,当前层所有节点都已经被扩展。它可以通过一个先进先出(FIFO)队列实现。
缺点:空间复杂度高、时间复杂度高
2.代价一致搜索
当所有单步耗散相等的时候,广度优先算法是最优的,因为它总是扩展深度最浅的未扩展节点。将其延伸,代价一致搜索是首先扩展路径耗散最低的节点。如果单步耗散一致的话,两种搜索是一致的。
3.深度优先搜索
深度优先搜索总是扩展搜索树的当前边缘的最深的节点。搜索直接推进到搜索树的最深层,那里的节点没有后继节点。那些节点扩展完之后,它们被从边缘中去掉,然后搜索算法“向上回到”下一个还有未扩展后继节点的 稍浅的节点。
这个搜索策略可以通过后进先出队列(LIFO)(栈)来实现。
深度优先搜索的一个变形称之为回溯搜索。回溯搜素中每个被部分扩展的节点要记住下一个要生成的节点是哪一个。这两种算法空间复杂度小。
4.深度有限搜索
即深度优先搜索中设定一个最大深度。深度有限搜索可能会引入不完备,但具体情况通过给出适合的深度限制,能够筛除不必要的搜索。
5.迭代深入深度优先搜索
迭代深入搜索是一个用来寻找最合适的深度限制的通用策略。它经常和深度优先搜索结合使用。他的做法是不断的增大深度限制,以此类推,直至找到目标节点。
6.双向搜索
运行两个同时的搜索, 一个从初始状态向前搜索,一个从目标状态向后搜索。当它们在中间相遇时终止搜索。
4.去除重复
算法中记录它访问过的每一个状态,相当于它直接探索状态空间图。为了做到这一点,我们可以在普通搜索算法中加入一个数据结构,成为closed表, 用它储存每一个扩展过的节点,储存未扩展过的边缘的表有时称之为open表。如果当前节点与closed表中的某一个节点相匹配,那么它将被放弃而不是被扩展。这个新的算法称之为图搜索——GRAPH-SEARCH。图搜索很有效,在最坏的情况下,它的时间和空间需求和状态空间成正比。
图搜索算法的优异性能给它带了一个棘手的问题。当一个重复状态被删除时,实际上算法找到了到达同一状态的不同路径。GRAPH-SEARCH会放弃后面找到的路径,如果之后找到的路径其耗散小一些,那么GRAPH-SEARCH就错过了最佳解。幸运的是,在单步耗散为常数的代价一致搜索和广度优先搜索的情况下这不会发生。另一方面,迭代深入算法使用深度优先扩展,很容易在找到最优解之前就找到一个非最优解, 因此迭代深入算法需要检测新发现的路径是否比以前的好,如果是则使用当前的代替以前的。
以下是GRAPH-SEARCH算法的一般版本。
function GRAPH-SEARCH(problem,fringe) returns a solution, or failure closed <--- an empty set fringe <--- INSERT(MAKE-NODE(INITIAL-STATE[problem]),fringe) loop do if EMPTY?(fringe) then return failure node <--- REMOVE-FIRST(fringe) if GOAL-TEST[problem](STATE[node]) then return SOLUTION(node) if STATE[node] is not in closed then add STATE[node] to closed fringe <--- INSERT-ALL(EXPAND(node,problem),fringe)
5.使用不完全信息的搜索
不同的不完全会导致三种不同的情况。
- 无传感问题(也称构造问题)。
- 偶发性问题。如果环境是部分可观察的或者行动不确定的,那么智能体在感知每个行动之后将会提供新的信息。每个可能的感知信息定义了一个必须提前准备处理计划的偶发事件。如果不确定性是由另外一个智能体引起的,就称其为对抗性的。
- 探索问题。当感知和行动都未知的时候,称之为探索问题。探索问题可以看做是偶发性问题的一种极端情况。
当环境是部分可观察的,智能体可以在信念状态空间,也就是也就是只能所处的可能状态集中应用搜索算法。在某些情况下,可以构造出唯一的序列解。:而其他情况下,智能体需要一个偶发性计划来处理可能出现的未知情况。
以上!