PyTorch推出0.2版本:加入分佈式機器學習功能

 2017-08-08 06:04:43.0

正值ICML 2017 期間,我們發行了下一代主版本PyTorch V0.2.0,現在你可從官網http://pytorch.org 安裝它,該版本的軟件包文檔可從http://pytorch.org/docs /0.2.0/ 獲取。 PyTorch V0.2 新增了期待已久的功能,比如廣播、高級索引、高階梯度以及最重要的分佈式 PyTorch。


由於引入了廣播功能,特定可廣播情景的代碼行為不同於 V0.1.12 中的行為。這可能導致你的現有代碼中存在不易發現的誤差。在「重要的破損量與工作區」這一章節中,我們將提供識別這些模糊代碼的簡單方法。


張量廣播(numpy 風格)

張量和變量的高級索引

高階梯度(Higher-order gradients)

分佈式 PyTorch(多結點訓練等)

神經網絡層與特徵: SpatialTransformers、WeightNorm、EmbeddingBag 等

torch 和 autograd 中的新東西:矩陣乘法、矩陣的逆等

更簡單的調試,更好的錯誤信息

漏洞修復

重要的破損量和工作區


張量廣播(numpy 風格)




簡單來說,如果一個 PyTorch 操作支持廣播,那麼其張量參數可自動擴展為相同大小(無需拷貝數據)。 PyTorch 廣播的語義嚴格遵守 numpy 風格的廣播;如果你很熟悉 numpy 廣播,事情就會按照你的預期發展。


通用語義



如果以下規則成立,則兩個張量是「可廣播的」:

每個張量至少有一個維度。

當開始迭代維度大小時,從後面的維度開始,維度大小必須相同,它們中的一個是 1,或者其中一個不存在

例如:


>>> x=torch.FloatTensor(5,7,3)

>>> y=torch.FloatTensor(5,7,3)

# same shapes are always broadcastable (i.e. the above rules always hold)

# can line up trailing dimensions

>>> x=torch.FloatTensor(5,3,4,1)

>>> y=torch.FloatTensor( 3,1,1)

# x and y are broadcastable.

# 1st trailing dimension: both have size 1

# 2nd trailing dimension: y has size 1

# 3rd trailing dimension: x size == y size

# 4th trailing dimension: y dimension doesn't exist

# but:>>> x=torch.FloatTensor(5,2,4,1)

>>> y=torch.FloatTensor( 3,1,1)

# x and y are not broadcastable, because in the 3rd trailing dimension 2 != 3


如果兩個張量 x、y 是可廣播的,得到的張量大小計算如下:


如果維度 x 和 y 的數量不相同,則把帶有更少維度的張量的維度設為 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 文檔網站 http://pytorch.org/docs/0.2.0/notes/broadcasting.html。同樣,每個 torch 函數列舉了其在文檔中的廣播語義。



張量和變量的高級索引




PyTorch 現在支持 NumPy 風格的高級索引的一個子集,這允許用戶使用相同的

[]風格的操作在張量的每一個維度上選擇任意的索引,包括非相鄰索引和重複索引;這也使得無需調用PyTorchIndex[Select, Add, ...]函數即可獲得一個更加靈活的索引策略。


讓我們看一些實例:


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


純整數組索引 - 在每個維度上指定任意的索引


x[[1, 2], [3, 2], [1, 0]]--> yields a 2-element Tensor (x[1][3][1], x[2][2][0 ])


同樣支持廣播、複製


x[[2, 3, 2], [0], [1]]--> yields a 3-element Tensor (x[2][0][1], x[3][0][1], x[2][0][1])


允許任意的分度器(indexer)形狀


x[[[1, 0], [0, 1]], [0], [1]].shape--> yields a 2x2 Tensor [[x[1][0][1], x[0] [0][1]],

                         [x[0][0][1], x[1][0][1]]]


可以使用冒號、橢圓


x[[0, 3], :, :]

x[[0, 3], ...]--> both yield a 2x5x5 Tensor [x[0], x[3]]


也可以使用張量來索引!


y = torch.LongTensor([0, 2, 4])

x[y, :, :]--> yields a 3x5x5 Tensor [x[0], x[2], x[4]]


選擇小於 n維,請注意逗號的使用。


