Treinar modelos com o PyTorch
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):
- Adadelta: um otimizador baseado no algoritmo Taxa de Aprendizagem Adaptável.
- Adam: um otimizador computacionalmente eficiente baseado no algoritmo Adam.
- SGD: um otimizador baseado no algoritmo de espaço descendente de gradiente estocástico.
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))