《人工智能——一种现代方法》(第二版)【第6章 对抗搜索】小结

第六章,总得来说,比较细致地讲了零和游戏中的极小极大值算法,还有对其强化的alpha-beta剪枝优化算法。然后,大概介绍了下截断搜索,多人游戏,几率游戏。

在竞争的环境中,每个智能体的目标是冲突的,于是就引出了对抗搜索问题——通常被称为博弈。

人工智能中的博弈专指博弈论专家们称为有完整信息的,确定性的,轮流行动的,两个游戏者的零和游戏。这意味着再确定性的,完全可观察的环境中的两个智能体必须交替行动,在游戏结束时效用值总是相等并符号相反的。

游戏可以形式地定义成含有下列组成部分的一类搜索问题:

*初始状态,包括棋盘局面和确定该哪个游戏者出招。

*后继函数,返回(move,state)对(两项分别为招数/状态)的一个列表,其中每一对表示一个合法的招数和其结果状态。

*终止测试,测试判断游戏是否结束。游戏结束的状态称为终止状态。

*效用函数(又称为目标函数或者收益函数),对终止状态给出一个数值。

每方的初始状态和合法招数定义了游戏的博弈树。

按照博弈的说法,我们说博弈树的深度是一步的,包括两个单方招数,每一个单方招数称为一层。

极小极大值算法的抽象表示:

MINIMAX-VALUE(n)= 

                                        UTILITY(n)当n为终止状态

                                        max s belongs to Successors(n) MINIMAX-VALUE(s) 当n为MAX节点

                                        min s belongs to Successors(n) MINIMAX-VALUE(s) 当n为MIN节点

伪代码如下:(维基百科上面的http://zh.wikipedia.org/wiki/%E6%9E%81%E5%B0%8F%E5%8C%96%E6%9E%81%E5%A4%A7%E7%AE%97%E6%B3%95)

function minimax(node, depth) // 指定当前节点和搜索深度
   // 如果能得到确定的结果或者深度为零,使用评估函数返回局面得分
   if node is a terminal node or depth = 0
       return the heuristic value of node
   // 如果轮到对手走棋,是极小节点,选择一个得分最小的走法
   if the adversary is to play at node
       let α := +∞
       foreach child of node
           α := min(α, minimax(child, depth-1))
   // 如果轮到我们走棋,是极大节点,选择一个得分最大的走法
   else {we are to play at node}
       let α := -∞
       foreach child of node
           α := max(α, minimax(child, depth-1))
   return α;

这个有点抽象过头,可以参考该书图6-3的伪代码。

上面的极小极大值算法对博弈树执行了一个完整的深度优先搜索。没有任何优化。如果树的最大深度是m,在每个节点合法的招数有b个,那么极小极大值算法的时间复杂度是O(b^m)。对于一次性生成所有的后继节点的算法,空间复杂度是O(bm),而对于每次生成一个后继的算法,空间复杂度是O(m)。

对于多人游戏的话,核心是把每个节点的单一值替换成一个向量值。然后博弈树每一层都会有多方的招数。

极小极大值算法的问题是必须检查的游戏状态的数目随着招数的数量指数级增长。通过alpha-beta剪枝技术,可以有效地将数目减半,这样不许要遍历博弈树中的每一个节点就可以计算出正确的极小极大值测策略。

极小极大搜索是深度优先的,所以任何时候我们不得不考虑树中一条单一路径上的节点。alpah-beta剪枝的名称就是从下面两个描述这条路径上任何地方的回传值界限的参数得到来:

alpha = 到目前为止我们在路径上的任意选择点发现的MAX的最佳(即极大值)选择

beta = 到目前为止我们在路径上的任意选择点发现的MIN的最佳(即极小值)选择

同样伪代码如下:

function alphabeta(node, depth, α, β, Player)         
    if  depth = 0 or node is a terminal node
        return the heuristic value of node
    if  Player = MaxPlayer // 极大节点
        for each child of node // 极小节点
            α := max(α, alphabeta(child, depth-1, α, β, not(Player) ))   
            if β ≤ α // 该极大节点的值>=α>=β,该极大节点后面的搜索到的值肯定会大于β,因此不会被其上层的极小节点所选用了。对于根节点,β为正无穷
                break                             (* Beta cut-off *)
        return α
    else // 极小节点
        for each child of node // 极大节点
            β := min(β, alphabeta(child, depth-1, α, β, not(Player) )) // 极小节点
            if β ≤ α // 该极大节点的值<=β<=α,该极小节点后面的搜索到的值肯定会小于α,因此不会被其上层的极大节点所选用了。对于根节点,α为负无穷
                break                             (* Alpha cut-off *)
        return β 
(* Initial call *)
alphabeta(origin, depth, -infinity, +infinity, MaxPlayer)


这个算法也是有缺陷的,它的效率很大程序上取决于检查后继的顺序。我们应该先尝试检查那些可能最好的后继。

另外,第三章提到过,搜索树中的重复状态会使搜索的代价显指数级增长。在游戏中,重复的状态频繁出现往往是因为调换——导致同样棋局的不同行棋序列的排列。第一次遇到某棋局时把对于该棋局的评价存储在哈希表里是值得的,这样当它后来再出现时不许要重新计算。存储以前见过的棋局的哈希表一般被称作调换表:它本质上和图搜索中哦功能的closed表相同。不过,如果我们可以每秒钟评价一百万个节点,那么调换表就不实际了。需要结合实际进行考虑。

接下来的是不完整的实时决策。

注意到,alpha-beta算法仍然要搜索至少一部分搜索空间直到终止状态。这样的搜索是不现实的,因为行棋要在合理的时间内确定。应该尽早截断搜索,把启发式评价函数用于搜索中的状态,有效地把非终止节点变为叶子终止节点。换句话说,建议按以下两种方法对极小极大值算法或alpha-beta算法进行修改,用可以估计棋局效用值的启发式函数EVAL取代效用函数,用可以决策什么时候运用EVAL的截断测试取代终止测试。

对于给定的棋局,评价函数返回一个对游戏的期望效用值的估计。

评价函数应该以和真正的效用函数同样的方式对终止状态进行排序;其次,评价函数的计算不能花费过多的时间;最后,对于非终止状态,评价函数应该和取胜的实际机会密切相关。

大多数评价函数的工作方式是计算状态的不同特征,这些特征在一起定义了状态的各种类别或者等价类:每类中的状态对所有特征都有相同的值。计算每个特征单独的数值贡献,然后把他们结合起来找到一个总值。(加权线性函数)这些假设前提都是每个特征的贡献独立于其他特征的值。但是,实际上很多都不是。需要用到非线性的特征组合。

截断搜索需要安排一些记录,这样当前的depth在每一次递归调用时逐渐增加。最直接的控制搜索次数的方法是设置一个固定的深度限制。根据花费时间不超过游戏规则许可的时间来决定深度d。

地平线效应:固定深度的搜素问题在于它相信这些延缓招数能阻止升变皇后的行棋——我们称延缓招数把不可避免的升变皇后的行棋“推出了搜索地平线”,将其推进了无法检测到的空间。

单一扩展,前向搜索……

包含几率因素的游戏,除了常规的MAX和MIN节点之外,还包括几率节点……

你可能感兴趣的:(AI)