pytorch實現mnist手寫數字識別(一)

? 深度學習的神經網絡往往是龐大的,有幾十層或幾百層,這就是“深度”一詞的由來。你可以只用權重矩陣來構建一個這樣的深層網絡,但是一般來說,這是非常麻煩和難以實現的。PyTorch有一個很好的模塊nn,它提供了一種有效構建大型神經網絡的好方法。

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

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

? 現在我們要建立一個更大的網絡來解決一個(之前的)難題,識別圖像中的文本。這里我們將使用MNIST數據集,它由手寫灰度數字圖像構成。每張圖片是28x28像素,您可以看到下面的示例:

mnist.png

? 我們的目標是建立一個神經網絡,可以獲取這些圖像中的一個并預測圖像中的數字。
首先,我們需要得到我們的數據集。這是通過torchvision包提供的。下面的代碼將下載MNIST數據集,然后為我們創建培訓和測試數據集。不要太擔心這里的細節,你稍后會了解更多。

### 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)

? 我們將訓練數據加載到trainloader中,并使用iter(trainloader)使其成為迭代器。稍后,我們將使用這個循環數據集進行訓練,比如:

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

? 您會注意到我創建了批大小為64的trainloader,shuffle=Truebatch_size是我們在一次迭代中從數據加載器獲得圖像數量,通常稱為批處理。shuffle=True每次我們再次開始遍歷數據加載器時,都要對數據集進行shuffle。但這里我只是抓到第一批,這樣我們就可以查看數據了。我們可以在下面看到,圖像只是一個大小為(64,1,28,28)的張量。因此,每批64個圖像,1個彩色通道,28x28個圖像。

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

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

練習:首先,展平一批圖像。然后,利用隨機張量的權值和偏差,建立一個包含784個輸入單元、256個隱藏單元和10個輸出單元的多層網絡。現在,對隱藏層使用sigmoid激活。不激活輸出層,接下來我們將添加一個概率分布。

## 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]])

? 現在我們的網絡有10個輸出。我們想把一個圖像傳給我們的網絡,得到一個類的概率分布,這個類告訴我們圖像可能屬于哪個類。像這樣的東西:


image_distribution.png

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

練習:實現一個函數softmax,它執行softmax計算并返回批中每個示例的概率分布。請注意,執行此操作時需要注意形狀。

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建立神經網絡

? PyTorch提供了一個模塊 nn,使構建網絡變得簡單多了。在這里,我將向您展示如何用784個輸入、256個隱藏單元、10個輸出單元和一個softmax輸出來構建與上面相同的一個。

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繼承。結合 super().__ init__() 創建一個跟蹤體系結構的類,并提供許多有用的方法和屬性。為網絡創建類時,必須從nn.Module繼承。類本身的名稱可以是任何內容。

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

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

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

? 類似地,這將創建另一個具有256個輸入和10個輸出的線性變換。

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

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

def forward(self, x):

? 使用 nn.Module創建的PyTorch網絡必須定義一個forward方法。 它接受張量x并將其傳遞給您在init方法中定義的操作。

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

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

? 現在我們可以創建一個網絡對象。

# 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模塊更加簡潔明了地定義網絡。 這是您將看到的網絡定義為最常見的方式,因為許多操作都是簡單的元素方式函數。 我們通常將此模塊定義為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

激活函數

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

activation.png

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

創建多層網絡

mlp_mnist.png

練習:創建一個具有784個輸入單元的網絡,一個具有128個單元的隱層和一個ReLU激活,然后一個具有64個單元的隱層和一個ReLU激活函數,最后是一個具有softmax激活的輸出層,如上所示。 ReLU激活函數可以利用nn.ReLU模塊或F.relu函數實現。

? 按圖層的網絡類型命名是一個不錯的方法。例如,“ fc”表示完全連接的圖層。 在編寫解決方案代碼時,請使用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)
)

初始化權重和偏置項

? 這里將會自動為您初始化權重,但是可以自定義它們的初始化方式。 權重和偏差是連接到您定義的層的張量。例如,您可以使用model.fc1.weight來獲得它們。

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)

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

# 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]])

前向傳播

? 現在我們有了一個網絡,讓我們看看傳遞圖像時會發生什么。

# 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

? 從上面可以發現,我們的網絡基本上不知道這個數字是多少。 這是因為我們尚未訓練它,所有的權重都是隨機的!

使用nn.Sequential

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

# 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個輸入單元,一個具有128個單元的隱藏層,ReLU激活,64個單元的隱藏層,另一個ReLU,然后是具有10個單元的輸出層,以及softmax輸出。

? 可以通過傳入適當的索引來進行操作。 例如,如果要獲得第一個線性運算并查看權重,則可以使用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來命名各個圖層和操作,而不是使用整數訪問。 請注意,字典鍵必須唯一,因此每個操作必須具有不同的名稱。

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)
)

? 現在您可以按整數或名稱訪問圖層

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

? 在下一次內容中,我們將看到如何訓練神經網絡來準確預測MNIST圖像中出現的數字。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375