深度学习(五):误差反向传播法

在四中,采取了数值微分的方式求损失函数关于权重参数的梯度,但是比较耗时间,这一节讲一个能够高效计算权重参数的梯度的方法------误差反向传播法。

一,计算图

计算图指的就是将计算过程用图形表示出来。

计算图的表示方式为:计算图通过节点和箭头表示计算过程。节点用○表示,○中是计算的内容。将计算的中间结果写在箭头的上方,表示各个节点的计算结果从左向右传递。

**问题1:**首先来看看一个简单的题: 小明在水果摊买了2个单价100的苹果,消费税为10%,请计算支付金额

表示如下:

如果在○里只写计算方式,可表示如下:

问题2:小明在水果摊买了2个苹果、3个砂糖橘,苹果每个100,砂糖橘每个150,消费税10%,请计算支付金额。

像这种从左向右来金蒜,简称为正向传播

从计算图中可以看到,无论全局是多么复杂的问题,只要各个节点的局部运算正确就可以解决问题

如果说想知道苹果价格的上涨会在多大程度上影响最终的支付金额,也就是说求"支付金额关于苹果的价格的导数",可以通过计算图的反向传播求导数

二,链式法则

在反向传播中,是将局部导数从右到左传递,而传递这个局部导数的原理,是基于链式法则

2.1 链式法则

是复合函数里面的定义,如有以下复合函数:

要求z关于x的导数,可表示为

链式法则的定义如下:如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。

2.2 计算图的反向传播

将上式的链式法则用计算图表示出来,如下

如图所示,反向传播的计算顺序是,将反向过程中的节点输入信号乘以节点的局部导数,然后将结果传递给下一个节点。

这时我们重新考虑之前的苹果的价格、苹果的个数、消费税这3个变量各自如何影响最终支付的金额的问题。具体如下:

这里主要是因为在计算图中,如果节点的算法为乘法,那么反向传播就是乘以输入信号的翻转值(对应偏导)

四,简单层的实现

在上面的例子中,我们主要涉及了乘法和加法两种,那么,我们将要实现的计算图的乘法节点和加法节点各自封装成层类

4.1 乘法层

乘法层实现如下:

python 复制代码
class MulLayer:
  #初始化x、y,保存正向传播的输入值
  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 # 翻转x和y
    dy = dout * self.x

    return dx, dy

假如借用这个类来实现买苹果的案例

代码实现如下:

ini 复制代码
apple = 100
apple_num = 2
tax = 1.1

# layer
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)
print(price) # 220

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax) # 2.2 110 200

4.2 加法层

加法层实现如下:

ruby 复制代码
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

4.3 案例实现

案例如下

代码来实现如下案例的前向和反向过程

ini 复制代码
apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1

# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num) #(1)
orange_price = mul_orange_layer.forward(orange, orange_num) #(2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price) #(3)
price = mul_tax_layer.forward(all_price, tax) #(4)

# backward
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice) #(4)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) #(3)
dorange, dorange_num = mul_orange_layer.backward(dorange_price) #(2)
dapple, dapple_num = mul_apple_layer.backward(dapple_price) #(1)
print(price) # 715
print(dapple_num, dapple, dorange, dorange_num, dtax) # 110 2.2 3.3 165 650

五,激活函数层的实现

结合计算图的思想,将神经网络的的激活函数层也类似实现

5.1 ReLU层

激活函数ReLU如下所示:

求导如下:

用计算图来表示,如下:

代码实现如下:

ruby 复制代码
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数组,正向传播时把x中小于等于0的保存为True,其他大于0的保存为False。

这样,在正向的时候,小于等于0的,输出0,大于0的输出不变

在反向的时候,使用正向的mask,将反向输入的小于等于0的输出0,大于0的输出不变

5.2 Sigmoid层

sigmoid函数表示如下:

用计算图来表示为:

求导为: <math xmlns="http://www.w3.org/1998/Math/MathML"> y ' = y ( 1 − y ) y` = y(1-y) </math>y'=y(1−y)

代码实现为:

ruby 复制代码
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

六,Affine/Softmax层的实现

6.1 Affine层(全连接)

在Affine层中,输入数据通过一个权重矩阵(W)和一个偏置向量(b)的乘法,再加上偏置向量来计算输出。其数学表示为:Y = np.dot(X, W) + B,用计算图表示如下

计算各自的偏导

再在计算图中加入反向传播,如下图所示

这时的输入X是以单个数据为对象的,那假如我们现在开始考虑N个数据一起进行正向传播

主要是偏置,正向传播时,偏置会被加到每一个数据,所以,反向传播时,各个数据的反向传播的值需要汇总为偏置的元素

代码实现如下:

python 复制代码
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

6.2 Softmax-with-Loss 层

softmax函数会将输入值正规化之后再输出,再考虑也包含作为损失函数的交叉熵误差,所以称为"Softmax-with-Loss层"。

假设要进行3类分类,计算图如下:

可以再简化一下

Softmax层将输入(a1, a2, a3)正规化,输出(y1, y2, y3)。 Cross Entropy Error层接收Softmax的输出(y1, y2, y3)和教师标签(t1, t2, t3),从这些数据中输出损失L。

用代码实现:

python 复制代码
class SoftmaxWithLoss:
  def __init__(self):
    self.loss = None # 损失
    self.y = None # softmax的输出
    self.t = None # 监督数据(one-hot vector)

  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
相关推荐
zmjia11123 分钟前
全流程Python编程、机器学习与深度学习实践技术应用
python·深度学习·机器学习
一只在学习的瓶子2 小时前
【大模型 AI 学习】大模型 AI 部署硬件配置方案(本地硬件配置 | 在线GPU)
深度学习·阿里云·ai
HyperAI超神经3 小时前
Meta 首个多模态大模型一键启动!首个多针刺绣数据集上线,含超 30k 张图片
大数据·人工智能·深度学习·机器学习·语言模型·大模型·数据集
Eric.Lee20213 小时前
数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall
python·深度学习·算法·目标检测·计算机视觉·数据集·螃蟹检测
DogDaoDao4 小时前
【预备理论知识——2】深度学习:线性代数概述
人工智能·深度学习·线性代数
牛哥带你学代码4 小时前
交叠型双重差分法
人工智能·深度学习·机器学习
深度学习实战训练营5 小时前
基于keras的停车场车位识别
人工智能·深度学习·keras
菜就多练_08286 小时前
《深度学习》OpenCV 摄像头OCR 过程及案例解析
人工智能·深度学习·opencv·ocr
没有余地 EliasJie6 小时前
Windows Ubuntu下搭建深度学习Pytorch训练框架与转换环境TensorRT
pytorch·windows·深度学习·ubuntu·pycharm·conda·tensorflow
技术无疆7 小时前
【Python】Streamlit:为数据科学与机器学习打造的简易应用框架
开发语言·人工智能·python·深度学习·神经网络·机器学习·数据挖掘