日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

自動微分----pytorch中的梯度運算與反向傳播函數(shù)(預(yù)備知識)

作者:Gaolw1102 更新時間: 2022-09-25 編程語言

文章目錄

    • 自動微分
      • 一個簡單的例子
      • 非標(biāo)量變量的反向傳播
      • 分離計算
      • Python控制流的梯度計算
      • 小結(jié)

自動微分

正如微積分中所說,求導(dǎo)是幾乎所有深度學(xué)習(xí)優(yōu)化算法的關(guān)鍵步驟。 雖然求導(dǎo)的計算很簡單,只需要一些基本的微積分。 但對于復(fù)雜的模型,手工進(jìn)行更新是一件很痛苦的事情(而且經(jīng)常容易出錯)。

度學(xué)習(xí)框架通過自動計算導(dǎo)數(shù),即 自動微分(automatic differentiation) 來加快求導(dǎo)。 實際中,根據(jù)我們設(shè)計的模型,系統(tǒng)會構(gòu)建一個 計算圖(computational graph), 來跟蹤計算是哪些數(shù)據(jù)通過哪些操作組合起來產(chǎn)生輸出。 自動微分使系統(tǒng)能夠隨后反向傳播梯度。 這里,反向傳播(backpropagate) 意味著跟蹤整個計算圖,填充關(guān)于每個參數(shù)的偏導(dǎo)數(shù)。

重要理解 requires_grad 梯度參數(shù)、backward() 反向傳播函數(shù)、detach() 分離函數(shù)。

一個簡單的例子

作為一個演示例子,假設(shè)我們想對函數(shù) y = 2 x T x y = 2x^{T}x y=2xTx 關(guān)于列向量 x ? \vec{x} x 求導(dǎo)。 首先,我們創(chuàng)建變量x并為其分配一個初始值。

import torch

x = torch.arange(4.0)                                  #生成一個一維張量
x
tensor([0., 1., 2., 3.])

在我們計算 y y y 關(guān)于的梯度 x x x 之前,我們需要一個地方(grad)來存儲梯度。 重要的是,我們不會在每次對一個參數(shù)求導(dǎo)時都分配新的內(nèi)存。 因為我們經(jīng)常會成千上萬次地更新相同的參數(shù),每次都分配新的內(nèi)存可能很快就會將內(nèi)存耗盡。 注意,一個標(biāo)量函數(shù)關(guān)于向量 x x x 的梯度是向量,并且與 x x x 具有相同的形狀。

x.requires_grad_(True)             #為張量x設(shè)置梯度,用來存儲計算得出的x梯度向量,等同于torch.arange(4.0, requires_grad=True)
print(x.grad)                       #輸出x默認(rèn)的梯度值, 為None
None

現(xiàn)在讓我們計算 y y y

y = 2 * torch.dot(x.T, x)                             #計算y = 2 * (x.T與x的內(nèi)積),結(jié)果為一個標(biāo)量
y
tensor(28., grad_fn=<MulBackward0>)

x是一個長度為4的向量,計算x和x的點積,得到了我們賦值給y的標(biāo)量輸出。 接下來,我們通過調(diào)用**反向傳播函數(shù)(backward())**來自動計算y關(guān)于x每個分量的梯度,并打印這些梯度。

這里我覺得大家需要明白一個概念。梯度,也就是標(biāo)量向量內(nèi)的每個元素偏導(dǎo)數(shù)組成的向量

y.backward()                                          #調(diào)用反向傳播函數(shù),計算之前組成y結(jié)果的x向量梯度
x.grad                                                #輸出x向量梯度
tensor([ 0.,  4.,  8., 12.])

它的計算過程如下所示:

在這里插入圖片描述

其中,函數(shù) y = 2 x T x y = 2x^{T}x y=2xTx 關(guān)于 x x x 的梯度應(yīng)為 4 x 4x 4x (計算原理為二次型對向量的求導(dǎo))。 如下圖(詳情請閱讀矩陣求導(dǎo)知識):

在這里插入圖片描述

其中

y = 2 x T x = 2 x T E x = 2 ( E T x + E x ) = 4 x y = 2x^{T}x = 2x^{T}Ex = 2(E^{T}x + Ex) = 4x y=2xTx=2xTEx=2(ETx+Ex)=4x

讓我們快速驗證這個梯度是否計算正確。

x.grad == 4*x                                      #驗證反向傳播函數(shù)的偏導(dǎo)數(shù)計算是否正確
tensor([True, True, True, True])

現(xiàn)在讓我們計算x的另一個函數(shù)。

#默認(rèn)情況下,pytorch會累計梯度,我們需要清楚之前的值
x.grad.zero_()

y = x.sum()                  #此時的y = x_0 + x_1 + x_2 + x_3
y.backward()                 #反向傳播求x向量內(nèi)分量的偏導(dǎo)數(shù),即x關(guān)于y的梯度
x.grad                       #輸出x的梯度
tensor([1., 1., 1., 1.])

非標(biāo)量變量的反向傳播

當(dāng)y不是標(biāo)量時,向量y關(guān)于向量x的導(dǎo)數(shù)的最自然解釋是一個矩陣。 對于高階和高維的y和x,求導(dǎo)的結(jié)果可以是一個高階張量。

