吴恩达机器学习作业4:神经网络(反向传播)(Python实现)

本博客聚焦于机器学习练习,实现神经网络的反向传播算法并应用于手写数字识别。涵盖神经网络的前馈传播、成本函数、正则化代价函数,以及反向传播算法中的S函数导数、随机初始化、梯度检测等内容,还介绍了可视化隐藏层的方法。

机器学习练习 4 - 神经网路

Introduction

在本练习中,将实现神经网络的反向传播算法,并将其应用于手写数字识别任务。我们将通过反向传播算法实现神经网络成本函数和梯度计算的非正则化和正则化版本,还将实现随机权重初始化和使用网络进行预测的方法。

1 Neural Networks

在前面的练习中,实现了神经网络的前馈传播,并使用它以及我们提供的权重来预测手写数字。在本练习中,您将实现反向传播算法来学习神经网络的参数。

1.1 Visualizing the data

ex4.m的第一部分,代码将加载数据,并通过调用函数显示数据,将其显示在一个二维图上。

编写代码加载数据,并通过调用函数显示数据。

这与上次练习中使用的数据集相同。在ex3data1.mat中有5000个训练样例,每个训练样例是一个20像素 $\times $ 20像素的数字灰度图像。每个像素用一个浮点数表示,表示该位置的灰度强度。20×2020\times 2020×20 的像素网格被"展开"成一个 400400400 维的向量。每个训练样例X数据矩阵中都变成了一行。给定一个 5000×4005000\times 4005000×400 的矩阵X,其中每一行都是一个手写数字图像的训练样例。

image-20220321222922104

ex4.m的第二部分是一个 500050005000 维的向量y(包含了训练集的标签)。将数字 000 映射到值 101010,而数字 111999 按其自然顺序被标记为 111999

先导入相关的函数库

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)


png

1.2 Model representation

神经网络如图所示:它有333个层,一个输入层,一个隐藏层和一个输出层。

image-20220131152535094

输入是数字图像的像素值。由于图像的大小是 20×2020\times 2020×20,这给了 400400400 个输入层单位(不包括总是输出 +1+1+1 的额外偏差单位)。训练数据将通过ex4.m加载到变量Xy中。ex4weights.mat中存储了已经训练过的神经网络参数 θ(1),θ(2)\theta(1),\theta(2)θ(1),θ(2),将它们加载到变量Theta1Theta2中。这些参数是针对一个在第二层有252525个单元和101010个输出单元的神经网络(对应于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)

将标签值(1,2,3,4,...,10)(1,2,3,4,...,10)(1,2,3,4,...,10)转化成非线性相关的向量,即yyy向量对应位置为111,比如y[5]=1y[5]=1y[5]=1那么y[5]=[0,0,0,0,1,0,0,0,0,0]y[5]=[0,0,0,0,1,0,0,0,0,0]y[5]=[0,0,0,0,1,0,0,0,0,0]。数据中的y[i]=10y[i]=10y[i]=10,就是表示原本的y[i]=0y[i]=0y[i]=0,只是数据提前把000转换成了101010

# 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中存储了已经训练过的神经网络参数 θ(1),θ(2)\theta(1),\theta(2)θ(1),θ(2),将它们加载到变量Theta1Theta2中。这些参数是针对一个在第二层有252525个单元和101010个输出单元的神经网络(对应于10个数字类)。θ1\theta1θ1的规模为25∗40125*40125401θ2\theta2θ2的规模为10∗2610*261026

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函数

image-20220131152535094

θ1\theta1θ1的规模为25×40125\times 40125×401θ2\theta2θ2的规模为10×2610\times 2610×26a1a_1a1的规模为5000×4015000\times 4015000×401

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

现在将实现神经网络的成本函数和梯度。神经网络的代价函数(没有正则化)是:

image-20220322144013322

k=10k=10k=10是可能的标签的总数。原始的标签(在变量y中)是1,2,...,101,2,...,101,2,...,10,为了训练一个神经网络,需要将标签重新编码为只包含值000111的向量。

image-20220322144223390

举个例子:如果x(i)x^{(i)}x(i)555的图像,那么y则是一个y[5]=1y[5]=1y[5]=1101010维向量,其他元素等于000

