pytorch實(shí)現(xiàn)mnist手寫數(shù)字識(shí)別(一)

? 深度學(xué)習(xí)的神經(jīng)網(wǎng)絡(luò)往往是龐大的,有幾十層或幾百層,這就是“深度”一詞的由來(lái)。你可以只用權(quán)重矩陣來(lái)構(gòu)建一個(gè)這樣的深層網(wǎng)絡(luò),但是一般來(lái)說,這是非常麻煩和難以實(shí)現(xiàn)的。PyTorch有一個(gè)很好的模塊nn,它提供了一種有效構(gòu)建大型神經(jīng)網(wǎng)絡(luò)的好方法。

# Import necessary packages
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import numpy as np
import torch
import helper
import matplotlib.pyplot as plt

? 現(xiàn)在我們要建立一個(gè)更大的網(wǎng)絡(luò)來(lái)解決一個(gè)(之前的)難題,識(shí)別圖像中的文本。這里我們將使用MNIST數(shù)據(jù)集,它由手寫灰度數(shù)字圖像構(gòu)成。每張圖片是28x28像素,您可以看到下面的示例:

mnist.png

? 我們的目標(biāo)是建立一個(gè)神經(jīng)網(wǎng)絡(luò),可以獲取這些圖像中的一個(gè)并預(yù)測(cè)圖像中的數(shù)字。
首先,我們需要得到我們的數(shù)據(jù)集。這是通過torchvision包提供的。下面的代碼將下載MNIST數(shù)據(jù)集,然后為我們創(chuàng)建培訓(xùn)和測(cè)試數(shù)據(jù)集。不要太擔(dān)心這里的細(xì)節(jié),你稍后會(huì)了解更多。

### Run this cell
from torchvision import datasets, transforms
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize((0.5,), (0.5,)),
                              ])
# Download and load the training data
trainset = datasets.MNIST('~/.pytorch/MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# print(trainloader)

? 我們將訓(xùn)練數(shù)據(jù)加載到trainloader中,并使用iter(trainloader)使其成為迭代器。稍后,我們將使用這個(gè)循環(huán)數(shù)據(jù)集進(jìn)行訓(xùn)練,比如:

for image, label in trainloader:
    ## do things with images and labels

? 您會(huì)注意到我創(chuàng)建了批大小為64的trainloader,shuffle=True。batch_size是我們?cè)谝淮蔚袕臄?shù)據(jù)加載器獲得圖像數(shù)量,通常稱為批處理。shuffle=True每次我們?cè)俅伍_始遍歷數(shù)據(jù)加載器時(shí),都要對(duì)數(shù)據(jù)集進(jìn)行shuffle。但這里我只是抓到第一批,這樣我們就可以查看數(shù)據(jù)了。我們可以在下面看到,圖像只是一個(gè)大小為(64,1,28,28)的張量。因此,每批64個(gè)圖像,1個(gè)彩色通道,28x28個(gè)圖像。

dataiter = iter(trainloader)
images, labels = dataiter.next()
print(type(images))
print(images.shape)
print(labels.shape)
<class 'torch.Tensor'>
torch.Size([64, 1, 28, 28])
torch.Size([64])

這就是其中一張照片的樣子。

plt.imshow(images[1].numpy().squeeze(), cmap='Greys_r');
output_7_0.png

? 首先,讓我們嘗試使用權(quán)重矩陣和矩陣乘法為這個(gè)數(shù)據(jù)集構(gòu)建一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)。然后,我們將看到如何使用PyTorch的nn模塊來(lái)實(shí)現(xiàn)這一點(diǎn),該模塊為定義網(wǎng)絡(luò)體系結(jié)構(gòu)提供了一種更加方便和強(qiáng)大的方法。
到目前為止,您看到的網(wǎng)絡(luò)稱為全連接網(wǎng)絡(luò)。一層中的每個(gè)單元都連接到下一層中的每個(gè)單元。在全連接網(wǎng)絡(luò)中,每一層的輸入必須是一維向量(可以作為一批多個(gè)示例疊加成二維張量)。然而,我們的圖像是28x28的2d張量,所以我們需要將它們轉(zhuǎn)換成1D向量??紤]到圖像的大小,我們需要將一批具有形狀(64,1,28,28)的圖像轉(zhuǎn)換為具有形狀(64,784)的圖像,784是28×28。這通常稱為展平,我們將二維圖像展平為一維向量。
這里我們需要10個(gè)輸出單位,每個(gè)數(shù)字一個(gè)。我們希望我們的網(wǎng)絡(luò)能夠預(yù)測(cè)圖像中顯示的數(shù)字,所以我們要做的是計(jì)算圖像屬于任何一個(gè)數(shù)字或類的概率。這最終是類(數(shù)字)上的離散概率分布,它告訴我們圖像的最可能屬于哪一類。這意味著我們需要10個(gè)輸出單位,用于10個(gè)類(數(shù)字)。接下來(lái)我們將看到如何將網(wǎng)絡(luò)輸出轉(zhuǎn)換為概率分布。

