これは非公式の PyTorch ガイドですが、この記事では PyTorch フレームワークを使用した 1 年以上の経験、特にディープラーニング関連の作業に最適なソリューションを開発するために使用した経験をまとめています。私たちが共有する経験は、主に研究と実践の観点からのものであることにご注意ください。 これは作業中のプロジェクトであり、他の読者がドキュメントを改善することを歓迎します。 https://github.com/IgorSusmelj/pytorch-styleguide. このドキュメントは 3 つの主要な部分で構成されています。まず、この記事では Python で最も一般的なツールを簡単に一覧にします。次に、この記事では、PyTorch を使用する際のヒントと提案をいくつか紹介します。 *** では、ワークフローの改善に役立った他のフレームワークを使用した洞察と経験をいくつか共有します。 1. Python機器の在庫 1. Python 3.6以上を使用することをお勧めします 私たちの経験に基づいて、簡潔なコードを簡単に記述できる次の機能があるため、Python 3.6 以上を使用することをお勧めします。 - 「typing」モジュールはPython 3.6以降でサポートされています
- フォーマット文字列(f文字列)はPython 3.6以降でサポートされています。
2. Python スタイルガイド 私たちは Google の Python プログラミング スタイルに従うように努めています。 Google が提供する優れた Python コーディング スタイル ガイドをご覧ください。 アドレス: https://github.com/google/styleguide/blob/gh-pages/pyguide.md。 ここでは、最も一般的に使用される命名規則の概要を示します。 3. 統合開発環境 一般的には、Visual Studio や PyCharm などの統合開発環境を使用することをお勧めします。 VS Code は比較的軽量なエディターで構文の強調表示と自動補完を提供しますが、PyCharm にはリモート クラスター タスクを処理するための高度な機能が多数あります。 4. Jupyter Notebook と Python スクリプト 一般的に、最初の調査や新しいモデルやコードを試す場合は、Jupyter Notebook を使用することをお勧めします。より大きなデータセットでモデルをトレーニングする場合は、再現性がより大きなデータセットでより重要になるため、Python スクリプトを使用する必要があります。 次のワークフローを採用することをお勧めします。 - 最初はJupyter Notebookを使う
- データとモデルを探索する
- ノートブックのセルにクラス/メソッドを構造化する
- コードをPythonスクリプトに移植する
- サーバー上でトレーニング/デプロイ
5.常設図書館を開設する よく使用されるライブラリは次のとおりです。 6. ファイルの構成 すべてのレイヤーとモデルを同じファイルに置かないでください。最善のアプローチは、最終的なネットワークを別のファイル (networks.py) に分離し、レイヤー、損失関数、およびさまざまな操作をそれぞれのファイル (layers.py、losss.py、ops.py) に保存することです。結果として得られるモデル (1 つ以上のネットワークで構成) は、モデルにちなんで命名され (例: yolov3.py、DCGAN.py)、個々のモジュールを参照する必要があります。 メイン プログラムと個々のトレーニングおよびテスト スクリプトでは、モデル名を含む Python ファイルをインポートするだけで済みます。 2. PyTorchの開発スタイルとテクニック ネットワークをより小さな再利用可能な部分に分解することを提案します。 nn.Module ネットワークには、さまざまな操作やその他の構成要素が含まれています。損失関数も nn.Module にラップされているため、ネットワークに直接統合できます。 nn.Module を継承するクラスには、各レイヤーまたは操作の順方向伝播を実装する「forward」メソッドが必要です。 nn.module は、「self.net(input)」を通じて入力データを処理できます。ここでは、オブジェクトの「call()」メソッドが直接使用され、入力データがモジュールに渡されます。 - 出力= self .net(入力)
1. PyTorch環境におけるシンプルなネットワーク 単一の入力と出力を持つ単純なネットワークは、次のパターンを使用して実装できます。 - クラスConvBlock(nn.Module):
- __init__(self)を定義します。
- super(ConvBlock, self).__init__()
- ブロック= [nn.Conv2d(...)]
- ブロック += [nn.ReLU()]
- ブロック += [nn.BatchNorm2d(...)]
- self.block = nn.Sequential (*ブロック)
- def forward(self, x):
- self.block(x) を返す
- クラス SimpleNetwork(nn.Module):
- __init__ を定義します (self、 num_resnet_blocks = 6 ):
- スーパー(SimpleNetwork、self)。__init__()
- # ここで個々のレイヤーを追加します
- レイヤー= [ConvBlock(...)]
- i が範囲内(num_resnet_blocks)の場合:
- レイヤー += [ResBlock(...)]
- self.net = nn.Sequential (*レイヤー)
- def forward(self, x):
- 自己.net(x) を返す
以下の点にご注意ください。 - 同じ再帰パターン (畳み込み、活性化関数、正規化) で構成される単純な再帰ビルディング ブロック (ConvBlocks など) を再利用し、独立した nn.Module にパックしました。
- 必要なレイヤーのリストを作成し、最後に nn.Sequential() を使用してすべてのレイヤーを 1 つのモデルに結合します。リスト オブジェクトを展開するには、リスト オブジェクトの前に「*」演算子を使用します。
- フォワードパスでは、入力データを使用してモデルを直接実行します。
2. PyTorch 環境におけるシンプルな残差ネットワーク - クラス 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 += [nn.Conv2d(...),
- ノルムレイヤー(...)、
- nn.ReLU()]
- use_dropoutの場合:
- conv_block += [nn.Dropout(...)]
- conv_block += [nn.Conv2d(...),
- ノルムレイヤー(...)]
- nn.Sequential(*conv_block) を返します。
- def forward(self, x):
- 出力= x + self.conv_block(x)
- 戻る
ここでは、ResNet モジュールのスキップ接続がフォワード パスで直接実装され、PyTorch はフォワード パス中に動的な操作を許可します。 3. PyTorch における複数の出力を持つネットワーク 複数の出力を持つネットワーク(たとえば、事前トレーニング済みの VGG ネットワークを使用して知覚損失を構築する)の場合、次のパターンを使用します。 - クラス Vgg19(torch.nn.Module):
- def __init__(self, requires_grad = False ):
- スーパー(Vgg19、自己)。__init__()
- vgg_pretrained_features = models.vgg19 (事前トレーニング済み = True ).features
- self.slice1 = torch.nn.Sequential () です。
- self.slice2 = torch.nn.Sequential () です。
- self.slice3 = torch.nn.Sequential () です。
- xが範囲内(7)の場合:
- self.slice1.add_module(str(x), vgg_pretrained_features[x])
- xが範囲(7, 21)内にある場合:
- self.slice2.add_module(str(x), vgg_pretrained_features[x])
- xが範囲(21, 30)の場合:
- self.slice3.add_module(str(x), vgg_pretrained_features[x])
- そうでない場合 requires_grad:
- self.parameters() 内の param の場合:
- param.requires_grad = False
- def forward(self, x):
- h_relu1 =自己.slice1(x)
- h_relu2 =自己.slice2(h_relu1)
- h_relu3 =自己.slice3(h_relu2)
- 出力= [h_relu1, h_relu2, h_relu3]
- 戻る
以下の点にご注意ください。 - 「torchvision」パッケージが提供する事前トレーニング済みモデルを使用します。
- ネットワークを 3 つのモジュールに分割します。各モジュールは、事前トレーニング済みモデルのレイヤーで構成されます。
- requires_grad = False に設定してネットワークの重みを修正します。
- 3つのモジュールの出力のリストを返します。
4. カスタム損失関数 PyTorch にはすでに多数の標準損失関数がありますが、独自の損失関数を作成する必要がある場合もあります。これを行うには、別の「losses.py」ファイルを作成し、「nn.Module」を拡張してカスタム損失関数を作成する必要があります。 - クラス CustomLoss(torch.nn.Module):
- __init__(self)を定義します。
- super(CustomLoss,self).__init__()
- def forward(self,x,y):
- 損失= torch.mean ((x - y)**2)
- リターンロス
5. ***トレーニングモデルのコード構造 トレーニング *** コード構造では、次の 2 つのモードを使用する必要があります。 - prefetch_generator で BackgroundGenerator を使用して次のデータ バッチをロードします。
- tqdm を使用してトレーニング プロセスを監視し、計算効率を表示します。これにより、データ読み込みプロセスのボトルネックを見つけることができます。
- # インポート文
- 輸入トーチ
- torch.nnをnnとしてインポートする
- torch.utilsからデータをインポートする
- ...
- # フラグ/シードを設定する
- torch.backends.cudnn.benchmark = True
- np.ランダムシード(1)
- トーチ.マニュアル_シード(1)
- torch.cuda.manual_seed(1)
- ...
- # メインコードから始める
- __name__ == '__main__' の場合:
- # 実験用の追加フラグのargparse
- parser = argparse.ArgumentParser ( description = "... のネットワークをトレーニングします" )
- ...
- opt =パーサー.parse_args ()
- # データセットのコードを追加します (常にトレーニングと検証/テスト セットを使用します)
- data_transforms = transforms.Compose([
- transforms.Resize((opt.img_size, opt.img_size)),
- transforms.RandomHorizontalFlip()、
- 変換.ToTensor()、
- 変換します。正規化します((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
- ])
- train_dataset =データセット.ImageFolder (
- ルート= os .path.join(opt.path_to_data, "train"),
- 変換=データ変換)
- train_data_loader = data.DataLoader(train_dataset, ...)
- test_dataset =データセット.ImageFolder (
- ルート= os .path.join(opt.path_to_data, "テスト"),
- 変換=データ変換)
- test_data_loader = data.DataLoader(test_dataset ...)
- ...
- # ネットワークをインスタンス化します (これは *networks.py* からインポートされています)
- ネット= MyNetwork (...)
- ...
- # 損失を作成する (pytorch の基準)
- criterion_L1 = torch.nn.L1Loss ()
- ...
- # GPU 上で実行していて、cuda 移動モデルを使用したい場合
- use_cuda = torch.cuda.is_available () です。
- use_cudaの場合:
- ネットネット= net.cuda()
- ...
- # オプティマイザーを作成する
- optim = torch.optim.Adam (net.parameters(), lr = opt.lr )
- ...
- # 必要に応じてチェックポイントをロードする
- 開始_n_iter = 0
- 開始エポック= 0
- opt.resumeの場合:
- ckpt = load_checkpoint (opt.path_to_checkpoint) # 最後のチェックポイントをロードするためのカスタムメソッド
- net.load_state_dict(ckpt['net'])
- start_epoch = ckpt ['epoch']
- start_n_iter = ckpt ['n_iter']
- optim.load_state_dict(ckpt['optim'])
- print("最後のチェックポイントが復元されました")
- ...
- # 複数のGPUで実験を実行したい場合は、モデルをそこに移動します
- ネット= torch.nn.DataParallel (ネット)
- ...
- # 通常、実験を追跡するためにtensorboardXを使用します
- ライター= SummaryWriter (...)
- # メインループを開始します
- n_iter =開始_n_iter
- 範囲内のエポックの場合(start_epoch、opt.epochs):
- # モデルをトレーニングモードに設定する
- ネット.トレイン()
- ...
- # データの反復処理には prefetch_generator と tqdm を使用します
- pbar = tqdm (列挙(BackgroundGenerator(train_data_loader, ...))、
- 合計=長さ(train_data_loader))
- start_time = 時間.時間()
- # データセットを巡回する for ループ
- iの場合、pbar内のデータ:
- # データ準備
- 画像、ラベル=データ
- use_cudaの場合:
- イメージイメージ= img.cuda()
- ラベルラベル= label.cuda()
- ...
- # データローダーの問題を見つけるために、tqdm を使用して準備時間と計算時間を追跡することは非常に良い習慣です。
- 準備時間=開始時間- 時間.時間()
- # 前方および後方パス
- 最適化ゼロ勾配()
- ...
- 損失.後方()
- 最適化ステップ()
- ...
- # tensorboardX の更新
- ライター.add_scalar(..., n_iter)
- ...
- # 計算時間と*compute_efficiency*を計算します
- process_time = start_time - time.time() - prepare_time
- pbar.set_description("計算効率: {:.2f}、エポック: {}/{}:".format(
- プロセス時間/(プロセス時間+準備時間)、エポック、opt.epochs))
- start_time = 時間.時間()
- # x エポックごとにテストパスを実行する
- エポック% x == x-1の場合:
- # モデルを評価モードにする
- ネット評価()
- ...
- #テストをいくつか行う
- pbar = tqdm (列挙(BackgroundGenerator(test_data_loader, ...))、
- 合計=長さ(test_data_loader))
- iの場合、pbar内のデータ:
- ...
- # 必要に応じてチェックポイントを保存する
- ...
3. PyTorchのマルチGPUトレーニング PyTorch には複数の GPU を使用したトレーニング用の 2 つのモードがあります。 私たちの経験では、どちらのモデルも効果的です。ただし、最初のアプローチの方が結果が良く、必要なコードも少なくなります。 2 番目のアプローチでは GPU 間の通信が少なくなるため、パフォーマンス上の利点がわずかに得られるようです。 1. 各ネットワーク入力バッチを分割する 最も一般的なアプローチは、すべてのネットワーク入力を異なるデータ バッチに直接分割し、各 GPU に割り当てることです。 このように、バッチサイズが 64 のモデルを 1 つの GPU で実行すると、2 つの GPU で実行すると各バッチのサイズは 32 になります。このプロセスは、nn.DataParallel(model) ラッパーを使用して自動化できます。 2. すべてのネットワークをスーパーネットワークにパックし、入力バッチを分割する このモードは一般的には使用されません。次のリポジトリには、このメソッドの実装を含む Nvidia の pix2pixHD 実装が示されています。 アドレス: https://github.com/NVIDIA/pix2pixHD 4. PyTorch ですべきこと、すべきでないこと 1. 「nn.Module」の「forward」メソッドでNumpyコードを使用しない Numpy は CPU 上で実行されるため、Torch コードよりも遅くなります。 torch の開発思想は numpy と似ているため、Numpy のほとんどの機能は PyTorch でもすでにサポートされています。 2. DataLoaderをメインプログラムコードから分離する データをロードするためのワークフローは、メインのトレーニング コードとは別にする必要があります。 PyTorch は、メインのトレーニング プロセスに干渉することなく、より効率的にデータをロードするために「バックグラウンド」プロセスを使用します。 3. 各ステップで結果を記録しない 通常、モデルを数千ステップにわたってトレーニングします。したがって、計算オーバーヘッドを削減するには、n ステップごとに損失やその他の計算結果を記録すれば十分です。特に、トレーニング中に中間結果を画像として保存するのは非常にコストがかかります。 4. コマンドラインパラメータの使用 コードの実行時に使用されるパラメータ (バッチ サイズ、学習率など) を設定するには、コマンド ライン引数を使用すると非常に便利です。 「parse_args」から受け取った辞書 (dict データ) を直接出力する、簡単な実験的な引数追跡メソッド: - # 引数をconfig.txtファイルに保存します
- opt = parser .parse_args() で、open("config.txt", "w") を f として指定します:
- f.write(opt.__str__())
5. 可能であれば、「.detach() を使用」を使用してテンソルを計算グラフから解放します。 自動微分化を実装するために、PyTorch はテンソルに関係するすべての演算を追跡します。不要な操作が記録されないようにするには、「.detach()」を使用してください。 6. スカラーテンソルを出力するには「.item()」を使用する 変数を直接印刷できます。ただし、「variable.detach()」または「variable.item()」を使用することをお勧めします。 PyTorch の以前のバージョン (< 0.4) では、変数内のテンソル値にアクセスするには「.data」を使用する必要がありました。 7. 「nn.Module」の「forward」メソッドの代わりに「call」メソッドを使用する 次の GitHub の問題が指摘しているように、2 つのアプローチはまったく同じではありません。 https://github.com/IgorSusmelj/pytorch-styleguide/issues/3 - 出力= self.net.forward (入力)
- # それらは等しくありません!
- 出力= self .net(入力)
オリジナルリンク: https://github.com/IgorSusmelj/pytorch-styleguide [この記事は51CTOコラム「Machine Heart」、WeChatパブリックアカウント「Machine Heart(id:almosthuman2014)」によるオリジナル翻訳です] この著者の他の記事を読むにはここをクリックしてください |