百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

AlphaGo Zero你也能造一个,PyTorch实现五脏俱全|附代码

csdh11 2024-11-30 14:13 5 浏览

原作 Dylan Djian

栗子棋 编译整理

量子位 出品 | 公众号 QbitAI

遥想当年,AlphaGo的Master版本,在完胜柯洁九段之后不久,就被后辈AlphaGo Zero (简称狗零) 击溃了。

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

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

如果能培育这样一只AI,即便自己不会下棋,也可以很骄傲吧。

于是,来自巴黎的少年Dylan Djian (简称小笛) ,就照着狗零的论文去实现了一下。

他给自己的AI棋手起名SuperGo,也提供了代码 (传送门见文底) 。

除此之外,还有教程——

一个身子两个头

智能体分成三个部分:

一是特征提取器 (Feature Extractor) ,二是策略网络 (Policy Network) ,三是价值网络 (Value Network) 。

于是,狗零也被亲切地称为“双头怪”。特征提取器是身子,其他两个网络是脑子

特征提取器

特征提取模型,是个残差网络 (ResNet) ,就是给普通CNN加上了跳层连接 (Skip Connection) , 让梯度的传播更加通畅。

跳跃的样子,写成代码就是:

1class BasicBlock(nn.Module):

2 """

3 Basic residual block with 2 convolutions and a skip connection

4 before the last ReLU activation.

5 """

6

7 def __init__(self, inplanes, planes, stride=1, downsample=None):

8 super(BasicBlock, self).__init__()

9

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

11 stride=stride, padding=1, bias=False)

12 self.bn1 = nn.BatchNorm2d(planes)

13

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

15 stride=stride, padding=1, bias=False)

16 self.bn2 = nn.BatchNorm2d(planes)

17

18

19 def forward(self, x):

20 residual = 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 out += residual

29 out = F.relu(out)

30

31 return out

然后,把它加到特征提取模型里面去:

1class Extractor(nn.Module):

2 def __init__(self, inplanes, outplanes):

3 super(Extractor, self).__init__()

4 self.conv1 = nn.Conv2d(inplanes, outplanes, stride=1,

5 kernel_size=3, padding=1, bias=False)

6 self.bn1 = nn.BatchNorm2d(outplanes)

7

8 for block in range(BLOCKS):

9 setattr(self, "res{}".format(block), \

10 BasicBlock(outplanes, outplanes))

11

12

13 def forward(self, x):

14 x = F.relu(self.bn1(self.conv1(x)))

15 for block in range(BLOCKS - 1):

16 x = getattr(self, "res{}".format(block))(x)

17

18 feature_maps = getattr(self, "res{}".format(BLOCKS - 1))(x)

19 return feature_maps

策略网络

策略网络就是普通的CNN了,里面有个批量标准化 (Batch Normalization) ,还有一个全连接层,输出概率分布

1class PolicyNet(nn.Module):

2 def __init__(self, inplanes, outplanes):

3 super(PolicyNet, self).__init__()

4 self.outplanes = outplanes

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.Linear(outplanes - 1, outplanes)

9

10

