使用 PyTorch 训练模型

已完成

PyTorch 是用于训练深度学习模型的常用机器学习框架。 在 Azure Databricks 中,PyTorch 预安装在 ML 群集中。

注意

本单元中的代码片段作为示例提供,以强调要点。 在本模块后面的练习中,你将有机会运行一个完整的、有效的示例代码。

定义 PyTorch 网络

在 PyTorch 中,模型基于定义的网络。 该网络由多个层组成,其中每个层都有指定的输入和输出。 此外,该网络定义了一个 forward 函数,当数据通过网络传递时,该函数会将函数应用于每个层。

以下示例代码定义网络。

import torch
import torch.nn as nn
import torch.nn.functional as F

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.layer1 = nn.Linear(4, 5)
        self.layer2 = nn.Linear(5, 5)
        self.layer3 = nn.Linear(5, 3)

    def forward(self, x):
        layer1_output = torch.relu(self.layer1(x))
        layer2_output = torch.relu(self.layer2(layer1_output))
        y = self.layer3(layer2_output)
        return y

虽然代码乍一看似乎很复杂,但此类定义了一个相对简单的网络,其中包含三个层:

  • 一个输入层,它接受四个输入值并为下一个层生成五个输出值。
  • 一个接受五个输入并生成五个输出的层。
  • 一个最终输出层,它接受五个输入并生成三个输出。

forward 函数将层应用于输入数据 (x),并将每一层的输出传递到下一层,最后返回最后一层的输出(其中包含标签预测向量 y)。 线性整流函数 (ReLU) 激活函数应用于第 1 层和第 2 层的输出,以将输出值限制为正数。

注意

根据所使用的损失标准的类型,可以选择将 log_softmax 等激活函数应用于返回值,以强制将该值范围限制为 0 - 1。 但是,某些损失标准(例如通常用于多类别分类的 CrossEntropyLoss)会自动应用合适的函数。

要创建用于训练的模型,只需创建网络类的实例,如下所示:

myModel = MyNet()

准备建模的数据

PyTorch 层处理格式为张量(类似矩阵的结构)的数据。 有多种函数可以将其他常见数据格式转换为张量,你可以定义 PyTorch 数据加载程序,用于将数据张量读取到用于训练或推理的模型。

与大多数监督式机器学习方法一样,你应该为训练和验证定义单独的数据集。 通过这种分离,可以验证模型在显示未经训练的数据时是否准确预测。

下面的代码定义了两个数据加载程序:一个用于训练,另一个用于测试。 假定此示例中每个加载程序的源数据为 Numpy 特征值数组和 Numpy 对应标签值数组。

# Create a dataset and loader for the training data and labels
train_x = torch.Tensor(x_train).float()
train_y = torch.Tensor(y_train).long()
train_ds = td.TensorDataset(train_x,train_y)
train_loader = td.DataLoader(train_ds, batch_size=20,
    shuffle=False, num_workers=1)

# Create a dataset and loader for the test data and labels
test_x = torch.Tensor(x_test).float()
test_y = torch.Tensor(y_test).long()
test_ds = td.TensorDataset(test_x,test_y)
test_loader = td.DataLoader(test_ds, batch_size=20,
    shuffle=False, num_workers=1)

此示例中的加载程序将数据拆分为 30 个批,这些批在训练或推理过程中传递给 forward 函数。

选择损失标准和优化器算法

通过将训练数据馈送到网络、度量损失(预测值和实际值之间的聚合差),并通过调整权重和平衡来优化网络,从而训练模型,以便将损失降到最低。 关于如何计算和最小化损失的具体详细信息取决于所选的损失标准和优化器算法。

损失标准

PyTorch 支持多种损失标准函数,包括(以及许多其他函数):

  • cross_entropy:用于度量多个变量的预测值和实际值之间的聚合差的函数(通常用于度量多类别分类中类别概率的损失)。
  • binary_cross_entropy:用于度量预测概率和实际概率之间差异的函数(通常用于度量二元分类中类别概率的损失)。
  • mse_loss:用于度量预测数值和实际数值的均方误差损失的函数(通常用于回归函数)。

若要指定训练模型时要使用的损失标准,可以创建相应函数的实例,如下所示:

import torch.nn as nn

loss_criteria = nn.CrossEntropyLoss

提示

