什麼是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=W1X
- 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=W2a
計算流程:
- 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,反向傳播梯度,更新權重:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL3lleOpXUU90dRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3QjN4EzN0QTMzATMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
對于前向傳播,按照要求為:
#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;
求權重的梯度有公式:
可以看出,要想知道權重的梯度必須知道前向計算時的激活值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都是葉子節點:
看完葉子張量,再了解一下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)
使用優化器訓練
訓練不必須使用傳統的梯度下降,還可以選擇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()