神经网络篇——从代码出发理解BP神经网络

       一提到反向传播算法,我们就不自觉的想到随机梯度下降、sigmoid激活函数和最让人头大的反向传播法则的推导,即便是把反向传播神经网络的原理学了一遍,也还是一头雾水,在这里推荐一本小编认为把BP神经网络讲的最通透的教材《Python神经网络编程》。下面小编将以代码的方式带着大家一步一步实现BP神经网络,并且还会再用框架的方式实现BP网络,用来和源码作比较,认识一下TensorFlow2.0的强大。

        我们首先要理清建立BP神经网络的目的,其次,确定BP神经网络的结构,简单地以一个输入层,一个隐藏层,一个输出层为例,我们来思考一下写代码的思路,然后一步一步实现。

神经网络篇——从代码出发理解BP神经网络_第1张图片

在这里,我们建立BP神经网络的目的是为了做预测,我们这里用700条MG时间序列数据作为训练数据,输入维度是10,输出为1,为了方便,我们设置一个隐藏层,这个隐藏层包含10个节点,所以我们的BP神经网络的结构就是[10,10,1]。接下来,开始思考一下流程:

1、读取数据,将数据分为训练集和测试集,注意这里的数据默认都是np.array()的格式。

2、初始化BP网络,主要是确定网络结构,输入层、隐藏层、输出层个数,以及随机初始化相互连接的权值

3、调用训练方法,传入数据进行训练

    1. 前向传播    

    2. 反向传播

    3. 保存模型进行测试

      里面还有很多细节,我们边写边完善,整个程序的框架已经很明确了。从第一步开始,读取数据,第一步中需要注意的是,读取的数据最好进行归一化处理,一方面,这样做有助于提高预测精度,另一方面,如果使用sigmoid作为激活函数的话,它的输出是在[0,1]之间的,如果数据不进行归一化,不在[0,1]区间的数是没有办法预测的。这里我们直接调用sklearn中的最大最小归一化的方法就可以,为了方便读者理解代码,我们将import紧挨着代码:

  #防止数据用科学计数法输出  np.set_printoptions(suppress=True)  #读取数据  way1 = 'Traindata11.csv'  data = np.array(pd.DataFrame(pd.read_csv(way1)))  # 归一化所有数据  from sklearn.preprocessing import MinMaxScaler  format_minmax = MinMaxScaler()  data = format_minmax.fit_transform(data)  x = data[:,:-1]     #需要训练的数据  y = data[:,-1]      #label,标签  #初始化BP网络  #训练网络

第二步,我们要初始化网络,确定网络的结构:

    def __init__(self, ni, nh, no):        """        构造神经网络        :param ni:输入单元数量        :param nh:隐藏单元数量        :param no:输出单元数量        """        self.ni = ni + 1  # +1 是为了偏置节点        self.nh = nh        self.no = no        # 激活值(输出值)        self.ai = [1.0] * self.ni   #这里会生成一个1*ni维,数值全为1的列表        self.ah = [1.0] * self.nh        self.ao = [1.0] * self.no        # 权重矩阵        self.wi = np.random.randn(self.ni, self.nh)     #输入层到隐藏层        self.wo = np.random.randn(self.nh, self.no)  # 隐藏层到输出层        # 记录权重矩阵的上次梯度        self.ci = np.zeros([self.ni, self.nh])        self.co = np.zeros([self.nh, self.no])

第三步开始训练并保存模型:

    def train(self,train,lable,max_iterations=1000, N=0.5, M=0.1):        """        训练        :param train:训练集        :param lable:标签        :param max_iterations:最大迭代次数        :param N:本次学习率        :param M:上次学习率        """        for i in range(max_iterations): #迭代最大训练次数(epoch)            for j in range(len(train)):  #训练集                inputs = train[j]   #输入向量                targets = lable[j]  #目标值                self.forword_propagation(inputs)    #前向传播训练                error = self.back_propagation(targets, N, M)    #反向传播训练,传入lable            if i % 50 == 0:     #每50次输出一次误差(loss),loss函数为mse                print('Combined error', error)        winame = 'wi.npy'        woname = 'wo.npy'        np.save(winame, self.wi)        np.save(woname, self.wo)

