九游官方网页版-九游(中国)





    1. 咨询(xún)热线:021-80392549

      九游官方网页版-九游(中国) QQ在线 九游官方网页版-九游(中国) 企业微信
      九游官方网页版-九游(中国)
      九游官方网页版-九游(中国) 资讯 > 人(rén)工智能 > 正文

      阿尔法(fǎ)狗的升级版zero变成通用棋类AI,再次强化(huà)学习算法解读

      2020/12/14雷锋网1334

      在本篇博文中,你将会了解并实现AlphaZero。AlphaZero是一(yī)个令人大开眼界且超乎寻常的强化学(xué)习算法(fǎ),它以绝对的(de)优势(shì)战胜了多名围棋以及(jí)国际象棋冠军(jun1)。本文将会带(dài)你使用AlphaZero来解决一个益(yì)智小游戏(Dots and Boxes)并将其(qí)部署成一个纯Javascript构建的Web应用。

      AlphaZero最(zuì)关键(jiàn)也是最令人(rén)诧(chà)异(yì)的一点,就是其能够在不依赖于外部(bù)先验知识(shí)的情况下在棋盘类游戏中获得超越(yuè)人(rén)类的表现。AlphaZero通(tōng)过自我博弈汲取(qǔ)经验(yàn)知识(shí)来不断精通游戏。


      最强通用棋类AI,AlphaZero强化(huà)学习算法解读

      我们会借助(zhù)于Github上由Surag Nair开发的一个“简化后的、高度灵活的、经过注释的且易于理解的(de)”Python版AlphaZero来进行该项目。

      你大可以(yǐ)先去这里玩(wán)一玩这(zhè)个游戏。而Web应用以及具(jù)体的Javascript实现代码(mǎ)可(kě)以在这(zhè)里获取得到。这份代码是从该Python实现(xiàn)中移(yí)植过来的。

      https://carlos-aguayo.github.io/alphazero/

      有关AlphaZero的原理(lǐ),你可以(yǐ)阅读这篇由(yóu)Silver,David等人撰写的论文:Mastering the game of Go without human knowledge” nature 550.7676 (2017): 354–359.

      Dots and Boxes小游戏

      Dots and Boxes是(shì)一个(gè)常见的儿童益智游(yóu)戏,不过其具有(yǒu)令人讶(yà)异的复杂度。

      该游戏中(zhōng),两名玩家轮流在两个相邻点之(zhī)间放置(zhì)一条水平(píng)或垂直线。如果某个 1×1 的小正方形的 4 条边(biān)都被连上了,那么补齐(qí)这个小方块的(de)一方就获得 1 分,得分(fèn)的玩家被奖励(lì)多走一步,再连一条线。当棋盘已满(mǎn)时,游戏结束,并且得分(fèn)最高的玩(wán)家获(huò)胜。

      (译(yì)者注:这个游(yóu)戏(xì)相当有意思,建议先去玩玩看,点这里。能(néng)不能战胜AlphaZero就看你了!)

      最强(qiáng)通用棋类(lèi)AI,AlphaZero强化学习算法解读

      人工智能与棋盘游戏

      机器是否(fǒu)能够产生智能,我们已经为(wéi)此思考了(le)很久很久。那么,该如(rú)何验证机器具有(yǒu)智能呢(ne)?一个常用方(fāng)法就是(shì)玩棋(qí)盘(pán)游戏,比如国际象棋,看看其是否具有超(chāo)人的能力,甚(shèn)至击败世(shì)界冠(guàn)军。

      1957年,Herbert Simon预言计算(suàn)机系统能(néng)够在十年内击败国际象棋冠(guàn)军(jun1)。虽说实际上花的时间长了(le)点,但是在1997年5月(yuè),计(jì)算机击败了当时的国(guó)际(jì)象棋冠军——Garry Kasparov。

      (译(yì)者注(zhù):战(zhàn)胜Kasparov的机器被(bèi)命名为(wéi)DeepBlue,意为(wéi)“深蓝”)

      尽(jìn)管这一(yī)里程碑事件意义(yì)非(fēi)凡,但人们仍可以(yǐ)争(zhēng)论这(zhè)一(yī)计算机系统是(shì)否“智能”。

      这一类计算机系(xì)统由以下三个组件构成:

      1. 人为定义的(de)评价函数(shù);

      2. 博(bó)弈树搜索(suǒ)算法

      3. 极(jí)为强悍的硬件设备。

      评价(jià)函数(shù)会将棋(qí)盘盘(pán)面(miàn)作为输(shū)入并输出(chū)该(gāi)盘面的“价值”。高价(jià)值表示(shì)当(dāng)前玩家处于非常有利的(de)位置。例如(rú),在国际象棋棋(qí)盘上,玩家即将进行“将死”时就会对(duì)应(yīng)一个非常高的值。

      博弈树搜索(suǒ)算法(比如 Minimax)在所(suǒ)有(yǒu)可能的走棋中进行搜索,寻找那些能够确保得(dé)到高价值(zhí)棋盘(pán)盘面的路径。对于那些已经明知不可能有效的路(lù)径可(kě)以直接(jiē)放弃搜索,从而使算法变得更(gèng)有效率。这就是 Alpha-beta剪(jiǎn)枝 的(de)作用(yòng)。

      最后,搭配上(shàng)异(yì)常强悍(hàn)的硬件,你就将拥有一台能够(gòu)打败(bài)国际象棋世界冠军的机(jī)器。

      问题在哪儿?经验丰富的棋手人为地(dì)精心调制这些评(píng)价函(hán)数。这(zhè)些计算(suàn)机系统还依赖于(yú)一本本记录着(zhe)最佳(jiā)走棋的开局棋谱(pǔ)。游戏(xì)中局,还会用到通(tōng)过研(yán)究大(dà)师们(men)的博弈而精心构造的评价函数。这些函数还会经由象棋大师们进一步的优(yōu)化调整。

      例如,我们完(wán)全就(jiù)可以为(wéi) Dots and Boxes 构造一个评价函数。一个合理而直(zhí)接的选(xuǎn)择就是做一个得分的比(bǐ)较。得分的正向(xiàng)差值越大,游戏盘(pán)面就对我们(men)越(yuè)有利。大多数(shù)情(qíng)况下,这是可行(háng)的。然而,在 Dots and Boxes 中,就像许多其他棋盘(pán)类游戏(xì)一样,最佳的走法可(kě)能需(xū)要牺牲短(duǎn)期利益来换取(qǔ)长期利益。在 Dots and Boxes 游戏中,有时最好不要急于得分(fèn)并获得额外先手,相反(fǎn),要迫使(shǐ)对手走某一(yī)步棋。因此,我们必须(xū)考虑大量复(fù)杂场景并精心(xīn)调制(zhì)评价函数(shù)!

      击败Kasparov的(de)评(píng)价函数需(xū)要识别多达8000个盘(pán)面特征!而且其中(zhōng)绝大多数都是手动描述并(bìng)调整的!

      所以(yǐ),倒(dǎo)也不(bú)是贬低这个击败国(guó)际象棋世界冠军重(chóng)要(yào)里程(chéng)碑的意思,只是,需要顶级玩家(jiā)来定义(yì)这(zhè)些(xiē)计算机的行为并手(shǒu)动调整如此(cǐ)多的变量实在是有(yǒu)够(gòu)折(shé)腾人的。

      AlphaZero是什么?为何它(tā)如此令(lìng)人心潮(cháo)澎湃(pài)?

      AlphaZero是首个(gè)能够在国(guó)际(jì)象棋、围棋等游戏中达到超越人(rén)类水平、击败世界冠军的计算机系统,且它仅依赖于(yú)游(yóu)戏规则,无(wú)需(xū)任何人类先验知识。

      仅凭(píng)给定的游戏规则,AlphaZero即可进行自我博弈(yì)。逐(zhú)步习得游(yóu)戏策略与技巧,很快即可获得超人的表现。

      像DeepBlue这样的系统(tǒng)会需要国际象棋专家(jiā)的协助(zhù),而AlphaZero却是凭借自(zì)我博(bó)弈来变强大的。不(bú)单单是在(zài)国际(jì)象棋上,哪怕是(shì)围(wéi)棋,AlphaZero同样表(biǎo)现出超越人(rén)类的强大统治力。考虑到围棋相较于其(qí)他棋盘游(yóu)戏更大的博弈空间等因素,对计算机来说(shuō),围棋是个(gè)极为复杂的游戏。

      人类从(cóng)几千(qiān)年(nián)来数百(bǎi)万次的博弈中方(fāng)才积累了诸如围棋和国际象棋等游戏的技(jì)艺,而AlphaZero,一个仅使用游(yóu)戏规(guī)则信息的算法,却能(néng)够在(zài)几天时间(jiān)内重新(xīn)寻(xún)获(huò)这些知识并发现新(xīn)的游戏策略。

      甚至还有(yǒu)一(yī)部(bù)关于它的纪录片。

      (译者注:这(zhè)部(bù)纪录片很值(zhí)得(dé)一看,无法(fǎ)访问YouTube的同学可(kě)以在B站观看,链(liàn)接在此。不过(guò)需要注明的是,本(běn)纪录(lù)片中实际(jì)上使用的是AlphaGo算(suàn)法,而非(fēi)AlphaZero,准确来说(shuō),AlphaZero是AlphaGo的(de)进阶版本,全名为AlphaGo Zero。纪(jì)录片中与李世石博弈的AlphaGo在跟AlphaGo Zero 博弈时,0-100全(quán)负,并且,AlphaGo Zero在训练中未(wèi)使用(yòng)任何手工设计的特征(zhēng)或者围棋领(lǐng)域的专业(yè)知识,仅仅以历史棋面(miàn)作(zuò)为输入,其训(xùn)练数(shù)据全部来自于自(zì)我博(bó)弈。可谓(wèi)恐怖如(rú)斯!)

      AlphaZero是怎么做到仅凭自我博弈就习得(dé)技(jì)艺的呢?

      回(huí)想一下(xià),像DeepBlue那样依(yī)赖于人为定(dìng)义的“评价函(hán)数”的系统会把棋盘的盘(pán)面状态作为输入,再(zài)输出该状(zhuàng)态的(de)“价值(zhí)”。

      如今,对于深度学习(xí)模型来说,输入一张照片然后识别出照(zhào)片里(lǐ)是猫(māo)还是狗简直简单到爆(bào)了。那么有个想法就是(shì),把棋盘盘面作为一个深度学(xué)习模型的输入并且训练它(tā),让(ràng)它预测这样(yàng)的(de)盘面(miàn)布置是(shì)会输还是会赢。

      但是,要训练(liàn)一(yī)个机器学习(xí)模型,就(jiù)需要数据(jù),海量的数据(jù)。从哪儿能得到(dào)那么多(duō)棋局博弈的数据呢?很(hěn)简单(dān),我们就让电脑自己跟(gēn)自己(jǐ)下着(zhe)玩儿,生成一(yī)堆棋局,然(rán)后再把它们做成一个数据(jù)集用(yòng)来训练。

      AlphaZero的训练算法

      这个算法简单明了(le):

      1. 让(ràng)计(jì)算机(jī)自(zì)我博弈数局,记录每一步走棋(qí)。一旦(dàn)胜(shèng)负已(yǐ)分(fèn),就给之(zhī)前的(de)每(měi)一步走棋打上标签——棋面最终是“赢”或是“输(shū)”。如此一来,我们就获得(dé)了一(yī)个可以(yǐ)用于(yú)神经网(wǎng)络(Neural Network,NN)训(xùn)练的数据集(jí),让该网络学会判断(duàn)给定棋面是(shì)“赢面(miàn)”还是“输面(miàn)”;

      2. 复(fù)制(zhì)这(zhè)个神(shén)经网络。用上一(yī)步得(dé)到的(de)数据集训(xùn)练该克隆网络;

      3. 让克隆网(wǎng)络与原始(shǐ)神(shén)经网络互相博(bó)弈;

      4. 上(shàng)一步中获胜(shèng)的(de)网络留(liú)下,败者弃之;

      5. 重复(fù)第1步。

      呼哈,就(jiù)像是(shì)魔(mó)法似的,经过多轮迭代后,你就将获得一个(gè)世界级模型。这个(gè)模(mó)型(xíng)在短(duǎn)短(duǎn)4小(xiǎo)时内(nèi)便(biàn)超(chāo)越了(le)最强(qiáng)大的计算机象棋程(chéng)序。

      AlphaZero的组件

      AlphaZero由两部分构成。我们已经提及了第一部(bù)分,就是神经网(wǎng)络。第二部分则是“蒙特卡洛树(shù)搜索(Monte Carlo Tree Search)”,或者简(jiǎn)称MCTS。

      1. 神经网络(NN)。以棋面作为输入,输出该(gāi)棋面的“价值”,外加所有(yǒu)可能走法的(de)概率(lǜ)分布。

      2. 蒙特卡洛树搜索(MCTS)。理想情况下(xià),使用神经网(wǎng)络就足以选择下(xià)一步走法了。不过,我们仍然希望(wàng)考虑尽可能多的棋面,并确保我们的的确确选择了最好的走法(fǎ)。MTCS和(hé)Minimax一样,是一种可以帮助(zhù)我(wǒ)们寻找可(kě)能棋面的算(suàn)法。与(yǔ)Minimax不同的是,MTCS能(néng)够帮助我们更加(jiā)高(gāo)效(xiào)地搜寻(xún)博弈树。

      让(ràng)我们深入细节,看(kàn)一看(kàn)下一(yī)步走棋究竟(jìng)是如何得到的

      我(wǒ)们不妨(fáng)先看看AlphaZero在(zài)决定下一(yī)步走棋(竞技(jì)模式)时具体干了什么,然后再去(qù)探究它的训练过程,这样可以帮助我们更容易地理解AlphaZero。

      神经网(wǎng)络在分类这件事儿上表(biǎo)现得异(yì)常出色,例如(rú)区分猫(māo)跟狗(gǒu)。所以这里的想(xiǎng)法很简单直接,神经网(wǎng)络能(néng)学会区分棋(qí)局输(shū)赢的类别(bié)吗?更具体地来说,就是让神(shén)经网络预(yù)测一(yī)个表示棋(qí)局输赢概率的数(shù)值(zhí)。此外,它还将输(shū)出所有(yǒu)可(kě)能走法的(de)概率分(fèn)布,来表示我们下一步(bù)应该如(rú)何决策。

      神经网络将(jiāng)博弈状态作为输入并(bìng)输出一个输赢概率数值以(yǐ)及所有可能(néng)走法的概(gài)率分布。对于Dots and boxes这个小游(yóu)戏来(lái)说,游戏状态由三个元(yuán)素表示:首先,某一条线是否已被占(zhàn)用(yòng),这可以(yǐ)用一个含有0与1的数(shù)组来表示,如果玩家已经画了某(mǒu)条线,则置(zhì)其为1,否则为0;第二(èr),当前(qián)的走法是否是空过;第三,双(shuāng)方玩家的得分。我们可以用这(zhè)三(sān)个元素来表示所有需要的信息,用(yòng)其计算当前盘(pán)面(miàn)的价值(zhí)并预测(cè)下一步(bù)的走(zǒu)法(fǎ)。

      让我们分析(xī)一下下图中的(de)博弈情形,该轮轮到(dào)蓝(lán)色(sè)玩(wán)家走。蓝色方(fāng)有两个选择,按(àn)照(zhào)图中上面的走法来(lái)画(huà)线就会(huì)输,按照下面的走法就会赢。

      最强通用棋类AI,AlphaZero强化学习算法解读 (译者(zhě)注:左下角(jiǎo)是每根(gēn)线的编号。如果你刚刚已经(jīng)在网页上跟AlphaZero玩过这个游戏了,那么相信这张图(tú)是很容易理解的。上(shàng)方第一种走法只(zhī)顾眼前短期利益,最(zuì)终葬送好局。)

      如果蓝色方走23再走21,那么红色方必赢。然而,如果蓝色方走23后再走9,那蓝色(sè)方(fāng)就赢了(le)。要是AlphaZero在蓝色方,它怎么(me)知(zhī)道哪(nǎ)一种走法能够赢(yíng)下来呢?

      你可以用这个在(zài)线notebook复现我(wǒ)们(men)即将呈现的效果。

      将棋面送入神经(jīng)网络,我们就能得到下(xià)一(yī)步走在不同位置的概率:

      move_probability[0]: 9.060527501880689e-12
      move_probability[1]: 3.9901679182996475e-10
      move_probability[2]: 3.0028431828490586e-15
      move_probability[3]: 7.959351400188552e-09
      move_probability[4]: 5.271672681717021e-11
      move_probability[5]: 4.101417122592821e-12
      move_probability[6]: 1.2123925357696643e-16
      move_probability[7]: 6.445387395019553e-23
      move_probability[8]: 2.8522254313207743e-22
      move_probability[9]: 0.0002768792328424752
      move_probability[10]: 1.179791128073232e-13
      move_probability[11]: 5.543385303737047e-13
      move_probability[12]: 3.2618200407341646e-07
      move_probability[13]: 4.302984970292259e-14
      move_probability[14]: 2.7477634988877216e-16
      move_probability[15]: 1.3767548163795204e-14
      move_probability[16]: 8.998188305575638e-11
      move_probability[17]: 7.494002147723222e-07
      move_probability[18]: 8.540691764924446e-11
      move_probability[19]: 9.55116696843561e-09
      move_probability[20]: 4.6348909953086714e-12
      move_probability[21]: 0.46076449751853943
      move_probability[22]: 2.179317506813483e-20
      move_probability[23]: 0.5389575362205505
      move_probability[24]: 5.8165523789057046e-15

      同(tóng)时,我(wǒ)们还能(néng)得到当前(qián)棋局(jú)的(de)赢面(miàn)有多大:

      -0.99761635

      你可以在这里查阅与(yǔ)这些输出(chū)相关的(de)代码(mǎ)。

      这些输出(chū)值有一(yī)些很有意思的地方,我们(men)来(lái)细品一(yī)下:

      1. 在所有可能画线(xiàn)的位置,23号(hào)、21号以及(jí)9号的概率值最大。如果神经网络选择在23号以及21号(hào)位置(zhì)处画线,那么它就能够得(dé)到1分。另外,23号才是能够赢下来的(de)走法,而相应地(dì),从网络输出(chū)的概率来看(kàn),23号位(wèi)置的概率(0.53)恰好比21号的(0.46)稍微高一(yī)点儿。

      2. 神(shén)经网络也会(huì)给不(bú)能够画线的位置输出一个概率值(zhí)。虽然如此,但是代码(mǎ)上还是要进行限制,以确保计算机不会在不合规(guī)则的位置画线。

      3. 棋面的输赢(yíng)概率为-0.99。这意味着(zhe)AlphaZero认为它已经输掉(diào)游(yóu)戏了(le)。这个(gè)概率值的范围是-1(输(shū))到(dào)1(赢)。这个(gè)值本应该很接近于1(赢)而(ér)不是-1(输)的,毕竟我(wǒ)们知道目前这个局面赢面很大。也许我(wǒ)们(men)应(yīng)该多(duō)训练几轮来让AlphaZero准确预(yù)估(gū)棋(qí)面的输赢概率(lǜ)。

      我们很容易利用神(shén)经(jīng)网络的输出来决(jué)定(dìng)下(xià)一(yī)步的走法。

      在棋盘游戏中(zhōng)(现实生活中也是(shì)),玩家在决定下(xià)一(yī)步怎么走的(de)时候(hòu)往往会“多想几步”。AlphaZero也一样。我们用神经网络来(lái)选择(zé)最(zuì)佳的下一步(bù)走法(fǎ)后(hòu),其余低概率的(de)位置就被忽略掉了(le)。像(xiàng)Minimax这一类传统的(de)AI博弈树(shù)搜索算法效(xiào)率都很(hěn)低,因为这些(xiē)算法(fǎ)在做出最终选择(zé)前需要穷尽每一种走法(fǎ)。即使是带有较(jiào)少(shǎo)分支因(yīn)子的(de)游戏也会使(shǐ)其(qí)博弈搜索空(kōng)间变得像(xiàng)是脱缰的野马(mǎ)似的难以驾驭。分支因子就是所有可能的(de)走法的数量。这个(gè)数量会随着游戏的进行不断变化。因此,你可(kě)以(yǐ)试着计算一个平均分支因子数,国(guó)际象棋(qí)的(de)平均分支(zhī)因子是(shì)35,而围棋则是250。

      这(zhè)意味着,在国际(jì)象棋中,仅走两步就有1,225(35²)种可能的棋(qí)面,而在围棋中(zhōng),这个数字会变成62,500(250²)。在Dots and Boxes游戏中,对于一个3×3大小的棋盘,初始的分支因(yīn)子数是(shì)24,随(suí)着棋盘(pán)不断被填充,这个(gè)数(shù)字会不(bú)断减少(除非空过)。所以,在行至中(zhōng)局,分支因子(zǐ)变为15的时候,仅走3步就会有多达2730(15*14*13)种可能(néng)的(de)棋面。

      现在,时代变了,神经网络将(jiāng)指(zhǐ)导并(bìng)告(gào)诉我们(men)哪(nǎ)些博弈路径值得探索,从而避免被(bèi)许多无(wú)用的搜索路径(jìng)所淹没。现在神经网络告(gào)诉(sù)我们23号和(hé)21号都(dōu)是非常值得一探究竟的走法。

      接着,蒙特(tè)卡(kǎ)洛树搜索算法就将(jiāng)登场啦!

      蒙(méng)特卡(kǎ)洛树搜索(MCTS)

      神经(jīng)网(wǎng)络为(wéi)我们(men)指示(shì)了(le)下(xià)一步可能的走法。蒙特(tè)卡洛树(shù)搜索算法将帮助我(wǒ)们遍历这些节(jiē)点来(lái)最终选择下(xià)一步(bù)的走法(fǎ)。

      去这个链接看看论文中(zhōng)有(yǒu)关蒙特卡洛树搜索的图形(xíng)化描述。

      使用MCTS的具体做法是(shì)这样的,给定一个棋面(miàn),MCTS共进行(háng)N次(cì)模(mó)拟(nǐ)。N是模型的超参(cān)数。N次模(mó)拟结束后,下(xià)一步的走法将是这(zhè)N次(cì)模拟中所经次数最多的一(yī)步。你可以由这里(lǐ)的代码一窥究(jiū)竟:

      # https://github.com/suragnair/alpha-zero-general/blob/5156c7fd1d2f3e5fefe732a4b2e0ffc5b272f819/MCTS.py#L37-L48
      for i in range(self.args.numMCTSSims):  # self.args.numMCTSSims, the number of MCTS simulations to compute
      self.search(canonicalBoard)  # "search" is a MCTS simulations
      s = self.game.stringRepresentation(canonicalBoard)
      # Count how many times we have visited each node
      counts = [self.Nsa[(s, a)] if (s, a) in self.Nsa else 0 for a in range(self.game.getActionSize())]
      if temp == 0:
      # Pick the node that was visited the most
      bestAs = np.array(np.argwhere(counts == np.max(counts))).flatten()
      bestA = np.random.choice(bestAs)
      probs = [0] * len(counts)
      probs[bestA] = 1
      return probs

      进行N次MCTS模拟

      一(yī)次MCTS模拟从当前的棋(qí)盘状态(tài)出发,沿着博弈树中(zhōng)具有(yǒu)最大(dà)“置(zhì)信区间上界(UCB)”值(后文会给出定义)的节(jiē)点不断向下追溯,直到遇到之前从未(wèi)见过(guò)的棋盘(pán)状态,也叫(jiào)做“叶子”状态。这就是(shì)原论(lùn)文中Part A所谓的“选择(Select)”。

      置信区间上界是什么呢?用数学形式来说就是 Q(s, a) + U(s, a)。其中 s 是状(zhuàng)态(tài),a 是走法。Q(s, a) 是(shì)我(wǒ)们希(xī)望(wàng)由走法“a”构成状态“s”能够获得的期望值,与Q-Learning中的期望值(zhí)一致。记住了,在这(zhè)种情(qíng)况(kuàng)下,该值(zhí)的范围是-1(输)到1(赢)。U(s, a) ∝ P(s, a) / (1 + N(s, a))。这意味着U正比于P和N。其中(zhōng),P(s, a) 是元组 (s, a) 的先验概率值,这个值是从神经网络那里得到的(de),而(ér) N(s, a) 是已经访问过状态 s 与对应的(de)走法 a 的(de)次数(shù)。

      # Upper Confidence Bound
      ucb = Qsa[(s,a)] + Ps[s,a] * sqrt(Ns[s]) / (1 + Nsa[(s,a)]

      UCB的要点在于,其起初更倾向于具有(yǒu)较(jiào)高先验概率(P)和较低访问次数(N)的走法,但渐渐地会倾向于具有较高动(dòng)作价值(Q)的走(zǒu)法。

      你不妨看看这(zhè)里的(de)代码好好理解一下。

      # https://github.com/suragnair/alpha-zero-general/blob/5156c7fd1d2f3e5fefe732a4b2e0ffc5b272f819/MCTS.py#L105-L121
      cur_best = -float('inf')
      best_act = -1
      # pick the action with the highest upper confidence bound
      for a in range(self.game.getActionSize()):
      if valids[a]:
      if (s, a) in self.Qsa:
      u = self.Qsa[(s, a)] + self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s]) / (
      1 + self.Nsa[(s, a)])
      else:
      u = self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s] + EPS)  # Q = 0 ?
      if u > cur_best:
      cur_best = u
      best_act = a
      a = best_act
      next_s, next_player = self.game.getNextState(canonicalBoard, 1, a)
      next_s = self.game.getCanonicalForm(next_s, next_player)
      # Recursively visit the node
      v = self.search(next_s)

      Part A——选择具有最高(gāo)置信区间上(shàng)界(jiè)值的走法

      一旦(dàn)找到一(yī)个叶子状态,就把这个棋(qí)面状态送入神经网络。这是论文中称作的Part B,“扩展与(yǔ)评估(gū)”。且看代码(mǎ)。

      # leaf node
      self.Ps[s], v = self.nnet.predict(canonicalBoard)
      valids = self.game.getValidMoves(canonicalBoard, 1)
      self.Ps[s] = self.Ps[s] * valids  # masking invalid moves
      sum_Ps_s = np.sum(self.Ps[s])
      self.Ps[s] /= sum_Ps_s  # renormalize
      self.Vs[s] = valids
      self.Ns[s] = 0

      Part B——扩(kuò)展与评估

      最后,我们将(jiāng)传回神经网络返回的值。这就(jiù)是论文所说的Part C——“备(bèi)份(fèn)”。您可以在此处看到相关代码。

      v = self.search(next_s)
      if (s, a) in self.Qsa:
      self.Qsa[(s, a)] = (self.Nsa[(s, a)] * self.Qsa[(s, a)] + v) / (self.Nsa[(s, a)] + 1)
      self.Nsa[(s, a)] += 1
      else:
      self.Qsa[(s, a)] = v
      self.Nsa[(s, a)] = 1
      self.Ns[s] += 1
      return -v

      Part C——备份

      决定下一步如何走

      让我们来看看AlphaZero面(miàn)对上文提及的棋面时会(huì)决定如何走。

      最(zuì)强通用棋类AI,AlphaZero强化学(xué)习(xí)算(suàn)法解读

      AlphaZero会进行50次蒙(méng)特卡(kǎ)洛(luò)树搜索模(mó)拟。

      你可以用这个在线notebook复现下面展示的(de)结果。

      下面展示(shì)的就是(shì)每次迭代的路径:

      Simulation #1 -> Expand root node
      Simulation #2 -> 23
      Simulation #3 -> 21
      Simulation #4 -> 9
      Simulation #5 -> 17
      Simulation #6 -> 12
      Simulation #7 -> 19
      Simulation #8 -> 3
      Simulation #9 -> 18
      Simulation #10 -> 23,24
      Simulation #11 -> 21,24
      Simulation #12 -> 23,24,21
      Simulation #13 -> 21,24,23,24
      Simulation #14 -> 23,24,9
      Simulation #15 -> 23,24,17
      Simulation #16 -> 21,24,9
      Simulation #17 -> 23,24,12
      Simulation #18 -> 23,24,18
      Simulation #19 -> 21,24,17
      Simulation #20 -> 23,24,21,24,9
      Simulation #21 -> 21,24,19
      Simulation #22 -> 23,24,3
      Simulation #23 -> 21,24,18
      Simulation #24 -> 23,24,19
      Simulation #25 -> 21,24,23,24,17
      Simulation #26 -> 23,24,21,24,18
      Simulation #27 -> 23,24,21,24,3
      Simulation #28 -> 21,24,3
      Simulation #29 -> 23,24,21,24,19
      Simulation #30 -> 21,24,12
      Simulation #31 -> 23,24,21,24,9,24
      Simulation #32 -> 21,24,23,24,12
      Simulation #33 -> 23,24,21,24,9,24,18
      Simulation #34 -> 21,24,23,24,9,24,17
      Simulation #35 -> 23,24,21,24,9,24,12
      Simulation #36 -> 23,24,21,24,9,24,3
      Simulation #37 -> 21,24,23,24,9,24,19
      Simulation #38 -> 23,24,21,24,9,24,18,17
      Simulation #39 -> 21,24,23,24,9,24,18,17,24
      Simulation #40 -> 23,24,21,24,9,24,18,17,24,19
      Simulation #41 -> 21,24,23,24,9,24,18,17,24,19,24
      Simulation #42 -> 23,24,9,21
      Simulation #43 -> 23,24,9,18
      Simulation #44 -> 23,24,9,17
      Simulation #45 -> 23,24,9,19
      Simulation #46 -> 23,24,9,12
      Simulation #47 -> 23,24,9,21,24
      Simulation #48 -> 23,24,9,3
      Simulation #49 -> 23,24,9,21,24,18
      Simulation #50 -> 23,24,9,21,24,17

      上(shàng)面显示的结果的(de)意思是:在第一次模拟中,由(yóu)于算法之前并未见过这个棋面,因此输入的棋面实际上是一(yī)个“叶子”状(zhuàng)态节点,需要先“扩展(zhǎn)”这个(gè)节点。所谓扩展就是(shì)把棋面送到(dào)神经网络(luò)里对每个位置进行概率评估。

      Simulation #1 -> Expand root node

      在(zài)第二(èr)次模拟(nǐ)中,因(yīn)为(wéi)上步我们已经扩展了根(gēn)节点,因此它不再是一个(gè)“叶子”节点(diǎn)了,就此,我们可以对具有(yǒu)最高置信区间上界值的节点进(jìn)行(háng)搜索:

      # https://github.com/suragnair/alpha-zero-general/blob/5156c7fd1d2f3e5fefe732a4b2e0ffc5b272f819/MCTS.py#L105-L121
      cur_best = -float('inf')
      best_act = -1
      # pick the action with the highest upper confidence bound
      for a in range(self.game.getActionSize()):
      if valids[a]:
      if (s, a) in self.Qsa:
      u = self.Qsa[(s, a)] + self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s]) / (
      1 + self.Nsa[(s, a)])
      else:
      u = self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s] + EPS)  # Q = 0 ?
      if u > cur_best:
      cur_best = u
      best_act = a
      a = best_act
      next_s, next_player = self.game.getNextState(canonicalBoard, 1, a)
      next_s = self.game.getCanonicalForm(next_s, next_player)
      # Recursively visit the node
      v = self.search(next_s)

      具有最(zuì)大置信区(qū)间上界值的是23号(hào)位置。搜索算法深入(rù)在(zài)23号位置画线的(de)状态,由于这个(gè)状态在之前搜索算法也没见过,因此这也是个“叶子”节点状态,搜索(suǒ)算法(fǎ)会(huì)“扩展”这个状态。就(jiù)这样(yàng),第二次模拟也(yě)完成啦。

      Simulation #2 -> 23

      这个(gè)时候,还(hái)记得上面神经网络(luò)输(shū)出(chū)的(de)输(shū)赢(yíng)概率吗,神(shén)经网络认为在23号位(wèi)置画线必输无疑(yí)。神经网(wǎng)络可(kě)以进行更多(duō)轮的训练来确保这的确是(shì)个很差(chà)的走法。不过目前来说,这就足够(gòu)了,我们后面会认识到这一点的。

      接下来(lái)的模拟中,会依次(cì)搜索(suǒ)剩(shèng)下的走法中(zhōng)具(jù)有最大(dà)置信区间上界的状态,不过只有下面给出的这些。因为在访(fǎng)问完以(yǐ)下(xià)这些走法之后,搜(sōu)索算法会发现剩下的(de)状态都具有很低(dī)的概率,也就是说(shuō)其置信区间上界都很(hěn)低,也就不(bú)必搜索了。

      Simulation #3 -> 21
      Simulation #4 -> 9
      Simulation #5 -> 17
      Simulation #6 -> 12
      Simulation #7 -> 19
      Simulation #8 -> 3
      Simulation #9 -> 18

      在之后的模拟中(zhōng),一个令人兴奋的模式逐渐揭(jiē)开面纱。记住,能够赢(yíng)下来的走法序列是23,24(对(duì)应空过(guò)),9。

      (译者注:填(tián)上23号之(zhī)后,由于补全了一(yī)个正方形,因(yīn)此对(duì)方空过。这里给(gěi)出的序列(liè)是两(liǎng)方的走法序(xù)列。)

      Simulation #10 -> 23,24
      Simulation #11 -> 21,24
      Simulation #12 -> 23,24,21
      Simulation #13 -> 21,24,23,24
      Simulation #14 -> 23,24,9
      Simulation #15 -> 23,24,17
      Simulation #16 -> 21,24,9
      Simulation #17 -> 23,24,12
      Simulation #18 -> 23,24,18
      Simulation #19 -> 21,24,17
      Simulation #20 -> 23,24,21,24,9
      Simulation #21 -> 21,24,19
      Simulation #22 -> 23,24,3
      Simulation #23 -> 21,24,18
      Simulation #24 -> 23,24,19

      在第10至第24次模拟中,很明显,MCTS已经(jīng)把注意力(lì)放在了21号(hào)节点(diǎn)与23号节(jiē)点上。这说得通,因为这两(liǎng)种走(zǒu)法都能让(ràng)我(wǒ)方得1分。

      Simulation #33 -> 23,24,21,24,9,24,18
      Simulation #34 -> 21,24,23,24,9,24,17
      Simulation #35 -> 23,24,21,24,9,24,12
      Simulation #36 -> 23,24,21,24,9,24,3
      Simulation #37 -> 21,24,23,24,9,24,19
      Simulation #38 -> 23,24,21,24,9,24,18,17
      Simulation #39 -> 21,24,23,24,9,24,18,17,24
      Simulation #40 -> 23,24,21,24,9,24,18,17,24,19
      Simulation #41 -> 21,24,23,24,9,24,18,17,24,19,24

      在第33至第41次模拟中,搜索算法深入探究了那(nà)些(xiē)导致败局的走法(fǎ)。这里要注(zhù)意到一件有意(yì)思的事情。尽(jìn)管追究得很深,但是搜索算法(fǎ)并没有抵达游戏终局(jú),后面还(hái)有可以走的步骤。

      Simulation #42 -> 23,24,9,21
      Simulation #43 -> 23,24,9,18
      Simulation #44 -> 23,24,9,17
      Simulation #45 -> 23,24,9,19
      Simulation #46 -> 23,24,9,12
      Simulation #47 -> 23,24,9,21,24
      Simulation #48 -> 23,24,9,3
      Simulation #49 -> 23,24,9,21,24,18
      Simulation #50 -> 23,24,9,21,24,17

      接着,在第(dì)42次至第50次模拟中,通(tōng)过神经网络的场外援助,搜索算法意识到(dào)了23,24,21或者21,24,23都(dōu)不是好的走法,这下子,它全然投入到(dào)能够获胜的走法(fǎ)序(xù)列:23,24,9。

      在50次模拟后(hòu),是时候做出(chū)决定了。MCTS选择了其访问(wèn)次数最(zuì)多的(de)位置。下面列出了每个走(zǒu)法的访问次数(只统计路径中的(de)第一个位置):

      counts[3] = 1
      counts[9] = 1
      counts[12] = 1
      counts[17] = 1
      counts[18] = 1
      counts[19] = 1
      counts[21] = 15
      counts[23] = 28

      3,9,12,17,18以及19号位置(zhì)只在最初10次(cì)模拟(nǐ)中访问了1次。接着MCTS专注于21和23号(hào)位置,且(qiě)在(zài)最后(hòu)9次模拟中都先(xiān)走23号。因为23号位置被访问次数最多(duō),达(dá)到了28次之多,因此MCTS最(zuì)终返回23作为下一步的走法。

      关(guān)键点是什(shí)么?

      1. 通过每一次模拟(nǐ),MCTS依靠神经网络, 使用累计价(jià)值(Q)、神(shén)经网络(luò)给出的走法先(xiān)验概率(P)以及(jí)访问对(duì)应节点(diǎn)的频率这些数字的组合,沿(yán)着最(zuì)有希望获胜的(de)路(lù)径(换句话说,也就是具有最高置信区间上界的路(lù)径)进行探索。

      2. 在每一(yī)次模拟(nǐ)中,MCTS会尽可能向(xiàng)纵(zòng)深进(jìn)行探索直至遇(yù)到它从未见过(guò)的盘面状态(tài),在这种情(qíng)况下,它会通(tōng)过神经(jīng)网络来(lái)评估该盘面状态的优劣(liè)。

      如果我们将上述方(fāng)法(fǎ)与使用带有Alpha-Beta剪枝以(yǐ)及(jí)一(yī)个评价(jià)函数的Minimax之类的传统方法进(jìn)行比较,我们可以发现以下几点:

      1. 在Minimax中,博弈树的搜索深度(dù)是(shì)由算法(fǎ)设计者自(zì)行设定的。它无论(lùn)如何都会搜索到(dào)那个深度,然(rán)后用那个可爱的评(píng)价函数进行盘面评估。要(yào)是没有(yǒu)Alpha-Beta剪枝的(de)话,它(tā)就得访问给定深(shēn)度(dù)下所有可(kě)能的盘面(miàn)节点,极为低效。在上面的情形中,如果还可以走(zǒu)8步,要搜索的深度为(wéi)3,就意味着总共需要(yào)评估336个盘面状态。使用MCTS,仅仅用了50次模拟,也就是50次评估,而且(qiě)在搜索中还尽(jìn)可能搜索(suǒ)得足(zú)够深(shēn)。

      2. Alpha-Beta剪枝能够帮(bāng)助(zhù)我们将336这个数字减少(shǎo)。然(rán)而,却并不能帮我(wǒ)们(men)找到一条优良的博弈路(lù)径。

      3. 我们是一直在用神经(jīng)网络来对盘面进(jìn)行评(píng)估的,而不是某个认为定义的评价函数。

      4. 很有意思的是,在起初几步中,神经网络并没(méi)有做出正确的盘(pán)面(miàn)评估。然而,随着在博弈(yì)树中(zhōng)搜(sōu)索(suǒ)的深度提(tí)升,它自动修正了它的输出(chū),而且搜索并未抵达游戏(xì)终局。

      5. 最后,要注意到AlphaZero的(de)优雅与简洁。而(ér)在Alpha-Beta剪枝中(zhōng),你的(de)不(bú)断跟踪alpha和beta参数来知悉哪些路径被砍掉了,你还得用(yòng)一个人为定(dìng)义(yì)的评价函数,更(gèng)不要说这(zhè)个函(hán)数又笨重又丑陋(lòu)。MCTS与NN让所有这一切都变得异常优雅与简洁。你甚(shèn)至可以在Javascript中把这一切都搞(gǎo)定(dìng)!

      训练神(shén)经(jīng)网络(luò)

      至此,我们还缺最后(hòu)一个关(guān)键部分。究竟该(gāi)如何训练这个神经网络呢?

      不要害怕,嘻嘻(xī),贼(zéi)简单。我们之前(qián)提到的步骤是:

      1. 让计(jì)算机在“训练模(mó)式”下自我(wǒ)博弈数局,记录每一步走棋。一旦胜负已分(fèn),就给(gěi)之前的每(měi)一(yī)步走棋(qí)打(dǎ)上标签——棋面最终是“赢”或(huò)是“输”。如此一来,我们(men)就获得(dé)了(le)一个可(kě)以用于神经网络(Neural Network,NN)训(xùn)练的数据集(jí),让该网络学会判断给定棋面是“赢面”还是“输面”;

      2. 复制神经网络(luò)。用上(shàng)一步得到的数(shù)据集训练该克隆网络;

      3. 让克隆网络与原始神经网络(luò)互相(xiàng)博(bó)弈;

      4. 上(shàng)一步中(zhōng)获胜的网络留下,败(bài)者弃(qì)之;

      5. 重复第1步。

      什(shí)么叫在(zài)“训(xùn)练模式”下进行(háng)博弈呢?这个区别非(fēi)常(cháng)细微。当(dāng)在“竞技模式”下(xià)博弈时,我们(men)会选择(zé)访问次(cì)数最多的(de)走法。而在“训练模(mó)式”下,在(zài)游戏(xì)刚开始的一定步数内,我们(men)会将不(bú)同走法的访问次(cì)数变成(chéng)概率分布,以此鼓励(lì)网络对不同的(de)走法进行探索(suǒ)。举个例子(zǐ),假设有3中可能的走法,对应的访问(wèn)次数分别是[2, 2, 4]。那么在(zài)竞技(jì)模式中(zhōng),由于(yú)第三(sān)种走法的访问次数(shù)最(zuì)多,所(suǒ)以我们就(jiù)选择(zé)第三种走法。但是在训(xùn)练模式中,我(wǒ)们会将[2, 2, 4]变成一个概率(lǜ)分布,因为2+2+4=8,因此概率分布就是[2/8, 2/8, 4/8] 或(huò)者说是 [0.25, 0.25, 0.5]。换句话说,我们在50%的情况下会选择第三种走(zǒu)法(fǎ),而第(dì)一以及第二种(zhǒng)走法有25%的概率被(bèi)选中。

      接着,我(wǒ)们(men)用一个简单的(de)井字(zì)棋(qí)来描述(shù)一下数据集的构(gòu)建。

      最强通用棋类AI,AlphaZero强化学习算(suàn)法解读

      在(zài)上面这副图(tú)片中,1号玩家执X获胜。

      我们可以将盘(pán)面中(zhōng)未落(luò)子(zǐ)的地方记作0,1号玩(wán)家打(dǎ)X的位置记(jì)作1,2号玩家打圈(quān)的地方记作-1。

      那么,上图中的棋盘各个盘面就可(kě)以变(biàn)成下边这样:

      0 0 0    1 0 0     1 0 0     1 0 0     1 0 0     1 0 0
      0 0 0 -> 0 0 0 -> -1 0 0 -> -1 1 0 -> -1 1 0 -> -1 1 0
      0 0 0    0 0 0     0 0 0     0 0 0    -1 0 0    -1 0 1

      或(huò)者,我们将盘(pán)面(miàn)降为一维表示,就是(shì)这(zhè)样:

      [0, 0, 0, 0, 0, 0, 0, 0, 0]
      [1, 0, 0, 0, 0, 0, 0, 0, 0]
      [1, 0, 0,-1, 0, 0, 0, 0, 0]
      [1, 0, 0,-1, 1, 0, 0, 0, 0]
      [1, 0, 0,-1, 1, 0,-1, 0, 0]
      [1, 0, 0,-1, 1, 0,-1, 0, 1]

      然后我们(men)要做两件事(shì)情。第一件事,找到(dào)所(suǒ)有属于1号玩家(jiā)轮次的棋盘盘(pán)面。我们只会给神经网(wǎng)络喂入1号玩家(jiā)的相关盘面(miàn)数据。在井字棋中,很容易就能挑选出来(lái)。而2号玩家轮(lún)次的那些盘面,直接将其数据取相(xiàng)反(fǎn)数,使其变为1号玩(wán)家视角下(xià)的盘面(miàn)状态。

      也就(jiù)是(shì),将:

      [0, 0, 0, 0, 0, 0, 0, 0, 0]  # Player 1 turn
      [1, 0, 0, 0, 0, 0, 0, 0, 0]  # Player 2 turn
      [1, 0, 0,-1, 0, 0, 0, 0, 0]  # Player 1 turn
      [1, 0, 0,-1, 1, 0, 0, 0, 0]  # Player 2 turn
      [1, 0, 0,-1, 1, 0,-1, 0, 0]  # Player 1 turn
      [1, 0, 0,-1, 1, 0,-1, 0, 1]  # Player 2 turn

      变为(wéi):

      [ 0, 0, 0, 0, 0, 0, 0, 0, 0]  # Player 1 turn
      [-1, 0, 0, 0, 0, 0, 0, 0, 0]  # Player 1 turn
      [ 1, 0, 0,-1, 0, 0, 0, 0, 0]  # Player 1 turn
      [-1, 0, 0, 1,-1, 0, 0, 0, 0]  # Player 1 turn
      [ 1, 0, 0,-1, 1, 0,-1, 0, 0]  # Player 1 turn
      [-1, 0, 0, 1,-1, 0, 1, 0,-1]  # Player 1 turn

      第二件(jiàn)事,我(wǒ)们(men)对获得的(de)每一条数据向后增加1位(wèi)数据,“1”表示(shì)1号玩家赢,“-1”表(biǎo)示1号玩家输。如此(cǐ)一来,数据就变成了:

      [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]  # Winning board
      [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0]  # Losing board
      [ 1, 0, 0,-1, 0, 0, 0, 0, 0, 1]  # Winning board
      [-1, 0, 0, 1,-1, 0, 0, 0, 0, 0]  # Losing board
      [ 1, 0, 0,-1, 1, 0,-1, 0, 0, 1]  # Winning board
      [-1, 0, 0, 1,-1, 0, 1, 0,-1, 0]  # Losing board

      嗯,这个数(shù)据集现在有(yǒu)模有样了呢!你看,这样一来,我们就获得了一批用于训(xùn)练神经网(wǎng)络的(de)数据,并(bìng)让神经网络学习判断盘面的(de)输(shū)赢。

      哦,我们(men)好像漏掉了概率。那些(xiē)不同走法的概率分布怎么(me)得到呢?记住了,在训练模式下(xià),我们每一(yī)步(bù)也还是会进行MCTS模拟的。正如我们会记(jì)录下不同的(de)盘面,我(wǒ)们也(yě)会将对(duì)应的(de)概率进行记录。

      然后我们就会复制(zhì)(或者说是克隆)神(shén)经网络(luò),并用新(xīn)获得的数(shù)据训练(liàn)这个克隆网络,我们(men)所期望的,是(shì)用上新获得的(de)数据后,这个克(kè)隆(lóng)网络可以变(biàn)得更强一点儿。通过与原始网络进(jìn)行对抗,我(wǒ)们可以(yǐ)验证其(qí)是否真的变强了。如(rú)果克隆(lóng)网络的胜率超过55%,我们就把原始网络丢掉,这个克隆网(wǎng)络便可取而(ér)代之。

      这个过程(chéng)会(huì)一直重复下去,神经网(wǎng)络也在这个(gè)过(guò)程中不断变强。

      你可以(yǐ)在这儿看(kàn)到论文中的相关图表。

      数据(jù)集(jí)中如何包(bāo)含(hán)更多更具代(dài)表性的数据呢?相较(jiào)于神经网络输出的原始走(zǒu)法概率分(fèn)布,数据集会(huì)倾向于根据MCTS生成的概率来选择更具借(jiè)鉴性(xìng)的走法。通(tōng)过让(ràng)MCTS搜索足(zú)够(gòu)多较深的博弈路径,神经网络可以获(huò)取更优质的数据(jù)并更加高效地学(xué)习。

      试(shì)试看用这个Colab Notebook训练一个Dots and Boxes模型(xíng)吧。

      将其部署至一个Web应用

      几个(gè)月前,我发了一篇博文,带你大致过了一遍使用TensorFlow.js将Keras或者(zhě)TensorFlow模型部署至Javascript的过(guò)程。这里我们(men)要做的(de)事(shì)情(qíng)大差不差。我们会把用Keras训练得到的模型转(zhuǎn)换成能够被TensorFlow.js调用的模(mó)型。

      这个Notebook展(zhǎn)示了如何(hé)将一个预训练的Dots and Boxes博弈模(mó)型转换为(wéi)一个TensorFlow.js模(mó)型。

      一旦转换完成,这(zhè)个模型就能够很轻松地(dì)使用Javascript进行调用。不妨看(kàn)看这里。

      结论

      在本篇博文中,你认识了AlphaZero,一个(gè)能(néng)够在双方零和博弈(yì)的棋盘游戏(xì)中战胜世界冠军的强(qiáng)化学习算法

      你(nǐ)也了解了它是如何使用蒙特卡洛树搜索算法以及神经网络来找(zhǎo)到最(zuì)佳的下一步走法(fǎ),以及如(rú)何训练这样(yàng)一个神经网络。

      最强通用棋类AI,AlphaZero强化学习算法(fǎ)解读



      关键词:




      AI人工智能网声明:

      凡资讯来源注明为其他(tā)媒体(tǐ)来源的信息,均为转载(zǎi)自其他媒体,并不代(dài)表(biǎo)本网站赞同其(qí)观点,也不代表(biǎo)本网站对其(qí)真实性负责。您若对该文(wén)章内容(róng)有任何疑问(wèn)或质疑,请立即(jí)与网站(www.baise.yingtan.bynr.xinxiang.zz.pingliang.ww38.viennacitytours.com)联系,本(běn)网站将迅速给您回应并做处理(lǐ)。


      联系电话:021-31666777   新(xīn)闻、技术(shù)文章投稿QQ:3267146135   投稿邮箱:syy@gongboshi.com

      精(jīng)选资讯更多

      相(xiàng)关资讯更多(duō)

      热门搜索(suǒ)

      工(gōng)博士人工智能网
      九游官方网页版-九游(中国)
      扫描二维(wéi)码关注(zhù)微(wēi)信
      扫码(mǎ)反馈

      扫(sǎo)一扫,反馈当(dāng)前页面(miàn)

      咨询反馈
      扫(sǎo)码(mǎ)关注

      微信公众号

      返回顶部

      九游官方网页版-九游(中国)

      九游官方网页版-九游(中国)