通过围棋我学会了

蚂蚁世界2022-06-19  8

自学围棋的AlphaGo Zero,你也可以造一个 01

当时的AlphaGo大师版,在彻底击败柯洁九段后不久,就被后辈AlphaGo Zero(简称狗零)击败。

从一个完全不懂围棋的AI,到打败Master,狗零只用了21天。

而且不需要人类知识的喂养,成为顶尖棋手全靠自学。

如果能培养出这样的AI,就算不会下棋也可以骄傲。

于是,来自巴黎的少年迪伦Djian(简称小迪)就跟着苟零的论文实现了。

他将自己的AI棋手命名为SuperGo,还提供了代码(门户见文章底部)。

除此之外,还有教程 mdash mdash

一个身体两个头。

代理分为三个部分:

一个是特征提取器,一个是策略网络,第三个是价值网络。

所以狗零也被亲切地称为 双头怪物 。
特征提取器是身体,另外两个网络是大脑。

特征提取器

特征提取模型是残差网络(ResNet),在普通的CNN上增加了一个跳跃连接,使梯度传播更加平滑。

跳转的样子,用代码写就是:

1class BasicBlock(nn。模块):

2 quot""

具有2个卷积和一个跳跃连接的3个基本剩余块

4在最后一次ReLU激活之前。

5 quot""

7 def __init__(自身,平面内,平面,步幅=1,下采样=无):

8超(BasicBlock,self)。__init__()

10 self.conv1 = nn。Conv2d(inplanes,planes,kernel_size=3

11步幅=步幅,填充=1,偏移=假)

12 self.bn1 = nn。BatchNorm2d(平面)

13

14 self.conv2 = nn。Conv2d(planes,planes,kernel_size=3

15步幅=步幅,填充=1,偏移=假)

16 self.bn2 = nn。BatchNorm2d(平面)

17

18

19 def forward(自身,x):

20残差= x

21

22 out = self.conv1(x)

23 out = F.relu(self.bn1(out))

24

25 out = self.conv2(out)

26 out = self.bn2(out)

27

28输出+=剩余

29 out = F.relu(out)

30

31返回出去

然后,将其添加到特征提取模型中:

1级提取器(nn。模块):

2 def __init__(自身,内平面,外平面):

3超(提取器,自带)。__init__()

4 self.conv1 = nn。Conv2d(输入平面,输出平面,步幅=1,

5 kernel_size=3,填充=1,偏差=假)

6 self.bn1 = nn。BatchNorm2d(输出平面)

8表示范围内的块(块):

9 setattr(self, quotres { } quot。格式(块),\

10 BasicBlock(输出平面,输出平面))

11

12

13 def forward(自身,x):

14 x = f . relu(self . bn1(self . con v1(x)))

15代表范围内的块(块- 1):

16 x = getattr(self, quotres { } quot。格式(块))(x)

17

18 feature_maps = getattr(self, quotres { } quot。格式(块- 1))(x)

19返回特征_地图

战略网络

网络就是普通的CNN,有一个批量归一化,有一个输出概率分布的全连接层。

1类别政策网(nn。模块):

2 def __init__(自身,内平面,外平面):

3超(PolicyNet,self)。__init__()

4 self . output planes = output planes

5 self.conv = nn。Conv2d(inplanes,1,kernel_size=1)

6 self.bn = nn。BatchNorm2d(1)

7 self.logsoftmax = nn。LogSoftmax(dim=1)

8 self.fc = nn。线性(输出平面- 1,输出平面)

10

11定义向前(自身,x):

12 x = F.relu(self.bn(self.conv(x)))

13 x = x.view(-1,self.outplanes - 1)

14 x = self.fc(x)

15 probas = self.logsoftmax(x)。exp()

16

17返回probas

价值网络

这个网络稍微复杂一点。
除了标准,我们还需要添加另一个完整的连接层。
最后用双曲正切计算(-1,1)之间的值,以显示当前状态下赢面有多大。

代码看起来是这样的 mdash mdash

1类值网(nn。模块):

2 def __init__(自身,内平面,外平面):

3超(ValueNet,self)。__init__()

4 self . output planes = output planes

5 self.conv = nn。Conv2d(inplanes,1,kernel_size=1)

6 self.bn = nn。BatchNorm2d(1)

7 self.fc1 = nn。线性(输出平面- 1,256)

8 self.fc2 = nn。线性(256,1)

10

11定义向前(自身,x):

12 x = F.relu(self.bn(self.conv(x)))

13 x = x.view(-1,self.outplanes - 1)

14 x = F.relu(self.fc1(x))

15 winning = F.tanh(self.fc2(x))

16回赢

一棵树以备不时之需

狗,另一个重要组成部分,是蒙特卡罗树搜索(MCTS)。

可以让AI玩家提前发现胜率最高的落点。

模拟器里模拟对手的下一手和下一手,给出对策,所以提前的远不止一步。

节点(节点)

树中的每个节点代表一种具有不同统计数据的不同情况:

每个节点经过的次数n,总行动值w,经过这个点的先验概率p,平均行动值q (q=w/n),从其他地方到这个节点所走的步,以及从这个节点开始的所有可能的下一步。

1类节点:

2 def __init__(self,parent=None,proba=None,move=None):

3 self.p = proba

4 self.n = 0

5 self.w = 0

6 self.q = 0

7 self.children = []

8 self.parent =父母

9 self.move =移动

部署(推广)

第一步是PUCT(多项式上的信任树)算法,它选择可以最大化PUCT函数的一个变体的方式(如下)。

用代码编写 mdash mdash

1def select(节点,c_puct=C_PUCT):

2 quot基于PUCT公式 quot

4总计数= 0

对于范围内的I(nodes . shape[0]):

