深度学习笔记1:自动微分与神经网络实现(附代码)

发布于:2024-11-27 ⋅ 阅读:(72) ⋅ 点赞:(0)

第 1 阶段:自动微分

步骤1:作为“箱子”的变量

1.1什么是变量

变量是存储数据的容器,用于在程序中传递和修改数据。变量是在程序中用于存储和操作数据的基本单元,它们可以随计算过程而改变其值。

1.2 实现Variable 类

Variable 类不仅存储了数据,还能跟踪梯度信息,并记录其创建者。

import numpy as np
 
class Variable:
    def __init__(self, data, grad=None):
        self.data = data
        self.grad = grad if grad is not None else np.zeros_like(data)
        self.creator = None  # 用于记录创建该变量的函数

1.3 NumPy的多维数组

NumPy提供了强大的多维数组对象ndarray,支持高效的数组和矩阵运算。

步骤2:创建变量的函数

2.1 什么是函数

函数是一段封装了特定逻辑的可重用代码块,接受输入并产生输出。

函数是执行特定任务的代码块,可以接受输入并返回输出。

2.2 Function类的实现

通过定义静态方法 forward 和 backward 来分别处理前向和反向传播

class Function:
    @staticmethod
    def forward(x):
        # 前向传播逻辑
        raise NotImplementedError
    
    @staticmethod
    def backward(x, gy):
        # 反向传播逻辑
        raise NotImplementedError

2.3 使用Function 类

通过继承Function类并实现其静态方法forwardbackward来创建具体的函数。

#通过继承 Function 类并实现其方法来创建具体的函数,如 Exp 函数class Exp(Function):
    @staticmethod
    def forward(x):
        return np.exp(x)
​
    @staticmethod
    def backward(x, gy):
        return gy * np.exp(x)

步骤3:函数的连续调用

通过依次调用多个函数,可以构建复杂的计算图,实现更复杂的计算逻辑。

3.1 Exp函数的实现

class Exp(Function):
    @staticmethod
    def forward(x):
        return np.exp(x)
    
    @staticmethod
    def backward(x, gy):
        return gy * np.exp(x)

3.2 函数的连续调用

通过链式调用多个函数,可以构建复杂的计算图。

# 3.2 函数的连续调用
# 通过链式调用多个函数,可以构建复杂的计算图。

class Square(Function):
    @staticmethod
    def forward(x):
        return x ** 2

    @staticmethod
    def backward(x, gy):
        return 2 * x * gy

class Exp(Function):
    @staticmethod
    def forward(x):
        return np.exp(x)

    @staticmethod
    def backward(x, gy):
        return np.exp(x) * gy

# 连续调用示例
def function_chain_call():
    """
    此函数展示了函数的连续调用
    """
    x = Variable(np.array(2.0))
    y = Square.forward(x)
    z = Exp.forward(y)
    print(z.data)

步骤4:数值微分

4.1 什么是导数

导数是函数在某一点的变化率,用于描述函数在该点的切线斜率。导数表示函数在某一点处的变化率,反映了函数的局部变化趋势。

4.2 数值微分的实现

通过数值方法(如有限差分法)近似计算导数。使用有限差分等数值方法来近似计算导数。


def numerical_derivative(f, x, epsilon=1e-4):
    """
    数值微分函数,通过有限差分法近似计算导数

    参数:
    f: 要计算导数的函数
    x: 计算导数的点
    epsilon: 微小增量,默认为 1e-4

    返回:
    函数在 x 点的近似导数
    """
    return (f(x + epsilon) - f(x - epsilon)) / (2 * epsilon)

# 示例函数
def example_function(x):
    return x ** 2 + 3 * x + 1

# 计算数值导数
def numerical_derivative_example():
    """
    此函数计算示例函数在特定点的数值导数
    """
    x = 5
    derivative = numerical_derivative(example_function, x)
    print(derivative)

4.3 复合函数的导数

复合函数的导数可以通过链式法则计算。根据链式法则,复合函数的导数是各层函数导数的乘积。

4.4 数值微分存在的问题

数值微分可能受到舍入误差的影响,而且对于复杂函数计算量较大。

步骤5:反向传播的理论知识

5.1 链式法则

链式法则是计算复合函数导数的基本法则。是计算复合函数导数的重要法则,通过依次相乘各层的导数来得到最终的导数。

5.2 反向传播的推导

