撰文|月踏
BP(Back Propagation)是深度学习神经网络的实践外围,本文通过两个例子展现手动推导BP的过程。
1
链式法则
链式法则是BP的外围,分两种状况:
(1)一元方程
在一元方程的状况下,链式法则比较简单,假如存在上面两个函数:
那么x的变动最终会影响到z的值,用数学符号示意如下:
z对x的微分能够示意如下:
(2)多元方程
在多元方程的状况下,链式法则略微简单一些,假如存在上面三个函数:
因为s的渺小变动会通过g(s)和h(s)两条门路来影响z的后果,这时z对s的微分能够示意如下:
这就是链式法则的全部内容,前面用理论例子来推导BP的具体过程。
2
只有一个weight的简略状况
做了一个简略的网络,这能够对应到链式法则的第一种状况,如下图所示:
图1
其中圆形示意叶子节点,方块示意非叶子节点,每个非叶子节点的定义如下,训练过程中的前向过程会依据这些公式进行计算:
这个例子中,咱们是想更新w1、b1、w2三个参数值,如果用lr示意learning rate,那么它们的更新公式如下:
在训练开始之前,b1、w1、w2都会被初始化成某个值,在训练开始之后,参数依据上面两个步骤来进行更新:
- 先进行一次前向计算,这样能够失去y1、y2、y3、loss的值
- 再进行一次反向计算,失去每个参数的梯度值,进而依据下面的公式(13)、(14)、(15)来更新参数值
上面看下反向流传时的梯度的计算过程,因为梯度值是从后往前计算的,所以先看w2的梯度计算:
再持续看w1的梯度计算:
最初看b1的梯度计算:
把w2、w1、b1的梯度计算出来之后,就能够依照公式(13)、(14)、(15)来更新参数值了,上面用OneFlow依照图1搭建一个对应的网络做试验,代码如下:
import oneflow as ofimport oneflow.nn as nnimport oneflow.optim as optimclass Sample(nn.Module): def __init__(self): super(Sample, self).__init__() self.w1 = of.tensor(10.0, dtype=of.float, requires_grad=True) self.b1 = of.tensor(1.0, dtype=of.float, requires_grad=True) self.w2 = of.tensor(20.0, dtype=of.float, requires_grad=True) self.loss = nn.MSELoss() def parameters(self): return [self.w1, self.b1, self.w2] def forward(self, x, label): y1 = self.w1 * x + self.b1 y2 = y1 * self.w2 y3 = 2 * y2 return self.loss(y3, label)model = Sample()optimizer = optim.SGD(model.parameters(), lr=0.005)data = of.tensor(1.0, dtype=of.float)label = of.tensor(500.0, dtype=of.float)loss = model(data, label)print("------------before backward()---------------")print("w1 =", model.w1)print("b1 =", model.b1)print("w2 =", model.w2)print("w1.grad =", model.w1.grad)print("b1.grad =", model.b1.grad)print("w2.grad =", model.w2.grad)loss.backward()print("------------after backward()---------------")print("w1 =", model.w1)print("b1 =", model.b1)print("w2 =", model.w2)print("w1.grad =", model.w1.grad)print("b1.grad =", model.b1.grad)print("w2.grad =", model.w2.grad)optimizer.step()print("------------after step()---------------")print("w1 =", model.w1)print("b1 =", model.b1)print("w2 =", model.w2)print("w1.grad =", model.w1.grad)print("b1.grad =", model.b1.grad)print("w2.grad =", model.w2.grad)optimizer.zero_grad()print("------------after zero_grad()---------------")print("w1 =", model.w1)print("b1 =", model.b1)print("w2 =", model.w2)print("w1.grad =", model.w1.grad)print("b1.grad =", model.b1.grad)print("w2.grad =", model.w2.grad)
这段代码只跑了一次forward和一次backward,而后调用step更新了参数信息,最初调用zero_grad来对这一轮backward算进去的梯度信息进行了清零,运行后果如下:
------------before backward()---------------w1 = tensor(10., requires_grad=True)b1 = tensor(1., requires_grad=True)w2 = tensor(20., requires_grad=True)w1.grad = Noneb1.grad = Nonew2.grad = None------------after backward()---------------w1 = tensor(10., requires_grad=True)b1 = tensor(1., requires_grad=True)w2 = tensor(20., requires_grad=True)w1.grad = tensor(-4800.)b1.grad = tensor(-4800.)w2.grad = tensor(-2640.)------------after step()---------------w1 = tensor(34., requires_grad=True)b1 = tensor(25., requires_grad=True)w2 = tensor(33.2000, requires_grad=True)w1.grad = tensor(-4800.)b1.grad = tensor(-4800.)w2.grad = tensor(-2640.)------------after zero_grad()---------------w1 = tensor(34., requires_grad=True)b1 = tensor(25., requires_grad=True)w2 = tensor(33.2000, requires_grad=True)w1.grad = tensor(0.)b1.grad = tensor(0.)w2.grad = tensor(0.)
3
以conv为例的含有多个weights的状况
用一个非常简单的conv来举例,这个conv的各种属性如下:
如下图所示:
图2
假设这个例子中的网络结构如下图:
图3
在这个简略的网络中,z节点示意一个avg-pooling的操作,kernel是2x2,loss采纳均方误差,上面是对应的公式:
前传局部同上一节一样,间接看反传过程,目标是为了求w0、w1、w2、w3的梯度,并更新这四个参数值,以下是求w0梯度的过程:
上面是求w1、w2、w3梯度的过程相似,间接写出后果:
最初再依照上面公式来更新参数即可:
用OneFlow依照图3来搭建一个对应的网络做试验,代码如下:
import oneflow as ofimport oneflow.nn as nnimport oneflow.optim as optimclass Sample(nn.Module): def __init__(self): super(Sample, self).__init__() self.op1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(2,2), bias=False) self.op2 = nn.AvgPool2d(kernel_size=(2,2)) self.loss = nn.MSELoss() def forward(self, x, label): y1 = self.op1(x) y2 = self.op2(y1) return self.loss(y2, label)model = Sample()optimizer = optim.SGD(model.parameters(), lr=0.005)data = of.randn(1, 1, 3, 3)label = of.randn(1, 1, 1, 1)loss = model(data, label)print("------------before backward()---------------")param = model.parameters()print("w =", next(param))loss.backward()print("------------after backward()---------------")param = model.parameters()print("w =", next(param))optimizer.step()print("------------after step()---------------")param = model.parameters()print("w =", next(param))optimizer.zero_grad()print("------------after zero_grad()---------------")param = model.parameters()print("w =", next(param))
输入如下(外面的input、param、label的值都是随机的,每次运行的后果会不一样):
------------before backward()---------------w = tensor([[[[ 0.2621, -0.2583], [-0.1751, -0.0839]]]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)------------after backward()---------------w = tensor([[[[ 0.2621, -0.2583], [-0.1751, -0.0839]]]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)------------after step()---------------w = tensor([[[[ 0.2587, -0.2642], [-0.1831, -0.0884]]]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)------------after zero_grad()---------------w = tensor([[[[ 0.2587, -0.2642], [-0.1831, -0.0884]]]], dtype=oneflow.float32, grad_fn=<accumulate_grad>)
参考资料:
1.http://speech.ee.ntu.edu.tw/~...
2.https://speech.ee.ntu.edu.tw/...
3.https://www.youtube.com/c/Hun...
欢送下载体验OneFlow v0.7.0最新版本:
https://github.com/Oneflow-In...