上一节,我们从围棋服务器中下载大量棋谱,并将其转换成网络可以解析的数据格式,在神经网络的开发中完成了最繁琐的一步,也就是数据准备。接下来我们将创建一个神经网络,对数据进行解读,使得网络具备6到7段的围棋专业水平,它尚未具备打败柯洁或李世石这些顶级高手的能力,但打败业余级高手则绰绰有余。
我们要完成的网络有三种形态,一种是小型网络,一种是中型网络,一种是大型网络,其能力由弱到强,当然训练的耗时也由少到多。网络的基本结构是,前4层我们都使用卷积层,最后一层是一个含有19*19个神经元的全连接层。这里我们将引入一种新的网络层叫ZeroPadding2D层。卷积网络在处理图像时,它会把图像分割成多个小块后分别识别,识别所得的结果在规格上,也就是宽和高上相对于输入时的数据而言会有所缩小,如果卷积层较多,那么图片在层层处理后,规格会严重减小。
为了防止规格缩小得太严重,我们在将图片输入卷积网络前,会给它填充若干行和列,假设我们要处理的图片规格为19*19,如果我们执行下面语句:
ZeroPadding2D(padding = 2, input_shape = input_shape, data_format='channel_first')
那意味着我们在1919的二维数组左边和右边分别添加2列,在上面和底部分别添加2行,这些列和行全部使用0来填充,于是就能得到一个2323的二维数组。当我们把输入数据先扩大,经过卷积网络处理后就不会严重缩水。
同时我们还得注意,在卷积层,我们会用多个过滤器对图片进行扫描,图片被一个过滤器扫描后就得到一个二维数组作为扫描结果,如果多个过滤器就会得到多个二维数组,如下图:
过滤器的个数,我们也叫做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%以上,由于它预测的是六段和七段专业选手的落子,因此这样的准确率足以打败业余级别的高水平人类选手。
新书上架,请诸位朋友多多支持: