自己动手写一个神经网络(1)—Anet 诞生记

174 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

自己动手去一个神经网络框架,代码和相关视频可以文章结尾处找到

基础类

Tensor

属性
属性名类型说明
datanumpy array持有数据
gradnumpy array梯度
ctxContext反向传播的环境
方法
  • backward

Context

属性名类型说明
argFucntion
parentsTensory = wx
saved_tensors[]:numpy array
  • save_for_backward

Functions

这里 Fucntion 是运算操作符的基类,apply 函数主要

class Function:
    def apply(self:Tensor,arg,*x):
        ctx = Context(arg,self,*x)
        ret = Tensor(arg.forward(ctx, self.data,*[t.data for t in x]))
        ret._ctx = ctx
        return ret

这里 apply 方法会被添加到 Tensor 上,所以这里 self 是指向 Tensor 实例

  • 创建 Context,将运算操作符类如 Sum、Dot 或者 Add 传入保存在 arg
  • self,*x 就是参与运算的 Tensor,比例 y=wx 中的 w 和 x 不过这时他们都是 Tensor 类型
  • 然后调用 arg 前向方法,这里传入刚刚创建好的 ctx 就是 y=wx 的 numpy类型传入,因为 x 和 w 都是 Tensor 类型,而他们 x.data 和 w.data 都应该是 numpy 类型,然后保存在 context 的 saved_tensors 数组
  • 最后将更新后的 ctx 赋值给前向传播计算结果 Tenesor 的 _ctx 属性

regiseter

这个方法是将基于 Function 的运算算子例如 Sum、Add 或者 Dot 等添加到 Tensor 类

def register(name,fxn):
    setattr(Tensor,name,partialmethod(fxn.apply,fxn))

这里有 2 点值得说一说

  • 使用 setattr 方法将Sum、Add 或者 Dot 这些函数添加到 Tensor 这个类上
  • 使用 partialmethod 将 apply 方法注入 arg 参数,返回一个新的方法做 Tensor 的方法

Dot、Sum 和 Add 等操作类

class Dot(Function):
    @staticmethod
    def forward(ctx, input, weight):
        ctx.save_for_backward(input,weight)
        return input.dot(weight)

    @staticmethod
    def backward(ctx,grad_output):
        
        input,weight = ctx.saved_tensors

        grad_input = grad_output.dot(weight.T)
        grad_weight = grad_output.T.dot(input).T

        return grad_input,grad_weight

forward(前向传播)

  • 这里传入的 input 和 weight 是 numpy array 类型,然后调用 ctx 对于 y =wx 那么 x 和 w 就分别对一个 input 和 weight y 都是前向传播的结果,最终 ctx 添加到前向传播的结果 y 这个 Tensor 上
  • 然后就是计算 input 和 weight 的内积

backward(反向传播)

  • 首先从 ctx 取出 weight 和 input
  • 计算 local 梯度也就是 w 和 x 对于 y 梯度,并且将 local 梯度乘以 y 对于 L 梯度,就是分别 input 和 weight 梯度

classes_relationship_002.PNG

  • 注意这里 grad (图中) 不是tensor类型而是 numpy array 类型

前向传播

forward_003.PNG

反向传播

softmax_computation.PNG

def backward(self,allow_fill=True):
    if self._ctx is None:
        return

    if self.grad is None and allow_fill:
        assert self.data.size == 1
        self.grad = np.ones_like(self.data)

    assert(self.grad is not None)

    grads = self._ctx.arg.backward(self._ctx,self.grad)
    if len(self._ctx.parents) == 1:
        grads = [grads]

    for t,g in zip(self._ctx.parents,grads):
        if g.shape != t.data.shape:
            print("grad shape must match tensor shape in %r, %r != %r" %(self._ctx.arg, g.shape, t.data.shape))
            #assert(False) "print something"
        t.grad = g
        t.backward(False)

结合下图对反向传播实现简单说明一下,out_sum 是一个标量,是对向量每个分量的求

test_computation_graph.PNG

相关资源

github 代码地址 github.com/zideajang/a… 视频资源