要求实现前馈计算,即为每个训练数据iii计算hθ(x(i))h_{\theta}(x^{(i)})hθ(x(i))并计算所有训练数据的成本。正确的最终成本约为0.2876290.2876290.287629

输出层输出的是对训练样本的预测,包含500050005000个数据,每个数据对应了一个包含101010个元素的向量,代表了结果有101010类。在公式中,每个元素与log项对应相乘。

构造Cost函数:使用提供训练好的参数θ,算出的cost应该为0.2876290.2876290.287629

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(正则化代价函数)

正则化神经网络的代价函数:

image-20220322151717932

假设神经网络将只有333个层:一个输入层,一个隐藏层和一个输出层。但是,设计的代码应该适用于任意数量的输入单元、隐藏单元和输出单元。不应该规范与偏差相对应的项。

使用之前的Theta1Theta2以及λ=1\lambda=1λ=1参数来调用Regularized_cost函数,正确的最终答案大约为0.3837700.3837700.383770。(注意不要将每层的偏置项正则化)

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(反向传播)

在本部分的练习中,将实现反向传播算法来计算神经网络代价函数的梯度。一旦计算了梯度,将通过使用高级优化器最小化代价函数J(Θ)J(Θ)J(Θ)来训练神经网络。

首先实现反向传播算法来计算(非正则化)神经网络的参数的梯度。验证了在非正则化情况下的梯度计算是正确的之后,再实现正则化神经网络的梯度。

2.1 Sigmoid gradient(S函数导数)

sss型函数的梯度:

image-20220322154321651

image-20220322154440934

def sigmoid_gradient(z):
    return sigmoid(z)*(1-sigmoid(z))

2.2 Random initialization(随机初始化)

在训练神经网络时,随机初始化参数是很重要的,可以打破数据的对称性。一个有效的策略是在均匀分布(−ϵ,ϵ)(−\epsilon,\epsilon)(ϵ,ϵ)中随机选择值,选择ϵ=0.12\epsilon = 0.12ϵ=0.12这个范围的值来确保参数足够小,使得训练更有效率。

因此接下来需要完成随机初始化权重,初始化Θ\ThetaΘ的权重

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(反向传播)

image-20220322220748837

给定一个训练数据(x(t),y(t))(x^{(t)},y^{(t)})(x(t),y(t)),将先运行一个向前传递来计算整个网络中的所有激活值,包括假设的输出值hΘ(x)h_{\Theta}(x)hΘ(x),然后,对于第lll层中的每个节点jjj,需要计算误差项δj(l)\delta_{j}^{(l)}δj(l)

对于一个输出节点,可以直接计算神经网络激活值和真实目标值之间的差异,并使用它来定义δj(3)\delta_{j}^{(3)}δj(3)(本题中第333层是输出层)。对于隐藏单元,根据第(l+1)(l+1)(l+1)层中节点的误差项的加权平均值来计算δj(l)\delta_{j}^{(l)}δj(l)

实现一个for循环t=1:mt=1:mt=1:m,并将步骤1∼41\sim414放在for循环中,第ttt次循环计算第ttt个训练数据(x(t),y(t))(x^{(t)},y^{(t)})(x(t),y(t))的相关数据。步骤555将累积的梯度除以mmm,得到神经网络代价函数的梯度。

1.将第ttt个训练数据x(t)x^{(t)}x(t)设置为输入层的值(a(1))(a^{(1)})(a(1))。执行前馈传递(feedforward),计算第222层和第333层的激活量(z(2)、a(2)、z(3)、a(3))(z^{(2)}、a^{(2)}、z^{(3)}、a^{(3)})(z(2)a(2)z(3)a(3))。注意,需要添加一个+1+1+1项,以确保第111层和第222层的激活向量也包括偏置单元。

2.对于第333层(输出层)中的每个输出单元kkk,设定δk(3)=(ak(3)−yk)(yk∈{0,1})\delta_{k}^{(3)}=(a_{k}^{(3)}-y_k)(y_k\in \lbrace 0,1 \rbrace )δk(3)=(ak(3)yk)(yk{0,1})yky_kyk表示当前的训练数据是否属于第kkk类,如果属于第kkk类则yk=1y_k=1yk=1,否则yk=0y_k=0yk=0

