01hr遥想当年,AlphaGo的Master版本,在完胜柯洁九段之后不久,就被后辈AlphaGoZero(简称狗零)击溃了。 从一只完全不懂围棋的AI,到打败Master,狗零只用了21天。 而且,它不需要用人类知识来喂养,成为顶尖棋手全靠自学。 如果能培育这样一只AI,即便自己不会下棋,也可以很骄傲吧。 于是,来自巴黎的少年DylanDjian(简称小笛),就照着狗零的论文去实现了一下。 他给自己的AI棋手起名SuperGo,也提供了代码(传送门见文底)。 除此之外,还有教程 一个身子两个头 智能体分成三个部分: 一是特征提取器(FeatureExtractor),二是策略网络(PolicyNetwork),三是价值网络(ValueNetwork)。 于是,狗零也被亲切地称为双头怪。特征提取器是身子,其他两个网络是脑子。 特征提取器 特征提取模型,是个残差网络(ResNet),就是给普通CNN加上了跳层连接(SkipConnection),让梯度的传播更加通畅。 跳跃的样子,写成代码就是: 1classBasicBlock(nn。Module): 2 3Basicresidualblockwith2convolutionsandaskipconnection 4beforethelastReLUactivation。 5 6:hr7definit(self,inplanes,planes,stride1,downsampleNone): 8super(BasicBlock,self)。init() 9:hr10self。conv1nn。Conv2d(inplanes,planes,kernelsize3, 11stridestride,padding1,biasFalse) 12self。bn1nn。BatchNorm2d(planes) 13:hr14self。conv2nn。Conv2d(planes,planes,kernelsize3, 15stridestride,padding1,biasFalse) 16self。bn2nn。BatchNorm2d(planes) 17:hr18:hr19defforward(self,x): 20residualx 21:hr22outself。conv1(x) 23outF。relu(self。bn1(out)) 24:hr25outself。conv2(out) 26outself。bn2(out) 27:hr28outresidual 29outF。relu(out) 30:hr31returnout 然后,把它加到特征提取模型里面去: 1classExtractor(nn。Module): 2definit(self,inplanes,outplanes): 3super(Extractor,self)。init() 4self。conv1nn。Conv2d(inplanes,outplanes,stride1, 5kernelsize3,padding1,biasFalse) 6self。bn1nn。BatchNorm2d(outplanes) 7:hr8forblockinrange(BLOCKS): 9setattr(self,res{}。format(block), 10BasicBlock(outplanes,outplanes)) 11:hr12:hr13defforward(self,x): 14xF。relu(self。bn1(self。conv1(x))) 15forblockinrange(BLOCKS1): 16xgetattr(self,res{}。format(block))(x) 17:hr18featuremapsgetattr(self,res{}。format(BLOCKS1))(x) 19returnfeaturemaps 策略网络 策略网络就是普通的CNN了,里面有个批量标准化(BatchNormalization),还有一个全连接层,输出概率分布。 1classPolicyNet(nn。Module): 2definit(self,inplanes,outplanes): 3super(PolicyNet,self)。init() 4self。outplanesoutplanes 5self。convnn。Conv2d(inplanes,1,kernelsize1) 6self。bnnn。BatchNorm2d(1) 7self。logsoftmaxnn。LogSoftmax(dim1) 8self。fcnn。Linear(outplanes1,outplanes) 9:hr10:hr11defforward(self,x): 12xF。relu(self。bn(self。conv(x))) 13xx。view(1,self。outplanes1) 14xself。fc(x) 15probasself。logsoftmax(x)。exp() 16:hr17returnprobas 价值网络 这个网络稍微复杂一点。除了标配之外,还要再多加一个全连接层。最后,用双曲正切(HyperbolicTangent)算出(1,1)之间的数值,来表示当前状态下的赢面多大。 代码长这样 1classValueNet(nn。Module): 2definit(self,inplanes,outplanes): 3super(ValueNet,self)。init() 4self。outplanesoutplanes 5self。convnn。Conv2d(inplanes,1,kernelsize1) 6self。bnnn。BatchNorm2d(1) 7self。fc1nn。Linear(outplanes1,256) 8self。fc2nn。Linear(256,1) 9:hr10:hr11defforward(self,x): 12xF。relu(self。bn(self。conv(x))) 13xx。view(1,self。outplanes1) 14xF。relu(self。fc1(x)) 15winningF。tanh(self。fc2(x)) 16returnwinning 未雨绸缪的树 狗零,还有一个很重要的组成部分,就是蒙特卡洛树搜索(MCTS)。 它可以让AI棋手提前找出,胜率最高的落子点。 在模拟器里,模拟对方的下一手,以及再下一手,给出应对之策,所以提前的远不止是一步。 节点(Node) 树上的每一个节点,都代表一种不同的局势,有不同的统计数据: 每个节点被经过的次数n,总动作值w,经过这一点的先验概率p,平均动作值q(qwn),还有从别处来到这个节点走的那一步,以及从这个节点出发、所有可能的下一步。 1classNode: 2definit(self,parentNone,probaNone,moveNone): 3self。pproba 4self。n0 5self。w0 6self。q0 7self。children〔〕 8self。parentparent 9self。movemove 部署(Rollout) 第一步是PUCT(多项式上置信树)算法,选择能让PUCT函数(下图)的某个变体(Variant)最大化,的走法。 写成代码的话 1defselect(nodes,cpuctCPUCT): 2OptimizedversionoftheselectionbasedofthePUCTformula 3:hr4totalcount0 5foriinrange(nodes。shape〔0〕): 6totalcountnodes〔i〕〔1〕 7:hr8actionscoresnp。zeros(nodes。shape〔0〕) 9foriinrange(nodes。shape〔0〕): 10actionscores〔i〕nodes〔i〕〔0〕cpuctnodes〔i〕〔2〕 11(np。sqrt(totalcount)(1nodes〔i〕〔1〕)) 12:hr13equalsnp。where(actionscoresnp。max(actionscores))〔0〕 14ifequals。shape〔0〕amp;gt;0: 15returnnp。random。choice(equals) 16returnequals〔0〕 结束(Ending) 选择在不停地进行,直至到达一个叶节点(LeafNode),而这个节点还没有往下生枝。 1defisleaf(self): 2Checkwhetheranodeisaleafornot 3:hr4returnlen(self。children)0 到了叶节点,那里的一个随机状态就会被评估,得出所有下一步的概率。 所有被禁的落子点,概率会变成零,然后重新把总概率归为1。 然后,这个叶节点就会生出枝节(都是可以落子的位置,概率不为零的那些)。代码如下 1defexpand(self,probas): 2self。children〔Node(parentself,moveidx,probaprobas〔idx〕) 3foridxinrange(probas。shape〔0〕)ifprobas〔idx〕amp;gt;0〕 更新一下 枝节生好之后,这个叶节点和它的妈妈们,身上的统计数据都会更新,用的是下面这两串代码。 1defupdate(self,v): 2Updatethenodestatisticsafterarollout 3:hr4self。wself。wv 5self。qself。wself。nifself。namp;gt;0else0 1whilecurrentnode。parent: 2currentnode。update(v) 3currentnodecurrentnode。parent 选择落子点 模拟器搭好了,每个可能的下一步,都有了自己的统计数据。 按照这些数据,算法会选择其中一步,真要落子的地方。 选择有两种,一就是选择被模拟的次数最多的点。试用于测试和实战。 另外一种,随机(Stochastically)选择,把节点被经过的次数转换成概率分布,用的是以下代码 1totalnp。sum(actionscores) 2probasactionscorestotal 3movenp。random。choice(actionscores。shape〔0〕,pprobas) 后者适用于训练,让AlphaGo探索更多可能的选择。 三位一体的修炼 狗零的修炼分为三个过程,是异步的。 一是自对弈(SelfPlay),用来生成数据。 1defselfplay(): 2whileTrue: 3newplayer,checkpointloadplayer() 4ifnewplayer: 5playernewplayer 6:hr7Createtheselfplaymatchqueueofprocesses 8resultscreatematches(player,coresPARALLELSELFPLAY, 9matchnumberSELFPLAYMATCH) 10forinrange(SELFPLAYMATCH): 11resultresults。get() 12db。insert({ 13game:result, 14id:gameid 15}) 16gameid1 二是训练(Training),拿新鲜生成的数据,来改进当前的神经网络。 1deftrain(): 2criterionAlphaLoss() 3datasetSelfPlayDataset() 4player,checkpointloadplayer(currenttime,loadedversion) 5optimizercreateoptimizer(player,lr, 6paramcheckpoint〔optimizer〕) 7bestplayerdeepcopy(player) 8dataloaderDataLoader(dataset,collatefncollatefn, 9batchsizeBATCHSIZE,shuffleTrue) 10:hr11whileTrue: 12forbatchidx,(state,move,winner)inenumerate(dataloader): 13:hr14Evaluateacopyofthecurrentnetwork 15iftotaliteTRAINSTEPS0: 16pendingplayerdeepcopy(player) 17resultevaluate(pendingplayer,bestplayer) 18:hr19ifresult: 20bestplayerpendingplayer 21:hr22example{ 23state:state, 24winner:winner, 25move:move 26} 27optimizer。zerograd() 28winner,probaspendingplayer。predict(example〔state〕) 29:hr30losscriterion(winner,example〔winner〕, 31probas,example〔move〕) 32loss。backward() 33optimizer。step() 34:hr35Fetchnewgames 36iftotaliteREFRESHTICK0: 37lastidfetchnewgames(collection,dataset,lastid) 训练用的损失函数表示如下: 1classAlphaLoss(torch。nn。Module): 2definit(self): 3super(AlphaLoss,self)。init() 4:hr5defforward(self,predwinner,winner,predprobas,probas): 6valueerror(winnerpredwinner)2 7policyerrortorch。sum((probas 8(1e6predprobas)。log()),1) 9totalerror(valueerror。view(1)policyerror)。mean() 10returntotalerror 三是评估(Evaluation),看训练过的智能体,比起正在生成数据的智能体,是不是更优秀了(最优秀者回到第一步,继续生成数据)。 1defevaluate(player,newplayer): 2resultsplay(player,opponentnewplayer) 3blackwins0 4whitewins0 5:hr6forresultinresults: 7ifresult〔0〕1: 8whitewins1 9elifresult〔0〕0: 10blackwins1 11:hr12Checkifthetrainedplayer(black)isbetterthan 13thecurrentbestplayerdependingonthethreshold 14ifblackwinsamp;gt;EVALTHRESHlen(results): 15returnTrue 16returnFalse 第三部分很重要,要不断选出最优的网络,来不断生成高质量的数据,才能提升AI的棋艺。 三个环节周而复始,才能养成强大的棋手。 有志于AI围棋的各位,也可以试一试这个PyTorch实现。 本来摘自量子位,原作DylanDjian。 代码实现传送门: 网页链接 教程原文传送门: 网页链接 AlphaGoZero论文传送门: 网页链接