天天看點

第二課.PyTorch入門什麼是PyTorchPytorch張量的基本操作CUDA tensor實驗一:用Numpy實作兩層神經網絡實驗二:用Pytorch完成實驗一模型定義進階

什麼是PyTorch

Pytorch是一個基于Python的科學計算庫,類似于Numpy,但可以使用GPU,可靈活開展深度學習實驗,其資料結構tensor類似于ndarray,但tensor可以在GPU上加速運算

GPU版本Pytorch安裝

GPU版本Pytorch的快速下載下傳安裝(比如cudatoolkit=10.0版本):

conda install cudatoolkit=10.0
conda install pytorch torchvision -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch
           

pytorch的導入為:

import torch
torch.__version__ #pytorch的版本号
           

Pytorch張量的基本操作

tensor的聲明

#構造一個未初始化的5x3矩陣
torch.empty(5,3)

#構造一個随機初始化的5x3矩陣
torch.rand(5,3)#值在0到1
torch.randn(5,3)#标準正态分布,值在-1到1

#構造一個全零矩陣,數值類型為long(int64)
torch.zeros(5,3,dtype=torch.long)
#或者簡寫為
torch.zeros(5,3).long()

#直接從資料建構tensor
torch.tensor([1.0,2.0])
           

tensor的複刻

從一個已有的tensor建構新tensor,這樣做的意義在于重用原來tensor的特征,例如資料類型,如果要更改資料類型,也可以通過關鍵字參數指定

#資料類型的複刻
x=torch.tensor([6,6],dtype=torch.double)
x_new=x.new_zeros(5,3) #資料類型也是float64
x_new=x.new_ones(5,3,dtype=torch.float) #資料類型改為float32

#tensor形狀的複刻
x=torch.randn(6,6,dtype=torch.float)
x_new=torch.ones_like(x,dtype=torch.int32) #_like代表新tensor形狀相同

#檢視形狀
x_new.shape
#等價于
x_new.size()
           

tensor的運算

tensor的運算方式很多,但無非基于加減乘除,卷積,矩陣等基本操作,下面将以加法操作為例子

x=torch.rand([5,3])
y=torch.rand([5,3])

#加法
x+y
#等價于
torch.add(x,y)
           

實際上,寫x+y時,會自動調用方法torch.add();

in-place操作

另一種加法操作基于in-place,in-place看似不起眼,但是不注意使用會導緻各種各樣的漏洞

in-place的實際案例:

#in-place案例
# 用 id() 這個函數,其傳回值是對象的記憶體位址
# 情景 1
a = torch.tensor([3.0, 1.0])
print(id(a)) # 2112716404344
a = a.exp()
print(id(a)) # 2112715008904
#在這個過程中a.exp()生成了一個新的對象,然後再讓a指向它的位址,是以這不是個 inplace 操作

# 情景 2
a = torch.tensor([3.0, 1.0])
print(id(a)) # 2112716403840
a[0] = 10
print(id(a), a) # 2112716403840 tensor([10.,  1.])
# inplace 操作,記憶體位址沒變
           

in-place加法:

x=torch.rand([5,3])
y=torch.rand([5,3])

#in-place加法,為了節省空間,結果儲存在原有對象中
print(y)
#in-place操作都有一個下劃線結尾,一旦使用in-place,原對象必然會被改變
y.add_(x)
print(y)#加法結果儲存在y内

###########################################
# i += 1 與 i = i+1 的差別
'''
對于Python數值對象,不存在in-place
'''
i = 0
print(id(i))  # 1564271360
i += 1
print(id(i))  # 1564271376
i = i + 1
print(id(i))  # 1564271392


'''
對于ndarray或是tensor,i+=1屬于inplace,i=i+1不屬于inplace
'''
import numpy as np

arr=np.array(6)
print(id(arr))

arr+=1
print(id(arr))

arr=arr+1
print(id(arr))

"""
140163988691504
140163988691504
140163988636272
"""
           

tensor索引和變形

Pytorch的索引與Numpy一緻,切片也是左閉右開(Python魔法方法__getitem__中定義的規則)

