文章目錄
- 1 導數拓展
- 1.1 標量導數
- 1.2 梯度:向量的導數
- 1.3 擴展到矩陣
- 1.4 鏈式法則
- 2 自動求導
- 2.1 計算圖
- 2.2 正向模式
- 2.3 反向模式
- 3 實戰:自動求導
- 3.1 簡單示例
- 3.2 非標量的反向傳播
- 3.3 分離計算
- 3.4 Python 控制流
硬件配置:
- Windows 11
- Intel?Core?i7-12700H
- NVIDIA GeForce RTX 3070 Ti Laptop GPU
軟件環境:
- Pycharm 2025.1
- Python 3.12.9
- Pytorch 2.6.0+cu124
1 導數拓展
1.1 標量導數
基本公式
- 常數: d ( a ) / d x = 0 d(a)/dx = 0 d(a)/dx=0
- 冪函數: d ( x n ) / d x = n ? x n ? 1 d(x^n)/dx = n·x^{n-1} d(xn)/dx=n?xn?1
- 指數/對數:
- d ( e x ) / d x = e x d(e^x)/dx = e^x d(ex)/dx=ex
- d ( ln ? x ) / d x = 1 / x d(\ln x)/dx = 1/x d(lnx)/dx=1/x
- 三角函數:
- d ( sin ? x ) / d x = cos ? x d(\sin x)/dx = \cos x d(sinx)/dx=cosx
- d ( cos ? x ) / d x = ? sin ? x d(\cos x)/dx = -\sin x d(cosx)/dx=?sinx

求導法則
d ( u + v ) d x = d u d x + d v d x d ( u v ) d x = u d v d x + v d u d x d f ( g ( x ) ) d x = f ′ ( g ( x ) ) ? g ′ ( x ) \begin{aligned}&\frac{d(u+v)}{dx}=\frac{du}{dx}+\frac{dv}{dx}\\&\frac{d(uv)}{dx}=u\frac{dv}{dx}+v\frac{du}{dx}\\&\frac{df(g(x))}{dx}=f^{\prime}(g(x))\cdotp g^{\prime}(x)\end{aligned} ?dxd(u+v)?=dxdu?+dxdv?dxd(uv)?=udxdv?+vdxdu?dxdf(g(x))?=f′(g(x))?g′(x)?
不可微函數的導數:亞導數
- ∣ x ∣ |x| ∣x∣ 在 x = 0 x=0 x=0 時的亞導數: [ ? 1 , 1 ] [-1,1] [?1,1] 區間任意值。
- ReLU 函數:
max(0,x)
在 x = 0 x=0 x=0 時導數可取 [ 0 , 1 ] [0,1] [0,1]。

1.2 梯度:向量的導數
形狀匹配規則
函數類型 | 自變量類型 | 導數形狀 | 示例 |
---|---|---|---|
標量 y y y | 標量 x x x | 標量 | d y / d x = 2 x dy/dx = 2x dy/dx=2x |
標量 y y y | 向量 x \mathbf{x} x | 行向量 | d y / d x = [ 2 x 1 , 4 x 2 ] dy/d\mathbf{x} = [2x_1,4x_2] dy/dx=[2x1?,4x2?] |
向量 y \mathbf{y} y | 標量 x x x | 列向量 | d y / d x = [ cos ? x , ? sin ? x ] T d\mathbf{y}/dx = [\cos x, -\sin x]^T dy/dx=[cosx,?sinx]T |
向量 y \mathbf{y} y | 向量 x \mathbf{x} x | 雅可比矩陣 | d y / d x = [ [ 1 , 0 ] , [ 0 , 1 ] ] d\mathbf{y}/d\mathbf{x} = [[1,0],[0,1]] dy/dx=[[1,0],[0,1]] |

案例 1
- y y y: x 1 2 + 2 x 2 2 x_1^2 + 2x_2^2 x12?+2x22?(第一個元素的平方與第二個元素平方的 2 倍之和)
- x \mathbf{x} x:向量。
d y d x = [ 2 x 1 , 4 x 2 ] \frac{dy}{d\mathbf{x}}=\begin{bmatrix}2x_1,&4x_2\end{bmatrix} dxdy?=[2x1?,?4x2??]
- 幾何解釋:梯度向量 [2, 4] 指向函數值增長最快方向。

-
其他情況
案例 2
- y \mathbf{y} y:向量。
- x x x:標量。

案例 3
- y \mathbf{y} y:向量。
- x \mathbf{x} x:向量。

- 其他情況

1.3 擴展到矩陣

