手把手推導Back Propagation
撰文|月踏
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 of
import oneflow.nn as nn
import oneflow.optim as optim
class 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 = None
b1.grad = None
w2.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 of
import oneflow.nn as nn
import oneflow.optim as optim
class 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/~tlkagk/courses.html
2.http://speech.ee.ntu.edu.tw/~hylee/index.php
3.http://www.youtube.com/c/HungyiLeeNTU
其他人都在看
歡迎下載體驗OneFlow v0.7.0:http://github.com/Oneflow-Inc/oneflow/
本文分享自微信公眾號 - OneFlow(OneFlowTechnology)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。
- 左益豪:用程式碼創造一個新世界|OneFlow U
- OneFlow原始碼解析:運算元指令在虛擬機器中的執行
- OneFlow原始碼解析:Op、Kernel與直譯器
- 18張圖,直觀理解神經網路、流形和拓撲
- 一種分散式深度學習程式設計新正規化:Global Tensor
- OneFlow v0.8.0正式釋出
- OneFlow原始碼一覽:GDB編譯除錯
- 大模型訓練難於上青天?效率超群、易用的“李白”模型庫來了
- 平行計算的量化模型及其在深度學習引擎裡的應用
- Geoffrey Hinton:我的五十年深度學習生涯與研究心法
- 圖解OneFlow的學習率調整策略
- 鍾珊珊:被爆錘後的工程師會起飛|OneFlow U
- 深度學習概述:從基礎概念、計算步驟到調優方法
- 訓練千億引數大模型,離不開四種GPU並行策略
- 一個運算元在深度學習框架中的旅程
- 一個運算元在深度學習框架中的旅程
- 李飛飛:我更像物理學界的科學家,而不是工程師|深度學習崛起十年
- 一個運算元在深度學習框架中的旅程
- 關於併發和並行,Go和Erlang之父都弄錯了?
- LLVM之父Chris Lattner:模組化設計決定AI前途,不服來辯