x[[1, 3], ]--> yields a 2x5x5 Tensor [x[1], x[3]]


高階梯度




現在你可以評估 PyTorch 中的高階微分。例如,你可以計算 Hessian-Vector積,以模型的梯度的範數為罰項,實現展開的 GAN 和提升的 WGAN 等。在0.2版本中,我們使得所有torch.XXX

函數和最流行的n層具備了計算高階梯度的能力。其餘的將會在下一個版本中介紹。


下面是一個簡短的實例,它以 Resnet-18 模型權重梯度的範數為罰項,因此權重數量的變化比較緩慢。


import torchfrom torchvision.models import resnet18from torch.autograd import Variable


model = resnet18().cuda()

# dummy inputs for the exampleinput = 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 = 0for 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()

這裡我們看兩個新的概念:

torch.autograd.grad 是一個函數,它包含 [輸出、輸入列表(為了它你需要梯度)],並且返回梯度 wrt。這些輸入作為元組,而不是將梯度累加到 .grad 屬性中。如果你想要進一步在梯度上操作,這很有幫助。

你可以在梯度上操作,並在其上調用backward()。

支持高階梯度的n層列表是:

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的新風格。更多函數新風格的信息請參閱http://pytorch.org/docs/0.2.0/notes/extending.html。你們中的大多數並不親自寫

autograd.Functions ,因為它們是把新操作引入到 autograd 引擎的低級基元,其中你指定了前向與後向調用。



分佈式PyTorch




我們還介紹了torch.distributed包,它允許我們在多機器間交換Tensors。使用該軟件包,我們能夠將神經網絡放在多機器和使用較大批量進行訓練。例如,給定一些基元,我們就能實現《精確的、大批量隨機梯度下降:在1小時內訓練ImageNet》。


該distributed包遵循MPI風格的程序設計模型。這意味著該軟件包會提供像send、recv、all_reduce那樣的函數以允許在結點(機器)之間交換Tensors。


對於每個機器最開始識別彼此並分配唯一的編碼(排級),我們提供了簡單的初始方法:


共享文件系統(要求所有進程都能訪問一個單一的文件系統)

IP 組播傳輸(要求所有的進程都在一個網絡中)

環境變量(要求我們手動配置等級,並且需要知道從所有進程中可獲得結點的地址)


我們的軟件包文檔包含更多初始化和可用後端的信息,現在我們了解一下使用組播地址初始化的案例:

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()))


該代碼片段將從第三個機器的process 2中打印“Hello”。


World size就是將參與整個工作的進程數量。每一個進程都將分配一個等級,即從0到world_size^( - 1)的數字,這些代號在整個工作中都是唯一的。它將成為處理識別符,並用於替代地址,例如指定哪一個進程將發送張量。


下面的代碼片段展示了點到點的傳輸是如何簡單地實現的:


# All processes (receiving ones too!) need to have tensors of appropriate# size preallocated.

x = torch.Tensor(10)if dist.get_rank() == 0:

    x.normal_()

    # Send x to process with rank 1

    dist.send(x, dst=1)else: # rank == 1# Receive data from process with rank 0 and save result in x

    dist.recv(x, src=0)


異步p2p函數(isend、irecv)同樣是可用的。


然而,一些通信模式經常出現,並且已經開發了更高效的集合調用。它們一般佔用整個進程組,並且比使用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)


該分佈式包是相當低級的,所以它允許實現更高級的算法和剪切代碼以適應特定的任務目標,但是數據並行訓練更為通用,所以我們為它構建了高級助手。因此,我們將介紹DistributedDataParallel,該函數基本上是nn.DataParallel的普適性替代。


下面的代碼展示了添加它到已存代碼的必要修改:


# Wr​​ap 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...


我們可以從以下查看全部的ImageNet訓練案例:

https://github.com/pytorch/examples/tree/master/imagenet


新型神經網絡層:SpatialTransformers、WeightNorm、EmbeddingBag等




新特徵


引入forward_pre_hook,以在前向函數(forward function)被調用之前執行用戶指定的閉包。

易於獲得非葉梯度(non-leaf gradient):目前,我們必須使用hooks獲取和檢查中間值的梯度。這種做法對於簡單的檢查並不方便。因此,我們引入了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 now supports dicts as inputs

現在,DataParallel作為輸入支持dicts。

