如何使用numpy实现一个全连接神经网络?(上)

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

如何使用numpy实现一个全连接神经网络?(上)

gfanqi   2019-12-22 我要评论

  全连接神经网络的概念我就不介绍了,对这个不是很了解的朋友,可以移步其他博主的关于神经网络的文章,这里只介绍我使用基本工具实现全连接神经网络的方法。

  所用工具:

    numpy == 1.16.4

    matplotlib 最新版

  我的思路是定义一个layer类,在这个类里边构建传播的前向传播的逻辑,以及反向传播的逻辑,然后在构建一个model类,在model类里边,将layer类中的对象拼接,就可以得到我们想要的模型。

  在Layers类的传播中,在Dense层中,我是按照公式output = X*w+b,来计算输出。X 是 (m,n)的矩阵,表示有m行数据,每一个数据是n维的向量。w是一个(n,1)的矩阵,是我们要优化的参数。b是一个(m,1)的矩阵,是偏置。在Activation层中,我是按照公式 output = f激活(X) 来计算输出的。f激活是激活函数,是逐元素函数。将Dense层与Activation层叠加,就能实现output = f激活(X*w+b)的效果,如果多次交替叠加,就相当于在计算output = f激活( f激活(f激活(X*w+b)*w+b)*w+b),这里只演示了三层,实际上这个就是全连接神经网络的基本数学表达式。

  构建这个模型的难点在于梯度的计算以及反向传播逻辑。中间层的每一层的输出对输入以及偏置求导,都是一个矩阵对另一个矩阵的求导。而矩阵的求导不同于高数中所学的导数,链式法则也有一些不同。关于这部分内容可参考:矩阵求导术(上),矩阵求导术(下),这里不再讲述。笔者正是在参考了这两篇文章的前提下实现这个过程的。

  导入工具包:

  

import numpy as np
import matplotlib.pyplot as plt

 

  定义Layer类中的Dense类中类:(这里可以把layers类单独拿出来作为一个父类,其余的层可以继承layers,然后钉子自己的反向传播逻辑,可以减少重复代码,这里为了方便展示,没有那么做)

class Layers:
    class Dense:
        '''
        全连接层
        '''

        def __init__(self, output_category):
            '''
            接收并初始化一个输出维度,用于确定这一层w的维度,以及用于梯度计算
            :param output_category:
            '''
            self.output_category = output_category

        def __call__(self, Input):
            '''
            使用魔法方法,实例化对象后,随机的方式初始化w参数,
            实例化输入数据,计算本层前向传播方式
            :param Input:
            :return:
            '''
            w_shape = (Input.shape[1], self.output_category)
            b_shape = (Input.shape[0], self.output_category)
            self.w = np.mat(np.random.random(size=w_shape))
            self.b = np.mat(np.random.random(size=b_shape))
            self.Input = Input
            result = self.Input * self.w +self.b
            self.result = result
            return result

        def backpropagation(self, w_grad_from_next_layer=None, learning_rate=None, use_bias=False):
            '''
            反向传播算法的数学描述,公式参考
            https://zhuanlan.zhihu.com/p/24863977
            公式 Y = X * w +b ,
            dY = dX * w + X * dw + db
               = I * dX * w + X * dw * I + I * db * I   (I是单位矩阵,
               公式里每个I都不一样维度,具体是多少要参考它与谁相乘)
            vec(dY) = np.kron(w,I)*vec(dX)  + np.kron(I,w)*vec(dw) + np.kron(I,I)*vec(db)
            :param w_grad_from_next_layer:从下一层传过来的梯度
            :param learning_rate:学习率
            :return:
            '''
            mid_w_grad = np.mat(np.kron(np.eye(self.output_category), self.Input))
            self.w_grad = w_grad_from_next_layer * mid_w_grad


            mid_x_grad = np.kron(self.w.T, np.eye(self.Input.shape[0]))
            self.x_grad = w_grad_from_next_layer * mid_x_grad

            if use_bias == True:
                mid_b_grad = np.kron(np.eye(self.output_category), np.eye(self.Input.shape[0]))
                self.b_grad = w_grad_from_next_layer * mid_b_grad

            if learning_rate is not None:
                self.w = self.w - learning_rate * self.w_grad.T
                if use_bias == True:
                    self.b = self.b - learning_rate * self.b_grad
            return self.x_grad

 

  定义Layers 中的Activation类:(这里初始化的时候必须传入一个激活函数,这个激活函数应该是一个类,应该有一个call方法来定义它的前向逻辑,应该有一个grad方法来计算梯度)

class Layers:
  class Activation:
        '''
        激活层
        '''
        def __init__(self, activate_func):
            '''
            传入激活方程类
            :param output_category:
            '''
            self.activate_func = activate_func

        def __call__(self, Input):
            '''
            使用魔法方法,实例化对象后,随机的方式初始化w参数,
            实例化输入数据,计算本层前向传播方式
            :param Input:
            :return:
            '''
            self.Input = Input
       self.activate_func_obj = self.activate_func(self.Input) result = self.activate_func_obj.call() self.result = result return result def backpropagation(self, w_grad_from_next_layer=None, learning_rate=None, ): ''' 反向传播算法的数学描述,公式参考 https://zhuanlan.zhihu.com/p/24863977 公式 Y = f(X) (f是逐元素函数) dY = df(X) = f'(X) ⊙ dX (⊙表示出逐元素相乘,也就是通缩意义上的对应位置相乘) 所以 vec(dY) = np.diagflat(f'(X))* vec(dX) :param w_grad_from_next_layer:从下一层传过来的梯度 :param learning_rate:学习率 :return: ''' mid_x_grad = self.activate_func_obj.grad() self.x_grad = w_grad_from_next_layer * mid_x_grad return self.x_grad

  定义了Activation后,但这个方程需要传入一个激活函数类,现在我们顺从定义一个函数类,用来给激活层提供激活函数类。这个类有两个功能,一是定义正向传播方法,一种是定义梯度计算。

class Funcs:
    '''
    方程
    '''
    class Relu:
        '''
        Relu函数
        '''

        def __init__(self,Input):
            self.Input = Input
            pass

        def call(self):
            '''
            计算结果,并返回
            :return:
            '''

            result = np.multiply((self.Input>0),self.Input)
            return result

        def grad(self):
            '''
            梯度计算
            :return:
            '''

            mid_grad = (self.Input>0)*1.0 #乘1 讲bollen类型转化为float类型
            result = np.diagflat(mid_grad)
            return result

  类似的,我们可以定义更多的种类的激活函数,比如selu,sigmoid等等。到目前为止,如果我们可以定义一个常用的损失函数,那么我们就具备了搭建简单的神经网络的基本要求了。这里我们的定义一个平方差损失函数。

class Funcs:
    '''
    方程
    '''

    class SqureLossError:
        '''
        平方误差类
        '''
        def __init__(self,y,pred):
            self.y=y
            self.pred = pred

        def call(self):
            '''
            计算损失,并返回
            :return:
            '''
            result = np.linalg.norm(self.y-self.pred)
            return result

        def grad(self):
            '''
            梯度计算
            :return:
            '''
            result = np.mat(2*(self.pred-self.y).T)
            return result

  到这里,我们已经具备了搭建神经网络的基本板块了,我们可以用这些基本模块来搭建一个简单的神经网络模型。下一章,我会继续详述一下这个过程。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们