使用 PyTorch 通过验证训练神经网络
神经网络是一种受生物学启发的编程范式,深度学习是围绕它构建的。 Python提供了各种库,您可以使用这些库在给定数据上创建和训练神经网络。 PyTorch 就是这样一个库,它为我们提供了各种实用程序来轻松构建和训练神经网络。当涉及到神经网络时,设置最佳架构和超参数变得至关重要。在训练神经网络时,只要学习率最佳,训练损失就会不断减少。但重要的是,我们的网络不仅在训练的数据上表现更好,而且在它以前从未见过的数据上表现更好。衡量这一点的一种方法是引入验证集来跟踪神经网络的测试准确性。在本文中,我们将如何在每个训练步骤中跟踪验证准确度,并以最佳验证准确度保存模型权重。
安装 PyTorch
安装 PyTorch 与任何其他Python库非常相似。我们可以使用 pip 或 conda 来安装 PyTorch:-
pip install torch torchvision
此命令将安装 PyTorch 和 torchvision,后者为计算机视觉提供各种数据集、模型和转换。要使用 conda 安装,您可以使用以下命令:-
conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch
加载数据中
在本教程中,我们将使用 torchvision 库中提供的 MNIST 数据集。在深度学习中,我们经常以特定大小的批次训练我们的神经网络,DataLoader 是 PyTorch 中的数据加载实用程序,可在这些批次的数据集上创建可迭代对象。让我们从加载数据开始:-
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, random_split
transforms = transforms.Compose([
transforms.ToTensor()
])
在上面的代码中,我们声明了一个名为 transform 的变量,它本质上帮助我们以定义的格式转换原始数据。在这里,我们的转换只是获取原始数据并将其转换为张量。张量是表示 n 维矩阵的一种奇特方式。
train = datasets.MNIST('', train = True, transform = transforms, download = True)
train, valid = random_split(train,[50000,10000])
现在我们正在下载我们的原始数据并对其应用变换以将其转换为张量, train 会告诉正在加载的数据是训练数据还是测试数据。最后,我们将训练张量拆分为 50000 和 10000 个数据点的 2 个张量,这些张量成为我们的训练和有效张量。
trainloader = DataLoader(train, batch_size=32)
validloader = DataLoader(valid, batch_size=32)
现在我们刚刚创建了上述 32 个批次大小的张量的DataLoader 。现在我们有了数据,让我们开始创建我们的神经网络。
构建我们的模型
我们可以通过两种方式在 PyTorch 中创建神经网络,即使用Sequential()方法或使用类方法。我们将使用类方法来创建我们的神经网络,因为它可以更好地控制数据流。使用类方法创建神经网络的格式如下:-
from torch import nn
class model(nn.Module):
def __init__(self):
# Define Model Here
def forward(self, x):
# Define Forward Pass Here
所以在 __init__() 方法中我们定义了我们的层和其他变量,在 forward() 方法中我们定义了我们的前向传递,即数据如何流经层。
import torch
from torch import nn
import torch.nn.functional as F
class Network(nn.Module):
def __init__(self):
super(Network,self).__init__()
self.fc1 = nn.Linear(28*28, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
def forward(self, x):
x = x.view(1,-1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = Network()
if torch.cuda.is_available():
model = model.cuda()
在上面的代码中,我们定义了一个具有以下架构的神经网络:-
- 输入层: 784 个节点,MNIST 图像的尺寸为 28*28,有 784 个像素,因此当展平后它将成为具有 784 个输入节点的神经网络的输入。
- 隐藏层 1: 256 个节点
- 隐藏层 2: 128 个节点
- 输出层: 10 个节点,用于 10 个类,即数字 0-9
nn.Linear()或 Linear Layer 用于对传入的数据应用线性变换。如果您熟悉 TensorFlow,它与密集层非常相似。
在forward()方法中,我们首先将图像展平并将其传递到每一层,并对其应用激活函数。之后,我们创建我们的神经网络实例,最后,我们只是检查机器是否有 GPU,如果有,我们会将模型转移到那里以加快计算速度。
定义标准和优化器
优化器定义了如何更新神经网络的权重,在本教程中,我们将使用 SGD 优化器或随机梯度下降优化器。优化器将模型参数和学习率作为输入参数。您可以尝试各种优化器,例如 Adam、Adagrad 等。
标准是您想要最小化的损失,在这种情况下是 CrossEntropyLoss(),它是 log_softmax() 和 NLLLoss() 的组合。
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)
使用验证训练神经网络
PyTorch 中的训练步骤几乎在每次训练时都相同。但在实现之前,让我们了解模型对象的 2 种模式:-
- 训练模式:由model.train()设置, 它告诉您的模型您正在训练模型。因此,像 dropout 等在训练和测试时表现不同的层可以相应地表现。
- 评估模式:由model.eval()设置,它告诉您的模型您正在测试模型。
即使您在这里不需要它,了解它们仍然更好。现在我们已经清楚了,让我们了解训练步骤:-
- 将数据移动到 GPU(可选)
- 使用optimizer.zero_grad()清除梯度
- 向前传球
- 计算损失
- 使用loss.backward()执行反向传递以计算梯度
- 使用optimizer.step()进行优化器步骤以更新权重
验证和测试步骤也相似,但您只需进行前向传递并计算损失。一个没有验证的简单训练循环如下所示:-
epochs = 5
for e in range(epochs):
train_loss = 0.0
for data, labels in tqdm(trainloader):
# Transfer Data to GPU if available
if torch.cuda.is_available():
data, labels = data.cuda(), labels.cuda()
# Clear the gradients
optimizer.zero_grad()
# Forward Pass
target = model(data)
# Find the Loss
loss = criterion(target,labels)
# Calculate gradients
loss.backward()
# Update Weights
optimizer.step()
# Calculate Loss
train_loss += loss.item()
print(f'Epoch {e+1} \t\t Training Loss: {train_loss / len(trainloader)}')
如果您添加验证循环,它将是相同的,但仅使用前向传递和损失计算。但是,您的最后一次迭代可能不是给您带来最少验证损失的迭代。为了解决这个问题,我们可以设置一个最大有效损失,它可以是np.inf ,如果当前有效损失小于我们可以保存模型的状态字典,我们可以稍后加载它,比如检查点。 state_dict是一个 OrderedDict 对象,它将每一层映射到其参数张量。
import numpy as np
epochs = 5
min_valid_loss = np.inf
for e in range(epochs):
train_loss = 0.0
model.train() # Optional when not using Model Specific layer
for data, labels in trainloader:
if torch.cuda.is_available():
data, labels = data.cuda(), labels.cuda()
optimizer.zero_grad()
target = model(data)
loss = criterion(target,labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
valid_loss = 0.0
model.eval() # Optional when not using Model Specific layer
for data, labels in validloader:
if torch.cuda.is_available():
data, labels = data.cuda(), labels.cuda()
target = model(data)
loss = criterion(target,labels)
valid_loss = loss.item() * data.size(0)
print(f'Epoch {e+1} \t\t Training Loss: {train_loss / len(trainloader)} \t\t Validation Loss: {valid_loss / len(validloader)}')
if min_valid_loss > valid_loss:
print(f'Validation Loss Decreased({min_valid_loss:.6f}--->{valid_loss:.6f}) \t Saving The Model')
min_valid_loss = valid_loss
# Saving State Dict
torch.save(model.state_dict(), 'saved_model.pth')
运行上述代码后,您应该得到以下输出,尽管您的损失可能会有所不同:-
代码
Python3
import torch
from torch import nn
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, random_split
import numpy as np
#Declare transform to convert raw data to tensor
transforms = transforms.Compose([
transforms.ToTensor()
])
# Loading Data and splitting it into train and validation data
train = datasets.MNIST('', train = True, transform = transforms, download = True)
train, valid = random_split(train,[50000,10000])
# Create Dataloader of the above tensor with batch size = 32
trainloader = DataLoader(train, batch_size=32)
validloader = DataLoader(valid, batch_size=32)
# Building Our Mode
class Network(nn.Module):
# Declaring the Architecture
def __init__(self):
super(Network,self).__init__()
self.fc1 = nn.Linear(28*28, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
# Forward Pass
def forward(self, x):
x = x.view(x.shape[0],-1) # Flatten the images
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = Network()
if torch.cuda.is_available():
model = model.cuda()
# Declaring Criterion and Optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)
# Training with Validation
epochs = 5
min_valid_loss = np.inf
for e in range(epochs):
train_loss = 0.0
for data, labels in trainloader:
# Transfer Data to GPU if available
if torch.cuda.is_available():
data, labels = data.cuda(), labels.cuda()
# Clear the gradients
optimizer.zero_grad()
# Forward Pass
target = model(data)
# Find the Loss
loss = criterion(target,labels)
# Calculate gradients
loss.backward()
# Update Weights
optimizer.step()
# Calculate Loss
train_loss += loss.item()
valid_loss = 0.0
model.eval() # Optional when not using Model Specific layer
for data, labels in validloader:
# Transfer Data to GPU if available
if torch.cuda.is_available():
data, labels = data.cuda(), labels.cuda()
# Forward Pass
target = model(data)
# Find the Loss
loss = criterion(target,labels)
# Calculate Loss
valid_loss += loss.item()
print(f'Epoch {e+1} \t\t Training Loss: {\
train_loss / len(trainloader)} \t\t Validation Loss: {\
valid_loss / len(validloader)}')
if min_valid_loss > valid_loss:
print(f'Validation Loss Decreased({min_valid_loss:.6f\
}--->{valid_loss:.6f}) \t Saving The Model')
min_valid_loss = valid_loss
# Saving State Dict
torch.save(model.state_dict(), 'saved_model.pth')