很久之前就看到了一片国外的博文,大神用了11行python1代码就实现了一个最简单的神经网络。他的最精简的代码如下:
X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ]) y = np.array([[0,1,1,0]]).T syn0 = 2*np.random.random((3,4)) - 1 syn1 = 2*np.random.random((4,1)) - 1 for j in xrange(60000): l1 = 1/(1+np.exp(-(np.dot(X,syn0)))) l2 = 1/(1+np.exp(-(np.dot(l1,syn1)))) l2_delta = (y - l2)*(l2*(1-l2)) l1_delta = l2_delta.dot(syn1.T) * (l1 * (1-l1)) syn1 += l1.T.dot(l2_delta) syn0 += X.T.dot(l1_delta)最近复习了一下有关于BP的理论知识,决定仿照大神的思路自己写一个简单的神经网络。其实神经网络的代码实现并不是特别困难,难就难在原理的推导上,实际原理到代码落地,这一步并不是很大的障碍。因此,也就会有11行搞定神经网络的代码了。
不过这个11行的代码确实有些精练了,因此我有扩充了一下,可以满足不同维度数据的需要,而且加入了阈值更新和多层网络,使之成为原汁原味的BP神经网络,看起来更加完善。
代码如下:
#encoding=utf-8 import numpy as np import sklearn # 初始化测试数据集 def loadTrainData(): x = np.array([[0,0,1,1,0],[0,1,1,1,0],[0,0,0,1,0],[0,0,0,0,0],[1,0,0,0,0],[1,0,1,1,1],[1,1,1,1,0],[1,1,1,0,1],[0,1,0,1,1]]) y = np.array([[0,1,0,0,0,1,1,1,1]]).T return x,y def loadTestData(): x = np.array([[1,0,0,0,1],[0,0,0,0,1],[0,0,1,1,1],[1,0,1,1,1]]) y = np.array([[0,0,1,1]]) return x,y # sigmoid函数,并同时求出其导数形式 def nonLin(x,deriv = False): if deriv == True: return x*(1-x) return 1/(1+np.exp(-x)) # 随机初始化第一层参数 def initParameter(featureNum): np.random.seed(1) syn0 = 2*np.random.random((featureNum,1))-1 #生成(特征数*1)的随机浮点数向量,均值为0,列值为1的原因为只有一个输出单元 theta = 2*np.random.random()-1 # 在单层网络中,最后只有一个输出单元,因此也就只有一个1*1的阈值 return syn0,theta # 一层神经网络训练 def train1(iterNum,alpha): X,Y = loadTrainData() featureNum = X.shape[1] syn0,theta = initParameter(featureNum) for iter in xrange(iterNum): l0 = X l1 = nonLin(np.dot(l0,syn0)-theta) # 向前传播 l1Error = Y - l1 l1Delta = l1Error*nonLin(l1,True) syn0 += np.dot(l0.T,l1Delta)*alpha # 更新权重 theta -= sum(alpha*l1Delta) # 由于之前均为批量操作,而在这里阈值是一个1*1的数值,因此用加和的形式做批量处理 print l1 return syn0,theta #初始化两层神经网络参数 def initParameter2(featureNum,hiddenNum): syn0 = 2*np.random.random((featureNum,hiddenNum))-1 # 维度为(特征数*隐藏单元数) syn1 = 2*np.random.random((hiddenNum,1))-1 # 维度为(隐藏单元数*1) theta1 = 2*np.random.random((1,hiddenNum))-1 # 维度为(1*隐藏单元数) theta2 = 2*np.random.random()-1 # 这里只有一个输出单元,因此这里仍是一个1*1的数值 return syn0,syn1,theta1,theta2 #两层神经网络训练 def train2(iterNum,alpha,hiddenNum): X,Y = loadTrainData() featurNum = X.shape[1] sampleNum = X.shape[0] syn0,syn1,theta0,theta1 = initParameter2(featurNum,hiddenNum) for iter in xrange(iterNum): l0 = X l1 = nonLin(np.dot(l0,syn0)-np.tile(theta0,[sampleNum,1]))# 向前传播,注意这里的的复制矩阵的操作类似于广播的操作 l2 = nonLin(np.dot(l1,syn1)-theta1) l2Error = Y - l2 l2Delta = l2Error*nonLin(l2,True) syn1 += l1.T.dot(l2Delta)*alpha # 更新权重 theta1 -= sum(alpha*l2Delta) # 更新阈值 l1Error = l2Delta.dot(syn1.T) # 注意这里前一层误差是由后一层计算得出的 l1Delta = l1Error*nonLin(l1,True) syn0 += l0.T.dot(l1Delta)*alpha theta0 -= np.sum(l1Delta,0)*alpha # 按列求和,theta0为隐藏层的权值,维度为(1*隐藏层) print l2 return syn0,syn1,theta0,theta1 def test1(syn0,theta): X,Y = loadTestData() l0 = X print l0.shape l1 = nonLin(np.dot(l0,syn0)-theta) print l1 def test(syn0,syn1,theta0,theta1): X,Y = loadTestData() l0 = X print l0.shape l1 = nonLin(np.dot(l0,syn0)-theta0) l2 = nonLin(np.dot(l1,syn1)-theta1) print l2 syn0,syn1,theta0,theta1 = train2(60000,0.1,4) test(syn0,syn1,theta0,theta1) syn0,theta = train1(60000,0.1) test1(syn0,theta)具体推导原理很多博文上都有,在此就不细说了,值得注意的是,以上代码实现的都是批量操作,也就是每次都是等待所有的训练数据跑完才做出参数更新,因此我们大量使用了numpy中的矩阵操作和广播机制,这一点在实际调试的过程中很容易造成困惑。因为批量操作涉及到的矩阵计算,维度是一个很让人烧脑的问题,不过我已经在注释中做了详细的描述。
美中不足的是,数据在训练集上测试效果很好,但是在测试集上并不太好,具体原因还不明确,不明白是我实现的还有问题或者是数据集取的有问题造成了过拟合,如果哪位大神能看出的话,还请不吝指教,在此感激不尽!