PyTorch 0.2.0 リリースノート

PyTorch 0.2.0 リリースノート (翻訳)

高次勾配、分散 PyTorch、ブロードキャスト、高度なインデキシング、新しい層、その他

翻訳 : (株)クラスキャット セールスインフォメーション
日時 : 08/09/2017

* 本ページは github PyTorch の releases の PyTorch 0.2.0 リリースノートに相当する、
“Higher order gradients, Distributed PyTorch, Broadcasting, Advanced Indexing, New Layers and more” を翻訳したものです:
    https://github.com/pytorch/pytorch/releases/tag/v0.2.0

 

PyTorch の次のメジャーリリースがついに登場です、ICML (訳注: International Conference on Machine Learning) にぎりぎり間に合いました。Web サイト http://pytorch.org から今日それをインストールしましょう。
このリリースのためのパッケージ文書は http://pytorch.org/docs/0.2.0/ で利用可能です。

ブロードキャスト、高度なインデキシング、高次勾配そして最後に: 分散 PyTorch といった、お待ちかねの特徴を導入します。

ブロードキャストの導入により、あるブロードキャスト可能な状況に対するコードの挙動は 0.1.12 における挙動と異なります。これは貴方の既存のコードの silent bug につながるかもしれません。この曖昧なコードを識別する簡単な方法は「重要な破損 (Breakages) と回避策」で提供します。

 

Tensor ブロードキャスト (numpy-スタイル)

手短に言えば、PyTorch 演算がブロードキャストをサポートするならば、その Tensor 引数は同じサイズになるように (データのコピーを作ることなしに) 自動的に広げられます 。

PyTorch ブロードキャストのセマンティクスは numpy-スタイルのブロードキャストを密接にフォロー しています; もし numpy ブロードキャストに馴染みがあれば、ちょうど期待どおりに動作するはずです。

一般的なセマンティクス

2つの Tensor は次のルールが保持されるのであれば “ブロードキャスト可能 (broadcastable)” です :

  • 各 tensor は少なくとも一つの次元を持つ。
  • 次元サイズについて繰り返した時、trailing dimension (末尾の次元) から始めて、次元サイズは同じであるか、それらの一つが 1 であるか、それらの一つが存在しないかでなければならない。

例えば :

>>> x=torch.FloatTensor(5,7,3)
>>> y=torch.FloatTensor(5,7,3)
# 同じ shape は常にブロードキャスト可能 (i.e. 上のルールが常に保持されます)

# can line up trailing dimensions
>>> x=torch.FloatTensor(5,3,4,1)
>>> y=torch.FloatTensor(  3,1,1)

# x と y はブロードキャスト可能。
# 1st trailing dimension: 両者はサイズ 1 を持つ
# 2nd trailing dimension: y がサイズ 1 を持つ
# 3rd trailing dimension: x サイズ == y サイズ
# 4th trailing dimension: y 次元は存在しない

# しかし:
>>> x=torch.FloatTensor(5,2,4,1)
>>> y=torch.FloatTensor(  3,1,1)
# x と y はブロードキャスト可能ではない、何故ならば 3rd trailing において次元 2 != 3

もし2つの tensor x, y が “ブロードキャスト可能” ならば、結果の tensor サイズは次のように計算されます :

  • x と y の次元の数が同じでないならば、それらを同じ長さにするためにより少ない数の次元を持つ tensor の次元の先頭に 1 を追加します。
  • それから、各次元サイズに対して、結果の次元サイズはその次元に沿った x と y のサイズの最大値です。

例えば :

# can line up trailing dimensions to make reading easier
>>> x=torch.FloatTensor(5,1,4,1)
>>> y=torch.FloatTensor(  3,1,1)
>>> (x+y).size()
torch.Size([5, 3, 4, 1])

# error case
>>> x=torch.FloatTensor(5,2,4,1)
>>> y=torch.FloatTensor(  3,1,1)
>>> (x+y).size()
RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 1

より詳細は PyTorch ドキュメンテーションサイトで見つかります。また、各 torch 関数はそのブロードキャスト・セマンティクスをドキュメンテーションでリストします。

 

