人工智能(7)Uniform Cost Search

Uniform cost search:

动态规划的核心是避免重复计算,是一种带有记忆地回溯搜索。对于搜索问题,比如,路径索搜,寻找从一个城市到终点城市的路径,不同的选择在搜索过程中会经过一些重复的城市,这些城市到终点城市的future cost就可以不用重复计算,存储下来即可。这一点可以从搜索路径的构造中看出来,刚开始,从出发点向下构造一棵树,每个结点代表一个城市,由于重复访问很多城市,节点的个数是指数增长的;当采用动态规划的方式之后,这样一棵树可以简化为一个非循环(acyclic)有向图的形式。这样的话,时间复杂度是O(n的平方),节点之间两两建立联系。当然,采用动态规划的前提也是存储的容量要足够包含中间的节点。

我们一直在重复,要记忆某些过去的状态,用来更好的预测将来行动的cost,和避免重复的计算。那么我们能不能选择性地记忆某些状态,忘记一些“没用”的状态,使算法更加地高效。一种状态只要足够用来帮助选择将来地行动即可。

那么问题来了,如果出现了循环怎么办?比如城市A到终点城市C依赖于城市B到城市C的距离,B又依赖于A,这样出现了相互依赖和循环的现象,如何解决这个问题?那就是uniform cost search (UCS)。

在动态规划中,我们要计算future的cost,在uniform cost search 中,要计算的是past cost. 对于两个点来讲,S, S’,如果处在一个非循环图中,毫无疑问,S在S’之前出现,那么计算past cost的时候, S也会在S‘之前计算。如果是一个循环图的话,就需要一个机制来排序不同的节点。

在UCS中,是按照past cost 递增的顺序来排序节点,并且我们的假设是每一个行动的cost都是非负的。但当面对行动是有回报(payoff),对应的cost就是负的,就需要用到Bellman-Ford 算法,这个在后面讲到 Markov Decision Process 的时候会提到。

其实UCS 和 Dijkstra 算法很像,逻辑上来讲,这两个是一样的;不同的地方是在于在实际应用中,UCS面对的问题是大型的甚至定义不明确的无限的一个图,Dijkstra 适用于一个非常具体的图。在实际应用中,面临的问题不仅仅是寻找两个城市最短的路径那么简单。
还有一点不同的地方是:Dijkstra 算法通常要寻找起始点到各个点中的最短的距离,而UCS通常要寻找一条到达终点的最短的路径。Uniform Cost Search指的是同等的对待那些past cost是一样的点,下次要说的A* 搜索会选择那些更偏向于终点的点。

通常来讲,在search的过程中,会将所有的点分成几个部分,Explored, Frontier, Unexplored。对应的,Explored 是那些已经找到从起始点到该点的最佳路径的那些点,Frontier 是那些还在寻找最优路径的那些点。Unexplored是那些我们都还没有预见到的点。这样一个过程是:把Unexplored的点放到Frontier集合里面,然后把Frontier集合里的点挪到Explored集合里面。当最终的节点挪到Explored里面之后,整个搜索过程就结束了。

USC算法实现过程(Dijkstra, 1956):

把初始结点放到Frontier集合里面(优先队列)
重复以下过程直到Frontier 变成空的:
从Frontier 里面把带有最小的优先权p(Past Cost)的结点取出来
如果s是终止状态的话,返回整个方案。
移动s到Explored 里面
对于每个行动s的每一个action:
得到相应的successor s’ <- Succ(s,a)
如果s’ 已经在Explored里面的话:continue 整个循环
添加s’到Frontier 集合,并且priority是p + cost(s,a). (注意:上面continue语句执行的话,这条语句就跳过了)

关于如何证明,一个Frontier中的点s跳出到Explored的时候的priority,也即PastCost(s) (简称Ps)就是从起点到s的最小的cost:

