Treinar modelos com o PyTorch

Concluído

O PyTorch é uma estrutura de machine learning comumente usada para treinar modelos de aprendizado profundo. No Azure Databricks, o PyTorch é instalado previamente em clusters de ML.

Observação

Os snippets de código desta unidade são fornecidos como exemplos para enfatizar os pontos-chave. Você terá a chance de executar o código de um exemplo completo e funcional no exercício mais adiante neste módulo.

Definir uma rede do PyTorch

No PyTorch, os modelos são baseados em uma rede definida por você. A rede consiste em várias camadas, cada uma com entradas e saídas especificadas. Além disso, o trabalho define uma função forward que aplica funções a cada camada, à medida que os dados são transmitidos pela rede.

O código de exemplo a seguir define uma rede.

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

Embora o código possa parecer complexo no início, esta classe define uma rede relativamente simples com três camadas:

  • Uma camada de entrada que aceita quatro valores de entrada e gera cinco valores de saída para a próxima camada.
  • Uma camada que aceita cinco entradas e gera cinco saídas.
  • Uma camada de saída final que aceita cinco entradas e gera três saídas.

A função forward aplica as camadas aos dados de entrada (x), transmitindo a saída de cada camada para a próxima e, por fim, retornando a saída da última camada (que contém o vetor de previsão de rótulo, y). Uma função de ativação ReLU (unidade linear) corrigida é aplicada às saídas das camadas 1 e 2 para restringir os valores de saída aos números positivos.

Observação

Dependendo do tipo de critério de perda usado, você pode optar por aplicar uma função de ativação como um log_softmax ao valor retornado para forçá-lo no intervalo de 0 a 1. No entanto, alguns critérios de perda (como CrossEntropyLoss, que é comumente usado para a classificação multiclasse) aplicam automaticamente uma função adequada.

Para criar um modelo para treinamento, basta criar uma instância da classe de rede como esta:

myModel = MyNet()

Preparar dados para modelagem

As camadas do PyTorch funcionam em dados formatados como tensores, estruturas semelhantes à matriz. Há várias funções para converter outros formatos de dados comuns em tensores, e você pode definir um carregador de dados do PyTorch para ler tensores de dados em um modelo para treinamento ou inferência.

Assim como acontece com a maioria das técnicas de machine learning supervisionadas, você deve definir conjuntos de dados separados para treinamento e validação. Essa separação permite que você valide se o modelo faz uma previsão precisa quando recebe dados nos quais ele não foi treinado.

O código a seguir define dois carregadores de dados: um para treinamento e outro para teste. Os dados de origem de cada carregador deste exemplo são considerados uma matriz Numpy de valores de recurso e uma matriz Numpy de valores de rótulo correspondentes.

# 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)

Os carregadores neste exemplo dividem os dados em lotes de 30, que são transmitidos para a função forward durante o treinamento ou a inferência.

Escolher um critério de perda e um algoritmo de otimizador

O modelo é treinado alimentando os dados de treinamento na rede, medindo a perda (a diferença agregada entre valores previstos e reais) e otimizando a rede ajustando os pesos e os saldos para minimizar a perda. Os detalhes específicos de como a perda é calculada e minimizada são controlados pelo critério de perda e pelo algoritmo de otimizador escolhido por você.

Critérios de perda

O PyTorch dá suporte a várias funções de critérios de perda, incluindo (entre muitas outras):

  • cross_entropy: uma função que mede a diferença de agregação entre valores previstos e reais para várias variáveis (normalmente usada para medir a perda de probabilidades de classe na classificação multiclasse).
  • binary_cross_entropy: uma função que mede a diferença entre as probabilidades previstas e reais (normalmente usada para medir a perda de probabilidades de classe na classificação binária).
  • mse_loss: uma função que mede a perda média de erros quadrados para valores numéricos previstos e reais (normalmente usada para regressão).

Para especificar o critério de perda que você deseja usar ao treinar seu modelo, crie uma instância da função apropriada, desta forma:

import torch.nn as nn

loss_criteria = nn.CrossEntropyLoss

Dica

Para obter mais informações sobre os critérios de perda disponíveis no PyTorch, confira Funções de perda na documentação do PyTorch.

Algoritmos de otimizador

Tendo calculado a perda, um otimizador é usado para determinar a melhor maneira de ajustar os pesos e os saldos para minimizá-lo. Os otimizadores são implementações específicas de uma abordagem de espaço descendente de gradiente para minimizar uma função. Os otimizadores disponíveis no PyTorch incluem (entre outros):

Para usar um desses algoritmos para treinar um modelo, você precisa criar uma instância do otimizador e definir os parâmetros necessários. Os parâmetros específicos variam dependendo do otimizador escolhido, mas a maioria exige que você especifique uma taxa de aprendizagem que controla o tamanho dos ajustes feitos com cada otimização.

O código a seguir cria uma instância do otimizador Adam.

import torch.optim as opt

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

Dica

Para obter mais informações sobre os otimizadores disponíveis no PyTorch, confira Algoritmos na documentação do PyTorch.

Criar funções de treinamento e teste

Depois de definir uma rede e preparar dados para ela, você pode usar os dados para treinar e testar um modelo transmitindo os dados de treinamento pela rede, calculando a perda, otimizando os pesos e os desvios da rede e validando o desempenho da rede com os dados de teste. É comum definir uma função que transmite dados pela rede para treinar o modelo com os dados de treinamento e uma função separada para testar o modelo com os dados de teste.

Criar uma função de treinamento

O exemplo a seguir mostra uma função para treinar um modelo.

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

O exemplo a seguir mostra uma função para testar o modelo.

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

Treinar o modelo em várias épocas

Para treinar um modelo de aprendizado profundo, normalmente, você executa a função de treinamento várias vezes (conhecido como épocas), com o objetivo de reduzir a perda calculada com base nos dados de treinamento a cada época. Use a função de teste para validar que a perda dos dados de teste (nos quais o modelo não foi treinado) também está sendo reduzida de modo alinhado com a perda de treinamento. Em outras palavras, que o treinamento do modelo não esteja produzindo um modelo sobreajustado aos dados de treinamento.

Dica

Você não precisa executar a função de teste para cada época. Você pode optar por executá-la a cada segunda época ou uma vez no final. No entanto, testar o modelo conforme ele é treinado pode ser útil para determinar após quantas épocas um modelo começa a ficar sobreajustado.

O código a seguir treina um modelo com mais de 50 épocas.

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)

Salvar o estado do modelo treinado

Depois de treinar um modelo com sucesso, salve os pesos e os desvios, desta forma:

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

Para carregar e usar o modelo posteriormente, crie uma instância da classe de rede na qual o modelo se baseia e carregue os pesos e os desvios salvos.

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