通過PyTorch 進行深度學習
數(shù)學公式如下所示:
對于向量來說,為兩個向量的點積/內積:
我們可以將這些單元神經元組合為層和堆棧,形成神經元網(wǎng)絡吓妆。一個神經元層的輸出變成另一層的輸入寞焙。對于多個輸入單元和輸出單元,我們現(xiàn)在需要將權重表示為矩陣熔掺。
張量
實際上神經網(wǎng)絡計算只是對張量進行一系列線性代數(shù)運算,矩陣是張量的一種形式非剃。向量是一維張量置逻,矩陣是二維張量,包含 3 個索引的數(shù)組是三維向量(例如 RGB 顏色圖像)备绽。神經網(wǎng)絡的基本數(shù)據(jù)結構是張量券坞,PyTorch(以及幾乎所有其他深度學習框架)都是以張量為基礎。
使用pytorch 構建神經網(wǎng)絡
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import numpy as np
import torch
import helper
一般而言肺素,PyTorch 張量的行為和 Numpy 數(shù)組相似恨锚。它們的索引都以 0 開始,并且支持切片倍靡。
改變形狀
改變張量的形狀是一個很常見的運算猴伶。首先使用 .size()
獲取張量的大小和形狀。然后塌西,使用 .resize_()
改變張量的形狀他挎。注意下劃線,改變形狀是原地運算捡需。
在 Numpy 與 Torch 之間轉換
在 Numpy 數(shù)組與 Torch 張量之間轉換非常簡單并且很實用办桨。要通過 Numpy 數(shù)組創(chuàng)建張量,使用 torch.from_numpy()
站辉。要將張量轉換為 Numpy 數(shù)組呢撞,使用 .numpy()
方法损姜。
內存在 Numpy 數(shù)組與 Torch 張量之間共享,因此如果你原地更改一個對象的值殊霞,另一個對象的值也會更改摧阅。
通過Pytorch 構建神經網(wǎng)絡
# Import things like usual
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import numpy as np
import torch
import helper
import matplotlib.pyplot as plt
from torchvision import datasets, transforms
需要獲取數(shù)據(jù)集。這些數(shù)據(jù)位于 torchvision 軟件包中脓鹃。以下代碼將下載 MNIST 數(shù)據(jù)集逸尖,然后為我們創(chuàng)建訓練數(shù)據(jù)集和測試數(shù)據(jù)集
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# Download and load the training data
trainset = datasets.MNIST('MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# Download and load the test data
testset = datasets.MNIST('MNIST_data/', download=True, train=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)
dataiter = iter(trainloader)
images, labels = dataiter.next()
我們將訓練數(shù)據(jù)加載到了 trainloader 中,并使用 iter(trainloader)使其變成迭代器瘸右。我們將用它循環(huán)訪問數(shù)據(jù)集以進行訓練娇跟,但是現(xiàn)在我只獲取了第一批數(shù)據(jù),以便查看數(shù)據(jù)太颤。從下方可以看出苞俘,images 是一個大小為 (64, 1, 28, 28) 的張量。因此龄章,每批有 64 個圖像吃谣、1 個顏色通道,共有 28x28 個圖像做裙。
構建神經網(wǎng)絡
要通過 PyTorch 構建神經網(wǎng)絡岗憋,你需要使用 torch.nn 模塊。網(wǎng)絡本身是繼承自 torch.nn.Module 的類锚贱。你需要單獨定義每個運算仔戈,例如針對具有 784 個輸入和 128 個單元的全連接層定義為 nn.Linear(784, 128)。
該類需要包含對網(wǎng)絡實現(xiàn)前向傳遞的 forward 方法拧廊。在此方法中监徘,你將對之前定義的每個運算傳遞輸入張量 x。torch.nn 模塊在 torch.nn.functional 中還具有一些對等的功能吧碾,例如 ReLU凰盔。此模塊通常導入為 F。要對某個層(只是一個張量)使用 ReLU 激活函數(shù)倦春,你需要使用 F.relu(x)户敬。以下是一些常見的不同激活函數(shù)。
對于此網(wǎng)絡睁本,我將添加三個全連接層山叮,然后添加一個預測類別的 softmax 輸出。softmax 函數(shù)和 S 型函數(shù)相似添履,都會將輸入調整到 0 到 1 之間,但是還會標準化這些輸入脑又,以便所有值的和為 1暮胧,就像正常的概率分布一樣锐借。
from torch import nn
from torch import optim
import torch.nn.functional as F
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()
model
權重等參數(shù)是系統(tǒng)自動初始化的,但是你也可以自定義如何初始化這些權重往衷。權重和偏差是附加到你所定義的層的張量钞翔,你可以通過 net.fc1.weight 獲取它們。
初始化權重和偏差
print(net.fc1.weight)
print(net.fc1.bias)
要自定義初始化過程席舍,請原地修改這些張量布轿。實際上存在 autograd 變量,因此我們需要通過 net.fc1.weight.data 獲取真正的張量来颤。獲得張量后汰扭,可以用 0(針對偏差)或隨機正常值填充這些張量。
# Set biases to all zeros
net.fc1.bias.data.fill_(0);
# sample from random normal with standard dev = 0.01
net.fc1.weight.data.normal_(std=0.01);
前向傳遞
我們已經創(chuàng)建好網(wǎng)絡福铅,看看傳入圖像后會發(fā)生什么萝毛。這一過程稱之為前向傳遞。我們將圖像數(shù)據(jù)轉換為張量滑黔,然后傳遞給網(wǎng)絡架構定義的運算笆包。
# Grab some data
dataiter = iter(trainloader)
images, labels = dataiter.next()
images.resize_(64, 1, 784)
# Need to wrap it in a Variable, will explain in next notebook
inputs = Variable(images)
# Forward pass through the network
img_idx = 0
logits = net.forward(inputs[img_idx,:])
# Predict the class from the network output
ps = F.softmax(logits, dim=1)
img = images[img_idx]
helper.view_classify(img.resize_(1, 28, 28), ps)
從上圖中可以看出,我們的網(wǎng)絡基本上根本不知道這個數(shù)字是什么略荡,因為我們還沒訓練它庵佣,所有權重都是隨機的!接下來汛兜,我們將了解如何訓練該網(wǎng)絡巴粪,使其能學習如何正確地對這些數(shù)字進行分類。
PyTorch提供了一種方便的方法來構建這樣的網(wǎng)絡序无,其中張量通過操作順序傳遞验毡。使用它來構建等效網(wǎng)絡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)
還可以傳入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
訓練神經網(wǎng)絡
一開始網(wǎng)絡很樸素哟玷,不知道將輸入映射到輸出的函數(shù)狮辽。我們通過向網(wǎng)絡展示實際數(shù)據(jù)樣本訓練網(wǎng)絡,然后調整網(wǎng)絡參數(shù)巢寡,使其逼近此函數(shù)喉脖。
要找到這些參數(shù),我們需要了解網(wǎng)絡預測真實輸出的效果如何抑月。為此树叽,我們將計算損失函數(shù)(也稱為成本),一種衡量預測錯誤的指標谦絮。例如题诵,回歸問題和二元分類問題經常使用均方損失
其中 n 是訓練樣本的數(shù)量洁仗,yi是真正的標簽,y ? i 是預測標簽
通過盡量減小相對于網(wǎng)絡參數(shù)的這一損失性锭,我們可以找到損失最低且網(wǎng)絡能夠以很高的準確率預測正確標簽的配置赠潦。我們使用叫做梯度下降法的流程來尋找這一最低值。梯度是損失函數(shù)的斜率草冈,指向變化最快的方向她奥。要以最短的時間找到最低值,我們需要沿著梯度(向下)前進怎棱×螅可以將這一過程看做沿著最陡的路線下山。
反向傳播
對于單層網(wǎng)絡蹄殃,梯度下降法實現(xiàn)起來很簡單携茂。但是,對于更深诅岩、層級更多的神經網(wǎng)絡(例如我們構建的網(wǎng)絡)讳苦,梯度下降法實現(xiàn)起來更復雜。我們通過反向傳播來實現(xiàn)吩谦,實際上是采用的微積分中的鏈式法則鸳谜。最簡單的理解方法是將兩層網(wǎng)絡轉換為圖形表示法。
在網(wǎng)絡的前向傳遞過程中式廷,我們的數(shù)據(jù)和運算從右到左咐扭。要通過梯度下降法訓練權重,我們沿著網(wǎng)絡反向傳播成本梯度滑废。從數(shù)學角度來講蝗肪,其實就是使用鏈式法則計算相對于權重的損失梯度。
我們使用此梯度和學習速率 α 更新權重蠕趁。
對于訓練步驟來說薛闪,首先我們需要定義損失函數(shù)。在 PyTorch 中俺陋,通常你會看到它寫成了 criterion 形式豁延。在此例中,我們使用 softmax 輸出腊状,因此我們希望使用 criterion = nn.CrossEntropyLoss() 作為損失函數(shù)诱咏。稍后在訓練時,你需要使用 loss = criterion(output, targets) 計算實際損失缴挖。
我們還需要定義優(yōu)化器袋狞,例如 SGD 或 Adam 等。我將使用 SGD,即 torch.optim.SGD硕并,并傳入網(wǎng)絡參數(shù)和學習速率法焰。
Autograd 自動計算梯度
Torch提供了一個自動編程模塊,用于自動計算張量的梯度倔毙。 它通過跟蹤在張量上執(zhí)行的操作來實現(xiàn)此目的。 為了確保PyTorch跟蹤張量上的運算并計算梯度乙濒,您需要在張量上設置requires_grad
陕赃。 您可以使用requires_grad關鍵字在創(chuàng)建時執(zhí)行此操作,也可以隨時使用x.requires_grad_(True)
執(zhí)行此操作颁股。
您可以使用torch.no_grad()內容關閉代碼塊的漸變:
x = torch.zeros(1么库,requires_grad = True)
with torch.no_grad():
... y = x * 2
y.requires_grad
false
此外,您可以使用torch.set_grad_enabled(True | False)完全打開或關閉梯度甘有。
使用z.backward()相對于某個變量z計算梯度诉儒。 這會向后傳遞創(chuàng)建z的操作。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
from collections import OrderedDict
import numpy as np
import time
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
import helper
x = torch.randn(2,2, requires_grad=True)
print(x)
y = x**2
print(y)
下面我們可以看到創(chuàng)建y的操作亏掀,一個冪運算操作PowBackward0忱反。
## grad_fn shows the function that generated this variable
print(y.grad_fn)
autgrad模塊會跟蹤這些操作,并知道如何計算每個操作的梯度滤愕。 通過這種方式温算,它能夠針對任何一個張量計算一系列操作的梯度。 讓我們將張量y減小到標量值间影,即平均值注竿。
你可以檢查X和Y的梯度,但是它們現(xiàn)在是空的
要計算梯度魂贬,您需要在Variable z上運行.backward方法巩割。 這將計算z相對于x的梯度
這些梯度計算對神經網(wǎng)絡特別有用。 對于訓練付燥,我們需要權重的梯度與成本宣谈。 使用PyTorch,我們通過網(wǎng)絡向前運行數(shù)據(jù)來計算成本机蔗,然后向后計算與成本相關的梯度蒲祈。 一旦我們得到了梯度,我們就可以做出梯度下降步驟萝嘁。
定義網(wǎng)絡結構
from torchvision import datasets, transforms
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# Download and load the training data
trainset = datasets.MNIST('MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
我將在這里使用nn.Sequential
構建一個網(wǎng)絡梆掸。 與上一部分的唯一區(qū)別是我實際上并沒有在輸出上使用softmax,而只是使用最后一層的原始輸出牙言。 這是因為softmax的輸出是概率分布酸钦。 通常,輸出的值確實接近于零或非常接近于1咱枉。 由于將數(shù)字表示為浮點的不準確性卑硫,具有softmax輸出的計算可能會失去準確性并變得不穩(wěn)定徒恋。 為了解決這個問題,我們將使用稱為logits的原始輸出來計算損失欢伏。
# Hyperparameters for our network
input_size = 784
hidden_sizes = [128, 64]
output_size = 10
# Build a feed-forward network
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()),
('logits', nn.Linear(hidden_sizes[1], output_size))]))
我們需要做的第一件事就是定義我們的損失函數(shù)入挣。 在PyTorch中,您通常會將此視為標準硝拧。 這里我們使用softmax輸出径筏,所以我們想使用criterion = nn.CrossEntropyLoss()
作為我們的損失。 稍后在訓練時障陶,您使用loss = criterion(output, targets)
來計算實際損失滋恬。
我們還需要定義我們正在使用的優(yōu)化器,SGD或Adam抱究,或者其他類似的東西恢氯。 在這里,我將使torch.optim.SGD
鼓寺,傳遞網(wǎng)絡參數(shù)和學習速率勋拟。
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
首先,在循環(huán)遍歷所有數(shù)據(jù)之前侄刽,我們只考慮一個學習步驟指黎。 PyTorch的一般過程:
- 通過網(wǎng)絡進行正向傳遞以獲取logits
- 使用logits計算損失
- 使用
loss.backward()
執(zhí)行向后傳遞網(wǎng)絡以計算漸變 - 使用優(yōu)化器更新權重
下面我將完成一個訓練步驟并打印出權重和梯度
print('Initial weights - ', model.fc1.weight)
images, labels = next(iter(trainloader))
images.resize_(64, 784)
# Clear the gradients, do this because gradients are accumulated
optimizer.zero_grad()
# Forward pass, then backward pass, then update weights
output = model.forward(images)
loss = criterion(output, labels)
loss.backward()
print('Gradient -', model.fc1.weight.grad)
optimizer.step()
print('Updated weights - ', net.fc1.weight)
實際訓練
optimizer = optim.SGD(model.parameters(), lr=0.003)
epochs = 3
print_every = 40
steps = 0
for e in range(epochs):
running_loss = 0
for images, labels in trainloader:
steps += 1
# Flatten MNIST images into a 784 long vector
images.resize_(images.size()[0], 784)
optimizer.zero_grad()
# Forward and backward passes
output = model.forward(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if steps % print_every == 0:
print("Epoch: {}/{}... ".format(e+1, epochs),
"Loss: {:.4f}".format(running_loss/print_every))
running_loss = 0
images, labels = next(iter(trainloader))
img = images[0].view(1, 784)
# Turn off gradients to speed up this part
with torch.no_grad():
logits = model.forward(img)
# Output of the network are logits, need to take softmax for probabilities
ps = F.softmax(logits, dim=1)
helper.view_classify(img.view(1, 28, 28), ps)
推理與驗證
在訓練神經網(wǎng)絡之后,你現(xiàn)在可以使用它來進行預測州丹。這種步驟通常被稱作推理醋安,這是一個借自統(tǒng)計學的術語。然而墓毒,神經網(wǎng)絡在面對訓練數(shù)據(jù)時往往表現(xiàn)得太過優(yōu)異吓揪,因而無法泛化未見過的數(shù)據(jù)。這種現(xiàn)象被稱作過擬合所计,它損害了推理性能柠辞。為了在訓練時檢測過擬合,我們測量并不在名為驗證集的訓練集中數(shù)據(jù)的性能主胧。在訓練時叭首,我們一邊監(jiān)控驗證性能,一邊進行正則化踪栋,如 Dropout焙格,以此來避免過擬合。在這個 notebook 中夷都,我將向你展示如何在 PyTorch 中做到這一點眷唉。
首先,我會實現(xiàn)我自己的前饋神經網(wǎng)絡,這個網(wǎng)絡基于第四部分的練習中的 Fashion-MNIST 數(shù)據(jù)集構建冬阳。它是第四部分練習的解決方案蛤虐,也是如何進行 Dropout 和驗證的例子。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import numpy as np
import time
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import datasets, transforms
import helper
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# Download and load the training data
trainset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# Download and load the test data
testset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)
構建網(wǎng)絡
跟 MNIST 數(shù)據(jù)集一樣肝陪,F(xiàn)ashion-MNIST 數(shù)據(jù)集中每張圖片的像素為 28x28驳庭,共 784 個數(shù)據(jù)點和 10 個類。我使用了nn.ModuleList
來加入任意數(shù)量的隱藏層见坑。這個模型中的hidden_layers
參數(shù)為隱藏層大小的列表(以整數(shù)表示)嚷掠。使用 nn.ModuleList
來寄存每一個隱藏模塊,這樣你可以在之后使用模塊方法荞驴。
由于每個nn.Linear操作都需要輸入大小和輸出大小
# Create ModuleList and add input layer
hidden_layers = nn.ModuleList([nn.Linear(input_size, hidden_layers[0])])
# Add hidden layers to the ModuleList
hidden_layers.extend([nn.Linear(h1, h2) for h1, h2 in layer_sizes])
獲得這些輸入和輸出大小對可以通過使用zip的方便技巧完成。
hidden_layers = [512, 256, 128, 64]
layer_sizes = zip(hidden_layers[:-1], hidden_layers[1:])
for each in layer_sizes:
print(each)
>> (512, 256)
>> (256, 128)
>> (128, 64)
我還使用了 forward
方法來返回輸出的 log-softmax贯城。由于 softmax 是類的概率分布熊楼,因此 log-softmax 是一種對數(shù)概率,它有許多優(yōu)點能犯。使用這種對數(shù)概率鲫骗,計算往往會更加迅速和準確。為了在之后獲得類的概率踩晶,我將需要獲得輸出的指數(shù)(torch.exp
)执泰。
我們可以使用
nn.Dropout
來在我們的網(wǎng)絡中加入 Dropout。這與 nn.Linear
等其他模塊的作用相似渡蜻。它還將 Dropout 概率作為一種輸入傳遞到網(wǎng)絡中术吝。
class Network(nn.Module):
def __init__(self, input_size, output_size, hidden_layers, drop_p=0.5):
''' Builds a feedforward network with arbitrary hidden layers.
Arguments
---------
input_size: integer, size of the input
output_size: integer, size of the output layer
hidden_layers: list of integers, the sizes of the hidden layers
drop_p: float between 0 and 1, dropout probability
'''
super().__init__()
# Add the first layer, input to a hidden layer
self.hidden_layers = nn.ModuleList([nn.Linear(input_size, hidden_layers[0])])
# Add a variable number of more hidden layers
layer_sizes = zip(hidden_layers[:-1], hidden_layers[1:])
self.hidden_layers.extend([nn.Linear(h1, h2) for h1, h2 in layer_sizes])
self.output = nn.Linear(hidden_layers[-1], output_size)
self.dropout = nn.Dropout(p=drop_p)
def forward(self, x):
''' Forward pass through the network, returns the output logits '''
for each in self.hidden_layers:
x = F.relu(each(x))
x = self.dropout(x)
x = self.output(x)
return F.log_softmax(x, dim=1)
訓練網(wǎng)絡
由于該模型的前向方法返回 log-softmax,因此我使用了負對數(shù)損失 作為標準茸苇。我還選用了Adam 優(yōu)化器排苍。這是一種隨機梯度下降的變體,包含了動量学密,并且訓練速度往往比基本的 SGD 要快淘衙。
還加入了一個代碼塊來測量驗證損失和精確度。由于我在這個神經網(wǎng)絡中使用了 Dropout腻暮,在推理時我需要將其關閉彤守,否則這個網(wǎng)絡將會由于許多連接的關閉而表現(xiàn)糟糕。在 PyTorch 中哭靖,你可以使用 model.train()
和 model.eval()
來將模型調整為“訓練模式”或是“評估模式”具垫。在訓練模式中,Dropout 為開啟狀態(tài)款青,而在評估模式中做修,Dropout 為關閉狀態(tài)。這還會影響到其他模塊,包括那些應該在訓練時開啟饰及、在推理時關閉的模塊蔗坯。
這段驗證代碼由一個通過驗證集(并分裂成幾個批次)的前向傳播組成。根據(jù) log-softmax 輸出來計算驗證集的損失以及預測精確度燎含。
# Create the network, define the criterion and optimizer
model = Network(784, 10, [516, 256], drop_p=0.5)## 784輸入宾濒,10輸出,兩個隱藏層
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Implement a function for the validation pass
def validation(model, testloader, criterion):
test_loss = 0
accuracy = 0
for images, labels in testloader:
images.resize_(images.shape[0], 784)
output = model.forward(images)
test_loss += criterion(output, labels).item()
ps = torch.exp(output)
equality = (labels.data == ps.max(dim=1)[1])
accuracy += equality.type(torch.FloatTensor).mean()
return test_loss, accuracy
epochs = 2
steps = 0
running_loss = 0
print_every = 40
for e in range(epochs):
model.train()
for images, labels in trainloader:
steps += 1
# Flatten images into a 784 long vector
images.resize_(images.size()[0], 784)
optimizer.zero_grad()
output = model.forward(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if steps % print_every == 0:
# Make sure network is in eval mode for inference
model.eval()
# Turn off gradients for validation, saves memory and computations
with torch.no_grad():
test_loss, accuracy = validation(model, testloader, criterion)
print("Epoch: {}/{}.. ".format(e+1, epochs),
"Training Loss: {:.3f}.. ".format(running_loss/print_every),
"Test Loss: {:.3f}.. ".format(test_loss/len(testloader)),
"Test Accuracy: {:.3f}".format(accuracy/len(testloader)))
running_loss = 0
# Make sure training is back on
model.train()
推理
模型已經訓練好了屏箍,我們現(xiàn)在可以使用它來進行推理绘梦。之前已經進行過這一步驟,但現(xiàn)在我們需要使用 model.eval() 來將模型設置為推理模式赴魁。
# Test out your network!
model.eval()
dataiter = iter(testloader)
images, labels = dataiter.next()
img = images[0]
# Convert 2D image to 1D vector
img = img.view(1, 784)
# Calculate the class probabilities (softmax) for img
with torch.no_grad():
output = model.forward(img)
ps = torch.exp(output)
# Plot the image and probabilities
helper.view_classify(img.view(1, 28, 28), ps, version='Fashion')
保存和加載模型
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms
import helper
import fc_model
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# Download and load the training data
trainset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
# Download and load the test data
testset = datasets.FashionMNIST('F_MNIST_data/', download=True, train=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)
image, label = next(iter(trainloader))
helper.imshow(image[0,:]);
我將模型架構和訓練代碼從最后一部分移動到一個名為fc_model的文件中卸奉。 導入此內容后,我們可以使用fc_model.Network輕松創(chuàng)建完全連接的網(wǎng)絡颖御,并使用fc_model.train訓練網(wǎng)絡榄棵。
# Create the network, define the criterion and optimizer
model = fc_model.Network(784, 10, [512, 256, 128])
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
fc_model.train(model, trainloader, testloader, criterion, optimizer, epochs=2)
可以想象,在每次使用神經網(wǎng)絡時都重新進行訓練很不現(xiàn)實潘拱。因此疹鳄,我們可以保存之前訓練好的網(wǎng)絡,并在繼續(xù)訓練或是進行預測時加載網(wǎng)絡芦岂。 PyTorch 網(wǎng)絡的參數(shù)都存儲在模型的 state_dict
中瘪弓。可以看到這個狀態(tài)字典包含了每個層的權重和偏差矩陣禽最。
print("Our model: \n\n", model, '\n')
print("The state dict keys: \n\n", model.state_dict().keys())
最簡單的做法是使用
torch.save
來保存狀態(tài)字典腺怯。比如,我們可以將它保存到文件 'checkpoint.pth'
中弛随。
torch.save(model.state_dict(), 'checkpoint.pth')
接著瓢喉,我們可以使用 torch.load 來加載這個狀態(tài)字典。
state_dict = torch.load('checkpoint.pth')
print(state_dict.keys())
要將狀態(tài)字典加載到神經網(wǎng)絡中舀透,你需要使用 model.load_state_dict(state_dict)
這看上去十分簡單栓票,但實際情況更加復雜。只有當模型結構與檢查點的結構完全一致時愕够,狀態(tài)字典才能成功加載走贪。如果我在創(chuàng)建模型時使用了不同的結構,便無法順利加載惑芭。
# Try this
model = fc_model.Network(784, 10, [400, 200, 100])
# This will throw an error because the tensor sizes are wrong!
model.load_state_dict(state_dict)
這意味著我們需要重建一個與訓練時完全相同的模型坠狡。有關模型結構的信息需要與狀態(tài)字典一起存儲在檢查點中碧注。為了做到這一點贪婉,你需要構建一個字典,字典中包含重建模型的全部信息济炎。
checkpoint = {'input_size': 784,
'output_size': 10,
'hidden_layers': [each.out_features for each in model.hidden_layers],
'state_dict': model.state_dict()}
torch.save(checkpoint, 'checkpoint.pth')
現(xiàn)在,檢查點中包含了重建訓練模型所需的全部信息凯亮。你可以隨意將它編寫為函數(shù)边臼。相似地,我們也可以編寫一個函數(shù)來加載檢查點假消。
def load_checkpoint(filepath):
checkpoint = torch.load(filepath)
model = fc_model.Network(checkpoint['input_size'],
checkpoint['output_size'],
checkpoint['hidden_layers'])
model.load_state_dict(checkpoint['state_dict'])
return model
model = load_checkpoint('checkpoint.pth')
print(model)
加載圖像數(shù)據(jù)
在實際項目中柠并,你可能會處理一些全尺寸的圖像,比如手機相機拍攝的圖片富拗。在這個 notebook 中臼予,我們將會學習如何加載圖像,并使用它們來訓練神經網(wǎng)絡啃沪。
我們將用到來自 Kaggle 的貓狗照片數(shù)據(jù)集粘拾。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import torch
from torchvision import datasets, transforms
import helper
加載圖像數(shù)據(jù)最簡單是方法是使用 torchvision
中的 datasets.ImageFolder
(資料)。dataset = datasets.ImageFolder('path/to/data', transform=transforms)
'path/to/data'
是通往數(shù)據(jù)目錄的文件路徑创千,transforms
是一個處理步驟的列表半哟,使用 torchvision
中的 transforms
模塊構建。ImageFolder 中的文件和目錄應按以下格式構建:
root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png
root/cat/123.png
root/cat/nsdf3.png
root/cat/asd932_.png
每個類都有各自存儲圖像的目錄(cat 和 dog)签餐。接著,這些圖像將被貼上摘自目錄名的標簽盯串。所以在這里氯檐,圖像 123.png 在加載時將被貼上類標簽 cat。
Transform
當你使用 ImageFolder 加載數(shù)據(jù)后体捏,你需要定義一些轉換冠摄。舉個例子,這些圖像的尺寸都不相同几缭,但我們需要統(tǒng)一尺寸以便進行訓練河泳。你可以使用 transforms.Resize() 來重新確定圖像尺寸,也可以使用 transforms.CenterCrop()年栓、transforms.RandomResizedCrop() 等進行切割拆挥。我們還需要使用 transforms.ToTensor() 來將圖像轉換為 PyTorch 張量。通常某抓,你會使用 transforms.Compose() 來將這些轉換結合到一條流水線中纸兔,這條流水線接收包含轉換的列表,并按順序運行否副。如下面的例子所示汉矿,它首先進行縮放,接著切割备禀,再轉換為張量:
transforms = transforms.Compose([transforms.Resize(255),
transforms.CenterCrop(224),
transforms.ToTensor()])
我們可以使用許多種轉換洲拇,接下來我會逐步講解奈揍,你也可以查看這里的資料。
Data Loader
在加載 ImageFolder
后赋续,你需要將它傳遞給一個 DataLoader
男翰。DataLoader
接收數(shù)據(jù)集(比如你從 ImageFolder
中獲取的數(shù)據(jù)集),并返回不同批次的圖像以及對應的標簽蚕捉。你可以設置不同參數(shù)奏篙,比如批次大小,也可以設置是否在每個階段后重組數(shù)據(jù)迫淹。
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
在這里秘通,dataloader
是一個生成器。要想從這個生成器中提取數(shù)據(jù)敛熬,你需要遍歷這個生成器肺稀,或是將它轉換為一個迭代器并調用 next()
。
# Looping through it, get a batch on each loop
for images, labels in dataloader:
pass
# Get one batch
images, labels = next(iter(dataloader))
# Run this to test your data loader
images, labels = next(iter(dataloader))
helper.imshow(images[0], normalize=False)
數(shù)據(jù)增強
訓練神經網(wǎng)絡的常用策略是在輸入數(shù)據(jù)中添加隨機性应民。舉個例子话原,你可以在訓練時隨意旋轉、鏡像诲锹、縮放以及/或剪切你的圖像繁仁。這樣一來,你的神經網(wǎng)絡在處理位置归园、大小黄虱、方向不同的相同圖像時,可以更好地進行泛化庸诱。
要想隨機旋轉捻浦、縮放、剪切和翻轉圖像桥爽,你需要按以下格式定義轉換:
train_transforms = transforms.Compose([transforms.RandomRotation(30),
transforms.RandomResizedCrop(100),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5],
[0.5, 0.5, 0.5])])
你通常還需要使用 transforms.Normalize 來標準化圖像朱灿。在傳入均值和標準差的列表后,顏色通道將按以下方法進行標準化:
減去 mean 能讓數(shù)據(jù)以 0 為中心钠四,除以 std 能夠將值集中在 -1 和 1 之間盗扒。標準化有助于神經網(wǎng)絡使權重接近 0,這能使反向傳播更為穩(wěn)定形导。倘若沒有標準化环疼,網(wǎng)絡往往無法進行學習。
data_dir = 'Cat_Dog_data'
# TODO: Define transforms for the training data and testing data
train_transforms = transforms.Compose([transforms.RandomRotation(30),
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
test_transforms = transforms.Compose([transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train', transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)
trainloader = torch.utils.data.DataLoader(train_data, batch_size=32)
testloader = torch.utils.data.DataLoader(test_data, batch_size=32)
# change this to the trainloader or testloader
data_iter = iter(testloader)
images, labels = next(data_iter)
fig, axes = plt.subplots(figsize=(10,4), ncols=4)
for ii in range(4):
ax = axes[ii]
helper.imshow(images[ii], ax=ax)
遷移學習
ImageNet 是一個龐大的數(shù)據(jù)集朵耕,其中有超過一百萬張帶有標簽的圖像炫隶,來自一千個不同類別。通常阎曹,我們使用一種名為卷積層的結構訓練深度神經網(wǎng)絡伪阶。在這里煞檩,我并不會深入介紹卷積網(wǎng)絡,但如果你感興趣栅贴,可以查看這個視頻斟湃。
一旦經過訓練,這些模型便能以絕佳表現(xiàn)檢測未見過的圖像的特征檐薯。這種使用預先訓練的網(wǎng)絡來分析訓練集之外的圖像的方法被稱為遷移學習凝赛。在這里,我們將使用遷移學習來訓練一個能夠以近乎完美的準確性分類貓狗圖像的網(wǎng)絡坛缕。
使用 torchvision.models墓猎,你可以下載這些預先訓練的網(wǎng)絡,并用于你的應用中赚楚。我們現(xiàn)在將導入 models
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import datasets, transforms, models
import helper
大多數(shù)預先訓練的模型要求輸入為 224x224 像素的圖像毙沾。同樣地,我們需要匹配訓練模型時進行的標準化宠页。每個顏色通道都分別進行了標準化左胞,均值為 [0.485, 0.456, 0.406],標準差為 [0.229, 0.224, 0.225]举户。
data_dir = 'Cat_Dog_data'
# TODO: Define transforms for the training data and testing data
train_transforms = transforms.Compose([transforms.RandomRotation(30),
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
test_transforms = transforms.Compose([transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train', transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)
trainloader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=32)
我們可以載入一個模型烤宙,比如 DenseNet。現(xiàn)在讓我們打印出這個模型的結構俭嘁,以便了解細節(jié)门烂。
這個模型主要有兩個部分,即特征和分類器兄淫。特征部分是一堆卷積層,能作為特征檢測器輸入分類器中蔓姚。分類器部分是一個單獨的全連接層 (classifier): Linear(in_features=1024, out_features=1000)捕虽。這個層根據(jù) ImageNet 數(shù)據(jù)集訓練,因此無法解決我們指定的問題坡脐。這意味著我們需要替換這個分類器泄私,不過這些特征本身能起到很大的作用。一般來說备闲,我認為預先訓練的網(wǎng)絡是絕佳的特征檢測器晌端,可以作為簡單的前饋分類器的輸入。
# Freeze parameters so we don't backprop through them
for param in model.parameters():
param.requires_grad = False
from collections import OrderedDict
classifier = nn.Sequential(OrderedDict([
('fc1', nn.Linear(1024, 500)),
('relu', nn.ReLU()),
('fc2', nn.Linear(500, 2)),
('output', nn.LogSoftmax(dim=1))
]))
model.classifier = classifier
在構建好模型之后恬砂,我們需要訓練分類器咧纠。然而,現(xiàn)在我們使用的是一個非常深的神經網(wǎng)絡泻骤。如果你還像之前一樣試圖在 CPU 上訓練它漆羔,這會耗費相當長的時間梧奢。因此,我們將使用 GPU 來進行運算演痒。在 GPU 上亲轨,線性代數(shù)運算同步進行,這使得運算速度提升了 100x鸟顺。我們還可以在多個 GPU 上進行訓練惦蚊,這能進一步縮短訓練時間。 PyTorch 和其他深度學習框架一樣讯嫂,使用 CUDA 來高效地在 GPU 上計算前向和后向傳播蹦锋。在 PyTorch 中,你可以使用 model.to(cuda) 將模型參數(shù)和其他張量轉移到 GPU 內存中端姚。當你需要在 PyTorch 之外處理網(wǎng)絡的輸出時晕粪,你也可以使用 model.to(cpu) 再將它們從 GPU 上轉移回去。我將分別使用 GPU 和不使用 GPU 進行前向傳播和后向傳播渐裸,好為你展示著兩者之間計算速度的差異巫湘。
import time
for device in ['cpu', 'cuda']:
criterion = nn.NLLLoss()
# Only train the classifier parameters, feature parameters are frozen
optimizer = optim.Adam(model.classifier.parameters(), lr=0.001)
model.to(device)
for ii, (inputs, labels) in enumerate(trainloader):
# Move input and label tensors to the GPU
inputs, labels = inputs.to(device), labels.to(device)
start = time.time()
outputs = model.forward(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
if ii==3:
break
print(f"CUDA = {cuda}; Time per batch: {(time.time() - start)/3:.3f} seconds")
# Putting the above into functions, so they can be used later
def do_deep_learning(model, trainloader, epochs, print_every, criterion, optimizer, device='cpu'):
epochs = epochs
print_every = print_every
steps = 0
# change to cuda
model.to('cuda')
for e in range(epochs):
running_loss = 0
for ii, (inputs, labels) in enumerate(trainloader):
steps += 1
inputs, labels = inputs.to('cuda'), labels.to('cuda')
optimizer.zero_grad()
# Forward and backward passes
outputs = model.forward(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if steps % print_every == 0:
print("Epoch: {}/{}... ".format(e+1, epochs),
"Loss: {:.4f}".format(running_loss/print_every))
running_loss = 0
def check_accuracy_on_test(testloader):
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))
do_deep_learning(model, trainloader, 3, 40, criterion, optimizer, 'gpu')
check_accuracy_on_test(testloader)