3.对于隐藏层l=2l=2l=2,设置δ(2)=(Θ(2))Tδ(3).∗g′(z(2))\delta^{(2)}=(\Theta^{(2)})^{T}\delta^{(3)}.*g'(z^{(2)})δ(2)=(Θ(2))Tδ(3).g(z(2))

4.使用以下公式累积此训练数据中的梯度。注意,应该跳过或删除δ0(2)\delta_{0}^{(2)}δ0(2)。那么Δ(l)=Δ(l)+δ(l+1)(a(l))T\Delta^{(l)}=\Delta^{(l)}+\delta^{(l+1)}(a^{(l)})^TΔ(l)=Δ(l)+δ(l+1)(a(l))T

5.通过将累积的梯度除以mmm,得到神经网络代价函数的(非正则化的)梯度。

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(梯度检测)

在神经网络中,需要最小化成本函数J(Θ)J(\Theta)J(Θ)。为了对参数执行梯度检查,考虑将参数Θ(1),Θ(2)\Theta^{(1)},\Theta^{(2)}Θ(1),Θ(2)"展开"到一个向量θ\thetaθ中。通过这样做,可以认为成本函数是J(Θ)J(\Theta)J(Θ),并使用以下梯度检查过程:

假设有一个函数fi(θ)f_i(\theta)fi(θ),可以计算∂∂θiJ(θ)\frac{\partial}{\partial\theta_i}J(\theta)θiJ(θ);检查一下fi(θ)f_i(\theta)fi(θ)是否输出了正确的导数值:

image-20220322225448211 image-20220322225453213

因此,θ(i+)θ^{(i+)}θ(i+)θ\thetaθ基本相同,除了它的第iii个元素被增加了ϵ\epsilonϵθ(i−)θ^{(i-)}θ(i)θ\thetaθ基本相同,除了它的第iii个元素被减少了ϵ\epsilonϵ。现在通过检查每个iii来数值验证fi(θ)f_i(\theta)fi(θ)的正确性:

image-20220322225702306

这两个值相互近似的程度将取决于JJJ。但是假设ϵ=10−4\epsilon=10^{-4}ϵ=104 ,通常会发现上面式子的左极限和右极限至少会存在4位有效的数字(通常是更多的)。如果反向传播实现是正确的,相对误差应该低于10−910^{-9}109

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 (正则化神经网络)

成功实现反向传播算法后,向梯度添加正则化。为了考虑正则化,可以在使用反向传播计算梯度后将其作为附加项添加。

具体来说,在使用反向传播计算了Δij(l)\Delta_{ij}^{(l)}Δij(l)之后,可以使用来添加正则化:

image-20220322231414463

注意,不要正则化偏差项Θ(l)\Theta^{(l)}Θ(l)的第一列。注意,在参数Θij(l)\Theta_{ij}^{(l)}Θij(l)中,下标i∈[1,]i\in[1,]i[1,],下标j∈[0,]j\in[0,]j[0,],如图:

image-20220322231659435

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%)。通过训练神经网络进行迭代,可以获得更高的训练精度。也可以考虑改变正则化参数λ\lambdaλ来得到更高的训练精度。更加正确的学习设置,就有可能让神经网络更完美地匹配训练集。

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,并且将它激活(即有一个激活值(ai(l))(a^{(l)}_i)(ai(l))接近111)。对于训练过的神经网络,Θ(1)\Theta^{(1)}Θ(1)中每一行都是一个401401401维的向量,代表每个隐藏层单元的参数。如果忽略偏置项,就能得到400400400维的向量,这个向量代表每个样本输入到每个隐层单元的像素的权重。因此可视化的其中一种方法是,reshape这个400400400维的向量为(20,20)(20,20)(20,20)的图像然后显示出来。

接下来通过使用显示数据功能,它将显示一个包含25个25个25单元的图像(如下图),每个单元对应于网络中的一个隐藏单元:

image-20220323144832440

在经过训练的神经网络中,会发现隐藏的单元大致对应于在输入中寻找笔画和其他模式的检测器。

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)

image-20220323150036342

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Phoenix_ZengHao

创作不易,能否打赏一瓶饮料?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值