本文已参与「新人创作礼」活动,一起开启掘金创作之路。
机器学习练习 4 - 神经网路
Introduction
在本练习中,将实现神经网络的反向传播算法,并将其应用于手写数字识别任务。我们将通过反向传播算法实现神经网络成本函数和梯度计算的非正则化和正则化版本,还将实现随机权重初始化和使用网络进行预测的方法。
1 Neural Networks
在前面的练习中,实现了神经网络的前馈传播,并使用它以及我们提供的权重来预测手写数字。在本练习中,您将实现反向传播算法来学习神经网络的参数。
1.1 Visualizing the data
在ex4.m的第一部分,代码将加载数据,并通过调用函数显示数据,将其显示在一个二维图上。
编写代码加载数据,并通过调用函数显示数据。
这与上次练习中使用的数据集相同。在ex3data1.mat中有5000个训练样例,每个训练样例是一个20像素 20像素的数字灰度图像。每个像素用一个浮点数表示,表示该位置的灰度强度。 的像素网格被"展开"成一个 维的向量。每个训练样例X数据矩阵中都变成了一行。给定一个 的矩阵X,其中每一行都是一个手写数字图像的训练样例。
在ex4.m的第二部分是一个 维的向量y(包含了训练集的标签)。将数字 映射到值 ,而数字 到 按其自然顺序被标记为 到 。
先导入相关的函数库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
编写加载数据的代码,并且传入参数:数据文件的路径
def load_data(path):
data=loadmat(path)
X=data['X']
y=data['y'].flatten() #将data['y']展开成一维
return X,y
随机绘制100个数字
def plot_images_100(X):
ind=np.random.choice(range(5000),100)#从[0,5000)中任选100个
images=X[ind]
fix,ax_array=plt.subplots(nrows=10,ncols=10,sharex=True,sharey=True,figsize=(8,8)) #绘制的图形为:10*10,并且公用x和y
for r in range(10):
for c in range(10):
ax_array[r,c].matshow(images[r*10+c].reshape(20,20),cmap='gray_r')#每一个图形都是400个数字构成,所以要reshape为 20*20
plt.xticks([])
plt.yticks([])
plt.show()
X,y=load_data('ex4data1.mat')
查看X和y的数据规模以及具体数据
X,y
(array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]),
array([10, 10, 10, ..., 9, 9, 9], dtype=uint8))
调用plot_images_100函数,查看绘制的图形
plot_images_100(X)
1.2 Model representation
神经网络如图所示:它有个层,一个输入层,一个隐藏层和一个输出层。
输入是数字图像的像素值。由于图像的大小是 ,这给了 个输入层单位(不包括总是输出 的额外偏差单位)。训练数据将通过ex4.m加载到变量X和y中。ex4weights.mat中存储了已经训练过的神经网络参数 ,将它们加载到变量Theta1和Theta2中。这些参数是针对一个在第二层有个单元和个输出单元的神经网络(对应于10个数字类)。
1.2.1 读取数据
获取训练数据集,并且进行相应的处理
raw_X,raw_y=load_data('ex4data1.mat')
X=np.insert(raw_X,0,np.ones(raw_X.shape[0]),axis=1)#在第0列插入全1
X.shape
(5000, 401)
将标签值转化成非线性相关的向量,即向量对应位置为,比如那么。数据中的,就是表示原本的,只是数据提前把转换成了。
# from sklearn.preprocessing import OneHotEncoder #用sklearn中OneHotEncoder函数
# y = np.mat(raw_y).T 转换成[5000,1]的矩阵
# encoder = OneHotEncoder(sparse=False) #sparse:若为True时,返回稀疏矩阵;否则返回数组,默认为True。
# y_onehot = encoder.fit_transform(y)
# y_onehot.shape,y_onehot[0]
def expand_y(y):
result=[]
#将整数y[i]修改为向量,对应下标位置置为1,其余为0
for it in y:
y_array=np.zeros(10)
y_array[it-1]=1
result.append(y_array)
return np.array(result)
y = expand_y(raw_y)
y.shape,y[0]
((5000, 10), array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]))
1.2.2 读取权重
ex4weights.mat中存储了已经训练过的神经网络参数 ,将它们加载到变量Theta1和Theta2中。这些参数是针对一个在第二层有个单元和个输出单元的神经网络(对应于10个数字类)。的规模为,的规模为。
def load_weight(path):
data=loadmat(path)
return data['Theta1'],data['Theta2']
Theta1,Theta2=load_weight('ex4weights.mat')
Theta1.shape,Theta2.shape
((25, 401), (10, 26))
1.2.3 参数展开
当使用高级优化方法来优化神经网络时,需要将多个参数矩阵展开,才能传入优化函数,然后再恢复形状。
def serialize(t1,t2):
#参数展开
return np.concatenate((t1.flatten(),t2.flatten()))#等同于 np.r_[t1.flatten(),t2.flatten()]
theta=serialize(Theta1, Theta2) #扁平化参数
theta.shape
(10285,)
1.2.4 提取参数
def deserialize(seq):
#提取参数
return seq[:25*401].reshape(25,401),seq[25*401:].reshape(10,26)
1.3 Feedforward and cost function
def sigmoid(z):
return 1/(1+np.exp(-z))
结合下图实现feed_forward函数
的规模为,的规模为,的规模为。
def feed_forward(theta,X):
Theta1,Theta2=deserialize(theta)
a1=X
z2=a1@Theta1.T
a2=sigmoid(z2)
a2=np.insert(a2,0,1,axis=1)
z3=a2@Theta2.T
a3=sigmoid(z3)
return a1,z2,a2,z3,a3
现在将实现神经网络的成本函数和梯度。神经网络的代价函数(没有正则化)是:
是可能的标签的总数。原始的标签(在变量y中)是,为了训练一个神经网络,需要将标签重新编码为只包含值或的向量。
举个例子:如果是的图像,那么y则是一个的维向量,其他元素等于。
要求实现前馈计算,即为每个训练数据计算并计算所有训练数据的成本。正确的最终成本约为。
输出层输出的是对训练样本的预测,包含个数据,每个数据对应了一个包含个元素的向量,代表了结果有类。在公式中,每个元素与log项对应相乘。
构造Cost函数:使用提供训练好的参数θ,算出的cost应该为。
def Cost(theta,X,y):
a1,z2,a2,z3,h=feed_forward(theta,X)
J=0
for i in range(X.shape[0]):
ans=np.multiply(-y[i],np.log(h[i]))-np.multiply((1-y[i]),np.log(1-h[i]))
sum=np.sum(ans)
J+=sum
J=J/(X.shape[0])
return J
Cost(theta, X, y)
0.2876291651613188
1.4 Regularized cost function(正则化代价函数)
正则化神经网络的代价函数:
假设神经网络将只有个层:一个输入层,一个隐藏层和一个输出层。但是,设计的代码应该适用于任意数量的输入单元、隐藏单元和输出单元。不应该规范与偏差相对应的项。
使用之前的Theta1和Theta2以及参数来调用Regularized_cost函数,正确的最终答案大约为。(注意不要将每层的偏置项正则化)
def Regularized_cost(theta,X,y,l=1):
J=Cost(theta, X, y)+l/(2*X.shape[0])*(np.sum(np.power(Theta1[:,1:],2))+np.sum(np.power(Theta2[:,1:],2)))
return J
Regularized_cost(theta, X, y,1)
0.38376985909092354
2 Backpropagation(反向传播)
在本部分的练习中,将实现反向传播算法来计算神经网络代价函数的梯度。一旦计算了梯度,将通过使用高级优化器最小化代价函数来训练神经网络。
首先实现反向传播算法来计算(非正则化)神经网络的参数的梯度。验证了在非正则化情况下的梯度计算是正确的之后,再实现正则化神经网络的梯度。
2.1 Sigmoid gradient(S函数导数)
型函数的梯度:
def sigmoid_gradient(z):
return sigmoid(z)*(1-sigmoid(z))
2.2 Random initialization(随机初始化)
在训练神经网络时,随机初始化参数是很重要的,可以打破数据的对称性。一个有效的策略是在均匀分布中随机选择值,选择这个范围的值来确保参数足够小,使得训练更有效率。
因此接下来需要完成随机初始化权重,初始化的权重
def random_init(size):
#np.random.uniform():随机生成指定范围的浮点数,从一个均匀分布[low,high)中随机采样,定义域是左闭右开,包含low,不包含high,ndarray类型,其形状与size中描述一致.
return np.random.uniform(-0.12,0.12,size)
2.3 Backpropagation(反向传播)
给定一个训练数据,将先运行一个向前传递来计算整个网络中的所有激活值,包括假设的输出值,然后,对于第层中的每个节点,需要计算误差项。
对于一个输出节点,可以直接计算神经网络激活值和真实目标值之间的差异,并使用它来定义(本题中第层是输出层)。对于隐藏单元,根据第层中节点的误差项的加权平均值来计算。
实现一个for循环,并将步骤放在for循环中,第次循环计算第个训练数据的相关数据。步骤将累积的梯度除以,得到神经网络代价函数的梯度。
1.将第个训练数据设置为输入层的值。执行前馈传递(feedforward),计算第层和第层的激活量。注意,需要添加一个项,以确保第层和第层的激活向量也包括偏置单元。
2.对于第层(输出层)中的每个输出单元,设定,表示当前的训练数据是否属于第类,如果属于第类则,否则。
3.对于隐藏层,设置。
4.使用以下公式累积此训练数据中的梯度。注意,应该跳过或删除。那么。
5.通过将累积的梯度除以,得到神经网络代价函数的(非正则化的)梯度。
def Gradient(theta,X,y):
Theta1,Theta2=deserialize(theta)
#step 1:
a1,z2,a2,z3,h=feed_forward(theta, X) #a2.shape:(5000, 26)
#step 2:
d3=h-y #d3.shape:(5000,10)
#step 3:
d2=np.multiply(d3@Theta2[:,1:],sigmoid_gradient(z2)) #d2.shape:(5000, 25)
#step 4:
D2=d3.T@a2 #D2.shape:(10, 26)
D1=d2.T@a1 #D1.shape:(25, 401)
#step 5:
D=np.multiply(1/(X.shape[0]),serialize(D1, D2)) #D.shape:(10285,)
return D
2.4 Gradient checking(梯度检测)
在神经网络中,需要最小化成本函数。为了对参数执行梯度检查,考虑将参数"展开"到一个向量中。通过这样做,可以认为成本函数是,并使用以下梯度检查过程:
假设有一个函数,可以计算;检查一下是否输出了正确的导数值:
因此,与基本相同,除了它的第个元素被增加了;与基本相同,除了它的第个元素被减少了。现在通过检查每个来数值验证的正确性:
这两个值相互近似的程度将取决于。但是假设 ,通常会发现上面式子的左极限和右极限至少会存在4位有效的数字(通常是更多的)。如果反向传播实现是正确的,相对误差应该低于
def Gradient_checking(theta,X,y,e):
def A_numeric_grad(right,left):
return (Regularized_cost(right, X, y)-Regularized_cost(left, X, y))/(2*e)
pass
numeric_grad=[]
right=theta.copy()
left=theta.copy()
for i in range(len(theta)):
if(i!=0):
right[i-1]=right[i-1]-e
left[i-1]=left[i-1]+e
right[i]=right[i]+e
left[i]=left[i]-e
numeric_grad.append(A_numeric_grad(right, left))
numeric_grad=np.array(numeric_grad)
analytic_grad=Regularized_cost(theta, X, y)
diff=np.linalg.norm(numeric_grad-analytic_grad)/np.linalg.norm(numeric_grad+analytic_grad)
print('如果反向传播实现是正确的,相对误差应该低于1e-9.\n相对误差: {}\n'.format(diff))
Gradient_checking(theta, X, y, 0.0001)#这个运行真的慢!!! 运行似乎有问题,之后改
2.5 Regularized Neural Networks (正则化神经网络)
成功实现反向传播算法后,向梯度添加正则化。为了考虑正则化,可以在使用反向传播计算梯度后将其作为附加项添加。
具体来说,在使用反向传播计算了之后,可以使用来添加正则化:
注意,不要正则化偏差项的第一列。注意,在参数中,下标,下标,如图:
def Regularized_gradient(theta,X,y,l=1):
a1,z2,a2,z3,h=feed_forward(theta, X) #a2.shape:(5000, 26)
D1,D2=deserialize(Gradient(theta, X, y))
Theta1,Theta2=deserialize(theta)
Theta1[:,0]=0
Theta2[:,0]=0
reg_D1=D1+(l/X.shape[0])*Theta1
reg_D2=D2+(l/X.shape[0])*Theta2
return serialize(reg_D1, reg_D2)
2.6 Learning parameters using fmincg (优化参数)
在成功实现了神经网络代价函数和梯度计算之后,使用fmincg来学习得到更优秀的集合参数。如果实验过程正确,那么训练准确率约为95.3%(由于随机初始化,误差约1%)。通过训练神经网络进行迭代,可以获得更高的训练精度。也可以考虑改变正则化参数来得到更高的训练精度。更加正确的学习设置,就有可能让神经网络更完美地匹配训练集。
import scipy.optimize as opt
def training(X,y):
theta=random_init(10285)
res=opt.minimize(fun=Regularized_cost,x0=theta,args=(X,y,1),method='TNC',jac=Regularized_gradient,options={'maxiter':400})
return res
res=training(X, y)
res
fun: 0.2289451433747141
jac: array([ 1.92570206e-04, 5.03964124e-07, -1.40295013e-06, ...,
-5.59838186e-05, -7.13012121e-06, -1.57255878e-04])
message: 'Converged (|f_n-f_(n-1)| ~= 0)'
nfev: 397
nit: 23
status: 1
success: True
x: array([-0.43822395, 0.00251982, -0.00701475, ..., -0.52977546,
-0.5889591 , 1.83802919])
from sklearn.metrics import classification_report
def accuracy(theta,X,y):
a1,z2,a2,z3,h=feed_forward(theta, X) #a2.shape:(5000, 26)
y_pred=np.argmax(h,axis=1)+1
y.shape,y_pred
print(classification_report(y, y_pred))
accuracy(res.x, X, raw_y)
precision recall f1-score support
1 0.99 1.00 0.99 500
2 0.99 0.99 0.99 500
3 0.99 0.99 0.99 500
4 1.00 0.99 0.99 500
5 1.00 1.00 1.00 500
6 1.00 1.00 1.00 500
7 0.99 0.99 0.99 500
8 1.00 1.00 1.00 500
9 0.99 0.99 0.99 500
10 0.99 1.00 1.00 500
accuracy 0.99 5000
macro avg 0.99 0.99 0.99 5000
weighted avg 0.99 0.99 0.99 5000
3 Visualizing the hidden layer(可视化隐藏层)
理解神经网络正在学习什么可以考虑将隐藏单元所捕获的表征可视化。即给定一个特定的隐藏单元,找到一个输入X,并且将它激活(即有一个激活值接近)。对于训练过的神经网络,中每一行都是一个维的向量,代表每个隐藏层单元的参数。如果忽略偏置项,就能得到维的向量,这个向量代表每个样本输入到每个隐层单元的像素的权重。因此可视化的其中一种方法是,reshape这个维的向量为的图像然后显示出来。
接下来通过使用显示数据功能,它将显示一个包含单元的图像(如下图),每个单元对应于网络中的一个隐藏单元:
在经过训练的神经网络中,会发现隐藏的单元大致对应于在输入中寻找笔画和其他模式的检测器。
def plot_hidden(theta):
Theta1,Theta2=deserialize(theta)
Theta1=Theta1[:,1:]
fix,ax_array=plt.subplots(nrows=5,ncols=5,sharex=True,sharey=True,figsize=(8,8))
for r in range(5):
for c in range(5):
ax_array[r,c].matshow(Theta1[r*5+c].reshape(20,20),cmap='gray_r')
plt.xticks([])
plt.yticks([])
plt.show()
plot_hidden(theta)