I dont quite understand what Question 3 wants us to answer. When I change the original a to a random vector or matrix, nothing strange happens. Can anyone help me? What is the situation supposed to be?
- Because it has to invoke ‘backward()’ twice which definitely consumes more resources.
- RuntimeError occurs:
RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.
- RuntimeError occurs:
RuntimeError: grad can be implicitly created only for scalar outputs
def f(x):
if torch.min(x) < 0:
y = x * x
else:
y = torch.mean(x)
return y
x = torch.randn(1, 5, requires_grad=True)
print(x)
y = f(x)
if y.dim() > 0:
y = y.sum()
y.backward()
print(x.grad)
print(x.grad==2*x if torch.min(x)<0 else x.grad==1/x.shape[1]*torch.ones_like(x))
%run calculus.ipynb
x = torch.arange(-2*torch.pi,2*torch.pi+0.1,0.1).requires_grad_()
f = lambda x: torch.sin(x)
f(x).sum().backward()
plot(x.detach(), [f(x).detach(), x.grad], 'x', 'y', legend=["sin(x)","D(sinx)"])
交作业:
- 为什么计算二阶导数比一阶导数的开销要更大?
我认为计算二阶导数需要更多的内存和计算资源。
内存:计算二阶导数时,需要存储更多的中间结果和梯度信息。
计算资源:计算二阶导数时,需要进行更多的计算操作,这增加了计算时间和资源消耗。
- 在运行反向传播函数之后,立即再次运行它,看看会发生什么。
发生了错误,因为在第一次backward()之后,梯度已经被清空了,所以在第二次backward()时,梯度为None
但我们可以通过设置retain_graph=True来保留计算图,这样就可以多次调用backward()了
# 课程的例1
import torch
x = torch.arange(4.0, requires_grad=True)
y = x * x # y 是矢量
y.sum().backward(retain_graph=True) # 由于 backward() 函数只能对标量调用,所以需要用 sum() 函数将 y 转换为标量
print(x.grad)
y.sum().backward()
print(x.grad)
- 在控制流的例子中,我们计算
d
关于a
的导数,如果将变量a
更改为随机向量或矩阵,会发生什么?
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=(7,), requires_grad=True) # a 现在是向量
d = f(a)
d.sum().backward()
print(a)
print(a.grad)
发生对应的长度变化。
梯度的本质是偏导数,而 PyTorch 会根据自动微分机制对每个分量求导。因此,
a 的梯度结构总是和 a 的结构一致:
标量:梯度是标量。ex: size=()
向量:梯度是每个分量的偏导数,形成向量。ex: size=(7,)
矩阵:梯度是每个分量的偏导数,形成矩阵。ex: size=(7,12)
- 重新设计一个求控制流梯度的例子,运行并分析结果。
def f(a):
if a > 0:
b = a * 2
else:
b = a * 3
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
# 创建一个随机标量 a,并设置 requires_grad=True
a = torch.randn(size=(), requires_grad=True)
# 计算 d = f(a)
d = f(a)
# 进行反向传播,计算梯度
d.sum().backward()
# 打印 a 的梯度
print("a 的值:", a)
print("a 的梯度:", a.grad)
运行结果:
a 的值: tensor(1.0775, requires_grad=True)
a 的梯度: tensor(1024.)
1 a 是一个随机标量,值为 1.0775
2 因为 a > 0,所以 b = a * 2,即 b = 1.0775 * 2 = 2.155。
进入 while 循环,不断将 b 乘以 2,直到 b.norm() 大于或等于 1000。
3 因为 b.sum() > 0,所以 c = b
4 d = c = b = 1103.36
进行反向传播,计算 a 的梯度。
因为 b 是通过不断乘以 2 得到的,所以 b 相对于 a 的梯度是 2^p,其中 p 是循环的次数。
在这个例子中,循环执行了 9 次,所以 p = 9,梯度是 2^9 = 512。
但是因为初始时 b = a * 2,所以总的梯度是 2 * 2^9 = 2^10 = 1024。
- 使f(x)=sin(x),绘制f(x)和df(x)/dx的图像,其中后者不使用f′(x)=cos(x)。
from math import pi
import matplotlib.pyplot as plt
def f(x):
y = torch.sin(x)
return y
x = torch.arange(0, 2*pi, 2*pi/100, requires_grad=True)
y = f(x)
y.sum().backward()
# 将张量从计算图中分离出来,然后转换为 numpy 数组并绘制 y = sin(x) 和 dy/dx 的图形
plt.plot(x.detach().numpy(), y.detach().numpy(), label='y = sin(x)')
plt.plot(x.detach().numpy(), x.grad.detach().numpy(), label='dy/dx')
# 添加标签和图例
plt.xlabel('x')
plt.ylabel('y / dy/dx')
plt.legend()
plt.grid(True)
plt.show()
踩了两个坑,关于只能对标量使用反向传播
1、这里的x和f(x)=sinx都是一个张量,画图的时候要用detach()方法看成标量,再进行画图。
d2l.plot(x.detach(), [f(x).detach(), x.grad], 'x', 'f(x)', figsize=(10, 2.5))
2、先对sinx求和得到张量,就可以得到x的梯度了。
f(x).sum().backward()