新型層



使用F.grid_sample和F.affine_grid的空間變換網絡


論文《自正則化神經網絡(Self-Normalizing Neural Networks)》提出nn.SeLU和nn.AlphaDropout。論文《從卷積序列到序列學習(Convolutional Sequence to Sequence Learning)》提出nn.GLU(線性門控單元)。


通過torch.utils.weight_norm實現權值歸一化(Weight Normalization)。


計算cross_entropy_loss和nll_loss時,可以使用ignore_index參數來

忽略特定的目標索引(target indice)。這是實現掩碼的一種廉價、有用的方式,你可以獲取計算損失時所忽略的mask指數。


F.normalize實現各維度的重歸一化。


F.upsample和nn.Upsample將多個上採樣層合併成一個函數。該函數可實現第二次和第三次雙線性/三線性/最近上採樣(bilinear/trilinear/nearest upsampling)。


nn.EmbeddingBag:在構建詞袋模型(bag-of-words model)時,在Sum或Mean之後執行Embedding是一種常見做法。對於不同長度的序列,計算詞袋嵌入涉及到掩碼。我們提供一個單獨的nn.EmbeddingBag,它能夠更高效、快捷地計算詞袋嵌入,尤其是不同長度的序列。


使用bce_with_logits的數值穩定的二元交叉熵參數(Binary Cross-Entropy loss)。


使用PoissonNLLLoss的帶有目標泊松分佈的負對數似然損失。


cosine_similarity:沿維度計算並返回x1和x2之間的餘弦相似度(cosine similarity)。



訓練工具




學習率調度器:torch.optim.lr_scheduler提供多個簡單或聰明的方法來調整當前的學習率。在實驗過程中,這些方法都很方便,i為用戶可能想要做的事提供代理。



提供多種策略,可根據具體情況使用,詳見

http://pytorch.org/docs/master/optim.html#how-to-adjust-learning-rate:


ReduceLROnPlateau, LambdaLR, StepLR, MultiStepLR, ExponentialLR


ConcatDataset是一種可以合併和連接兩個單獨的數據集的數據集元類。



torch 和 autograd 中的新特性



現在,所有reduce函數如sum和mean默認為擠壓降維。例如,

torch.sum(torch.randn(10, 20))返回一個1D 張量。

x.shape,與numpy類似。一種等價於x.size()的便捷屬性。

torch.matmul,與np.matmul類似。

bitwise and、or、xor、lshift、rshift對inverse、gesv、cumprod、atan2的autograd支持


通過關鍵字參數選項(keyword argument option)可獲取無偏var和std。

torch.scatter_add - torch.scatter,除了重複指數的情況,這些值都可以匯總。

無給定參數時,torch.median與torch.sum類似,即它減少所有維度,並返回扁平張量(flattened Tensor)的單個median值。

masked_copy_被重命名為masked_scatter_(因為對masked_copy_有反對聲)。

torch.manual_seed現在也對所有的CUDA設備播種。

你現在可以通過關鍵字參數torch.rand(1000, generator=gen)

指定隨機數生成器對象(random number generator object)。



修正和小提升


現在,當變量轉為布爾類型時,會出現一個錯誤,例如:

b = Variable(torch.zeros(1))

if b[0]: # errors now

修正了CUDA中qr分解的正確性問題。

現已支持 IBM PowerPC64 平台。

現在在運行時會檢查 CuDNN 版本是否相同。

改進了CUDA子進程中的錯誤消息。

現在,Pytorch在CPU上可以更快地轉置。

改進了InstanceNorm上的錯誤信息。

為各種例程添加了更多的參數檢查,特別是BatchNorm和Convolution例程。

在CPU後端時報告可以提出更好的錯誤信息。

支持每台機器超過 8 塊 GPU (仍有 CUDA p2p 限制)

當訪問不存在的屬性時,錯誤消息獲得了改進。

變量的T()與Tensor一致。

防止被零除時dropout p=1

修復在非當前設備上共享CUDA張量的問題。

當 BNε < 允許 CuDNN 值時,回退到THNN

修正了當使用不同線程數量的MKL和OMP時線程破壞問題。

在使用CuDNN RNN時提升了內存使用效率。

使用負填充修正了ZeroPad2d後向的問題。

