これは、Pytorchの公式スタイルガイドではありません。このドキュメントは、Pytorchフレームワークを使用した深い学習に関する1年以上の経験からのベストプラクティスをまとめたものです。私たちが共有する学習は、主に研究とスタートアップの観点から来ていることに注意してください。
これはオープンプロジェクトであり、他の協力者はドキュメントを編集および改善することを大いに歓迎しています。
このドキュメントの3つの主要部分があります。まず、Pythonでのベストプラクティスの簡単な要約に続いて、Pytorchを使用したいくつかのヒントと推奨事項が続きます。最後に、一般的にワークフローを改善するのに役立つ他のフレームワークを使用して、いくつかの洞察と経験を共有します。
更新20.12.2020
更新30.4.2019
非常に肯定的なフィードバックの後、私は一般的に使用されているビルディングブロックの要約を軽視して追加しました:あなたは(VGGを使用した自己attention、スペクトル正規化、適応インスタンス正規化などを使用した知覚損失、...)の構成要素を見つけるでしょう)
損失、レイヤー、その他のビルディングブロックのコードスニペット
私たちの経験から、クリーンでシンプルなコードに非常に便利になった次の機能のために、Python 3.6+を使用することをお勧めします。
PythonのGoogle StyleGuideをフォローしようとしています。 Googleが提供するPythonコードに関する十分に文書化されたスタイルガイドを参照してください。
ここでは、最も一般的に使用されるルールの概要を提供します。
3.16.4から
| タイプ | 大会 | 例 |
|---|---|---|
| パッケージとモジュール | lower_with_under | from from generatorからImport backgroundgenerator |
| クラス | キャップワード | クラスのデータローダー |
| 定数 | caps_with_under | batch_size = 16 |
| インスタンス | lower_with_under | データセット=データセット |
| 方法と関数 | lower_with_under() | def Visualize_tensor() |
| 変数 | lower_with_under | background_color = 'blue' |
一般に、Visual StudioコードやPycharmなどのIDEを使用することをお勧めします。一方、VSコードは、比較的軽量のエディターでの構文の強調表示とオートコンプリートを提供します。Pycharmには、リモートクラスターを操作するための多くの高度な機能があります。 VSコードは、拡張の急速に成長しているエコシステムで非常に強力になりました。
次の拡張機能がインストールされていることを確認してください。
適切にセットアップすると、次のことを行うことができます。
一般に、最初の探索/新しいモデルとコードを使用してJupyterノートブックを使用することをお勧めします。 Pythonスクリプトは、再現性がより重要であるより大きなデータセットでモデルをトレーニングしたいとすぐに使用する必要があります。
推奨されるワークフロー:
| Jupyterノートブック | Pythonスクリプト |
|---|---|
| +探索 | +中断することなくより長いジョブを実行します |
| +デバッグ | + gitで変更を簡単に追跡できます |
| - 巨大なファイルになることができます | - デバッグは、主にスクリプト全体を再実行することを意味します |
| - 中断することができます(長いトレーニングに使用しないでください) | |
| - エラーが発生しやすくなり、混乱します |
一般的に使用されるライブラリ:
| 名前 | 説明 | に使用されます |
|---|---|---|
| トーチ | ニューラルネットワークを扱うためのベースフレームワーク | テンソル、ネットワーク、およびバックプロップを使用してそれらをトレーニングすること |
| Torchvision | Pytorchコンピュータービジョンモジュール | 画像データの前処理、増強、ポストプロセス |
| 枕(PIL) | Pythonイメージングライブラリ | 画像を読み込み、保存します |
| numpy | Pythonを使用した科学コンピューティング用パッケージ | データの前処理とポストプロセス |
| pretch_generator | バックグラウンド処理のためのライブラリ | 計算中にバックグラウンドで次のバッチを読み込みます |
| TQDM | 進捗バー | 各エポックのトレーニング中の進行 |
| トーチンフォ | PytorchのKerasのようなモデルの概要を印刷します | ネットワークを表示すると、各レイヤーのパラメーターとサイズです |
| torch.utils.tensorboard | Pytorch内のテンソルボード | 実験を記録し、テンソルボードで表示します |
すべてのレイヤーとモデルを同じファイルに入れないでください。ベストプラクティスは、最終ネットワークを別のファイル( networks.py )に分離し、それぞれのファイル( layers.py 、 loss.py 、 ops.py )にレイヤー、損失、およびOPを保持することです。完成したモデル(1つまたは複数のネットワークで構成されている)は、その名前のファイルに参照する必要があります(例: Yolov3.py 、 dcgan.py )
メインルーチンは、それぞれ列車とテストのスクリプトがモデルの名前を持つファイルからのみインポートする必要があります。
ネットワークをより小さな再利用可能なピースに分割することをお勧めします。ネットワークは、操作またはその他のnn.module Sで構成される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ブロックのスキップ接続がフォワードパスに直接実装されています。 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-EXAMPLEフォルダーに完全な例が提供されています。
次のパターンを使用したことに注意してください。
# 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を使用する2つの異なるパターンがあります。私たちの経験から、両方のパターンが有効です。ただし、最初の1つは、より良いコードで結果をもたらします。 2番目のものは、GPU間の通信が少ないため、わずかなパフォーマンスの利点があるようです。公式のPytorchフォーラムで、ここで2つのアプローチについて質問しました
最も一般的なのは、すべてのネットワークのバッチを個々のGPUに単純に分割することです。
したがって、バッチサイズ64で1 GPUで実行されるモデルは、それぞれ32のバッチサイズで2 GPUで実行されます。これは、nn.dataparallel(モデル)でモデルをラッピングすることで自動的に実行できます。
このパターンはあまり一般的ではありません。このアプローチを実装するリポジトリは、NVIDIAによるPIX2PIXHD実装にここに示されています
NumpyはCPUで実行され、トーチコードよりも遅いです。トーチはnumpyに似ているために開発されているため、ほとんどのNumpy関数はPytorchによってすでにサポートされています。
データの読み込みパイプラインは、メイントレーニングコードとは無関係である必要があります。 Pytorchは、データをより効率的にロードするためにバックグラウンドワーカーを使用し、主なトレーニングプロセスを乱すことなく使用します。
通常、モデルを何千ものステップでトレーニングします。したがって、オーバーヘッドを減らすためにn'thステップごとに損失やその他の結果を記録するだけで十分です。特に、トレーニング中に画像が費用がかかる可能性があるため、中間結果を節約できます。
コード実行中にコマンドライン引数を使用してパラメーターを設定するのが非常に便利です(バッチサイズ、学習レートなど)。実験の引数を追跡する簡単な方法は、 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()を使用して、不要な操作の記録を防ぎます。
変数を直接印刷できますが、 variable.detach()またはvariable.item()を使用することをお勧めします。以前のPytorchバージョン<0.4では、 .dataを使用して変数のテンソルにアクセスする必要があります。
2つの方法は、ここの問題の1つで指摘されているように同一ではありません。
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 GPUでは、コードの先頭に次の行を追加できます。これにより、CUDAバックエンドは最初の実行中にグラフを最適化できます。ただし、ネットワーク入力/出力テンソルサイズを変更すると、変更が発生するたびにグラフが最適化されることに注意してください。これにより、ランタイムが非常に遅くなり、メモリエラーが発生する可能性があります。入力と出力が常に同じ形状の場合にのみ、このフラグを設定します。通常、これにより約20%が改善されます。
torch . backends . cudnn . benchmark = True使用するマシン、プリプロシングパイプライン、ネットワークサイズに依存します。 1080TI GPUでSSDで実行されると、理想的なシナリオであるほぼ1.0の計算効率が見られます。浅い(小)ネットワークまたは遅いハードディスクが使用されると、セットアップに応じて数値が約0.1-0.2に低下する場合があります。
Pytorchでは、非常に簡単に仮想バッチサイズを実装できます。オプティマイザーがパラメーターの更新を行い、 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などの前処理モデルを使用して損失を計算するが、それをトレーニングしない場合(例えば、スタイル転送/ GANS/自動エンコーダーの知覚損失)、次のパターンを使用できます。
...
# 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%高速です
dodo ...
私たちの経験から、あなたは約20%のスピードアップを得ることができます。しかし、モデルを初めて実行すると、最適化されたグラフを構築するのにかなりの時間がかかります。場合によっては(フォワードパスのループ、固定入力形状なし、前方の場合など)、このフラグはメモリまたは他のエラーがなくなる可能性があります。
dodo ...
計算グラフからテンソルを解放する場合。ここに素敵なイラストが示されています
このスタイルガイドを改善する方法についてフィードバックを提供してください!プルリクエストを作成することにより、問題を開きたり、変更を提案したりできます。
このリポジトリが気に入ったら、私たちから他のフレームワークをチェックすることを忘れないでください。