使用numpy搭建自己的深度学习框架(三)

自动求导(完善算子)

注:本系列搭建的深度学习框架名称叫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中,xy都是Tensor类,但是z却是numpy.ndarray类,这是不符合常识的,也不利于复杂的运算,将所有的算子操作都放进_op之后,输出的也会是Tensor类。同时,我们也需要对Tensor.backward方法进行调整:

    def backward(self, grad=None):
        self.grad += grad
        self.creator.backward(grad)

这样,就将梯度按照自动求导(基础知识)中描述的那样传递下去了,有兴趣可以去github中查看更加详细的源码。

解耦合

完成上面两个步骤,算子和Tensor就完成了初步的解耦合,他们也可以分开来,独立地进行开发了。Tensor类能自动求导的操作种类完全取决于你实现了多少种基本算子,各种基本算子还可以相互组合,形成新的操作。

至此,我们已经完成了第一个目标:自动求导功能的实现。

下一节我们开始第二个目标:搭建基本的神经层。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值