系列文章目录
跟李沐学AI:chapter_preliminaries/index/autograd下的笔记以及课后练习
文章目录
一、笔记
1.自动求导&计算图
pytorch处理自动求导时将每一次计算代码表示成一个无环图,并在求导结束后自动销毁。
动态构建计算图: 在执行每一步涉及张量的操作时,PyTorch都会记录这个操作及其输入和输出的关系,形成一个节点在网络计算图中。例如,当执行conv_layer(input_data)时,会创建一个新的节点代表卷积运算,该节点的输入是input_data,输出是经过卷积后的张量。
实时更新与销毁: 每次前向、反向传播都根据当前输入数据即时构建一个全新的动态计算图,且在完成一次前向传播和相应的反向传播后,系统不会持久保存这次计算图的具体结构,而是会在下次前向传播时重新构建。
注意到:
y=x.sum()
y.backward()
x.grad
执行两次backward操作仍然成功,即期间并未销毁计算图,应该是sum()函数本身的原因,能够不销毁计算图。
2.requires_grad_(True)以及detach
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
这里x.requires_grad=True 的作用是让 backward 可以追踪这个参数并且计算它的梯度,且后续关于x的函数也默认requires_grad参数时True,如若detach则不跟踪关于该变量的导数
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
3.张量进行反向传播
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x #对应位相乘,y.shape==x.shape
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
4.backward()函数的说明
刚才说到:对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
用一个例子解释:
以下转载自: https://www.cnblogs.com/bytesfly/p/why-need-gradient-arg-in-pytorch-backward.html
import torch
x1 = torch.tensor(1, requires_grad=True, dtype=torch.float)
x2 = torch.tensor(2, requires_grad=True, dtype=torch.float)
x3 = torch.tensor(3, requires_grad=True, dtype=torch.float)
x = torch.tensor([x1, x2, x3])
y = torch.randn(3)
y[0] = x1 * x2 * x3
y[1] = x1 + x2 + x3
y[2] = x1 + x2 * x3
y.backward(torch.tensor([0.1, 0.2, 0.3], dtype=torch.float))
print(x1.grad, x2.grad, x3.grad)
输出:
tensor(1.1000) tensor(1.4000) tensor(1.)
总结:
backward()不能对向量使用,必须要找一个标量函数进行反向传播求导,往往通过sum函数得到标量结果再回去求导,求和并不影响每个结果向量对各个变量的导数。
二、课后作业
1.题目
2.解答
1.需要暂存第一阶导数并计算两次。
2.报错提示:
尝试第二次向后遍历图(或者在已释放的张量之后直接访问已保存的张量)。当您调用.backward()或>autograd.grad()时,将释放图中保存的中间值。如果需要第二次向后遍历图,或者在调用backward后需要访>问保存的张量,则指定retain_graph=True。
pytorch处理自动求导时采用计算图机制,将代码分解成操作子,将计算表示成一个无环图
y.backward(retain_graph=True),若不设置,则在backward操作之后该无环图析构无法进行再次求导操作
3.报错:
grad can be implicitly created only for scalar outputs(backward()只能对标量进行操作)
a = torch.randn(size=(2,1), requires_grad=True)
a
d = f(a)
d.sum().backward()
a.grad
a.grad == d / a#对应位相除
输出:
tensor([[-1.3022],
[ 0.0885]], requires_grad=True)
tensor([[1024.],
[1024.]])
tensor([[True],
[True]])
4.修改函数计算标量对矩阵的导数:
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
#a = torch.randn(size=(2,2), requires_grad=True)
a=torch.tensor([[1,2],[3,4]],dtype=torch.float32,requires_grad=True)
d = f(a)
d.sum().backward()
a.grad
a.grad == d / a
输出:
tensor([[256., 256.],
[256., 256.]])
tensor([[True, True],
[True, True]])
5.结合calculus一章下的作图代码,修改函数如下:
def get_tangent(f,x,h):
return (f(x+h)-f(x))/h
x = np.arange(-3.15, 3.15, 0.01)
plot(x, [np.sin(x), get_tangent(np.sin,x,0.01)], 'x', 'f(x)', legend=['f(x)', 'Tangent function of sin(x)'])
输出如下:
较为奇怪的是如果定义get_tangent函数为:(f(x+h)-f(x-h))/2*h图像就会错得离谱(欢迎评论区指教)