然而,雖然這些更奇特的對象確實出現(xiàn)在高級機器學(xué)習(xí)中(包括深度學(xué)習(xí)中), 但當(dāng)我們調(diào)用向量的反向計算時,我們通常會試圖計算一批訓(xùn)練樣本中每個組成部分的損失函數(shù)的導(dǎo)數(shù)。 這里,我們的目的不是計算微分矩陣,而是單獨計算批量中每個樣本的偏導(dǎo)數(shù)之和。

# 對非標(biāo)量調(diào)用backward需要傳入一個gradient參數(shù),該參數(shù)指定微分函數(shù)關(guān)于self的梯度。
# 在我們的例子中,我們只想求偏導(dǎo)數(shù)的和,所以傳遞一個1的梯度是合適的

x.grad.zero_()
y = x * x
# 等價于y.backward(torch.ones(len(x)))
y.sum().backward()                               #y.sum() = x_o的平方 + x_1的平方 + x_2的平方 + x_3的平方
x.grad                                           #其對x的偏導(dǎo)數(shù)分別為2x_0,2x_1,2x_2,2x_3, 梯度為(2x_0,2x_1,2x_2,2x_3)
tensor([0., 2., 4., 6.])

分離計算

有時,我們希望將某些計算移動到記錄的計算圖之外。 例如,假設(shè)y是作為x的函數(shù)計算的,而z則是作為y和x的函數(shù)計算的。 想象一下,我們想計算z關(guān)于x的梯度,但由于某種原因,我們希望將y視為一個常數(shù), 并且只考慮到x在y被計算后發(fā)揮的作用。

在這里,我們可以分離y來返回一個新變量u,該變量與y具有相同的值, 但丟棄計算圖中如何計算y的任何信息。 換句話說,梯度不會向后流經(jīng)u到x。 因此,下面的反向傳播函數(shù)計算z=ux關(guān)于x的偏導(dǎo)數(shù),同時將u作為常數(shù)處理, 而不是z=xx*x關(guān)于x的偏導(dǎo)數(shù)。

#將x之前的梯度進(jìn)行清空
x.grad.zero_()
y = x * x                      #y是關(guān)于自變量x的函數(shù)

u = y.detach()                 #detach()函數(shù),即分離。只是將y的值賦值給u,即u僅僅作為普通常數(shù),而不會把y是如何得來的計算圖賦值給u
u.requires_grad_(True)

z = u * x                      #表面上還是z = y * x,但此時的u和x未有任何聯(lián)系,可以無顧慮地進(jìn)行偏導(dǎo)數(shù)求解



#z.sum() = u * x = u0x0 +  u1x1 +  u2x2 +  u3x3, 對x的偏導(dǎo)數(shù)為(u0,u1,u2,u3), 對u的偏導(dǎo)數(shù)為(x0,x1,x2,x3)
#故x關(guān)于z的梯度為(0,1,4,9), u關(guān)于z的梯度為(0,1,2,3)

z.sum().backward()                                 #進(jìn)行反向傳播求出偏導(dǎo)數(shù)             
x.grad, u.grad, x.grad == u                        #分別輸出x與u的梯度,并判斷x的梯度是否等于y的值
(tensor([0., 1., 4., 9.]),
 tensor([0., 1., 2., 3.]),
 tensor([True, True, True, True]))

由于記錄了 y y y 的計算結(jié)果,我們可以隨后在 y y y 上調(diào)用反向傳播, 得到 y = x ? x y=x*x y=x?x 關(guān)于的 x x x 的導(dǎo)數(shù),即 2 x 2x 2x

x.grad.zero_()               #將x的梯度清0
y.sum().backward()           #其中, y.sum() = x0的平方 + x1的平方 + x2的平方 + x3的平方。計算出x的梯度為2(x0,x1,x2,x3)
x.grad == 2 * x              #判斷梯度是否正確
tensor([True, True, True, True])

Python控制流的梯度計算

使用自動微分的一個好處是: 即使構(gòu)建函數(shù)的計算圖需要通過Python控制流(例如,條件、循環(huán)或任意函數(shù)調(diào)用),我們仍然可以計算得到的變量的梯度。 在下面的代碼中,while循環(huán)的迭代次數(shù)和if語句的結(jié)果都取決于輸入a的值。

def f(a):
    b = a * 2
    while b.norm() < 1000:
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = b * 100
    return c

讓我們計算梯度。

a = torch.randn(size=(),requires_grad=True)                          #隨機生成一個符合正態(tài)分布的小數(shù)

d = f(a)                                                              #調(diào)用函數(shù)f,計算關(guān)于a的函數(shù)
d.backward()                                                          #調(diào)用反向傳播函數(shù)

我們現(xiàn)在可以分析上面定義的 f f f 函數(shù)。 請注意,它在其輸入 a a a 中是分段線性的。 換言之,對于任何 a a a ,存在某個常量標(biāo)量k,使得 f ( a ) = k a f(a)=ka f(a)=ka ,其中 k k k 的值取決于輸入 a a a 。 因此,我們可以用 d / a d/a d/a 驗證梯度是否正確。

a.grad, a.grad == d / a                                               #計算a的梯度
(tensor(1024.), tensor(True))

小結(jié)

深度學(xué)習(xí)框架可以自動計算導(dǎo)數(shù):

我們首先將梯度附加到想要對其計算偏導(dǎo)數(shù)的變量上,然后我們記錄目標(biāo)值的計算,執(zhí)行它的反向傳播函數(shù),并訪問得到的梯度。

原文鏈接:https://blog.csdn.net/weixin_43479947/article/details/126989990

欄目分類
最近更新