如何使用numpy实现一个全连接神经网络?(上)
gfanqi 人气:0全连接神经网络的概念我就不介绍了,对这个不是很了解的朋友,可以移步其他博主的关于神经网络的文章,这里只介绍我使用基本工具实现全连接神经网络的方法。
所用工具:
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
到这里,我们已经具备了搭建神经网络的基本板块了,我们可以用这些基本模块来搭建一个简单的神经网络模型。下一章,我会继续详述一下这个过程。
加载全部内容