打爆李世石第一步:使用神经网络设计人工智能围棋机器人

作者: 望月从良 | 来源:发表于2019-04-06 16:01 被阅读1次

    上一节,我们使用基于蒙特卡洛树搜索的机器人来自我对弈,同时我们把机器人落子方式和落子时的棋盘编码记录下来,本节我们就使用上一节数据来训练神经网络,让网络学会如何在给定棋盘下进行精确落子。

    神经网络的运行原理如下:

    屏幕快照 2019-04-04 下午5.49.46.png

    当网络训练好后,我们把棋盘编码对应的二维矩阵转换为一维矩阵输入网络,网络给出大小与棋盘对应的一维向量,每个向量对弈一个0到1之间的值,该值表示落子在对应位置上的赢率,我们只要从输出的一维矩阵中选择值最大那个分量对应的位置落子即可。

    一开始我们会构造一个简单的双层全连接网络,第一层有1000个神经元,第二层有500个神经元,最后一层有81个神经元,它对应9*9棋盘上的每个落子位置,最后一层输出结果中,值最大的节点就对应网络预测应该落子之处,我们将要开发的第一个网络层次结构如下:

    WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
    Instructions for updating:
    Colocations handled automatically by placer.
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    dense_1 (Dense)              (None, 1000)              82000     
    _________________________________________________________________
    dense_2 (Dense)              (None, 500)               500500    
    _________________________________________________________________
    dense_3 (Dense)              (None, 81)                40581     
    =================================================================
    Total params: 623,081
    Trainable params: 623,081
    Non-trainable params: 0
    

    结构简单到只有两层的全连接网络却拥有62万多个参数需要训练。由于一个9*9棋盘总共有81个落子位置,如果我们随便选一个位置,那么选中最佳位置的概率是1/81 = 1.2%,因此网络预测的准确率必须要高于1.2%才算有效。上面全连接网络训练后得到的准确率为2.3%,虽然高于随机落子,但是准确率依然低得令人毛骨悚然。

    对于了解神经网络或者上过我的课程用神经网络构造图像和语言识别系统的同学会知道卷积网络特别适用于识别图像,图像本质就是一个二维矩阵,在识别图像时,网络会将图像转换为灰度图,并将每个像素点预处理成0到1之间的值,这不就跟我们现在对棋盘编码的二维向量没有差别了吗!因此后面我们会使用卷积网络去识别棋盘编码后的二维向量,由此能大大提高预测准确率。

    卷积神经网络在识别输入时一个特点是,它会把二维向量切割成多份,每份对应一个规模更小的二维向量,例如把n*n规格的二维向量切分成多个3*3规格的二维向量组合,然后分别识别这些小规格二维向量,最后把识别结果综合起来,因此卷积网络特别擅长于抓出图像中的某些特征部位。

    对于棋盘而言,棋子所形成的局部模式对落子判断非常重要,某些局部地区的棋子摆放模式甚至决定了整部棋局的最终走向,因此网络必须能有效抓住局部棋盘的变化,由此卷积网络非常契合这种需求。

    于是在进一步改进中,我们构造如下结构的卷积网络:

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    conv2d_8 (Conv2D)            (None, 9, 9, 48)          480       
    _________________________________________________________________
    dropout_2 (Dropout)          (None, 9, 9, 48)          0         
    _________________________________________________________________
    conv2d_9 (Conv2D)            (None, 9, 9, 48)          20784     
    _________________________________________________________________
    max_pooling2d_1 (MaxPooling2 (None, 4, 4, 48)          0         
    _________________________________________________________________
    dropout_3 (Dropout)          (None, 4, 4, 48)          0         
    _________________________________________________________________
    flatten_2 (Flatten)          (None, 768)               0         
    _________________________________________________________________
    dense_3 (Dense)              (None, 512)               393728    
    _________________________________________________________________
    dropout_4 (Dropout)          (None, 512)               0         
    _________________________________________________________________
    dense_4 (Dense)              (None, 81)                41553     
    =================================================================
    Total params: 456,545
    Trainable params: 456,545
    Non-trainable params: 0
    

    卷积网络能把预测准确率提升到8%,相对于原来是一个巨大提升,当然结果还是不尽如人意,后面我们会不断改进网络结构,不断提升判断准确率。

    接下来我们看看具体代码实现,我们将上节存储的数据加载,进行预处理,准备输入网络进行训练:

    import  numpy as np
    from keras.models import Sequential
    from keras.layers import Dense
    
    #加载棋盘编码
    np.random.seed(234)
    X = np.load('content/gdrive/My Drive/features-40k.npy')
    Y = np.load('content/gdrive/My Drive/labels-40k.npy')
    #棋盘个数
    samples = X.shape[0]
    board_size = 9*8
    
    #将棋盘编码对应的三维向量转换为二维向量
    X = X.reshape(samples, board_size)
    Y = Y.reshape(samples, board_size)
    
    #将数据分成两部分90%作为训练数据,剩下10%作为测试数据
    train_samples = int(0.9 * samples)
    X_train, X_test = X[:train_samples], X[train_samples:]
    Y_train, Y_test = Y[:train_samples], Y[train_samples:]
    
    

    对于一个9*9的棋盘,上面有81个落子位置,如果我们闭着眼睛随便选一个位置,选中最优落子点的概率是1/81,也就是1.2%,因此我们的网络预测最佳落子位置的准确概率比1.2%越大就意味着网络预测效果越好,我们构造一个含有两层的全连接层网络,看看用数据训练后效果如何:

    model = Sequential()
    model.add(Dense(1000, activation = 'sigmoid', input_shape = (board_size, )))
    model.add(Dense(500, activation = 'sigmoid'))
    #第三层输出9*9=81个结果,由此对应棋盘上每个落子位置
    model.add(Dense(board_size, activation = 'sigmoid'))
    model.summary()
    
    model.compile(loss = 'mean_squared_error', optimizer = 'sgd',
                 metrics = ['accuracy'])
    
    model.fit(X_train, Y_train, batch_size = 64, epochs = 15,
             verbose = 1, validation_data = (X_test, Y_test))
    
    score = model.evaluate(X_test, Y_test, verbose = 0)
    print('Test loss:', score[0])
    print('Test accuracy: ', score[1])
    
    

    上面代码运行后,得到网络预测的正确性是2.6%,虽然高于随机概率1.2%,但是准确性依然非常低,这意味着我们还有很大的改进空间。我们使用的全连接网络并不是很合适与分析像棋盘这样的二维数据结构,因为它要把二维向量压缩成一维后才能进行解析,但压缩成一维就失去了落子点的空间信息,该信息显然对于判断走法好坏非常重要。

    善于捕捉二维向量空间排列信息的莫过于卷积网络莫属。同时网络中的激活函数以及成本损失函数还有很大的改进空间。接下来我们要使用卷积网络替代前面过于简单的全连接网络,以便提高预测的准确性。

    首先我们将数据预处理成符合卷积网络的输入格式:

    #加载棋盘编码
    np.random.seed(234)
    X = np.load('/content/gdrive/My Drive/features-40k.npy')
    Y = np.load('/content/gdrive/My Drive/labels-40k.npy')
    #棋盘个数
    samples = X.shape[0]
    size = 9
    input_shape = (size , size, 1)
    
    #将多个棋盘编码转换为四维向量
    X = X.reshape(samples, size, size, 1)
    
    
    #将数据分成两部分90%作为训练数据,剩下10%作为测试数据
    train_samples = int(0.9 * samples)
    X_train, X_test = X[:train_samples], X[train_samples:]
    Y_train, Y_test = Y[:train_samples], Y[train_samples:]
    

    接着我们构造一个卷积网络,代码如下:

    from keras.layers import Conv2D, Flatten
    
    model = Sequential()
    '''
    我们把棋盘切分成3*3小块,用48个不同卷积向量与3*3小块做运算,
    通常情况下,卷积运算后得到的二维向量会比原向量小,但设置padding='same',
    框架会用0拓展二维向量的宽和高,使得最终做卷积后得到的二维向量与原向量大小相同
    '''
    model.add(Conv2D(filters = 48, kernel_size = (3,3), activation = 'sigmoid',
                    padding = 'same', input_shape = input_shape))
    model.add(Conv2D(48, (3,3), padding = 'same', activation = 'sigmoid'))
    
    #把输出结果压平成一维向量
    model.add(Flatten())
    
    model.add(Dense(512, activation = 'sigmoid'))
    model.add(Dense(size * size, activation = 'sigmoid'))
    
    model.summary()
    

    然后我们启动训练流程:

    model.compile(loss = 'categorical_crossentropy',
                 optimizer = 'sgd',
                 metrics = ['accuracy'])
    
    model.fit(X_train, Y_train, batch_size = 64,
             epochs = 100, verbose = 1, validation_data = (X_test, Y_test))
    
    score = model.evaluate(X_test, Y_test, verbose = 0)
    print('Test loss: ', score[0])
    print('Test accuracy: ', score[1])
    

    上面代码运行后,得到准确率为8%左右,相比于前一个网络有很大提升,但力度还是不够,下一节我们会做进一步的改进。

    更详细的讲解和代码调试演示过程,请点击链接

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:


    这里写图片描述

    相关文章

      网友评论

        本文标题:打爆李世石第一步:使用神经网络设计人工智能围棋机器人

        本文链接:https://www.haomeiwen.com/subject/bopqiqtx.html