深度学习-07-反向传播的自动化

深度学习-07-反向传播的自动化


本文是《深度学习入门2-自製框架》 的学习笔记,记录自己学习心得,以及对重点知识的理解。如果内容对你有帮助,请支持正版,去购买正版书籍,支持正版书籍不仅是尊重作者的辛勤劳动,也是鼓励更多优秀作品问世。

当前笔记内容主要为:步骤7 反向传播的自动化 章节的相关理解。

书籍总共分为5个阶段,每个阶段分很多步骤,最终是一步一步实现一个深度学习框架。例如前两个阶段为:

第 1 阶段共包括 10 个步骤 。 在这个阶段,将创建自动微分的机制
第 2 阶段,从步骤11-24,该阶段的主要目标是扩展当前的 DeZero ,使它能够执行更复杂的计算 ,使它能 够处理接收多个输入的函数和返回多个输出的函数


1.为反向传播的自动化创造条件

之前我们在实现反向传播的时候,我们是手动编写进行反向传播计算的代码,这意味着我们每次都要编写这些代码。例如下面的:

    A = Square()
    B = Exp()
    C = Square()

    x = Variable(np.array(0.5))
    a = A(x)
    b = B(a)
    y = C(b)

    y.grad = np.array(1.0)
    b.grad = C.backward(y.grad)
    a.grad = B.backward(b.grad)
    x.grad = A.backward(a.grad)
    print(x.grad)

如果计算图不一样,那么我们每次就需要白那些不一样的代码来计算反向传播。

图:不同计算图的例子。

那我们就开始思考如果自动化这块内容?书中讲解了一种机制:无论普通的计算流程(正向传播)中是什么样的计算,反向传播都能 自动进行 。这里引入一个概念:Define- by-Run。
Define-by-Run;是在深度学习中进行计算时 ,在计算之间建立"连接"的机 制 。 这种机制也称为动态计算图


计算图都是流水线式的计算 。 因此,只要以列表的形式记录函数的顺序,就可以通过反向回溯自动进行反向传播
    
    
在实现反向传播的自动化之前,我们先思考一下变量和函数之间的关系。解决方案主要从这里入手。我们需要考察变量和函数的关系并且用代码表示出来。

函数的变量包括"输入变量 "(input) 和"输出变量"(output)。函数是变量的 "父母" 如果没有父母,说明这个变量是用户的输入变量。

修改Variable 定义,函数和变量之间的"连接,让这个"连接"在执行普通计算(正向传播)的那一刻创建。

class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self,func):
        self.creator = func

上面代码,定义了熟悉 creator 标识是那个函数。并且定义了set 方法,来进行变量与函数之间的关联。

修改 Function 定义:

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(y)
        output.set_creator(self)  # 输出者保存创造者对象
        self.input = input
        self.output = output  # 保存输出者。我是创造者的信息,这是动态建立 "连接"这 一 机制的核心
        return output

    def forward(self, x):
        raise NotImplementedError()  # 使用Function  这个方法forward 方法的人 , 这个方法应该通过继承采实现

    def backward(self, gy):
        raise NotImplementedError()


在函数执行时,就设置输出变量的 creator 为”自己“。并且将输出保存起来。

经过上面的操作后,变量和函数之间建立的联系,并且这两种联系的建立时代码执行过程中创建的。

有了连接,我们手动检查下,计算下反向遍历计算图  

    A = Square()
    B = Exp()
    C = Square()

    x = Variable(np.array(0.5))
    a = A(x)
    b = B(a)
    y = C(b)

    assert y.creator == C
    assert y.creator.input == b
    assert y.creator.input.creator == B
    assert y.creator.input.creator.input == a
    assert y.creator.input.creator.input.creator == A
    assert y.creator.input.creator.input.creator.input == x

没有抛出异常,这意味着 assert 语句的所有条件都得到了满足。


2.尝试反向传播

利用上面的变量与函数之间的关系。进行反向传播计算。

先 y ---> b 

    y.grad = np.array(1.0)

    C = y.creator  # 获取函数
    b = C.input     # 获取函数的输入
    b.grad = C.backward(y.grad)    # 调用函数的backward 方法

然后  b ---> a
    
 

    B = b.creator
    b = B.input
    a.grad = B.backward(b.grad)

最后  a----> x 

    A = a.creator
    x = A.input
    x.grad = A.backward(a.grad)
    print(x.grad)


    
其实每一步的规律是:
1 获取函数 
2 获取函数的输入 
3 调用函数的 backward 方法


