PyTorch 小课堂!梯度计算详解
共 25356字,需浏览 51分钟
·
2022-06-09 23:31
目录
1. 函数的反向传播
2. 计算图的反向传播
3. 数值梯度检查
4. 在自动求导时检测错误产生路径
5. 设置是否需要梯度
6. model.eval() 与 torch.no_grad()
7. 提供 function 级别的统计信息
小伙伴们大家好呀~ PyTorch 源码解读系列又和大家见面啦!在前面的系列文章中,我们介绍了模型训练的原料——数据的预处理,以及 PyTorch 所提供的 nn.Module 结构解析。接下来,我们将为大家介绍 PyTorch 中的 autograd 模块的功能。其中主要涉及 torch/autograd 目录下代码,未涉及底层的 C++ 实现,请大家放心食用。
函数的反向传播
(torch.autograd.function)
我们在构建网络的时候,通常会使用 PyTorch 所提供的 nn.Module(例如 nn.Conv2d, nn.ReLU 等)作为基本单元。而这些 Module 通常包裹着 autograd function,以其作为真正实现的部分。例如 nn.ReLU 实际使用 torch.nn.functional.relu(F.relu):
from torch.nn import functional as F
class ReLU(Module):
__constants__ = ['inplace']
inplace: bool
def __init__(self, inplace: bool = False):
super(ReLU, self).__init__()
self.inplace = inplace
def forward(self, input: Tensor) -> Tensor:
return F.relu(input, inplace=self.inplace)
这里的 F.relu 类型为 function,若再剥开一层,其实际包裹的函数类型为 builtin_function_or_method,这也是真正完成运算的部分,而这些部分通常使用 C++ 实现(如 ATen)。至此我们已经知道,一个模型的运算部分由 autograd functions 组成,这些 autograd functions 内部定义了 forward,backward 方法用以描述前向和梯度反传的过程,组合后可以实现整个模型的前向和梯度反传。因而以 torch.autograd.function 中所定义的 Function 类为基类,我们可以实现自定义的 autograd function。
需要注意的是,所实现的 function 需包含 forward 及 backward 两个方法。以下以 Exp 和 GradCoeff 两个自定义 autograd function 为例:
class Exp(Function): # 此层计算e^x
def forward(ctx, i): # 模型前向
result = i.exp()
ctx.save_for_backward(result) # 保存所需内容,以备backward时使用,所需的结果会被保存在saved_tensors元组中;此处仅能保存tensor类型变量,若其余类型变量(Int等),可直接赋予ctx作为成员变量,也可以达到保存效果
return result
def backward(ctx, grad_output): # 模型梯度反传
result, = ctx.saved_tensors # 取出forward中保存的result
return grad_output * result # 计算梯度并返回
# 尝试使用
x = torch.tensor([1.], requires_grad=True) # 需要设置tensor的requires_grad属性为True,才会进行梯度反传
ret = Exp.apply(x) # 使用apply方法调用自定义autograd function
print(ret) # tensor([2.7183], grad_fn=
) ret.backward() # 反传梯度
print(x.grad) # tensor([2.7183])
Exp 函数的前向很简单,直接调用 tensor 的成员方法 exp 即可。反向时,我们知道:
因此我们直接使用
同时,我们可以注意到:前向后所得的结果包含了 grad_fn 属性,这一属性指向用于计算其梯度的函数(即 Exp 的 backward 函数)。接下来我们一起看另一个函数 GradCoeff,其功能是反传梯度时乘以一个自定义系数。
class GradCoeff(Function):
def forward(ctx, x, coeff): # 模型前向
ctx.coeff = coeff # 将coeff存为ctx的成员变量
return x.view_as(x)
def backward(ctx, grad_output): # 模型梯度反传
return ctx.coeff * grad_output, None # backward的输出个数,应与forward的输入个数相同,此处coeff不需要梯度,因此返回None
# 尝试使用
x = torch.tensor([2.], requires_grad=True)
ret = GradCoeff.apply(x, -0.1) # 前向需要同时提供x及coeff,设置coeff为-0.1
ret = ret ** 2
print(ret) # tensor([4.], grad_fn=
) ret.backward()
print(x.grad) # tensor([-0.4000]),梯度已乘以相应系数
计算图的反向传播
(torch.autograd.functional)
前面我们描述了单个函数的反向传播,以及如何编写自定义的 autograd function。下面,我们简单介绍 PyTorch 中所提供的计算图反向传播的接口。
在训练过程中,我们通常利用 prediction 和 groundtruth label 来计算 loss(loss 的类型为 Tensor),随后调用 loss.backward() 进行梯度反传。而 Tensor 类的 backward 方法,实际调用的就是 torch.autograd.backward 这一接口。这一 python 接口实现了计算图级的反向传播。
class Tensor(torch._C._TensorBase)
def backward(self, gradient=None, retain_graph=None, create_graph=False):
relevant_args = (self,)
...
torch.autograd.backward(self, gradient, retain_graph, create_graph)
# gradient: 形状与tensor一致,可以理解为链式求导的中间结果,若tensor标量,可以省略(默认为1)
# retain_graph: 多次反向传播时梯度累加。反向传播的中间缓存会被清空,为进行多次反向传播需指定retain_graph=True来保存这些缓存。
# create_graph: 为反向传播的过程同样建立计算图,可用于计算二阶导
在 PyTorch 实现中,autograd 会随着用户的操作,记录生成当前 variable 的所有操作,并建立一个有向无环图(DAG)。图中记录了操作 Function,每一个变量在图中的位置可通过其 grad_fn 属性在图中的位置推测得到。
在反向传播过程中,autograd 沿着这个图从当前变量(根节点 F)溯源,可以利用链式求导法则计算所有叶子节点的梯度。每一个前向传播操作的函数都有与之对应的反向传播函数用来计算输入的各个 variable 的梯度,这些函数的函数名通常以 Backward 结尾。我们构建如下图所示的简化的计算图,并以此为例进行简单介绍。
A = torch.tensor(2., requires_grad=True)
B = torch.tensor(.5, requires_grad=True)
E = torch.tensor(1., requires_grad=True)
C = A * B
D = C.exp()
F = D + E
print(F) # tensor(3.7183, grad_fn=
) 打印计算结果,可以看到F的grad_fn指向AddBackward,即产生F的运算 print([x.is_leaf for x in [A, B, C, D, E, F]]) # [True, True, False, False, True, False] 打印是否为叶节点,由用户创建,且requires_grad设为True的节点为叶节点
print([x.grad_fn for x in [F, D, C, A]]) # [
, , , None] 每个变量的grad_fn指向产生其算子的backward function,叶节点的grad_fn为空 print(F.grad_fn.next_functions) # ((
, 0), ( , 0)) 由于F = D + E, 因此F.grad_fn.next_functions也存在两项,分别对应于D, E两个变量,每个元组中的第一项对应于相应变量的grad_fn,第二项指示相应变量是产生其op的第几个输出。E作为叶节点,其上没有grad_fn,但有梯度累积函数,即AccumulateGrad(由于反传时多出可能产生梯度,需要进行累加) F.backward(retain_graph=True) # 进行梯度反传
print(A.grad, B.grad, E.grad) # tensor(1.3591) tensor(5.4366) tensor(1.) 算得每个变量梯度,与求导得到的相符
print(C.grad, D.grad) # None None 为节约空间,梯度反传完成后,中间节点的梯度并不会保留
我们再来看看下面的计算图,并在这个计算图上模拟 autograd 所做的工作:
A = torch.tensor([3.], requires_grad=True)
B = torch.tensor([2.], requires_grad=True)
C = A ** 2
D = B ** 2
E = C * D
F = D + E
F.manual_grad = torch.tensor(1) # 我们用manual_grad表示,在已知计算图结构的情况下,我们模拟autograd过程手动算得的梯度
D.manual_grad, E.manual_grad = F.grad_fn(F.manual_grad)
C.manual_grad, tmp2 = E.grad_fn(E.manual_grad)
D.manual_grad = D.manual_grad + tmp2 # 这里我们先完成D上的梯度累加,再进行反传
A.manual_grad = C.grad_fn(C.manual_grad)
B.manual_grad = D.grad_fn(D.manual_grad) # (tensor([24.], grad_fn=
), tensor([40.], grad_fn= ))
下面,我们编写一个简单的函数,并在这个计算图上进行 autograd,并验证结果是否正确。
# 这一例子仅可用于每个op只产生一个输出的情况,且效率很低(由于对于某一节点,每次未等待所有梯度反传至此节点,就直接将本次反传回的梯度直接反传至叶节点)
def autograd(grad_fn, gradient):
auto_grad = {}
queue = [[grad_fn, gradient]]
while queue != []:
item = queue.pop()
gradients = item[0](item[1])
functions = [x[0] for x in item[0].next_functions]
if type(gradients) is not tuple:
gradients = (gradients, )
for grad, func in zip(gradients, functions):
if type(func).__name__ == 'AccumulateGrad':
if hasattr(func.variable, 'auto_grad'):
func.variable.auto_grad = func.variable.auto_grad + grad
else:
func.variable.auto_grad = grad
else:
queue.append([func, grad])
A = torch.tensor([3.], requires_grad=True)
B = torch.tensor([2.], requires_grad=True)
C = A ** 2
D = B ** 2
E = C * D
F = D + E
autograd(F.grad_fn, torch.tensor(1))
print(A.auto_grad, B.auto_grad) # tensor(24., grad_fn=
) tensor(40., grad_fn= )
# 这一autograd同样可作用于编写的模型,我们将会看到,它与PyTorch自带的backward产生了同样的结果
from torch import nn
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(10, 5)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(5, 2)
self.fc3 = nn.Linear(5, 2)
self.fc4 = nn.Linear(2, 2)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x1 = self.fc2(x)
x2 = self.fc3(x)
x2 = self.relu(x2)
x2 = self.fc4(x2)
return x1 + x2
x = torch.ones([10], requires_grad=True)
mlp = MLP()
mlp_state_dict = mlp.state_dict()
# 自定义autograd
mlp = MLP()
mlp.load_state_dict(mlp_state_dict)
y = mlp(x)
z = torch.sum(y)
autograd(z.grad_fn, torch.tensor(1.))
print(x.auto_grad) # tensor([-0.0121, 0.0055, -0.0756, -0.0747, 0.0134, 0.0867, -0.0546, 0.1121, -0.0934, -0.1046], grad_fn=
)
mlp = MLP()
mlp.load_state_dict(mlp_state_dict)
y = mlp(x)
z = torch.sum(y)
z.backward()
print(x.grad) # tensor([-0.0121, 0.0055, -0.0756, -0.0747, 0.0134, 0.0867, -0.0546, 0.1121, -0.0934, -0.1046])
PyTorch 使用的是动态图,而它的计算图在每次前向传播时都是从头开始构建,所以它能够让我们根据需求使用 python 控制语句(如 for、if 等)创建计算图。下面提供一个例子:
def f(x):
result = 1
for ii in x:
if ii.item()>0: result=ii*result
return result
x = torch.tensor([0.3071, 1.1043, 1.3605, -0.3471], requires_grad=True)
y = f(x) # y = x[0]*x[1]*x[2]
y.backward()
print(x.grad) # tensor([1.5023, 0.4178, 0.3391, 0.0000])
x = torch.tensor([ 1.2817, 1.7840, -1.7033, 0.1302], requires_grad=True)
y = f(x) # y = x[0]*x[1]*x[3]
y.backward()
print(x.grad) # tensor([0.2323, 0.1669, 0.0000, 2.2866])
此前的例子使用的是 Tensor.backward() 接口(内部调用 autograd.backward),接下来我们来介绍 autograd 提供的 jacobian() 和 hessian() 接口,并直接利用其进行自动微分。这两个函数的输入为运算函数(接受输入 tensor,返回输出 tensor)和输入 tensor,返回 jacobian 和 hessian 矩阵。对于jacobian 接口,输入输出均可以为 n 维张量;对于 hessian 接口,输出必需为一标量。
jacobian 返回的张量 shape 为 output_dim x input_dim(若函数输出为标量,则 output_dim 可省略),hessian 返回的张量为 input_dim x input_dim。除此之外,这两个自动微分接口同时支持运算函数接收和输出多个 tensor。
from torch.autograd.functional import jacobian, hessian
from torch.nn import Linear, AvgPool2d
fc = Linear(4, 2)
pool = AvgPool2d(kernel_size=2)
def scalar_func(x):
y = x ** 2
z = torch.sum(y)
return z
def vector_func(x):
y = fc(x)
return y
def mat_func(x):
x = x.reshape((1, 1,) + x.shape)
x = pool(x)
x = x.reshape(x.shape[2:])
return x ** 2
vector_input = torch.randn(4, requires_grad=True)
mat_input = torch.randn((4, 4), requires_grad=True)
j = jacobian(scalar_func, vector_input)
assert j.shape == (4, )
assert torch.all(jacobian(scalar_func, vector_input) == 2 * vector_input)
h = hessian(scalar_func, vector_input)
assert h.shape == (4, 4)
assert torch.all(hessian(scalar_func, vector_input) == 2 * torch.eye(4))
j = jacobian(vector_func, vector_input)
assert j.shape == (2, 4)
assert torch.all(j == fc.weight)
j = jacobian(mat_func, mat_input)
assert j.shape == (2, 2, 4, 4)
在前面的例子中,我们已经介绍了 autograd.backward() 为节约空间,仅会保存叶节点的梯度。若我们想得知输出关于某一中间结果的梯度,我们则可以选择使用 autograd.grad() 接口,或是使用 hook 机制:
A = torch.tensor(2., requires_grad=True)
B = torch.tensor(.5, requires_grad=True)
C = A * B
D = C.exp()
torch.autograd.grad(D, (C, A)) # (tensor(2.7183), tensor(1.3591)), 返回的梯度为tuple类型, grad接口支持对多个变量计算梯度
def variable_hook(grad): # hook注册在Tensor上,输入为反传至这一tensor的梯度
print('the gradient of C is:', grad)
A = torch.tensor(2., requires_grad=True)
B = torch.tensor(.5, requires_grad=True)
C = A * B
hook_handle = C.register_hook(variable_hook) # 在中间变量C上注册hook
D = C.exp()
D.backward() # 反传时打印:the gradient of C is:tensor(2.7183)
hook_handle.remove() # 如不再需要,可remove掉这一hook
数值梯度检查
torch.autograd.gradcheck
在编写好自己的 autograd function 后,可以利用 gradcheck 中提供的 gradcheck 和 gradgradcheck 接口,对数值算得的梯度和求导算得的梯度进行比较,以检查 backward 是否编写正确。以函数
在下面的例子中,我们自己实现了 Sigmoid 函数,并利用 gradcheck 来检查 backward 的编写是否正确。
class Sigmoid(Function):
def forward(ctx, x):
output = 1 / (1 + torch.exp(-x))
ctx.save_for_backward(output)
return output
def backward(ctx, grad_output):
output, = ctx.saved_tensors
grad_x = output * (1 - output) * grad_output
return grad_x
test_input = torch.randn(4, requires_grad=True) # tensor([-0.4646, -0.4403, 1.2525, -0.5953], requires_grad=True)
torch.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-3) # pass
torch.autograd.gradcheck(torch.sigmoid, (test_input,), eps=1e-3) # pass
torch.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-4) # fail
torch.autograd.gradcheck(torch.sigmoid, (test_input,), eps=1e-4) # fail
我们发现:eps 为 1e-3 时,我们编写的 Sigmoid 和 torch 自带的 builtin Sigmoid 都可以通过梯度检查;但 eps 下降至 1e-4 时,两者反而都无法通过。而一般我们的直觉是,计算数值梯度时, eps 越小,求得的值应该更接近于真实的梯度。那么为什么这里会出现反常的现象呢?
其实这是由于机器精度带来的误差所致:test_input 的类型为 torch.float32,因此在 eps 过小的情况下,产生了较大的精度误差(计算数值梯度时,eps 作为被除数),因而与真实精度间产生了较大的 gap。当我们将 test_input 换为 float64 的 tensor 后,将不再出现这一现象。
这个现象也为我们提了个醒:在编写 backward 时,要考虑数值计算的一些性质,尽可能保留更精确的结果。
test_input = torch.randn(4, requires_grad=True, dtype=torch.float64) # tensor([-0.4646, -0.4403, 1.2525, -0.5953], dtype=torch.float64, requires_grad=True)
torch.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-4) # pass
torch.autograd.gradcheck(torch.sigmoid, (test_input,), eps=1e-4) # pass
torch.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-6) # pass
torch.autograd.gradcheck(torch.sigmoid, (test_input,), eps=1e-6) # pass
在自动求导时检测错误产生路径
torch.autograd.anomaly_mode
在这个过程中,还有一个问题我们不容忽视:如何在自动求导时检测错误产生路径呢?一般情况下,我们可借助 with autograd.detect_anomaly() 或是 torch.autograd.set_detect_anomaly(True) 来启用:
import torch
from torch import autograd
>>>
class MyFunc(autograd.Function):
...
staticmethod @
def forward(ctx, inp):
return inp.clone()
...
staticmethod @
def backward(ctx, gO):
# Error during the backward pass
raise RuntimeError("Some error in backward")
return gO.clone()
>>>
def run_fn(a):
out = MyFunc.apply(a)
return out.sum()
>>>
10, 10, requires_grad=True) inp = torch.rand(
out = run_fn(inp)
out.backward()
Traceback (most recent call last):
Some Error Log
RuntimeError: Some error in backward
with autograd.detect_anomaly():
10, 10, requires_grad=True) inp = torch.rand(
out = run_fn(inp)
out.backward()
Traceback of forward call that caused the error: # 检测到错误发生的Trace
File "tmp.py", line 53, in
out = run_fn(inp)
File "tmp.py", line 44, in run_fn
out = MyFunc.apply(a)
Traceback (most recent call last):
Some Error Log
RuntimeError: Some error in backward
设置是否需要梯度
torch.autograd.grad_mode
我们在 inference 的过程中,不希望 autograd 对 tensor 求导,因为求导需要缓存许多中间结构,增加额外的内存/显存开销。在 inference 时,关闭自动求导可实现一定程度的速度提升,并节省大量内存及显存(被节省的不仅限于原先用于梯度存储的部分。torch 中我们可以利用 grad_mode 中的 troch.no_grad() 来关闭自动求导:
from torchvision.models import resnet50
import torch
net = resnet50().cuda(0)
num = 128
inp = torch.ones([num, 3, 224, 224]).cuda(0)
net(inp) # 若不开torch.no_grad(),batch_size为128时就会OOM (在1080 Ti上)
net = resnet50().cuda(1)
num = 512
inp = torch.ones([num, 3, 224, 224]).cuda(1)
with torch.no_grad(): # 打开torch.no_grad()后,batch_size为512时依然能跑inference (节约超过4倍显存)
net(inp)
model.eval() 与 torch.no_grad()
这两项在 inference 的过程中需要都打开:model.eval() 令 model 中的 BatchNorm, Dropout 等 module 采用 eval mode,保证 inference 结果的正确性,但不起到节省显存的作用;torch.no_grad() 声明不计算梯度,可节省大量内存和显存。
提供 function 级别的统计信息
torch.autograd.profiler
import torch
from torchvision.models import resnet18
x = torch.randn((1, 3, 224, 224), requires_grad=True)
model = resnet18()
with torch.autograd.profiler.profile() as prof:
for _ in range(100):
y = model(x)
y = torch.sum(y)
y.backward()
# NOTE: some columns were removed for brevity
print(prof.key_averages().table(sort_by="self_cpu_time_total"))
输出为包含 CPU 时间及占比,调用次数等信息。这里需要注意:由于一个 kernel 可能还会调用其他 kernel,因此 Self CPU 指 CPU 本身所耗时间(不含其他 kernel 被调用所耗时间)。
下表展示了使用一块联想小新 i5 轻薄本所耗的时间,我们可以看到:耗时达到了惊人的 51s~
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------
aten::mkldnn_convolution_backward_weights 26.85% 13.936s 26.99% 14.012s 7.006ms 2000
aten::mkldnn_convolution_backward_input 24.20% 12.564s 24.28% 12.605s 6.302ms 2000
aten::mkldnn_convolution 22.66% 11.764s 22.75% 11.810s 5.905ms 2000
aten::add_ 4.54% 2.358s 4.54% 2.358s 335.031us 7037
aten::native_batch_norm 4.20% 2.178s 6.35% 3.294s 1.647ms 2000
aten::threshold_backward 3.85% 1.999s 3.85% 1.999s 1.176ms 1700
aten::native_batch_norm_backward 2.40% 1.247s 2.60% 1.348s 673.925us 2000
struct torch::autograd::AccumulateGrad 1.75% 906.138ms 5.90% 3.062s 485.973us 6300
aten::sum 1.56% 808.045ms 1.61% 836.033ms 363.493us 2300
aten::max_pool2d_with_indices 1.26% 655.751ms 1.26% 655.751ms 6.558ms 100
aten::add 0.78% 407.001ms 0.78% 407.001ms 145.357us 2800
aten::clamp_min 0.71% 367.014ms 0.71% 367.014ms 215.891us 1700
autograd::engine::evaluate_function: NativeBatchNorm... 0.55% 287.113ms 3.29% 1.707s 853.746us 2000
aten::empty 0.49% 255.215ms 0.49% 255.225ms 8.451us 30200
autograd::engine::evaluate_function: MkldnnConvoluti... 0.47% 246.019ms 52.59% 27.302s 13.651ms 2000
aten::mm 0.45% 232.766ms 0.46% 240.850ms 1.204ms 200
autograd::engine::evaluate_function: ReluBackward0 0.40% 206.792ms 4.35% 2.256s 1.327ms 1700
aten::fill_ 0.30% 154.547ms 0.30% 154.547ms 61.819us 2500
aten::max_pool2d_with_indices_backward 0.24% 122.017ms 0.50% 260.168ms 2.602ms 100
autograd::engine::evaluate_function: struct torch::a... 0.20% 106.389ms 6.10% 3.168s 502.860us 6300
autograd::engine::evaluate_function: MaxPool2DWithIn... 0.16% 84.665ms 0.76% 395.121ms 3.951ms 100
aten::empty_like 0.15% 75.751ms 0.30% 154.099ms 18.566us 8300
NativeBatchNormBackward0 0.14% 72.529ms 2.74% 1.420s 710.189us 2000
aten::div_ 0.14% 70.880ms 0.33% 172.975ms 82.369us 2100
aten::addmm 0.13% 68.586ms 0.14% 71.177ms 711.770us 100
aten::mkldnn_convolution_backward 0.12% 61.287ms 51.39% 26.678s 13.339ms 2000
aten::_convolution 0.11% 57.955ms 22.86% 11.868s 5.934ms 2000
aten::mean 0.11% 57.625ms 2.03% 1.054s 501.744us 2100
MaxPool2DWithIndicesBackward0 0.10% 50.288ms 0.60% 310.456ms 3.105ms 100
ReluBackward0 0.10% 49.924ms 3.95% 2.049s 1.205ms 1700
aten::relu_ 0.09% 47.010ms 0.83% 430.029ms 252.958us 1700
aten::_batch_norm_impl_index 0.09% 45.831ms 6.45% 3.349s 1.674ms 2000
aten::_to_copy 0.07% 37.126ms 0.15% 77.273ms 35.124us 2200
aten::as_strided_ 0.06% 32.698ms 0.06% 32.698ms 5.450us 6000
MkldnnConvolutionBackward0 0.06% 29.531ms 51.45% 26.707s 13.354ms 2000
aten::to 0.06% 29.155ms 0.21% 106.428ms 46.273us 2300
aten::conv2d 0.05% 28.411ms 22.95% 11.914s 5.957ms 2000
aten::copy_ 0.05% 28.014ms 0.05% 28.014ms 11.206us 2500
aten::batch_norm 0.05% 24.090ms 6.50% 3.373s 1.686ms 2000
aten::div 0.04% 21.886ms 0.05% 26.064ms 260.640us 100
aten::convolution 0.03% 18.080ms 22.90% 11.886s 5.943ms 2000
aten::empty_strided 0.03% 16.717ms 0.03% 16.717ms 7.268us 2300
aten::clamp_min_ 0.03% 16.005ms 0.74% 383.019ms 225.305us 1700
aten::as_strided 0.03% 13.844ms 0.03% 13.844ms 4.774us 2900
autograd::engine::evaluate_function: AddBackward0 0.02% 12.168ms 0.03% 14.762ms 18.453us 800
aten::t 0.01% 7.523ms 0.03% 13.994ms 27.988us 500
aten::_reshape_alias 0.01% 6.235ms 0.01% 6.235ms 31.175us 200
AddmmBackward0 0.01% 5.238ms 0.49% 253.384ms 2.534ms 100
autograd::engine::evaluate_function: AddmmBackward0 0.01% 4.729ms 0.51% 265.184ms 2.652ms 100
aten::expand 0.01% 4.448ms 0.01% 5.570ms 18.567us 300
aten::transpose 0.01% 4.039ms 0.01% 6.471ms 12.942us 500
AddBackward0 0.00% 2.594ms 0.00% 2.594ms 3.243us 800
aten::max_pool2d 0.00% 2.513ms 1.27% 658.264ms 6.583ms 100
MeanBackward1 0.00% 2.391ms 0.06% 30.648ms 306.480us 100
aten::clone 0.00% 2.388ms 0.01% 7.760ms 38.800us 200
aten::flatten 0.00% 2.179ms 0.01% 6.422ms 64.220us 100
aten::linear 0.00% 2.104ms 0.15% 77.744ms 777.440us 100
aten::view 0.00% 1.449ms 0.00% 1.449ms 14.490us 100
aten::zero_ 0.00% 1.418ms 0.27% 138.151ms 1.382ms 100
ReshapeAliasBackward0 0.00% 1.323ms 0.01% 4.498ms 44.980us 100
autograd::engine::evaluate_function: MeanBackward1 0.00% 1.295ms 0.06% 31.943ms 319.430us 100
aten::adaptive_avg_pool2d 0.00% 1.283ms 0.05% 25.883ms 258.830us 100
aten::reshape 0.00% 1.183ms 0.01% 3.175ms 31.750us 100
autograd::engine::evaluate_function: ReshapeAliasBac... 0.00% 1.177ms 0.01% 5.675ms 56.750us 100
SumBackward0 0.00% 1.148ms 0.01% 3.796ms 37.960us 100
autograd::engine::evaluate_function: TBackward0 0.00% 1.073ms 0.01% 3.808ms 38.080us 100
autograd::engine::evaluate_function: SumBackward0 0.00% 1.008ms 0.01% 4.804ms 48.040us 100
aten::ones_like 0.00% 946.000us 0.01% 2.688ms 26.880us 100
detach 0.00% 512.000us 0.00% 512.000us 8.127us 63
aten::resolve_conj 0.00% 505.000us 0.00% 505.000us 0.842us 600
TBackward0 0.00% 500.000us 0.01% 2.735ms 27.350us 100
aten::detach 0.00% 240.000us 0.00% 752.000us 11.937us 63
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Self CPU time total: 51.911s
本次的梯度计算模块分享就到此结束啦!恭喜看到这里的你又 get 了一个新技能~在之后的系列分享中,我们会继续给大家带来有关 PyTorch 优化算法接口的相关知识。点个关注不迷路,我们下期见哦!