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

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

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

pnn神经网络代码 神经网络nn算法_数据

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轮的预测结果

pnn神经网络代码 神经网络nn算法_神经网络_02

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