有关 PyTorch 中可用损失标准的详细信息,请参阅 PyTorch 文档中的损失函数

优化器算法

计算出损失后,将使用优化器来确定如何最好地调整权重和平衡,以使其最小化。 优化器是用于最小化函数的梯度下降方法的特定实现。 PyTorch 中可用的优化器包括(以及其他优化器):

若要使用这些算法中的任何一种来训练模型,需要创建优化器的实例并设置任何所需的参数。 具体参数因所选优化器而异,但大多数参数都要求指定用于控制对每次优化所做的调整大小的学习率。

以下代码创建 Adam 优化器的实例。

import torch.optim as opt

learning_rate = 0.001
optimizer = opt.Adam(model.parameters(), lr=learning_rate)

提示

有关 PyTorch 中可用优化器的详细信息,请参阅 PyTorch 文档中的算法

创建训练和测试函数

定义网络并为其准备数据后,可以使用数据来训练和测试模型,方法是通过网络传递训练数据、计算损失、优化网络权重和偏差,以及使用测试数据验证网络性能。 通常的做法是,定义通过网络传递数据的函数以使用训练数据来训练模型,以及定义用于使用测试数据来测试模型的单独函数。

创建训练函数

以下示例显示用于训练模型的函数。

def train(model, data_loader, optimizer):

    # Use GPU if available, otherwise CPU
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)
    
    # Set the model to training mode (to enable backpropagation)
    model.train()
    train_loss = 0
    
    # Feed the batches of data forward through the network
    for batch, tensor in enumerate(data_loader):
        data, target = tensor # Specify features and labels in a tensor
        optimizer.zero_grad() # Reset optimizer state
        out = model(data) # Pass the data through the network
        loss = loss_criteria(out, target) # Calculate the loss
        train_loss += loss.item() # Keep a running total of loss for each batch

        # backpropagate adjustments to weights/bias
        loss.backward()
        optimizer.step()

    #Return average loss for all batches
    avg_loss = train_loss / (batch+1)
    print('Training set: Average loss: {:.6f}'.format(avg_loss))
    return avg_loss

以下示例显示用于测试模型的函数。

def test(model, data_loader):
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)
    # Switch the model to evaluation mode (so we don't backpropagate)
    model.eval()
    test_loss = 0
    correct = 0

    # Pass the data through with no gradient computation
    with torch.no_grad():
        batch_count = 0
        for batch, tensor in enumerate(data_loader):
            batch_count += 1
            data, target = tensor
            # Get the predictions
            out = model(data)

            # calculate the loss
            test_loss += loss_criteria(out, target).item()

            # Calculate the accuracy
            _, predicted = torch.max(out.data, 1)
            correct += torch.sum(target==predicted).item()
            
    # Calculate the average loss and total accuracy for all batches
    avg_loss = test_loss/batch_count
    print('Validation set: Average loss: {:.6f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        avg_loss, correct, len(data_loader.dataset),
        100. * correct / len(data_loader.dataset)))
    return avg_loss

在多个时期训练模型

要训练深度学习模型,通常会多次运行训练函数(称为“时期”),目的是减少每个时期根据训练数据计算得出的损失。 可以使用测试函数来验证根据测试数据(未训练模型)得出的损失是否也随着训练损失而减少,换句话说,模型训练不会生成过度拟合训练数据的模型。

提示

无需为每个时期都运行测试函数。 可选择每两个时期运行一次,也可在结束时运行一次。 但在训练模型时,对该模型进行测试有助于确定其在多少个时期后开始变得过度拟合。

以下代码训练模型超过 50 个时期。

epochs = 50
for epoch in range(1, epochs + 1):

    # print the epoch number
    print('Epoch: {}'.format(epoch))
    
    # Feed training data into the model to optimize the weights
    train_loss = train(model, train_loader, optimizer)
    print(train_loss)
    
    # Feed the test data into the model to check its performance
    test_loss = test(model, test_loader)
    print(test_loss)

保存经过训练的模型状态

成功训练模型后,可以保存其权重和偏差,如下所示:

model_file = '/dbfs/my_model.pkl'
torch.save(model.state_dict(), model_file)

要在以后加载和使用模型,请创建模型所基于的网络类的实例并加载保存的权重和偏差。

model = myNet()
model.load_state_dict(torch.load(model_file))