import torch
= torch.tensor(
tensor
[1.0, 2.0],
[3.0, 4.0],
[5.0, 6.0],
[
] )
Nesse tutorial, os principais conceitos da biblioteca PyTorch são apresentados, comparando a biblioteca com outras bibliotecas familiares, mostrando o diferencial da biblioteca e mostrando um exemplo de como criar uma rede neural simples usando o framework.
Tensores
torch
é primeiramente uma biblioteca de processamento de tensores (vetores com \(n\) dimensões).
A biblioteca permite realizar diversas interações com tensores através do objeto torch.tensor
usando uma interface similar ao numpy
, como:
Criar tensores
tensor.shape
torch.Size([3, 2])
tensor.dtype
torch.float32
Acessar elementos
= torch.tensor(
A
[1.0, 2.0],
[3.0, 4.0],
[
]
)
0] A[
tensor([1., 2.])
0, 0] A[
tensor(1.)
-1] A[:,
tensor([2., 4.])
Manipular tensores
2 * 2) A.reshape(
tensor([1., 2., 3., 4.])
4, 1)) A.view((
tensor([[1.],
[2.],
[3.],
[4.]])
# Transposição tensorial
A.T
tensor([[1., 3.],
[2., 4.]])
Realizar operações
# x^2 realizado elemento-a-elemento em A
**2 A
tensor([[ 1., 4.],
[ 9., 16.]])
= torch.tensor(
B
[5.0, 6.0],
[7.0, 8.0],
[
]
)
# Multiplicação elemento-a-elemento ou A.mul(B)
* B A
tensor([[ 5., 12.],
[21., 32.]])
# Multiplicação tensorial ou A.matmul(B)
@ B A
tensor([[19., 22.],
[43., 50.]])
# Também realiza o produto escalar em tensores unidimensionais (vetores),
# assim como A[0].dot(B[0])
0] @ A[1] A[
tensor(11.)
# Operações podem ser feitas in-place, reduzindo seu gasto de memória
2)
B.pow_( B
tensor([[25., 36.],
[49., 64.]])
Diferenciais
Porém, o torch
possui outras funcionalidades voltadas à otimização e inferência de modelos de forma computacionalmente eficiente, como:
- Paralelismo usando GPUs
- Diferenciação automática
- Carregamento eficiente de dados em memória
- Muitas utilidades para o treinamento e avaliação de redes neurais
Paralelismo usando GPUs
Além de operações em CPU, é possível usar diferentes tipos de dispositivos como GPUs para acelerar a inferêncis de certas operações através do hardware especializado.
# Caso sua máquina possua uma GPU NVIDIA
= "cuda" if torch.cuda.is_available() else "cpu"
device device
'cuda'
1, 2, 3], device=device) torch.tensor([
tensor([1, 2, 3], device='cuda:0')
= torch.tensor([1, 2, 3], device="cpu")
tensor = tensor.to(device)
tensor tensor.device
device(type='cpu')
Diferenciação automática
O PyTorch possui um motor de diferenciação automática (torch.autograd
), que simplifica e otimiza o processo de otimização de parâmetros através quando técnicas como gradiente descendente e backpropagation são usadas.
O módulo torch.nn
oferece a classe abstrata Module
para utilizar a diferenciação automática de forma simples em componentes como:
- Parâmetros treináveis
- Camadas densas e funções de ativação em redes neurais
- Funções de custo / perda / recompensa
Basta que seja possível implementar os métodos forward
e backward
para esse componente.
Internamente, quando objetos diferenciáveis (como Module
) são criados, é gerado um grafo representando suas etapas de execução (com base nos seus atributos diferenciáveis).
from torch import nn
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.input_layer = nn.Linear(5, 10)
self.activation = nn.ReLU()
self.output_layer = nn.Linear(10, 2)
def forward(self, x):
= self.input_layer(x)
hidden_input = self.activation(hidden_input)
logits_input = self.output_layer(logits_input)
logits return logits
= NeuralNetwork() model
Ao executar forward
, é atualizado o gradiente (atributo grad
) com base na sua função (atributo grad_fn
) e na topologia do grafo usando backpropagation.
= nn.CrossEntropyLoss()
loss_fn = torch.ones(5)
X = torch.ones(2)
y_eval
# executa o forward implicitamente
= model(X)
y_pred = loss_fn(y_pred, y_eval)
loss loss
tensor(1.4235, grad_fn=<DivBackward1>)
Ao executar backward
, é gerado um gradiente com base nos resultados do forward
usando backpropagation.
loss.backward()
model.input_layer.weight.grad
tensor([[ 0.1061, 0.1061, 0.1061, 0.1061, 0.1061],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0294, 0.0294, 0.0294, 0.0294, 0.0294],
[ 0.1186, 0.1186, 0.1186, 0.1186, 0.1186],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0493, 0.0493, 0.0493, 0.0493, 0.0493],
[-0.0381, -0.0381, -0.0381, -0.0381, -0.0381],
[ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],
[ 0.0979, 0.0979, 0.0979, 0.0979, 0.0979]])
Usando esses dois métodos, é possível gerar um método de otimização para treinar os parâmetros de um modelo para um conjunto de dados de forma simples.
= NeuralNetwork()
model = nn.CrossEntropyLoss()
loss_fn = torch.optim.SGD(model.parameters())
optimizer
# Cria tensores 3D com valores aleatórios entre 0 e 1 (representando os dados de treino)
= torch.randint(0, 2, (10, 5), dtype=torch.float32)
X_train_dataset = torch.randint(0, 2, (10, 2), dtype=torch.float32)
y_train_dataset
# Boa prática antes do treino para garantir o funcionamento de certos componentes
model.train()
= 1
num_epochs
for epoch in range(1, num_epochs + 1):
print(f"Epoch: {epoch}")
for index, (X, y_eval) in enumerate(zip(X_train_dataset, y_train_dataset), 1):
= model(X)
y_pred = loss_fn(y_pred, y_eval)
loss print(f"{index:>2}/{len(X_train_dataset)}: {loss:<+5.2f}")
loss.backward()# Atualiza os parâmetros do modelo
optimizer.step()# Zera os gradientes depois de atualizar os parâmetros
optimizer.zero_grad()
Epoch: 1
1/10: -0.00
2/10: +0.71
3/10: +0.48
4/10: +1.39
5/10: +0.64
6/10: +0.86
7/10: -0.00
8/10: +1.39
9/10: -0.00
10/10: -0.00