11 def forward(self, 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 return probas

价值网络

这个网络稍微复杂一点。除了标配之外,还要再多加一个全连接层。最后,用双曲正切 (Hyperbolic Tangent) 算出 (-1,1) 之间的数值,来表示当前状态下的赢面多大。

代码长这样——

1class ValueNet(nn.Module):

2 def __init__(self, inplanes, outplanes):

3 super(ValueNet, self).__init__()

4 self.outplanes = outplanes

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

6 self.bn = nn.BatchNorm2d(1)

7 self.fc1 = nn.Linear(outplanes - 1, 256)

8 self.fc2 = nn.Linear(256, 1)

9

10

11 def forward(self, 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 return winning

未雨绸缪的树

狗零,还有一个很重要的组成部分,就是蒙特卡洛树搜索 (MCTS) 。

它可以让AI棋手提前找出,胜率最高的落子点。

在模拟器里,模拟对方的下一手,以及再下一手,给出应对之策,所以提前的远不止是一步

节点 (Node)

树上的每一个节点,都代表一种不同的局势,有不同的统计数据:

每个节点被经过的次数n,总动作值w,经过这一点的先验概率p,平均动作值q (q=w/n) ,还有从别处来到这个节点走的那一步,以及从这个节点出发、所有可能的下一步

1class Node:

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 = parent

9 self.move = move

部署 (Rollout)

第一步是PUCT (多项式上置信树) 算法,选择能让PUCT函数 (下图) 的某个变体 (Variant) 最大化,的走法。

写成代码的话——

1def select(nodes, c_puct=C_PUCT):

2 " Optimized version of the selection based of the PUCT formula "

3

4 total_count = 0

5 for i in range(nodes.shape[0]):

6 total_count += nodes[i][1]

7

8 action_scores = np.zeros(nodes.shape[0])

9 for i in range(nodes.shape[0]):

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

11 (np.sqrt(total_count) / (1 + nodes[i][1]))

12

13 equals = np.where(action_scores == np.max(action_scores))[0]

14 if equals.shape[0] > 0:

15 return np.random.choice(equals)

16 return equals[0]

结束 (Ending)

选择在不停地进行,直至到达一个叶节点 (Leaf Node) ,而这个节点还没有往下生枝。

1def is_leaf(self):

2 """ Check whether a node is a leaf or not """

3

4 return len(self.children) == 0

到了叶节点,那里的一个随机状态就会被评估,得出所有“下一步”的概率。

所有被禁的落子点,概率会变成零,然后重新把总概率归为1。

然后,这个叶节点就会生出枝节 (都是可以落子的位置,概率不为零的那些) 。代码如下——

1def expand(self, probas):

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

3 for idx in range(probas.shape[0]) if probas[idx] > 0]

更新一下

枝节生好之后,这个叶节点和它的妈妈们,身上的统计数据都会更新,用的是下面这两串代码。

1def update(self, v):

2 """ Update the node statistics after a rollout """

3

4 self.w = self.w + v

5 self.q = self.w / self.n if self.n > 0 else 0

1while current_node.parent:

2 current_node.update(v)

3 current_node = current_node.parent

选择落子点

模拟器搭好了,每个可能的“下一步”,都有了自己的统计数据。

按照这些数据,算法会选择其中一步,真要落子的地方。

选择有两种,一就是选择被模拟的次数最多的点。试用于测试和实战。

另外一种,随机 (Stochastically) 选择,把节点被经过的次数转换成概率分布,用的是以下代码——

1 total = np.sum(action_scores)

2 probas = action_scores / total

3 move = np.random.choice(action_scores.shape[0], p=probas)

后者适用于训练,让AlphaGo探索更多可能的选择。

三位一体的修炼

狗零的修炼分为三个过程,是异步的。

一是自对弈 (Self-Play) ,用来生成数据。

1def self_play():

2 while True:

3 new_player, checkpoint = load_player()

4 if new_player:

5 player = new_player

6

7 ## Create the self-play match queue of processes

8 results = create_matches(player, cores=PARALLEL_SELF_PLAY,

9 match_number=SELF_PLAY_MATCH)

10 for _ in range(SELF_PLAY_MATCH):

11 result = results.get()

12 db.insert({

13 "game": result,

14 "id": game_id

15 })

16 game_id += 1

二是训练 (Training) ,拿新鲜生成的数据,来改进当前的神经网络。

1def train():

2 criterion = AlphaLoss()

3 dataset = SelfPlayDataset()

4 player, checkpoint = load_player(current_time, loaded_version)

5 optimizer = create_optimizer(player, lr,

6 param=checkpoint['optimizer'])

7 best_player = deepcopy(player)

8 dataloader = DataLoader(dataset, collate_fn=collate_fn, \

9 batch_size=BATCH_SIZE, shuffle=True)

10

11 while True:

12 for batch_idx, (state, move, winner) in enumerate(dataloader):

13

14 ## Evaluate a copy of the current network

15 if total_ite % TRAIN_STEPS == 0:

16 pending_player = deepcopy(player)

17 result = evaluate(pending_player, best_player)

18

19 if result:

20 best_player = pending_player

21

22 example = {

23 'state': state,

24 'winner': winner,

25 'move' : move

26 }

27 optimizer.zero_grad()

28 winner, probas = pending_player.predict(example['state'])

29

30 loss = criterion(winner, example['winner'], \

31 probas, example['move'])

32 loss.backward()

33 optimizer.step()

34

35 ## Fetch new games

36 if total_ite % REFRESH_TICK == 0:

37 last_id = fetch_new_games(collection, dataset, last_id)

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

1class AlphaLoss(torch.nn.Module):

2 def __init__(self):

3 super(AlphaLoss, self).__init__()

4

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).mean()

10 return total_error

三是评估 (Evaluation) ,看训练过的智能体,比起正在生成数据的智能体,是不是更优秀了 (最优秀者回到第一步,继续生成数据) 。

1def evaluate(player, new_player):

2 results = play(player, opponent=new_player)

3 black_wins = 0

4 white_wins = 0

5

6 for result in results:

7 if result[0] == 1:

8 white_wins += 1

9 elif result[0] == 0:

10 black_wins += 1

11

12 ## Check if the trained player (black) is better than

13 ## the current best player depending on the threshold

14 if black_wins >= EVAL_THRESH * len(results):

15 return True

16 return False

第三部分很重要,要不断选出最优的网络,来不断生成高质量的数据,才能提升AI的棋艺。

三个环节周而复始,才能养成强大的棋手。

年幼的SuperGo

小笛用学校的服务器训练了AI棋手一星期