print(x)
print(x[:,1:]) #取所有行,取第一列到最後一列
"""
tensor([[0.6817, 0.5885, 0.4934],
        [0.4521, 0.7160, 0.6664],
        [0.4955, 0.6965, 0.0801],
        [0.5151, 0.2415, 0.4981],
        [0.2836, 0.0920, 0.2456]])
tensor([[0.5885, 0.4934],
        [0.7160, 0.6664],
        [0.6965, 0.0801],
        [0.2415, 0.4981],
        [0.0920, 0.2456]])
"""
           

Resizing或者Reshape,在pytorch中是view

x=torch.randn(4,4)
print(x.shape)
y=x.view(16)
print(y.shape)

#指定某個次元為-1,則該次元會自動被計算出形狀
z=x.view(2,-1)
print(z.shape)

"""
torch.Size([4, 4])
torch.Size([16])
torch.Size([2, 8])
"""
           

如果tensor内部隻有一個元素,使用item()可以将value取出作為Python的數值

x[1,1]
#>tensor(0.1198)
x[1,1].item()
#>0.11984860897064209
           

補充:如果dir(tensor對象),得到張量内有兩個重要對象:data和grad;

data是張量本身,gard是張量的梯度:

x=torch.randn(1)
x.data
x.grad
           

交換次元

tensor.transpose(dim0,dim1)交換次元0和次元1,是以,對于一個張量x(x是3維及以上的張量),x.transpose(0,2)等價于x.transpose(2,0)

x=torch.randn(5,3,2)
print(x.shape)

x_t=x.transpose(0,2)
print(x_t.shape)

"""
torch.Size([5, 3, 2])
torch.Size([2, 3, 5])
"""
           

ndarray和tensor類型轉換

tensor預設在CPU上,在程式運作中,tensor将和ndarray共享記憶體,是以,CPU上的TorchTensor與NumpyNdarray之間的轉換很容易

tensor轉ndarray:

a=torch.ones(5)#tensor([1., 1., 1., 1., 1.])
#轉為array
b=a.numpy()#array([1., 1., 1., 1., 1.], dtype=float32)

#共享記憶體
b[1]=6
print(a)#tensor([1., 6., 1., 1., 1.])
           

ndarray轉tensor:

import numpy as np
import torch

a=np.ones(5)
b=torch.from_numpy(a)#生成tensor,指派到b,b和a共享記憶體

#in-place操作
np.add(a,1,out=a)
"""
如果寫a=a+1
b将還是tensor([1., 1., 1., 1., 1.]),如果寫a=a+1,out并沒有儲存到原本的a對象,實際上是重新定義了對象,隻是名字還叫a
"""
print(b)
#tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
#對a操作,改變了b,再次驗證array與CPU的tensor共享記憶體
           

CUDA tensor

所謂CUDA tensor是指布置在GPU上的張量,GPU可以加速張量的計算,首先判斷GPU是否可以使用:

#判斷GPU是否可用
torch.cuda.is_available()
           

tensor預設在CPU上,現在将tensor遷移到GPU:

#把tensor布置到GPU上(tensor預設都是在CPU上)
x=torch.randn(5,3)

if torch.cuda.is_available():
    device_gpu = torch.device("cuda")
    y = torch.ones_like(x, device=device_gpu)

    # 也可以直接用.to部署到gpu
    x = x.to(device_gpu)

    # z将在GPU上完成運算,對于小資料沒有感覺到不同,對于大資料,會感受到明顯的計算速度差異
    z = x + y
    print(z)

	# 如果要将tensor轉為array,必須先從GPU搬運到CPU
    # 現在把z轉回CPU
    device_cpu = torch.device("cpu")
    z = z.to(device=device_cpu, dtype=torch.double)
    print(z)

    z = z.numpy()
    print(z)
           

Pytorch的tensor布置到GPU需要一步一步完成,為了化簡步驟,可以先定義模型,然後直接:

molde=model.cuda()

,自動将模型布置到GPU上;

對于設定某個張量的遷移,一般推薦使用

.to()

