? 深度學(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像素,您可以看到下面的示例:

? 我們的目標(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');

? 首先,讓我們嘗試使用權(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è)類。像這樣的東西:

? 在這里,我們看到每個(gè)類的概率大致相同。這表示一個(gè)未經(jīng)訓(xùn)練的網(wǎng)絡(luò),它還沒有看到任何數(shù)據(jù),所以它只返回一個(gè)均勻分布,每個(gè)類的概率相等。
為了計(jì)算這個(gè)概率分布,我們經(jīng)常使用softmax函數(shù)。從數(shù)學(xué)上看
? 這樣做目的是保證每個(gè)輸入 都在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)換的模塊,有784個(gè)輸入和256個(gè)輸出,并將其分配給
self.hidden。模塊會(huì)自動(dòng)創(chuàng)建weight和bias張量,我們將在forward方法中使用這些張量。使用net.hidden.weight和net.hidden.bias創(chuàng)建網(wǎng)絡(luò)(net)后,可以訪問weight和bias張量。
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(線性校正單元)。

? 實(shí)際上,ReLU函數(shù)幾乎專門用作隱藏層的激活函數(shù)。
創(chuàng)建多層網(wǎng)絡(luò)

練習(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)

? 從上面可以發(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)
)

? 在這里,我們的模型與以前相同: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ù)字。