Tensor と Variable のための高度なインデキシング

PyTorch は今では Numpy スタイルの高度なインデキシングのサブセットをサポートします。これは Tensor の各次元で、同じ []-スタイル操作を使用して、隣接していない (non-adjacent) インデックスと二重の (duplicate) インデックスを含む、任意のインデックスをユーザに選択することを可能にします。これはより柔軟なインデキシング・ストラテジーを PyTorch の Index[Select, Add, …] 関数の呼び出しの必要なく可能にします。

いくつかの例を見てみましょう :

x = torch.Tensor(5, 5, 5)

単なる整数配列インデキシング – 各次元の任意のインデックスを指定します

x[[1, 2], [3, 2], [1, 0]]
--> 2-要素 Tensor を生成する (x[1][3][1], x[2][2][0])

またブロードキャスト、二重化もサポートします

x[[2, 3, 2], [0], [1]]
--> 3-要素 Tensor を生成する (x[2][0][1], x[3][0][1], x[2][0][1])

任意のインデクサー shape が可能

x[[[1, 0], [0, 1]], [0], [1]].shape
--> 2x2 Tensor を生成する [[x[1][0][1], x[0][0][1]],
                         [x[0][0][1], x[1][0][1]]]

colon, ellipse (訳注: ellipsis) が利用可能

x[[0, 3], :, :]
x[[0, 3], ...]
--> 両者とも 2x5x5 Tensor を生成する [x[0], x[3]]

また Tensor をインデックスに使用!

y = torch.LongTensor([0, 2, 4])
x[y, :, :]
--> 3x5x5 Tensor を生成する [x[0], x[2], x[4]]

ndim より小さい選択、comma の使用に注意

x[[1, 3], ]
--> 2x5x5 Tensor を生成する [x[1], x[3]]

 

高次勾配 (Higher order gradients)

今では高次微分を PyTorch で評価できます。例えば、ヘシアン・ベクトル積 (Hessian-Vector products) を計算し、モデルの勾配ノルムをペナルティとして科し、Unrolled GAN と Improved WGAN, etc. を実装することができます。

0.2 リリースでは、全ての torch.XXX 関数と多くの普及している nnlayers のための高次勾配を計算するための能力を有効にしました。残りは次のリリースでカバーされるでしょう。

ここに短いサンプルを示します、これは Resnett-18 モデルの重み勾配のノルムをペナルティとして科すもので、重みのボリュームがゆっくりと変化します。

import torch
from torchvision.models import resnet18
from torch.autograd import Variable

model = resnet18().cuda()

# dummy inputs for the example
input = Variable(torch.randn(2,3,224,224).cuda(), requires_grad=True)
target = Variable(torch.zeros(2).long().cuda())

# as usual
output = model(input)
loss = torch.nn.functional.nll_loss(output, target)

grad_params = torch.autograd.grad(loss, model.parameters(), create_graph=True)
# torch.autograd.grad does not accumuate the gradients into the .grad attributes
# It instead returns the gradients as Variable tuples.

# now compute the 2-norm of the grad_params
grad_norm = 0
for grad in grad_params:
    grad_norm += grad.pow(2).sum()
grad_norm = grad_norm.sqrt()

# take the gradients wrt grad_norm. backward() will accumulate
# the gradients into the .grad attributes
grad_norm.backward()

# do an optimization step
optimizer.step()

ここで2つの新しいコンセプトが見れます :

  1. torch.autograd.grad は [outputs, inputs のリスト (そのために望む勾配)] を取り入れ、勾配を .grad 属性に蓄積するのではなく、タプルとしてのこれらの入力に関する勾配を返します。勾配上で更なる演算を望む場合にはこれは有用です。
  2. 勾配上で演算可能で、それらの上で backward() が呼び出せます。

高次勾配をサポートする nn 層のリストは :

  • AvgPool*d, BatchNorm*d, Conv*d, MaxPool1d,2d, Linear, Bilinear
  • pad, ConstantPad2d, ZeroPad2d, LPPool2d, PixelShuffle
  • ReLU6, LeakyReLU, PReLU, Tanh, Tanhshrink, Threshold, Sigmoid, HardTanh, ELU, Softsign, SeLU
  • L1Loss, NLLLoss, PoissonNLLLoss, LogSoftmax, Softmax2d

    残りは次のリリースで有効になります。

