python alpha beta 剪枝_alpha-beta 剪枝算法

$\alpha-\beta$ 剪枝算法

The minimax algorithm is a way of finding an optimal move in a two player game. Alpha-beta pruning is a way of finding the optimal minimax solution while avoiding searching subtrees of moves which won't be selected. In the search tree for a two-player game, there are two kinds of nodes, nodes representing your moves and nodes representing your opponent's moves. Nodes representing your moves are generally drawn as squares (or possibly upward pointing triangles):

博弈的基本概念

零和博弈 有完整信息的, 确定性的, 轮流的(回合的), 两个参与者的博弈

有完整信息 双方都可以查询本场游戏的完整历史记录完整信息举例不完整信息举例象棋、围棋、五子棋斗地主、红警、文明6

零和 双方的效用值在游戏结束时的代数和为0.

效用值 utival 是游戏结束时用于表征比赛胜负的变量, 如围棋中黑方 utival = 1, 白方 utival = -1, 可表示黑方获胜. 在博弈树上, 若根节点为 MAX 节点, 则叶节点上的值即为 MAX 方的效用值.

极小极大算法

极大值搜索 对于当前局面(父节点), 计算所有可能的落子点的局面得分, 选取其中得分最大者(最大子节点), 作为落子点.

极小极大搜索 设我方为 MAX 方, 对方为 MIN 方, utival 为效用值. 假设双方都同样地足够明智. MAX 方的目标是使效用值 utival 最大化, 而 MIN 方的目标是使 utival 最小化, 两方轮流落子.

现在我们从下到上倒着考虑. 考虑顶点为 MAX, 深度为n, 从上到下各层依次为 MAX1, MIN2, MAX3, MIN4, ..., MIN_n 的博弈树. 这棵树 MIN_n 层之下全为终局叶节点.

考虑 MIN_n 层结点 Ni , Ni 的子节点全为终局(叶)节点. 此时是 MIN 方的最后一步, 因此其一定会选择效用值最小的叶节点来落子, 因此在零和博弈的情况下结点 Ni 的下一步是确定的. 也就是说, MIN_n 层的全部结点的下一步都是可确定的. 我们将结点 Ni 的极小极大值 minimax 保存在 Ni 上. 同理, 对于 MAX_(n-1) 层的节点 Mi, 下一步也是确定的. 对其所有子节点取最大值, 即为 Mi 的 minimax 值, 保存在 Mi 上. 依次类推, 直到计算出根节点 MAX1 的 minimax 值.

极小极大算法使用了简单的递归算法, 计算根节点的每个后继结点的极大极小值. 算法自上而下一直前进到树的叶节点, 然后将极小极大值逐层回传. 将极小极大值回传的过程中, 记录落子位置, 也就是记录子节点的索引. 最后, 我们得到了根节点处的落子策略.

python实现class Tree:

def __init__(self, val, childs=None, maxormin='max'):

self.val = val

self.childs = childs # list

self.maxormin = maxormin

def is_leaf(self): # 当前结点是否是叶节点

return self.childs is None

def Max(self): # 返回子节点的最大值

return max([child.val for child in self.childs])

def argmax(self): # 返回子节点最大值位置的索引

return [child.val for child in self.childs].index(self.Max())

def Min(self): # 返回子节点的最大值

return min([child.val for child in self.childs])

def argmin(self): # 返回子节点最大值位置的索引

return [child.val for child in self.childs].index(self.Min())

def minimax(self):

inf = 999999999

choices = dict() # 记录每步的选择, choises[tree] = index

arg= -1 # 存放最大值或最小值的索引

def max_value(tree=self):

if tree.is_leaf():

return tree.val

nonlocal arg

v = -inf

for i, child in enumerate(tree.childs):

minval = min_value(child)

if minval > v:

v = max(v, minval)

arg = i

choices[tree] = arg

# print(v, choices)

return v

def min_value(tree=self):

if tree.is_leaf():

return tree.val

nonlocal arg

v = inf

for i, child in enumerate(tree.childs):

