人工智能神经网络:200行代码手写了一个全连接神经网络(NN),基于FP和BP算法,单条数据计算更新梯度,速度比较慢,计划改为批量计算,代码详细注释

    技术2022-07-10  138

    1.算法原理就是FP和BP算法,采用的梯度下降更新梯度。

    2.算法的loss函数还是交叉熵函数,也是常用的分类loss函数。

    3.训练数据集是使用的mnist数据集28*28的手写数字灰度图片,用csv文件保存的结构数据,格式如下,第一个列是label,后面784个列是每个像素的值。

    4.目前没有写算法说明,回头再更吧,算法有详细的注释。

    import numpy as np # 基于交叉熵的loss函数计算和梯度 class CrossEntropy(): # 正向计算 def forward(self, input, y): ''' 正向计算成本函数的残熵结果值,注意input和y都是概率值在[0, 1]之间, :param input: 预测的分类结果,是个向量,存放的是各分类结果的概率, 例如:[0.5, 0.8, 1, 0.3, 0.1] :param y: 实际分类结果,例如:[0,1,0,0,0] :return: 返回一条训练数据的交叉熵成本值 ''' # 公式 ce = -sum(y*log(p) + (1 - y)*log(1 - p)) # 为了规避是0 为了规避log出现负数,先计算log然后代入公式 m = [np.log(x) if x >0 else 0 for x in input] n = [np.log(1 - x) if 1 - x > 0 else 0 for x in input] result = - np.sum(y * m + (1 - y) * n) return result pass def backward(self, input, y): ''' # 反向计算损失函数的梯度 :param input: 预测的分类结果,是个向量,存放的是各分类结果的概率, 例如:[0.5, 0.8, 1, 0.3, 0.1] :param y: 实际分类结果,例如:[0,1,0,0,0] :return: 返回的是成本函数计算出来的梯度值 ''' # input是神经网络的分类结果 return (input - y)/(input * (1 - y)) pass pass # sigmoid激活函数和梯度计算 class Sigmoid(): def forward(self, input): ''' 正向计算求值 :param input: 输入是个向量,一次计算多个输入 :return: ''' return 1.0 / (1.0 + np.exp(-input)) pass # 基于sigmoid函数的梯度公式:g'(x) = g(x)*(1 - g(x)),g是sigmoid函数g(x) = 1/(1+e^(-x)) def backward(self, output): ''' 反向计算求梯度 :param output: 是正向计算的结果,output其实是forward的计算结果 :return: ''' return output * (1 - output) pass pass # 定义了神经网络层的正向计算和梯度计算 class Dense(): def __init__(self, inputSize=0, outputSize=0, activator=Sigmoid()): ''' 对于一个神经网络层来说,需要知道输入的神经元个数和输出的神经元个数 :param inputSize: 输入神经元的个数 :param outputSize: 输出神经元的个数 :param activator: 当前层的激活函数 :return: ''' self.inputSize = inputSize self.outputSize = outputSize self.activator = activator # 定义和初始化权重矩阵 基于公式 f(Wx) = W.dot(x) + b W是系数矩阵 x是输入的神经元, # b是截距项 x和b都是列向量 self.W = np.random.uniform(-0.1, 0.1, (outputSize, inputSize)) # 定义截距项数组,初始化为全0 self.b = np.zeros((outputSize,)) # 定义输出数组 self.output = np.zeros((outputSize,)) pass def forward(self, inputArray): ''' 定义当前层的正向计算 :param inputArray: 输入是一个列向量 :return: ''' # 当前层的神经元 self.input = inputArray # 基于公式 f(Wx) = W.dot(x) + b 然后使用激活 h(f(Wx)) = h(W.dot(x) + b) # 计算出的是下一个层的神经元 self.output = self.activator.forward(self.W.dot(self.input) + self.b) # 输出其实就是下一层的输入 return self.output pass def backward(self, inputDelta): ''' 反向计算梯度 :param inputDelta:是反向传播时的上一层,其实是当前层的后一层的delta值 :return: ''' # 计算当前层的delta,detla其实计算的是神经元的梯度 self.delta = self.W.T.dot(inputDelta) * self.activator.backward(self.input) # 通过神经元的梯度计算出当前层权重的梯度 # 梯度计算注意:inputDelta 和 self.input 是两个向量, # 例如假设:inputDelta=[1,2,3,4] input=[1,3,2,4] # inputDelta通过nputDelta[:,np.newaxis]后,扩展了列变为二维:[[1],[2],[3],[4]] # input通过self.input[np.newaxis,:]后,扩展了行变为:[[1,3,2,4]] # 这样就是一个4*1 的矩阵和 一个1*4的矩阵点积运算,得到一个4*4的矩阵 self.WGrad = np.dot(inputDelta[:,np.newaxis], self.input[np.newaxis,:]) # 因为b值是做加法,所以他的梯度就是后一次计算的梯度Delta self.bGrad = inputDelta pass def update(self, learningRate): ''' 根据学习速率和已经获得的梯度,更新权重系数和截距项的梯度 :param learningRate: :return: ''' self.W -= self.WGrad * learningRate self.b -= self.bGrad * learningRate pass def verbose(self, verbose=0): ''' :param verbose: 0不显示梯度变化信息, 1显示 :return: ''' if verbose: print("Grandient update now:") print("W:", self.W) print("b:", self.b) pass pass class Model(object): def __init__(self): self.layers = [] # 保存层对象,一个三层网络,只需要来个层对象 self.loss = None pass def predict(self, input): ''' 正向计算,预测结果 :param input: 是一个向量 :return: ''' output = input for layer in self.layers: output = layer.forward(output) pass return output pass def add(self, layer): ''' 向神经网络添加层 :return: ''' self.layers.append(layer) pass def compile(self): ''' 构建神经网络模型,初始化模型相关的参数 :return: ''' pass def optimizor(self, y, loss=None, learningRate=0.005): ''' 优化器,计算梯度和更新梯度 :return: ''' # 当使用交叉熵作为损失函数的时候 if loss == None or loss == 'CrossEntropy': # 最后的delta计算其实是基于交叉熵成本函数公式求导的梯度 # ce = -sum(y*log(p) + (1 - y)*log(1 - p)) # self.layers[-1].activator.backward(self.layers[-1].output)得到的是激活函数的梯度 # 对于sigmoid其实就是output*(1 - output) delta = self.layers[-1].activator.backward(self.layers[-1].output) * \ (self.layers[-1].output - y) / (self.layers[-1].output * (1 - self.layers[-1].output)) # 计算其他层的delta和梯度 for layer in self.layers[:: -1]: layer.backward(delta) delta = layer.delta pass # 梯度更新,每一条数据都要产生梯度影响 for layer in self.layers: # print(layer.W) layer.update(learningRate) pass pass def trainOneSample(self, x, y, loss=None, learningRate=0.005): ''' 注意本例子使用的每次计算一条样本 :param x: 一条样本数据 :param y: 样本的实际结果 :return: ''' # 正向计算得到结果 output = self.predict(x) self.optimizor(y, loss, learningRate) pass def fit(self, X, Y, loss=None, epochs=1000, learningRate=0.1): ''' 训练模型 :param X: 样本 :param Y: 标签 :param loss: 成本函数 :return: ''' for i in range(epochs): # epochs print("epochs:", i) for x, y in zip(X, Y): self.trainOneSample(x, y, learningRate=0.01) pass pass pass pass if __name__ == "__main__": # 定义模型 model = Model() # 一个三层的网络input->hidden->output,只需要两个计算层 model.add(Dense(inputSize=784, outputSize=64, activator=Sigmoid())) model.add(Dense(inputSize=64, outputSize=10, activator=Sigmoid())) # 加载数据 # 5.1 读取训练数据,训练的是处理好的mnist数据集,就是手写数字图片 trainData = np.loadtxt('digits_training.csv', delimiter=',', skiprows=1) # 将样本数据X和y xTrain = trainData[:, 1:] yTrain = trainData[:, 0] # 正则化 # xTrain = (xTrain - np.mean(xTrain, axis=0))/np.std(xTrain, axis=0) xTrain = xTrain / np.max(xTrain) # 优化出10组w,第一组w识别是0,还是不是0,第二组w识别是1还是不是1 # 处理yTrain,变为二维数组oneHot编码 y = np.zeros(shape=(len(yTrain), 10)) # print(y) for m, row in zip(yTrain, y): row[int(m)] = 1 pass # print(xTrain) # print(y) model.fit(xTrain, y, epochs=100) print("预测和原始值:") print("原始结果:", np.argmax(y[0]), '预测结果:', np.argmax(model.predict(xTrain[0])), '预测值:',model.predict(xTrain[0])) print("原始结果:", np.argmax(y[1]), '预测结果:', np.argmax(model.predict(xTrain[1])), '预测值:',model.predict(xTrain[1])) print("原始结果:", np.argmax(y[2]), '预测结果:', np.argmax(model.predict(xTrain[2])), '预测值:',model.predict(xTrain[2])) pass

    5.训练100轮的预测结果

    人工智能神经网络:200行代码手写了一个全连接神经网络(NN),基于单条数据计算更新梯度,速度比较慢,计划改为批量计算,代码详细注释

    Processed: 0.014, SQL: 9