高次勾配を可能にするために、autograd.Function を書く新しいスタイルを導入しました (関数を書く現在の/古いスタイルは完全に後方互換です)。新しいスタイルの関数についてここで更に読むことができます

あなた方の多くは自身の autograd.Function を書かないでしょう、それらは autograd エンジンに新しい演算を導入する低位なプリミティブで、そこでは forward と backward call を明記します。

 

分散 PyTorch

複数マシンの間で Tensor を交換することを可能にする、torch.distributed パッケージを導入します。このパッケージを使用して、ネットワーク訓練を複数マシンとより大きなミニバッチでスケールできます。例えば、Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour を実装するためのプリミティブが与えられています。

分散パッケージは MPI-スタイル・プログラミング・モデルをフォローしています (訳注: MPI – Message Passing Interface は 並列処理の標準化 API)。この事は send, recv, all_reduce のような提供される関数があることを意味し、これはノード (マシン) 間で Tensor を交換します。

マシン各々にまず互いを識別するためにそして互いに一意の数を割り当てる (rank) ために、単純な初期化メソッドを提供します :

  • 共有ファイルシステム (すべてのプロセスが単一のファイルシステムにアクセスできることを必要とする)
  • IP マルチキャスト (すべてのプロセスが同じネットワークにあることを必要とする0
  • 環境変数 (手動で rank を割り当ててすべてのプロセスから到達可能なノードのアドレスを知る必要がある)

パッケージ文書は初期化と利用可能な backend のより詳細を含みますが、multicast アドレスを使用する初期化の例がこれです :

import torch.distributed as dist

dist.init_process_group(backend='tcp',
                        init_method='tcp://[ff15:1e18:5d4c:4cf0:d02d:b659:53ba:b0a7]:23456',
                        world_size=4)

print('Hello from process {} (out of {})!'.format(
        dist.get_rank(), dist.get_world_size()))

例えばこれは、3番目のマシン上で “Hello from process 2 (out of 4)” を出力するでしょう。

world size はジョブに参加するプロセスの数です。各々には rank が割り当てられ、これは 0 と world_size -1 の間の数で、このジョブに一意です。それはプロセス識別子として機能してアドレスの代わりに、例えば、どのプロセスに tensor が送られるべきかを指定するために使われます。

ここにどのくらい単純に point-to-point 通信が実行されるかを示す snippet があります :

# All processes (receiving ones too!) need to have tensors of appropriate
# size preallocated.
x = torch.Tensor(10)
if dist.get_rank() == 0:
    x.normal_()
    # x を rank 1 のプロセスに送ります
    dist.send(x, dst=1)
else:  # rank == 1
    # rank 0 のプロセスからデータを受けとって x に結果を保存します
    dist.recv(x, src=0)

非同期 p2p 関数 (isend, irecv) もまた利用できます。

けれども、幾つかの通信パターンはより効率的な colletive call が開発されたほどに良く現れます。それらは典型的にはプロセスグループ全体に携わり send/recv を使用した素朴なアルゴリズムよりもかなり速いです。一つの例は all_reduce です :

x = torch.Tensor([dist.get_rank()])
# Add tensors from all processes such that they all receive the result.
# x is an input and output to this operation.
dist.all_reduce(x)

分散パッケージはかなり低位で、より高度なアルゴリズムを実装して非常に特殊な目的に適合させることを可能にします、しかしデータ並列 (data-parallel) 訓練はそのために高位なヘルパーを作成したほどに一般的なものです。

それで、DistributedDataParallel を導入しました、これは nn.DataParallel のための殆ど差し込み式 (drop-in) の置き換えとなることを意味しています。これは既存の訓練コードにそれを追加するために必要な変更を示すコード snippet です :

# Wrap model in DistributedDataParallel (CUDA only for the moment)
model = torch.nn.parallel.DistributedDataParallel(model.cuda())

# Use a DistributedSampler to restrict each process to a distinct subset
# of the dataset.
train_dataset = ...
train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
train_loader = torch.utils.data.DataLoader(
    train_dataset, batch_size=args.batch_size, num_workers=args.workers,
    pin_memory=True, sampler=train_sampler)

for epoch in range(args.num_epochs):
    # Use .set_epoch() method to reshuffle the dataset partition at every iteration
    train_sampler.set_epoch(epoch)
    # training loop
    ...

より完全な Imgenet 訓練サンプルをここで 見ることができます。

 

新しい nn 層: SpatialTransformers, WeightNorm, EmbeddingBag, etc.

新しい特徴

  • ユーザ指定の closure を forward 関数が呼び出される直前に実行するために forward_pre_hook が導入されました。
  • non-leaf 勾配への便利なアクセス :
    現在、中間値の勾配にアクセスして探査するためには、フックを使用しなければなりません。簡単な調査を行うためにはこれは不便です。それで、retain_grad を導入します。それはサンプルを通して説明するのが最適でしょう :

    input = Variable(torch.rand(1, 3), requires_grad=True)
    h1 = input * 3
    out = (h1 * h1).sum()
    
    h1.retain_grad()
    out.backward()
    
    print(h1.grad)
    # without calling retain_grad(), h1.grad is None
    
  • DataParallel は今では入力として辞書をサポートします。

新しい層

  • F.grid_sample と F.affine_grid による Spatial Transformer Network
  • nn.SeLU と nn.AlphaDropout が導入されました、ペーパー: Self-Normalizing Neural Networks から
  • nn.GLU (Gated Linear Unit) が導入されました、ペーパー: Convolutional Sequence to Sequence Learning から
  • Weight Normalization は今では torch.utils.weight_norm で実装されています。
  • cross_entropy_loss と nll_loss を ignore_index argument を使用して計算する時には特定のターゲット・インデックスは今では無視できます。これは masking を実装するお手軽で有用な方法で、ここでは損失を計算するときに無視できる mask index を持つことができます。
  • F.normalize は dimension-wise な renormalization を実装します。
  • F.upsample and nn.Upsample は複数の Upsampling 層を一つの関数に統合しました。それは 2d と 3d bilinear/trilinear/nearest upsampling を実装しています。
  • nn.EmbeddingBag: bag-of-wards モデルを構築する時、Sum または Mean が続く Embedding を行なうことは一般的です。可変長シーケンス (variable length sequences) のために、bags of embeddings を計算することは masking を伴います。nn.EmbeddingBag を提供します、これは bags of embeddings を計算するにより効率的で高速で、特に可変長シーケンスのためにです。
  • bce_with_logits による数値的に安定な Binary Cross-Entropy 損失
  • PoissonNLLLoss によるターゲットの Poisson 分布に従う負の対数尤度損失
  • cosine_similarity: dim に沿って計算された、x1 と x2 間のコサイン類似度を返します、

訓練ユティリティ

学習率スケジューラ (Learning Rate Schedulers) : torch.optim.lr_scheduler は現在の学習率を調整するために幾つかのバカで賢い (dumb and smart) 方法を提供します。それらは実験するときに非常に便利で、ユーザとしての貴方が望んでしがちなことのために代理を与えます。

提供される様々なストラテジーがあり、適切な状況に依存して使用可能で、それ以上は パッケージ文書 で読むことができます :

  • ReduceLROnPlateau, LambdaLR, StepLR, MultiStepLR, ExponentialLR

ConcatDataset は便利なデータセット meta-class で2つの個々のデータセットをマージして連結できます。

 

torch と autograd における新規

  • sum と mean のような全ての reduce 関数は削減された次元を搾る (squeeze) ようにデフォルト設定されています。例えば torch.sum(torch.randn(10, 20)) は 1D Tensor を返します。
  • x.shape, numpy に類似。x.size() に同値の便利な属性。
  • torch.matmul, np.matmul に類似
  • bitwise な and, or, xor, lshift, rshift
  • nverse, gesv, cumprod, atan2 のための autograd サポート
  • unbiased var と std がキーワード引数オプションで今では利用可能です
  • torch.scatter_add – torch.scatter, (訳注: その違いは) 重複するインデックスが発生する場合、値は合計される点を除いて
  • torch.median は引数が与えられない時は torch.sum と類似に振る舞う、i.e. それは全ての次元を削減して flattened Tensor の単一の median 値を返す。
  • masked_copy_ は masked_scatter_ に名前変更されました (masked_copy_ を deprecation として)
  • torch.manual_seed は今では全ての CUDA device にもまた seed します
  • キーワード引数 torch.rand(1000, generator=gen) で乱数生成器 (random number generator) オブジェクトを今では指定できます。

 

バグ修正と小さな改良

  • Variable が bool に変換されると今ではエラーを吐きます。例えば :
    b = Variable(torch.zeros(1))
    if b[0]: # errors now
    
  • CUDA の qr decomposition の correctness bug を修正。
  • IBM PowerPC64 プラットフォームのサポート
  • コンパイル時の CuDNN バージョンがランタイムに同じバージョンであるかをチェックします。
  • CUDA の fork されたサブプロセスのエラーメッセージを改良する
  • より高速な CPU 上の transposed-copy
  • InstanceNorm のエラーメッセージの改良
  • 様々なルーティンにより多くの引数チェックを追加、特に BatchNorm と Convolution ルーティン。
  • CPU バックエンドに渡る shape reporing まわりのより良いエラーメッセージ。
  • マシンあたり 8 GPU 以上のサポート (CUDA p2p 制限の回避策)
  • 存在しない属性へのアクセス時のエラーメッセージを改良
  • Variable の t() は Tensor と一致
  • dropout p=1 の時 divide-by-zero を防ぐ
  • non-current device 上の CUDA tensor の共有を修正
  • BN epsilon < 許容された CuDNN 値の時、THNN にフォールバック
  • MKL と OMP のための異なるスレッド数を使用する時 thread-trashing を修正
  • CuDNN RNN 使用時のメモリ使用を改良
  • negative padding を持つ ZeroPad2d backwards を修正
  • ダミーの tensor.data 属性の追加、ユーザに解釈可能なエラーメッセージを提供するため
  • Python3 のために in-place 除算を修正
  • 0-dim 配列上の from_numpy 呼び出し時にエラーをあげる
  • multiprocessing に渡り共有される時 Empty Tensor はエラーを出さない
  • expanded tensor のために baddbmm を修正
  • parallel_apply に任意の入力を受け取らせる
  • Tensor と Variable のキーワード引数は今では一致
  • Magma が利用可能でない時 torch.inverse を修正
  • ByteTensor のために論理 not 演算子を追加
  • scatter/gather カーネルで device assert を追加

 

重要な破損 (Breakages) と回避策

既に読んだように、後方互換ではない2つの重要な変更を導入しました :

  • Numpy-スタイル・ブロードキャスト
  • sum(1) のような reduction 関数は今では keepdim=False にデフォルト設定されています

Python 警告の異なるレベルを提供します、これは deprecated な挙動を使用している時やコードの挙動が変更されたときにアラートをあげることを有効にすることができます。

tl;dr

貴方のスクリプトの冒頭に追加可能なコード・スニペットがここにあります。このコードの追加は非互換なコードをハイライトする警告を生成します。

警告を生成しないようにコードを修正します。

# insert this to the top of your scripts (usually main.py)
import sys, warnings, traceback, torch
def warn_with_traceback(message, category, filename, lineno, file=None, line=None):
    sys.stderr.write(warnings.formatwarning(message, category, filename, lineno, line))
    traceback.print_stack(sys._getframe(2))
warnings.showwarning = warn_with_traceback; warnings.simplefilter('always', UserWarning);
torch.utils.backcompat.broadcast_warning.enabled = True
torch.utils.backcompat.keepdim_warning.enabled = True

全ての警告が消えたら、コード・スニペットを取り除くことができます。

更に入念に

さて、3つの非互換な変更を例で見てみましょう。

Using the (now deprecated) 1-dimensional view pointwise function

(省略)

Broadcasting in code where it didn’t happen before

(省略)

KeepDim=False for Reduction Functions

(省略)

 

以上