这篇博客介绍的内容是pytorch深度学习框架中最重要的内容之一,如果想要成为pytorch大佬的话,这篇博客你绝对不能错过,本人亲自复现使用过代码,获益良多,希望能帮到大家。这个代码呢也不是我写的,我自己加了些个人的理解上去,如果有不妥的地方,希望大家不吝赐教,话不多说,上车吧骚年~
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: yehaizi time:2019/8/13:22:17
# 自动求导
# PyTorch的Autograd模块实现了深度学习的算法中的向后传播求导数,在张量(Tensor类)上的所有操作,
# Autograd都能为他们自动提供微分,简化了手动计算导数的复杂过程。
# 从0.4起, Variable 正式合并入Tensor类, 通过Variable嵌套实现的自动微分功能已经整合进入了Tensor类中。
# 虽然为了代码的兼容性还是可以使用Variable(tensor)这种方式进行嵌套, 但是这个操作其实什么都没做
# 以后的代码建议直接使用Tensor类进行操作,因为官方文档中已经将Variable设置成过期模块。
# 在张量创建时,通过设置 requires_grad 标识为Ture来告诉Pytorch需要对该张量进行自动求导
# PyTorch会记录该张量的每一步操作历史并自动计算
# PyTorch会自动追踪和记录对与张量的所有操作,当计算完成后调用.backward()方法自动计算梯度并且将计算结果保存到grad属性中。
import torch
x=torch.rand(5,5,requires_grad=True)
print(x)
y=torch.rand(5,5,requires_grad=True)
print(y)
# 此处grad_fn已经被赋予了一个新的函数,这个函数引用了一个创建了这个Tensor类的Function对象。
# Tensor和Function互相连接生成了一个非循环图,它记录并且编码了完整的计算历史。每个张量都有一个.grad_fn属性
# 如果这个张量是用户手动创建的那么这个张量的grad_fn是None。
# 意思就是这里的张量a是由Function对象 sumBackward0 来创建的
a=torch.sum(x+y)
print(a)
# 开始调用反向传播函数,计算其梯度
# 如果Tensor类表示的是一个标量(即它包含一个元素的张量),则不需要为backward()指定任何参数,但是如果它有更多的元素
# 则需要指定一个gradient参数,它是形状匹配的张量
# 1、简单自动求导 此处对x,y求导
# z.backward()相当于是z.backward(torch.tensor(1.))的简写
a.backward()
print(x.grad,y.grad)
# 2、复杂的自动求导
x=torch.rand(5,5,requires_grad=True)
print(x)
y=torch.rand(5,5,requires_grad=True)
z=x**2+y**3
print(z)
# 返回值不是一个标量,所以需要输入一个大小相同的张量作为参数,这里我们用ones_like函数根据x生成一个张量
z.backward(torch.ones_like(x))
print(x.grad) # x的导数也就是2x
# 我们可以使用with torch.no_grad()上下文管理器临时禁止对已设置requires_grad=True的张量进行自动求导。
# 这个方法在测试集计算准确率的时候会经常用到,例如:
with torch.no_grad():
print((x+y**2).requires_grad)
# Autograd 过程解析
# 为了说明Pytorch的自动求导原理,我们来尝试分析一下PyTorch的源代码,虽然Pytorch的 Tensor和 TensorBase都是使用CPP来实现的
# 但是可以使用一些Python的一些方法查看这些对象在Python的属性和状态。 Python的 dir() 返回参数的属性、方法列表。z是一个Tensor变量,
# 看看里面有哪些成员变量。
print(dir(z))
# 返回很多,我们直接排除掉一些Python中特殊方法(以_开头和结束的)和私有方法(以开头的,直接看几个比较主要的属性:
# .is_leaf:记录是否是叶子节点。通过这个属性来确定这个变量的类型 在官方文档中所说的“graph leaves”,“leaf variables”,
# 都是指像x,y这样的手动创建的、而非运算得到的变量,这些变量成为创建变量。 像z这样的,是通过计算后得到的结果称为结果变量
# 一个变量是创建变量还是结果变量是通过.is_leaf来获取的。
print("x.is_leaf="+str(x.is_leaf))
print("z.is_leaf="+str(z.is_leaf))
# 为什么我们执行z.backward()方法会更新x.grad和y.grad呢? .grad_fn属性记录的就是这部分的操作,
# x是手动创建的没有通过计算,所以他被认为是一个叶子节点也就是一个创建变量,而z是通过x与y的一系列计算得到的,所以不是叶子结点也就是结果变量。
# 这样整个规程就很清晰了:当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
# 这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点。
# 计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
# 求导结束。所有的叶节点的grad变量都得到了相应的更新
# 最终当我们执行完c.backward()之后,a和b里面的grad值就得到了更新。