加入了虛擬tensor.data屬性,為用戶提供可解釋的錯誤消息。

修正了Python3原位分配。

在0-dim數組上調用from_numpy時生成錯誤。

現在,空張量在多處理器共享時不會發生錯誤了。

修復擴展張量的baddbmm

現在,parallel_apply 可以接受任意輸入了。

Tensor 和 Variable 中的關鍵字參數現在是一致的。

修正了Magma不可用時的fix torch.inverse

為ByteTensor添加邏輯非運算符。

在分散/集中核心裡加入設備聲明。


重要問題和解決方法




值得注意的是,有兩個重要的變化是無法向下兼容的:

Numpy風格的廣播

縮減函數,如sum(1)現在默認為keepdim=False

我們提供不同級別的Python警告,它們可以在代碼變更、使用錯誤操作時提醒用戶。


例子



以下是一個代碼片段,你可以將其添加到腳本的頂部。


添加此代碼會高亮不合適的代碼,並生成警告。


修復代碼,警告就會消除。


# insert this to the top of your scripts (usually main.py)import sys, warnings, traceback, torchdef 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


在所有警告消失後,你就可以刪除此代碼片段了。


細節



下面將介紹三種不兼容的變化和例子。


使用(現已棄用)一維視圖點態函數


早期版本的PyTorch允許點態函數在不同形狀的張量上執行,只要每個張量中的元素數量和相等即可。舊的框架可以將每個張量視為一維來執行點操作。新版PyTorch支持廣播。 “一維”點操作被認為是不推薦的,並且在張量不可廣播但具有相同數量元素的情況下會產生Python警告。


>>> torch.add(torch.ones(4), torch.ones(2,2))

__main__:1: UserWarning: self and other not broadcastable, but have the same

number of elements. Falling back to deprecated pointwise behavior.2222

[torch.FloatTensor of size 4]


在實現沒有出現過的代碼中進行廣播


在兩個張量尺寸不同的情況下,廣播的引入可能導致向後不兼容的變化,但是可以廣播並具有相同數量的元素,例如:


>>> torch.add(torch.ones(4,1), torch.randn(4))


可以預先生成一個特定尺寸的張量:torch.Size([4,1])


現在則生成張量尺寸:torch.Size([4,4])


為了幫助你識別代碼中廣播可能造成的後向不兼容情況,你可能需要將

torch.utils.backcompat.broadcast_warning.enabled

設置為True,這樣就會在相應的問題發生時生成python警告。例如:


>>> torch.utils.backcompat.broadcast_warning.enabled=True>>> torch.add(torch.ones(4,1), torch.ones(4))

__main__:1: UserWarning: self and other do not have the same shape, but are broadcastable, and have the same number of elements.


注意:此設置會觸發廣播有效性(包含庫代碼)的警告,所以你或許會希望在遷移代碼後關閉這個警告。


減少函數:使用Keepdim=False


如需在默認Keepdim參數時使用維度縮減獲函數得警告,請將

torch.utils.backcompat.keepdim_warning.enabled

設置為True。例如:


>>> torch.sum(torch.ones(2,3), 1)

__main__:1: UserWarning: backwards compatibility: call to "sum" uses default value for keepdim which has changed default to False. Consider passing as kwarg.33

[torch.FloatTensor of size 2]


可能會出現torch.utils.backcompat.broadcast_warning.enabled

,這一警告會被有效代碼出發,所以你肯定希望在代碼遷移後屏蔽它。


同時需注意:用keepdim=False可以使你的已有代碼和廣播“可以工作”。例如:


# behavior with (old) keepdim=True, causes accidental broadcast>>> torch.add(torch.ones(4), torch.ones(4,4).sum(dim=1, keepdim=True))5 5 5 55 5 5 55 5 5 55 5 5 5

[torch.FloatTensor of size 4x4]

# new behavior with keepdim=False is equivalent to non-broadcasted result>>> torch.add(torch.ones(4), torch.ones(4,4).sum(dim=1, keepdim=False))5555

[torch.FloatTensor of size 4]



下載



源代碼(Zip):https://github.com/pytorch/pytorch/archive/v0.2.0.zip

源代碼(tar.gz):https://github.com/pytorch/pytorch/archive/v0.2.0.tar.gz

文章來源:機器之心