이것은 Pytorch의 공식 스타일 가이드가 아닙니다. 이 문서는 Pytorch 프레임 워크를 사용한 딥 러닝 경험 1 년 이상의 모범 사례를 요약합니다. 우리가 공유하는 학습은 대부분 연구 및 스타트 업 관점에서 비롯됩니다.
이 프로젝트는 개방형 프로젝트이며 다른 공동 작업자는 문서를 편집하고 개선 할 수있는 환영을받습니다.
이 문서의 주요 부분을 찾을 수 있습니다. 먼저, 파이썬에서 모범 사례를 빠르게 요약 한 다음 Pytorch를 사용한 몇 가지 팁과 권장 사항이 있습니다. 마지막으로, 우리는 일반적으로 워크 플로를 개선하는 데 도움이되는 다른 프레임 워크를 사용하여 몇 가지 통찰력과 경험을 공유합니다.
업데이트 20.12.2020
업데이트 30.4.2019
너무 많은 긍정적 인 피드백을받은 후 나는 또한 가볍게 프로젝트에서 일반적으로 사용되는 빌딩 블록에 대한 요약을 추가했습니다.
손실, 레이어 및 기타 빌딩 블록에 대한 코드 스 니펫
우리의 경험에서 우리는 깨끗하고 간단한 코드에 매우 편리한 다음 기능 때문에 Python 3.6+를 사용하는 것이 좋습니다.
우리는 Python의 Google StyleGuide를 따르려고 노력합니다. Google에서 제공하는 Python 코드에 대한 잘 문서화 된 스타일 안내서를 참조하십시오.
우리는 여기에서 가장 일반적으로 사용되는 규칙의 요약을 제공합니다.
3.16.4에서
| 유형 | 협약 | 예 |
|---|---|---|
| 패키지 및 모듈 | lower_with_under | prefetch_generator import BackgroundGenerator에서 |
| 수업 | 캡 워드 | 클래스 데이터 로더 |
| 상수 | caps_with_under | batch_size = 16 |
| 인스턴스 | lower_with_under | 데이터 세트 = 데이터 세트 |
| 방법 및 기능 | lower_with_under () | def visualize_tensor () |
| 변수 | lower_with_under | background_color = '파란색' |
일반적으로 Visual Studio Code 또는 Pycharm과 같은 IDE를 사용하는 것이 좋습니다. 반면 VS 코드는 비교적 가벼운 편집기에서 구문 강조 표시 및 자동 완성을 제공합니다. VS 코드는 빠르게 성장하는 확장 생태계로 인해 매우 강력 해졌습니다.
다음 확장 장치가 설치되어 있는지 확인하십시오.
올바르게 설정하면 다음을 수행 할 수 있습니다.
일반적으로 새로운 모델과 코드를 사용하여 초기 탐색/ 재생에 Jupyter 노트북을 사용하는 것이 좋습니다. 파이썬 스크립트는 재현성이 더 중요한 더 큰 데이터 세트에서 모델을 훈련 시키 자마자 사용해야합니다.
권장 워크 플로 :
| Jupyter 노트북 | 파이썬 스크립트 |
|---|---|
| + 탐사 | + 중단없이 더 긴 일자리를 실행합니다 |
| + 디버깅 | + git로 변경 사항을 쉽게 추적합니다 |
| - 거대한 파일이 될 수 있습니다 | - 디버깅은 주로 전체 스크립트를 다시 실행하는 것을 의미합니다 |
| - 방해받을 수 있습니다 (긴 훈련에 사용하지 않음) | |
| - 오류가 발생하기 쉬우 며 엉망이됩니다 |
일반적으로 사용되는 라이브러리 :
| 이름 | 설명 | 사용 |
|---|---|---|
| 토치 | 신경망 작업을위한 기본 프레임 워크 | 텐서, 네트워크 생성 및 백 프롭을 사용하여 훈련 |
| 횃불 | Pytorch 컴퓨터 비전 모듈 | 이미지 데이터 전처리, 증강, 후 처리 |
| 베개 (PIL) | 파이썬 이미징 라이브러리 | 이미지를로드하고 저장합니다 |
| Numpy | 파이썬을 사용한 과학 컴퓨팅 패키지 | 데이터 전처리 및 후 처리 |
| prefetch_generator | 백그라운드 처리를위한 라이브러리 | 계산 중에 백그라운드에서 다음 배치를로드합니다 |
| TQDM | 진행률 바 | 각 시대의 훈련 중 진보 |
| Torchinfo | Pytorch에 대한 Keras와 같은 모델 요약을 인쇄하십시오 | 네트워크를 표시하면 각 계층의 매개 변수 및 크기입니다 |
| Torch.utils.tensorboard | Pytorch 내 텐서 보드 | 실험을 기록하고 텐서 보드로 보여줍니다 |
모든 레이어와 모델을 동일한 파일에 넣지 마십시오. 모범 사례 는 최종 네트워크를 별도 의 파일 ( 네트워크 . 완성 된 모델 (하나 또는 다중 네트워크로 구성)은 이름이있는 파일에서 참조해야합니다 (예 : Yolov3.py , dcgan.py )
주요 루틴, 각각 열차 및 테스트 스크립트는 모델 이름이있는 파일에서만 가져와야합니다.
작은 재사용 가능한 조각으로 네트워크를 분해하는 것이 좋습니다. 네트워크는 운영 또는 기타 NN.Module 로 구성된 NN.Module 입니다. 손실 기능도 NN.Module 이므로 네트워크에 직접 통합 될 수 있습니다.
Nn.Module 의 클래스 상속은 각 층 또는 작동의 전방 패스를 구현하는 전방 방법이 있어야합니다.
nn.module은 self.net (입력)을 사용하여 입력 데이터에 사용할 수 있습니다. 이것은 단순히 객체의 콜 메소드를 사용하여 모듈을 통해 입력을 공급합니다.
output = self . net ( input )단일 입력과 단일 출력을 가진 간단한 네트워크에는 다음 패턴을 사용하십시오.
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 )다음을 참고하십시오.
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 out여기서 RESNET 블록 의 건너 뛰기 연결은 Forward Pass에서 직접 구현되었습니다. Pytorch는 순방향 패스 중에 동적 작업을 허용합니다.
사전 배치 된 VGG 네트워크를 사용하여 지각 손실을 구축하는 것과 같은 여러 출력이 필요한 네트워크의 경우 다음 패턴을 사용합니다.
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 out다음은 다음과 같습니다.
Pytorch에 이미 많은 표준 손실 함수가 있더라도 때로는 자신의 손실 기능을 만들어야 할 수도 있습니다. 이를 위해 별도의 파일 losses.py 작성하고 nn.Module 클래스를 확장하여 사용자 정의 손실 기능을 만듭니다.
class CustomLoss ( nn . Module ):
def __init__ ( self ):
super ( CustomLoss , self ). __init__ ()
def forward ( self , x , y ):
loss = torch . mean (( x - y ) ** 2 )
return loss 이 저장소의 CIFAR10- 측정 폴더에 전체 예제가 제공됩니다.
다음 패턴을 사용했습니다.
# 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
...Pytorch에는 훈련을 위해 여러 GPU를 사용하는 두 가지 패턴이 있습니다. 우리의 경험에서 두 패턴 모두 유효합니다. 그러나 첫 번째는 코드가 더 좋고 적습니다. 두 번째는 GPU 간의 의사 소통이 적기 때문에 약간의 성능 이점이있는 것 같습니다. 공식 Pytorch 포럼에서 두 가지 접근 방식에 대해 질문했습니다.
가장 일반적인 것은 모든 네트워크 의 배치를 개별 GPU로 간단히 나누는 것입니다.
따라서 배치 크기 64로 1 GPU에서 실행되는 모델은 각각의 배치 크기 32로 2 GPU에서 실행됩니다. NN.DataparAllel (모델) 으로 모델을 래핑하여 자동으로 수행 할 수 있습니다.
이 패턴은 덜 일반적으로 사용됩니다. 이 접근법을 구현하는 저장소는 NVIDIA의 PIX2PIXHD 구현에 나와 있습니다.
Numpy는 CPU에서 실행되며 Torch 코드보다 느립니다. Torch는 Numpy와 유사하게 개발되었으므로 대부분의 Numpy 기능은 이미 Pytorch에 의해 지원됩니다.
데이터로드 파이프 라인은 기본 교육 코드와 무관해야합니다. Pytorch는 기본 교육 프로세스를 방해하지 않고 데이터를보다 효율적으로로드하기 위해 배경 작업자를 사용합니다.
일반적으로 우리는 모델을 수천 단계로 훈련시킵니다. 따라서 오버 헤드를 줄이기 위해 손실 및 기타 결과를 기록하는 것으로 충분합니다. 특히 훈련 중에 이미지가 비용이 많이들 수 있으므로 중개 결과를 절약 할 수 있습니다.
명령 줄 인수를 사용하여 코드 실행 중 매개 변수를 설정하는 것이 매우 편리합니다 ( 배치 크기 , 학습 속도 등). 실험에 대한 인수를 추적하는 쉬운 방법은 parse_args 에서받은 사전을 인쇄하는 것입니다.
...
# saves arguments to config.txt file
opt = parser . parse_args ()
with open ( "config.txt" , "w" ) as f :
f . write ( opt . __str__ ())
...Pytorch는 자동 차별화를위한 텐서와 관련된 모든 작업을 추적합니다. 불필요한 작업의 기록을 방지하려면 .detach () 를 사용하십시오.
변수를 직접 인쇄 할 수는 있지만 변수를 사용 하는 것이 좋습니다. 이전 Pytorch 버전 <0.4에서는 변수의 텐서에 액세스하려면 .Data를 사용해야합니다.
두 가지 방법은 여기에서 문제 중 하나에서 지적 된 것처럼 동일하지 않습니다.
output = self . net . forward ( input )
# they are not equal!
output = self . net ( input )코드 시작시 다음 씨앗을 설정하는 것이 좋습니다.
np . random . seed ( 1 )
torch . manual_seed ( 1 )
torch . cuda . manual_seed ( 1 )NVIDIA GPUS에서 코드 시작시 다음 줄을 추가 할 수 있습니다. 이를 통해 CUDA 백엔드는 첫 번째 실행 중에 그래프를 최적화 할 수 있습니다. 그러나 네트워크 입력/출력 텐서 크기를 변경하면 변경이 발생할 때마다 그래프가 최적화됩니다. 이로 인해 런타임이 매우 느리고 메모리 오류가 발생할 수 있습니다. 입력과 출력의 모양이 항상 같은 경우에만이 플래그를 설정하십시오. 일반적으로 이로 인해 약 20%가 향상됩니다.
torch . backends . cudnn . benchmark = True사용 된 기계, 전처리 파이프 라인 및 네트워크 크기에 따라 다릅니다. 1080TI GPU에서 SSD를 실행하면 이상적인 시나리오 인 거의 1.0의 컴퓨팅 효율이 보입니다. 얕은 (작은) 네트워크 또는 느린 하드 디스크가 사용되는 경우 숫자는 설정에 따라 약 0.1-0.2로 떨어질 수 있습니다.
Pytorch에서는 매우 쉽게 가상 배치 크기를 구현할 수 있습니다. Optimizer가 매개 변수를 업데이트하지 못하고 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
...여기에 표시된대로 인스턴스화 된 최적화기를 사용하여 학습 속도에 직접 액세스 할 수 있습니다.
...
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 ))
...vgg와 같은 사전 여분의 모델을 사용하여 손실을 계산하지만 훈련하지 않으려면 (예 : 스타일 전송/ 간/ 자동 인코더의 지각 손실) 다음 패턴을 사용할 수 있습니다.
...
# 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 )
...이러한 방법은 Batchnorm2D 또는 Dropout2D 와 같은 레이어를 훈련에서 추론 모드로 설정하는 데 사용됩니다. Nn.Module 에서 상속되는 모든 모듈에는 ISTraining 이라는 속성이 있습니다. .eval () 및 .train () 은 단순히이 속성을 true/ false로 설정합니다. 이 방법이 구현되는 방법에 대한 자세한 내용은 Pytorch의 모듈 코드를 살펴보십시오.
코드 실행 중에 그라디언트가 계산 및 저장되지 않도록하십시오. 다음 패턴을 사용하여 다음과 같이 보장 할 수 있습니다.
with torch . no_grad ():
# run model here
out_tensor = net ( in_tensor )Pytorch에서는 층을 동결 할 수 있습니다. 이렇게하면 최적화 단계에서 업데이트되지 않습니다.
# you can freeze whole modules using
for p in pretrained_VGG . parameters (): # reset requires_grad
p . requires_grad = FalsePytorch 0.4 * 변수 및 텐서가 병합되었습니다. 더 이상 가변 객체를 명시 적으로 만들 필요가 없습니다.
C ++ 버전은 약 10% 빠릅니다
TODO ...
우리의 경험을 통해 약 20%의 속도를 얻을 수 있습니다. 그러나 모델을 처음 실행하면 최적화 된 그래프를 작성하는 데 꽤 시간이 걸립니다. 경우에 따라 (정방향 패스로 루프, 고정 입력 모양이 없거나, 전방에있는 경우/다른 경우)이 플래그는 메모리 나 기타 오류가 발생할 수 있습니다.
TODO ...
계산 그래프에서 텐서를 해제하는 경우. 멋진 그림이 여기에 표시됩니다
이 스타일 가이드를 개선 할 수있는 방법에 대한 피드백을주십시오! 풀 요청을 만들어 문제를 열거나 변경 사항을 제안 할 수 있습니다.
이 저장소가 마음에 들면 우리의 다른 프레임 워크를 확인하는 것을 잊지 마십시오.