7.2.3 Theano中的導(dǎo)數(shù)
譯者:Python 文檔協(xié)作翻譯小組,原文:Derivatives in Theano。
本文以 CC BY-NC-SA 4.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。
Python 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。
計算梯度
現(xiàn)在讓我們使用Theano來完成一個稍微復(fù)雜的任務(wù):創(chuàng)建一個函數(shù),該函數(shù)計算相對于其參數(shù)x的某個表達式y的導(dǎo)數(shù)。為此,我們將使用宏T.grad。例如,我們可以計算


這里是計算這個梯度的代碼:
>>> import numpy
>>> import theano
>>> import theano.tensor as T
>>> from theano import pp
>>> x = T.dscalar('x')
>>> y = x ** 2
>>> gy = T.grad(y, x)
>>> pp(gy) # print out the gradient prior to optimization
'((fill((x ** TensorConstant{2}), TensorConstant{1.0}) * TensorConstant{2}) * (x ** (TensorConstant{2} - TensorConstant{1})))'
>>> f = theano.function([x], gy)
>>> f(4)
array(8.0)
>>> numpy.allclose(f(94.2), 188.4)
True
在這個例子中,我們可以從pp(gy)看到我們正在計算正確的符號梯度。fill((x ** 2), 1.0)表示生成一個與x ** 2相同形狀的矩陣并以1.0填充它。
注意
優(yōu)化器簡化了符號梯度表達式。你可以通過挖掘編譯后的函數(shù)的內(nèi)部屬性來看到這一點。
pp(f.maker.fgraph.outputs[0])
'(2.0 * x)'
優(yōu)化后,圖中只剩下一個Apply節(jié)點,這將使輸入加倍。
我們還可以計算復(fù)雜表達式的梯度,例如上面定義的logistic函數(shù)。事實證明,logistic的導(dǎo)數(shù)是:。

logistic函數(shù)的梯度圖,其中x軸為x,y軸為

>>> x = T.dmatrix('x')
>>> s = T.sum(1 / (1 + T.exp(-x)))
>>> gs = T.grad(s, x)
>>> dlogistic = theano.function([x], gs)
>>> dlogistic([[0, 1], [-1, -2]])
array([[ 0.25 , 0.19661193],
[ 0.19661193, 0.10499359]])
一般來說,對于任何標量表達式s,T.grad(s, w)提供Theano表達式用于計算。這樣,Theano可用于對符號進行高效的微分(由于T.grad返回的表達式將在編譯期間優(yōu)化),即使對于具有多個輸入的函數(shù)也是如此。(有關(guān)符號微分的描述,請參見自動微分)。
注意
T.grad的第二個參數(shù)可以是一個列表,在這種情況下,輸出也是一個列表。兩個列表中的順序很重要:輸出列表的元素i是T.grad第一個參數(shù)相對于第二個參數(shù)列表中的第i元素的梯度。T.grad的第一個參數(shù)必須是標量(大小為1的張量)。有關(guān)T.grad參數(shù)的語義的更多信息以及實現(xiàn)的細節(jié),請參見庫的這部分。
有關(guān)微分內(nèi)部工作原理的其他信息,也可以在更高級的教程擴展Theano中找到。
計算Jacobian
在Theano的用語中,術(shù)語Jacobian表示函數(shù)相對于其輸入的一階偏導(dǎo)數(shù)的張量。(這是對數(shù)學(xué)中所謂的Jacobian矩陣的泛化。)Theano實現(xiàn)theano.gradient.jacobian()宏,執(zhí)行計算Jacobian所需的所有內(nèi)容。以下內(nèi)容說明如何手動執(zhí)行。
為了手動計算某些函數(shù)y相對于某個參數(shù)x的雅可比矩陣,我們需要使用scan。我們所做的是循環(huán)y中的條目,并計算y [i]相對于x的梯度。
注意
scan是Theano中的通用操作,允許以符號方式寫入各種循環(huán)方程。創(chuàng)建符號循環(huán)(并優(yōu)化它們的性能)是一項艱巨的任務(wù),人們正在努力提高scan的性能。我們將在本教程后面回到scan。
>>> import theano
>>> import theano.tensor as T
>>> x = T.dvector('x')
>>> y = x ** 2
>>> J, updates = theano.scan(lambda i, y, x : T.grad(y[i], x), sequences=T.arange(y.shape[0]), non_sequences=[y, x])
>>> f = theano.function([x], J, updates=updates)
>>> f([4, 4])
array([[ 8., 0.],
[ 0., 8.]])
我們在這段代碼中使用T.arange生成從0到y.shape[0]的int序列。然后,我們循環(huán)該序列,并且在每個步驟,我們計算元素y[i]相對于x的梯度。 scan自動連接所有這些行,生成對應(yīng)于Jacobian的矩陣。
注意
關(guān)于T.grad,有一些缺陷需要注意。其中一個是你不能重寫上面的Jacobian表達式為theano.scan(lambda y_i,x: T.grad(y_i,x), sequences=y, non_sequences=x),即使從scan的文檔看來是可能的。原因是y_i將不再是x的函數(shù),而y[i]仍然是。
計算Hessian
在Theano中,術(shù)語Hessian具有通常的數(shù)學(xué)概念:它是由函數(shù)的二階偏導(dǎo)數(shù)組成的矩陣,該函數(shù)的輸出為標量和輸入為向量。Theano實現(xiàn)theano.gradient.hessian()宏,完成計算Hessian所需要的所有內(nèi)容。以下內(nèi)容說明如何手動執(zhí)行。
你可以類似于類似于的方式手動計算Hessian?,F(xiàn)在唯一的區(qū)別是,我們計算T.grad(cost,x)的Jacobian,而不是計算某個表達式y的Jacobian,其中cost是某個標量。
>>> x = T.dvector('x')
>>> y = x ** 2
>>> cost = y.sum()
>>> gy = T.grad(cost, x)
>>> H, updates = theano.scan(lambda i, gy,x : T.grad(gy[i], x), sequences=T.arange(gy.shape[0]), non_sequences=[gy, x])
>>> f = theano.function([x], H, updates=updates)
>>> f([4, 4])
array([[ 2., 0.],
[ 0., 2.]])
Jacobian乘以向量
有時我們可以用Jacobians乘以向量或向量乘以Jacobians來表達算法。與求值Jacobians然后進行相乘相比,有方法計算所需的結(jié)果同時避免對Jacobians進行真正的求值。這可以帶來顯著的性能提升。一個這樣的算法的描述可以在這里找到:
- Barak A. Pearlmutter,“Fast Exact Multiplication by the Hessian”,Neural Computation,1994
雖然原則上我們希望Theano為我們自動識別這些模式,但在實踐中,以通用的方式實現(xiàn)這樣的優(yōu)化是非常困難的。因此,我們提供專門用于這些任務(wù)的特殊函數(shù)。
R操作符
R操作符用于求值Jacobian和向量之間的乘積,即。該公式甚至可以推廣為x是一個矩陣、或者一個普通的張量,在這種情況下Jacobian變?yōu)閺埩坎⑶页朔e變?yōu)槟撤N張量的積。因為在實踐中,我們最終需要根據(jù)權(quán)重矩陣來計算這樣的表達式,所以Theano支持這種更通用的操作形式。為了求值表達式y相對于x的R操作,將Jacobian與v相乘,你需要做類似這樣的事情:
>>> W = T.dmatrix('W')
>>> V = T.dmatrix('V')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> JV = T.Rop(y, W, V)
>>> f = theano.function([W, V, x], JV)
>>> f([[1, 1], [1, 1]], [[2, 2], [2, 2]], [0,1])
array([ 2., 2.])
實現(xiàn)Rop的操作的列表。
L操作符
類似于R操作符,L操作符將計算行向量乘以Jacobian。數(shù)學(xué)公式是

