构建一个能够打败人类的围棋神经网络

上一节,我们从围棋服务器中下载大量棋谱,并将其转换成网络可以解析的数据格式,在神经网络的开发中完成了最繁琐的一步,也就是数据准备。接下来我们将创建一个神经网络,对数据进行解读,使得网络具备6到7段的围棋专业水平,它尚未具备打败柯洁或李世石这些顶级高手的能力,但打败业余级高手则绰绰有余。

我们要完成的网络有三种形态,一种是小型网络,一种是中型网络,一种是大型网络,其能力由弱到强,当然训练的耗时也由少到多。网络的基本结构是,前4层我们都使用卷积层,最后一层是一个含有19*19个神经元的全连接层。这里我们将引入一种新的网络层叫ZeroPadding2D层。卷积网络在处理图像时,它会把图像分割成多个小块后分别识别,识别所得的结果在规格上,也就是宽和高上相对于输入时的数据而言会有所缩小,如果卷积层较多,那么图片在层层处理后,规格会严重减小。

为了防止规格缩小得太严重,我们在将图片输入卷积网络前,会给它填充若干行和列,假设我们要处理的图片规格为19*19,如果我们执行下面语句:

ZeroPadding2D(padding = 2, input_shape = input_shape, data_format='channel_first')

那意味着我们在1919的二维数组左边和右边分别添加2列,在上面和底部分别添加2行,这些列和行全部使用0来填充,于是就能得到一个2323的二维数组。当我们把输入数据先扩大,经过卷积网络处理后就不会严重缩水。

同时我们还得注意,在卷积层,我们会用多个过滤器对图片进行扫描,图片被一个过滤器扫描后就得到一个二维数组作为扫描结果,如果多个过滤器就会得到多个二维数组,如下图:

filters.png

过滤器的个数,我们也叫做channel,上面代码中channel_first,表示卷积网络输出结果中,把过滤器的数值放在扫描结果对应的二维数组前面,假设卷积层网络有N个过滤器,每个过滤器扫描图片后得到的结果二维数组为W和H,于是上面‘channel_first'指定卷积层输出的结果为:[N, W, H],也就是把过滤器的个数放在结果向量的前面。

有了上面的基本解释后,我将用代码构造如下结构的神经网络:

Layer (type)                 Output Shape              Param #   
=================================================================
zero_padding2d_5 (ZeroPaddin (None, 1, 25, 25)         0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 48, 19, 19)        2400      
_________________________________________________________________
activation_6 (Activation)    (None, 48, 19, 19)        0         
_________________________________________________________________
zero_padding2d_6 (ZeroPaddin (None, 48, 23, 23)        0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 32, 19, 19)        38432     
_________________________________________________________________
activation_7 (Activation)    (None, 32, 19, 19)        0         
_________________________________________________________________
zero_padding2d_7 (ZeroPaddin (None, 32, 23, 23)        0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 32, 19, 19)        25632     
_________________________________________________________________
activation_8 (Activation)    (None, 32, 19, 19)        0         
_________________________________________________________________
zero_padding2d_8 (ZeroPaddin (None, 32, 23, 23)        0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 32, 19, 19)        25632     
_________________________________________________________________
activation_9 (Activation)    (None, 32, 19, 19)        0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 11552)             0         
_________________________________________________________________
dense_3 (Dense)              (None, 512)               5915136   
_________________________________________________________________
activation_10 (Activation)   (None, 512)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 361)               185193    
=================================================================
Total params: 6,192,425
Trainable params: 6,192,425
Non-trainable params: 0

有了上面网络后,我们再通过上节实现的数据加载方法,将棋盘数据输入网络进行训练,完成后网络就具备了能打败业余高手的能力。通过上面网络加上人类棋谱的训练,网络对棋手下一步走法的预测率能高达85%作用,注意到网络预测的是专业六段和七段棋手的走法,因此它能轻易打败业余级的高手。

