我正在编写一个强化学习检查引擎,但我遇到了性能障碍。网络能够在我的机器上快速学习,但是我的游戏/mcts 实现非常慢,以至于网络需要几个小时才能与自己玩几百场比赛。这使得学习过程非常缓慢,所以我决定使用位板重写游戏。这是我在重写之前的第一个实现:
import numpy as np
def jump_generator(i, j, board, player, is_king, jumped):
d = -(-1)**(i%2)
no_move = True
for l in [player, -player][0:is_king+1]:
for k in [j+d,j]:
if -1 < i+2*l and i+2*l < 8:
if -1 < j+d*(-1)**(j == k) and j+d*(-1)**(j == k) < 4:
if board[i+l][k]*player < 0:
if board[i+2*l][j+d*(2*(j != k)-1)] == 0:
if not (i+l,k) in jumped:
no_move = False
for continuation in jump_generator(i+2*l,
j+d*(-1)**(j == k),
board,
player,
is_king,
jumped+[(i+l, k)]):
yield [(i+l, k)] + continuation
if no_move:
yield [(i, j)]
def move_generator(i, j, board, player, is_king):
d = -(-1)**(i%2)
no_move = True
for l in [player, -player][0:is_king+1]:
for k in [j+d,j]:
if -1 < i+l and i+l < 8:
if -1 < k and k < 4:
if board[i+l][k]*player == 0:
yield [(i, j), (i+l, k)]
no_move = False
if no_move:
yield [(i, j)]
class GameState():
def __init__(self, board, counter=None, legal=None, score=None):
self.board = board
self.turn = self.board[-1]
if legal == None:
moves = []
jumps = []
for i, row in enumerate(self.board[:-1]):
for j, square in enumerate(row):
if square*self.turn > 0:
for jump_seq in jump_generator(i, j, self.board[:-1], self.turn, square**2 > 1, []):
if len(jump_seq) > 1:
jumps.append([(i, j)]+jump_seq)
if not jumps:
for move in move_generator(i, j, self.board[:-1], self.turn, square**2 > 1):
if len(move) > 1:
moves.append(move)
if jumps:
legal = jumps
else:
legal = moves
legal = [tuple(a) for a in legal]
self.legal = legal
if counter == None:
counter = 0
self.counter = counter
if score == None:
if self.counter > 50:
score = 0
if not self.legal:
score = -self.turn
self.score = score
def next_board(self, move):
board = [list(row) for row in self.board[:-1]]
is_king = board[move[0][0]][move[0][1]]**2 > 1
if not is_king and 2*move[-1][0] == 7+7*self.turn:
board[move[-1][0]][move[-1][1]] = self.turn*2
else:
board[move[-1][0]][move[-1][1]] = board[move[0][0]][move[0][1]]
board[move[0][0]][move[0][1]] = 0
move = move[1:-1]
for square in move:
board[square[0]][square[1]] = 0
return tuple([tuple(row) for row in board])+(-self.turn,)
def next_state(self, move):
man_moved = self.board[move[0][0]][move[0][1]]**2 == 1
reset_counter = man_moved or len(move) > 2
return GameState(self.next_board(move), (not reset_counter)*(self.counter+1))
本质上,游戏被表示为一个 4x8 numpy 数组,其条目范围为 -2、-1、0、1、2。这些值表示不同的棋子类型和一个空方块。辅助函数 jump_generator 和 move_generator 采用给定的正方形 (i, j) 并返回从那里可能的步骤/捕获列表。jump_function 递归生成强制捕获序列。游戏状态对象使用棋盘数组进行初始化,然后使用辅助函数生成合法动作。基本上就是这样。它还有一种从自己的法律行为中生成新状态的方法。
下面是我使用位板的新实现。之前的二维数组现在是四个 32 位整数。每个整数代表一个棋子类型,整数的二进制表示该棋子类型在棋盘上的存在。该板以一种巧妙的方式编码,因此只需将每个“移动向量”的位旋转固定数字即可访问角落相邻的方块。有用于这些位操作的函数,以及对导致每个移动“向量”越界的正方形进行编码的常量整数掩码。有一个函数需要四个平面(整数)并返回 8 个移动平面。这一切都是使用 np.uint32 上的位逻辑完成的。我什至使用了一些记忆。最后,这些平面通过位移“迭代”以找到合法移动并生成结果位置。
import numpy as np
int32 = np.uint32
int8 = np.uint8
zero = int32(0)
one = int32(1)
def rot(b, n):
n %= 32
n = int32(n)
return (b >> n) | (b << (int32(32)-n))
def reverse(b):
r = zero
for i in range(32):
r = r << one
if b & one:
r = r ^ one
b = b >> one
return r
def swap(b):
return reverse(rot(b, int32(20)))
masks_move = (~int32(2**1 + 2**5 + 2**9 + 2**11 + 2**17 + 2**25 + 2**31),
~int32(2**2 + 2**5 + 2**10 + 2**11 + 2**18 + 2**25 + 2**26 + 2**31),
~int32(2**0 + 2**1 + 2**6 + 2**9 + 2**12 + 2**17 + 2**18 + 2**25),
~int32(2**0 + 2**2 + 2**6 + 2**10 + 2**12 + 2**18 + 2**26))
masks_jump = (~int32(2**0 + 2**4 + 2**8 + 2**10 + 2**16 + 2**24 + 2**30),
~int32(2**3 + 2**4 + 2**19 + 2**24 + 2**27 + 2**30),
~int32(2**7 + 2**8 + 2**13 + 2**16 + 2**19 + 2**24),
~int32(2**1 + 2**3 + 2**7 + 2**11 + 2**13 + 2**19 + 2**27))
masks = masks_move + masks_jump
promote = ~int32(2**5 + 2**11 + 2**25 + 2**31)
def move_boards(m1, k1, m2, k2):
ally = m1 | k1
enemy = m2 | k2
every = ally | enemy
enemy_0 = rot(enemy, one)
enemy_4 = rot(enemy_0, one)
enemy_1 = rot(enemy_4, int32(5))
enemy_5 = rot(enemy_1, int32(7))
enemy_7 = rot(enemy_5, int32(4))
enemy_2 = rot(enemy_7, int32(7))
enemy_6 = rot(enemy_2, int32(5))
enemy_3 = rot(enemy_6, one)
ally_0 = rot(ally, one)
ally_4 = rot(ally_0, one)
ally_1 = rot(ally_4, int32(5))
ally_5 = rot(ally_1, int32(7))
ally_7 = rot(ally_5, int32(4))
ally_2 = rot(ally_7, int32(7))
ally_6 = rot(ally_2, int32(5))
ally_3 = rot(ally_6, one)
board0 = ally & ~(ally_0 | enemy_0) & masks[0]
board1 = ally & ~(ally_1 | enemy_1) & masks[1]
board2 = ally & ~(ally_2 | enemy_2) & masks[2]
board3 = ally & ~(ally_3 | enemy_3) & masks[3]
board4 = ally & enemy_0 & ~ally_4 & masks[0] & masks[4]
board5 = ally & enemy_1 & ~ally_5 & masks[1] & masks[5]
board6 = ally & enemy_2 & ~ally_6 & masks[2] & masks[6]
board7 = ally & enemy_3 & ~ally_7 & masks[3] & masks[7]
return (board0, board1, board2, board3, board4, board5, board6, board7)
def actions(m1, k1, m2, k2):
boards = move_boards(m1, k1, m2, k2)
every = zero
actions = []
for b, board in enumerate(boards):
every |= board
place = one
for i in range(32):
if place & every == place:
for b, board in enumerate(boards):
if place & board == place:
actions.append((i,b))
place <<= one
return actions
我通过让它们连续一千次“推出”到终端状态来测试两者。第一个实现的速度提高了 3-4 倍。对于任意多的游戏来说,这似乎都是正确的。