>>> W = T.dmatrix('W')
>>> v = T.dvector('v')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> VJ = T.Lop(y, W, v)
>>> f = theano.function([v,x], VJ)
>>> f([2, 2], [0, 1])
array([[ 0., 0.],
[ 2., 2.]])
注意
v是求值的關(guān)鍵點,其在L操作和R操作中不同。對于L操作符,這個求值的關(guān)鍵點需要具有與輸出相同的形狀,而對于R操作符,該點應(yīng)具有與輸入相同的形狀參數(shù)。此外,這兩個操作的結(jié)果不同。L操作符的結(jié)果與輸入?yún)?shù)具有相同的形狀,而R操作符的結(jié)果具有與輸出相似的形狀。
R操作符
R操作符用于求值Jacobian和向量之間的乘積,即。該公式甚至可以推廣為x是一個矩陣、或者一個普通的張量,在這種情況下Jacobian變?yōu)閺埩坎⑶页朔e變?yōu)槟撤N張量的積。因為在實踐中,我們最終需要根據(jù)權(quán)重矩陣來計算這樣的表達式,所以Theano支持這種更通用的操作形式。為了求值表達式y相對于x的R操作,將Jacobian與v相乘,你需要做類似這樣的事情:
>>> W = T.dmatrix('W')
>>> V = T.dmatrix('V')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> JV = T.Rop(y, W, V)
>>> f = theano.function([W, V, x], JV)
>>> f([[1, 1], [1, 1]], [[2, 2], [2, 2]], [0,1])
array([ 2., 2.])
實現(xiàn)Rop的操作的列表。
L操作符
類似于R操作符,L操作符將計算行向量乘以Jacobian。數(shù)學(xué)公式是

>>> W = T.dmatrix('W')
>>> v = T.dvector('v')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> VJ = T.Lop(y, W, v)
>>> f = theano.function([v,x], VJ)
>>> f([2, 2], [0, 1])
array([[ 0., 0.],
[ 2., 2.]])
注意
v是求值的關(guān)鍵點,其在L操作和R操作中不同。對于L操作符,這個求值的關(guān)鍵點需要具有與輸出相同的形狀,而對于R操作符,該點應(yīng)具有與輸入相同的形狀參數(shù)。此外,這兩個操作的結(jié)果不同。L操作符的結(jié)果與輸入?yún)?shù)具有相同的形狀,而R操作符的結(jié)果具有與輸出相似的形狀。
Hessian乘以向量
如果你需要計算Hessian乘一個向量,你可以利用上面定義的操作符,它比實際計算精確的Hessian然后執(zhí)行乘積更有效率。由于Hessian矩陣的對稱性,你有兩個選擇將給你相同的結(jié)果,雖然這些選擇可能表現(xiàn)出不同的性能。因此,我們建議在使用以下兩種方法之前分析它們:
>>> x = T.dvector('x')
>>> v = T.dvector('v')
>>> y = T.sum(x ** 2)
>>> gy = T.grad(y, x)
>>> vH = T.grad(T.sum(gy * v), x)
>>> f = theano.function([x, v], vH)
>>> f([4, 4], [2, 2])
array([ 4., 4.])
或使用R操作符:
>>> x = T.dvector('x')
>>> v = T.dvector('v')
>>> y = T.sum(x ** 2)
>>> gy = T.grad(y, x)
>>> Hv = T.Rop(gy, x, v)
>>> f = theano.function([x, v], Hv)
>>> f([4, 4], [2, 2])
array([ 4., 4.])
最后的要點
-
grad函數(shù)以符號的方式工作:它接收并返回Theano變量。 -
grad可以與宏進行比較,因為它可以重復(fù)應(yīng)用。 - 標量costs只能由
grad直接處理。數(shù)組通過重復(fù)應(yīng)用來處理。 - 內(nèi)置函數(shù)使得高效地計算向量乘以Jacobian和向量乘以Hessian。
- 優(yōu)化工作還在進行中,包括有效計算完全Jacobian和Hessian矩陣以及Jacobian乘以向量。