现在,我们将计算图的思路应用到神经网络中。这里,我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。
激活函数ReLU(Rectified Linear Unit)由下式表示:
可以求出y关于x的导数:
如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游。反过来,如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处。用计算图表示:
现在我们来实现ReLU层。在神经网络的层的实现中,一般假定forward()和backward()的参数是NumPy数组。Relu类有实例变量mask。这个变量mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False。mask变量保存了由True/False构成的NumPy数组。ReLU层实现如下:
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
ReLU层的作用就像电路中的开关一样。正向传播时,有电流通过的话,就将开关设为 ON;没有电流通过的话,就将开关设为 OFF。反向传播时,开关为ON的话,电流会直接通过;开关为OFF的话,则不会有电流通过。
接下来,我们来实现sigmoid函数。sigmoid函数由下式表示:
用计算图表示如下:
除了“×”和“+”节点外,还出现了新的“exp”和“/”节点。“exp”节点会进行y = exp(x)的计算,“/”节点会进行y=1/x的计算。sigmoid函数的计算由局部计算的传播构成。下面我们就来进行sigmoid函数的计算图的反向传播。作为总结,我们来依次看一下反向传播的流程。
步骤一:
“/”节点表示 ,它的导数可以解析性地表示为下式:
反向传播时,会将上游的值乘以−y2(正向传播的输出的平方乘以−1后的值)后,再传给下游。计算图如下所示:
步骤2:
“+”节点将上游的值原封不动地传给下游。计算图如下所示:
步骤三:
“exp”节点表示y = exp(x),它的导数由下式表示:
计算图中,上游的值乘以正向传播时的输出(这个例子中是exp(−x))后,再传给下游:
步骤4:
“×”节点将正向传播时的值翻转后做乘法运算。因此,这里要乘以−1:
简洁版的计算图可以省略反向传播中的计算过程,因此计算效率更高。此外,通过对节点进行集约化,可以不用在意Sigmoid层中琐碎的细节,而只需要专注它的输入和输出,这一点也很重要。结果可以进一步整理如下:
现在,我们用Python实现Sigmoid层:
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
这个实现中,正向传播时将输出保存在了实例变量out中。然后,反向传播时,使用该变量out进行计算。