練習(xí):首先,展平一批圖像。然后,利用隨機(jī)張量的權(quán)值和偏差,建立一個(gè)包含784個(gè)輸入單元、256個(gè)隱藏單元和10個(gè)輸出單元的多層網(wǎng)絡(luò)。現(xiàn)在,對(duì)隱藏層使用sigmoid激活。不激活輸出層,接下來(lái)我們將添加一個(gè)概率分布。

## Your solution
def activation(x):
    return 1/(1+torch.exp(-x))

# Flatten the input image
inputs = images.view(images.shape[0],-1)

# Create parameters
w1 = torch.randn(784,256)
b1 = torch.randn(256)
w2 = torch.randn(256,10)
b2 = torch.randn(10)
h = activation(torch.mm(inputs,w1) + b1)
out = torch.mm(h,w2) + b2
print(out)
tensor([[ 1.4445e+01, -2.4370e+00, -1.5834e+00,  1.1148e+01, -1.3730e+01,
         -4.7537e+00,  4.9938e+00, -1.1234e+01,  7.7436e-01, -1.8633e+00],
          ...,
         -5.0983e+00,  2.4600e+00, -7.6534e+00,  8.2278e+00, -8.3573e+00],
        [ 1.8891e+01,  4.5289e+00, -7.1427e+00,  2.5687e+01, -7.6912e+00,
         -1.0540e+01, -8.1520e+00, -2.1016e+01,  1.4421e+01,  4.0628e+00],
        [ 5.5792e+00, -8.7805e+00, -8.6987e+00,  2.0828e+01, -7.1683e+00,
          1.7617e+00, -3.0304e+00, -2.2572e+01, -1.8938e+00, -4.7331e+00]])

? 現(xiàn)在我們的網(wǎng)絡(luò)有10個(gè)輸出。我們想把一個(gè)圖像傳給我們的網(wǎng)絡(luò),得到一個(gè)類的概率分布,這個(gè)類告訴我們圖像可能屬于哪個(gè)類。像這樣的東西:


image_distribution.png

? 在這里,我們看到每個(gè)類的概率大致相同。這表示一個(gè)未經(jīng)訓(xùn)練的網(wǎng)絡(luò),它還沒有看到任何數(shù)據(jù),所以它只返回一個(gè)均勻分布,每個(gè)類的概率相等。
為了計(jì)算這個(gè)概率分布,我們經(jīng)常使用softmax函數(shù)。從數(shù)學(xué)上看
\Large \sigma(x_i) = \cfrac{e^{x_i}}{\sum_k^K{e^{x_k}}}
? 這樣做目的是保證每個(gè)輸入 x_i 都在0到1之間,并規(guī)范化這些值,以給出一個(gè)適當(dāng)?shù)母怕史植?,其中概率總和?。

練習(xí):實(shí)現(xiàn)一個(gè)函數(shù)softmax,它執(zhí)行softmax計(jì)算并返回批中每個(gè)示例的概率分布。請(qǐng)注意,執(zhí)行此操作時(shí)需要注意形狀。

def softmax(x):
    return torch.exp(x)/torch.sum(torch.exp(x),dim=1).view(-1,1)

# Here, out should be the output of the network in the previous excercise with shape (64,10)
probabilities = softmax(out)

# Does it have the right shape? Should be (64, 10)
print(probabilities.shape)
# Does it sum to 1?
print(probabilities.sum(dim=1))
torch.Size([64, 10])
tensor([1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000,
        1.0000])

使用pytorch建立神經(jīng)網(wǎng)絡(luò)

? PyTorch提供了一個(gè)模塊 nn,使構(gòu)建網(wǎng)絡(luò)變得簡(jiǎn)單多了。在這里,我將向您展示如何用784個(gè)輸入、256個(gè)隱藏單元、10個(gè)輸出單元和一個(gè)softmax輸出來(lái)構(gòu)建與上面相同的一個(gè)。

