1.5 自动微分
"""
1.5.1 一个简单的例子
求导是深度学习优化算法的关键步骤,因此自动微分可以大大简化深度学习的计算步骤
从很小的例子来看,y=2x^?x,若想对y求导,就可以借助pythorch的相关函数
"""
import torch
x = torch.arange(5.0)
print(x)
"""接下来要计算y关于x的梯度,自然需要有地方来存储这些计算出来的梯度"""
x.requires_grad_(True)
print(x.grad)
y = 2 * torch.dot(x,x)
"""在计算梯度时,需要用到反向传播函数
反向传播(英語:Backpropagation,缩写为BP)是“误差反向传播”的简称,是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见方法。该方法对网络中所有权重计算损失函数的梯度。这个梯度会回馈给最佳化方法,用来更新权值以最小化损失函数。
在pytorch中可以利用backward()函数来进行梯度计算,前面已经提到梯度就是函数偏导数的矩阵表示
"""
y.backward()
x.grad
print(x.grad)
"""上述中,y = 2x^Tx,那么其梯度的单个元素表达式应该是其导数,即为4x,接下来便来验证是否正确"""
print(x.grad == 4*x)
"""若要计算另外函数的梯度,则需要清空原有梯度计算值,因为pytorch中默认会积累梯度,这样就需要用到x.grad.zero_()"""
x.grad.zero_()
y = x.sum()
y.backward()
print(x.grad)
"""1.5.2 非标量变量的反向传播
当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。对于高阶和高维的y和x,求导的结果可以是一个高阶张量。
但当我们调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
"""
x.grad.zero_()
y = x * x
print(y)
print(y.sum())
y.sum().backward()
print(x.grad)
"""1.5.3 分离计算
有时,我们希望将某些计算移动到记录的计算图之外。例如,假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。想象一下,我们想计算z关于x的梯度,但由于某种原因,我们希望将y视为一个常数,并且只考虑到x在y被计算后发挥的作用。相当于,我们只想考虑x与z,y只作为一个常数一样存在,虽然其是通过y与x的计算得出的。
在这里,我们可以分离y来返回一个新变量u,该变量与y具有相同的值,但丢弃计算图中如何计算y的任何信息。换句话说,梯度不会向后流经u到x,这就是detach()函数的作用,只会单纯的将y作为一个常量,彻底抛弃与y有关的信息。因此,下面的反向传播函数计算z=u*x关于x的偏导数,同时将u作为常数处理, 而不是z=x*x*x关于x的偏导数。
"""
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
print(x.grad)
print(x.grad == u)
"""1.5.4 Python控制流的梯度计算
使用自动微分的一个好处就是当函数的计算图需要经过python中条件、循环或任意函数调用时,我们仍然可以计算得到的变量的梯度。下面的例子里,while循环的迭代次数和if语句的结果都取决于输入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.tensor(size = (), requires_grad=True)
print(a)
d = f(a)
d.backward()
print(a.grad)
print(a.grad == d/a)
'''
课后作业:
1.为什么计算二阶导数比一阶导数的开销要更大?
2.在运行反向传播函数之后,立即再次运行它,看看会发生什么。
3.在控制流的例子中,我们计算d关于a的导数,如果我们将变量a更改为随机向量或矩阵,会发生什么?
4.重新设计一个求控制流梯度的例子,运行并分析结果。
5.使 f(x)=sin(x) ,绘制 f(x) 和 df(x)/dx 的图像,其中后者不使用 f'(x)=cos(x) 。
'''
def g(a):
b = a * 10.0
while b.norm() < 2000:
b = b * 2
if b.sum() > 3000:
c = b
else:
c = b * 100
return c
a = torch.randn(1,1)
a.requires_grad = True
d = g(a)
d.backward()
print(a.grad)
from pylab import *
import matplotlib.pyplot as plt
import numpy as np
x = torch.linspace(start = -np.pi, end = np.pi)
x.requires_grad = True
y = torch.sin(x)
plt.plot(x.detach(),y.detach(),label = 'sin(x)')
z = y.sum()
z.backward()
plt.plot(x.detach(),x.grad,label = 'sin(x)-lim')
|