深度学习中的数据操作(五) 自动微分 基于pytorch实现

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)  #这也可以在定义x的时候完成:torch.arange(4.0,requer_grad_=True)
print(x.grad) #此时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()  #2x(x分别赋值0,1,2,3,4)
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) #相当于前面有一个值为x^2的常数
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)
#a = torch.tensor([[1.0,2.0],[3.0,4.0]], requires_grad=True)
print(a)
d = f(a)
d.backward()  #经过上面的一系列操作,d中的每个元素依然是xxx*a,即其求导后就为一个常数xxx
#若a为矩阵 用 d.sum().backgrad()
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) 。
'''
#1
#个人理解为计算二阶导数是要在一阶导数的基础上进行的,从梯度的角度来说,其需要累积梯度,自然会比一节导数的开销更大
#2
#会报错,因为pytorch自动累计梯度,需要加上x.grad.zero_()来消除原先计算的梯度,防止累积,这样才可以重新计算梯度
#3
#d.backgrad()报错:RuntimeError: grad can be implicitly created only for scalar outputs
#需要改为d.sum().backgrad()
#4
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)

#5
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)    #这里都是用torch的原因是因为numpy对反向传播并不友好
plt.plot(x.detach(),y.detach(),label = 'sin(x)')    #写法也要替换成torch的写法
z = y.sum()     #由于y是一个张量,因此给他求和
z.backward()
plt.plot(x.detach(),x.grad,label = 'sin(x)-lim')

你可能感兴趣的:(limu深度学习笔记,pytorch,深度学习,python)