反向传播通过链式法则从输出层向输入层逐层计算梯度。从输出层开始,依据链式法则逐步向输入层计算梯度。

5.3 用计算图表示

计算图是一种表示函数计算过程的图形化工具,可以帮助理解反向传播。以图形的方式直观展示函数的计算流程和依赖关系,有助于理解反向传播。

步骤6:手动进行反向传播

6.1 Variable 类的功能扩展

Variable类中添加用于记录梯度传播路径的属性。添加属性来记录梯度的传播路径和相关信息。

# 6.1 Variable 类的功能扩展
class Variable:
    def __init__(self, data, grad=None, creator=None):
        self.data = data
        self.grad = grad if grad is not None else np.zeros_like(data)
        self.creator = creator

6.2 Function类的功能扩展

Function类中添加用于记录输入变量的属性。记录输入变量,以便在反向传播时使用。

# 6.2 Function 类的功能扩展
class Function:
    def __init__(self):
        self.inputs = []

    def forward(self, *inputs):
        self.inputs = inputs
        # 前向传播逻辑
        raise NotImplementedError

    def backward(self, gy):
        # 反向传播逻辑
        raise NotImplementedError

6.3 Square类和Exp类的功能扩展

为这些具体的函数类添加完整的反向传播实现。

# 6.3 Square 类和 Exp 类的功能扩展
class Square(Function):
    @staticmethod
    def forward(x):
        y = x.data ** 2
        output = Variable(y)
        output.creator = Square
        return output

    @staticmethod
    def backward(x, gy):
        gx = 2 * x.data * gy.data
        return Variable(gx)

class Exp(Function):
    @staticmethod
    def forward(x):
        y = np.exp(x.data)
        output = Variable(y)
        output.creator = Exp
        return output

    @staticmethod
    def backward(x, gy):
        gx = np.exp(x.data) * gy.data
        return Variable(gx)

6.4 反向传播的实现

通过递归地调用函数类的 backward 方法,实现梯度的反向传播。

# 6.4 反向传播的实现
def backward(v):
    """
    反向传播函数

    参数:
    v: 最终的输出变量
    """
    funcs = []
    while v.creator is not None:
        funcs.append(v.creator)
        v = v.creator.inputs[0]

    gy = Variable(np.ones_like(v.data))
    for func in funcs[::-1]:
        gy = func.backward(func.inputs[0], gy)

步骤7:反向传播的自动化

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

修改 Variable 和 Function 类的结构和方法,使其能够自动记录和处理计算图。

7.2 尝试反向传播

进行初步的测试和验证,确保自动化反向传播的基本功能正常。

7.3 增加backward方法

在 Variable 类中添加 backward 方法,以方便触发反向传播过程。

# 7.3 增加 backward 方法
class Variable:
    def backward(self):
        """
        触发反向传播
        """
        backward(self)

步骤8:从递归到循环

8.1 现在的Variable 类

分析当前Variable类的实现,具体的结构和功能,找出可能存在的性能瓶颈或可优化点。

8.2 使用循环实现

使用循环代替递归,以提高性能。将递归方式改为循环方式,以提高计算效率和避免递归深度限制。

8.3 代码验证

编写测试用例来验证循环实现的正确性和性能优

# 9.2 简化 backward 方法
class Variable:
    def backward(self):
        """
        简化 backward 方法的调用
        """
        backward(self)

势。

步骤9:让函数更易用

9.1 作为Python函数使用

修改Function类,使其能够像Python函数一样调用,提高代码的简洁性和可读性。

# 9.1 作为 Python 函数使用
""" 
使 Function 类能够像函数一样被调用
参数:*inputs: 输入参数
返回:计算结果
"""

class Function:
    def __call__(self, *inputs):
        
        outputs = self.forward(*inputs)
        return outputs

9.2 简化backward方法

简化backward方法的调用方式。优化 backward 方法的调用接口,使其更易于使用和理解。

# 9.2 简化 backward 方法
class Variable:
    def backward(self):
        """
        简化 backward 方法的调用
        """
        backward(self)

9.3 只支持ndarray

限制输入数据类型为ndarray,确保计算的一致性和高效性,以提高性能。

步骤10:测试

10.1 Python的单元测试

利用 Python 的 unittest 框架编写单元测试用例,对各个功能模块进行测试。

# 10.1 Python 的单元测试
import unittest

