`
fuerbosi
  • 浏览: 464588 次
文章分类
社区版块
存档分类
最新评论

人工智能之博弈二、最大最小值方法

 
阅读更多

现在我们来看看博弈树节点标注的另一种方法:最小最大值方法。整个博弈树尽管大的出奇,然而在只有一部分有用的情况下,利用最小最大值方法是有其优点的,很容易推广使用。

比方说,竞赛的结果是以钱为赌注的。为方便起见,设赌金为一块钱。如果棋手赢的,他就获得一块钱;如果他输了,这输一块钱。在和局的情况下,他不输也不赢。

我们把棋手赢的钱称之为收益。如果棋手赢了,其收益为1;如果输了,收益为-1;和局时为0。

现在我们来定义一个节点的值。对于w节点其收益为1;l为-1;d为0。

我们用不着一定要先把节点标上w、d或l,然后再去注明它们的值,而是可以直接计算出它们的值。

前面我们已经知道,对于每一个叶节点,比赛的结果或者是赢、或者是输、或者是和局。因此我们先把比赛能赢的那些叶节点标上1,把和局的节点标上0,把输的节点标上-1。

如同我们前面说过的那样。从叶节点开始,其余节点按照其子节点的值来计算出它的值,一直到把根结点的值算出来。

这种计算后面所包含的想法是:棋手总是选择能得到最大收益的棋步;而对手总是选择通往最小收益的棋步。更确切地说,我们按下面的规则来计算每个节点的值:

  1. 轮到棋手走时,节点的值是其子节点之中的最大值。
  2. 轮到对手走时,节点的值为其子节点中的最小值。


到现在为止,最大最小值方法已经给我们提供了一个简明而有效的手段来标注博弈树的节点。然而,这种方法不是采用任意的标号,如前面所说的w、l或d来标注节点,而是用数字来标注。这样做使得我们能够很容易的推广这个方法,不用把节点的值局限于1、0或-1。

终局评价

现在我们终于要面临这样一个不能回避的问题:即对于其国际象棋这样一类的比赛,我们不可能建造一个完整的博弈树。甚至也不可能建造该博弈树的基本部分。我们充其量所能做到的,就是围绕目前正在下的棋局,把博弈树中的很小一部分建造出来。

这样一来,我们所建造起来的博弈树的根就不再是棋赛的起始棋局,而是轮到棋手走的那个棋局。在博弈树中,我们对那些已经下过的棋局并不感兴趣,而只关心那些将要下的棋局。

同样的,这时博弈树的叶也不再代表这场棋赛赢、输或者和棋的终局,而是表示在合理的时间和允许的存储空间内,比赛路径所能达到的博弈树的最远的节点。


我们还是沿用以前采用的方法,把对应于叶节点的棋局叫做终局。那么,什么是终局的判断标准呢?或者更确切的说,在探索一条特定的比赛路径时,什么时候终止这种探索对竞赛才是有意义的呢?

一般我们倾向于首先对终局标上它们各自的值,用以反映每个终局对棋手的价值。然后我们用最小最大值方法来处理这些值。因此在考虑到双方所拥有的棋子的数量、类型以及它们在棋局中的作用等因素以后终局的值就可以很可靠的评价出来。

如果有一个棋局不能用这种方法来进行可靠的评价,那么该棋局就不是一个合适的终局。

深度优先的最小最大值评价方法

现在我们回头来看看棋手对当前的棋局是如何考虑的,以及应如何决定下一步的走法。假设有了一种方法,它对任何棋局都可以给出所有的合法棋步,同时还能给出采用这些合法棋步中任何一步以后所形成的新的棋局。另外我们假设已经有了上面所谈到判断终局的方法。我们希望能决定棋手应走的棋步。

我们从当前的棋局开始,将其扩展,然后再扩展它的子棋局,如此类推。这样我们就得到了一个部分博弈树。但是,每一个结点在展开之前,我们都要先检测这个节点是否为终局。如果是终局那么就不展开这个节点;如果不是那么就展开它。

上述的扩展一直进行下去,直到所有的节点都不能再展开为止。最后我们得到了这样一个博弈树,它的根结点对应于当前的棋局而其叶节点对应于我们认为的终局。

应用最小最大值方法,我们对该博弈树的每一个结点都标注上一个值。如前所述,棋手所要走的节点,它的值是其所有子节点中的最大值;而对手所走的节点,它的值是其所有子节点中的最小值。由于这些节点的计算方法本身的原故,那些非叶节点所标注的值通常称为回溯值。

对棋手来说,他所选择的最佳棋步就是能够得到具有最大回溯值棋局的棋步。

在对手的下一步棋走完以后,那么就要从这时的当前棋局开始建造一个新的博弈树。这一过程会不断重复。因此,每一个部分博弈树实际上只是用来决定一步棋。真正用到的值仅仅是根的子节点的回溯值。这些子节点所对应的棋局就是棋手自由选择棋步所能走到的棋局。

实际上,我们的确用不着一次就把整个博弈树储存起来。如果运用一个叫做深度优先的最小最大值方法,那么在只储存整个博弈树的很小一部分的情况下,我们也能计算出根的子节点的值。当节点需要计算其值时,我们就产生这个节点;当节点已经完成它的使命之后,我们便删除这个节点。

同所有的深度优先方法一样,深度优先的最小最大值方法是从根部开始处理,沿着从亲节点到子节点的方向进行,直到遇到终局为止。然后再返回,一直到找到另一条未曾探索的路径,再沿着这条路径到终局为止,如此类推。

这一方法可以同时既建立博弈树,又对其节点进行估值计算。在沿着从亲节点到子节点的方向采用这一方法时,节点是展开的。当节点第一次展开时,只新建了一个子节点。其他子节点只有在过程返回并重新访问该节点时才建立。只有那些处于当前正在搜索路径上的子节点以及那些尚未标注回溯值的子节点才被储存起来。


当采用这种方法达到终节点时,就使用评价函数来处理该终节点所对应的棋局,并对该节点标上评价值。然后,这种过程又回溯到终结点的新节点上去。

如前所述,在回溯期间如果访问到某个节点,通常要对该节点继续进行扩展,产生一个新的子节点,进而在对该新的子节点进行处理。实际上,该节点的所有子节点最终都会产生出来(即相应的棋局的所有合法棋步都会被探测到)。在处理过程重新遇到该节点时,就根据其子节点的值来计算出该节点的回溯值。是取这些之节点中的最大值还是最小值作为回溯值,这取决于轮到哪一方下棋。这时所有的子节点就不再有用了,可以删除掉。程序过程又回到刚刚赋值的亲节点上,再继续上述过程。

除了那些正处于扩展路径上的子结点以外,我们不需要储存任何别的子节点。我们对每一节点只标出其部分回溯值。在该节点产生时,该值作为节点的初始值;在以后节点每次被重新访问时,该值都会被更新。在任何时候,一个节点的部分回溯值总是其计算出来的子节点中的最大值或最小值。当一个节点的所有子节点的值都计算出来以后,其部分回溯值就作为该节点的值,也就是所有子节点中的最大值或者最小值。

对于轮到棋手下棋的一个节点来说,其部分回溯值一开始就给定为一个很大的负数。当重新访问到这个节点时,就要把这个值同刚刚计算出来的子节点的值进行比较,取它们中间大的那个作为该节点的新的回溯值。

对于轮到对手下棋的一个节点来说,其部分回溯值一开始就给定为一个很大的正数。当重新访问到这个节点时,就要把这个值同刚刚计算出来的时间点的值进行比较,取它们中间小的那个作为该节点的新的回溯值。

在我们需要储存的任何时刻,应该储存的节点仅仅是从根节点到该时刻当前节点之间的路径上的那些节点。把这些节点储存到堆栈中是非常方便的。在堆栈中,根节点在底部,当前节点在顶部。

尽管我们能实现只储存博弈树中从根节点到当前节点中的那一部分,但是却不能保证说这是最佳的方法。有些程序就是把整个博弈树储存起来的,以便先前查看过的对弈路径能被重新访问,从而做进一步的的查询。然而,在储存空间受到限制时,这种节省空间的方法却是富有吸引力的。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics