1. 程式人生 > >深度學習框架MXNet(2)--autograd

深度學習框架MXNet(2)--autograd

          這一節,我們將介紹MXNet框架中的自動求導模組autograd。在深度學習演算法中,經常需要計算的就是一個向量的梯度,但求梯度是一個手動編碼比較麻煩的事情,並且求向量的梯度並不是演算法思想的精髓部分,使用MXNet封裝並設計好的autograd模組來計算梯度,不僅可以大大的減少編碼的工作量,還能夠讓我們更好的將注意力集中在演算法思想的實現上。

1.什麼是梯度?

       梯度是一個數學上的概念,它在機器學習中很常見。在數學中我們對二維空間中的一個點求導,就是求這個點在某一方向上的變化率。如函式y=f(x),求x在函式f上的變化率,就是求導dy/dx。如下圖:

       

         如上圖,在函式曲線上,過A點作與A的法線垂直的直線l,l標識的就是A點在函式曲線上的變化率(也做斜率)。斜率直線越陡,求得的函式曲線在A點的導數dy/dx越大,A點的變化率越大;斜率曲線越平,求得的函式曲線在A點的導數dy/dx越小,A點的變化率也就越小。

         推廣到三維空間中,z=f(x,y),求點A的變化率,需要求點A在x,y方向的導數。即微積分中的求偏導數,(dz/dx,dz/dy)。如下圖:

       同樣,dz/dx越大說明,點A在x方向上的變化率越大,變化的越快,否則相反;dz/dy越大,點A在y方向上的變化率越大,變化的越快,否則相反。

       再次推廣到多維空間,z=f(x1,x2,x3,....,xn),在多維空間中,求某一點A(x1,x2,x3,...,xn)的變化率,就是對點A中的每個變數求偏導數,即grad(f)=(dz/dx1,dz/dx2,dz/dx3,...,dz/dxn),grad(f)即點A的梯度方向。

梯度方向grad(f)標識的是點A變化最快,變化率最大的方向。在深度學習中,使用優化演算法不斷減小模型損失的過程中,需要不斷的更新模型的引數向量,這時就需要計算引數向量的導數,即梯度。

       以上我們只是從感性上認識梯度,這一節我們主要介紹的是使用MXNet框架中的autograd模組進行梯度的計算。

2.梯度求導

       MXNet中的autograd模組提供了梯度求導的函式,呼叫其中封裝好的函式,我們可以避開數學細節,快速的求得向量的梯度。如下程式碼:

from mxnet import ndarray as nd
import mxnet.autograd as ag # 匯入autograd模組

x=nd.array([[1,2],[3,4]])#定義需要求導的變數
x.attach_grad()#申請求導需要的空間,空間用於存放x的導數
with ag.record():
    z=x*x*x*2#定義求導的函式z=2*x*x*x
z.backward()#函式z對x進行求導:dz/dx
print("x.grad:",x.grad)

       在使用autograd模組前,首先需要從MXNet中匯入模組。求導的過程一般固定為以下幾步:

       ① 首先定義待求導的變數。如上程式碼,通過NDArray模組,定義了一個2X2的向量矩陣x;

       ②為變數求導申請空間。如上,呼叫NDArray矩陣物件x的attach_grad()方法後,會為x申請一份用於存放求導結果的空間Buffer。物件x附帶著導數空間Buffer,當對x求導成功後,便將求導結果儲存在x的空間Buffer中;

       ③定義關於待求導變數的函式f。定義函式f,需要呼叫autograd模組的record()方法,這個方法返回的結果可以理解為就是我們定義的函式f,需要在with的宣告下呼叫record()方法。如上,z=x*x*x*2,z其實就是我們定義的關於x的函式;

       ④求導。求導只需要函式物件f呼叫backward()方法,封裝好的backward()函式就會自動對變數求導,並將求導結果儲存在之前申請的Buffer空間中。如上,z.backward()即是對變數x求導。求導結果:z(x)=2*x^3,z'(x)=6*x^2。求導結果如下:

       

       需要注意的一點是,求導結果z'(x)=6*x*x,運算x*x時,是點乘運算,而不是線性代數中矩陣間的叉乘運算。如下:

      

3.對控制流求導

       當對自變數求導時,可能會根據自變數x的差異,會有不同的函式f。這個時候我們就可能需要定義一個方法function,function包含控制流for和if,迴圈迭代的次數和判斷語句的執行都取決於輸入的自變數x,不同的輸入會返回不同的函式f。在正式介紹控制流求導前,我們首先介紹NDArray的相關方法。如下:

from mxnet import ndarray as nd
import mxnet.autograd as ag # 匯入autograd模組
x=nd.array([[3,4]])
print(nd.sum(x))#將矩陣中每個元素相加
print(nd.norm(x))#L2正規化
y=nd.array([5])
print(y.asscalar())#將只具有1個元素的陣列轉換為常量

       如上,我們定義了一個NDArray矩陣物件x,sum()方法返回矩陣中的每個元素相加的和;norm()方法返回矩陣的L2正規化,即將矩陣中每個元素平方和相加後,開根號返回結果。

呼叫asscalar()方法的矩陣只有一個元素,asscalar()方法返回就是這個唯一元素的值,是一個常量。以上程式碼列印結果如下:

       

       對控制流求導與普通的梯度求導過程唯一的區別是,一般需要定義一個方法function。function根據輸入自變數x的不同,通過迭代和判斷來選取關於x的函式。程式碼如下:

from mxnet import ndarray as nd
import mxnet.autograd as ag # 匯入autograd模組
#控制流求導
def function(a):#根據自變數a,來選取函式f並返回
    b=a
    while nd.norm(b).asscalar()<1000:#判斷條件為:矩陣b的L2範數<1000
        b=b*2
        if nd.sum(b).asscalar()>0:#判斷條件為:矩陣b的L1範數>0
            c=b
        else:
            c=100*b
    return c
a=nd.array([3,4,5])
a.attach_grad()#申請變數a的求導空間
with ag.record():#定義函式f
    c=function(a)
c.backward()#對a求導
print("a.grad:",a.grad)#求導結果

       如上程式碼所示,自變數a為待求導的變數,自定義函式function(a)通過迭代和判斷返回關於變數a的函式f,然後函式f對a進行求導,得出變數a的求導結果。

       本例介紹的重點是,通過控制流的條件判斷來獲取函式f並求導,至於function(a)是如何迭代和判斷的,讀者可以自行閱讀function(a)中的程式碼,這裡不再詳述。程式碼的列印結果如下:

      

            這一節介紹的內容不多,最近更新部落格的速度明顯沒有三月頻繁,主要是因為事太多,只能週末寫部落格,敬請見諒。下一節將介紹線性迴歸,敬請期待。