不妨假设现在:
Explored 集合:包括N多个点,其中有start, n 两个点(对于Explored中的点,priority也即PastCost)。
Frontier 集合:包括M多个点,包括s,m.
刚好start-n-m-s 存在一条路径,我们只要证明Ps 比这条任意的路径短就可以。
假设Pm 是m 的priority,那么Pm <= Pn + cost(n,a), cost(n,a)是n到m的cost,
那么Pm <= Pn + cost(n,a) <= Pn + cost(n,a) + cost(m,a), cost(m,a)是n到m的cost也即start-n-m-s路径的cost,cost都是非负值。
因为从Frontier里面跳出的是s,而不是m,所以 Ps <=Pm<= Pn + cost(n,a) + cost(m,a)。即证。
通常UCS 的时间复杂度和空间复杂度都是 O(b^(1+C/e)),C是最优路径的cost,e是每一步最小的cost,C/e是整个搜索的层数,我们前面提到,层数在时间开销上是指数级别的,由于需要存储每一层的节点到Frontier和Explored里面,空间复杂度也是指数级别的。

对比一下DP和UCS,如果考虑总共有N个状态,DP的时间和空间复杂度都是O(N)。对于UCS,只考虑终止状态之前的所有n个状态的话,时间空间复杂度都是O(n log n)。所以由此看来,实际应用中如何设计好一个状态(state)会对结果产生很大的影响。

在实际的应用中,我们通常使用python中自带的 PriorityQueue 。

来看一个简单的例子实现:

# 机器树 Uniform Cost Search
from queue import PriorityQueue

def ucs(graph, home, guest):
    if home not in graph:
        raise TypeError(str(home) + ' not found in graph !')
        return
    if guest not in graph:
        raise TypeError(str(guest) + ' not found in graph !')
        return
    # visited = []
    queue = PriorityQueue()
    queue.put((0, [home]))   
    # visited.append(home)

    while not queue.empty():
        # print ("Currnet queue is:",queue.queue)
        # 会取出queue里面cost最小的那个
        node = queue.get()
        # print ("Node:",node)
        # 避免重复搜索
        visited = node[1]
        current = node[1][len(node[1]) - 1]
        # current = node[1][0]
        # print ("Current:",current)
        if guest in node[1]:
            print("Path found: " + str(node[1]) + ", Cost = " + str(node[0]))
            break

        cost = node[0]
        for neighbor in graph[current]:
            if neighbor in visited:
                continue
            temp = node[1][:]
            # print ("Temp:",temp)
            temp.append(neighbor)
            # print ("Temp append neighbor:",temp)
            queue.put((cost + graph[current][neighbor], temp))
    # print (queue)

def main():
    file = open("maps.txt","r")
    lines = file.readlines()
    # 构建一个词典,来保存整个图
    graph = {}
    for line in lines:
        # print (line)
        token = line.split()
        node = token[0]
        graph[node] = {}

        for i in range(1, len(token)-1, 2):
            graph[node][token[i]] = int(token[i + 1])
    # graph = retrieval()
    # print (len(graph["Anyang"]))
    print ("Graph:",graph)
    ucs(graph, "Anyang", "HongKong")

if __name__ == "__main__":
    main()

maps.txt 里面的数据,每一行内城市名字用空格分开:

Anyang Zhengzhou 200 Beijing 500 Wuhan 600
Zhengzhou Anyang 200 Nanchang 800 Hefei 120
Beijing Xian 700 Wuhan 900 Anyang 500
Wuhan Nanchang 900 Hefei 450 Anyang 600 Beijing 900
Nanchang Shenzhen 400 Xiamen 700 Zhengzhou 800 Wuhan 900
Hefei Zhengzhou 120 Wuhan 450
Xian Beijing 700
Shenzhen HongKong 50 Nanchang 400
HongKong Shenzhen 50 Xiamen 500
Xiamen HongKong 500 Nanchang 700

下次一起来看看 A* 搜索。喜欢的朋友可以关注公众号喔。

《人工智能(7)Uniform Cost Search》 机器树.jpg

    原文作者:机器树
    原文地址: https://www.jianshu.com/p/18d53b2b9fd9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