? 深度學習的神經網絡往往是龐大的,有幾十層或幾百層,這就是“深度”一詞的由來。你可以只用權重矩陣來構建一個這樣的深層網絡,但是一般來說,這是非常麻煩和難以實現的。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像素,您可以看到下面的示例:
? 我們的目標是建立一個神經網絡,可以獲取這些圖像中的一個并預測圖像中的數字。
首先,我們需要得到我們的數據集。這是通過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=True
。batch_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');
? 首先,讓我們嘗試使用權重矩陣和矩陣乘法為這個數據集構建一個簡單的網絡。然后,我們將看到如何使用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個輸出。我們想把一個圖像傳給我們的網絡,得到一個類的概率分布,這個類告訴我們圖像可能屬于哪個類。像這樣的東西:
? 在這里,我們看到每個類的概率大致相同。這表示一個未經訓練的網絡,它還沒有看到任何數據,所以它只返回一個均勻分布,每個類的概率相等。
為了計算這個概率分布,我們經常使用softmax函數。從數學上看
? 這樣做目的是保證每個輸入 都在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)
? 這行創建一個用于線性轉換的模塊,有784個輸入和256個輸出,并將其分配給
self.hidden
。模塊會自動創建weight
和bias
張量,我們將在forward
方法中使用這些張量。使用net.hidden.weight
和net.hidden.bias
創建網絡(net
)后,可以訪問weight
和bias
張量。
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(線性校正單元)。
? 實際上,ReLU函數幾乎專門用作隱藏層的激活函數。
創建多層網絡
練習:創建一個具有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)
? 從上面可以發現,我們的網絡基本上不知道這個數字是多少。 這是因為我們尚未訓練它,所有的權重都是隨機的!
使用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)
)
? 在這里,我們的模型與以前相同: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圖像中出現的數字。