理解誤差反向傳播&用python實作自動微分
使用計算圖理解誤差反向傳播

這張圖展示了一個從左向右的計算流程,看起來很簡單是不是,這種“從左向右進行計算”是一種正方向上的傳播,稱為正向傳播(forward propagation),而像下圖中那樣反過來“從右向左進行計算”是一種反方向上的傳播,稱為反向傳播(backward propagation),

加粗橫線就是反向傳播的途徑,反向傳播會傳遞“區域導數”——也就是寫在加粗橫線下面的數字,從圖中我們可以清晰地看到“支付金額“關于”蘋果的價格“的導數的值是2.2,無論多么復雜的計算,都可以講它拆分為一個一個單獨地計算(加減乘除)然后用這種方式清楚地計算出導數,

如圖,對于任意一個簡單計算函式f,我們都可以很輕松的計算出反向傳播輸出的區域導數值,如圖,在反向傳播中E是流入f的值(輸入),Edy/dx是流出f的值(輸出),反向傳播的計算規則是,將信號E乘以節點的區域導數(dy/dx),然后將結果傳遞給下一個節點,
誤差反向傳播
- 為什么反向傳播能夠計算導數呢?
如上圖蘋果的例子所示,我們把蘋果的價格當做自變數x,那么支付金額y = f(x) = 蘋果的個數*消費稅*x,
那么“支付金額”關于“蘋果價格”的導數就是:df/dx = 蘋果的個數*消費稅,
在圖中的表示就是經過兩個”乘法計算“最終得到2*1.1=2.2,
- 為什么叫誤差反向傳播呢?
因為在神經網路中,正向傳播的終點、反向傳播的起點是損失函式值(誤差),我們要求得也是損失函式值關于各個引數的梯度(很多偏導陣列成的向量),
加法節點的反向傳播

加法節點的反向傳播將輸入的值會原封不動地流向下一個節點,

乘法節點的反向傳播

乘法的反向傳播會將上游的值乘以正向傳播時的輸入信號的“翻轉值”后傳遞給下游,
舉個例子

y=1/x的反向傳播
反向傳播時,會將上游的值乘以?y2(正向傳播的輸出的平方乘以?1后的值)后,再傳給下游,

計算圖的優點
-
區域計算,每個“計算”只需要按照規則將上游的輸入經過簡單的計算后,傳遞給下一個“計算即可,
-
將中間的計算結果全部保存起來,如上面例子所示,在反向傳播中會用到正向傳播時的兩個輸入x和y,因此就需要這個“計算”將中間結果保存起來,而一旦在代碼中實作這樣的一個一個“計算”,那就實作了自動微分,

