电脑之心正式发布
译者:zasdfgbnm
责任编辑如是说两个用作 PyTorch 标识符的VirtualDub TorchSnooper。译者是TorchSnooper的译者,也是PyTorch开发人员众所周知GitHub 工程项目门牌号: https://github.com/zasdfgbnm/TorchSnooper
他们可能将碰到这模样的所苦:比如说运转他们撰写的 PyTorch 标识符的这时候,PyTorch 提示重要信息你说正则表达式不相匹配,须要两个 double 的 tensor 但你给的看似 float;再或是是须要两个 CUDA tensor, 你给的看似个 CPU tensor。比如上面此种:
RuntimeError: Expected object of scalar type Double but got scalar type Float
此种难题增容出来很麻烦事,即使你不晓得从这儿已经开始出难题的。比如你可能将在标识符的第二行用 torch.zeros 增建了两个 CPU tensor, 接着那个 tensor 展开了若干个演算,尽是在 CPU 上展开的,始终没收起,直至第九行须要跟你做为输出传进去的 CUDA tensor 展开演算的这时候,才收起。要增容此种严重错误,有这时候就不得已间或地记事本 print 句子,十分麻烦事。
再或是,你可能将脑海中想像着将两个 tensor 展开甚么模样的操作方式,就会获得甚么模样的结论,但 PyTorch 半途收起说 tensor 的花纹不相匹配,或是压根儿没收起但最后出的花纹并非他们想的。那个这时候,他们往往也不晓得是甚么地方已经开始跟他们「预期的发生偏离的」。他们有这时候也得须要插入一大堆 print 句子才能找到原因。
TorchSnooper 是两个设计了用来解决那个难题的工具。TorchSnooper 的安装十分简单,只须要执行标准的 Python 包安装指令就好:
pip install torchsnooper
安装完了以后,只须要用 @torchsnooper.snoop() 装饰一下要增容的函数,那个函数在执行的这时候,就会手动 print 出每一行的执行结论的 tensor 的花纹、正则表达式、设备、是否须要梯度的重要信息。
安装完了以后,上面就用两个例子来说明一下怎么使用。
例子1
比如说他们写了两个十分简单的函数:
def myfunc(mask, x):
y = torch.zeros(6)
y.masked_scatter_(mask, x)
return y
他们是这模样使用那个函数的:
mask = torch.tensor([0, 1, 0, 1, 1, 0], device=cuda)
source = torch.tensor([1.0, 2.0, 3.0], device=cuda)
y = myfunc(mask, source)
上面的标识符看出来似乎没啥难题,然而实际上跑出来,却收起了:
RuntimeError: Expected object of backend CPU but got backend CUDA for argument #2 mask
难题在这儿呢?让他们 snoop 一下!用 @torchsnooper.snoop() 装饰一下 myfunc 函数:
import torch
import torchsnooper
@torchsnooper.snoop()
def myfunc(mask, x):
y = torch.zeros(6)
y.masked_scatter_(mask, x)
return y
mask = torch.tensor([0, 1, 0, 1, 1, 0], device=cuda)
source = torch.tensor([1.0, 2.0, 3.0], device=cuda)
y = myfunc(mask, source)
接着运转他们的脚本,他们看到了这样的输出:
Starting var:.. mask = tensor<(6,), int64, cuda:0>
Starting var:.. x = tensor<(3,), float32, cuda:0>
21:41:42.941668 call 5 def myfunc(mask, x):
21:41:42.941834 line 6 y = torch.zeros(6)
New var:……. y = tensor<(6,), float32, cpu>
21:41:42.943443 line 7 y.masked_scatter_(mask, x)
21:41:42.944404 exception 7 y.masked_scatter_(mask, x)
结合他们的严重错误,他们主要去看输出的每个变量的设备,找找最早从哪个变量已经开始是在 CPU 上的。他们注意到这一行:
New var:……. y = tensor<(6,), float32, cpu>
这一行直接告诉他们,他们创建了两个新变量 y, 并把两个 CPU tensor 赋值给了那个变量。这一行对应标识符中的 y = torch.zeros(6)。于是他们意识到,在使用 torch.zeros 的这时候,如果不人为指定设备的话,默认创建的 tensor 是在 CPU 上的。他们把这一行改成 y = torch.zeros(6, device=cuda),这一行的难题就修复了。
这一行的难题虽然修复了,他们的难题并没解决完整,再跑修改过的标识符还是收起,但那个这时候严重错误变成了:
RuntimeError: Expected object of scalar type Byte but got scalar type Long for argument #2 mask
好吧,这次严重错误出在了数据类型上。这次严重错误报告比较有提示重要信息性,他们大概能晓得是他们的 mask 的正则表达式错了。再看一遍 TorchSnooper 的输出,他们注意到:
Starting var:.. mask = tensor<(6,), int64, cuda:0>
果然,他们的 mask 的类型是 int64, 而不应该是应有的 uint8。他们把 mask 的定义修改好:
mask = torch.tensor([0, 1, 0, 1, 1, 0], device=cuda, dtype=torch.uint8)
接着就可以运转了。
例子 2
这次他们要构建两个简单的线性模型:
model = torch.nn.Linear(2, 1)
他们想拟合两个平面 y = x1 + 2 * x2 + 3,于是他们创建了这样两个数据集:
x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y = torch.tensor([3.0, 5.0, 4.0, 6.0])
他们使用最普通的 SGD 优化器来展开优化,完整的标识符如下:
import torch
model = torch.nn.Linear(2, 1)
x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y = torch.tensor([3.0, 5.0, 4.0, 6.0])
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
for _ in range(10):
optimizer.zero_grad()
pred = model(x)
squared_diff = (y – pred) ** 2
loss = squared_diff.mean()
print(loss.item())
loss.backward()
optimizer.step()
然而运转的过程他们发现,loss 降到 1.5 左右就不再降了。这是很不正常的,即使他们构建的数据都是无误差落在要拟合的平面上的,loss 应该降到 0 才算正常。
乍看上去,不晓得难题在这儿。抱着试试看的想法,他们来 snoop 一下子。那个例子中,他们没自定义函数,但他们可以使用 with 句子来激活 TorchSnooper。把训练的那个循环装进 with 句子中去,标识符就变成了:
import torch
import torchsnooper
model = torch.nn.Linear(2, 1)
x = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y = torch.tensor([3.0, 5.0, 4.0, 6.0])
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
with torchsnooper.snoop():
for _ in range(10):
optimizer.zero_grad()
pred = model(x)
squared_diff = (y – pred) ** 2
loss = squared_diff.mean()
print(loss.item())
loss.backward()
optimizer.step()
运转程序,他们看到了一长串的输出,一点一点浏览,他们注意到
New var:……. model = Linear(in_features=2, out_features=1, bias=True)
New var:……. x = tensor<(4, 2), float32, cpu>
New var:……. y = tensor<(4,), float32, cpu>
New var:……. optimizer = SGD (Parameter Group 0 dampening: 0 lr: 0….omentum: 0 nesterov: False weight_decay: 0)
02:38:02.016826 line 12 for _ in range(10):
New var:……. _ = 0
02:38:02.017025 line 13 optimizer.zero_grad()
02:38:02.017156 line 14 pred = model(x)
New var:……. pred = tensor<(4, 1), float32, cpu, grad>
02:38:02.018100 line 15 squared_diff = (y – pred) ** 2
New var:……. squared_diff = tensor<(4, 4), float32, cpu, grad>
02:38:02.018397 line 16 loss = squared_diff.mean()
New var:……. loss = tensor<(), float32, cpu, grad>
02:38:02.018674 line 17 print(loss.item())
02:38:02.018852 line 18 loss.backward()
26.979290008544922
02:38:02.057349 line 19 optimizer.step()
仔细观察这里面各个 tensor 的花纹,他们不难发现,y 的花纹是 (4,),而 pred 的花纹看似 (4, 1),他们俩相减,由于广播的存在,他们获得的 squared_diff 的花纹就变成了 (4, 4)。
这自然并非他们想的结论。那个难题修复出来也很简单,把 pred 的定义改成 pred = model(x).squeeze() 即可。现在再看修改后的标识符的 TorchSnooper 的输出:
New var:……. model = Linear(in_features=2, out_features=1, bias=True)
New var:……. x = tensor<(4, 2), float32, cpu>
New var:……. y = tensor<(4,), float32, cpu>
New var:……. optimizer = SGD (Parameter Group 0 dampening: 0 lr: 0….omentum: 0 nesterov: False weight_decay: 0)
02:46:23.545042 line 12 for _ in range(10):
New var:……. _ = 0
02:46:23.545285 line 13 optimizer.zero_grad()
02:46:23.545421 line 14 pred = model(x).squeeze()
New var:……. pred = tensor<(4,), float32, cpu, grad>
02:46:23.546362 line 15 squared_diff = (y – pred) ** 2
New var:……. squared_diff = tensor<(4,), float32, cpu, grad>
02:46:23.546645 line 16 loss = squared_diff.mean()
New var:……. loss = tensor<(), float32, cpu, grad>
02:46:23.546939 line 17 print(loss.item())
02:46:23.547133 line 18 loss.backward()
02:46:23.591090 line 19 optimizer.step()
现在那个结论看出来就正常了。并且经过测试,loss 现在已经可以降到很接近 0 了。大功告成。
责任编辑为电脑之心正式发布,。