问题求解agent
背景介绍
上一节我们介绍了无信息搜索,这种搜索方式看上去挺无脑的,因为我们的搜索是毫无方向的。这节我们将来了解有信息搜索,这种搜索方式相比无信息搜索能更有策略、更高效的进行问题求解。这节我们会先了解启发式函数,然后学习A星搜索。
一、问题描述
所有问题描述跟上一节是相同的,这节的具体例子也跟上一节是相同的。
二、A星算法和启发式函数(heuristic function)
启发式函数的作用是评估出从给定状态到目标的成本。比如我们看上面的例子。从Arad 到Bucharest的直线距离的评估就是启发式函数要做的事,启发式函数给出了从Arad到Bucharest的最小路径代价。启发式函数是在搜索算法利用问题额外信息的最常见的形式。在本文中我们规定启发式函数所给出的评估非负,即h(n)>=0。若n是目标节点,则h(n)=0。
下面我们将讲解评估函数f(n)。
Evaluation function f(n) = g(n) + h(n)
- g(n)代表到目前为止到达n已经花费的成本
- h(n)代表从n到目标最接近的花费成本,这个成本是评估出来的,并不是实际的成本。
- f(n)代表穿过n到路径的成本,这个成本里有我们走到n时已经花费的成本,也有评估从n到目标预计花费的成本。
在实现A*算法时,我们需要使用一个能按照成本升序排列的FIFO队列。
A算法的前置知识已经讲完了,现在我们来讲讲什么是A算法。在搜索问题中,我们只需要最优解,然而在无信息搜索中我们并不能避免展开那些成本很高的路径。在A*搜索中,我们可以通过启发式函数来评估当前搜索路径的最小成本,从而避免继续扩展那些路径成本已经很高的路径了。
三、可采纳性和一致性
构建A算法的启发式函数是一个难点。为了让A算法的性能优越,我们需要让启发式函数符合两个特性——可采纳性(admissible)和一致性(consistency)。
1.可采纳性(admissible)
可采纳性是指启发函数不会过高估计到达目标的代价。因为g(n)是当前路径到达结点n的实际代价,而f(n)=g(n)+h(n),我们可以看出f(n)是永远不会超出经过结点n的解的实际成本的。如果h(n)给出的成本高于实际成本,A*算法将无法求出最优解。
2.一致性(consistency)
一致性条件一般作用于在图搜索中的A算法。如果对于每个结点n和通过任一行动a生成的n的每个后继节点n’,从结点n到达目标的估计代价不大于从n到n’的单步代价于从n’到达目标的估计代价之和: h(n)<=c(n,a,n’) + h(n’)。可能大家看到这段描述有点懵,我刚开始也不理解。这里我们画一个图来讲解。
整个路径是n是起点,经过n’到达终点G。我们在n点时f(n) = g(n)+h(n) = h(n).当我们到达了n’时f(n’) = g(n’)+h(n’) = c(n,a,n’)+h(n’).此时我们可以看出这其实是一个三角不等式。如果h(n)>c(n,a,n’)+h(n’)时,可采纳性不成立,A算法搜不出最优解。如果从n经过n’到达G比h(n)的成本小,这个三角形也不成立,此时相当于给h(n)规定了一个下界。如果h(n)给出的评估成本过小,那么A*算法会搜索很多多余的节点,启发式函数也就失去了意义。
四、代码
可能大家看到这里已经一头雾水了,这里我们给出代码,通过代码来分析上面我们提到的性质。
def astar_search(problem, h=None, display=False):
"""A* search is best-first graph search with f(n) = g(n)+h(n).
You need to specify the h function when you call astar_search, or
else in your Problem subclass."""
h = memoize(h or problem.h, 'h')
return best_first_graph_search(problem, lambda n: n.path_cost + h(n), display)
def best_first_graph_search(problem, f, display=False):
"""Search the nodes with the lowest f scores first.
You specify the function f(node) that you want to minimize; for example,
if f is a heuristic estimate to the goal, then we have greedy best
first search; if f is node.depth then we have breadth-first search.
There is a subtlety: the line "f = memoize(f, 'f')" means that the f
values will be cached on the nodes as they are computed. So after doing
a best first search you can examine the f values of the path returned."""
f = memoize(f, 'f')
node = Node(problem.initial)
frontier = PriorityQueue('min', f)
frontier.append(node)
explored = set()
while frontier:
node = frontier.pop()
if problem.goal_test(node.state):
if display:
print(len(explored), "paths have been expanded and", len(frontier), "paths remain in the frontier")
return node
explored.add(node.state)
for child in node.expand(problem):
if child.state not in explored and child not in frontier:
frontier.append(child)
elif child in frontier:
if f(child) < frontier[child]:
del frontier[child]
frontier.append(child)
return None
五、总结
最后,我们联系上一节的内容对这些搜索算法的使用场景和策略进行总结。
策略 | 解决方案 | 使用场景 | 队列 | 算法与状态空间 |
---|---|---|---|---|
DFS | 任意的 | 存在许多解 | LIFO | 有限无圈图的树搜索,有限无圈图的递归算法,为有限图添加循环检测 |
BFS | 最短的 | 解的步骤很短 | FIFO | 树搜索,图搜索(简单)可以提高性能 |
UC(一致代价搜索) | 最优的 | 缺乏良好的可采纳启发式函数 | 优先级队列通过g排序 | 树搜索,图搜索(简单)的等步成本,任意步长成本的图搜索(最优)且不会重复扩展 |
A* | 最优的 | 存在良好的可采纳启发式函数 | 优先级队列通过f排列 | 树搜索和可采纳启发式,可采纳启发式的图搜索(最优)(一致性启发式不需要重复扩展) |
贪心 | 通常都很好 | 好的(不可采纳性)启发式函数 | 优先级队列通过h排列 | 有限无圈图的树搜索,有限图的图搜索(简单) |
这篇博客写的比较简陋,后期我会再补充一些例子的,最近比较忙,很多事情蜂拥而至,耽误了写博客的任务。