from torch import nn
class Network(nn.Module):
    def __init__(self):
        super().__init__()
        
        # Inputs to hidden layer linear transformation
        self.hidden = nn.Linear(784, 256)
        # Output layer, 10 units - one for each digit
        self.output = nn.Linear(256, 10)
        
        # Define sigmoid activation and softmax output 
        self.sigmoid = nn.Sigmoid()
        self.softmax = nn.Softmax(dim=1)
        
    def forward(self, x):
        # Pass the input tensor through each of our operations
        x = self.hidden(x)
        x = self.sigmoid(x)
        x = self.output(x)
        x = self.softmax(x)
        
        return x

? 讓我們慢慢地看。

class Network(nn.Module):

? 這里我們從 nn.Module繼承。結(jié)合 super().__ init__() 創(chuàng)建一個(gè)跟蹤體系結(jié)構(gòu)的類,并提供許多有用的方法和屬性。為網(wǎng)絡(luò)創(chuàng)建類時(shí),必須從nn.Module繼承。類本身的名稱可以是任何內(nèi)容。

self.hidden = nn.Linear(784, 256)

? 這行創(chuàng)建一個(gè)用于線性轉(zhuǎn)換的模塊x\mathbf{W} + b,有784個(gè)輸入和256個(gè)輸出,并將其分配給self.hidden。模塊會(huì)自動(dòng)創(chuàng)建weightbias張量,我們將在forward方法中使用這些張量。使用net.hidden.weightnet.hidden.bias創(chuàng)建網(wǎng)絡(luò)(net)后,可以訪問weightbias張量。

self.output = nn.Linear(256, 10)

? 類似地,這將創(chuàng)建另一個(gè)具有256個(gè)輸入和10個(gè)輸出的線性變換。

self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)

? 這里我定義了sigmoid激活函數(shù)和softmax的操作。在nn.Softmax中設(shè)置dim=1計(jì)算各列的softmax。

def forward(self, x):

? 使用 nn.Module創(chuàng)建的PyTorch網(wǎng)絡(luò)必須定義一個(gè)forward方法。 它接受張量x并將其傳遞給您在init方法中定義的操作。

x = self.hidden(x)
x = self.sigmoid(x)
x = self.output(x)
x = self.softmax(x)

? 在這里,輸入張量x進(jìn)行操作,然后重新分配給x。 我們可以看到輸入張量通過hidden層,然后是sigmoid函數(shù),然后是output層,最后是softmax函數(shù)。 只要在操作中輸入和輸出與您要構(gòu)建的網(wǎng)絡(luò)體系結(jié)構(gòu)相匹配,在此為變量命名都沒有關(guān)系。 在init方法中定義事物的順序并不重要,但是您需要在forward方法中正確地對(duì)操作進(jìn)行排序。

? 現(xiàn)在我們可以創(chuàng)建一個(gè)網(wǎng)絡(luò)對(duì)象。

# Create the network and look at it's text representation
model = Network()
print(model)
Network(
  (hidden): Linear(in_features=784, out_features=256, bias=True)
  (output): Linear(in_features=256, out_features=10, bias=True)
  (sigmoid): Sigmoid()
  (softmax): Softmax(dim=1)
)

? 您可以使用torch.nn.functional模塊更加簡(jiǎn)潔明了地定義網(wǎng)絡(luò)。 這是您將看到的網(wǎng)絡(luò)定義為最常見的方式,因?yàn)樵S多操作都是簡(jiǎn)單的元素方式函數(shù)。 我們通常將此模塊定義為import torch.nn.functional as F。

import torch.nn.functional as F

class Network(nn.Module):
    def __init__(self):
        super().__init__()
        # Inputs to hidden layer linear transformation
        self.hidden = nn.Linear(784, 256)
        # Output layer, 10 units - one for each digit
        self.output = nn.Linear(256, 10)
        
    def forward(self, x):
        # Hidden layer with sigmoid activation
        x = F.sigmoid(self.hidden(x))
        # Output layer with softmax activation
        x = F.softmax(self.output(x), dim=1)
        
        return x

激活函數(shù)

? 到目前為止,我們只研究了sigmoid激活函數(shù),但是通常任何函數(shù)都可以用作激活函數(shù)。 唯一的要求是,對(duì)于網(wǎng)絡(luò)來(lái)說,近似非線性函數(shù),激活函數(shù)必須是非線性的。 以下是一些常見的激活函數(shù)示例:Tanh(雙曲正切)和ReLU(線性校正單元)。

activation.png

? 實(shí)際上,ReLU函數(shù)幾乎專門用作隱藏層的激活函數(shù)。

創(chuàng)建多層網(wǎng)絡(luò)

