1、计算图:将计算过程用数据结构图表示,通过多个节点和边表示,节点用O表示,O中是计算内容,将计算的中间结果写在箭头上方,表示各个节点的计算结果从左向右传递。用计算图解题的流程即:①构建计算图;②在计算图上从左向右进行计算(正向传播,从计算图出发点到结束点的传播,反向传播正好相反)。
2、计算图的优点:
①计算图可以集中精力于局部计算,从而简化问题,无论全局计算多么复杂,各个步骤所要做的就是对象节点的局部计算,通过传递它的计算结果,可以获得全局的复杂计算结果。
②可以通过反向传播高效计算导数,反向传播使用与正方向相反的箭头(粗线表示),传递“局部导数”,将导数值写在箭头下方,并且计算途中求得的导数结果可以被共享,从而可以高效地计算多个导数。
3、链式法则:如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的导数的乘积表示,这是关于复合函数的导数的性质。以复合函数z = (x+y)^2为例,由式子z = t^2,t = x+y构成。
反向传播的顺序是,先将节点的输入信号乘以节点的局部导数(偏导数),然后再传递给下一个节点,这个过程也基于链式法则。
4、反向传播的结构:
-
加法节点的反向传播——以z=x+y为对象观察反向传播,将上游传来的导数乘以1传向下游,因为加法节点的反向传播只乘以1,所以输入值原封不动流向下一个节点。
-
乘法节点的反向传播——y以z=xy为对象观察反向传播,乘法的反向传播会将上游的值乘以正向传播时输入信号的翻转值(一种翻转关系),然后传递给下游,这个过程要保存正向传播的输入信号。
5、乘法层的实现:层的实现中有两个共通的方法(接口)forward()和backward(),分别对应正向传播和反向传播,*_ init _()*中初始化实例变量x和y,用于保存正向传播时的输入值,forward()接收x和y两个参数,将它们相乘后输出,backward()将从上游传来的导数乘以正向传播的翻转值,然后传给下游。
class MulLayer: #乘法层
def __init__(self):
self.x = None
self.y = None
def forward(self,x,y):
self.x = x
self.y = y
out = x * y
return out
def backward(self,dout):
dx = dout * self.y
dy = dout * self.x
return dx,dy
6、加法层的实现:不需要特意进行初始化,_ init _()中什么也不运行,加法层的forward()接收x和y两个参数,将它们相加后输出,backward()将上游传来的导数原封不动传给下游。
class AddLayer: #加法层
def __init__(self):
pass
def forward(self,x,y):
out = x + y
return out
def backward(self,dout):
dx = dout * 1
dy = dout * 1
return dx,dy
7、ReLU层的实现:激活函数ReLU由下式表示,并可求出y关于x的导数,如果正向传播时所输入的x>0,则反向传播会将上游的值原封不动传给下游;如果正向传播时输入的x≤0,则反向传播中传递给下游的信号将停在此处。
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
其中,实例变量mask是由True/False构成的Numpy数组,将输入元素中≤0的值的索引保存为True,反向传播中会使用正向传播时保存的mask,将从上游传来的dout的mask中元素为True的地方设为0。
8、Sigmoid层的实现:除了“x”和“+”节点,还有新节点“exp”和“/”节点,①“/”节点表示y=1/x,导数可以解析性地表示为-1/(x^2)=-(y**2),反向传播时会将上游值乘以该值后再传给下游;②“+”节点将上游值原封不动传给下游;③“exp”节点表示y=exp(x),导数仍是自身;④“x”节点将正向传播值翻转后做乘法运算,即-1。通过对节点进行集约化,可以无须在意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
9、Affine层的实现:神经网络的正向传播中进行的矩阵乘积运算在几何学领域被称为“仿射变换”,仿射变换的处理过程即Affine层的实现过程。比如正向传播为了计算加权信号总和,使用了矩阵的乘积运算,Y = np.dot(X,W) + B,在反向传播时以矩阵为对象,按矩阵各个元素进行计算,步骤和以标量为对象的计算图相同。
批版本的Affine层:输入X的形状由(1,2)变为(N,2),在正向传播时,偏置会被加到X▪W的各个数据上,比如N=2时偏置会被分别加到这两个数据计算结果上;反向传播时,各个数据的反向传播值需要汇总为偏置元素,使用np.sum()函数(axis=0指定0维)指定对应轴方向上的元素进行求和。
class Affine:
def __init__(self,W,b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
def forward(self,x):
self.x = x
out = np.dot(x,self.W) + self.b
return out
def backward(self,dout):
dx = np.dot(dout,self.W.T)
self.dW = np.dot(self.x.T,dout)
self.db = np.sum(dout,axis=0)
return dx
10、Softmax-with-Loss层:Softmax会将输入值正规化之后再输出(将输出值的和调整为1),考虑到也包含作为损失函数的交叉熵误差(cross entropy error),所以称为Softmax-with-Loss层。
上述计算图可以进行简化,softmax函数记为Softmax层,交叉熵误差记为Cross Entropy Error层,假设要进行3类分类,从前面层接收3个输入,Softmax层将这3个输入(a1,a2,a3)正规化,输出(y1,y2,y3),Cross Entropy Error层接收(y1,y2,y3)和监督标签(t1,t2,t3),从这些数据中输出损失L。反向传播时Softmax层得到的(y1-t1,y2-t2,y3-t3)是该层输出和监督标签的差分,神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习的重要性质,因为神经网络的学习目的就是通过调整权重参数,使神经网络的输出接近监督标签(y与t)。
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self,x,t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y,self.t)
return self.loss
def backward(self,dout = 1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
#传递给前面层的是单个数据的误差
return dx
11、误差反向传播法的实现:
①前提:神经网络中有合适的权重和偏置,调整权重和偏置以便拟合训练的过程称为学习;
②mini-batch:从训练数据中随机选择一部分数据;
③计算梯度:计算损失函数关于各个权重参数的梯度;
④更新参数:将权重参数沿着梯度方向进行微小更新;
⑤重复上述步骤。
误差反向传播法主要在③中得以运用,和需要花费较多时间的数值微分不同,误差反向传播法可以快速高效地计算梯度,通过使用层,获得识别结果的处理(predict())和计算梯度的处理(gradient())只需要通过层之间的传递就能实现。在确认误差反向传播法的实现是否正确时需要使用数值微分,将两者的结果进行比较的操作称为梯度确认。
class TwoLayerNet:
def __init__(self,input_size,hidden_size,output_size,weight_init_std=0.01):
#初始化权重
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size,hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size,output_size)
self.params['b2'] = np.zeros(output_size)
#生成层
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss()
def predict(self,x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self,x,t):
y = self.predict(x)
return self.lastLayer.forward(y,t)
def accuracy(self,x,t):
y = self.predict(x)
y = np.argmax(y,axis=1)
if t.ndim != 1:
t = np.argmax(t,axis=1)
accuracy = np.sum(y==t) / float(x.shape[0])
return accuracy
def numerical_gradient(self,x,t):
loss_W = lambda W: self.loss(x,t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self,x,t):
#forward
self.loss(x,t)
#backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
#设定
grads = {}
grads['W1'] = self.layers['Affine1'].dW
grads['b1'] = self.layers['Affine1'].db
grads['W2'] = self.layers['Affine2'].dW
grads['b2'] = self.layers['Affine2'].db
return grads
将神经网络的层保存为OrderDict有序字典,自身可以记住向字典里添加元素的顺序,正向传播只需按照添加元素的顺序调用各层的forward()方法,反向传播则按照相反的顺序调用各层。