SuperGo还年幼,是在9x9棋盘上训练的。

小笛说,他的AI现在好像还不懂生死一类的事,但应该已经知道围棋是个抢地盘的游戏了。

虽然,没有训练出什么超神的棋手,但这次尝试依然值得庆祝。

Reddit上面也有同仁发来贺电。

有志于AI围棋的各位,也可以试一试这个PyTorch实现。

代码实现传送门:

https://github.com/dylandjian/SuperGo

教程原文传送门:

https://dylandjian.github.io/alphago-zero/

AlphaGo Zero论文传送门:

https://www.nature.com/articles/nature24270.epdf

最后一句

昨天 (8月2日) ,是柯洁的生日。

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复“招聘”两个字。

量子位 QbitAI · 头条号签约作者

?'?' ? 追踪AI技术和产品新动态

相关推荐

Micheal Nielsen's神经网络学习之二

依然是跟着MichaelNielsen的神经网络学习,基于前一篇的学习,已经大概明白了神经网络的基本结构和BP算法,也能通过神经网络训练数字识别功能,之后我试验了一下使用神经网络训练之前的文本分类,...

CocoaPods + XCTest进行单元测试 c单元测试工具

在使用XCTest进行单元测试时,我们经常会遇到一些CocoaPods中的开源框架的调用,比如“Realm”或“Alamofire”在测试的时候,如果配置不当,会导致“frameworknotfo...

Java基础知识回顾第四篇 java基础讲解

1、&和&&的区别作为逻辑运算符:&(不管左边是什么,右边都参与运算),&&(如果左边为false,右边则不参与运算,短路)另外&可作为位运算符...

项目中的流程及类似业务的设计模式总结

说到业务流程,可能是我做过的项目中涉及业务最多的一个方面了。除了在流程设计之外,在一些考核系统、产业审批、还有很多地方,都用到相似的设计思路,在此一并总结一下。再说到模式,并不是因为流行才用这个词,而...

联想三款显示器首批获得 Eyesafe Certified 2.0 认证

IT之家7月31日消息,据外媒报道,三款全新联想显示器是全球首批满足EyesafeCertified2.0的设备。据报道,联想获得EyesafeCertified2.0认证的显...

maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期

1.maven生命周期一个完整的项目构建过程通常包括清理、编译、测试、打包、集成测试、验证、部署等步骤,Maven从中抽取了一套完善的、易扩展的生命周期。Maven的生命周期是抽象的,其中的具体任务都...

多线程(3)-基于Object的线程等待与唤醒

概述在使用synchronized进行线程同步中介绍了依赖对象锁定线程,本篇文章介绍如何依赖对象协调线程。同synchronized悲观锁一样,线程本身不能等待与唤醒,也是需要对象才能完成等待与唤醒的...

jquery mobile + 百度地图 + phonegap 写的一个"校园助手"的app

1jquerymobile+百度地图+phonegap写的一个"校园助手"的app,使用的是基于Flat-UI的jQueryMobile,请参考:https://github.com/...

Apache 服务启动不了 apache系统服务启动不了

{我是新手,从未遇到此问题,请各位大大勿喷}事由:今天早上上班突然发现公司网站出现问题。经过排查,发现是Apache出现问题。首先检查配置文件没有出问题后,启动服务发现Apache服务能启动,但是没法...

健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)

在Solr4.4之后,Solr提供了SolrCloud分布式集群的模式,它带来的主要好处是:(1)大数据量下更高的性能(2)更好扩展性(3)更高的可靠性(4)更简单易用什么时候应该使用Sol...

Eye Experience怎么用?HTC告诉你 eyebeam怎么用

IT之家(www.ithome.com):EyeExperience怎么用?HTC告诉你HTC上周除了发布HTCDesireEYE自拍机和HTCRE管状运动相机之外,还发布了一系列新的智能手机...

Android系统应用隐藏和应用禁止卸载

1、应用隐藏与禁用Android设置中的应用管理器提供了一个功能,就是【应用停用】功能,这是针对某些系统应用的。当应用停用之后,应用的图标会被隐藏,但apk还是存在,不会删除,核心接口就是Packag...

计算机软件技术分享--赠人玫瑰,手遗余香

一、Netty介绍Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty...

Gecco爬虫框架的线程和队列模型 爬虫通用框架

简述爬虫在抓取一个页面后一般有两个任务,一个是解析页面内容,一个是将需要继续抓取的url放入队列继续抓取。因此,当爬取的网页很多的情况下,待抓取url的管理也是爬虫框架需要解决的问题。本文主要说的是g...

一点感悟(一) 初识 初读感知的意思

时间过得很快,在IT业已从业了两年多。人这一辈子到底需要什么,在路边看着人来人往,大部分人脸上都是很匆忙。上海真是一个魔都,它有魅力,有底蕴,但是一个外地人在这里扎根置业,真的是举全家之力,还贷3...