接下来我们看看具体代码的实现:

from keras.layers.core import Dense, Activation, Flatten
from keras.layers.convolutional import Conv2D, ZeroPadding2D

def  layers(input_shape):
  return  [
      ZeroPadding2D(padding  =3, input_shape = input_shape,
                    data_format = 'channels_first'),
      Conv2D(48, (7,7), data_format = 'channels_first'),
      Activation('relu'),
      
      ZeroPadding2D(padding = 2, data_format = 'channels_first'),
      Conv2D(32, (5,5), data_format = 'channels_first'),
      Activation('relu'),
      
      ZeroPadding2D(padding = 2, data_format = 'channels_first'),
      Conv2D(32, (5,5), data_format = 'channels_first'),
      Activation('relu'),
      
      ZeroPadding2D(padding = 2, data_format = 'channels_first'),
      Conv2D(32, (5,5), data_format = 'channels_first'),
      Activation('relu'),
      
      Flatten(),
      Dense(512),
      Activation('relu'),
      
      
  ]

上面代码构造了网络各个处理层,我们把网络层作为单独对象放在一个队列中的好处是,后面我们可以根据需要进行加载,如果我们想让网络处理能力更强,那么就多加载几个网络层,如果希望处理能力小一些,就可以加载少一些网络层,如此能让网络的构建更加灵活。

接下来我们通过调用上面函数构建整个网络:

#encoder.num_planes对应channel
input_shape = (encoder.num_planes, go_board_rows, go_board_cols) 
#将网络层一层层加进来
network_layers = layers(input_shape)
model = Sequential()
for layer in network_layers:
  model.add(layer)

model.add(Dense(num_classes, activation = 'softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer = 'sgd',
             metrics = ['accuracy'])
model.summary()

上面代码生成的就是前面我们展示的网络结构,有了网络后,我们加载数据为网络的训练和验证做准备:

#加载训练数据和测试数据,这个过程相当耗时
processor = GoDataProcessor(encoder = encoder.name())
generator = processor.load_go_data('train', num_games, use_generator = True)
test_generator = processor.load_go_data('test', num_games, use_generator = True)

然后就可以启动网络的训练流程了:


#由于数据加载过程耗时,这段训练过程在没有足够算力情况下很难执行
model.fi_generator(generator = generator.generate(batch_size, num_classes),
                    epochs = epochs, 
                    steps_per_epoch = generator.get_num_samples() / batch_size,
                    validation_data = test_generator.generate(batch_size, num_classes),
                    validation_step = test_generator.get_num_samples() / batch_size,
                    callbacks = [
                        ModelCheckpoint('/content/gdrive/My Drive/GO_RECORD/checkpoints/small_model_epoch{epoch}.h5')
                    ]
                   )

一个问题在于,训练过程非常耗时,在没有GPU或相关算力支持的情况下我们需要等待很久时间才能完成整改训练流程,幸运的是我们可以直接加载已经训练好的网络参数,直接越过耗时耗力的训练过程:

from keras.models import load_model
model = load_model('/content/gdrive/My Drive/GO_RECORD/small_model_epoch_5.h5')
#幸运的是,已经有训练好的网络,我们可以跳过烦琐的训练阶段,直接加载训练好的网络参数以检验效果
model.evaluate_generator(generator = test_generator.generate(batch_size, num_classes),
                        steps = test_generator.get_num_samples() / batch_size)

上面代码中small_model_epoch_5.h5是已经训练好的网络参数存储文件,我们将其直接加载到我们的网络结构中从而省略掉训练过程,得到最终网络后,我们将其直接应用在测试数据上,运行后可发现,网络对数据预测的准确率达到85%以上,由于它预测的是六段和七段专业选手的落子,因此这样的准确率足以打败业余级别的高水平人类选手。

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

新书上架,请诸位朋友多多支持:
WechatIMG1.jpeg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容