『PyTorch』第五弹_深入理解autograd_下:函数扩展&高阶导数

一、封装新的PyTorch函数

继承Function类

forward:输入Variable->中间计算Tensor->输出Variable

backward:均使用Variable

线性映射

from torch.autograd import Function

class MultiplyAdd(Function):                       # <----- 类需要继承Function类

    @staticmethod                                  # <-----forward和backward都是静态方法
    def forward(ctx, w, x, b):                     # <-----ctx作为内部参数在前向反向传播中协调
        print(‘type in forward‘,type(x))
        ctx.save_for_backward(w,x)                 # <-----ctx保存参数
        output = w * x + b
        return output                              # <-----forward输入参数和backward输出参数必须一一对应

    @staticmethod                                  # <-----forward和backward都是静态方法
    def backward(ctx, grad_output):                # <-----ctx作为内部参数在前向反向传播中协调
        w,x = ctx.saved_variables                  # <-----ctx读取参数
        print(‘type in backward‘,type(x))
        grad_w = grad_output * x
        grad_x = grad_output * w
        grad_b = grad_output * 1
        return grad_w, grad_x, grad_b              # <-----backward输入参数和forward输出参数必须一一对应

调用方法一

类名.apply(参数)

输出变量.backward()

import torch as t
from torch.autograd import Variable as V

x = V(t.ones(1))
w = V(t.rand(1), requires_grad = True)
b = V(t.rand(1), requires_grad = True)
print(‘开始前向传播‘)
z=MultiplyAdd.apply(w, x, b)                       # <-----forward
print(‘开始反向传播‘)
z.backward() # 等效                                 # <-----backward

# x不需要求导,中间过程还是会计算它的导数,但随后被清空
print(x.grad, w.grad, b.grad)
开始前向传播
type in forward <class ‘torch.FloatTensor‘>
开始反向传播
type in backward <class ‘torch.autograd.variable.Variable‘>
(None,  Variable containing:
   1
  [torch.FloatTensor of size 1],  Variable containing:
   1
  [torch.FloatTensor of size 1])

调用方法二

类名.apply(参数)

输出变量.grad_fn.apply()

x = V(t.ones(1))
w = V(t.rand(1), requires_grad = True)
b = V(t.rand(1), requires_grad = True)
print(‘开始前向传播‘)
z=MultiplyAdd.apply(w,x,b)                         # <-----forward
print(‘开始反向传播‘)

# 调用MultiplyAdd.backward
# 会自动输出grad_w, grad_x, grad_b
z.grad_fn.apply(V(t.ones(1)))                      # <-----backward,在计算中间输出,buffer并未清空,所以x的梯度不是None
开始前向传播
type in forward <class ‘torch.FloatTensor‘>
开始反向传播
type in backward <class ‘torch.autograd.variable.Variable‘>
(Variable containing:
  1
 [torch.FloatTensor of size 1], Variable containing:
  0.7655
 [torch.FloatTensor of size 1], Variable containing:
  1
 [torch.FloatTensor of size 1])

之所以forward函数的输入是tensor,而backward函数的输入是variable,是为了实现高阶求导。backward函数的输入输出虽然是variable,但在实际使用时autograd.Function会将输入variable提取为tensor,并将计算结果的tensor封装成variable返回。在backward函数中,之所以也要对variable进行操作,是为了能够计算梯度的梯度(backward of backward)。下面举例说明,有关torch.autograd.grad的更详细使用请参照文档。

二、高阶导数

grad_x =t.autograd.grad(y, x, create_graph=True)

grad_grad_x = t.autograd.grad(grad_x[0],x)

x = V(t.Tensor([5]), requires_grad=True)
y = x ** 2

grad_x = t.autograd.grad(y, x, create_graph=True)
print(grad_x) # dy/dx = 2 * x

grad_grad_x = t.autograd.grad(grad_x[0],x)
print(grad_grad_x) # 二阶导数 d(2x)/dx = 2
(Variable containing:
  10
 [torch.FloatTensor of size 1],)
(Variable containing:
  2
 [torch.FloatTensor of size 1],)

三、梯度检查

t.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-3)

此外在实现了自己的Function之后,还可以使用gradcheck函数来检测实现是否正确。gradcheck通过数值逼近来计算梯度,可能具有一定的误差,通过控制eps的大小可以控制容忍的误差。

class Sigmoid(Function):

    @staticmethod
    def forward(ctx, x):
        output = 1 / (1 + t.exp(-x))
        ctx.save_for_backward(output)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        output,  = ctx.saved_variables
        grad_x = output * (1 - output) * grad_output
        return grad_x                            

# 采用数值逼近方式检验计算梯度的公式对不对
test_input = V(t.randn(3,4), requires_grad=True)
t.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-3)
True

测试效率,

def f_sigmoid(x):
    y = Sigmoid.apply(x)
    y.backward(t.ones(x.size()))

def f_naive(x):
    y =  1/(1 + t.exp(-x))
    y.backward(t.ones(x.size()))

def f_th(x):
    y = t.sigmoid(x)
    y.backward(t.ones(x.size()))

x=V(t.randn(100, 100), requires_grad=True)
%timeit -n 100 f_sigmoid(x)
%timeit -n 100 f_naive(x)
%timeit -n 100 f_th(x)

实际测试结果,

245 μs ± 70.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
211 μs ± 23.3 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
219 μs ± 36.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

书中说的结果,

100 loops, best of 3: 320 μs per loop
100 loops, best of 3: 588 μs per loop
100 loops, best of 3: 271 μs per loop

很奇怪,我的结果竟然是:简单堆砌<官方封装<自己封装……不过还是引用一下书中的结论吧:

显然f_sigmoid要比单纯利用autograd加减和乘方操作实现的函数快不少,因为f_sigmoid的backward优化了反向传播的过程。另外可以看出系统实现的buildin接口(t.sigmoid)更快。

原文地址:https://www.cnblogs.com/hellcat/p/8453615.html

时间: 2024-10-09 05:32:28

『PyTorch』第五弹_深入理解autograd_下:函数扩展&高阶导数的相关文章

『PyTorch』第五弹_深入理解autograd_下:Variable梯度探究

查看非叶节点梯度的两种方法 在反向传播过程中非叶子节点的导数计算完之后即被清空.若想查看这些变量的梯度,有两种方法: 使用autograd.grad函数 使用hook autograd.grad和hook方法都是很强大的工具,更详细的用法参考官方api文档,这里举例说明基础的使用.推荐使用hook方法,但是在实际使用中应尽量避免修改grad的值. 求z对y的导数 x = V(t.ones(3)) w = V(t.rand(3),requires_grad=True) y = w.mul(x) z

『PyTorch』第五弹_深入理解autograd_上:Variable

一.Variable类源码简介 class Variable(_C._VariableBase): """ Attributes: data: 任意类型的封装好的张量. grad: 保存与data类型和位置相匹配的梯度,此属性难以分配并且不能重新分配. requires_grad: 标记变量是否已经由一个需要调用到此变量的子图创建的bool值.只能在叶子变量上进行修改. volatile: 标记变量是否能在推理模式下应用(如不保存历史记录)的bool值.只能在叶变量上更改.

『PyTorch』第五弹_深入理解Tensor对象_中上:索引

一.普通索引 示例 a = t.Tensor(4,5) print(a) print(a[0:1,:2]) print(a[0,:2]) # 注意和前一种索引出来的值相同,shape不同 print(a[[1,2]]) # 容器索引 3.3845e+15 0.0000e+00 3.3846e+15 0.0000e+00 3.3845e+15 0.0000e+00 3.3845e+15 0.0000e+00 3.3418e+15 0.0000e+00 3.3845e+15 0.0000e+00 3

『PyTorch』第五弹_深入理解Tensor对象_中下:数学计算以及numpy比较

一.简单数学操作 1.逐元素操作 t.clamp(a,min=2,max=4)近似于tf.clip_by_value(A, min, max),修剪值域. a = t.arange(0,6).view(2,3) print("a:",a) print("t.cos(a):",t.cos(a)) print("a % 3:",a % 3) # t.fmod(a, 3) print("a ** 2:",a ** 2) # t.po

『PyTorch』第五弹_深入理解Tensor对象_下:从内存看Tensor

Tensor存储结构如下, 如图所示,实际上很可能多个信息区对应于同一个存储区,也就是上一节我们说到的,初始化或者普通索引时经常会有这种情况. 一.几种共享内存的情况 view a = t.arange(0,6) print(a.storage()) b = a.view(2,3) print(b.storage()) print(id(a.storage())==id(b.storage())) a[1] = 10 print(b) 上面代码,我们通过.storage()可以查询到Tensor

『PyTorch』第四弹_通过LeNet初识pytorch神经网络_下

『PyTorch』第四弹_通过LeNet初识pytorch神经网络_上 # Author : Hellcat # Time : 2018/2/11 import torch as t import torch.nn as nn import torch.nn.functional as F class LeNet(nn.Module): def __init__(self): super(LeNet,self).__init__() self.conv1 = nn.Conv2d(3, 6, 5)

『PyTorch』第十弹_循环神经网络

『cs231n』作业3问题1选讲_通过代码理解RNN&图像标注训练 对于torch中的RNN相关类,有原始和原始Cell之分,其中RNN和RNNCell层的区别在于前者一次能够处理整个序列,而后者一次只处理序列中一个时间点的数据,前者封装更完备更易于使用,后者更具灵活性.实际上RNN层的一种后端实现方式就是调用RNNCell来实现的. 一.nn.RNN import torch as t from torch import nn from torch.autograd import Variab

『PyTorch』第六弹_最小二乘法的不同实现手段(待续)

PyTorch的Variable import torch as t from torch.autograd import Variable as V import matplotlib.pyplot as plt from IPython import display # 指定随机数种子 t.manual_seed(1000) def get_fake_data(batch_size=8): x = t.rand(batch_size,1)*20 y = x * 2 + 3 + 3*t.ran

『PyTorch』第三弹_自动求导

torch.autograd 包提供Tensor所有操作的自动求导方法. 数据结构介绍 autograd.Variable 这是这个包中最核心的类. 它包装了一个Tensor,并且几乎支持所有的定义在其上的操作.一旦完成了你的运算,你可以调用 .backward()来自动计算出所有的梯度,Variable有三个属性: 访问原始的tensor使用属性.data: 关于这一Variable的梯度则集中于 .grad: .creator反映了创建者,标识了是否由用户使用.Variable直接创建(No