日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[转]一文解释PyTorch求导相关 (backward, autograd.grad)

發布時間:2024/9/18 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转]一文解释PyTorch求导相关 (backward, autograd.grad) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

PyTorch是動態圖,即計算圖的搭建和運算是同時的,隨時可以輸出結果;而TensorFlow是靜態圖。

在pytorch的計算圖里只有兩種元素:數據(tensor)和 運算(operation)

運算包括了:加減乘除、開方、冪指對、三角函數等可求導運算

數據可分為:葉子節點(leaf node)和非葉子節點;葉子節點是用戶創建的節點,不依賴其它節點;它們表現出來的區別在于反向傳播結束之后,非葉子節點的梯度會被釋放掉,只保留葉子節點的梯度,這樣就節省了內存。如果想要保留非葉子節點的梯度,可以使用retain_grad()方法。

torch.tensor 具有如下屬性:

  • 查看 是否可以求導 requires_grad
  • 查看 運算名稱 grad_fn
  • 查看 是否為葉子節點 is_leaf
  • 查看 導數值 grad

針對requires_grad屬性,自己定義的葉子節點默認為False,而非葉子節點默認為True,神經網絡中的權重默認為True。判斷哪些節點是True/False的一個原則就是從你需要求導的葉子節點到loss節點之間是一條可求導的通路。

當我們想要對某個Tensor變量求梯度時,需要先指定requires_grad屬性為True,指定方式主要有兩種:

x = torch.tensor(1.).requires_grad_() # 第一種x = torch.tensor(1., requires_grad=True) # 第二種

PyTorch提供兩種求梯度的方法:backward() and torch.autograd.grad() ,他們的區別在于前者是給葉子節點填充.grad字段,而后者是直接返回梯度給你,我會在后面舉例說明。還需要知道y.backward()其實等同于torch.autograd.backward(y)

一個簡單的求導例子是:y=(x+1)?(x+2)y=(x+1)*(x+2)y=(x+1)?(x+2),計算 ?y/?x\partial y /\partial x?y/?x ,假設給定 x=2x=2x=2, 先畫出計算圖:

手算的話,
?y?x=?y?a?a?x+?y?b?b?x=x+2+x+1=7\frac{\partial y}{\partial x}=\frac{\partial y}{\partial a} \frac{\partial a}{\partial x} + \frac{\partial y}{\partial b}\frac{\partial b}{\partial x} = x+2+x+1=7 ?x?y?=?a?y??x?a?+?b?y??x?b?=x+2+x+1=7

使用backward()

x = torch.tensor(2., requires_grad=True)a = torch.add(x, 1) b = torch.add(x, 2) y = torch.mul(a, b)y.backward() print(x.grad) >>>tensor(7.)

看一下這幾個tensor的屬性:

print("requires_grad: ", x.requires_grad, a.requires_grad, b.requires_grad, y.requires_grad) print("is_leaf: ", x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf) print("grad: ", x.grad, a.grad, b.grad, y.grad)>>>requires_grad: True True True True >>>is_leaf: True False False False >>>grad: tensor(7.) None None None

