pytorch搭建模型的五大層次級別解讀(由淺入深)
前言
神經(jīng)網(wǎng)絡的搭建本身是一個較為復雜的過程,但是現(xiàn)在有非常多的、非常人性化的開源框架提供給我們使用,但是即便如此,網(wǎng)絡的搭建也是有多種方法可以選擇,本文以pytorch為例子加以說明。
神經(jīng)網(wǎng)絡的基本流程可以分為兩大步驟,即
網(wǎng)絡結(jié)構(gòu)搭建+參數(shù)的梯度更新(后者又包括 “前向傳播+計算參數(shù)的梯度+梯度更新”)
這其實也是后面pytorch搭建神經(jīng)網(wǎng)絡的一個基本思路
1 、原始搭建——使用numpy實現(xiàn)
# -*- coding: utf-8 -*- import numpy as np # N是訓練的batch size; D_in 是input輸入數(shù)據(jù)的維度; # H是隱藏層的節(jié)點數(shù); D_out 輸出的維度,即輸出節(jié)點數(shù). N, D_in, H, D_out = 64, 1000, 100, 10 # 創(chuàng)建輸入、輸出數(shù)據(jù) x = np.random.randn(N, D_in) #(64,1000) y = np.random.randn(N, D_out) #(64,10)可以看成是一個10分類問題 # 權(quán)值初始化 w1 = np.random.randn(D_in, H) #(1000,100),即輸入層到隱藏層的權(quán)重 w2 = np.random.randn(H, D_out) #(100,10),即隱藏層到輸出層的權(quán)重 learning_rate = 1e-6 #學習率 for t in range(500): # 第一步:數(shù)據(jù)的前向傳播,計算預測值p_pred h = x.dot(w1) h_relu = np.maximum(h, 0) y_pred = h_relu.dot(w2) # 第二步:計算計算預測值p_pred與真實值的誤差 loss = np.square(y_pred - y).sum() print(t, loss) # 第三步:反向傳播誤差,更新兩個權(quán)值矩陣 grad_y_pred = 2.0 * (y_pred - y) #注意:這里的導函數(shù)也是自己求的,因為這個地方是很簡答的函數(shù)表達式 grad_w2 = h_relu.T.dot(grad_y_pred) grad_h_relu = grad_y_pred.dot(w2.T) grad_h = grad_h_relu.copy() grad_h[h < 0] = 0 grad_w1 = x.T.dot(grad_h) # 更新參數(shù) w1 -= learning_rate * grad_w1 w2 -= learning_rate * grad_w2
缺點:
(1)沒辦法搭建復雜的網(wǎng)絡結(jié)構(gòu)(網(wǎng)絡的結(jié)構(gòu)搭建太底層);
(2)梯度需要自己求導函數(shù),如果函數(shù)太復雜,網(wǎng)絡太深就很難求了;
(3)沒有辦法使用GPU加速
2、使用torch的Tensor原始實現(xiàn)
import torch dtype = torch.float device = torch.device("cpu") # device = torch.device("cuda:0") # 這里使用CPU,但實際上可以使用GPU # N是訓練的batch size; D_in 是input輸入數(shù)據(jù)的維度; # H是隱藏層的節(jié)點數(shù); D_out 輸出的維度,即輸出節(jié)點數(shù). N, D_in, H, D_out = 64, 1000, 100, 10 # 創(chuàng)建輸入、輸出數(shù)據(jù) x = torch.randn(N, D_in, device=device, dtype=dtype) #(64,1000) y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一個10分類問題 # 權(quán)值初始化 w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即輸入層到隱藏層的權(quán)重 w2 = torch.randn(H, D_out, device=device, dtype=dtype)#(100,10),即隱藏層到輸出層的權(quán)重 learning_rate = 1e-6 for t in range(500): # 第一步:數(shù)據(jù)的前向傳播,計算預測值p_pred h = x.mm(w1) h_relu = h.clamp(min=0) y_pred = h_relu.mm(w2) # 第二步:計算計算預測值p_pred與真實值的誤差 loss = (y_pred - y).pow(2).sum().item() print(t, loss) # 第三步:反向傳播誤差,更新兩個權(quán)值矩陣 grad_y_pred = 2.0 * (y_pred - y) #注意:這里的導函數(shù)也是自己求的,因為這個地方是很簡答的函數(shù)表達式 grad_w2 = h_relu.t().mm(grad_y_pred) grad_h_relu = grad_y_pred.mm(w2.t()) grad_h = grad_h_relu.clone() grad_h[h < 0] = 0 grad_w1 = x.t().mm(grad_h) # 參數(shù)更新(梯度下降法) w1 -= learning_rate * grad_w1 w2 -= learning_rate * grad_w2
缺點:
(1)沒辦法搭建復雜的網(wǎng)絡結(jié)構(gòu)(網(wǎng)絡的結(jié)構(gòu)搭建太底層);
(2)梯度需要自己求導函數(shù),如果函數(shù)太復雜,網(wǎng)絡太深就很難求了;
解決了GPU計算問題、少了一個缺點。
3、torch的自動求導autograd
import torch dtype = torch.float device = torch.device("cpu") # device = torch.device("cuda:0") # 這里使用CPU,但實際上可以使用GPU # N是訓練的batch size; D_in 是input輸入數(shù)據(jù)的維度; # H是隱藏層的節(jié)點數(shù); D_out 輸出的維度,即輸出節(jié)點數(shù). N, D_in, H, D_out = 64, 1000, 100, 10 # 創(chuàng)建輸入、輸出數(shù)據(jù) x = torch.randn(N, D_in, device=device, dtype=dtype) #(64,1000) y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一個10分類問題 # 權(quán)值初始化 w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即輸入層到隱藏層的權(quán)重 w2 = torch.randn(H, D_out, device=device, dtype=dtype)#(100,10),即隱藏層到輸出層的權(quán)重 learning_rate = 1e-6 for t in range(500): # 第一步:數(shù)據(jù)的前向傳播,計算預測值p_pred y_pred = x.mm(w1).clamp(min=0).mm(w2) # 第二步:計算計算預測值p_pred與真實值的誤差 loss = (y_pred - y).pow(2).sum() print(t, loss.item()) # 第三步:反向傳播誤差,更新兩個權(quán)值矩陣,這就是關鍵了,不再需要自己寫出導函數(shù),求導是自動完成的 loss.backward() #一步到位、自動求導 # 參數(shù)梯度更新 with torch.no_grad(): w1 -= learning_rate * w1.grad #grad屬性獲取梯度,其實這個地方就是相當于是梯度下降法,優(yōu)化的過程可以自己定義,因為這里參數(shù)很少 w2 -= learning_rate * w2.grad 求完一次之后將梯度歸零 w1.grad.zero_() w2.grad.zero_()
缺點:
(1)沒辦法搭建復雜的網(wǎng)絡結(jié)構(gòu)(網(wǎng)絡的結(jié)構(gòu)搭建太底層);
解決了GPU計算問題、而且梯度導數(shù)也不用自己求了,少了兩個缺點。那現(xiàn)在就只剩一個問題沒解決了,那就是怎么快速搭建更復雜、更深層的網(wǎng)絡結(jié)構(gòu)。
(2)上面更新參數(shù)w1和w2的過程其實就是一個優(yōu)化過程,這里是用的就是簡單的頭下降法,這樣做有一個很大的缺點,那就是這里只有兩個參數(shù)需要優(yōu)化,所以可以自己寫,但是現(xiàn)在的網(wǎng)絡有很多的參數(shù)需要優(yōu)化,都自己寫的話實在是太麻煩了。于是就有了后面的方法,參見下面
4、使用pytorch.nn模塊
可以將它理解為一個較高層次的API封裝
# -*- coding: utf-8 -*- import torch # N是訓練的batch size; D_in 是input輸入數(shù)據(jù)的維度; # H是隱藏層的節(jié)點數(shù); D_out 輸出的維度,即輸出節(jié)點數(shù). N, D_in, H, D_out = 64, 1000, 100, 10 # 創(chuàng)建輸入、輸出數(shù)據(jù) x = torch.randn(N, D_in) y = torch.randn(N, D_out) #模型搭建:這是與前面關鍵的區(qū)別,不再需要自己手動進行矩陣相乘,而是這種一步到位的方法 model = torch.nn.Sequential( torch.nn.Linear(D_in, H), torch.nn.ReLU(), torch.nn.Linear(H, D_out), ) #定義損失函數(shù) loss_fn = torch.nn.MSELoss(reduction='sum') learning_rate = 1e-4 for t in range(500): # 第一步:數(shù)據(jù)的前向傳播,計算預測值p_pred y_pred = model(x) # 第二步:計算計算預測值p_pred與真實值的誤差 loss = loss_fn(y_pred, y) print(t, loss.item()) # 在反向傳播之前,將模型的梯度歸零 model.zero_grad() # 第三步:反向傳播誤差,更新兩個權(quán)值矩陣,這就是關鍵了,不再需要自己寫出導函數(shù),求導是自動完成的 loss.backward() #更新參數(shù)的梯度 with torch.no_grad(): for param in model.parameters(): param -= learning_rate * param.grad #這其實就是梯度下降法,優(yōu)化參數(shù),通過循環(huán)自動實現(xiàn),不要再一個一個寫了,相較于上面的參數(shù)更新方法簡單了很多。但是還不夠
總結(jié):
到這一步,基本解決了前面的三個致命問題,即解決了GPU計算問題、而且梯度導數(shù)也不用自己求了,并且可以搭建復雜的網(wǎng)絡,不需要自己進行一個一個的矩陣相乘,少了三個缺點。
使用這里的層次,一些簡單的網(wǎng)絡類型就沒什么問題了,當然我們還可以進一步優(yōu)化,因為上面我們對loss進行自動求導之后,還需要通過一個循環(huán)來對模型的各個參數(shù)逐個進行參數(shù)的更新,所以下面提供了兩個層次方面的優(yōu)化措施:
5、使用torch.optim
來進一步簡化訓練過程——進一步省略手動的參數(shù)更新,更加一步到位
# -*- coding: utf-8 -*- import torch # N是訓練的batch size; D_in 是input輸入數(shù)據(jù)的維度; # H是隱藏層的節(jié)點數(shù); D_out 輸出的維度,即輸出節(jié)點數(shù). N, D_in, H, D_out = 64, 1000, 100, 10 # 創(chuàng)建輸入、輸出數(shù)據(jù) x = torch.randn(N, D_in) y = torch.randn(N, D_out) #模型搭建:這是與前面關鍵的區(qū)別,不再需要自己手動進行矩陣相乘,而是這種一步到位的方法 model = torch.nn.Sequential( torch.nn.Linear(D_in, H), torch.nn.ReLU(), torch.nn.Linear(H, D_out), ) #定義損失函數(shù) loss_fn = torch.nn.MSELoss(reduction='sum') learning_rate = 1e-4 #構(gòu)造一個optimizer對象 optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) for t in range(500): # 第一步:數(shù)據(jù)的前向傳播,計算預測值p_pred y_pred = model(x) # 第二步:計算計算預測值p_pred與真實值的誤差 loss = loss_fn(y_pred, y) print(t, loss.item()) # 在反向傳播之前,將模型的梯度歸零,這 optimizer.zero_grad() # 第三步:反向傳播誤差 loss.backward() # 直接通過梯度一步到位,更新完整個網(wǎng)絡的訓練參數(shù),一句話優(yōu)化所有的參數(shù),是不是很牛逼 optimizer.step()
這是不是更加簡單了?
如果是使用過keras這樣高層次的API,所以到這里應該發(fā)現(xiàn),他們實在是太像了,現(xiàn)在來總結(jié)一下使用pytorch搭建神經(jīng)網(wǎng)路的一般步驟:
(1)第一步:搭建網(wǎng)絡的結(jié)構(gòu),得到一個model。網(wǎng)絡的結(jié)構(gòu)可以是上面這種最簡單的序貫模型,當然還可以是多輸入-單輸出模型、單輸入-多輸出模型、多輸入-多輸出模型、跨層連接的模型等,我們好可以自己定義模型,后面再講。
(2)第二步:定義損失函數(shù)。
loss?= torch.nn.MSELoss(reduction='sum')
(3)第二步:定義優(yōu)化方式。構(gòu)造一個optimizer對象
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
上面是模型以及模型相關的配置三步走,下面是訓練的五步走。
(1)第一步:計算y_pred;
(2)第二步:根據(jù)損失函數(shù)計算loss
(3)第三步:梯度歸零,
optimizer.zero_grad()
(4)第四步:反向傳播誤差
loss.backward()
(5)更新參數(shù),使用step()
optimizer.step()
個人觀點:
深度學習框架最大的地方在于兩個點,正是這兩個點大大簡化了我們自己的實現(xiàn)思路,當然這兩個點不在于一層一層的搭建,個人認為最重要的在于以下兩個:
(1)第一,自動求導。設想一下,如果一個如此復雜的“高維度”、“非線性”的函數(shù),需要自己寫出求導公式,在進行矩陣運算,這一項就很不現(xiàn)實了。
(2)第二,參數(shù)的優(yōu)化。即所謂的optimizer的作用,它是每一個參數(shù)的更新規(guī)則,由于模型參數(shù)眾多,不可能一個一個來更新,而且沒一個更新的原理是重復的,深度學習框架正好提供了一步到位的方法,不管是tensorflow還是pytorch。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
pytorch torch.expand和torch.repeat的區(qū)別詳解
這篇文章主要介紹了pytorch torch.expand和torch.repeat的區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11Python?第三方庫?Pandas?數(shù)據(jù)分析教程
這篇文章主要介紹了Python?第三方庫?Pandas?數(shù)據(jù)分析教程的相關資料,需要的朋友可以參考下2022-09-09