最后执行的结果为  
3.29744

3.增加backward 方法

前面这些反向传播的代码可以看山,它们有着相同的处理流程准确 来说,是从一个变量到前一个变量的反向传播逻辑相同。

修改Variable 类,增加一个新的方法-backward 


class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self,func):
        self.creator = func

    def backward(self):
        f = self.creator  # 获取函数
        if f is not None:
            x = f.input # 获取函数的输入
            x.grad =f.backward(self.grad) # 调用函数的backward() 方法
            x.backward()  # 调用自己前面的那个变量的 backward()方法
4.项目代码
'''
step07.py
自动实现反向传播

'''

import numpy as np

class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self,func):
        self.creator = func

    def backward(self):
        f = self.creator  # 获取函数
        if f is not None:
            x = f.input # 获取函数的输入
            x.grad =f.backward(self.grad) # 调用函数的backward() 方法
            x.backward()  # 调用自己前面的那个变量的 backward()方法

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(y)
        output.set_creator(self)  # 输出者保存创造者对象
        self.input = input
        self.output = output  # 保存输出者。我是创造者的信息,这是动态建立 "连接"这 一 机制的核心
        return output

    def forward(self, x):
        raise NotImplementedError()  # 使用Function  这个方法forward 方法的人 , 这个方法应该通过继承采实现

    def backward(self, gy):
        raise NotImplementedError()

class Square(Function):
    def forward(self, x):
        y = x ** 2
        return y

    def backward(self, gy):
        x= self.input.data
        gx = 2 * x * gy     #方法的参数 gy 是 一个 ndarray 实例 , 它是从输出传播而来的导数 。
        return gx


class Exp(Function):
    def forward(self, x):
        y = np.exp(x)
        return y

    def backward(self, gy):
        x = self.input.data
        gx = np.exp(x) * gy
        return gx

if __name__ == '__main__':
    A = Square()
    B = Exp()
    C = Square()

    x = Variable(np.array(0.5))
    a = A(x)
    b = B(a)
    y = C(b)

    assert y.creator == C
    assert y.creator.input == b
    assert y.creator.input.creator == B
    assert y.creator.input.creator.input == a
    assert y.creator.input.creator.input.creator == A
    assert y.creator.input.creator.input.creator.input == x

    # 反向传播
    y.grad = np.array(1.0)

    C = y.creator  # 获取函数
    b = C.input     # 获取函数的输入
    b.grad = C.backward(y.grad)    # 调用函数的backward 方法

    B = b.creator
    b = B.input
    a.grad = B.backward(b.grad)

    A = a.creator
    x = A.input
    x.grad = A.backward(a.grad)
    print(x.grad)

    # 自动反向传播
    x = Variable(np.array(0.5))
    a = A(x)
    b = B(a)
    y = C(b)
    y.grad = np.array(1.0)
    y.back()
    print(x.grad)
5.总结

经过此小节的代码丰富,目前我们可以执行自动反向传播计算。

相关推荐

  1. 深度学习反向传播数学计算过程

    2024-06-06 00:04:03       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-06 00:04:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-06 00:04:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-06 00:04:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-06 00:04:03       18 阅读

热门阅读

  1. Oracle 表数据段收缩示例

    2024-06-06 00:04:03       8 阅读
  2. CMakeLists.txt和Package.xml

    2024-06-06 00:04:03       8 阅读
  3. C++--DAY3

    C++--DAY3

    2024-06-06 00:04:03      8 阅读
  4. 直播带货行业的瓶颈来了吗?

    2024-06-06 00:04:03       9 阅读
  5. 旋转之后截取图像

    2024-06-06 00:04:03       7 阅读
  6. 服务器硬件基础知识

    2024-06-06 00:04:03       8 阅读
  7. 【Redis】本地锁和分布式锁的区别

    2024-06-06 00:04:03       7 阅读
  8. Kafka 请求处理揭秘:从入门到精通

    2024-06-06 00:04:03       9 阅读
  9. 如何发现并解决 Redis 热点 Key 问题

    2024-06-06 00:04:03       9 阅读
  10. 字幕转换: vtt转为srt

    2024-06-06 00:04:03       7 阅读
  11. 都可以写好后端接口

    2024-06-06 00:04:03       6 阅读
  12. 服务器环境搭建

    2024-06-06 00:04:03       8 阅读
  13. Sass详解

    2024-06-06 00:04:03       7 阅读