自动求导(完善算子)
注:本系列搭建的深度学习框架名称叫numpyflow,缩写nf,用以熟悉目前主流的深度学习框架的基础和原理。本系列的目标是使用nf可以训练resnet。
开源地址:RanFeng/NumpyFlow
简介
上一节自动求导(封装算子)中,创建了加法算子并重写了__add__
方法来对加法操作进行自动求导。这一节我们完善Tensor
类,并将所有的算子操作都整合到一个函数中,统一进行调用,使得代码结构更加清晰。
完善算子
class Tensor:
def __init__(self, data=None, creator=None):
self.data = data.copy()
if creator is None:
creator = Assign()
creator(self) # 看似没有用,但是可以用来在计算图的可视化,可视化Assign节点
self.creator = creator
self.grad = np.zeros_like(self.data, dtype=np.float64)
我们添加了一个creator
域,用以记录这个Tensor
的创建者是哪个算子,如果不提供creator
的话,默认创建者是一个叫Assign
的赋值算子:
class Operation:
"""
所有Op的基类
"""
def __call__(self, *input_vars):
self.variables = input_vars
raise NotImplementedError
def backward(self, grad, **kwargs):
raise NotImplementedError
class Assign(Operation):
def __call__(self, a):
self.variables = (a)
return a
def backward(self, grad, **kwargs):
return None
我们同时创建一个算子的基类,用以统一所有算子的调用格式。
接下来,我们需要将所有的算子操作整合到一个函数中,这样代码结构的调整有利于我们后期进一步扩大我们的项目。
完善Tensor
我们在Tensor
类中添加一个方法:
@classmethod
def _op(cls,
Op: Type[Operation],
*input_vars,
op_args=None,
op_kwargs=None,
requires_grad=False
):
if op_args is None:
op_args = tuple()
if op_kwargs is None:
op_kwargs = dict()
tensor_vars = tuple(
cls(var, requires_grad=False) if not isinstance(var, cls) else var
for var in input_vars
)
requires_grad = requires_grad or any(var.requires_grad for var in tensor_vars)
f = Op()
op_out = f(*tensor_vars, *op_args, **op_kwargs)
return cls(op_out, requires_grad=requires_grad, creator=f)
def __add__(self, other):
return self._op(Add, self, other)
同时将__add__
方法改写成统一调用_op
方法。上一节举例z=x+y
中,x
和y
都是Tensor
类,但是z
却是numpy.ndarray
类,这是不符合常识的,也不利于复杂的运算,将所有的算子操作都放进_op
之后,输出的也会是Tensor
类。同时,我们也需要对Tensor.backward
方法进行调整:
def backward(self, grad=None):
self.grad += grad
self.creator.backward(grad)
这样,就将梯度按照自动求导(基础知识)中描述的那样传递下去了,有兴趣可以去github中查看更加详细的源码。
解耦合
完成上面两个步骤,算子和Tensor
就完成了初步的解耦合,他们也可以分开来,独立地进行开发了。Tensor
类能自动求导的操作种类完全取决于你实现了多少种基本算子,各种基本算子还可以相互组合,形成新的操作。
至此,我们已经完成了第一个目标:自动求导功能的实现。
下一节我们开始第二个目标:搭建基本的神经层。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END