自己怎樣免費建設網(wǎng)站分發(fā)平臺
多層感知機理論部分
本文系統(tǒng)的講解多層感知機的pytorch復現(xiàn),以及詳細的代碼解釋。
部分文字和代碼來自《動手學深度學習》!!
目錄
- 多層感知機理論部分
- 隱藏層
- 多層感知機數(shù)學邏輯
- 激活函數(shù)
- 1. ReLU函數(shù)
- 2. sigmoid函數(shù)
- 3. tanh函數(shù)
- 多層感知機的從零開始實現(xiàn)
- 數(shù)據(jù)集
- 初始化參數(shù)模型
- 激活函數(shù)
- 模型定義
- 損失函數(shù)
- 訓練
- 多層感知機的pytorch實現(xiàn)(pytorch實現(xiàn)神經(jīng)網(wǎng)絡的流程)
- 1. 數(shù)據(jù)集
- 模型定義及參數(shù)初始化
- 損失函數(shù),優(yōu)化器和超參數(shù)的定義
- tensorboard的初始化
- 訓練
- 預測
- 模型的保存與獲取
隱藏層
上兩節(jié)我們使用的網(wǎng)絡都是線性的,即只有輸入層和輸出層兩層,但生活中線性模型有很大的限制。例如,如何對貓和狗的圖像進行分類呢? 增加位置處像素的強度是否總是增加(或降低)圖像描繪狗的似然? 對線性模型的依賴對應于一個隱含的假設, 即區(qū)分貓和狗的唯一要求是評估單個像素的強度。 在一個倒置圖像后依然保留類別的世界里,這種方法注定會失敗。
所以我們在網(wǎng)絡中加入隱藏層來克服線性模型的限制,使其能更普遍的處理函數(shù)關系模型。
要做到這一點,最簡單的方法是將許多全連接層堆疊在一起。 每一層都輸出到上面的層,直到生成最后的輸出。 我們可以把前層看作表示,把最后一層看作線性預測器。 這種架構通常稱為多層感知機(multilayer perceptron),通常縮寫為MLP。像下面的圖示:
這個多層感知機有4個輸入,3個輸出,其隱藏層包含5個隱藏單元。 輸入層不涉及任何計算,因此使用此網(wǎng)絡產(chǎn)生輸出只需要實現(xiàn)隱藏層和輸出層的計算。 因此,這個多層感知機中的層數(shù)為2。 注意,這兩個層都是全連接的。 每個輸入都會影響隱藏層中的每個神經(jīng)元, 而隱藏層中的每個神經(jīng)元又會影響輸出層中的每個神經(jīng)元。
多層感知機數(shù)學邏輯
多層感知機(Multilayer Perceptron, MLP)是一種基于多個全連接層的前饋神經(jīng)網(wǎng)絡,其公式如下:
假設輸入為 x∈Rd\boldsymbol{x} \in \mathbb{R}^dx∈Rd,第 iii 個全連接層的權重和偏差參數(shù)為 Wi∈Rhi?1×hi\boldsymbol{W}i \in \mathbb{R}^{h{i-1}\times h_i}Wi∈Rhi?1×hi? 和 bi∈R1×hi\boldsymbol_i \in \mathbb{R}^{1\times h_i}bi?∈R1×hi?,激活函數(shù)為 ?i(?)\phi_i(\cdot)?i?(?)。輸出為 o∈Rq\boldsymbol{o} \in \mathbb{R}^{q}o∈Rq,則有:
h0=xhi=?i(hi?1Wi+bi),i=1,2,…,n?1o=hn?1Wn+bn\begin{aligned} \boldsymbol{h}_0 &= \boldsymbol{x} \\ \boldsymbol{h}i &= \phi_i(\boldsymbol{h}_{i-1} \boldsymbol{W}_i + \boldsymboli), \quad i=1,2,\ldots,n-1\\ \boldsymbol{o} &= \boldsymbol{h}_{n-1} \boldsymbol{W}_n + \boldsymbol_n \end{aligned} h0?hio?=x=?i?(hi?1?Wi?+bi),i=1,2,…,n?1=hn?1?Wn?+bn??
其中 nnn 表示全連接層數(shù),hi\boldsymbol{h}_ihi? 表示第 iii 層的輸出,h0\boldsymbol{h}_0h0? 為輸入,Wn∈Rhn?1×q\boldsymbol{W}n \in \mathbb{R}^{h{n-1}\times q}Wn∈Rhn?1×q 和 bn∈R1×q\boldsymbol_n \in \mathbb{R}^{1\times q}bn?∈R1×q 是輸出層的權重和偏差參數(shù)。hih_ihi? 表示第 iii 層的輸出維度,也就是該層的神經(jīng)元個數(shù)。最后一層的激活函數(shù)一般不加,也可以視為 ?n(?)=identity(?)\phi_n(\cdot) = \mathrm{identity}(\cdot)?n?(?)=identity(?)。
激活函數(shù)
激活函數(shù)將非線性特性引入到神經(jīng)網(wǎng)絡中。在下圖中,輸入的 inputs 通過加權,求和后,還被作用了一個函數(shù)f,這個函數(shù)f就是激活函數(shù)。引入激活函數(shù)是為了增加神經(jīng)網(wǎng)絡模型的非線性。沒有激活函數(shù)的每層都相當于矩陣相乘。就算你疊加了若干層之后,無非還是個矩陣相乘罷了。
為什么使用激活函數(shù)
如果不用激活函數(shù),每一層輸出都是上層輸入的線性函數(shù),無論神經(jīng)網(wǎng)絡有多少層,輸出都是輸入的線性組合,這種情況就是最原始的感知機(Perceptron)。如果使用的話,激活函數(shù)給神經(jīng)元引入了非線性因素,使得神經(jīng)網(wǎng)絡可以任意逼近任何非線性函數(shù),這樣神經(jīng)網(wǎng)絡就可以應用到眾多的非線性模型中。
1. ReLU函數(shù)
最受歡迎的激活函數(shù)是修正線性單元(Rectified linear unit,ReLU), 因為它實現(xiàn)簡單,同時在各種預測任務中表現(xiàn)良好。 ReLU提供了一種非常簡單的非線性變換。 給定元素x,ReLU函數(shù)被定義為該元素與的0最大值:
ReLU(x)=max(x,0)ReLU(x) = max(x, 0)ReLU(x)=max(x,0)
通俗地說,ReLU函數(shù)通過將相應的活性值設為0,僅保留正元素并丟棄所有負元素。 為了直觀感受一下,我們可以畫出函數(shù)的曲線圖。 正如從圖中所看到,激活函數(shù)是分段線性的。
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))
、
當輸入為負時,ReLU函數(shù)的導數(shù)為0,而當輸入為正時,ReLU函數(shù)的導數(shù)為1。 注意,當輸入值精確等于0時,ReLU函數(shù)不可導。 在此時,我們默認使用左側的導數(shù),即當輸入為0時導數(shù)為0。 我們可以忽略這種情況,因為輸入可能永遠都不會是0。
觀察其求導圖像
y.backward(torch.ones_like(x), retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))
觀察圖像可知,它求導表現(xiàn)得特別好:要么讓參數(shù)消失,要么讓參數(shù)通過。 這使得優(yōu)化表現(xiàn)得更好,并且ReLU減輕了困擾以往神經(jīng)網(wǎng)絡的梯度消失問題
2. sigmoid函數(shù)
sigmoid函數(shù)是一種常用的激活函數(shù),是早期深度學習和神經(jīng)網(wǎng)絡研究比較常用的激活函數(shù),但在現(xiàn)在使用較少,其常用于神經(jīng)網(wǎng)絡的輸出層,將輸入的值映射到[0, 1]區(qū)間內(nèi),公式如下:
sigmoid(x)=11+exp(?x)sigmoid(x)=\frac{1}{1+exp(-x)}sigmoid(x)=1+exp(?x)1?
圖像為:
其中,x為輸入值。sigmoid函數(shù)的輸出值范圍在(0, 1)之間,當x為0時,sigmoid函數(shù)的值為0.5。sigmoid函數(shù)的導數(shù)可以用其自身表示:
σ(x)=11+e?xddxσ(x)=e?x(1+e?x)2&=11+e?x?e?x1+e?x=σ(x)(1?σ(x))\begin{aligned}\sigma(x) & =\frac{1}{1+e^{-x}} \ \fracvxwlu0yf4{dx} \sigma(x) &= \frac{e^{-x}}{(1+e^{-x})^2}\&=\frac{1}{1+e^{-x}}\cdot \frac{e^{-x}}{1+e^{-x}} \ &=\sigma(x)(1-\sigma(x))\end{aligned}σ(x)?=1+e?x1??dxd?σ(x)?=(1+e?x)2e?x?&=1+e?x1??1+e?xe?x???=σ(x)(1?σ(x))?
sigmoid函數(shù)具有連續(xù)可導、單調(diào)遞增、輸出值在(0, 1)之間等特點,但其缺點也比較明顯,如當輸入值過大或過小時,其導數(shù)趨于0,容易出現(xiàn)梯度消失的問題。
感興趣的同學可以參照上面的代碼自行繪制sigmoid函數(shù)導數(shù)的圖像
3. tanh函數(shù)
tanh函數(shù)是雙曲正切函數(shù)(hyperbolic tangent function)的縮寫,數(shù)學表達式為:tanh(x)=1?e?2x1+e?2x\text{tanh}(x) = \frac{1 - e^{-2x}}{1 + e^{-2x}}tanh(x)=1+e?2x1?e?2x?
圖像為:
與sigmoid函數(shù)類似,tanh函數(shù)也是一種常用的激活函數(shù)。它的取值范圍為 (?1,1)(-1, 1)(?1,1),且在原點處取值為0。相比于sigmoid函數(shù),在輸入接近0的時候,tanh函數(shù)的函數(shù)值變化更為明顯,可以使得模型更快地學習到特征。同時,tanh函數(shù)在原點兩側的函數(shù)值的變化也更加平緩,避免了在輸入較大或較小的時候出現(xiàn)梯度消失的問題。
其導數(shù)為:
ddxtanh?(x)=1?tanh?2(x)\fracvxwlu0yf4{dx}\operatorname{tanh}(x) = 1 - \operatorname{tanh}^2(x) dxd?tanh(x)=1?tanh2(x)
多層感知機的從零開始實現(xiàn)
數(shù)據(jù)集
我們同樣使用fashion-MNIST數(shù)據(jù)集,為了與上一節(jié)softmax回歸做對比,理解線性和非線性之間的差別。
import torch
from torch import nn
from d2l import torch as d2lbatch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
注:load_data_fashion_mnist函數(shù)在上一節(jié)實現(xiàn)過,這里我們直接調(diào)用李沐大佬做的d2l包中的函數(shù),源碼在上一節(jié)。
初始化參數(shù)模型
回想一下,Fashion-MNIST中的每個圖像由784個灰度像素值組成。 所有圖像共分為10個類別。 忽略像素之間的空間結構, 我們可以將每個圖像視為具有784個輸入特征 和10個類的簡單分類數(shù)據(jù)集。 首先,我們將實現(xiàn)一個具有單隱藏層的多層感知機, 它包含256個隱藏單元。 注意,我們可以將這兩個變量都視為超參數(shù)。 通常,我們選擇2的若干次冪作為層的寬度。 因為內(nèi)存在硬件中的分配和尋址方式,這么做往往可以在計算上更高效。
為了簡化代碼,我們實現(xiàn)三層的感知機,故參數(shù)為輸入層到隱藏層的W1,b1和隱藏層到輸出層的W2,b2
num_inputs, num_outputs, num_hiddens = 784, 10, 256W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))params = [W1, b1, W2, b2]
Parameter 是一個 Tensor 的子類,在定義神經(jīng)網(wǎng)絡時常用于將需要優(yōu)化的參數(shù)包裹在其中。與普通的 Tensor 不同, Parameter 會被自動添加到模型的參數(shù)列表中,以便在反向傳播過程中自動求導更新參數(shù)。當我們在使用優(yōu)化算法訓練模型時,我們只需要調(diào)用模型的 parameters() 方法即可獲得所有需要優(yōu)化的參數(shù)。
torch.randn(num_inputs, num_hiddens, requires_grad=True) 生成了一個大小為 (num_inputs, num_hiddens) 的張量,表示輸入和隱藏層之間的權重。由于該張量是需要被優(yōu)化的,因此 requires_grad=True 表示該張量的導數(shù)需要被計算和存儲,以便在反向傳播中計算梯度。
生成的隨機數(shù)乘以 0.01 的目的是將其縮小,從而避免初始參數(shù)值過大,導致神經(jīng)網(wǎng)絡訓練過程中出現(xiàn)梯度消失或爆炸的問題。
激活函數(shù)
def relu(X):a = torch.zeros_like(X)return torch.max(X, a)
模型定義
def net(X):X = X.reshape((-1, num_inputs))H = relu(X@W1 + b1) # 這里“@”代表矩陣乘法return (H@W2 + b2)
損失函數(shù)
我們采用交叉熵作為損失函數(shù)
loss = nn.CrossEntropyLoss(reduction='none')
訓練
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
其中train_ch3的實現(xiàn)部分在上一節(jié)
多層感知機的pytorch實現(xiàn)(pytorch實現(xiàn)神經(jīng)網(wǎng)絡的流程)
1. 數(shù)據(jù)集
import torch
from torch import nn
from d2l import torch as d2l
import torchvision
from torch.utils import data
from torchvision import transforms
def get_dataloader_workers(): #@save"""使用4個進程來讀取數(shù)據(jù)"""return 4def load_data_fashion_mnist(batch_size, resize=None): #@save"""下載Fashion-MNIST數(shù)據(jù)集,然后將其加載到內(nèi)存中"""trans = [transforms.ToTensor()]if resize:trans.insert(0, transforms.Resize(resize))trans = transforms.Compose(trans)mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)return (data.DataLoader(mnist_train, batch_size, shuffle=True,num_workers=get_dataloader_workers()),data.DataLoader(mnist_test, batch_size, shuffle=False,num_workers=get_dataloader_workers()))
模型定義及參數(shù)初始化
net = nn.Sequential(nn.Flatten(),nn.Linear(784, 256),nn.ReLU(),nn.Linear(256, 10))def init_weights(m):if type(m) == nn.Linear:nn.init.normal_(m.weight, std=0.01)net.apply(init_weights);
損失函數(shù),優(yōu)化器和超參數(shù)的定義
loss = torch.nn.CrossEntropyLoss()#損失函數(shù)
lr = 0.01#學習率
optim = torch.optim.SGD(net.parameters(),lr=lr)#優(yōu)化器
epoch = 10
batch_size=256
tensorboard的初始化
tensorboard用于存儲和可視化損失函數(shù)和精確度等數(shù)據(jù)
from torch.utils.tensorboard import SummaryWriter
log_dir = ''
writer = SummaryWriter(log_dir)
訓練
train_iter, test_iter = load_data_fashion_mnist(batch_size)
train_step = 0 #每輪訓練的次數(shù)
net.train()#模型在訓練狀態(tài)
for i in range(epoch):print("第{}輪訓練".format(i+1))train_step = 0total_loss = 0.0#總損失total_correct = 0#總正確數(shù)total_examples = 0#總訓練數(shù)for X,y in train_iter:# 前向傳播output=net(X)l=loss(output,y)# 反向傳播和優(yōu)化optim.zero_grad()l.backward()optim.step()#記錄/輸出損失和精確率total_loss += l.item()total_correct += (output.argmax(dim=1) == y).sum().item()total_examples += y.size(0)# 記錄訓練損失和準確率到 tensorboardwriter.add_scalar('train/loss', l.item(), epoch * len(train_iter) + i)writer.add_scalar('train/accuracy', total_correct/total_examples, epoch * len(train_iter) + i)train_step+=1if(train_step%100==0):#每訓練一百組輸出一次損失print("第{}輪的第{}次訓練的loss:{}".format((i+1),train_step,l.item()))# 在測試集上面的效果net.eval() #在驗證狀態(tài)total_test_correct = 0total_test_examples = 0with torch.no_grad(): # 驗證的部分,不是訓練所以不要帶入梯度for X,y in test_iter:outputs = net(X)test_result_loss=loss(outputs,y)total_test_correct += (outputs.argmax(1)==y).sum()total_test_examples += y.size(0)print("第{}輪訓練在測試集上的準確率為{}".format((i+1),(total_test_correct/total_test_examples)))
這段代碼是用于訓練和測試深度學習模型的代碼。首先使用 load_data_fashion_mnist(batch_size) 函數(shù)載入了Fashion-MNIST數(shù)據(jù)集并將訓練數(shù)據(jù)和測試數(shù)據(jù)分別封裝成 train_iter 和 test_iter 迭代器。
然后,將模型設置為訓練狀態(tài):net.train()。接下來,開始訓練模型。epoch 表示要迭代的輪數(shù),外層循環(huán)控制訓練輪數(shù)。內(nèi)層循環(huán)遍歷 train_iter 迭代器中的所有數(shù)據(jù),并通過前向傳播和反向傳播更新模型參數(shù)。
在訓練過程中,通過 total_loss、total_correct 和 total_examples 分別記錄總的損失值、總的正確預測數(shù)量和總的樣本數(shù)量,用于計算訓練過程中的損失值和精確度。同時,將訓練損失和準確率寫入 tensorboard 中,以便后續(xù)可視化分析。
每訓練一百次,將當前訓練的損失值輸出顯示。
在訓練完一輪后,使用 net.eval() 將模型轉換為測試狀態(tài)。在 with torch.no_grad() 的上下文管理器中,遍歷 test_iter 迭代器中的所有測試數(shù)據(jù),并通過模型進行預測,計算總的正確預測數(shù)量和總的樣本數(shù)量,以計算測試準確率。最后,輸出該輪訓練在測試集上的準確率。
結果:
預測
def predict(net, test_iter, n=6): #@savefor X, y in test_iter:breaktrues = d2l.get_fashion_mnist_labels(y)preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))titles = [true +'\n' + pred for true, pred in zip(trues, preds)]d2l.show_images(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])predict(net, test_iter)
結果如下:
模型的保存與獲取
torch.save(net,'這里填保存地址')
modlue = torch.load('這里填模型地址')