maxval = max_value(child)

if maxval < v:

v = min(v, maxval)

arg = i

choices[tree] = arg

# print(v, list(choices.keys())[0].childs, self)

return v

if self.maxormin == 'max':

return max_value(), choices[self]

else:

return min_value(), choices[self]

B = Tree(0, [Tree(3), Tree(12), Tree(8)], 'min')

C = Tree(0, [Tree(4), Tree(4), Tree(6)], 'min')

D = Tree(0, [Tree(14), Tree(5), Tree(2)], 'min')

A = Tree(0, [B, C, D], 'max')

E = Tree(0, [A, Tree(1)], 'min')

print(E.minimax())

$\alpha-\beta$ 剪枝

为每个结点引入两个新的变量——$\alpha$ 与 $\beta$, 它们的值会随着搜索过程而变化. 其中

$\alpha$ 表示结点minimax值的下界, $\beta$ 表示结点minimax值的上界. $[\alpha, \beta]$ 即为结点minimax可能的取值区间.

MAX 结点的作用是对子节点取大(取最大值), 因此 MAX 结点的 $\alpha$ 在搜索过程中单调递增.

MIN 结点的作用是对子节点取小(取最小值), 因此 MIN 结点的 $\beta$ 在搜索过程中单调递减.

以上是朴素的自然语言描述. 现在将上述内容数学化描述如下.

数学表述

设 $x, a, b \in \mathbb R,$ 我们定义, 若 $a \leqslant x,$ 则称 $a$ 为变量 $x$ 的一个下界. 若 $a \leqslant x, b \leqslant x,$ 且 $a

同理可定义上界与更优上界.

设 $\mathscr {M,N}$ 分别为全体 MAX , MIN 结点的集合. 结点$\mathcal M_i \in \mathscr M,$ 结点 $ \mathcal {N_j} \in \mathscr N$.

定义极小极大函数

$$

\mathrm {minimax}: \mathscr M \cup \mathscr N \to \mathbb R

$$

$\mathrm{minimax}(N)$ 表示结点 N 的极小极大值.

定义子节点函数

$$

\mathrm{Ch}:\mathscr M \cup \mathscr N \to M \cup \mathscr N

$$

$\mathrm {Ch}(N)$ 表示结点 N 的全体子节点的集合.

$\alpha_N, \beta_N$ 分别表示结点 N 的极小极大值 $\mathrm{minimax}(N)$ 的更优下界与更优上界.

事实上, 若 $x

同样地, MIN 层结点进行取小运算, 因此$\mathcal N_i$ 的极小极大值的更优上界 $\beta_{\mathcal N_i}$ 是只减不增的, 会越来越小.

由此, $\alpha_N,\beta_N$ 确定了一个取值范围, 即区间 $I_N=[\alpha_N,\beta_N],$ 结点 N 的极小极大值一定在 $I_N$ 内. 也就是说, $\mathrm{minimax}(N) \in [\alpha_N, \beta_N]$.

对于 MAX 结点 $\mathcal M_i,$

$$

\mathrm{minimax}(\mathcal M_i) = \max\limits_{N_j\in \mathrm{Ch(\mathcal M_i)} } {\mathrm{minimax}(\mathcal N_j)}

$$

也就是说, $\mathcal M_i$ 的极小极大值等于所有 $\mathcal M_i$ 的子节点的极小极大值取大.

对于 MIN 结点 $\mathcal N_i,$

$$

\mathrm{minimax}(\mathcal N_i) = \min\limits_{M_j\in \mathrm{Ch}(N_i)} {\mathrm {minimax}(\mathcal M_j) }

$$

也就是说, $\mathcal N_i$ 的极小极大值等于所有 $\mathcal N_i$ 的子节点的极小极大值取小.

下面解释 $\alpha-\beta$ 剪枝的过程.