自動微分
pytorch中實作了tensor類來實作自動微分,這里我們自己實作一個簡單的自動微分,
部分代碼參考《Python深度學習入門:從零構建CNN和RNN》
首先我們定義了一個Numberable包括了用于計算的整數和浮點數,便于統一計算,ensure_number用于把int和float型別裝換為Numberable型別,
from typing import *
Numberable = Union[float, int]
def ensure_number(num: Numberable):
# isinstance: Return whether an object is an instance of a class or of a subclass thereof
if isinstance(num, NumberWithGrad):
return num
else:
return NumberWithGrad(num)
這就是我們用于實作自動微分的類了,它保存正向傳播時的中間結果(depends_on)用于反向傳播時使用,還保存反向傳播的區域梯度(grad),
class NumberWithGrad(object):
def __init__(self,num: Numberable,depends_on: List[Numberable] = None,creation_op: str = ''):
# 本身的值
self.num = num
# 對應的區域梯度
self.grad = None
# 它所參與的“計算”需要用到的正向傳播中的輸入
self.depends_on = depends_on or []
# 計算型別標識
self.creation_op = creation_op
# 在 Python 中,使用 + 或 – 等運算子實際上會呼叫 _add_ 或 _sub_ 之類的基礎隱藏方法,所以我們要重寫這兩個方法
def __add__(self,other: Numberable):
# 進行加法計算
return NumberWithGrad(self.num + ensure_number(other).num,depends_on = [self, ensure_number(other)],creation_op = 'add')
def __mul__(self,other: Numberable = None):
# 進行乘法計算
return NumberWithGrad(self.num * ensure_number(other).num,depends_on=[self, ensure_number(other)],creation_op='mul')
def backward(self, backward_grad: Numberable = None) -> None:
if backward_grad is None:
# 反向傳播最開始的梯度設為1
self.grad = 1
else:
# 這些行允許累積梯度,
# 如果梯度尚不存在,就將其設定為backward_grad
if self.grad is None:
self.grad = backward_grad
# 否則,只需向現有梯度添加backward_grad
else:
self.grad += backward_grad
if self.creation_op == "add":
# 加法節點的反向傳播將輸入的值會原封不動地流向下一個節點
self.depends_on[0].backward(self.grad)
self.depends_on[1].backward(self.grad)
if self.creation_op == "mul":
# 乘法的反向傳播會將上游的值乘以正向傳播時的輸入信號的“翻轉值”后傳遞給下游,
# 計算關于第1個元素的導數
new = self.depends_on[1] * self.grad
# 向后發送關于該元素的導數
self.depends_on[0].backward(new.num)
# 計算關于第2個元素的導數
new = self.depends_on[0] * self.grad
# 向后發送關于該元素的導數
self.depends_on[1].backward(new.num)
我們接下來使用自動微分功能計算蘋果例子中的導數:
apple_price = NumberWithGrad(100)
apple_num = NumberWithGrad(2)
tax = NumberWithGrad(1.1)
b = apple_price * apple_num
c = b * tax
#這里只需要呼叫c.backward(),即可計算出上面所有變數再反向傳播中的梯度了,
c.backward()
print("apple_price.grad=",apple_price.grad)
print("apple_num.grad=",apple_num.grad)
print("tax.grad=",tax.grad)
print("中間變數b.grad=",b.grad)
print("中間變數c.grad=",c.grad)

可以看到,這跟我們在計算圖中推到的結果一致,

下面用自動微分解決一個難一點的問題:計算Sigmoid函式的導數

這次的難點主要是包含了除法和指數運算,我們需要在原有的代碼上添加對除法運算和指數運算的自動微分支持,
在NumberWithGrad類中添加方法
def div(self,other: Numberable = None):
# 進行除法計算
return NumberWithGrad(self.num / ensure_number(other).num,depends_on=[self, ensure_number(other)],creation_op='div')
def exp(self):
# 進行指數計算
return NumberWithGrad(math.exp(self.num),depends_on=[self],creation_op='exp')
在backward方法的最后添加條件判斷
if self.creation_op == "div":
# 除法z=x/y的反向傳播,
# 計算關于x的導數 dz/dx = 1/y
new = ensure_number(1/self.depends_on[1].num * self.grad)
# 向后發送關于該元素的導數
self.depends_on[0].backward(new.num)
# 計算關于y的導數 dz/dy = x*-(1/y)**2
new = ensure_number(-1 * self.depends_on[0].num * (1/self.depends_on[1].num) * (1/self.depends_on[1].num) * self.grad)
# 向后發送關于該元素的導數
self.depends_on[1].backward(new.num)
if self.creation_op == "exp":
# 指數函式y=exp(x)的反向傳播,
# 計算關于x的導數 dy/dx = exp(x)
new = ensure_number(math.exp(self.depends_on[0].num) * self.grad)
# 向后發送關于該元素的導數
self.depends_on[0].backward(new.num)
使用如下代碼進行自動微分:(dl/dy默認是1,并且設x=2)
# Sigmoid
x=NumberWithGrad(2)
num_f1=NumberWithGrad(-1)
num_1_1=NumberWithGrad(1)
num_1_2=NumberWithGrad(1)
b=x*num_f1
c=b.exp()
d=c+num_1_1
y=num_1_2.div(d)
y.backward()
print("x.grad=",x.grad)

下圖是用計算圖推導的反向傳播程序,我們來驗證一下自動微分的結果,

y=1/(1+math.exp(-2))
x_grad=1 * y * y * math.exp(-2)
print(x_grad)

結果一致,說明我們設計的自動微分工具可以正常的自動求出sigmoid函式的微分!!!!
最后放在一張圖,是我關于神將網路中誤差反向傳播的理解
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/433210.html
標籤:AI
下一篇:tensorflow 安裝GPU版本,CUDA與cuDNN版本對應關系,RTX3050Ti (notebook)