mlp_mnist.png

練習(xí):創(chuàng)建一個(gè)具有784個(gè)輸入單元的網(wǎng)絡(luò),一個(gè)具有128個(gè)單元的隱層和一個(gè)ReLU激活,然后一個(gè)具有64個(gè)單元的隱層和一個(gè)ReLU激活函數(shù),最后是一個(gè)具有softmax激活的輸出層,如上所示。 ReLU激活函數(shù)可以利用nn.ReLU模塊或F.relu函數(shù)實(shí)現(xiàn)。

? 按圖層的網(wǎng)絡(luò)類型命名是一個(gè)不錯(cuò)的方法。例如,“ fc”表示完全連接的圖層。 在編寫解決方案代碼時(shí),請(qǐng)使用fc1,fc2和fc3作為圖層名稱。

## Solution

class Network(nn.Module):
    def __init__(self):
        super().__init__()
        # Defining the layers, 128, 64, 10 units each
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 64)
        # Output layer, 10 units - one for each digit
        self.fc3 = nn.Linear(64, 10)
        
    def forward(self, x):
        ''' Forward pass through the network, returns the output logits '''
        
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        x = self.fc3(x)
        x = F.softmax(x, dim=1)
        
        return x
model = Network()
print(model)
Network(
  (fc1): Linear(in_features=784, out_features=128, bias=True)
  (fc2): Linear(in_features=128, out_features=64, bias=True)
  (fc3): Linear(in_features=64, out_features=10, bias=True)
)

初始化權(quán)重和偏置項(xiàng)

? 這里將會(huì)自動(dòng)為您初始化權(quán)重,但是可以自定義它們的初始化方式。 權(quán)重和偏差是連接到您定義的層的張量。例如,您可以使用model.fc1.weight來(lái)獲得它們。

print(model.fc1.weight)
print(model.fc1.bias)
Parameter containing:
tensor([[-0.0226,  0.0333,  0.0146,  ...,  0.0298, -0.0240,  0.0174],
        [-0.0322,  0.0049, -0.0257,  ...,  0.0022, -0.0102, -0.0090],
        ...,
        [ 0.0035,  0.0124,  0.0179,  ..., -0.0189,  0.0286,  0.0191]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0226,  0.0062,  0.0039, -0.0245, -0.0128,  0.0230,  0.0034, -0.0092,
...,
        -0.0211,  0.0169,  0.0084, -0.0007,  0.0350,  0.0187, -0.0236,  0.0140],
       requires_grad=True)

? 對(duì)于自定義初始化,我們想就地修改這些張量。 這些實(shí)際上是 autograd變量,因此我們需要使用model.fc1.weight.data來(lái)獲取實(shí)際的張量。 一旦有了張量,就可以用零(用于偏差)或隨機(jī)正態(tài)分布值填充它們。

# Set biases to all zeros
model.fc1.bias.data.fill_(0)
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
...,
        0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 
        0., 0., 0., 0., 0., 0., 0., 0.])
# sample from random normal with standard dev = 0.01
model.fc1.weight.data.normal_(std=0.01)
tensor([[ 4.5523e-03,  2.8657e-03, -1.5015e-04,  ..., -2.1070e-02,
         -1.6504e-03, -4.8392e-03],
        [-2.0217e-02,  4.0232e-03,  6.3457e-03,  ...,  9.6438e-03,
          1.2516e-02, -1.1635e-02],
        [ 1.1426e-03,  1.5297e-04, -1.6124e-03,  ..., -1.3250e-02,
          1.5046e-02,  6.9769e-03],
        ...,
        [ 5.4590e-06, -7.0351e-03,  2.4117e-02,  ...,  4.7201e-03,
         -2.1668e-03,  3.2850e-03],
        [ 5.2893e-03,  7.5558e-03, -6.5974e-03,  ..., -1.3320e-02,
          9.0465e-03,  1.1979e-02],
        [-6.0043e-04,  8.7822e-03,  5.4735e-04,  ...,  6.5182e-03,
         -2.9462e-03,  1.6791e-04]])

前向傳播

? 現(xiàn)在我們有了一個(gè)網(wǎng)絡(luò),讓我們看看傳遞圖像時(shí)會(huì)發(fā)生什么。

# Grab some data 
dataiter = iter(trainloader)
images, labels = dataiter.next()

# Resize images into a 1D vector, new shape is (batch size, color channels, image pixels) 
images.resize_(64, 1, 784)
# or images.resize_(images.shape[0], 1, 784) to automatically get batch size

