我们在《AlphaZero背后的算法原理解析》中已经大致提过卷积网络的实现,同时也在《从零开始用Golang实现AlphaZero下象棋——蒙特卡洛树》一文中提到过蒙特卡洛搜索树的实现。那么本文继续用Golang实现AlphaZero所需要的卷积神经网络。

既然要实现卷积神经网络,那我们先回顾一下这个网络的概念,通常来讲,结构如下:



这个结构是比较简单的了,省略了预处理,省略了两个不同输出之间所需要的层的不同。对了,本CNN模型最大的特点就是给出了两个不同的输出,一般来说我们的CNN模型都是给出一个输出,要么多分类,要么回归这两种比较常见。又回归,又给策略,算是一个AlphaZero的小特点。

用数学简单的描述一下,就是:

f_\theta = (P,V)

其中:

P就是P(a|s)也就是在状态S(棋局形势)下,采取策略a的概率;

V是一个标量,代表目前形势下最后取胜的一个概率;

关于这个网络和蒙特卡洛树的交互过程我们在本文中就不再赘述了。

有了上面这些定义,我们进一步给出这个网络的损失函数:

Loss = (Z-V)^2 + \pi ^ 2 \cdot Ln(P) + c \cdot ||\theta||^2

含义也是比较清晰的,第一项表达了预测取胜的标量和真正结果之间的距离;第二项表达了策略损失,第三项表达了CNN的参数。

介绍完了概念,我们终于可以开始写代码了:-)

我们本次用Golang实现这个神经网络,并且选用了Gorgonia这个库,算是给大家看一下TensorFlow以外的一些选择。

整个这个训练下棋策略的网络是比较复杂的,但是,我们可以分层来看这个网络,一部分一部分来实现。具体来说,可以看看常见的卷积网络套路,然后看看不同输出需要的输出层是什么样子。

先说我们的主角:卷积层

在gorgonia库中,对于二维图片的卷积层,可以用这个方法Conv2d,它有下面一些参数:

filter 这是卷积的过滤器数量,所谓卷积可以理解为把一张二维的图片“卷积”成一堆摞起来的小图片,主要就是靠一个一个的过滤器,多设置一些过滤器,网络就“厚实”一些,少一点的话,网络就“扁平”一点。

kernelShape 一个卷积窗有多宽有多高,来一对参数,就是这个了

pad 这个和Keras里面不同,这个是一个二维的数,描述宽和高,就是卷积窗口移动超出了原来的图的范围可咋办,补些0吧,按这个宽和高来补

stride 每次卷积窗口挪动的时候步子有多大,也是一对数,一个描述x轴,一个描述y轴

dilation 这个是一个高级用法,表示“扩散率”,就是用扩散卷积的时候涉及到的一个参数,不用就空着也没事

说了这么多,卷积运算到底是怎么回事?我们来回顾和复习一下:

卷积运算

卷积是两个实变函数之间的一种运算,它的灵感来源于神经科学,是神经科学在计算机工程中的一种具体应用。

我们可以大致来从一个例子引出来卷积这个概念:

譬如踢足球的梅西同学,我们想要知道他在球场上每时每刻的位置,怎么办?设一个函数x(t),这个函数可以知道任何时刻t梅西在球场上的位置x,当然了t是实数。为了实现这个函数,我们可以选择一个传感器,或者摄像头之类的东西,来捕捉梅西的动作,从而算出来身体所在的位置。可是实际问题来了,这种测量,或者拍摄,总是会有一点延迟,不能反映现在这个时刻t真正的梅西的位置,而是反映了一个微小的延迟以前的那个时间点t’的位置,这怎么可以忍受?!那么我们进一步调整一下,为了让这个延迟尽量从宏观上不要威胁到我们的测量,我们采用一种加权平均的操作,如下:

S(t)=\int x(t-t') \cdot \omega (t') \cdot d(t-t')

没错,这个就是卷积的样子了:

s(t)=(x\ast\omega)(t)

通常来说, 是一个概率密度函数,起名字叫“核函数”。

工程问题通常没有数学这么完美,一般积分是不存在的,因为工程上都是计算抽样值,所以一些列的抽样值,就变成了求和:

s(t)=\sum {x(t-t') \cdot \omega (t')}

前面提到过卷积可以读取图像,那么如果能把二维变量直接输入进来,岂不是计算很方便,于是就有:

s(i,j)=(I \ast K) \cdot (i,j)=\sum \sum I(m,n) K(i-m, j-n)

以上形式不用深究,因为这个形式已经被各种各样的底层机器学习代码库给实现了。

我们下来看一看这种运算的近似的图形化意义:

以图片为例,譬如分析一下那些图片包含梅西,哪些没有。这就是一个基本的神经网络的分类问题,可以用卷积神经网络来给出分类判断,怎么做呢,通常卷积的过程如下:



背景就好比一个大矩阵,也就是一个图片经过预处理以后的数据矩阵吧,然后橘红色的框框就是一个卷积窗口,再加上一个wxyz组成的核,就有了一个简单的计算结果,再一步一步的挪动卷积窗口,从而达到求和的目的。

继续往下走:batch normalization 层,啥意思,就是卷积以后,批量的正规化一下。数学上是非常简单的。

如果对于第k层,有 x^k = W^T_k \cdot x^{k-1} ,那么单另把中间的部分拿出来,有 Z_k=W^T_k \cdot x^{k-1} ,进一步就有 Z = \frac {Z_k-E[Z_k]}{\sqrt{V[Z_k]}} ,E的意思就是均值,V的意思就是方差,合起来就是正规化。在Gorgonia库里面有现成的一个方法:BatchNorm可以用。

最后一步,总得用激活函数激活一下吧,Gorgonia库给我们一个Rectif方法,带进去就可以了。用代码总结一下:

是不是大大一篇理论到了这里就三句话的感觉,也并不比TensorFlow复杂太多。

好了,本次先到这里为止,我们下次继续构建我们的AlphaZero。

欢迎光临我的专栏