方法,可以具體選擇要哪一個GPU

實驗一:用Numpy實作兩層神經網絡

要求:全連接配接網絡,激活函數為ReLU,權重不加bias,L2 loss,完全用Numpy實作前向和反向計算;

  • h = W 1 X h=W_1X h=W1​X
  • a = m a x ( 0 , h ) a=max(0,h) a=max(0,h)
  • y h a t = W 2 a y_{hat}=W_2a yhat​=W2​a

計算流程:

  • forward pass
  • loss
  • backward pass

ndarray隻是一個普通的n維array,它内部沒有任何DL相關的對象(比如梯度),也不知道計算圖,ndarray隻是一個用于數學計算的資料結構

首先,熟悉numpy的一些操作,在np.後的方法,操作對象為每一個元素(可廣播),比如:

np.maximum(y,0)

,y是一個6x3矩陣,maximum會将0廣播到每個元素,傳回比較結果,傳回的依然是6x3矩陣;

np.square(y_pred-y)

會取對應位置的每個元素求square,最後傳回的結果還是6x3矩陣:

# 關于Numpy的操作
# 在np.後的方法,操作對象為每一個元素(可廣播)

import numpy as np

y=np.random.randn(6,3)
y_pred=np.random.randn(6,3)

print(y)
print(np.maximum(y,0))

temp=np.square(y_pred-y)

temp.sum(),temp.mean()
"""
[[ 0.46866875  1.53064624  0.61860204]
 [-1.81656938 -1.01940481  0.79725882]
 [-1.03559533  0.78828324  1.17177584]
 [ 2.10932798  0.08326097 -0.34045735]
 [ 1.97852545 -0.4410186   0.4620433 ]
 [-0.39703326 -1.46239008  0.35678806]]
[[0.46866875 1.53064624 0.61860204]
 [0.         0.         0.79725882]
 [0.         0.78828324 1.17177584]
 [2.10932798 0.08326097 0.        ]
 [1.97852545 0.         0.4620433 ]
 [0.         0.         0.35678806]]

(67.80287390030654, 3.7668263277948077)
"""
           

假設這個神經網絡輸入為1000,中間層輸出為100,最後輸出結果是10維,訓練的樣本為64,學習率設定1e-6:

import numpy as np

N=64 #樣本數
D_in=1000
H=100 #中間層輸出
D_out=10 #最後一層輸出

# 随機建立訓練資料
x=np.random.randn(N,D_in)
y=np.random.randn(N,D_out)

# 随機初始權重
w1=np.random.randn(D_in,H)
w2=np.random.randn(H,D_out)

learning_rate=1e-6
           

一共疊代500次(由于每次都是使用整個資料集更新,是以一次就是一個epoch),每次都需要前向傳播,計算loss,反向傳播梯度,更新權重:

第二課.PyTorch入門什麼是PyTorchPytorch張量的基本操作CUDA tensor實驗一:用Numpy實作兩層神經網絡實驗二:用Pytorch完成實驗一模型定義進階

對于前向傳播,按照要求為:

#Forward Pass
    h=x.dot(w1) # x乘w1->N*H
    h_relu=np.maximum(h,0) #N*H
    y_pred=h_relu.dot(w2) # h_relu乘w2->N*D_out
           

然後計算loss:

#compute loss,假設用 MSE loss
    loss=np.square(y_pred-y).sum() 
    #np.square(y_pred-y)為N*D_out,通過sum()對ndarray求和獲得loss值(是一個數值)
           

然後反向傳播梯度,目标是更新權重,是以最終需要的是權重的梯度:grad_w1,grad_w2;

求權重的梯度有公式:

第二課.PyTorch入門什麼是PyTorchPytorch張量的基本操作CUDA tensor實驗一:用Numpy實作兩層神經網絡實驗二:用Pytorch完成實驗一模型定義進階

可以看出,要想知道權重的梯度必須知道前向計算時的激活值a,和反向傳播時的下一神經元的激活前的z的梯度,求z的梯度才是展現反向傳播的關鍵,比如本實驗的grad_h_relu的計算:

g r a d _ h _ r e l u = g r a d _ y _ p r e d . d o t ( W 2 . T ) grad\_h\_relu=grad\_y\_pred.dot(W_2.T) grad_h_relu=grad_y_pred.dot(W2​.T)

W 2 . T W_2.T W2​.T是 W 2 W_2 W2​的轉置;

除此之外,梯度流經激活函數ReLU時,需要乘0或者1(相乘操作來自求導鍊式法則),即正向計算中,h大于0,相乘的導數是1,否則為0,可以大緻寫一個流程:

grad_h[h<0]=0;

grad_h[h>=0]=1*grad_h_relu;

但是實際實作時不要直接指派,因為數組直接指派,兩者共享記憶體,最好進行深拷貝以避免不必要的錯誤;

于是這個網絡的反向傳播為:

#Backward Pass
    #compute gradient
    grad_y_pred=2.0*(y_pred-y)
    grad_w2=h_relu.T.dot(grad_y_pred)
    grad_h_relu=grad_y_pred.dot(w2.T) #N*D_out乘D_out*H->N*H
    #深拷貝:拷貝到父子對象級别,前後完全是兩個不相關的對象,以避免科學計算出現錯誤
    grad_h=grad_h_relu.copy()
    #Numpy的快速資料檢索
    grad_h[h<0]=0
    grad_w1=x.T.dot(grad_h) #D_in*N乘N*H->D_in*H
           

最後更新權重:

#update weights of w1 and w2
    w1=w1-learning_rate*grad_w1
    w2=w2-learning_rate*grad_w2
           

完整的過程如下:

import numpy as np

N=64 #樣本數
D_in=1000
H=100 #中間層輸出
D_out=10 #最後一層輸出

# 随機建立訓練資料
x=np.random.randn(N,D_in)
y=np.random.randn(N,D_out)

# 随機初始權重
w1=np.random.randn(D_in,H)
w2=np.random.randn(H,D_out)

learning_rate=1e-6

for ite in range(500):
    #Forward Pass
    h=x.dot(w1) # x乘w1->N*H
    h_relu=np.maximum(h,0) #N*H
    y_pred=h_relu.dot(w2) # h_relu乘w2->N*D_out
    
    #compute loss,假設用 MSE loss
    loss=np.square(y_pred-y).sum() #np.square(y_pred-y)為N*D_out,通過sum()對ndarray求和獲得loss值(是一個數值)
    print(ite,loss)
    
    #Backward Pass
    #compute gradient
    grad_y_pred=2.0*(y_pred-y)
    grad_w2=h_relu.T.dot(grad_y_pred)
    grad_h_relu=grad_y_pred.dot(w2.T) #N*D_out乘D_out*H->N*H
    #深拷貝:拷貝到父子對象級别,前後完全是兩個不相關的對象,以避免科學計算出現錯誤
    grad_h=grad_h_relu.copy()
    #Numpy的快速資料檢索
    grad_h[h<0]=0
    grad_w1=x.T.dot(grad_h) #D_in*N乘N*H->D_in*H
    
    #update weights of w1 and w2
    w1=w1-learning_rate*grad_w1
    w2=w2-learning_rate*grad_w2
           

使用numpy實作神經網絡有助于充分了解forward pass和backward pass的整個過程

實驗二:用Pytorch完成實驗一

資料從ndarray更換到tensor;

矩陣乘法在np中是.dot,在pytorch中是.mm(即matrix multiplication);

torch的clamp()比numpy的maximum()更好,clamp類似鉗位電路,可設定min鉗位和max鉗位,讓在值内的信号通過,否則為min,max;

是以,獲得第一次的修改:

import torch

N=64 #樣本數
D_in=1000
H=100 #中間層輸出
D_out=10 #最後一層輸出

# 随機建立訓練資料
x=torch.randn(N,D_in)
y=torch.randn(N,D_out)

# 随機初始權重
w1=torch.randn(D_in,H)
w2=torch.randn(H,D_out)

learning_rate=1e-6