仍然考虑 MAX 结点 $\mathcal M_i,$ 设当前 $\mathcal M_i$ 的 $\alpha, \beta$ 值分别为 $\alpha_{\mathcal M_i}, \beta_{\mathcal M_i}.$ 在对 $\mathcal M_i$ 的子节点逐一取大过程的某个时刻, 若存在某个 $\mathcal N_k \in \mathrm{Ch}(\mathcal M_i),$ 使得 $\alpha_{\mathcal N_k} < \alpha_{\mathcal M_i},$ 即 $\alpha_{\mathcal N_k} \notin [\alpha_{\mathcal M_i}, \beta_{\mathcal M_i}]. $ 那么这意味着, 相对于 $\mathrm{minimax}(\mathcal N_k), $ $\alpha_{\mathcal M_i}$是 $\mathrm{minimax}(\mathcal M_i)$ 的更优下界, 而 $\alpha_{\mathcal N_k}$ 是较差的下界. 因此我们直接将结点 $\mathcal N_k$ 及其全部后继结点舍去, 也就是说在 $\mathcal N_k$ 处进行剪枝. MIN 结点同理.

python实现inf = 999999999

class Tree:

def __init__(self, val, childs=None, maxormin='max', name=''):

self.val = val

self.childs = childs # list

self.maxormin = maxormin

self.alpha = -inf

self.beta = inf

self.name = name

def set_name(self, name):

self.name = name

def is_leaf(self): # 当前结点是否是叶节点

return self.childs is None

def Max(self): # 返回子节点的最大值

return max([child.val for child in self.childs])

def argmax(self): # 返回子节点最大值位置的索引

return [child.val for child in self.childs].index(self.Max())

def Min(self): # 返回子节点的最大值

return min([child.val for child in self.childs])

def argmin(self): # 返回子节点最大值位置的索引

return [child.val for child in self.childs].index(self.Min())

def minimax(self):

"""

返回self结点的极小极大值, 以及推荐落子位置的索引

:return: tuple, (Minimax value, index)

"""

choices = dict()

def helper(tree):

if tree.is_leaf():

tree.alpha = tree.val

tree.beta = tree.val

return

for i, child in enumerate(tree.childs):

# ab向下传递

child.alpha = tree.alpha

child.beta = tree.beta

# 更新child结点的a,b值

helper(child)

if tree.maxormin == 'max':

temp = tree.alpha

tree.alpha = max(tree.alpha, child.alpha, child.beta)

if temp != tree.alpha: # 若alpha值发生变化

choices[tree] = i

else: # min

temp = tree.beta

tree.beta = min(tree.beta, child.alpha, child.beta)

if temp != tree.beta: # 若beta值发生变化

print('???')

choices[tree] = i

# print(choices)

if tree.alpha >= tree.beta:

print(f'{tree.name} is pruned!', f'(a, b) = ({tree.alpha}, {tree.beta})')

return

print(f"{tree.name}: ({tree.alpha}, {tree.beta})")

# print(f"minimax of {tree.name} is {tree.alpha if tree.maxormin == 'max' else tree.beta}")

return tree.alpha if tree.maxormin == 'max' else tree.beta

ans = helper(self)

print('choices =', list(map(lambda item: item[0].name, list(choices.items()))), list(choices.values()))

index = list(choices.values())[list(choices.keys()).index(self)]

return ans, index

def test_tree():

D = Tree(0, childs=[Tree(1), Tree(2)], maxormin='max', name='D')

E = Tree(0, childs=[Tree(6), Tree(4)], maxormin='max', name='E')

F = Tree(0, childs=[Tree(3), Tree(5)], maxormin='max', name='F')

G = Tree(0, childs=[Tree(7), Tree(8)], maxormin='max', name='G')

B = Tree(0, childs=[D, E], maxormin='min', name='B')

C = Tree(0, childs=[F, G], maxormin='min', name='C')

A = Tree(0, childs=[B, C], maxormin='max', name='A')

return A

if __name__ == '__main__':

A = test_tree()

print(A.minimax())

参考资料<>

【课程】数据结构与算法Python版-北京大学-陈斌-19-棋类的决策树搜索

你可能感兴趣的:(python,alpha,beta,剪枝)