Este não é um guia de estilo oficial para Pytorch. Este documento resume as melhores práticas de mais de um ano de experiência com aprendizado profundo usando a estrutura Pytorch. Observe que os aprendizados que compartilhamos vêm principalmente de uma perspectiva de pesquisa e startup.
Este é um projeto aberto e outros colaboradores são muito bem -vindos para editar e melhorar o documento.
Você encontrará três partes principais deste documento. Primeiro, uma rápida recapitulação das melhores práticas em Python, seguida por algumas dicas e recomendações usando Pytorch. Por fim, compartilhamos algumas idéias e experiências usando outras estruturas que nos ajudaram a melhorar nosso fluxo de trabalho.
Atualização 20.12.2020
Atualização 30.4.2019
Depois de tanto feedback positivo, também adicionei um resumo dos blocos de construção comumente usados em nossos projetos de ânimo leve: você encontrará blocos de construção para (auto-atimento, perda perceptiva usando VGG, normalização espectral, normalização de instância adaptativa, ...)
Trechos de código para perdas, camadas e outros blocos de construção
Pela nossa experiência, recomendamos o uso do Python 3.6+ por causa dos seguintes recursos que se tornaram muito úteis para um código limpo e simples:
Tentamos seguir o Google Styleguide para Python. Consulte o guia de estilo bem documentado sobre o código Python fornecido pelo Google.
Fornecemos aqui um resumo das regras mais usadas:
De 3.16.4
| Tipo | Convenção | Exemplo |
|---|---|---|
| Pacotes e módulos | inferior_with_under | de prefetch_generator importar BackgroundGenerator |
| Classes | CapWords | Classe Dataloader |
| Constantes | Caps_with_under | Batch_size = 16 |
| Instâncias | inferior_with_under | conjunto de dados = conjunto de dados |
| Métodos e funções | inferior_with_under () | def Visualize_tensor () |
| Variáveis | inferior_with_under | background_color = 'azul' |
Em geral, recomendamos o uso de um IDE, como o Código do Visual Studio ou o PyCharm. Enquanto o Código VS fornece sintaxe destacando e preenchimento automático em um editor relativamente leve Pycharm possui muitos recursos avançados para trabalhar com clusters remotos. O código VS se tornou muito poderoso com seu ecossistema de extensões em rápido crescimento.
Verifique se você tem as seguintes extensões instaladas:
Se configurado corretamente, isso permite que você faça o seguinte:
Em geral, recomendamos o uso de notebooks Jupyter para exploração/ brincar inicial com novos modelos e código. Os scripts do Python devem ser usados assim que você desejar treinar o modelo em um conjunto de dados maior, onde também é mais importante a reprodutibilidade.
Nosso fluxo de trabalho recomendado:
| Notebook Jupyter | Scripts Python |
|---|---|
| + Exploração | + Executando trabalhos mais longos sem interrupção |
| + Depuração | + Fácil de rastrear mudanças com o Git |
| - pode se tornar um arquivo enorme | - Depuração significa principalmente reformular todo o script |
| - pode ser interrompido (não use para treinamento longo) | |
| - propenso a erros e se tornar uma bagunça |
Bibliotecas comumente usadas:
| Nome | Descrição | Usado para |
|---|---|---|
| tocha | Estrutura básica para trabalhar com redes neurais | criando tensores, redes e treinando -os usando o backprop |
| Torchvision | Módulos de visão computacional pytorch | Pré -processamento de dados de imagem, aumento, pós -processamento |
| Travesseiro (PIL) | Biblioteca de imagem Python | Carregando imagens e armazenando -as |
| Numpy | Pacote para computação científica com Python | Pré -processamento de dados e pós -processamento |
| prefetch_generator | Biblioteca para processamento de fundo | Carregando o próximo lote em segundo plano durante a computação |
| TQDM | Barra de progresso | Progresso durante o treinamento de cada época |
| Torchinfo | Imprimir resumo do modelo semelhante a Keras para Pytorch | Exibe rede, são parâmetros e tamanhos em cada camada |
| Torch.utils.tensorboard | Tensorboard dentro de Pytorch | Experimentos de registro e mostrando -os em tensorboard |
Não coloque todas as camadas e modelos no mesmo arquivo. Uma prática recomendada é separar as redes finais em um arquivo separado ( Networks.py ) e manter as camadas, perdas e OPS nos respectivos arquivos ( sayers.py , losses.py , ops.py ). O modelo acabado (composto de uma ou várias redes) deve ser referência em um arquivo com seu nome (por exemplo, Yolov3.py , dcgan.py )
A rotina principal, respectiva os scripts de trem e teste, devem importar apenas o arquivo com o nome do modelo.
Recomendamos dividir a rede em suas peças reutilizáveis menores. Uma rede é um módulo de nn. que consiste em operações ou outros nn.module s como blocos de construção. As funções de perda também são NN.Module e podem, portanto, ser diretamente integradas à rede.
Uma classe herdadora do NN.Module deve ter um método avançado implementando o passe direto da respectiva camada ou operação.
Um nn.module pode ser usado nos dados de entrada usando self.net (entrada) . Isso simplesmente usa o método Call () do objeto para alimentar a entrada através do módulo.
output = self . net ( input )Use o seguinte padrão para redes simples com uma única entrada e saída única:
class ConvBlock ( nn . Module ):
def __init__ ( self ):
super ( ConvBlock , self ). __init__ ()
self . block = nn . Sequential (
nn . Conv2d (...),
nn . ReLU (),
nn . BatchNorm2d (...)
)
def forward ( self , x ):
return self . block ( x )
class SimpleNetwork ( nn . Module ):
def __init__ ( self , num_resnet_blocks = 6 ):
super ( SimpleNetwork , self ). __init__ ()
# here we add the individual layers
layers = [ ConvBlock (...)]
for i in range ( num_resnet_blocks ):
layers += [ ResBlock (...)]
self . net = nn . Sequential ( * layers )
def forward ( self , x ):
return self . net ( x )Observe o seguinte:
class ResnetBlock ( nn . Module ):
def __init__ ( self , dim , padding_type , norm_layer , use_dropout , use_bias ):
super ( ResnetBlock , self ). __init__ ()
self . conv_block = self . build_conv_block (...)
def build_conv_block ( self , ...):
conv_block = []
conv_block += [ nn . Conv2d (...),
norm_layer (...),
nn . ReLU ()]
if use_dropout :
conv_block += [ nn . Dropout (...)]
conv_block += [ nn . Conv2d (...),
norm_layer (...)]
return nn . Sequential ( * conv_block )
def forward ( self , x ):
out = x + self . conv_block ( x )
return outAqui, a conexão Skip de um bloco de resnet foi implementada diretamente no passe para a frente. O Pytorch permite operações dinâmicas durante o passe para a frente.
Para uma rede que requer vários resultados, como criar uma perda perceptiva usando uma rede VGG pré -treinada, usamos o seguinte padrão:
class Vgg19 ( nn . Module ):
def __init__ ( self , requires_grad = False ):
super ( Vgg19 , self ). __init__ ()
vgg_pretrained_features = models . vgg19 ( pretrained = True ). features
self . slice1 = torch . nn . Sequential ()
self . slice2 = torch . nn . Sequential ()
self . slice3 = torch . nn . Sequential ()
for x in range ( 7 ):
self . slice1 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 7 , 21 ):
self . slice2 . add_module ( str ( x ), vgg_pretrained_features [ x ])
for x in range ( 21 , 30 ):
self . slice3 . add_module ( str ( x ), vgg_pretrained_features [ x ])
if not requires_grad :
for param in self . parameters ():
param . requires_grad = False
def forward ( self , x ):
h_relu1 = self . slice1 ( x )
h_relu2 = self . slice2 ( h_relu1 )
h_relu3 = self . slice3 ( h_relu2 )
out = [ h_relu1 , h_relu2 , h_relu3 ]
return outObserve aqui o seguinte:
Mesmo que o Pytorch já tenha muita função de perda padrão, às vezes pode ser necessário criar sua própria função de perda. Para isso, crie uma losses.py de arquivo separada.py e estenda a classe nn.Module para criar sua função de perda personalizada:
class CustomLoss ( nn . Module ):
def __init__ ( self ):
super ( CustomLoss , self ). __init__ ()
def forward ( self , x , y ):
loss = torch . mean (( x - y ) ** 2 )
return loss Um exemplo completo é fornecido na pasta Cifar10-Exemplo deste repositório.
Observe que usamos os seguintes padrões:
# import statements
import torch
import torch . nn as nn
from torch . utils import data
...
# set flags / seeds
torch . backends . cudnn . benchmark = True
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )
...
# Start with main code
if __name__ == '__main__' :
# argparse for additional flags for experiment
parser = argparse . ArgumentParser ( description = "Train a network for ..." )
...
opt = parser . parse_args ()
# add code for datasets (we always use train and validation/ test set)
data_transforms = transforms . Compose ([
transforms . Resize (( opt . img_size , opt . img_size )),
transforms . RandomHorizontalFlip (),
transforms . ToTensor (),
transforms . Normalize (( 0.5 , 0.5 , 0.5 ), ( 0.5 , 0.5 , 0.5 ))
])
train_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "train" ),
transform = data_transforms )
train_data_loader = data . DataLoader ( train_dataset , ...)
test_dataset = datasets . ImageFolder (
root = os . path . join ( opt . path_to_data , "test" ),
transform = data_transforms )
test_data_loader = data . DataLoader ( test_dataset ...)
...
# instantiate network (which has been imported from *networks.py*)
net = MyNetwork (...)
...
# create losses (criterion in pytorch)
criterion_L1 = torch . nn . L1Loss ()
...
# if running on GPU and we want to use cuda move model there
use_cuda = torch . cuda . is_available ()
if use_cuda :
net = net . cuda ()
...
# create optimizers
optim = torch . optim . Adam ( net . parameters (), lr = opt . lr )
...
# load checkpoint if needed/ wanted
start_n_iter = 0
start_epoch = 0
if opt . resume :
ckpt = load_checkpoint ( opt . path_to_checkpoint ) # custom method for loading last checkpoint
net . load_state_dict ( ckpt [ 'net' ])
start_epoch = ckpt [ 'epoch' ]
start_n_iter = ckpt [ 'n_iter' ]
optim . load_state_dict ( ckpt [ 'optim' ])
print ( "last checkpoint restored" )
...
# if we want to run experiment on multiple GPUs we move the models there
net = torch . nn . DataParallel ( net )
...
# typically we use tensorboardX to keep track of experiments
writer = SummaryWriter (...)
# now we start the main loop
n_iter = start_n_iter
for epoch in range ( start_epoch , opt . epochs ):
# set models to train mode
net . train ()
...
# use prefetch_generator and tqdm for iterating through data
pbar = tqdm ( enumerate ( BackgroundGenerator ( train_data_loader , ...)),
total = len ( train_data_loader ))
start_time = time . time ()
# for loop going through dataset
for i , data in pbar :
# data preparation
img , label = data
if use_cuda :
img = img . cuda ()
label = label . cuda ()
...
# It's very good practice to keep track of preparation time and computation time using tqdm to find any issues in your dataloader
prepare_time = start_time - time . time ()
# forward and backward pass
optim . zero_grad ()
...
loss . backward ()
optim . step ()
...
# udpate tensorboardX
writer . add_scalar (..., n_iter )
...
# compute computation time and *compute_efficiency*
process_time = start_time - time . time () - prepare_time
pbar . set_description ( "Compute efficiency: {:.2f}, epoch: {}/{}:" . format (
process_time / ( process_time + prepare_time ), epoch , opt . epochs ))
start_time = time . time ()
# maybe do a test pass every x epochs
if epoch % x == x - 1 :
# bring models to evaluation mode
net . eval ()
...
#do some tests
pbar = tqdm ( enumerate ( BackgroundGenerator ( test_data_loader , ...)),
total = len ( test_data_loader ))
for i , data in pbar :
...
# save checkpoint if needed
...Existem dois padrões distintos em Pytorch para usar várias GPUs para treinamento. Pela nossa experiência, ambos os padrões são válidos. O primeiro resulta, no entanto, em um código mais agradável e menos. O segundo parece ter uma pequena vantagem de desempenho devido a menos comunicação entre as GPUs. Fiz uma pergunta no fórum oficial de Pytorch sobre as duas abordagens aqui
O mais comum é simplesmente dividir os lotes de todas as redes nas GPUs individuais.
Um modelo em execução em 1 GPU com tamanho de lote 64, portanto, seria executado em 2 GPUs, com cada um tamanho de lote de 32. Isso pode ser feito automaticamente, envolvendo o modelo por nn.dataparalleallel (modelo) .
Esse padrão é menos comumente usado. Um repositório implementando esta abordagem é mostrado aqui na implementação PIX2PIXHD pela NVIDIA
Numpy é executado na CPU e é mais lento que o código da tocha. Desde que a Torch foi desenvolvida com o Numpy em mente, a maioria das funções Numpy já é suportada por Pytorch.
O pipeline de carregamento de dados deve ser independente do seu código de treinamento principal. A Pytorch usa trabalhadores em segundo plano para carregar os dados com mais eficiência e sem perturbar o processo de treinamento principal.
Normalmente, treinamos nossos modelos para milhares de etapas. Portanto, basta registrar a perda e outros resultados a cada neve etapa para reduzir a sobrecarga. Especialmente, economizar resultados intermediários como imagens pode ser caro durante o treinamento.
É muito útil usar argumentos da linha de comando para definir parâmetros durante a execução do código ( tamanho do lote , taxa de aprendizado etc.). Uma maneira fácil de acompanhar os argumentos para um experimento é apenas imprimir o dicionário recebido de parse_args :
...
# saves arguments to config.txt file
opt = parser . parse_args ()
with open ( "config.txt" , "w" ) as f :
f . write ( opt . __str__ ())
...A Pytorch acompanha todas as operações envolvendo tensores para diferenciação automática. Use .detach () para evitar a gravação de operações desnecessárias.
Você pode imprimir variáveis diretamente, no entanto, é recomendável usar variável.detach () ou variable.item () . Nas versões anteriores de Pytorch <0,4, você deve usar os dados .datos para acessar o tensor de uma variável.
As duas maneiras não são idênticas, como apontado em uma das questões aqui:
output = self . net . forward ( input )
# they are not equal!
output = self . net ( input )Recomendamos definir as seguintes sementes no início do seu código:
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )Nas GPUs da NVIDIA, você pode adicionar a seguinte linha no início do nosso código. Isso permitirá que o back -end do CUDA otimize seu gráfico durante a primeira execução. No entanto, esteja ciente de que, se você alterar o tamanho do tensor de entrada/saída de rede, o gráfico será otimizado cada vez que ocorre uma alteração. Isso pode levar a tempo de execução muito lento e erros de memória. Defina apenas esse sinalizador se sua entrada e saída sempre tiveram a mesma forma. Geralmente, isso resulta em uma melhoria de cerca de 20%.
torch . backends . cudnn . benchmark = TrueDepende da máquina usada, do pipeline de pré -processamento e do tamanho da rede. Em execução em um SSD em uma GPU 1080TI, vemos uma eficiência de computação de quase 1,0, que é um cenário ideal. Se redes rasas (pequenas) ou um disco rígido lento for usado, o número pode cair para cerca de 0,1-0,2, dependendo da sua configuração.
Em Pytorch, podemos implementar tamanhos de lote muito facilmente virtuais. Apenas impedimos que o otimizador faça uma atualização dos parâmetros e resumisse os gradientes para os ciclos Batch_Size .
...
# in the main loop
out = net ( input )
loss = criterion ( out , label )
# we just call backward to sum up gradients but don't perform step here
loss . backward ()
total_loss += loss . item () / batch_size
if n_iter % batch_size == batch_size - 1 :
# here we perform out optimization step using a virtual batch size
optim . step ()
optim . zero_grad ()
print ( 'Total loss: ' , total_loss )
total_loss = 0.0
...Podemos acessar a taxa de aprendizado diretamente usando o otimizador instanciado, como mostrado aqui:
...
for param_group in optim . param_groups :
old_lr = param_group [ 'lr' ]
new_lr = old_lr * 0.1
param_group [ 'lr' ] = new_lr
print ( 'Updated lr from {} to {}' . format ( old_lr , new_lr ))
...Se você deseja usar um modelo pré-terenciado, como o VGG, para calcular uma perda, mas não treiná-lo (por exemplo, perda perceptiva de transferência de estilo/ gans/ codificador automático), você pode usar o seguinte padrão:
...
# instantiate the model
pretrained_VGG = VGG19 (...)
# disable gradients (prevent training)
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = False
...
# you don't have to use the no_grad() namespace but can just run the model
# no gradients will be computed for the VGG model
out_real = pretrained_VGG ( input_a )
out_fake = pretrained_VGG ( input_b )
loss = any_criterion ( out_real , out_fake )
...Esses métodos são usados para definir camadas como BatchNorm2D ou Dropout2D do treinamento ao modo de inferência. Todo módulo que herda do NN.Module possui um atributo chamado isTreining . .eval () e .Train () simplesmente define esse atributo para true/ false. Para obter mais informações de como esse método é implementado, dê uma olhada no código do módulo em pytorch
Certifique -se de que nenhum gradiente seja calculado e armazenado durante a execução do código. Você pode simplesmente usar o seguinte padrão para garantir que:
with torch . no_grad ():
# run model here
out_tensor = net ( in_tensor )Em Pytorch, você pode congelar camadas. Isso impedirá que eles sejam atualizados durante uma etapa de otimização.
# you can freeze whole modules using
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = FalseDesde que o pytorch 0.4 * variável e tensor foram mesclados. Não precisamos mais criar explicitamente um objeto variável .
A versão C ++ é cerca de 10% mais rápida
Pendência...
Pela nossa experiência, você pode ganhar cerca de 20% de aceleração. Mas a primeira vez que você executa seu modelo, leva algum tempo para criar o gráfico otimizado. Em alguns casos (loops no passe para a frente, nenhuma forma de entrada fixa, se/else for forward etc.) esse sinalizador pode resultar em erros fora da memória ou de outros erros.
Pendência...
Se libertar um tensor de um gráfico de computação. Uma boa ilustração é mostrada aqui
Por favor, dê feedback sobre como podemos melhorar este guia de estilo! Você pode abrir um problema ou propor alterações criando uma solicitação de tração.
Se você gosta deste repositório, não se esqueça de conferir outras estruturas nossas: