博弈树搜索技术(Minimax算法,ɑ-β 算法)
一、 算法的理解
- Minimax算法
概括:算法可以概括为——己方利益最大化,对方利益最小化”。 即一方要在可选的选项中选择将其优势最大化的选择,而另一方则选择令对手优势最小化的方法。
实现方法:它使用了简单的深度递归搜索技术来确定每个节点的值:它从根节点开始,一直前进到叶子节点,然后在递归回溯时,将叶子节点的效用值往上回传——对于MAX 方,计算最大值,对于MIN 方,计算最小值。
def Minimax(node, depth, player):参数分别为(根节点,深度,玩家)
if depth == 0 or node is a terminal node:
递归出口:节点没有子节点或者已能判断出胜负的时候
return the heuristic value of node
if player == True:
bestValue = -∞
当是标记为true(max方,要bestValue最大)的玩家时,先赋值一个最小值,为取最大值做准备。然后开始取孩子节点中的最大值。
for each child of node:
v = Minimax(child, depth-1, False)
bestValue = max(bestValue, v)在当前孩子节点 与 当前记录的最大值中取最大值。
return bestValue
返回所有孩子节点中的最大值
else:否则玩家是false方(mini方,要bestValue最小),先赋值一个最大值,为取最大值做准备。然后开始取孩子节点中的最大值。
bestValue = +∞
for each child of node:
v = Minimax(child, depth-1, True)
bestValue = min(bestValue, v) 在当前孩子节点 与 当前记录的最大值中取最小值。
return bestValue返回所有孩子节点中的最小值
ɑ-β 算法
概括:ɑ-β 算法是在Minmax算法的基础上进行剪枝,从而提高搜索效率。Minimax 算法是基于深度优先的搜索,因此,任何时候只需要考虑树中某条路径上的节点。
剪枝条件:
ɑ路径上发现的MAX 方的最佳值;β路径上发现的MIN 方的最佳值;在搜索的过程中,ɑ-β 算法不断地更新MAX 方的_ 值和MIN 方的ɑ值,并且一旦条件成熟时即进行剪枝:MAX 方发现回传值低于自己的当前最佳值,即进行β剪枝;MIN 方发现回传值高于自己的当前最佳值,即进行ɑ剪枝。
ɑ-β 算法是在Minimax算法上进行改进的,算法的关键就在于剪枝。
结合伪代码着重讲一下剪枝
def alpha-beta(node, depth, alpha, beta, player):
if depth == 0 or node is a terminal node:
return the heuristic value of node
if player:
当是标记为true(max方)的玩家时,先赋值一个最小值,为取最大值做准备。然后开始取孩子节点中的最大值。
v = -∞
for each child of node:
v = max(v, alpha-beta(child, depth-1, alpha, beta, False))
alpha = max(alpha, v)
if beta <= alpha: MAX 方发现回传值低于自己的当前最佳值,即进行β剪枝。理解:mini方取最小值,所以mini方的取值范围为负无穷到beata,
max方取最大值,所以max的当前取值范围为malpha到正无穷。当beta下与alpha时,二者无交集,没有“商量”的余地,所以此时可以进行β剪枝
break #beta pruning
return v
else: MIN 方
v = +∞
for each child of node:
v = min(v, alpah-beta(child, depth-1, alpha, beta, True))
beta = min(beta, v)
if beta <= alpha: 道理和β剪枝类似。MIN 方发现回传值高于自己的当前最佳值,即进行ɑ剪枝。
break #alpha pruning
return v
#minimax
def Minimax(node, depth):
result=node.isGameOver()
if result!=None:
return result,(),depth
if node.getPlayer()==TicTacToe.BLACK:
bestValue=-sys.maxsize
bestmove=()
bestdepth=sys.maxsize
moves=node.getAllMoves()
for move in moves:
child=node.clone()
child.play(*move)
v,_,leafDepth=Minimax(child,depth+1)
if bestValue<v:#为什么不是<=
bestValue=v
bestmove=move
bestdepth=depth
if bestValue==v and bestdepth>leafDepth:
bestValue=v
bestmove=move
bestdepth=depth
return bestValue,bestmove,bestdepth
else:
bestValue=sys.maxsize
bestmove=()
bestdepth=sys.maxsize
moves=node.getAllMoves()
for move in moves:
child=node.clone()
child.play(*move)
v,_,leafDepth=Minimax(child,depth+1)
if bestValue>v:
bestValue=v
bestmove=move
bestdepth=depth
if bestValue==v and bestdepth>leafDepth:
bestValue=v
bestmove=move
bestdepth=depth
return bestValue,bestmove,bestdepth
#AlphaBeta
def AlphaBeta(node, depth,alpha,beta):
result = node.isGameOver()
if result != None:
return result, (), depth
if node.getPlayer() == TicTacToe.BLACK:
bestValue = -sys.maxsize
bestMove = ()
bestDepth = sys.maxsize
moves = node.getAllMoves()
for move in moves:
child = node.clone()
child.play(*move)
v, _, leafDepth = AlphaBeta(child, depth+1,alpha,beta)
if bestValue == v and bestDepth > leafDepth:
bestValue = v
bestMove = move
bestDepth = leafDepth
if bestValue < v:
bestValue = v
bestMove = move
bestDepth = leafDepth
alpha = max(alpha, bestValue)
if beta<=alpha:
break
return bestValue, bestMove, bestDepth
else:
bestValue = sys.maxsize
bestMove = ()
bestDepth = sys.maxsize
moves = node.getAllMoves()
for move in moves:
child = node.clone()
child.play(*move)
v, _, leafDepth = AlphaBeta(child, depth,alpha,beta)
if bestValue == v and bestDepth > leafDepth:
bestValue = v
bestMove = move
bestDepth = leafDepth
if bestValue > v:
bestValue = v
bestMove = move
bestDepth = leafDepth
beta = min(beta, bestValue)
if beta<=alpha:
break
return bestValue, bestMove, bestDepth
二、 运行结果(截图)
Minimax算法
ɑ-β 算法
三、 碰到的问题与解决办法
Minimax算法
实现过程中逻辑不够严密:
对于所有的孩子节点,在“择优”时,依据我的理解判断条件是bestValue<V(当前孩子节点的值)或者是bestValue<=都可以,因为选取的依据就是值的大小。
严谨正确的理解:通过分析老师的代码,判断的依据不仅仅是值的大小,还有节点的深度。当值有优势的时候,该节点被选中一定是没有疑问的。然而出现两个节点值相等时,就以深度为依据,选择深度小的节点。
ɑ-β 算法
由于我是在Minimax算法的基础上进行修改来实现ɑ-β 算法。首先是修改函数名,然后分别在max方和mini方中加入剪枝操作。剪枝操作比较简单,只需要两行代码。
这个问题能成功的编译和运行,在下第一步棋后就一场退出,提示如上错误。根据提示找到相对应的代码,网上搜索分析后,判断递归可能进入死循环,分析:到子节点时depth的值没有进行加一,在value值相等时,那些深度较小的节点被忽略,导致找不到“最大利益”,从而导致递归无出口。
四、 阶段总结
minimax算法通过递归的方式,从当前状态开始搜索,一直到游戏结束,然后回溯,在搜索时分别使两方的利益最大化,两方之间相互抗衡。但是当深度变深时,博弈树的节点数量就会指数爆炸。
依据A* 算法的原理,该算法从来不会扩展那些搜索代价大于f_ 的节点。实际上,从树搜索的角度看,这就是一种剪枝(Pruning)——这些节点及其分支没有得到扩展的机会,而被整个地剪掉了。对于minimax中双方不对最大化自己利益的过程中,出现的二者“不可商量”的情况进行舍弃。即ɑ-β 算中beta <= alpha,此时二者已无交集。对于这种情况进行剪枝,从而提高算法的效率。