# Forward pass through the network
img_idx = 0
ps = model.forward(images[img_idx,:])

img = images[img_idx]
helper.view_classify(img.view(1, 28, 28), ps)
output_28_0.png

? 從上面可以發(fā)現(xiàn),我們的網(wǎng)絡(luò)基本上不知道這個(gè)數(shù)字是多少。 這是因?yàn)槲覀兩形从?xùn)練它,所有的權(quán)重都是隨機(jī)的!

使用nn.Sequential

? PyTorch提供了一種構(gòu)建這樣的網(wǎng)絡(luò)的便捷方法,其中張量通過nn.Sequential (documentation)操作順序傳遞。 使用它來(lái)構(gòu)建相同效果的網(wǎng)絡(luò):

# Hyperparameters for our network
input_size = 784
hidden_sizes = [128, 64]
output_size = 10

# Build a feed-forward network
model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]),
                      nn.ReLU(),
                      nn.Linear(hidden_sizes[0], hidden_sizes[1]),
                      nn.ReLU(),
                      nn.Linear(hidden_sizes[1], output_size),
                      nn.Softmax(dim=1))
print(model)

# Forward pass through the network and display output
images, labels = next(iter(trainloader))
images.resize_(images.shape[0], 1, 784)
ps = model.forward(images[0,:])
helper.view_classify(images[0].view(1, 28, 28), ps)
Sequential(
  (0): Linear(in_features=784, out_features=128, bias=True)
  (1): ReLU()
  (2): Linear(in_features=128, out_features=64, bias=True)
  (3): ReLU()
  (4): Linear(in_features=64, out_features=10, bias=True)
  (5): Softmax(dim=1)
)

output_30_1.png

? 在這里,我們的模型與以前相同:784個(gè)輸入單元,一個(gè)具有128個(gè)單元的隱藏層,ReLU激活,64個(gè)單元的隱藏層,另一個(gè)ReLU,然后是具有10個(gè)單元的輸出層,以及softmax輸出。

? 可以通過傳入適當(dāng)?shù)乃饕齺?lái)進(jìn)行操作。 例如,如果要獲得第一個(gè)線性運(yùn)算并查看權(quán)重,則可以使用model [0]

print(model[0])
print(model[0].weight)
Linear(in_features=784, out_features=128, bias=True)
Parameter containing:
tensor([[-0.0047, -0.0203,  0.0070,  ..., -0.0073,  0.0203, -0.0019],
        [ 0.0242,  0.0299, -0.0162,  ..., -0.0110, -0.0216,  0.0247],
        [-0.0168,  0.0304,  0.0289,  ..., -0.0317,  0.0306,  0.0131],
        ...,
        [-0.0035, -0.0160, -0.0171,  ..., -0.0045, -0.0104,  0.0091],
        [-0.0018,  0.0039,  0.0196,  ...,  0.0281, -0.0169, -0.0170],
        [-0.0069, -0.0119, -0.0130,  ...,  0.0082,  0.0078, -0.0179]],
       requires_grad=True)

? 您也可以傳入OrderedDict來(lái)命名各個(gè)圖層和操作,而不是使用整數(shù)訪問。 請(qǐng)注意,字典鍵必須唯一,因此每個(gè)操作必須具有不同的名稱。

from collections import OrderedDict
model = nn.Sequential(OrderedDict([
                      ('fc1', nn.Linear(input_size, hidden_sizes[0])),
                      ('relu1', nn.ReLU()),
                      ('fc2', nn.Linear(hidden_sizes[0], hidden_sizes[1])),
                      ('relu2', nn.ReLU()),
                      ('output', nn.Linear(hidden_sizes[1], output_size)),
                      ('softmax', nn.Softmax(dim=1))]))
model
Sequential(
  (fc1): Linear(in_features=784, out_features=128, bias=True)
  (relu1): ReLU()
  (fc2): Linear(in_features=128, out_features=64, bias=True)
  (relu2): ReLU()
  (output): Linear(in_features=64, out_features=10, bias=True)
  (softmax): Softmax(dim=1)
)

? 現(xiàn)在您可以按整數(shù)或名稱訪問圖層

print(model[0])
print(model.fc1)
Linear(in_features=784, out_features=128, bias=True)
Linear(in_features=784, out_features=128, bias=True)

? 在下一次內(nèi)容中,我們將看到如何訓(xùn)練神經(jīng)網(wǎng)絡(luò)來(lái)準(zhǔn)確預(yù)測(cè)MNIST圖像中出現(xiàn)的數(shù)字。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容