class TestFunctions(unittest.TestCase):
    def test_square_forward(self):
        """
        测试 Square 函数的前向传播
        """
        x = Variable(np.array(2.0))
        y = Square.forward(x)
        self.assertEqual(y.data, 4.0)

    def test_square_backward(self):
        """
        测试 Square 函数的反向传播
        """
        x = Variable(np.array(2.0))
        y = Square.forward(x)
        y.backward()
        self.assertEqual(x.grad, 4.0)

if __name__ == '__main__':
    unittest.main()

10.2 square函数反向传播的测试

专门针对 square 函数的反向传播进行详细的测试,确保其正确性。

import numpy as np
import unittest

class Square(Function):
    @staticmethod
    def forward(x):
        return x ** 2

    @staticmethod
    def backward(x, gy):
        return 2 * x * gy

class TestSquareBackpropagation(unittest.TestCase):
    def test_square_backpropagation(self):
        x = Variable(np.array(3.0))
        y = Square.forward(x)
        y.backward()
        expected_grad = 6.0  
        self.assertAlmostEqual(x.grad, expected_grad, places=5)

if __name__ == '__main__':
    unittest.main()

10.3 通过梯度检验来自动测试

使用梯度检验方法自动验证反向传播计算的准确性。

import numpy as np

class Variable:
    def __init__(self, data, grad=None):
        self.data = data
        self.grad = grad if grad is not None else np.zeros_like(data)

class Function:
    def forward(self, x):
        raise NotImplementedError

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

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

    def backward(self, x, gy):
        return 2 * x * gy

def gradient_check(f, x, epsilon=1e-4):
    x.grad = None
    f(x).backward()
    analytic_grad = x.grad

    num_grad = np.zeros_like(x.data)
    for i in range(x.data.size):
        tmp_val = x.data[i]
        x.data[i] = tmp_val + epsilon
        f_pos = f(x).data
        x.data[i] = tmp_val - epsilon
        f_neg = f(x).data
        x.data[i] = tmp_val
        num_grad[i] = (f_pos - f_neg) / (2 * epsilon)

    return np.allclose(analytic_grad, num_grad, atol=1e-5)

x = Variable(np.random.rand(5))
if gradient_check(Square, x):
    print("梯度检验通过")
else:
    print("梯度检验未通过")

10.4 测试小结

总结测试结果,分析是否通过测试,找出可能存在的问题和改进方向。

import numpy as np

# 假设之前的测试结果存储在一个字典中
test_results = {
    "square_backpropagation": True,
    "gradient_check": True
}

# 总结测试通过情况
passed_tests = [test_name for test_name, passed in test_results.items() if passed]
failed_tests = [test_name for test_name, passed in test_results.items() if not passed]

# 打印通过和未通过的测试
if passed_tests:
    print("通过的测试:")
    for test in passed_tests:
        print(f"- {test}")

if failed_tests:
    print("未通过的测试:")
    for test in failed_tests:
        print(f"- {test}")

# 分析可能存在的问题和提出改进方向
print("可能的问题:")
if failed_tests:
    print(" - 未通过测试的函数实现可能有误或未处理好边界情况。")
else:
    print(" - 复杂场景和极端输入下可能有潜在问题,测试用例覆盖可能不全。")

print("改进方向:")
print(" - 调试未通过测试的函数。")
print(" - 补充更多样化的测试用例。")
print(" - 优化数值计算方法和算法。")
print(" - 定期重构和优化代码。")

本篇系统梳理了 DeZero 框架,涵盖从自动微分到神经网络测试的全流程,包括变量与函数的相关操作数值微分与反向传播函数类扩展与优化、易用性提升及全面测试环节。

由于篇幅较长且整理过程较为繁琐,我计划逐步整理并发布后续内容。我深信,科技应当服务于大众,我希望可以为促进知识的共享与学习,贡献自己绵薄之力,根据我的整理节省后来人的时间。如果对自动化定位感兴趣,可以看之前相关博客 

关于python自动化定位的9种函数方法-CSDN博客

python自动登录跳转获取信息等_自动登录网站查找信息做记录-CSDN博客

整理不易,诚望各位看官点赞 收藏 评论 予以支持,这将成为我持续更新的动力源泉。若您在阅览时存有异议或建议,敬请留言指正批评,让我们携手共同学习,共同进取,吾辈自当相互勉励!