for ite in range(500):
    #Forward Pass
    h=x.mm(w1) # x乘w1->N*H
    h_relu=h.clamp(min=0) #N*H
    y_pred=h_relu.mm(w2) # h_relu乘w2->N*D_out
    
    #compute loss,假設用 MSE loss
    loss=(y_pred-y).pow(2).sum().item() #sum結果是tensor,是以用item()轉為Python數值
    print(ite,loss)
    
    #Backward Pass
    #pytorch是會自動求梯度的,但強行手動反向傳播也不是不行
    grad_y_pred=2.0*(y_pred-y)
    grad_w2=h_relu.t().mm(grad_y_pred)
    grad_h_relu=grad_y_pred.mm(w2.t()) #N*D_out乘D_out*H->N*H
    #深拷貝:拷貝到父子對象級别,前後完全是兩個不相關的對象,以避免科學計算出現錯誤
    grad_h=grad_h_relu.clone()
    #快速資料檢索
    grad_h[h<0]=0
    grad_w1=x.t().mm(grad_h) #D_in*N乘N*H->D_in*H
    
    #update weights of w1 and w2
    w1=w1-learning_rate*grad_w1
    w2=w2-learning_rate*grad_w2
           

以上修改中,我還是人為地去計算梯度了,pytorch支援自動求梯度(依據定義模型的計算圖),是以先探索一下autograd,當定義一個張量時,有參數requires_grad預設為False,該參數用于前向傳播時自動計算梯度:

#pytorch的autograd(自動求梯度)
x=torch.tensor(6,requires_grad=True,dtype=torch.float32)
           

對于自動計算梯度的認識,有以下簡單執行個體:

#簡單的自動求梯度
x=torch.tensor(1.) #x不能自動計算梯度
w=torch.tensor(2.,requires_grad=True)
b=torch.tensor(3.,requires_grad=True)

#這是一個計算圖
y=w*x+b #y=2*1+3

#反向計算不用手動寫過程(基于計算圖自動反向計算),直接寫:
y.backward()

#dy/dw
print(w.grad)
#dy/dx,由于x的requires_grad=False,是以其梯度為None
print(x.grad)
#dy/db
print(b.grad)

"""
tensor(1.)
None
tensor(1.)
"""
           

這裡補充一下,這樣的執行個體看起來很正常,但當我在訓練中inplace更新權重時,将會遇到一些錯誤,是以先了解一下葉子張量

葉子張量

葉子張量(葉子節點)是人為定義的張量,為了節省顯存,運算完一次,非葉子節點(由節點生成的節點,因為它類似于臨時變量)的梯度就會被釋放,當然,可以用

tensor.retain_grad()

儲存非葉子節點的梯度;

如果隻是為了列印梯度而便于調試,可以用

tensor.register_hook()

注意,不管requires_grad是True還是False,人為定義的tensor都是葉子節點:

第二課.PyTorch入門什麼是PyTorchPytorch張量的基本操作CUDA tensor實驗一:用Numpy實作兩層神經網絡實驗二:用Pytorch完成實驗一模型定義進階

看完葉子張量,再了解一下torch.no_grad()

torch.no_grad()

當我們在做 evaluating 的時候(不需要計算導數),我們可以将推斷(inference)的代碼包裹在

with torch.no_grad()

之中,以達到暫時不追蹤網絡參數中的導數的目的;

如果用pytorch的autograd(比如之前的

w1.grad

)去更新參數,寫成in-place的形式才能更新,為什麼?

因為一旦寫成:

w1=w1-learning_rate*w1.grad
           

就代表w1已經不再是葉子節點,

loss.backward()

語句結束後,w1的梯度就被間隔性釋放了,更新時有可能出現根本找不到w1.grad(已經為None)的情況;

而pytorch又有規定,對于requires_grad為True的張量,不允許in-place更新,現在很沖突,那該如何更新?

1.首先可以用

torch.no_grad()

,将更新語句置于其下,這樣,執行到

torch.no_grad()

,pytorch不會檢查(追蹤)張量的requires_grad,此時就可以放心進行in-place,但注意一點,w.grad的結果是不斷累積的,為了避免訓練出錯,必須在每次更新前進行grad對象清零:

上述過程為:

with torch.no_grad():
        #in-place更新時,需要指定暫時不追蹤計算圖,不然沒法更新
        #如果不用in-place更新,w=w-grad*lr,會導緻葉子節點w變成非葉子節點,梯度會丢失,進而無法更新
        w1-=learning_rate*w1.grad
        w2-=learning_rate*w2.grad
        
        #梯度會累加,需要每次進行清零
        w1.grad.zero_()
        w2.grad.zero_()   
           

2.另一種方式下,依然追蹤計算圖,當然就不能in-place了,但是我可以在loss.backward()前保留梯度:

w1.retain_grad()
    w2.retain_grad()
    
    #Backward Pass
    loss.backward()
    
    w1=w1-learning_rate*w1.grad
    w2=w2-learning_rate*w2.grad
           

現在,通過自動梯度,簡化實驗如下:

#借助autograd化簡代碼
import torch

N=64 #樣本數
D_in=1000
H=100 #中間層輸出
D_out=10 #最後一層輸出

# 随機建立訓練資料
x=torch.randn(N,D_in)
y=torch.randn(N,D_out)

# 随機初始權重
w1=torch.randn(D_in,H,requires_grad=True)
w2=torch.randn(H,D_out,requires_grad=True)

learning_rate=1e-6

for ite in range(500):
    #Forward Pass
    h=x.mm(w1) # x乘w1->N*H
    h_relu=h.clamp(min=0) #N*H
    y_pred=h_relu.mm(w2) # h_relu乘w2->N*D_out
    
    #compute loss,假設用 MSE loss
    loss=(y_pred-y).pow(2).sum() # 基于pytorch的實作,loss(不進行item)實際是computation graph
    loss_value=loss.item()
    print(ite,loss_value)
    
    #Backward Pass
    loss.backward()
    
    with torch.no_grad():
        #in-place更新時,需要指定暫時不追蹤計算圖,不然沒法更新
        #如果不用in-place更新,w=w-grad*lr,會導緻葉子節點w變成非葉子節點,梯度會丢失,進而無法更新
        w1-=learning_rate*w1.grad
        w2-=learning_rate*w2.grad
        
        #梯度會累加,需要每次進行清零
        w1.grad.zero_()
        w2.grad.zero_() 
           

進一步化簡

上面這樣寫總感覺還有些麻煩;

pytorch還有一個庫nn,使用起來就像keras一樣搭建積木,可以用網絡庫nn化簡:

#Pytorch内部提供了一個豐富的網絡庫:nn
#使用起來就像keras
import torch.nn

N,D_in,H,D_out=64,1000,100,10

#随機生成訓練資料
x=torch.randn(N,D_in)
y=torch.randn(N,D_out)

#使用Sequential搭模組化型
model=torch.nn.Sequential(
    torch.nn.Linear(D_in,H),#預設bias=True
    torch.nn.ReLU(),
    torch.nn.Linear(H,D_out)
)

#設定模型初始化
torch.nn.init.normal_(model[0].weight)
#model[1]是ReLU,沒法初始化權重
torch.nn.init.normal_(model[2].weight)

if torch.cuda.is_available():
    model=model.cuda()

#reduction決定了輸出張量次元縮減的方式,預設是mean
loss_fn=torch.nn.MSELoss(reduction='sum')
    
learning_rate=1e-6

for ite in range(500):
    #前向傳播
    y_pred=model(x)#本質是call :model.forward(x)
    
    #計算loss
    loss=loss_fn(y_pred,y)
    print(ite,loss.item())
    
    #反向傳播
    loss.backward()
    with torch.no_grad():
        #model.parameters()是模型的參數集合
        for param in model.parameters():
            param-=learning_rate*param.grad
        #參數梯度清零,直接簡寫為
        model.zero_grad()
           

還可以檢視model[0]的權重,第0層:

torch.nn.Linear(D_in,H)

第二課.PyTorch入門什麼是PyTorchPytorch張量的基本操作CUDA tensor實驗一:用Numpy實作兩層神經網絡實驗二:用Pytorch完成實驗一模型定義進階

