通过PyTorch 进行深度学习
数学公式如下所示:
对于向量来说,为两个向量的点积/内积:
我们可以将这些单元神经元组合为层和堆栈,形成神经元网络。一个神经元层的输出变成另一层的输入。对于多个输入单元和输出单元,我们现在需要将权重表示为矩阵。
张量
实际上神经网络计算只是对张量进行一系列线性代数运算,矩阵是张量的一种形式。向量是一维张量,矩阵是二维张量,包含 3 个索引的数组是三维向量(例如 RGB 颜色图像)。神经网络的基本数据结构是张量,PyTorch(以及几乎所有其他深度学习框架)都是以张量为基础。
使用pytorch 构建神经网络
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import numpy as np
import torch
import helper
一般而言,PyTorch 张量的行为和 Numpy 数组相似。它们的索引都以 0 开始,并且支持切片。
改变形状
改变张量的形状是一个很常见的运算。首先使用 .size()
获取张量的大小和形状。然后,使用 .resize_()
改变张量的形状。注意下划线,改变形状是原地运算。
在 Numpy 与 Torch 之间转换
在 Numpy 数组与 Torch 张量之间转换非常简单并且很实用。要通过 Numpy 数组创建张量,使用 torch.from_numpy()
。要将张量转换为 Numpy 数组,使用 .numpy()
方法。
内存在 Numpy 数组与 Torch 张量之间共享,因此如果你原地更改一个对象的值,另一个对象的值也会更改。
通过Pytorch 构建神经网络
# 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
需要获取数据集。这些数据位于 torchvision 软件包中。以下代码将下载 MNIST 数据集,然后为我们创建训练数据集和测试数据集
# 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()
我们将训练数据加载到了 trainloader 中,并使用 iter(trainloader)使其变成迭代器。我们将用它循环访问数据集以进行训练,但是现在我只获取了第一批数据,以便查看数据。从下方可以看出,images 是一个大小为 (64, 1, 28, 28) 的张量。因此,每批有 64 个图像、1 个颜色通道,共有 28x28 个图像。
构建神经网络
要通过 PyTorch 构建神经网络,你需要使用 torch.nn 模块。网络本身是继承自 torch.nn.Module 的类。你需要单独定义每个运算,例如针对具有 784 个输入和 128 个单元的全连接层定义为 nn.Linear(784, 128)。
该类需要包含对网络实现前向传递的 forward 方法。在此方法中,你将对之前定义的每个运算传递输入张量 x。torch.nn 模块在 torch.nn.functional 中还具有一些对等的功能,例如 ReLU。此模块通常导入为 F。要对某个层(只是一个张量)使用 ReLU 激活函数,你需要使用 F.relu(x)。以下是一些常见的不同激活函数。
对于此网络,我将添加三个全连接层,然后添加一个预测类别的 softmax 输出。softmax 函数和 S 型函数相似,都会将输入调整到 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
权重等参数是系统自动初始化的,但是你也可以自定义如何初始化这些权重。权重和偏差是附加到你所定义的层的张量,你可以通过 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);
前向传递
我们已经创建好网络,看看传入图像后会发生什么。这一过程称之为前向传递。我们将图像数据转换为张量,然后传递给网络架构定义的运算。
# 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)
从上图中可以看出,我们的网络基本上根本不知道这个数字是什么,因为我们还没训练它,所有权重都是随机的!接下来,我们将了解如何训练该网络,使其能学习如何正确地对这些数字进行分类。
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)
还可以传入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
训练神经网络
一开始网络很朴素,不知道将输入映射到输出的函数。我们通过向网络展示实际数据样本训练网络,然后调整网络参数,使其逼近此函数。
要找到这些参数,我们需要了解网络预测真实输出的效果如何。为此,我们将计算损失函数(也称为成本),一种衡量预测错误的指标。例如,回归问题和二元分类问题经常使用均方损失
其中 n 是训练样本的数量,yi是真正的标签,y ̂ i 是预测标签
通过尽量减小相对于网络参数的这一损失,我们可以找到损失最低且网络能够以很高的准确率预测正确标签的配置。我们使用叫做梯度下降法的流程来寻找这一最低值。梯度是损失函数的斜率,指向变化最快的方向。要以最短的时间找到最低值,我们需要沿着梯度(向下)前进。可以将这一过程看做沿着最陡的路线下山。
反向传播
对于单层网络,梯度下降法实现起来很简单。但是,对于更深、层级更多的神经网络(例如我们构建的网络),梯度下降法实现起来更复杂。我们通过反向传播来实现,实际上是采用的微积分中的链式法则。最简单的理解方法是将两层网络转换为图形表示法。
在网络的前向传递过程中,我们的数据和运算从右到左。要通过梯度下降法训练权重,我们沿着网络反向传播成本梯度。从数学角度来讲,其实就是使用链式法则计算相对于权重的损失梯度。
我们使用此梯度和学习速率 α 更新权重。
对于训练步骤来说,首先我们需要定义损失函数。在 PyTorch 中,通常你会看到它写成了 criterion 形式。在此例中,我们使用 softmax 输出,因此我们希望使用 criterion = nn.CrossEntropyLoss() 作为损失函数。稍后在训练时,你需要使用 loss = criterion(output, targets) 计算实际损失。
我们还需要定义优化器,例如 SGD 或 Adam 等。我将使用 SGD,即 torch.optim.SGD,并传入网络参数和学习速率。
Autograd 自动计算梯度
Torch提供了一个自动编程模块,用于自动计算张量的梯度。 它通过跟踪在张量上执行的操作来实现此目的。 为了确保PyTorch跟踪张量上的运算并计算梯度,您需要在张量上设置requires_grad
。 您可以使用requires_grad关键字在创建时执行此操作,也可以随时使用x.requires_grad_(True)
执行此操作。
您可以使用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计算梯度。 这会向后传递创建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)
下面我们可以看到创建y的操作,一个幂运算操作PowBackward0。
## grad_fn shows the function that generated this variable
print(y.grad_fn)
autgrad模块会跟踪这些操作,并知道如何计算每个操作的梯度。 通过这种方式,它能够针对任何一个张量计算一系列操作的梯度。 让我们将张量y减小到标量值,即平均值。
你可以检查X和Y的梯度,但是它们现在是空的
要计算梯度,您需要在Variable z上运行.backward方法。 这将计算z相对于x的梯度
这些梯度计算对神经网络特别有用。 对于训练,我们需要权重的梯度与成本。 使用PyTorch,我们通过网络向前运行数据来计算成本,然后向后计算与成本相关的梯度。 一旦我们得到了梯度,我们就可以做出梯度下降步骤。
定义网络结构
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
构建一个网络。 与上一部分的唯一区别是我实际上并没有在输出上使用softmax,而只是使用最后一层的原始输出。 这是因为softmax的输出是概率分布。 通常,输出的值确实接近于零或非常接近于1。 由于将数字表示为浮点的不准确性,具有softmax输出的计算可能会失去准确性并变得不稳定。 为了解决这个问题,我们将使用称为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))]))
我们需要做的第一件事就是定义我们的损失函数。 在PyTorch中,您通常会将此视为标准。 这里我们使用softmax输出,所以我们想使用criterion = nn.CrossEntropyLoss()
作为我们的损失。 稍后在训练时,您使用loss = criterion(output, targets)
来计算实际损失。
我们还需要定义我们正在使用的优化器,SGD或Adam,或者其他类似的东西。 在这里,我将使torch.optim.SGD
,传递网络参数和学习速率。
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
首先,在循环遍历所有数据之前,我们只考虑一个学习步骤。 PyTorch的一般过程:
- 通过网络进行正向传递以获取logits
- 使用logits计算损失
- 使用
loss.backward()
执行向后传递网络以计算渐变 - 使用优化器更新权重
下面我将完成一个训练步骤并打印出权重和梯度
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)
推理与验证
在训练神经网络之后,你现在可以使用它来进行预测。这种步骤通常被称作推理,这是一个借自统计学的术语。然而,神经网络在面对训练数据时往往表现得太过优异,因而无法泛化未见过的数据。这种现象被称作过拟合,它损害了推理性能。为了在训练时检测过拟合,我们测量并不在名为验证集的训练集中数据的性能。在训练时,我们一边监控验证性能,一边进行正则化,如 Dropout,以此来避免过拟合。在这个 notebook 中,我将向你展示如何在 PyTorch 中做到这一点。
首先,我会实现我自己的前馈神经网络,这个网络基于第四部分的练习中的 Fashion-MNIST 数据集构建。它是第四部分练习的解决方案,也是如何进行 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)
构建网络
跟 MNIST 数据集一样,Fashion-MNIST 数据集中每张图片的像素为 28x28,共 784 个数据点和 10 个类。我使用了nn.ModuleList
来加入任意数量的隐藏层。这个模型中的hidden_layers
参数为隐藏层大小的列表(以整数表示)。使用 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 是一种对数概率,它有许多优点。使用这种对数概率,计算往往会更加迅速和准确。为了在之后获得类的概率,我将需要获得输出的指数(torch.exp
)。
我们可以使用
nn.Dropout
来在我们的网络中加入 Dropout。这与 nn.Linear
等其他模块的作用相似。它还将 Dropout 概率作为一种输入传递到网络中。
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)
训练网络
由于该模型的前向方法返回 log-softmax,因此我使用了负对数损失 作为标准。我还选用了Adam 优化器。这是一种随机梯度下降的变体,包含了动量,并且训练速度往往比基本的 SGD 要快。
还加入了一个代码块来测量验证损失和精确度。由于我在这个神经网络中使用了 Dropout,在推理时我需要将其关闭,否则这个网络将会由于许多连接的关闭而表现糟糕。在 PyTorch 中,你可以使用 model.train()
和 model.eval()
来将模型调整为“训练模式”或是“评估模式”。在训练模式中,Dropout 为开启状态,而在评估模式中,Dropout 为关闭状态。这还会影响到其他模块,包括那些应该在训练时开启、在推理时关闭的模块。
这段验证代码由一个通过验证集(并分裂成几个批次)的前向传播组成。根据 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()
推理
模型已经训练好了,我们现在可以使用它来进行推理。之前已经进行过这一步骤,但现在我们需要使用 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轻松创建完全连接的网络,并使用fc_model.train训练网络。
# 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)
可以想象,在每次使用神经网络时都重新进行训练很不现实。因此,我们可以保存之前训练好的网络,并在继续训练或是进行预测时加载网络。 PyTorch 网络的参数都存储在模型的 state_dict
中。可以看到这个状态字典包含了每个层的权重和偏差矩阵。
print("Our model: \n\n", model, '\n')
print("The state dict keys: \n\n", model.state_dict().keys())
最简单的做法是使用
torch.save
来保存状态字典。比如,我们可以将它保存到文件 'checkpoint.pth'
中。
torch.save(model.state_dict(), 'checkpoint.pth')
接着,我们可以使用 torch.load 来加载这个状态字典。
state_dict = torch.load('checkpoint.pth')
print(state_dict.keys())
要将状态字典加载到神经网络中,你需要使用 model.load_state_dict(state_dict)
这看上去十分简单,但实际情况更加复杂。只有当模型结构与检查点的结构完全一致时,状态字典才能成功加载。如果我在创建模型时使用了不同的结构,便无法顺利加载。
# 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)
这意味着我们需要重建一个与训练时完全相同的模型。有关模型结构的信息需要与状态字典一起存储在检查点中。为了做到这一点,你需要构建一个字典,字典中包含重建模型的全部信息。
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')
现在,检查点中包含了重建训练模型所需的全部信息。你可以随意将它编写为函数。相似地,我们也可以编写一个函数来加载检查点。
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)
加载图像数据
在实际项目中,你可能会处理一些全尺寸的图像,比如手机相机拍摄的图片。在这个 notebook 中,我们将会学习如何加载图像,并使用它们来训练神经网络。
我们将用到来自 Kaggle 的猫狗照片数据集。
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import torch
from torchvision import datasets, transforms
import helper
加载图像数据最简单是方法是使用 torchvision
中的 datasets.ImageFolder
(资料)。dataset = datasets.ImageFolder('path/to/data', transform=transforms)
'path/to/data'
是通往数据目录的文件路径,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 加载数据后,你需要定义一些转换。举个例子,这些图像的尺寸都不相同,但我们需要统一尺寸以便进行训练。你可以使用 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
接收数据集(比如你从 ImageFolder
中获取的数据集),并返回不同批次的图像以及对应的标签。你可以设置不同参数,比如批次大小,也可以设置是否在每个阶段后重组数据。
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
在这里,dataloader
是一个生成器。要想从这个生成器中提取数据,你需要遍历这个生成器,或是将它转换为一个迭代器并调用 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)
数据增强
训练神经网络的常用策略是在输入数据中添加随机性。举个例子,你可以在训练时随意旋转、镜像、缩放以及/或剪切你的图像。这样一来,你的神经网络在处理位置、大小、方向不同的相同图像时,可以更好地进行泛化。
要想随机旋转、缩放、剪切和翻转图像,你需要按以下格式定义转换:
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 能让数据以 0 为中心,除以 std 能够将值集中在 -1 和 1 之间。标准化有助于神经网络使权重接近 0,这能使反向传播更为稳定。倘若没有标准化,网络往往无法进行学习。
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 是一个庞大的数据集,其中有超过一百万张带有标签的图像,来自一千个不同类别。通常,我们使用一种名为卷积层的结构训练深度神经网络。在这里,我并不会深入介绍卷积网络,但如果你感兴趣,可以查看这个视频。
一旦经过训练,这些模型便能以绝佳表现检测未见过的图像的特征。这种使用预先训练的网络来分析训练集之外的图像的方法被称为迁移学习。在这里,我们将使用迁移学习来训练一个能够以近乎完美的准确性分类猫狗图像的网络。
使用 torchvision.models,你可以下载这些预先训练的网络,并用于你的应用中。我们现在将导入 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
大多数预先训练的模型要求输入为 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。现在让我们打印出这个模型的结构,以便了解细节。
这个模型主要有两个部分,即特征和分类器。特征部分是一堆卷积层,能作为特征检测器输入分类器中。分类器部分是一个单独的全连接层 (classifier): Linear(in_features=1024, out_features=1000)。这个层根据 ImageNet 数据集训练,因此无法解决我们指定的问题。这意味着我们需要替换这个分类器,不过这些特征本身能起到很大的作用。一般来说,我认为预先训练的网络是绝佳的特征检测器,可以作为简单的前馈分类器的输入。
# 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
在构建好模型之后,我们需要训练分类器。然而,现在我们使用的是一个非常深的神经网络。如果你还像之前一样试图在 CPU 上训练它,这会耗费相当长的时间。因此,我们将使用 GPU 来进行运算。在 GPU 上,线性代数运算同步进行,这使得运算速度提升了 100x。我们还可以在多个 GPU 上进行训练,这能进一步缩短训练时间。 PyTorch 和其他深度学习框架一样,使用 CUDA 来高效地在 GPU 上计算前向和后向传播。在 PyTorch 中,你可以使用 model.to(cuda) 将模型参数和其他张量转移到 GPU 内存中。当你需要在 PyTorch 之外处理网络的输出时,你也可以使用 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)
网友评论