1.4 鏈式法則
標量鏈式法則的向量化
? 當 y = f ( u ) , u = g ( x ) y = f(u), u = g(x) y=f(u),u=g(x) 時:
d y d x = d y d u ? d u d x \frac{dy}{d\mathbf{x}}=\frac{dy}{du}\cdot\frac{du}{d\mathbf{x}} dxdy?=dudy??dxdu?
- d y / d u dy/du dy/du:標量 → 形狀不變
- d u / d x du/dx du/dx:若 u u u 是向量, x x x 是向量 → 雅可比矩陣(形狀 [ d i m ( u ) , d i m ( x ) ] [dim(u), dim(x)] [dim(u),dim(x)])

多變量鏈式法則
d z d w = d z d b ? d b d a ? d a d w = 2 b ? 1 ? x T \frac{dz}{d\mathbf{w}}=\frac{dz}{db}\cdot\frac{db}{da}\cdot\frac{da}{d\mathbf{w}}=2b\cdot1\cdot\mathbf{x}^T dwdz?=dbdz??dadb??dwda?=2b?1?xT
- 示例:線性回歸 z = ( x T w ? y ) 2 z = (x^Tw - y)^2 z=(xTw?y)2 的梯度計算

2 自動求導
? 自動求導計算一個函數在指定值上的導數,它有別于
- 符號求導
- 數值求導

2.1 計算圖
構建原理
-
將代碼分解成操作子
-
將計算表示成一個無換圖
-
節點:輸入變量(如 x , w , y x,w,y x,w,y)或基本操作(如 + , ? , × +,-,× +,?,×)
-
邊:數據流向
-

顯式 vs 隱式構造
類型 | 代表框架 | 特點 |
---|---|---|
顯式 | TensorFlow,Mxnet,Theano | 先定義計算圖,后喂入數據 |
隱式 | PyTorch,Mxnet | 動態構建圖,操作即記錄 |

2.2 正向模式
? 從輸入到輸出逐層計算梯度,每次計算一個輸入變量對輸出的梯度,通過鏈式法則逐層傳遞梯度。
? 以 z = ( x ? w ? y ) 2 z = (x \cdot w - y)^2 z=(x?w?y)2 為例(線性回歸損失函數):
# 正向計算過程
a = x * w # a對x的梯度:?a/?x = w
b = a - y # b對a的梯度:?b/?a = 1
z = b ** 2 # z對b的梯度:?z/?b = 2b
-
特點:每次只能計算一個輸入變量(如
x
或w
)的梯度,需多次計算。 -
計算復雜度:
O(n)
(n
為輸入維度) -
內存復雜度:
O(1)
(不需要存儲中間結果) -
適用場景:輸入維度低(如參數少)、輸出維度高的函數。
2.3 反向模式
? 從輸出到輸入反向傳播梯度,一次性計算所有輸入變量對輸出的梯度。
數學原理
-
前向計算
計算所有中間值(
a,b,z
)并存儲。 -
反向傳播(Back Propagation,也稱反向傳遞)
從輸出
z
開始,按鏈式法則逐層回傳梯度。先計算
?z/?b = 2b
,再計算?b/?a = 1
,最后計算?a/?x = w
。

? 同樣以 z = ( x ? w ? y ) 2 z = (x \cdot w - y)^2 z=(x?w?y)2 為例:
-
前向計算
a = x * w # 存儲 a b = a - y # 存儲 b z = b ** 2
-
反向傳播
dz_db = 2 * b # ?z/?b db_da = 1 # ?b/?a da_dx = w # ?a/?x dz_dx = dz_db * db_da * da_dx # 最終梯度

- 計算復雜度:
O(n)
(與正向模式相同) - 內存復雜度:
O(n)
(需存儲所有中間變量) - 適用場景:深度學習(輸入維度高,輸出為標量損失函數)。
3 實戰:自動求導
3.1 簡單示例
? 以函數 y = 2 x ? x y=2\mathbf{x}^{\top}\mathbf{x} y=2x?x 為例,關于列向量 x \mathbf{x} x 求導。
-
首先,創建變量
x
并為其分配一個初始值。import torchx = torch.arange(4.0) x
-
在計算 y y y 關于 x \mathbf{x} x 的梯度之前,需要一個地方來存儲梯度。
我們不會在每次對一個參數求導時都分配新的內存。
因為我們經常會成千上萬次地更新相同的參數,每次都分配新的內存可能很快就會將內存耗盡。
注意,一個標量函數關于向量 x \mathbf{x} x 的梯度是向量,并且與 x \mathbf{x} x 具有相同的形狀。
x.requires_grad_(True) # 等價于x=torch.arange(4.0,requires_grad=True) x.grad # 默認值是None
-
現在計算 y y y。
y = 2 * torch.dot(x, x) y
-
x
是一個長度為 4 的向量,計算x
和x
的點積,得到了我們賦值給y
的標量輸出。
接下來,通過調用反向傳播函數來自動計算y
關于x
每個分量的梯度,并打印這些梯度。y.backward() x.grad
-
函數 y = 2 x ? x y=2\mathbf{x}^{\top}\mathbf{x} y=2x?x 關于 x \mathbf{x} x 的梯度應為 4 x 4\mathbf{x} 4x。讓我們快速驗證這個梯度是否計算正確。
x.grad == 4 * x
-
探究
x
的另一個函數。# 在默認情況下,PyTorch會累積梯度,我們需要清除之前的值 x.grad.zero_() y = x.sum() y.backward() x.grad
3.2 非標量的反向傳播
? 當y
不是標量時,向量y
關于向量x
的導數的最自然解釋是一個矩陣。
? 對于高階和高維的y
和x
,求導的結果可以是一個高階張量。
? 雖然這些更奇特的對象確實出現在高級機器學習中(包括[深度學習中]),但當調用向量的反向計算時,我們通常會試圖計算一批訓練樣本中每個組成部分的損失函數的導數。
? 這里,我們的目的不是計算微分矩陣,而是單獨計算批量中每個樣本的偏導數之和。
# 對非標量調用backward需要傳入一個gradient參數,該參數指定微分函數關于self的梯度。
# 本例只想求偏導數的和,所以傳遞一個1的梯度是合適的
x.grad.zero_()
y = x * x
# 等價于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

理解
x.grad.zero_()
作用:清空
x
的梯度(grad
)緩存。為什么需要清零?
- PyTorch 會累積梯度(
grad
),如果之前已經計算過x
的梯度(比如在循環中多次backward()
),新的梯度會加到舊的梯度上。- 調用
zero_()
可以避免梯度累積,確保每次計算都是新的梯度。
y = x \* x
計算
y = x2
(逐元素相乘)。例如:
- 如果
x = [1, 2, 3]
,那么y = [1, 4, 9]
。
y.backward(torch.ones(len(x)))
backward()
的作用:計算y
對x
的梯度(即dy/dx
)。- 為什么需要
gradient
參數?
- 如果
y
是 標量(單個值),可以直接調用y.backward()
,PyTorch 會自動計算dy/dx
。- 但如果
y
是 非標量(向量/矩陣),PyTorch 不知道如何計算梯度,必須傳入一個gradient
參數(形狀和y
相同),表示y
的梯度權重。gradient=torch.ones(len(x))
的含義:
- 這里
gradient
是一個全 1 的張量,表示我們希望計算y
的所有分量對x
的 梯度之和(相當于sum(y)
對x
的梯度)。- 數學上:
y = [y?, y?, y?] = [x?2, x?2, x?2]
sum(y) = x?2 + x?2 + x?2
d(sum(y))/dx = [2x?, 2x?, 2x?]
(這就是x.grad
的結果)
結果
x.grad
由于
y = x2
,dy/dx = 2x
。由于
gradient=torch.ones(len(x))
,PyTorch 計算的是sum(y)
的梯度:
x.grad = [2x?, 2x?, 2x?]
(即2 * x
)。例如:
- 如果
x = [1, 2, 3]
,那么x.grad = [2, 4, 6]
。
3.3 分離計算
? 有時,我們希望將某些計算移動到記錄的計算圖之外。例如,假設y
是作為x
的函數計算的,而z
則是作為y
和x
的函數計算的。
? 想象一下,我們想計算z
關于x
的梯度,但由于某種原因,希望將y
視為一個常數,并且只考慮到x
在y
被計算后發揮的作用。這里可以分離y
來返回一個新變量u
,該變量與y
具有相同的值,但丟棄計算圖中如何計算y
的任何信息?
? 換句話說,梯度不會向后流經u
到x
。因此,下面的反向傳播函數計算z = u * x
關于x
的偏導數,同時將u
作為常數處理,而不是z = x * x * x
關于x
的偏導數。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * xz.sum().backward()
x.grad == u

? 由于記錄了y
的計算結果,我們可以隨后在y
上調用反向傳播,得到y = x * x
關于的x
的導數,即2 * x
。
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x

3.4 Python 控制流
? 使用自動微分的一個好處是:即使構建函數的計算圖需要通過 Python 控制流(例如,條件、循環或任意函數調用),我們仍然可以計算得到的變量的梯度。
? 在下面的代碼中,while
循環的迭代次數和if
語句的結果都取決于輸入a
的值。
def f(a):# type: (torch.Tensor)->torch.Tensorb = a * 2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn c
? 讓我們計算梯度。
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
? 我們現在可以分析上面定義的f
函數。請注意,它在其輸入a
中是分段線性的。換言之,對于任何a
,存在某個常量標量k
,使得f(a)=k*a
,其中k
的值取決于輸入a
,因此可以用d/a
驗證梯度是否正確。
a.grad, d / a, a.grad == d / a
