第 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
类并实现其静态方法forward
和backward
来创建具体的函数。
#通过继承 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博客
整理不易,诚望各位看官点赞 收藏 评论 予以支持,这将成为我持续更新的动力源泉。若您在阅览时存有异议或建议,敬请留言指正批评,让我们携手共同学习,共同进取,吾辈自当相互勉励!