6 total_count += nodes[i][1]

8 action _ scores = NP . zeros(nodes . shape[0])

对于范围内的I(nodes . shape[0]):

10 action_scores[i] =节点[i][0] + c_puct *节点[i][2] * \

11(NP . sqrt(total _ count)/(1+nodes[I][1]))

12

13等于= NP . where(action _ scores = = NP . max(action _ scores))[0]

14 if equals.shape[0]>0:

15 return np.random.choice(等于)

16返回等于[0]

结束(结尾)

选择继续进行,直到到达尚未向下分支的叶节点。

1def is_leaf(self):

2 quot""检查节点是否是叶节点 quot""

4返回len(self.children) == 0

在叶节点处,将评估那里的随机状态,并且所有 下一步 的概率。

所有禁止落点的概率会变成零,然后总概率会再次归类为1。

然后这个叶节点会生出分支(都是可以掉的位置,概率不为零)。
代码如下 mdash mdash

1定义扩展(self,probas):

2 self . children =[Node(parent = self,move=idx,proba=probas[idx]) \

如果probas[idx] gt;则对于范围内的idx(probas . shape[0])为3;0]

更新它

在分支诞生后,这个叶节点及其母节点的统计数据将被更新,使用下面的两串代码。

1def更新(self,v):

2 quot""在卷展栏后更新节点统计""

4 self.w = self.w + v

5 self . q = self . w/self . n if self . n gt;0否则0

1当current_node.parent:

2当前节点更新(v)

3当前节点=当前节点.父节点

选择放置点

模拟器设置好了,一切可能的 下一步 ,有自己的统计。

根据这些数据,算法会选择其中一个步骤,在这个步骤中,它确实被留在了后面。

有两个选择。一种是选择模拟次数最多的点。
试试看测试和实战。

另一种是随机选择,它将节点被传递的次数转换成概率分布,使用下面的代码 mdash mdash

1 total = np.sum(action_scores)

2 probas =行动得分/总数

3 move = NP . random . choice(action _ scores . shape[0],p=probas)

后者适合训练,让AlphaGo探索更多可能的选项。

三位一体培养

养狗分三个流程,异步进行。

一个是自玩,用来生成数据。

1def self_play():

2虽然正确:

3新玩家,检查点=加载玩家()

4如果是新玩家:

5名玩家=新玩家

7 ##创建进程的自玩匹配队列

8结果= create_matches(玩家,核心=PARALLEL_SELF_PLAY,

9 match_number=SELF_PLAY_MATCH

10 for _ in范围(SELF_PLAY_MATCH):

11 result = results.get()

12 db.insert({

13 quot游戏 quot:结果,

14 quotid quot:游戏id

15 })

16 game_id += 1

第二种是训练,利用新产生的数据来改进当前的神经网络。

1定义训练():

2标准= AlphaLoss()

3 dataset = SelfPlayDataset()

4播放器,检查点= load_player(当前时间,已加载版本)

5优化器= create_optimizer(player,lr,

6 param =检查点[ # 39;优化器 # 39;])

7 best_player = deepcopy(player)

8 dataloader = DataLoader(数据集,collate_fn=collate_fn,\

9 batch_size=BATCH_SIZE,shuffle=True

10

11虽然正确:

12对于枚举(数据加载器)中的batch_idx,(state,move,winner):

13

14 ##评估当前网络的副本

15如果total_ite % TRAIN_STEPS == 0:

16 pending _ player = deep copy(player)

17结果=评估(待定_玩家,最佳_玩家)

18

19如果结果:

20最佳玩家=待定玩家

21

22示例= {

23 #39;国家 # 39;:状态,

24 #39;赢家 # 39;:赢家,

25 #39;动 # 39;:移动

26 }

27 optimizer.zero_grad()

28 winner,probas = pending_player.predict(示例[ # 39;国家 # 39;])

29

30损失=标准(赢家,示例[ # 39;赢家 # 39;], \

31 probas,示例[ # 39;动 # 39;])

32 loss.backward()

33 optimizer.step()

34

35 ##获取新游戏

36如果total_ite % REFRESH_TICK == 0:

37 last_id = fetch_new_games(集合,数据集,last_id)

训练的损失函数表示如下:

1类AlphaLoss(torch.nn.Module):

2 def __init__(self):

3超(AlphaLoss,self)。__init__()

5 def forward(self,pred_winner,winner,pred_probas,probas):

6 value _ error =(winner-pred _ winner)* * 2

7 policy _ error = torch . sum((-probas *

8 (1e-6 + pred_probas)。log()),1)

9 total _ error =(value _ error . view(-1)+policy _ error)。平均值()

10返回总计_错误

第三个是评估,看经过训练的代理是否比正在生成数据的代理更好(最好的一个回到第一步继续生成数据)。

1def evaluate(玩家,新玩家):

2结果=游戏(玩家,对手=新玩家)

3黑_胜= 0

4白色胜= 0

6对于结果中的结果:

7如果结果[0] == 1:

8白胜+= 1

9 elif结果[0] == 0:

10黑胜+= 1

11

12 ##检查受训球员(黑色)是否优于

13 ##当前最佳玩家取决于阈值

14 if black _ wins gt= EVAL _阈值*长度(结果):

15返回True

16返回假

第三部分非常重要。只有不断选择最好的网络,不断产生高质量的数据,才能提高AI的棋艺。

只有重复三个环节,才能培养出强大的棋手。

对AI Go感兴趣的也可以试试这个PyTorch实现。

最初取自量子位,原迪伦Djian。

代码实施门户:

网页链接

教程门户:

网页链接

AlphaZero纸质门户网站:

网页链接

转载请注明原文地址:https://juke.outofmemory.cn/read/372803.html

最新回复(0)