使用優化器訓練

訓練不必須使用傳統的梯度下降,還可以選擇Adam等更高階的優化方法:

#優化方法
import torch.nn
import torch

N,D_in,H,D_out=64,1000,100,10

#随機生成訓練資料
x=torch.randn(N,D_in)
y=torch.randn(N,D_out)

#使用Sequential搭模組化型
model=torch.nn.Sequential(
    torch.nn.Linear(D_in,H),#預設bias=True
    torch.nn.ReLU(),
    torch.nn.Linear(H,D_out)
)

#這裡沒有正态分布初始化,初始化與optimizer有聯系,Adam不一定适合正态分布初始化

if torch.cuda.is_available():
    model=model.cuda()

#reduction決定了輸出張量次元縮減的方式,預設是mean
loss_fn=torch.nn.MSELoss(reduction='sum')
    
learning_rate=1e-4 #Adam一般使用1e-3到1e-4

#優化方法,不再使用傳統的梯度更新
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)

for ite in range(500):
    #前向傳播
    y_pred=model(x)
    
    #計算loss
    loss=loss_fn(y_pred,y)
    print(ite,loss.item())
    
    #反向傳播
    loss.backward()
    #更新模型
    optimizer.step()
    #梯度清零
    optimizer.zero_grad()
           

基于優化器,代碼更加簡潔:

loss.backward()

用于計算好梯度;

optimizer.step()

根據計算好的梯度進行更新;

optimizer.zero_grad()

用于在下一次梯度計算前進行梯度清零,等價于

model.zero_grad()

模型定義進階

上次模型定義依賴于torch.nn.Sequential,我現在繼承一個子類,用自定義的類去初始化模型;

一般來說,隻要在__init__()和forward()兩個方法進行修改就能滿足部分需求;

模型繼承自torch.nn.Module,要遵循規則:

1.要求導的層需要定義在__init__下

2.層的前向傳播過程寫在forward下

class TowLayerNet(torch.nn.Module):
    def __init__(self,D_in,H,D_out):
        #子類調用父類初始化,繼承到父類的所有屬性
        super().__init__()
        self.linear1=torch.nn.Linear(D_in,H,bias=False)
        self.linear2=torch.nn.Linear(H,D_out,bias=False)
    
    def forward(self,x):
        y_pred=self.linear2(self.linear1(x).clamp(min=0))
        return y_pred
           

執行個體化網絡:

前向傳播:

y_pred=model.forward(x)
#或者
y_pred=model(x)
           

完整實作:

#模型定義進階
import torch.nn
import torch

N,D_in,H,D_out=64,1000,100,10

#随機生成訓練資料
x=torch.randn(N,D_in)
y=torch.randn(N,D_out)


#模型繼承自torch.nn.Module,要遵循規則:要求導的層需要定義在__init__下
#層的前向傳播過程寫在forward下
class TowLayerNet(torch.nn.Module):
    def __init__(self,D_in,H,D_out):
	#子類調用父類初始化,繼承到父類的所有屬性	
        super().__init__()
        self.linear1=torch.nn.Linear(D_in,H,bias=False)
        self.linear2=torch.nn.Linear(H,D_out,bias=False)
    
    def forward(self,x):
        y_pred=self.linear2(self.linear1(x).clamp(min=0))
        return y_pred


model=TowLayerNet(D_in,H,D_out)

if torch.cuda.is_available():
    model=model.cuda()

#reduction決定了輸出張量次元縮減的方式,預設是mean
loss_fn=torch.nn.MSELoss(reduction='sum')
    
learning_rate=1e-4 #Adam一般使用1e-3到1e-4

#優化方法,不再使用傳統的梯度更新
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)

for ite in range(500):
    #前向傳播
    y_pred=model(x)
    
    #計算loss
    loss=loss_fn(y_pred,y)
    print(ite,loss.item())
    
    #反向傳播
    loss.backward()
    #更新模型
    optimizer.step()
    #梯度清零
    optimizer.zero_grad()
           

繼續閱讀