多層感知機


多層感知機

  1. 多層感知機的基本知識
  2. 使用多層感知機圖像分類的從零開始的實現
  3. 使用pytorch的簡潔實現

多層感知機的基本知識

深度學習主要關注多層模型。在這里,我們將以多層感知機(multilayer perceptron,MLP)為例,介紹多層神經網絡的概念。

隱藏層

下圖展示了一個多層感知機的神經網絡圖,它含有一個隱藏層,該層中有5個隱藏單元。

Image Name

表達公式

具體來說,給定一個小批量樣本\(\boldsymbol{X} \in \mathbb{R}^{n \times d}\),其批量大小為\(n\),輸入個數為\(d\)。假設多層感知機只有一個隱藏層,其中隱藏單元個數為\(h\)。記隱藏層的輸出(也稱為隱藏層變量或隱藏變量)為\(\boldsymbol{H}\),有\(\boldsymbol{H} \in \mathbb{R}^{n \times h}\)。因為隱藏層和輸出層均是全連接層,可以設隱藏層的權重參數和偏差參數分別為\(\boldsymbol{W}_h \in \mathbb{R}^{d \times h}\)\(\boldsymbol{b}_h \in \mathbb{R}^{1 \times h}\),輸出層的權重和偏差參數分別為\(\boldsymbol{W}_o \in \mathbb{R}^{h \times q}\)\(\boldsymbol{b}_o \in \mathbb{R}^{1 \times q}\)

我們先來看一種含單隱藏層的多層感知機的設計。其輸出\(\boldsymbol{O} \in \mathbb{R}^{n \times q}\)的計算為

\[ \begin{aligned} \boldsymbol{H} &= \boldsymbol{X} \boldsymbol{W}_h + \boldsymbol{b}_h,\\ \boldsymbol{O} &= \boldsymbol{H} \boldsymbol{W}_o + \boldsymbol{b}_o, \end{aligned} \]

也就是將隱藏層的輸出直接作為輸出層的輸入。如果將以上兩個式子聯立起來,可以得到

\[ \boldsymbol{O} = (\boldsymbol{X} \boldsymbol{W}_h + \boldsymbol{b}_h)\boldsymbol{W}_o + \boldsymbol{b}_o = \boldsymbol{X} \boldsymbol{W}_h\boldsymbol{W}_o + \boldsymbol{b}_h \boldsymbol{W}_o + \boldsymbol{b}_o. \]

從聯立后的式子可以看出,雖然神經網絡引入了隱藏層,卻依然等價於一個單層神經網絡:其中輸出層權重參數為\(\boldsymbol{W}_h\boldsymbol{W}_o\),偏差參數為\(\boldsymbol{b}_h \boldsymbol{W}_o + \boldsymbol{b}_o\)。不難發現,即便再添加更多的隱藏層,以上設計依然只能與僅含輸出層的單層神經網絡等價。

激活函數

上述問題的根源在於全連接層只是對數據做仿射變換(affine transformation),而多個仿射變換的疊加仍然是一個仿射變換。解決問題的一個方法是引入非線性變換,例如對隱藏變量使用按元素運算的非線性函數進行變換,然后再作為下一個全連接層的輸入。這個非線性函數被稱為激活函數(activation function)。

下面我們介紹幾個常用的激活函數:

ReLU函數

ReLU(rectified linear unit)函數提供了一個很簡單的非線性變換。給定元素\(x\),該函數定義為

\[\text{ReLU}(x) = \max(x, 0). \]

可以看出,ReLU函數只保留正數元素,並將負數元素清零。為了直觀地觀察這一非線性變換,我們先定義一個繪圖函數xyplot。

%matplotlib inline
import torch
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)
1.3.0
def xyplot(x_vals, y_vals, name):
    # d2l.set_figsize(figsize=(5, 2.5))
    plt.plot(x_vals.detach().numpy(), y_vals.detach().numpy())
    plt.xlabel('x')
    plt.ylabel(name + '(x)')
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = x.relu()
xyplot(x, y, 'relu')
y.sum().backward()
xyplot(x, x.grad, 'grad of relu')

Sigmoid函數

sigmoid函數可以將元素的值變換到0和1之間:

\[\text{sigmoid}(x) = \frac{1}{1 + \exp(-x)}. \]

x.grad.zero_()
y = x.sigmoid()
xyplot(x, y, 'sigmoid')

依據鏈式法則,sigmoid函數的導數