接下来我们就要实现前向传播和反向传播的方法了,我们按照顺序从前向传播开始,前向传播用到了sigmoid作为激活函数,所以我们把sigmoid函数也一并列出:

    def sigmoid(self,x):        """        sigmoid 函数,1/(1+e^-x)        :param x:        :return:        """        return 1.0 / (1.0 + math.exp(-x))    def forword_propagation(self, inputs):        """        前向传播进行分类        :param inputs:输入        :return:类别        """        if len(inputs) != self.ni - 1:  #检查输入数据的维度是否和声明的一致            print('输入维度有误,请重新检查输入')        for i in range(self.ni - 1):    #按顺序将n维的input映射到n个输入节点上            self.ai[i] = inputs[i]        for j in range(self.nh):        #遍历隐藏层节点,每个隐藏层节点的值为n个输入节点*对应权值(输入层和隐藏层)的和            sum = 0.0            for i in range(self.ni):                sum += (self.ai[i] * self.wi[i][j])            self.ah[j] = self.sigmoid(sum)  #将节点值经过sigmoid函数激活后,变为[0,1]之间的值        for k in range(self.no):    #遍历输出层节点,每个输出层节点的值为n个隐藏层节点*对应权值(隐藏层和输出层)的和            sum = 0.0            for j in range(self.nh):                sum += (self.ah[j] * self.wo[j][k])            self.ao[k] = self.sigmoid(sum)  ##将节点值经过sigmoid函数激活后,变为[0,1]之间的值        return self.ao  #返回输出层的值

接下来是反向传播,反向传播中需要对sigmoid进行求导,为了方便我们直接写出sigmoid的求导结果,另外这里的误差函数(loss)选择mse:

     def dsigmoid(self,y):        """        sigmoid 函数的导数        :param y:        :return:        """        return y * (1 - y)     def back_propagation(self, targets, N, M):        """        反向传播算法        :param targets: 实例的类别        :param N: 本次学习率        :param M: 上次学习率        :return: 最终的误差平方和的一半        """        # 计算输出层 deltas,求导数        # dE/dw[j][k] = (t[k] - ao[k]) * s'( SUM( w[j][k]*ah[j] ) ) * ah[j]        output_deltas = [0.0] * self.no     #初始化输出层列表为0        for k in range(self.no):            error = targets[k] - self.ao[k]     #计算输出层误差            output_deltas[k] = error * self.dsigmoid(self.ao[k])    #计算每一个节点对误差的影响大小,占比越大,影响越大        # 根据delta更新隐藏层和输出层之间的权值        for j in range(self.nh):            for k in range(self.no):                # output_deltas[k] * self.ah[j] 才是 dError/dweight[j][k]                change = output_deltas[k] * self.ah[j]  #根据比例计算需要调整的值                self.wo[j][k] += N * change + M * self.co[j][k]     #调整权值,调整方法有很多,例如随机梯度下降,这里只是简单写了一下                self.co[j][k] = change  #保存调整的梯度        # 计算隐藏层 deltas        hidden_deltas = [0.0] * self.nh        for j in range(self.nh):            error = 0.0            for k in range(self.no):                error += output_deltas[k] * self.wo[j][k]            hidden_deltas[j] = error * self.dsigmoid(self.ah[j])        # 更新输入层权值        for i in range(self.ni):            for j in range(self.nh):                change = hidden_deltas[j] * self.ai[i]                # print 'activation',self.ai[i],'synapse',i,j,'change',change                self.wi[i][j] += N * change + M * self.ci[i][j]                self.ci[i][j] = change        # 计算MSE        error = 0.0        for k in range(len(targets)):            error += (targets[k] - self.ao[k]) ** 2        error = error/len(y)        return error       

到这里就差不多实现了一个最简单的BP神经网络,不过小编强烈反对用这个代码来进行实战,这个代码只适用于理解BP神经网络。在下一篇中,我将用Tensorflow框架和Pytorch框架来实现一个通用的BP神经网络,并且解答一下为什么要使用激活函数。

不知不觉,2021年到来了,马上天就要亮了,这一篇是小编的第一篇公众号,仅以此铭记2021.1.1日凌晨。祝各位读者,元旦快乐!!!

欢迎关注公众号“NNResearch”

 

公众号发送“BP神经网络图书”,即可获取电子版图书。

公众号发送“BP源码”,即可获取完整版源码。

你可能感兴趣的:(神经网络,机器学习,python,人工智能)