Ini bukan panduan gaya resmi untuk Pytorch. Dokumen ini merangkum praktik terbaik dari lebih dari satu tahun pengalaman dengan pembelajaran mendalam menggunakan kerangka Pytorch. Perhatikan bahwa pembelajaran yang kami bagikan sebagian besar berasal dari perspektif penelitian dan startup.
Ini adalah proyek terbuka dan kolaborator lainnya sangat disambut untuk mengedit dan meningkatkan dokumen.
Anda akan menemukan tiga bagian utama dari dokumen ini. Pertama, rekap cepat praktik terbaik di Python, diikuti oleh beberapa tips dan rekomendasi menggunakan Pytorch. Akhirnya, kami berbagi beberapa wawasan dan pengalaman menggunakan kerangka kerja lain yang membantu kami secara umum meningkatkan alur kerja kami.
UPDATE 20.12.2020
UPDATE 30.4.2019
Setelah begitu banyak umpan balik positif, saya juga menambahkan ringkasan blok bangunan yang umum digunakan dari proyek kami dengan ringan: Anda akan menemukan blok bangunan untuk (perhatian, kehilangan perseptual menggunakan VGG, normalisasi spektral, normalisasi instance adaptif, ...)
Cuplikan kode untuk kerugian, lapisan, dan blok bangunan lainnya
Dari pengalaman kami, kami sarankan menggunakan Python 3.6+ karena fitur -fitur berikut yang menjadi sangat berguna untuk kode yang bersih dan sederhana:
Kami mencoba mengikuti Google StyleGuide untuk Python. Silakan merujuk ke panduan gaya yang terdokumentasi dengan baik pada kode Python yang disediakan oleh Google.
Kami memberikan ringkasan aturan yang paling umum digunakan:
Dari 3.16.4
| Jenis | Konvensi | Contoh |
|---|---|---|
| Paket & Modul | lower_with_under | dari prefetch_generator impor latar belakanggenerator |
| Kelas | Capwords | Kelas Dataloader |
| Konstanta | Caps_with_under | Batch_Size = 16 |
| Instance | lower_with_under | dataset = dataset |
| Metode & Fungsi | lower_with_under () | def visualize_tensor () |
| Variabel | lower_with_under | latar belakang_color = 'biru' |
Secara umum, kami merekomendasikan penggunaan IDE seperti Visual Studio Code atau Pycharm. Sedangkan VS Code menyediakan sintaksis sintaksis dan pelengkapan otomatis dalam editor yang relatif ringan Pycharm memiliki banyak fitur canggih untuk bekerja dengan kelompok jarak jauh. VS Code telah menjadi sangat kuat dengan ekosistem ekstensi yang berkembang pesat.
Pastikan Anda memiliki ekstensi berikut yang diinstal:
Jika diatur dengan benar, ini memungkinkan Anda untuk melakukan hal berikut:
Secara umum, kami sarankan untuk menggunakan buku catatan Jupyter untuk eksplorasi awal/ bermain -main dengan model dan kode baru. Skrip python harus digunakan segera setelah Anda ingin melatih model pada dataset yang lebih besar di mana juga reproduktifitas lebih penting.
Alur kerja yang direkomendasikan kami:
| Jupyter Notebook | Skrip Python |
|---|---|
| + Eksplorasi | + Menjalankan pekerjaan yang lebih lama tanpa gangguan |
| + Debugging | + Mudah melacak perubahan dengan git |
| - bisa menjadi file besar | - Debugging sebagian besar berarti mengulangi seluruh naskah |
| - dapat terganggu (jangan gunakan untuk pelatihan lama) | |
| - rentan terhadap kesalahan dan menjadi kekacauan |
Perpustakaan yang umum digunakan:
| Nama | Keterangan | Digunakan untuk |
|---|---|---|
| obor | Kerangka dasar untuk bekerja dengan jaringan saraf | Membuat Tensor, Jaringan, dan Melatihnya Menggunakan BackProp |
| Torchvision | Modul Visi Komputer Pytorch | Preprocessing Data Gambar, Augmentasi, Postprocessing |
| Bantal (PIL) | Perpustakaan Pencitraan Python | Memuat gambar dan menyimpannya |
| Numpy | Paket untuk Komputasi Ilmiah dengan Python | Data Processing & Postprocessing |
| prefetch_generator | Perpustakaan untuk pemrosesan latar belakang | Memuat batch berikutnya di latar belakang selama perhitungan |
| TQDM | Bilah kemajuan | Kemajuan selama pelatihan setiap zaman |
| Torchinfo | Cetak ringkasan model seperti keras untuk pytorch | Menampilkan jaringan, parameter dan ukurannya di setiap lapisan |
| Torch.utils.tensorboard | Tensorboard di Pytorch | Eksperimen logging dan menunjukkannya di papan tensor |
Jangan masukkan semua lapisan dan model ke dalam file yang sama. Praktik terbaik adalah memisahkan jaringan akhir menjadi file terpisah ( networks.py ) dan menjaga lapisan, kerugian, dan op di masing -masing file ( layers.py , losses.py , ops.py ). Model jadi (terdiri dari satu atau beberapa jaringan) harus menjadi referensi dalam file dengan namanya (misalnya yolov3.py , dcgan.py )
Rutin utama, masing -masing skrip kereta dan pengujian hanya boleh mengimpor dari file yang memiliki nama model.
Kami merekomendasikan memecah jaringan menjadi potongan -potongan yang lebih kecil yang dapat digunakan kembali. Jaringan adalah nn.module yang terdiri dari operasi atau nn.module s lainnya sebagai blok bangunan. Fungsi kerugian juga nn.module dan oleh karena itu, dapat secara langsung diintegrasikan ke dalam jaringan.
Kelas yang mewarisi dari NN.module harus memiliki metode maju yang menerapkan lintasan maju dari masing -masing lapisan atau operasi.
NN.module dapat digunakan pada data input menggunakan self.net (input) . Ini hanya menggunakan metode call () dari objek untuk memberi makan input melalui modul.
output = self . net ( input )Gunakan pola berikut untuk jaringan sederhana dengan input tunggal dan output tunggal:
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 )Perhatikan yang berikut:
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 outDi sini koneksi lompatan dari blok ResNet telah diimplementasikan secara langsung di Forward Pass. Pytorch memungkinkan operasi dinamis selama umpan ke depan.
Untuk jaringan yang membutuhkan banyak output, seperti membangun kerugian perseptual menggunakan jaringan VGG pretrained, kami menggunakan pola berikut:
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 outCatatan di sini yang berikut:
Bahkan jika Pytorch sudah memiliki banyak fungsi kerugian standar, kadang -kadang mungkin diperlukan untuk membuat fungsi kerugian Anda sendiri. Untuk ini, buat losses.py file terpisah.py dan rentangkan kelas nn.Module untuk membuat fungsi kerugian kustom Anda:
class CustomLoss ( nn . Module ):
def __init__ ( self ):
super ( CustomLoss , self ). __init__ ()
def forward ( self , x , y ):
loss = torch . mean (( x - y ) ** 2 )
return loss Contoh lengkap disediakan di folder CIFAR10-contoh repositori ini.
Perhatikan bahwa kami menggunakan pola berikut:
# 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
...Ada dua pola berbeda di Pytorch untuk menggunakan beberapa GPU untuk pelatihan. Dari pengalaman kami, kedua pola itu valid. Namun yang pertama menghasilkan kode yang lebih bagus dan lebih sedikit. Yang kedua tampaknya memiliki sedikit keunggulan kinerja karena lebih sedikit komunikasi antara GPU. Saya mengajukan pertanyaan di forum Pytorch resmi tentang dua pendekatan di sini
Yang paling umum adalah dengan sekadar membagi batch semua jaringan ke GPU individu.
Model yang berjalan pada 1 GPU dengan ukuran batch 64 akan, oleh karena itu, dijalankan pada 2 GPU dengan masing -masing ukuran batch 32. Ini dapat dilakukan secara otomatis dengan membungkus model dengan nn.dataParallel (model) .
Pola ini lebih jarang digunakan. Repositori yang mengimplementasikan pendekatan ini ditampilkan di sini dalam implementasi PIX2PIXHD oleh NVIDIA
Numpy berjalan di CPU dan lebih lambat dari kode obor. Karena Torch telah dikembangkan dengan menjadi mirip dengan Numpy dalam pikiran, sebagian besar fungsi Numpy sudah didukung oleh Pytorch.
Pipa pemuatan data harus terlepas dari kode pelatihan utama Anda. Pytorch menggunakan pekerja latar belakang untuk memuat data secara lebih efisien dan tanpa mengganggu proses pelatihan utama.
Biasanya kami melatih model kami selama ribuan langkah. Oleh karena itu, itu cukup untuk mencatat kehilangan dan hasil lainnya setiap langkah ke -nth untuk mengurangi overhead. Terutama, menyimpan hasil perantara karena gambar bisa mahal selama pelatihan.
Sangat berguna untuk menggunakan argumen baris perintah untuk mengatur parameter selama eksekusi kode ( ukuran batch , tingkat pembelajaran , dll). Cara mudah untuk melacak argumen untuk percobaan adalah dengan hanya mencetak kamus yang diterima dari parse_args :
...
# saves arguments to config.txt file
opt = parser . parse_args ()
with open ( "config.txt" , "w" ) as f :
f . write ( opt . __str__ ())
...Pytorch melacak semua operasi yang melibatkan tensor untuk diferensiasi otomatis. Gunakan .detach () untuk mencegah perekaman operasi yang tidak perlu.
Anda dapat mencetak variabel secara langsung, namun disarankan untuk menggunakan variabel.detach () atau variabel.item () . Dalam versi Pytorch sebelumnya <0,4 Anda harus menggunakan .data untuk mengakses tensor suatu variabel.
Dua cara tidak identik seperti yang ditunjukkan dalam salah satu masalah di sini:
output = self . net . forward ( input )
# they are not equal!
output = self . net ( input )Kami sarankan mengatur benih berikut di awal kode Anda:
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )Pada NVIDIA GPU Anda dapat menambahkan baris berikut di awal kode kami. Ini akan memungkinkan backend CUDA untuk mengoptimalkan grafik Anda selama eksekusi pertama. Namun, ketahuilah bahwa jika Anda mengubah ukuran input/output jaringan, grafik akan dioptimalkan setiap kali perubahan terjadi. Ini dapat menyebabkan runtime yang sangat lambat dan keluar dari kesalahan memori. Hanya atur flag ini jika input dan output Anda selalu memiliki bentuk yang sama. Biasanya, ini menghasilkan peningkatan sekitar 20%.
torch . backends . cudnn . benchmark = TrueItu tergantung pada mesin yang digunakan, pipa preprocessing dan ukuran jaringan. Menjalankan pada SSD pada GPU 1080TI kita melihat efisiensi komputasi hampir 1,0 yang merupakan skenario yang ideal. Jika jaringan dangkal (kecil) atau harddisk lambat digunakan, jumlahnya dapat turun menjadi sekitar 0,1-0,2 tergantung pada pengaturan Anda.
Di Pytorch kami dapat menerapkan ukuran batch virtual yang sangat mudah. Kami hanya mencegah pengoptimal membuat pembaruan parameter dan merangkum gradien untuk siklus 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
...Kami dapat mengakses laju pembelajaran secara langsung menggunakan pengoptimal instantiated seperti yang ditunjukkan di sini:
...
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 ))
...Jika Anda ingin menggunakan model pretrained seperti VGG untuk menghitung kerugian tetapi tidak melatihnya (misalnya kehilangan persepsi dalam gaya-transfer/ gans/ auto-encoder) Anda dapat menggunakan pola berikut:
...
# 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 )
...Metode -metode tersebut digunakan untuk mengatur lapisan seperti batchnorm2d atau dropout2d dari pelatihan ke mode inferensi. Setiap modul yang mewarisi dari nn.module memiliki atribut yang disebut istraining . .eval () dan .train () cukup mengatur atribut ini ke True/ False. Untuk informasi lebih lanjut tentang bagaimana metode ini diimplementasikan, harap lihat kode modul di Pytorch
Pastikan tidak ada gradien yang dihitung dan disimpan selama eksekusi kode Anda. Anda cukup menggunakan pola berikut untuk memastikan bahwa:
with torch . no_grad ():
# run model here
out_tensor = net ( in_tensor )Di Pytorch Anda dapat membekukan lapisan. Ini akan mencegah mereka diperbarui selama langkah optimasi.
# you can freeze whole modules using
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = FalseSejak variabel pytorch 0,4 * dan tensor telah digabungkan. Kami tidak harus secara eksplisit membuat objek variabel lagi.
Versi C ++ sekitar 10% lebih cepat
TODO ...
Dari pengalaman kami, Anda dapat memperoleh sekitar 20% percepatan. Tetapi pertama kali Anda menjalankan model Anda dibutuhkan cukup waktu untuk membangun grafik yang dioptimalkan. Dalam beberapa kasus (loop in forward pass, tidak ada bentuk input tetap, jika/lain di depan, dll.) Bendera ini dapat menyebabkan keluar dari memori atau kesalahan lainnya.
TODO ...
Jika membebaskan tensor dari grafik perhitungan. Ilustrasi yang bagus ditampilkan di sini
Tolong berikan umpan balik tentang bagaimana kami dapat meningkatkan panduan gaya ini! Anda dapat membuka masalah atau mengusulkan perubahan dengan membuat permintaan tarik.
Jika Anda menyukai repo ini, jangan lupa untuk memeriksa kerangka kerja lain dari kami: