幾個例子幫你梳理PyTorch知識點(張量、autograd)
theme: v-green
持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第32天,點選檢視活動詳情
因為我最近想學Pytorch lightning,重構一下之前的程式碼,所以回來梳理一下Pytorch的語法,好進行下一步學習,所以從頭重新回顧一下Pytorch。這個文章是通過幾個簡單例子幫大家回顧一下Pytorch一些重點基礎概念。
Pytorch有兩個重要的特徵:
- 使用n維張量進行運算,可以使用GPU加速計算。
- 使用自動微分構建、訓練神經網路
從$sin(x)$開始
Tensor和Numpy的用法差不多,但是Tensor可是使用進行加速計算,這比CPU計算要快50倍甚至更多。
更多區別可以看這個:PyTorch的Tensor這麼簡單,你還用不明白嗎? - 掘金 (juejin.cn)
我們知道,在基礎的迴歸問題中:給定一些資料,符合一定的分佈,我們要建立一個神經網路去擬合這個分佈,神經網路學習出來的表示式就作為我們資料分佈的表示式。
這裡我們用一個三次多項式$y=a+bx+cx^2+dx^3$來擬合$sin(x)$,訓練過程使用隨機梯度下降進行訓練,通過計算最小化預測值和真實值之間的歐氏距離來擬合隨機資料。
程式碼解釋見程式碼中的註釋部分。
```py import torch import math
dtype = torch.float device = torch.device("cpu")
下邊這行程式碼可以用也可以不用,註釋掉就是在CPU上執行
device = torch.device("cuda:0")
建立輸入輸出資料,這裡是x和y代表[-π,π]之間的sin(x)的值
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x)
隨機初始化權重
a = torch.randn((), device=device, dtype=dtype) b = torch.randn((), device=device, dtype=dtype) c = torch.randn((), device=device, dtype=dtype) d = torch.randn((), device=device, dtype=dtype)
learning_rate = 1e-6 for t in range(2000): # 前向過程,計算y的預測值 y_pred = a + b * x + c * x 2 + d * x 3
# 計算預測值和真實值的loss
loss = (y_pred - y).pow(2).sum().item()
if t % 100 == 99:
print(t, loss)
# 反向過程計算 a, b, c, d 關於 loss 的梯度
grad_y_pred = 2.0 * (y_pred - y)
grad_a = grad_y_pred.sum()
grad_b = (grad_y_pred * x).sum()
grad_c = (grad_y_pred * x ** 2).sum()
grad_d = (grad_y_pred * x ** 3).sum()
# 使用梯度下降更新引數
a -= learning_rate * grad_a
b -= learning_rate * grad_b
c -= learning_rate * grad_c
d -= learning_rate * grad_d
print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3') ``` 結果如下:
如果換成勒讓德多項式呢?
之前講過深扒torch.autograd原理 - 掘金 (juejin.cn)
這裡我們再淺淺簡述一下autograd。
在上邊的例子裡,我們是手動實現了神經網路的前向和反向傳播過程,因為這只是一個簡單的兩層網路,所以現實來也不是很困難,但是放對於一些大的複雜網路,要手動實現整個前向和反向過程就是非常困難的事情了。
現在我們可以使用pytorch提供autograd
包去自動求導,自動計算神經網路的反向過程。當我們使用autograd
的時候,神經網路前向過程就是定義一個計算圖,計算圖上的節點都是張量,邊是從輸入到輸出的計算函式。用計算圖進行反向傳播可以輕鬆計算梯度。
雖然聽起來很複雜,但是用起來很簡單。每個張量都代表計算圖上的一個節點,如果x
是一個張量,並且你設定好了x.requires_grad=True
,那x.grad
就是另一個儲存x
關於某些標量的梯度的張量。
然後我們繼續使用三次多項式來擬合我們的sin(x),但是現在我們就可以不用手動實現反向傳播的過程了。
```py import torch import math
dtype = torch.float device = torch.device("cpu")
下邊這行程式碼可以用也可以不用,註釋掉就是在CPU上執行
device = torch.device("cuda:0")
建立輸入輸出資料,這裡是x和y代表[-π,π]之間的sin(x)的值
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x)
隨機初始化權重
注意這裡我們設定了requires_grad=True,讓autograd自動跟蹤計算圖的梯度計算
a = torch.randn((), device=device, dtype=dtype, requires_grad=True) b = torch.randn((), device=device, dtype=dtype, requires_grad=True) c = torch.randn((), device=device, dtype=dtype, requires_grad=True) d = torch.randn((), device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6 for t in range(2000):
# 前向過程,計算y的預測值
y_pred = a + b * x + c * x ** 2 + d * x ** 3
# 計算預測值和真實值的loss
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# 使用autograd計算反向過程,呼叫之後會計算所有設定了requires_grad=True的張量的梯度
# 呼叫之後 a.grad, b.grad. c.grad d.grad 會儲存 abcd關於loss的梯度
loss.backward()
# 使用梯度下降更新引數
# 因為權重設定了requires_grad=True,但是在梯度更新這裡我們不需要跟蹤梯度,所以加上with torch.no_grad()
with torch.no_grad():
a -= learning_rate * a.grad
b -= learning_rate * b.grad
c -= learning_rate * c.grad
d -= learning_rate * d.grad
# 更新之後將氣度清零,以便下一輪運算,不清零的話它會一直累計
a.grad = None
b.grad = None
c.grad = None
d.grad = None
print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3') ```
在pytorch這種autograd的情況下,每個基礎的的autograd操作只是兩個作用於張量的方法。
forward
:由輸入張量計算輸出張量backward
:接收輸出張量相對於某個標量值的梯度,並計算輸入張量相對於相同標量值的梯度。
在pytorch中我們可以定義我們自己的autograd操作,你只需要實現一個torch.autograd.Function
的子類,寫好forward
和backward
函式即可。構造好新的autograd之後我們就可以像呼叫其他函式一樣呼叫它,將輸入張量傳遞進去即可。
比如我們不用$y=a+bx+cx^2+dx^3$了,改成一個三次勒讓德多項式(Legendre polynomial),形式為$y = a+bP_3(c+dx)$,其中$P_3(x) = \frac 1 2 (5x^3-3x)$。
```py import torch import math
class LegendrePolynomial3(torch.autograd.Function): def forward(ctx, input):
# 在前向過程中我們接受一個輸入張量,並返回一個輸出張量
# ctx是一個上下文物件,用於儲存反向過程的內容
# 你可以用save_for_backward方法快取任意在反向計算過程中要用的物件。
ctx.save_for_backward(input)
return 0.5 * (5 * input ** 3 - 3 * input)
def backward(ctx, grad_output):
# 在反向過程中,我們接受一個張量包含了損失關於輸出的梯度,我們需要計算損失關於輸入的梯度。
input, = ctx.saved_tensors
return grad_output * 1.5 * (5 * input ** 2 - 1)
dtype = torch.float device = torch.device("cpu")
下邊這行程式碼可以用也可以不用,註釋掉就是在CPU上執行
device = torch.device("cuda:0")
建立輸入輸出資料,這裡是x和y代表[-π,π]之間的sin(x)的值
x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x)
隨機初始化權重
注意這裡我們設定了requires_grad=True,讓autograd自動跟蹤計算圖的梯度計算
a = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True) b = torch.full((), -1.0, device=device, dtype=dtype, requires_grad=True) c = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True) d = torch.full((), 0.3, device=device, dtype=dtype, requires_grad=True)
learning_rate = 5e-6 for t in range(2000): # 我們給我們自定義的autograd起個名叫P3,然後用Function.apply方法呼叫 P3 = LegendrePolynomial3.apply
# 前向過程計算y,用的是我們自定義的P3的autograd
y_pred = a + b * P3(c + d * x)
# 計算並輸出loss
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# 使用autograd計算反向過程
loss.backward()
# 使用梯度下降更新權重
with torch.no_grad():
a -= learning_rate * a.grad
b -= learning_rate * b.grad
c -= learning_rate * c.grad
d -= learning_rate * d.grad
# 在下一輪更新之前將梯度清零,否則會一直累計
a.grad = None
b.grad = None
c.grad = None
d.grad = None
print(f'Result: y = {a.item()} + {b.item()} * P3({c.item()} + {d.item()} x)') ```
- 【翻譯】最近興起的擴散模型
- 深扒torch.autograd原理
- 用一個影象分類例項拿捏Pytorch使用方法
- 帶你瞭解自然語言處理文字生成方向
- 你們居然還在用負樣本做對比學習?
- 4個例子幫你梳理PyTorch的nn module
- 幾個例子幫你梳理PyTorch知識點(張量、autograd)
- SimCSE,丹琦女神把對比學習用到了NLP中了!
- 兩個視角給你解讀 熵、交叉熵、KL散度
- 你們那種對比學習是辣雞,我SwAV今天就要開啟新局面!
- “軍備競賽”時期的對比學習
- 怎麼用PyTorch實現一個Encoder-Decoder框架?
- 公式程式碼都有了,速來學LSTM 長短期記憶網路
- 【翻譯】圖解自注意力機制
- 用pytorch寫個RNN 迴圈神經網路
- 用pytorch寫個 GRU 門控迴圈單元
- 手把手教你實現 seq2seq
- 突然火起來的diffusion model是什麼?
- TensorBoard 一行程式碼實現指標視覺化
- 【翻譯】圖解transformer