使用backward()函數反向傳播計算tensor的梯度時,并不計算所有tensor的梯度,而是只計算滿足這幾個條件的tensor的梯度:

  • 類型為葉子節點、
  • requires_grad=True
  • 依賴該tensor的所有tensor的requires_grad=True。
  • 所有滿足條件的變量梯度會自動保存到對應的grad屬性里。

    使用autograd.grad()

    x = torch.tensor(2., requires_grad=True)a = torch.add(x, 1) b = torch.add(x, 2) y = torch.mul(a, b)grad = torch.autograd.grad(outputs=y, inputs=x) print(grad[0]) >>>tensor(7.)

    因為指定了輸出y,輸入x,所以返回值就是?y/?x\partial y/\partial x?y/?x這一梯度,完整的返回值其實是一個元組,保留第一個元素就行,后面元素是?

    再舉一個復雜一點且高階求導的例子:z=x2yz=x^2yz=x2y,計算 ?z/?x,?z/?y,?2z/?x2\partial z/\partial x,\partial z/\partial y,\partial^2z/\partial x^2?z/?x,?z/?y,?2z/?x2 ,假設給定x=2,y=3x=2, y=3x=2,y=3

    手算的話:
    ?z?x=2xy→12,?z?y=x2→4,?2z?x2=2y→6\frac{\partial z}{\partial x}=2xy \to12,\frac{\partial z}{\partial y}=x^2 \to 4,\frac{\partial^2z}{\partial x^2}=2y \to 6 ?x?z?=2xy12,?y?z?=x24,?x2?2z?=2y6
    求一階導可以用backward().

    x = torch.tensor(2., requires_grad=True) y = torch.tensor(3., requires_grad=True)z = x * x * yz.backward() print(x.grad, y.grad) >>>tensor(12.) tensor(4.)

    也可以用autograd.grad()

    x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x) print(grad_x[0]) # grad_y = torch.autograd.grad(outputs=z, inputs=y) 無法對y進行求導了 >>>tensor(12.)

    為什么不在這里面同時也求對y的導數呢?因為無論是backward還是autograd.grad在計算一次梯度后圖就被釋放了,如果想要保留,需要添加retain_graph=True

    x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x, retain_graph=True) grad_y = torch.autograd.grad(outputs=z, inputs=y)print(grad_x[0], grad_y[0]) >>>tensor(12.) tensor(4.)

    再來看如何求高階導,理論上其實是上面的grad_x再對x求梯度,試一下看:

    x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * ygrad_x = torch.autograd.grad(outputs=z, inputs=x, retain_graph=True) grad_xx = torch.autograd.grad(outputs=grad_x, inputs=x)print(grad_xx[0]) >>>RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

    報錯了,雖然retain_graph=True保留了計算圖和中間變量梯度, 但沒有保存grad_x的運算方式,需要使用creat_graph=True在保留原圖的基礎上再建立額外的求導計算圖,也就是會把?z/?x=2xy\partial z/\partial x=2xy?z/?x=2xy這樣的運算存下來。

    grad_xx這里也可以直接用backward(),相當于直接從?z/?x=2xy\partial z/\partial x=2xy?z/?x=2xy開始回傳:

    # autograd.grad() + backward() x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * ygrad = torch.autograd.grad(outputs=z, inputs=x, create_graph=True) grad[0].backward()print(x.grad) >>>tensor(6.)

    也可以先用backward()然后對x.grad這個一階導繼續求導:

    # backward() + autograd.grad() x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True) grad_xx = torch.autograd.grad(outputs=x.grad, inputs=x)print(grad_xx[0]) >>>tensor(6.)

    那是不是也可以直接用兩次backward()呢?第二次直接x.grad從開始回傳,我們試一下:

    # backward() + backward() x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True) # x.grad = 12 x.grad.backward()print(x.grad) >>>tensor(18., grad_fn=<CopyBackwards>)

    發現了問題,結果不是6,而是18,發現第一次回傳時輸出x梯度是12。這是因為PyTorch使用backward()時默認會累加梯度,也就是12+6=18,需要手動把前一次的梯度清零:

    x = torch.tensor(2.).requires_grad_() y = torch.tensor(3.).requires_grad_()z = x * x * yz.backward(create_graph=True) x.grad.data.zero_() x.grad.backward()print(x.grad) >>>tensor(6., grad_fn=<CopyBackwards>)

    有沒有發現前面都是對標量求導,如果不是標量會怎么樣呢?

    x = torch.tensor([1.,2.]).requires_grad_() y=x+1 y.backward() print(x.grad) >>>RuntimeError: grad can be implicitly created only for scalar outputs

    報錯了,因為只能標量對標量,標量對向量求梯度,xxx可以是標量或者向量,但yyy只能是標量;所以只需要先將$$y轉變為標量,對分別求導沒影響的就是求和。

    此時,
    x=[x1,x2],y=[x12,x22],y′=y.sum()=x12+x22,?y′?x1=2x1→2,?y′?x2=2x2→4x=[x_1,x_2],y=[x_1^2, x_2^2],y\prime=y.sum()=x_1^2+x_2^2, \\ \frac{\partial y\prime}{\partial x_1}=2x_1 \to 2,\frac{\partial y\prime}{\partial x_2}=2x_2 \to 4 x=[x1?,x2?]y=[x12?,x22?]y=y.sum()=x12?+x22?,?x1??y?=2x1?2?x2??y?=2x2?4

    x = torch.tensor([1., 2.]).requires_grad_() y = x * xy.sum().backward() print(x.grad) >>>tensor([2., 4.])

    再具體一點來解釋,讓我們寫出求導計算的雅可比矩陣,y=[y1,y2]\boldsymbol y=[y_1,y_2]y=[y1?,y2?]是一個向量,
    J=[?y?x1,?y?x2]=[?y1?x1?y1?x2?y2?x1?y2?x2]\boldsymbol J=[\frac{\partial \boldsymbol y}{\partial x_1},\frac{\partial \boldsymbol y}{\partial x_2}]=\begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} \\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} \end{bmatrix} J=[?x1??y?,?x2??y?]=[?x1??y1???x1??y2????x2??y1???x2??y2???]
    而我們希望最終的求導結果是[?y1?x1,?y2?x2][\frac{\partial y_1}{\partial x_1}, \frac{\partial y_2}{\partial x_2}][?x1??y1??,?x2??y2??],那怎么得到呢?注意?y1?x2\frac{\partial y_1}{\partial x_2}?x2??y1???y2?x1\frac{\partial y_2}{\partial x_1}?x1??y2??都是0,那是不是
    [?y1?x1,?y2?x2]T=[?y1?x1?y1?x2?y2?x1?y2?x2][11][\frac{\partial y_1}{\partial x_1}, \frac{\partial y_2}{\partial x_2}]^\mathsf{T}=\begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} \\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} \end{bmatrix}\begin{bmatrix} 1 \\ 1 \end{bmatrix} [?x1??y1??,?x2??y2??]T=[?x1??y1???x1??y2????x2??y1???x2??y2???][11?]
    所以不用y.sum()的另一種方式是:

    x = torch.tensor([1., 2.]).requires_grad_() y = x * xy.backward(torch.ones_like(x)) print(x.grad) >>>tensor([2., 4.])

    也可以使用autograd。上面和這里的torch.ones_like(x) 位置指的就是雅可比矩陣右乘的那個向量。

    x = torch.tensor([1., 2.]).requires_grad_() y = x * xgrad_x = torch.autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(x)) print(grad_x[0]) >>>tensor([2., 4.])

    或者

    x = torch.tensor([1., 2.]).requires_grad_() y = x * xgrad_x = torch.autograd.grad(outputs=y.sum(), inputs=x) print(grad_x[0]) >>>tensor([2., 4.])

    下面是著重強調以及引申的幾點

    • 梯度清零
      Pytorch 的自動求導梯度不會自動清零,會累積,所以一次反向傳播后需要手動清零。
      x.grad.zero_()
      而在神經網絡中,我們只需要執行optimizer.zero_grad()
    • 使用detach()切斷,不會再往后計算梯度
      假設有模型A和模型B,我們需要將A的輸出作為B的輸入,但訓練時我們只訓練模型B,那么可以這樣做input_B = output_A.detach()
      如果還是以前面的為例子,將a切斷,將只有b一條通路,且a變為葉子節點。x = torch.tensor([2.], requires_grad=True)a = torch.add(x, 1).detach() b = torch.add(x, 2) y = torch.mul(a, b)y.backward()print("requires_grad: ", x.requires_grad, a.requires_grad, b.requires_grad, y.requires_grad) print("is_leaf: ", x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf) print("grad: ", x.grad, a.grad, b.grad, y.grad)>>>requires_grad: True False True True >>>is_leaf: True True False False >>>grad: tensor([3.]) None None None

    總結

    以上是生活随笔為你收集整理的[转]一文解释PyTorch求导相关 (backward, autograd.grad)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。