\[\text{sigmoid}'(x) = \text{sigmoid}(x)\left(1-\text{sigmoid}(x)\right). \]

下面繪制了sigmoid函數的導數。當輸入為0時,sigmoid函數的導數達到最大值0.25;當輸入越偏離0時,sigmoid函數的導數越接近0。

y.sum().backward()
xyplot(x, x.grad, 'grad of sigmoid')

tanh函數

tanh(雙曲正切)函數可以將元素的值變換到-1和1之間:

\[\text{tanh}(x) = \frac{1 - \exp(-2x)}{1 + \exp(-2x)}. \]

我們接着繪制tanh函數。當輸入接近0時,tanh函數接近線性變換。雖然該函數的形狀和sigmoid函數的形狀很像,但tanh函數在坐標系的原點上對稱。

y = x.tanh()
xyplot(x, y, 'tanh')

依據鏈式法則,tanh函數的導數

\[\text{tanh}'(x) = 1 - \text{tanh}^2(x). \]

下面繪制了tanh函數的導數。當輸入為0時,tanh函數的導數達到最大值1;當輸入越偏離0時,tanh函數的導數越接近0。

x.grad.zero_()
y.sum().backward()
xyplot(x, x.grad, 'grad of tanh')

關於激活函數的選擇

ReLu函數是一個通用的激活函數,目前在大多數情況下使用。但是,ReLU函數只能在隱藏層中使用。

用於分類器時,sigmoid函數及其組合通常效果更好。由於梯度消失問題,有時要避免使用sigmoid和tanh函數。

在神經網絡層數較多的時候,最好使用ReLu函數,ReLu函數比較簡單計算量少,而sigmoid和tanh函數計算量大很多。

在選擇激活函數的時候可以先選用ReLu函數如果效果不理想可以嘗試其他激活函數。

多層感知機

多層感知機就是含有至少一個隱藏層的由全連接層組成的神經網絡,且每個隱藏層的輸出通過激活函數進行變換。多層感知機的層數和各隱藏層中隱藏單元個數都是超參數。以單隱藏層為例並沿用本節之前定義的符號,多層感知機按以下方式計算輸出:

\[ \begin{aligned} \boldsymbol{H} &= \phi(\boldsymbol{X} \boldsymbol{W}_h + \boldsymbol{b}_h),\\ \boldsymbol{O} &= \boldsymbol{H} \boldsymbol{W}_o + \boldsymbol{b}_o, \end{aligned} \]

其中\(\phi\)表示激活函數。

多層感知機從零開始的實現

import torch
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)
1.3.0

獲取訓練集

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')

定義模型參數

num_inputs, num_outputs, num_hiddens = 784, 10, 256 # w層, h層,輸出層 

W1 = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_hiddens)), dtype=torch.float)
b1 = torch.zeros(num_hiddens, dtype=torch.float) # h = w * x + b
W2 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens, num_outputs)), dtype=torch.float)
b2 = torch.zeros(num_outputs, dtype=torch.float) # output = h*w + b

params = [W1, b1, W2, b2]
for param in params:
    param.requires_grad_(requires_grad=True)

定義激活函數

def relu(X):
    return torch.max(input=X, other=torch.tensor(0.0))

定義網絡

def net(X):
    X = X.view((-1, num_inputs))
    H = relu(torch.matmul(X, W1) + b1)
    return torch.matmul(H, W2) + b2

定義損失函數

loss = torch.nn.CrossEntropyLoss()

訓練

num_epochs, lr = 5, 100.0
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
              params=None, lr=None, optimizer=None):
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
        for X, y in train_iter:
            y_hat = net(X)
            l = loss(y_hat, y).sum()
            
            # 梯度清零
            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()
            
            l.backward()
            if optimizer is None:
                d2l.sgd(params, lr, batch_size)
            else:
                optimizer.step()  # “softmax回歸的簡潔實現”一節將用到
            
            
            train_l_sum += l.item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
            n += y.shape[0]
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
              % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))

d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)
epoch 1, loss 0.0031, train acc 0.708, test acc 0.762
epoch 2, loss 0.0019, train acc 0.822, test acc 0.803
epoch 3, loss 0.0017, train acc 0.844, test acc 0.830
epoch 4, loss 0.0015, train acc 0.857, test acc 0.822
epoch 5, loss 0.0015, train acc 0.864, test acc 0.837

多層感知機pytorch實現

import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l

print(torch.__version__)
1.3.0

初始化模型和各個參數

num_inputs, num_outputs, num_hiddens = 784, 10, 256
    
net = nn.Sequential(
        d2l.FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens),
        nn.ReLU(),
        nn.Linear(num_hiddens, num_outputs), 
        )
    
for params in net.parameters():
    init.normal_(params, mean=0, std=0.01)

訓練

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')
loss = torch.nn.CrossEntropyLoss()

optimizer = torch.optim.SGD(net.parameters(), lr=0.5)

num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
epoch 1, loss 0.0031, train acc 0.706, test acc 0.798
epoch 2, loss 0.0019, train acc 0.822, test acc 0.830
epoch 3, loss 0.0016, train acc 0.844, test acc 0.848
epoch 4, loss 0.0015, train acc 0.857, test acc 0.853
epoch 5, loss 0.0014, train acc 0.865, test acc 0.813


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM