• paddle动态图自定义算子(python版)


    1.动态图自定义Python算子

    先说一下辅助用的,这个接口:

    Paddle 通过 PyLayer 接口和PyLayerContext接口支持动态图的Python端自定义OP。 

    自定义好的算子如何使用呢?需要特定的接口,这个接口就是PyLayer

    PyLayer 接口描述如下:

    1. class PyLayer:
    2. @staticmethod
    3. def forward(ctx, *args, **kwargs):
    4. pass
    5. @staticmethod
    6. def backward(ctx, *args, **kwargs):
    7. pass
    8. @classmethod
    9. def apply(cls, *args, **kwargs):
    10. pass

     其中,

    1. --forward 是自定义Op的前向函数,必须被子类重写,它的第一个参数是 PyLayerContext 对象,其他输入参数的类型和数量任意。
    2. --backward 是自定义Op的反向函数,必须被子类重写,其第一个参数为 PyLayerContext 对象,其他输入参数为forward输出Tensor的梯度。它的输出Tensor为forward输入Tensor的梯度。
    3. --apply 是自定义Op的执行方法,构建完自定义Op后,通过apply运行Op。

    PyLayerContext 接口描述如下:

    1. class PyLayerContext:
    2. def save_for_backward(self, *tensors):
    3. pass
    4. def saved_tensor(self):
    5. pass

    其中,

    • save_for_backward 用于暂存backward需要的Tensor,这个API只能被调用一次,且只能在forward中调用。

    • saved_tensor 获取被save_for_backward暂存的Tensor

    ===================================== 

     ========重点开始了===========

    动态图自定义

    如何编写动态图Python Op

    以下以tanh为例,介绍如何利用 PyLayer 编写Python Op。

    • 第一步:创建PyLayer子类并定义前向函数和反向函数

    前向函数和反向函数均由Python编写,可以方便地使用Paddle相关API来实现一个自定义的OP。需要遵守以下规则:

    1. forwardbackward都是静态函数,它们的第一个参数是PyLayerContext对象。

    2. backward 除了第一个参数以外,其他参数都是forward函数的输出Tensor的梯度,因此,backward输入的Tensor的数量必须等于forward输出Tensor的数量。如果您需在backward中使用forward中的Tensor,您可以利用save_for_backwardsaved_tensor这两个方法传递Tensor

    3. backward的输出可以是Tensor或者list/tuple(Tensor),这些Tensorforward输入Tensor的梯度。因此,backward的输出Tensor的个数等于forward输入Tensor的个数。如果backward的某个返回值(梯度)在forward中对应的Tensorstop_gradient属性为False,这个返回值必须是Tensor类型。

    两步搞定,就像写函数一样,先写方法,然后调用。这是一个类似的比喻。

    第一步:以正切算子为例,其他算子  比葫芦画瓢  应该都会吧。

    1. import paddle
    2. from paddle.autograd import PyLayer
    3. # 通过创建`PyLayer`子类的方式实现动态图Python Op
    4. class cus_tanh(PyLayer):
    5. @staticmethod
    6. def forward(ctx, x):
    7. y = paddle.tanh(x)
    8. # ctx 为PyLayerContext对象,可以把y从forward传递到backward。
    9. ctx.save_for_backward(y)
    10. return y
    11. @staticmethod
    12. # 因为forward只有一个输出,因此除了ctx外,backward只有一个输入。
    13. def backward(ctx, dy):
    14. # ctx 为PyLayerContext对象,saved_tensor获取在forward时暂存的y。
    15. y, = ctx.saved_tensor()
    16. # 调用Paddle API自定义反向计算
    17. grad = dy * (1 - paddle.square(y))
    18. # forward只有一个Tensor输入,因此,backward只有一个输出。
    19. return grad
    • 第二步:通过apply方法组建网络。 apply的输入为forward中除了第一个参数(ctx)以外的输入,apply的输出即为forward的输出。

    1. data = paddle.randn([2, 3], dtype="float32")
    2. data.stop_gradient = False
    3. # 通过 apply运行这个Python算子
    4. z = cus_tanh.apply(data)
    5. z.mean().backward()
    6. print(data.grad)

     运行结果;

    1. Tensor(shape=[2, 3], dtype=float32, place=Place(gpu:0), stop_gradient=False,
    2. [[0.15810412, 0.16457692, 0.15256986],
    3. [0.16439323, 0.10458803, 0.16661729]])
    4. Process finished with exit code 0

    下面是一些注意事项,特殊情况特殊分析

    • 为了从forwardbackward传递信息,您可以在forward中给PyLayerContext添加临时属性,在backward中读取这个属性。如果传递Tensor推荐使用save_for_backwardsaved_tensor,如果传递非Tensor推荐使用添加临时属性的方式。

    1. import paddle
    2. from paddle.autograd import PyLayer
    3. import numpy as np
    4. class tanh(PyLayer):
    5. @staticmethod
    6. def forward(ctx, x1, func1, func2=paddle.square):
    7. # 添加临时属性的方式传递func2
    8. ctx.func = func2
    9. y1 = func1(x1)
    10. # 使用save_for_backward传递y1
    11. ctx.save_for_backward(y1)
    12. return y1
    13. @staticmethod
    14. def backward(ctx, dy1):
    15. y1, = ctx.saved_tensor()
    16. # 获取func2
    17. re1 = dy1 * (1 - ctx.func(y1))
    18. return re1
    19. input1 = paddle.randn([2, 3]).astype("float64")
    20. input2 = input1.detach().clone()
    21. input1.stop_gradient = False
    22. input2.stop_gradient = False
    23. z = tanh.apply(x1=input1, func1=paddle.tanh)
    • forward的输入和输出的类型任意,但是至少有一个输入和输出为Tensor类型。

    1. # 错误示例
    2. class cus_tanh(PyLayer):
    3. @staticmethod
    4. def forward(ctx, x1, x2):
    5. y = x1+x2
    6. # y.shape: 列表类型,非Tensor,输出至少包含一个Tensor
    7. return y.shape
    8. @staticmethod
    9. def backward(ctx, dy):
    10. return dy, dy
    11. data = paddle.randn([2, 3], dtype="float32")
    12. data.stop_gradient = False
    13. # 由于forward输出没有Tensor引发报错
    14. z, y_shape = cus_tanh.apply(data, data)
    15. # 正确示例
    16. class cus_tanh(PyLayer):
    17. @staticmethod
    18. def forward(ctx, x1, x2):
    19. y = x1+x2
    20. # y.shape: 列表类型,非Tensor
    21. return y, y.shape
    22. @staticmethod
    23. def backward(ctx, dy):
    24. # forward两个Tensor输入,因此,backward有两个输出。
    25. return dy, dy
    26. data = paddle.randn([2, 3], dtype="float32")
    27. data.stop_gradient = False
    28. z, y_shape = cus_tanh.apply(data, data)
    29. z.mean().backward()
    30. print(data.grad)
    • 如果forward的某个输入为Tensorstop_gredient = True,则在backward中与其对应的返回值应为None

    1. class cus_tanh(PyLayer):
    2. @staticmethod
    3. def forward(ctx, x1, x2):
    4. y = x1+x2
    5. return y
    6. @staticmethod
    7. def backward(ctx, dy):
    8. # x2.stop_gradient=True,其对应梯度需要返回None
    9. return dy, None
    10. data1 = paddle.randn([2, 3], dtype="float32")
    11. data1.stop_gradient = False
    12. data2 = paddle.randn([2, 3], dtype="float32")
    13. z = cus_tanh.apply(data1, data2)
    14. fake_loss = z.mean()
    15. fake_loss.backward()
    16. print(data1.grad)
    • 如果forward的所有输入Tensor都是stop_gredient = True的,则backward不会被执行。

    1. class cus_tanh(PyLayer):
    2. @staticmethod
    3. def forward(ctx, x1, x2):
    4. y = x1+x2
    5. return y
    6. @staticmethod
    7. def backward(ctx, dy):
    8. return dy, None
    9. data1 = paddle.randn([2, 3], dtype="float32")
    10. data2 = paddle.randn([2, 3], dtype="float32")
    11. z = cus_tanh.apply(data1, data2)
    12. fake_loss = z.mean()
    13. fake_loss.backward()
    14. # 因为data1.stop_gradient = Truedata2.stop_gradient = True,所以backward不会被执行。
    15. print(data1.grad is None)

    自于静态的自定义算子,我不太感兴趣

    可以参考官网

    自定义Python算子-使用文档-PaddlePaddle深度学习平台

  • 相关阅读:
    好用的TCP-UDP_debug工具下载和使用
    C#使用正则表达式 判断string 字符串是否包含汉字
    【韩老师零基础30天学会Java 02】学会 2种冒泡排序 和 2种二分查找 递归与非递归
    四旋翼无人机学习第11节--页连接符与跨页连接符的使用
    微服务与SpringCloud
    01 WIFI ----- SDIO接口驱动
    通信原理学习笔记2-1:模拟调制——相干解调的载波恢复、锁相环(平方环/Costas环)、变频/混频技术
    利用证书给pdf文件添加数字签名
    POJ 1975 (传递闭包)
    100. Go单测系列0---单元测试基础
  • 原文地址:https://blog.csdn.net/Vertira/article/details/127130516