前言
在上一篇文章中我们介绍了BP神经网络算法的理论知识,这篇文章我们就来用Python实现BP神经网络算法,在看这篇文章之前,建议先看一下前面的《Python机器学习神经网络算法理论(BP)》文章,里面有详细的前馈神经网络算法的理论实现,可以帮助这篇文章的代码理解,因为这篇文章中的代码流程就是根据上篇理论知识来编写的,所使用的公式也是上篇文章中介绍的公式。
这篇文章只是简单的使用Python实现BP神经网络算法,并没有进行代码的优化,代码写的也是一般,但大体的流程还是有些参考价值的。
在写这篇文章的代码的时候参考了很多网上的代码程序,说实话,网上的代码很多都如出一折,很多(简单的)都是同版本copy过来的,能拿来学习的真的寥寥无几。
废话不多说,下面我们就开始看代码吧。
定义Sigmoid函数
上篇文章中说过了激活函数使用的是Sigmoid函数,我们编程是常用的两种是tanh函数和logistic函数,这两个函数的图像都跟上一篇文章中的图像很相似,都处在(0,1)之间,下面分别看一下这两个函数图像:
tanh图像
logistic图像
除了他们本身之外,我们在神经网络往回反馈的时候计算误差率需要用到他们的导函数,其导函数如下
l
在Python的numpy库中已经为我们写好了tanh函数,我们只要根据公式去实现以下它的导函数就可以了,logistic函数比较简单,我们可以直接使用代码实现。
下面是用代码来定义这两个函数和他们的导函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#coding:utf8 import numpy as np def tanh(x): return np.tanh(x) def tanh_deriv(x): return 1.0 - np.tanh(x) * np.tanh(x) def logistic(x): return 1 / (1 + np.exp(-x)) def logistic_derivative(x): return logistic(x) * (1 - logistic(x)) |
BP神经网络算法
上面的激活函数只是我们定义的几个函数,是下面我们在构建算法的时候要用到的。下面直接看代码,重要的解释都在代码注释中了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
''' ClassName:NeuralNetwork Author:kTWO Time:2017-8-10 ''' class NeuralNetwork: #构造函数 def __init__(self, layers, activation='tanh'): ''' :param layers: list类型,比如[2,2.1]代表输入层有两个神经元,隐藏层有两个,输出层有一个 :param activation: 激活函数 ''' self.layers = layers #选择后面用到的激活函数 if activation == 'logistic': self.activation = logistic self.activation_deriv = logistic_derivative elif activation == 'tanh': self.activation = tanh self.activation_deriv = tanh_deriv #定义网络的层数 self.num_layers = len(layers) ''' 生成除输入层外的每层中神经元的biase值,在(-1,-1)之间,每一层都是一行一维数组数据 randn函数执行一次生成x行y列的数据 ''' self.biases = [np.random.randn(x) for x in layers[1:]] print "偏向:",self.biases ''' 随机生成每条连接线的权重,在(-1,1)之间 weights[i-1]代表第i层和第i-1层之间的权重,元素个数等于i层神经元个数 weights[i-1][0]表示第i层中第一个神经单元和第i-1层每个神经元的权重,元素个数等于i-1层神经元个数 ''' self.weights = [np.random.randn(y, x) for x, y in zip(layers[:-1], layers[1:])] print "权重:",self.weights #训练模型,进行建模 def fit(self, X, y, learning_rate=0.2, epochs=1): ''' :param self: 当前对象指针 :param X: 训练集 :param y: 训练标记 :param learning_rate: 学习率 :param epochs: 训练次数 :return: void ''' for k in range(epochs): #每次迭代都循环一次训练集 for i in range(len(X)): #存储本次的输入和后几层的输出 activations = [X[i]] #向前一层一层的走 for b, w in zip(self.biases, self.weights): # print "w:",w # print "activations[-1]:",activations[-1] # print "b:", b #计算激活函数的参数,计算公式:权重.dot(输入)+偏向 z = np.dot(w, activations[-1])+b #计算输出值 output = self.activation(z) #将本次输出放进输入列表,后面更新权重的时候备用 activations.append(output) # print "计算结果",activations #计算误差值 error = y[i]-activations[-1] #print "实际y值:",y[i] #print "预测值:",activations[-1] # print "误差值",error #计算输出层误差率 deltas = [error * self.activation_deriv(activations[-1])] #循环计算隐藏层的误差率,从倒数第2层开始 for l in range(self.num_layers-2, 0, -1): # print "第l层的权重",self.weights[l] # print "l+1层的误差率",deltas[-1] deltas.append(self.activation_deriv(activations[l]) * np.dot( deltas[-1],self.weights[l])) #将各层误差率顺序颠倒,准备逐层更新权重和偏向 deltas.reverse() # print "每层的误差率:",deltas #更新权重和偏向 for j in range(self.num_layers-1): #本层结点的输出值 layers = np.array(activations[j]) # print "本层输出:",layers # print "错误率:",deltas[j] # 权重的增长量,计算公式,增长量 = 学习率 * (错误率.dot(输出值)) delta = learning_rate * ((np.atleast_2d(deltas[j]).T).dot(np.atleast_2d(layers))) #更新权重 self.weights[j] += delta #print "本层偏向:",self.biases[j] #偏向增加量,计算公式:学习率 * 错误率 delta = learning_rate * deltas[j] #print np.atleast_2d(delta).T #更新偏向 self.biases[j] += delta #print self.weights def predict(self, x): ''' :param x: 测试集 :return: 各类型的预测值 ''' for b, w in zip(self.biases, self.weights): # 计算权重相加再加上偏向的结果 z = np.dot(w, x) + b # 计算输出值 x = self.activation(z) return x |
代码各模块的解释都在注释中了,就不多说了,下面说一下算法的实现流程。
1、首先的我们是定义了一个NeuralNetwork类,并且在其构造函数中传入了几个构建模型需要用到的参数。分别定义了权重(weights)和偏向(biases),在定义的时候我们使用了正太分布的随机函数生成的随机数在(-1,1)之间,到此为止我们就定义好了网络的结构,具体结构可以在测试中print一下看看。
2、下一步便是重要的一般,我们要在fit中传入训练集进行训练,构建数学模型,所使用的算法便是BackPropagation(BP)算法。该算法分为两个过程,一个是正向的前进过程,一个是反向的反馈更新过程,每一次的迭代过程都使用训练集中的一个实例。
3、向前传输,计算每层神经单元的输出,并保存。
4、根据最后一层也就是输出层的输出和真实值比较得到误差,并使用计算公式得到输出层的误差率。
5、根据输出层的误差率我们可以依次向前根据公式推导出每一次的误差率。
6、根据每一次层的误差率,我们可以使用权重的更新公式和偏向的更新公式更新权重和偏向。
7、BP算法到此结束,我们要做的就是将大量的训练集进行迭代的训练,每次迭代都要使用全部的训练集,知道满足我们的迭代次数为止。
8、实现预测函数(predict)。
上面的算法流程可以参考开头提到的前一篇理论文章。
简单的预测
算法实现完了,我们下面就进行一次简单的训练和预测,就是用最简单的异或。
1 2 3 4 5 6 7 8 9 10 |
nn = NeuralNetwork([2,4,3,1], 'tanh') #训练集 X = np.array([[0, 0], [0, 1], [1, 0],[1, 1]]) #lanbel标记 y = np.array([0, 1, 1, 0]) #建模 nn.fit(X, y, epochs=1000) #预测 for i in [[0, 0], [0, 1], [1, 0], [1,1]]: print(i, nn.predict(i)) |
输出如下:
本次预测我们训练的次数是1000次,构造的神经网络结构模式一个2、4、3、1的网络结构。从预测的结果中我们可以看出来,其预测结果已经非常的精确了,实践发现,通过调整隐藏层的结构和增加训练的次数都可以提升预测的准确率,但我们我要在准确率和预测时间之间取一个最优解,既要准确率也要训练和预测效率。
结束语
本篇文章算法中有大量的print输出测试注释,可供学习者在研究代码流程时使用,另外提醒一下,Python版本是2.7哟!
PS:本篇文章主要是算法的实现,在下一篇文章中我们将使用该神经网络算法进行手写数字的识别实战。
2017年10月13日 19:24 沙发
我选A
2020年6月11日 17:06 板凳
你好 ,你的初始layers数组输入定义和你最后使用代入并不一致啊
2020年6月11日 17:18 1层
@李明 哪里不一致? Input层2神经元,4×3的Hidden, 1 Output = 2:4:3:1, 训练集是 [[0, 0], [0, 1], [1, 0],[1, 1]] ,4批,每批2输入。
2020年6月11日 17:26 地板
您能具体说明4*3hidden是分别代表隐藏层数还是神经元个数吗 在定义里是这样的:param layers: list类型,比如[2,2.1]代表输入层有两个神经元,隐藏层有两个,输出层有一个
2020年6月11日 17:29 1层
@李明 都是表示神经元个数,第二层4神经元,第三层3神经元,理论文章:https://www.k2zone.cn/?p=992