山东大学《机器学习》实验报告
----BP神经网络处理MNIST数据集
软件16-6 李昊 201600301309
问题背景:
实验要求是运用BP神经网络处理MNIST数据集,并且比较单层与多层的神经网络的准确率的差别。MNIST数据集是由60000个训练样本与10000个测试样本组成的,从官网获得的数据集包含有四个文件:
我们应该处理好这些数据的格式,然后构建网络进行训练与测试。
问题分析:
首先应该处理好数据,将训练用例与测试用例处理为易于被操作的格式。
然后首先从一层的BP神经网络入手,完成这个过程。
然后完善反向传播的过程,实现两层的BP神经网络。
最后测试比较它们的成功率。
问题解决:
处理MNIST数据集
首先是处理MNIST数据集,通过官网的信息的了解,其图片是以字节形式进行存储,我们需要把它转化为我们易于处理的矩阵的形式。其每张图片由28×28个像素点构成,每个像素点用一个灰度值表示,我们需要把这些像素点的灰度值读取到一个784维的向量中去。
通过网站上对其数据集内容的解释,在其文件头有描述文件协议的信息,这是我们不需要的,所以在读取时应忽略。以下是代码:
def loadmnist(kind):
labels_path=('%s-labels.idx1-ubyte'%kind)
images_path=('%s-images.idx3-ubyte'%kind)
with open(labels_path, 'rb') as lbpath:
magic,n = struct.unpack('>II',lbpath.read(8))
labels = np.fromfile(lbpath,dtype=np.uint8)
with open(images_path, 'rb') as imgpath:
magic,num,rows,cols = struct.unpack('>IIII',imgpath.read(16))
images = np.fromfile(imgpath,dtype=np.uint8).reshape(len(labels), 784)
return images, labels
通过运行,得出的images与labels分别是这样的格式:
labels是每个图片代表的数字,images每一行是一个784列的向量,代表一个图片。
ps:但是在后来构建好之后,发现成功率并不理想,所以实际中采用了另一种方式,然而到最后发现是因为一个拼写错误,可是已经完成了,就沿用了后来那种读取方式。
构建一层BP神经网络
首先我们初始化数据,模拟前向传播的过程。
weight=np.random.randn(785,10)
v=np.dot(np.insert(images[j],0,[0]),weight)
激活函数及其求导写在函数里:
def sigmoid(z):
return 1.0/(1.0+np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z)*(1-sigmoid(z))
之后便是反向传播的过程,我们知道,反向传播需要求出
δ
j
(
n
)
\delta_j(n)
δj(n)
进一步我们知道:
δ
j
(
n
)
=
(
d
j
(
n
)
−
y
j
(
n
)
φ
j
′
(
v
j
(
n
)
)
)
\delta_j(n)=(d_j(n)-y_j(n)\varphi'_j(v_j(n)))
δj(n)=(dj(n)−yj(n)φj′(vj(n)))
用代码表示出来就是:
delta=(labels[j].T-sigmoid(v))*sigmoid_prime(v)
求得了delta_j(n),我们便可以通过梯度下降求得:
Δ
w
=
η
δ
j
(
n
)
y
i
(
n
)
\Delta w=\eta\delta_j(n)y_i(n)
Δw=ηδj(n)yi(n)
整体过程用代码表述如下:
def network(images,labels):
eta=1
global weight
for i in range(10):
for j in range(len(images)):
v=np.dot(np.insert(images[j],0,[0]),weight)
delta=(labels[j].T-sigmoid(v))*sigmoid_prime(v)
#print(sigmoid(v))
dw=eta*np.dot(np.insert(images[j],0,[0]).reshape(785,1),delta.reshape(1,10))
weight+=dw
经过如此处理,我们得到了训练过的weight,现在我们用这个weight来测试我们的准确度。在测试中,只需进行一次前向传播,在输出结果中取最大的元素的位置即为预测的数字。在每次预测时计算预测正确的次数,最后获得准确率:
def test(images,labels):
count=0
for i in range(len(images)):
y=np.dot(np.insert(images[i],0,[0]),weight)
pre=np.argmax(y)
lab=np.argmax(labels[i])
count+=int(pre==lab)
print('Predict rate is %s'%(count/len(images)))
观察结果,十轮训练准确率一般在80%左右,好的时候能接近90%
构建两层BP神经网络
与构建一层神经网络类似,所以我选择了在之前一层神经网络上进行增量式开发。
与一层神经网络不同的是,我们现在要考虑更复杂的反向传播的情况。之前是由输出层直接传播到输入层,现在我们多了一层隐藏层,需要从右往左逐步调节weight。
我们知道,在处理隐藏层神经元的权重时,delta的公式如下:
δ
j
(
n
)
=
φ
′
(
v
j
(
n
)
)
δ
k
(
n
)
w
k
(
n
)
\delta_j(n)=\varphi'(v_j(n)) \delta_k(n)w_k(n)
δj(n)=φ′(vj(n))δk(n)wk(n)
这也就是与一层网络的不同之处,我们要通过前一层(或者说右边那层)得到的delta和weight来计算本层的delta。有了delta之后,过程就与一层时类似了。
代码与一层类似,所以这里只给了不同之处:
delta1=np.dot(delta2.reshape(1,10),weight2.reshape(10,31))*sigmoid_prime(v1)
dw1=eta*np.dot(np.insert(images[j],0,[0]).reshape(785,1),delta1.reshape(1,31))
至于前向传播,可以理解为两次单层的前向传播,第一次映射到30维的向量里,第二次映射到10维的输出层向量里。代码如下:
v1=np.dot(np.insert(images[j],0,[0]),weight1)
y1=sigmoid(v1)
v2=np.dot(y1,weight2)
y2=sigmoid(v2)
我们同样可以观察十轮训练之后的结果:
通过与上面一层网络的比较我们可以看出,两层的效果是明显比一层好一个层次的。
感想:
这次实验又刷新了实验难度的新高。。。
不过在做完之后回头看看,也不过是那么一回事。
在构建神经网络的过程中,前向传播比较顺利,主要难题出现在反向传播的实现中,因为理解不够透彻,还是无法通过程序将反向传播的过程表示出来。在再次复习一遍PPT之后,找到了实现反向传播的关键,也就是求出delta,有了delta,一切就变得豁然开朗,有了delta就可以顺势求出weight的变化量,进而训练weight。后来在总结时,感觉是因为对于公式中的每个字母分别表达的含义不够深入,导致进度一度停止。
本以为在完成一层网络之后,实现两层网络顺其自然,可是现实还是狠狠的给我了一巴掌。两层网络的反向传播过程比一层网络复杂的多,还是跟实现一层时遇到的问题一样,因为对于字母表达的含义不清楚,就会遇见本应该使用输出层的数据,却用了隐藏层的,而使用隐藏层的数据,又到了输出层去。好在经过努力理解和多次试错,最后终于调出来了。
这次实验可以让我们管中窥豹的看到,机器学习在实际生活中的巨大作用,比如这次的程序稍加修改,就可以用于验证码的识别,同时实际上手操作这种看上去高难度的实验,让我们在成功后有巨大的成就感,受益匪浅。