向量链式法则:
自动求导:(计算一个函数在指定值上的导数)
计算图:
自动求导的两种模式:(正向和反向)
复杂度:
自动求导实现:
import torch
x = torch.arange(4.0) # 创建一个向量x并分配一个初始值
print(x)
# 在计算y关于x的梯度之前,需要一个地方来存储梯度
x.requires_grad_(True) # 等价于x = torch.arange(4.0, requires_grad=True)
print(x.grad) # 初始值为None
# 计算y
y = 2*torch.dot(x,x)
print(y)
# 通过调用反向传播函数来自动计算y关于x每个分量的梯度
y.backward() # 求导
print(x.grad)
print(x.grad == 4*x)
# 计算x的另一个函数
# 在默认情况下,pytorch会累加梯度,所以需要清除之前的值
x.grad.zero_() # 清除x的梯度值,重新赋值为0
y = x.sum() # 向量求和导数全为1
y.backward()
print(x.grad)
# 深度学习中,目的不是计算微分矩阵,而是批量中每个样本单独计算的偏导数之和
# 对非标量调用`backward`需要传入一个`gradient`参数,该参数指定微分函数关于`self`的梯度。在我们的例子中,我们只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x # x和y都是向量
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward() # 求和-标量,再求导
print(x.grad)
# 将某些计算移动到记录的计算图之外
x.grad.zero_()
y = x * x
u = y.detach() # 把y当作一个常数而不是关于x的函数。u就是x*x的值。返回一个新的Tensor,但返回的结果是没有梯度的。
z = u * x
z.sum().backward()
print(x.grad == u)
x.grad.zero_()
y.sum().backward() # y是关于x的函数
print(x.grad == 2 * x)
# python控制流的梯度计算
def f(a):
b = a * 2
while b.norm() < 1000: # norm是L2范数
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(),requires_grad=True) # a是随机数,size=()指a是标量
d = f(a) # d的结果等于一个常数k乘a,k就是a.grad!
d.backward()
print(a.grad == d/a)
课后练习:
-
为什么计算二阶导数比一阶导数的开销要更大?
-
在运行反向传播函数之后,立即再次运行它,看看会发生什么。
-
在控制流的例子中,我们计算d关于a的导数,如果我们将变量a更改为随机向量或矩阵,会发生什么?此时,计算结果f(a)不再是标量。结果会发生什么?我们如何分析这个结果?
-
重新设计一个求控制流梯度的例子。运行并分析结果。
-
使f(x)=sin(x),绘制f(x)和(frac{df(x)}{dx})的图像,其中后者不使用f'(x)=cos(x)。
import torch
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
# 1为什么计算二阶导数比一阶导数的开销要更大?
# 二阶导数的计算是在一阶导数的基础上进行的
# 2在运行反向传播函数之后,立即再次运行它,看看会发生什么
x = torch.arange(4.0, requires_grad=True)
y = 2*torch.dot(x, x)
y.backward()
print(x.grad)
# y.backward() # 报错,不可以连续两次backward,需要更新x.grad
# 3将a改成随机向量或矩阵会发生什么
def f(a):
b = a * 2
while b.norm() < 1000: # norm是L2范数
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(10,requires_grad=True) # a是随机数,size=()指a是标量
d = f(a) # d的结果等于一个常数k乘a,k就是a.grad!
d.sum().backward() # 直接backward会报错
print(a.grad)
# print(a.grad == d/a)
# 4重新设计一个求控制流梯度的例子。运行并分析结果
def f(e):
g = e * e
while g < 100: # norm是L2范数
g = g * 2
if g.norm() > 0:
h = g + 100
else:
h = 100 * g
return h
x = torch.randn(size=(),requires_grad=True) # a是随机数,size=()指a是标量
y = f(x) # d的结果等于一个常数k乘a,k就是a.grad!
y.backward()
print(x.grad == y/x)
# 5使f(x)=sin(x),绘制f(x)和(frac{df(x)}{dx})的图像,其中后者不使用f'(x)=cos(x)。
x = torch.arange(-10,10,0.1,requires_grad=True,dtype=torch.float32)
y = torch.sin(x)
y.sum().backward()
plt.plot(x.detach(),y.detach(),label='y=sin(x)')
plt.plot(x.detach(),x.grad,label='dy/dx')
plt.show()



