本博文經過幾個獨立的例子介紹了 PyTorch
的基礎概念。node
其核心,PyTorch 提供了兩個主要的特徵:python
咱們將使用三階多項式去擬合 y=sin(x)
的問題做爲咱們的例子。神經網絡將會有 4 個參數,而且將使用梯度降低經過最小化(minimizing)網絡輸出和真實輸出的歐氏距離(Euclidean distance)去擬合隨機數據。git
在介紹 PyTorch 以前,咱們首先使用 NumPy 實現一個神經網絡。github
NumPy 提供了一種 n-維數組(n-dimensional)數組對象,和許多操縱這些數組的函數。NumPy 對於科學計算是一個充滿活力的框架;它不須要了解任何關於計算圖(computation graphs)、深度學習或梯度。可是,咱們能夠輕鬆地使用 NumPy 操做手動實現網絡的前向傳播和反向傳播,擬合一個三階多項式到 sine 函數。算法
import numpy as np import math # 建立隨機輸入和輸出的數據 x = np.linspace(-math.pi, math.pi, 2000) y = np.sin(x) # 隨機初始化權重(weight) a = np.random.randn() b = np.random.randn() c = np.random.randn() d = np.random.randn() learning_rate = 1e-6 for t in range(2000): # 前向傳播:計算預測的 y # y = a + bx + cx^2 + dx^3 y_pred = a + b * c + c * x ** 2 + d * x ** 3 # 計算打印損失值(loss) loss = np.square(y_pred - y).sum() 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() # 更新權重參數(weight) 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} + {b} x + {c} x^2 + {d} x^3')
99 880.579689608281 199 854.0555627713447 299 835.0649579500803 399 821.369172352883 499 811.4200797717988 599 804.1424592009078 699 798.7835679283971 799 794.8123966679943 899 791.8516135436988 999 789.6312092332105 1099 787.9566727255892 1199 786.6869765834631 1299 785.7192368208233 1399 784.9779487653068 1499 784.4073829648856 1599 783.9661786053822 1699 783.6234753797376 1799 783.3561293941161 1899 783.146697690572 1999 782.9819710274254 Result: y = -0.05099192206877935 + 5.075189949472816 x + 0.004669889031269278 x^2 + 0.028076619049389115 x^3
儘管 NumPy 是一個很是棒的框架,可是它不能夠利用 GPU 去加速數值計算。對於現代深度神經網絡,GPU 經過提供了 50倍或以上 的加速,不幸地是 NumPy 對於深度學習是還不夠的。數組
這裏咱們介紹了 PyTorch 最基礎的概念:Tensor 。一個 PyTorch Tensor 從概念上與 NumPy 數組是徹底相同的:Tensor 是一個 n 維數組(n-dimensional array),而且 PyTorch 提供了許多處理這些 Tensor 的函數。幕後,Tensor 記錄了一個計算圖(computation graph)和梯度,而且這些對於科學計算也是一個很是實用並充滿活力的工具。緩存
不像 NumPy,PyTorch Tensor 能夠利用 GPU 加速數值計算。爲了將 PyTorch Tensor 運行在 GPU 上,你只須要簡單地指定正確的設備。安全
這裏咱們使用 PyTorch Tensor 去擬合一個三階多項式到 sine 函數。就像上面 NumPy 的例子,咱們須要手動實現網絡的前向傳播和反向傳播。網絡
import torch import math dtype = torch.float device = torch.device("cpu") # 下面這條註釋,可使用 GPU # device = torch.device("cuda:0") # 建立隨機輸入輸出 x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x) # 隨機初始化權重(weight) 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) learnign_rate = 1e-6 for t in range(2000): # 前向傳播:計算預測的 y y_pred = a + b * c + 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() # 使用梯度降低更新權重(weight) a -= learnign_rate * grad_a b -= learnign_rate * grad_b c -= learnign_rate * grad_c d -= learnign_rate * grad_d print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')
99 883.4296875 199 846.044921875 299 823.0543823242188 399 808.742919921875 499 799.6961059570312 599 793.8943481445312 699 790.1231079101562 799 787.6404418945312 899 785.9865112304688 999 784.872314453125 1099 784.113525390625 1199 783.5916748046875 1299 783.2293090820312 1399 782.9754028320312 1499 782.7958984375 1599 782.6681518554688 1699 782.5762939453125 1799 782.509765625 1899 782.4613647460938 1999 782.4259033203125 Result: y = 0.015941178426146507 + 2.6255147457122803 x + -0.0018874391680583358 x^2 + 0.028076613321900368 x^3
在上面的例子中,咱們必須手動實現神經網絡的前向傳播和反向傳播。app
對於一個小的兩層網絡手動實現反向傳播並無什麼大不了的,可是對於更大更復雜的網絡是一件很是可怕的事情。
幸好的是,咱們可使用 自動微分(automatic differentiation) 自動化神經網絡的反向傳播的計算。在 PyTorch 的 autograd 包正好提供了這個功能。當使用 autograd 時,神經網絡的前向傳播將定義一個 計算圖(Computational graph) ,圖中的結點(nodes)將會是一個 Tensor,每條邊(edges)將會是從一個輸入 Tensor 產生輸出 Tensor 的函數。反向傳播經過計算圖讓咱們輕鬆地計算梯度。
這聽起來有些複雜,可是其實是至關簡單的。每個 Tensor 表明了計算圖中的結點。若是 x
是一個 Tensor,它就有 x.requires_grad=True
而後 x.grad
就是另外一個張量,其持有 x
關於某個標量值的梯度。
這裏咱們使用 PyTorch Tensor 和 autograd 實現使用一個三階多項式去擬合 sine
曲線的例子;如今,咱們再也不須要手動地實現網絡的反向傳播。
import torch import math dtype = torch.float device = torch.device("cpu") # 下面這條註釋,可使用 GPU # device = torch.device("cuda:0") # 建立 Tensor # 默認狀況下,requires_grad=False 表示咱們不須要計算關於這些 Tensor 的梯度 x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x) # 建立隨機權重(weight) Tensor # 對於一個三階多項式,咱們須要 4 個參數 # 設置 requires_grad=True 代表咱們在反向傳播的時候想要計算關於這些 Tensor 的梯度 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) learnign_rate = 1e-6 for t in range(2000): # 使用 Tensor 上的運算前向傳播計算預測的 y y_pred = a + b * c + c * x ** 2 + d * x ** 3 # 使用 Tensor 上的操做計算並打印損失值(loss) # 如今,loss 是一個 Tensor,shape 是 (1,) # loss.item() 獲得 loss 持有的標量值 loss = (y_pred - y).pow(2).sum() if t % 100 == 99: print(t, loss.item()) # 使用 autograd 計算反向傳播。下面這個調用將會計算 loss 關於全部 requires_grad=True 的 Tensor 的梯度。 # 在此以後,調用 a.grad, b.grad, c.grad, d.grad 將獲得 a, b, c, d 關於 loss 的梯度 loss.backward() # 使用梯度降低手動更新權重。使用 torch.no_grad() 包起來。 # 由於這些權重(weight)都有 requires_grad=True 可是在 autograd 中,咱們不須要跟蹤這些操做。 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')
99 943.6873168945312 199 903.8673095703125 299 873.939453125 399 851.4177856445312 499 834.4534301757812 599 821.6660766601562 699 812.0220336914062 799 804.74560546875 899 799.2537841796875 999 795.108154296875 1099 791.9780883789062 1199 789.6143798828125 1299 787.8292846679688 1399 786.4810791015625 1499 785.4628295898438 1599 784.693603515625 1699 784.1126708984375 1799 783.6737060546875 1899 783.34228515625 1999 783.091796875 Result: y = 0.032735299319028854 + 0.6361034512519836 x + -0.005412467289716005 x^2 + 0.028076613321900368 x^3
在底層,每個原始自動求導(autograd)運算符其實是兩個操做在 Tensor 上的函數。前向傳播(forward) 函數計算出從輸入 Tensor 到輸出 Tensor。反向傳播(backward) 函數收到輸出 Tensor 關於某個標量值的梯度,而且計算輸入 Tensor 關於那些相同標量值的梯度。
在 PyTorch 中咱們能夠輕鬆地定義咱們本身的自動求導運算符,一個 torch.autograd.Function
的子類而且實現 forward
和 backward
函數。而後咱們可使用咱們新定義的自動求導運算符,構建一個類實例(instance)而後像函數樣調用它,傳入輸入數據的 Tensor。
在這個例子中,咱們定義咱們的模型爲 \(y=a+b P_3(c+dx)\) 而不是 \(y=a+bx+cx^2+dx^3\),其中 \(P_3(x)=\frac{1}{2}\left(5x^3-3x\right)\) 是一個 3 次(degree)Legendre 多項式。對於計算 \(P_3\) 的前向傳播和反向傳播,咱們寫下自定義的自動求導函數,並用它實現咱們的模型。
import torch import math class LegendrePolynomial3(torch.autograd.Function): """ 咱們能夠經過繼承 torch.autograd.Function 實現咱們自定義自動求導函數, 並實現操做在 Tensor 上的前向傳播和反向傳播。 """ @staticmethod def forward(ctx, input): """ 在前向傳播中,咱們接受一個包含輸入的 Tensor 並返回包含輸出的 Tensor。 ctx 是一個上下文(context)對象,用來存放反向傳播計算時用到的信息。 你可使用 ctx.save_for_backward 方法緩存任意對象以供反向傳播使用。 """ ctx.save_for_backward(input) return 0.5 * (5 * input ** 3 - 3 * input) @staticmethod def backward(ctx, grad_output): """ 在反向傳播中,咱們接收一個張量,其持有 loss 關於輸出的梯度, 而且咱們須要計算 loss 關於輸入的梯度。 """ input, = ctx.saved_tensors return grad_output * 1.5 * (5 * input ** 2 - 1) dtype = torch.float device = torch.device("cpu") # device = torch.device("cuda:0") # 取消註釋可使用 GPU # 建立輸入輸出 Tensor。 # 默認狀況下,requires_grad=False 代表咱們在反向傳播時不須要計算這些 Tensor 的梯度。 x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype) y = torch.sin(x) # 建立隨機權重(weight)Tensor。在這個例子,咱們須要 4 個權重: # y = a + b * P3(c + d * x) 爲保證收斂,這些權重須要被初始化成離正確結果不太遠。 # 設置 requires_grad=True 代表咱們在反向傳播期間想要計算關於這些 Tensor 的梯度。 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): # 爲了應用(apply)咱們的函數,咱們使用 Function.apply # 並將這個函數取個別名 P3 P3 = LegendrePolynomial3.apply # 前向傳播:使用操做(operations)計算預測的 y。 # 咱們使用咱們自定義的 autograd 操做計算 P3。 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)')
99 209.95834350585938 199 144.66018676757812 299 100.70249938964844 399 71.03519439697266 499 50.97850799560547 599 37.403133392333984 699 28.206867218017578 799 21.973188400268555 899 17.7457275390625 999 14.877889633178711 1099 12.931766510009766 1199 11.610918045043945 1299 10.714258193969727 1399 10.10548210144043 1499 9.692106246948242 1599 9.411375045776367 1699 9.220745086669922 1799 9.091285705566406 1899 9.003361701965332 1999 8.943639755249023 Result: y = -5.423830273798558e-09 + -2.208526849746704 * P3(1.3320399228078372e-09 + 0.2554861009120941 x)
對於定義一個複雜的運算符和自動微分,計算圖和 autograd 是一個很是強大的範例(paradigm),可是對於一個很大的神經網絡來講,原生的(raw)autograd 就有一點低級(low-level)了。
當構建神經網絡的時候,咱們常常考慮將這些計算安排整理到一個 層(layers) 中,在學習期間,一些 可學習參數(learnable parameters) 將會被優化。
在 TensorFlow 中,像 Keras, TensorFlow-Slim 和 TFLearn 包在原生計算圖之上提供了更高階的抽象,這很是有益於構建神經網絡。
在 PyTorch 中,nn
包服務於相同的目的。nn
包定義了一套 Modules,大體上等價於神經網絡中的層(layers)。一個 Module 接受輸入 Tensor 並計算輸出 Tensor,也許也會持有內部的狀態(state),好比包含可學習的參數的(learnable parameters)Tensor。當訓練神經網絡時,nn
包也定了一套損失函數(loss function)。
在這個例子中,咱們使用 nn
包實現咱們的多項式模型。
import torch import math # 建立輸入輸出 Tensor x = torch.linspace(-math.pi, math.pi, 2000) y = torch.sin(x) # 在這個例子中,輸出 y 是一個(x,x^2,x^3)的線性函數,因此 # 咱們能夠考慮它是一個線性(linear layer)神經網絡層。 # 讓咱們準備 Tensor(x,x^2,x^3) p = torch.tensor([1, 2, 3]) xx = x.unsqueeze(-1).pow(p) # 在上面的代碼中,x.unsqueeze(-1) 有着 (2000,1)的形狀(shape),而且 p 有着 (3,)的形狀 # 對於這個例子,廣播語義(broadcasting semantics),獲得一個 (2000,3)的 Tensor。 # 使用 nn 包定義咱們一系列的層的模型。nn.Sequential 是一個 Module,其包含其它 Modules # 並按順序應用它們產生輸出。線性 Modules 從輸入使用一個線性函數計算輸出 # 並在內部持有模型的 weight 和 bias 的 Tensor。Flatten layer 展開線性層的輸出到 # 一個匹配 y 的形狀(shape)的一維(1D)的 Tensor。 model = torch.nn.Sequential( torch.nn.Linear(3, 1), torch.nn.Flatten(0, 1) ) # nn 包一樣包含流行的損失函數(loss function)的定義;在這個例子中, # 咱們將使用均方偏差(Mean Square Error——MSE)做爲咱們的損失函數。 loss_fn = torch.nn.MSELoss(reduction='sum') learning_rate = 1e-6 for t in range(2000): # 前向傳播:經過傳入 x 到 model 計算預測的 y。Module 對象重寫了 # __call__ 函數,所咱們能夠就像調用函數同樣調用他們。當你這麼作的時候 # 傳入輸入 Tensor 到 Module 而後它計算產生輸出的 Tensor。 y_pred = model(xx) # 計算並打印損失值(loss)。咱們傳入 y 的預測值和真實值的 Tensor, # 以後 loss function 返回損失值(loss)的 Tensor。 loss = loss_fn(y_pred, y) if t % 100 == 99: print(t, loss.item()) # 運行反向傳播以前清零一下梯度 model.zero_grad() # 反向傳播:計算 loss 關於全部 model 的可學習參數的梯度。從底層上來講, # 每個 requires_grad=True 的 Module 的參數(parameters)都被存儲在一個 Tensor 中, # 因此下面這個調用將計算 model 中全部可學習參數的的梯度。 loss.backward() # 使用梯度降低更新權重。每個參數都是一個 Tensor, # 因此咱們就像以前同樣獲得它的梯度。 with torch.no_grad(): for param in model.parameters(): param -= learning_rate * param.grad # 你也能夠就像獲得列表(list)的第一個元素同樣,獲得 model 的第一層 linear_layer = model[0] # 對於 linear layer,它的參數被存儲爲 weight 和 bias。 print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + {linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')
99 672.3364868164062 199 448.7064208984375 299 300.4986572265625 399 202.26097106933594 499 137.13494873046875 599 93.9523696899414 699 65.31460571289062 799 46.318885803222656 899 33.71611785888672 999 25.35308074951172 1099 19.802162170410156 1199 16.116811752319336 1299 13.669382095336914 1399 12.043644905090332 1499 10.963366508483887 1599 10.245325088500977 1699 9.7678804397583 1799 9.450323104858398 1899 9.23902416229248 1999 9.098373413085938 Result: y = -0.006202241405844688 + 0.8414672017097473 x + 0.0010699888225644827 x^2 + -0.09115784615278244 x^3
到目前爲止,咱們經過 torch.no_grad()
手動更改持有可學習參數的 Tensor 來更新了咱們模型的權重。這對於一些簡單的優化算法,好比隨機梯度降低(stochastic gradient descent),並非一個太大的負擔,可是實際上,咱們常用更復雜的優化器(Optimizer),好比 AdaGrad,RMSprop,Adam等,訓練神經網絡。
PyTorch 裏的 optim
包抽象了一個優化算法的思想,而且提供了經常使用的優化算法的實現。
在這個例子,咱們依舊使用 nn
包定義咱們的模型,可是咱們將使用 optim
包提供的 RMSprop 算法優化模型。
import torch import math # 建立輸入輸出的 Tensor。 x = torch.linspace(-math.pi, math.pi, 2000) y = torch.sin(x) # 準備輸入的 Tensor(x,x^2,x^3)。 p = torch.tensor([1, 2, 3]) xx = x.unsqueeze(-1).pow(p) # 使用 nn 包定義咱們的模型和損失函數。 model = torch.nn.Sequential( torch.nn.Linear(3, 1), torch.nn.Flatten(0, 1) ) loss_fn = torch.nn.MSELoss(reduction='sum') # 使用 optim 包定義一個將會爲咱們更新模型的權重(weight)的優化器(Optimizer)。 # 這裏咱們將使用 RMSprop,optim 包包含了許多其它優化算法。 # RMSprop 構造器的第一個參數是告訴優化器哪些 Tensor 應該被更新。 learning_rate = 1e-3 optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate) for t in range(2000): # 前向傳播:傳入 x 到 model 計算預測的 y y_pred = model(xx) # 計算並打印損失值(loss) loss = loss_fn(y_pred, y) if t % 100 == 99: print(t, loss.item()) # 在反向傳播以前,須要使用優化器(optimizer)對象清零全部將被更新的變量的梯度 # (模型的可學習參數(learnable weight))。這是由於在默認狀況下,不論什麼時候 .backward() 被調用時, # 梯度會被累積在緩衝區(換言之,不會被覆蓋)。能夠經過 torch.autograd.backward 的官方文檔查看更多細節。 optimizer.zero_grad() # 反向傳播:計算 loss 關於模型參數的梯度。 loss.backward() # 調用優化器上的 step 函數,更新它的參數。 optimizer.step() linear_layer = model[0] print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + {linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')
99 1610.013671875 199 844.4555053710938 299 525.6090698242188 399 334.3529968261719 499 213.2096405029297 599 135.09706115722656 699 83.0303955078125 799 48.23293685913086 899 26.453231811523438 999 14.763155937194824 1099 10.078741073608398 1199 9.046186447143555 1299 8.941937446594238 1399 8.894529342651367 1499 8.899309158325195 1599 8.912175178527832 1699 8.913202285766602 1799 8.911144256591797 1899 8.92212200164795 1999 8.92563247680664 Result: y = -0.0005531301139853895 + 0.8562383651733398 x + -0.0005647626821883023 x^2 + -0.0938328206539154 x^3
某些時候,你想要指定的模型比 Modules 存在的順序(sequence)模型還要複雜,在這種狀況下,你能夠經過繼承 nn.Module
的子類而且定義一個 forward
函數,這個函數使用其它 Modules 或者其它 autograd 操做符,接收輸入 Tensor 並計算輸出 Tensor。
在這個例子中,咱們實現咱們的三階多項式做爲定製的 Module 的子類。
import torch import math class Polynomial3(torch.nn.Module): def __init__(self): """ 在這個構造器,咱們實例化四個參數並賦它們爲成員 parameters。 """ super().__init__() self.a = torch.nn.Parameter(torch.randn(())) self.b = torch.nn.Parameter(torch.randn(())) self.c = torch.nn.Parameter(torch.randn(())) self.d = torch.nn.Parameter(torch.randn(())) def forward(self, x): """ 在前向傳播函數,咱們接收一個輸入數據的 Tensor,而且咱們必須返回輸出數據的 Tensor。 咱們可使用定義在構造器的 Modules 以及任意的操做在 Tensor 上的運算符。 """ return self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3 def string(self): """ 就像 Python 中的任意一個類同樣,你也能夠隨便定義任何的方法(method)在 PyTorch Modules中。 """ return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3' # 建立輸入輸出的 Tensor。 x = torch.linspace(-math.pi, math.pi, 2000) y = torch.sin(x) # 實例化上面定義的類,構造咱們的模型。 model = Polynomial3() # 構造咱們的損失函數(loss function)和一個優化器(Optimizer)。在 SGD 構造器裏 # 調用 model.parameters(),構造器將包含 nn.Linear Module 的可學習的參數(learnable parameters) # 其是模型(model)的成員變量。 criterion = torch.nn.MSELoss(reduce='sum') optimizer = torch.optim.SGD(model.parameters(), lr=1e-6) for t in range(2000): # 前向傳播:傳入 x 到模型計算預測的 y y_pred = model(x) # 計算並打印損失值(loss) loss = criterion(y_pred, y) if t % 100 == 99: print(t, loss.item()) # 清零梯度,執行反向傳播,更新參數。 optimizer.zero_grad() loss.backward() optimizer.step() print(f'Result: {model.string()}')
99 62.18498229980469 199 58.930118560791016 299 55.8530387878418 399 52.94403076171875 499 50.19393539428711 599 47.59403991699219 699 45.136138916015625 799 42.812469482421875 899 40.61569595336914 999 38.538875579833984 1099 36.575462341308594 1199 34.71923828125 1299 32.964359283447266 1399 31.305295944213867 1499 29.736791610717773 1599 28.2539005279541 1699 26.85195541381836 1799 25.52651023864746 1899 24.273393630981445 1999 23.08867073059082 Result: y = -1.397053837776184 + -0.8716074824333191 x + 0.35939672589302063 x^2 + -0.23259805142879486 x^3
做爲一個動態圖和權重共享的例子,咱們實現一個很是強大的模型:一個 3-5 階的多項式,在前向傳播時選擇一個 3-5 之間的隨機數,而且使用許多階,屢次重複使用相同的權重計算第四階和第五階。
對於這個模型,咱們可使用典型的 Python 控制流實現循環,而且當定義前向傳播時,咱們能夠簡單地複用相同的參數屢次實現權重共享。
咱們能夠繼承 Module 類輕鬆地實現這個模型。
import random import torch import math class DynamicNet(torch.nn.Module): def __init__(self): """ 在這個構造器中,咱們實例化五個參數而且將它們賦值給成員變量。 """ super().__init__() self.a = torch.nn.Parameter(torch.randn(())) self.b = torch.nn.Parameter(torch.randn(())) self.c = torch.nn.Parameter(torch.randn(())) self.d = torch.nn.Parameter(torch.randn(())) self.e = torch.nn.Parameter(torch.randn(())) def forward(self, x): """ 對於模型的前向傳播,咱們隨機選擇 4 或 5 並複用參數 e 計算這些階的貢獻(contribution)。 由於每一次前向傳播都構建了一個動態的計算圖,當定義模型的前向傳播時, 咱們可使用常規的 Python 控制流操做符,像循環或條件語句。 這裏咱們也看到了當定義一個計算圖時重複使用相同的參數屢次是至關安全。 """ y = self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3 for exp in range(4, random.randint(4, 6)): y = y + self.e * x ** exp return y def string(self): """ 就像 Python 中的任意一個類同樣,你也能夠隨便定義任何的方法(method)在 PyTorch Modules中。 """ return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3 + {self.e.item()} x^4 ? + {self.e.item()} x^5 ?' # 建立持有輸入輸出的 Tensor。 x = torch.linspace(-math.pi, math.pi, 2000) y = torch.sin(x) # 經過實例化上面定義的類,構造咱們的模型。 model = DynamicNet() # 構造咱們的損失函數(loss fcuntion)和一個優化器(Optimizer)。使用毫無特點的 # 隨機梯度降低(stochastic gradient descent)訓練這個強大的模型是艱難的, # 因此咱們使用動量(momentum)。 criterion = torch.nn.MSELoss(reduce='sum') optimizer = torch.optim.SGD(model.parameters(), lr=1e-8, momentum=0.9) for t in range(30000): # 前向傳播:傳入 x 到 model 計算預測的 y。 y_pred = model(x) # 計算並打印損失值(loss) loss = criterion(y_pred, y) if t % 2000 == 1999: print(t, loss.item()) # 清零梯度、執行反向傳播、更新參數 optimizer.zero_grad() loss.backward() optimizer.step() print(f'Result: {model.string()}')
1999 31.825870513916016 3999 30.36163330078125 5999 28.165559768676758 7999 4.4947919845581055 9999 25.11688804626465 11999 4.422863960266113 13999 22.777265548706055 15999 21.440027236938477 17999 20.374134063720703 19999 19.437679290771484 21999 18.513486862182617 23999 17.685436248779297 25999 16.829214096069336 27999 16.081615447998047 29999 15.38708782196045 Result: y = 0.5208832621574402 + -2.605482578277588 x + 0.06938754767179489 x^2 + 0.6473004221916199 x^3 + -0.033068109303712845 x^4 ? + -0.033068109303712845 x^5 ?