深度学习实战(一)——识别手写数字

这里的实战是根据Neural Networks and Deep Learning的前两章整理出来的。用了它提供的数据集以及一些代码。然后自己修改了他的一些代码,使得更加容易理解。

一、任务及数据

就是自己写一个神经网络来实现对于MNIST数据集的手写体分类任务。这里使用随机梯度下降算法和MNIST训练数据。首先获取MNIST 数据。如果你是一个 git用户,那么你能够通过克隆这本书的代码仓库获得数据

git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git

 也可以直接从MNIST的数据集下载数据和代码。
下载下来直接放在工程目录里面就行了。 
训练(train) : 50,000 
验证(validation): 10,000 
测试(test): 10,000 

注:验证集将在后面用到,用来设置某些网络的超参数,例如学习速率等,这些参数不被学习算法直接选择。

除了MNIST数据,我们还需要一个叫Numpy的python库,用来做快速线性代数(在一开始安装Tensorflow时会装)

从任务和输入就能够得到大概的网络结构: 

                                              
损失函数为二次误差函数 
激活函数为sigmoid函数。

二、读取数据

读取数据由mnist_loader.py这个文件实现。 

代码:

#### Libraries
# Standard library
import pickle
#import random
import gzip
# Third-party libraries
import numpy as np

#从数据集中载入数据
def load_data():
    f = gzip.open('../data/mnist.pkl.gz', 'rb')
    training_data, validation_data, test_data = pickle.load(f,encoding = 'bytes') #注:这里要加上encoding = 'bytes'这句,将ascii解码为byte类型数据。
    f.close()
    return (training_data, validation_data, test_data)

##改编数据集的格式	
def load_data_wrapper():
    '''Return a tuple containing ``(training_data, validation_data,
    test_data)``. Based on ``load_data``, but the format is more
    convenient for use in our implementation of neural networks.

    In particular, ``training_data`` is a list containing 50,000
    2-tuples ``(x, y)``.  ``x`` is a 784-dimensional numpy.ndarray
    containing the input image.  ``y`` is a 10-dimensional
    numpy.ndarray representing the unit vector corresponding to the
    correct digit for ``x``.

    ``validation_data`` and ``test_data`` are lists containing 10,000
    2-tuples ``(x, y)``.  In each case, ``x`` is a 784-dimensional
    numpy.ndarry containing the input image, and ``y`` is the
    corresponding classification, i.e., the digit values (integers)
    corresponding to ``x``.

    Obviously, this means we're using slightly different formats for
    the training data and the validation / test data.  These formats
    turn out to be the most convenient for use in our neural network
    code.'''
    tr_d, va_d, te_d = load_data()
    print('load_data success!') #验证这一步是否导入成功
	#训练集
    training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
    training_results = [vectorized_result(y) for y in tr_d[1]]
    training_data = list(zip(training_inputs, training_results)) #这里要加上list()
	
	#验证集
    validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
    validation_data = list(zip(validation_inputs, va_d[1]))
	
	#测试集
    test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
    test_data = list(zip(test_inputs, te_d[1]))
    return (training_data, validation_data, test_data)

def vectorized_result(j):
    """Return a 10-dimensional unit vector with a 1.0 in the jth
    position and zeroes elsewhere.  This is used to convert a digit
    (0...9) into a corresponding desired output from the neural
    network."""
    e = np.zeros((10, 1)) #输出10行1列
    e[j] = 1.0
    return e

代码解释: 
1.load_data函数 

#从数据集中载入数据
def load_data():
    file=gzip.open('mnist.pkl.gz','rb')
    training_data,validation_data,test_data=pickle.load(file,encoding = 'bytes') #注:这里要加上encoding = 'bytes'这句,将ascii解码为byte类型数据。
    file.close()
    return training_data,validation_data,test_data

Load_data()主要作用:解压数据集,然后从数据集中把数据取出来
然后取出来之后的几个变量代表的数据的格式分别是这样的:

training_data:是一个由两个元素构成的元组
其中一个元素是测试图片集合,是一个50000*784的numpy ndarray(其中50000行就是数据的数量,784列就是一个数据的维度(这里是像素)). 
第二个元素就是一个测试图片的标签集.是一个50000*1的numpy ndarray.其中指明了每行是一个什么数字…通俗的来说就是这个样子: 

                                   
validation_datatest_data的结构和上面的training_data是一样的,只是数量(元素的行数)不一样.这两个是10000行.

2.load_data_wrapper函数 

def data_wrapper():
    tr_d,va_d,te_d=load_data()
    #训练集
    training_inputs=[np.reshape(x,(784,1)) for x in tr_d[0]]
    training_labels=[vectorized_label(x) for x in tr_d[1]]
    training_data=list(zip(training_inputs,training_labels))

    #验证集
    validation_inputs=[np.reshape(x,(784,1)) for x in va_d[0]]
    validation_data=list(zip(validation_inputs,va_d[1]))

    #测试集
    test_inputs=[np.reshape(x,(784,1)) for x in te_d[0]]
    test_data=list(zip(test_inputs,te_d[1]))

    return training_data,validation_data,test_data

之前的load_data返回的格式虽然很漂亮,但是并不是非常适合我们这里计划的神经网络的结构,因此我们在load_data的基础上面使用load_data_wrapper函数来进行一点点适当的数据集变换,使得数据集更加适合我们的神经网络训练. 
以训练集的变换为例 
对于training_inputs来说,就是把之前的返回的training_data[0]的所有例子都放到了一个列表中: 
简单的来说如下图所示 
                            
同样可以知道training_labels的样子为 
                                                        
然后training_data为zip函数组合,那么training_data为一个列表,其中每个元素是一个元组,二元组又有一个training_inputs和一个training_labels的元素组合而成.如下图 
                                            

三、神经网络部分

一个神经网络的训练过程一般由以下4个步骤组成:

  • 1.初始化权重和偏置
  • 2.前向传播(forward propagation):使用输入X,权重W和偏置b,对于每一层计算Z和A。在最后一层中,计算f(A ^(L-1)),它可能会是S形函数softmax或线性函数的A ^(L-1),并得到预测值y_hat。
  • 3.计算损失函数(loss function):该函数是理想标签y和预测标签y_hat二者的函数,它表明预测值离实际目标值有多大差距,训练神经网络模型的目的就是要尽量减少损失函数的值。
  • 4.反向传播(back propagation):在这一过程中,需要计算损失函数f(y,y_hat)相对于A、W和b的梯度,分别称为dA、dW和db。使用这些梯度值,将参数的值从最后一层反向更新到第一层。
  • 5.对n次迭代重复步骤2-4,直到我们觉得已经最小化了损失函数,且没有过拟合训练数据时则表明训练结束。

链接:https://www.jianshu.com/p/30784bd13101

        神经网络代码的核心片段是一个Network 类,我们用来表示一个神经网络。这是我们用来初始化一个 Network 对象的代码:

class Network(object):
  def __init__(self, sizes):
    self.num_layers = len(sizes)
    self.sizes = sizes
    self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
    self.weights = [np.random.randn(y, x)
        for x, y in zip(sizes[:-1], sizes[1:])]

  根据独立高斯随机变量来选择权重和偏置,其被归一化为均值为 0,标准差 1。

  

我们想要 Network 对象做的主要事情是学习。为此我们给它们一个实现随即梯度下降算法的 SGD 算法。代码如下。

def SGD(self, training_data, epochs, mini_batch_size, eta,test_data=None):
    """Train the neural network using mini-batch stochastic
    gradient descent. The "training_data" is a list of tuples
    "(x, y)" representing the training inputs and the desired
    outputs. The other non-optional parameters are
    self-explanatory. If "test_data" is provided then the
    network will be evaluated against the test data after each
    epoch, and partial progress printed out. This is useful for
    tracking progress, but slows things down substantially."""
     if test_data: n_test = len(test_data) #这是个判断句,如果test_data不是None时,执行下面的n_test = len(test_data)
        n = len(training_data)#训练数据大小
        #迭代过程
        for j in range(epochs):
            random.shuffle(training_data)
            ##mini_batches是列表中放切割之后的列表
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in range(0, n, mini_batch_size)]
             #每个mini_batch都更新一次,重复完整个数据集
            for mini_batch in mini_batches:
                 #update_mini_batch()存储C对于各个参数的偏导
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print ("Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test))
            else:
                print ("Epoch {0} complete".format(j))

代码如下工作。在每个迭代期,

1、它首先随机地将训练数据打乱,然后将它分成多个适当大小的小批量数据。这是一个简单的从训练数据的随机采样方法。

2、然后对于每一个 mini_batch我们应用一次梯度下降。这是通过代码 self.update_mini_batch(mini_batch, eta) 完成的,它仅仅使用 mini_batch 中的训练数据,根据单次梯度下降的迭代更新网络的权重和偏置

这是update_mini_batch 方法的代码:

def update_mini_batch(self, mini_batch, eta):
    """Update the network's weights and biases by applying
    gradient descent using backpropagation to a single mini batch.
    The "mini_batch" is a list of tuples "(x, y)", and "eta"
        is the learning rate."""
     nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        #mini_batch中的一个实例调用梯度下降得到各个参数的偏导
        for x, y in mini_batch:
             #从一个实例得到的梯度
            delta_nabla_b, delta_nabla_w = self.backprop(x, y) #调用反向传播算法,大部分工作由这行代码完成
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        #每一个mini_batch更新一下参数
        self.weights = [w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]

         

四、Python shell中运行该代码的指令

4.1 加载MNIST数据

>>> import mnist_loader

>>> training_data, validation_data, test_data = mnist_loader.load_data_wrapper()

4.2 导入有30个隐藏层神经元的Network网络

>>> import network

>>> net = network.Network([784, 30, 10])

4.3 将使用随机梯度下降来从 MNIST training_data 学习超过 30 次迭代期,小批量数据大小为 10,学习速率 η = 3.0

>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)

代码执行过程需要三四分钟,请耐心等待。

解决了n多的bug后,运行结果如下:

                                         

python3版本的完整代码在这里下载。

        当然,为了获得这些准确性,我不得不对训练的迭代期数量小批量数据大小学习速率 η做特别的选择。正如我上面所提到的,这些在我们的神经网络中被称为超参数,以区别于通过我们的学习算法所学到的参数(权重和偏置)。如果我们选择了糟糕的超参数,我们会得到较差的结果。其次通过改变学习速率η来改变网络性能

        调参技巧:当你看到网络的性能随着时间的推移慢慢地变好了。这表明应该增大学习速率,例如 η = 0.01。如果我们那样做了,我们会得到更好的结果,这表明我们应该再次增加学习速率。(如果改变能够改善一些事情,试着做更多!)如果我们这样做几次,我们最终会得到一个像 η = 1.0 的学习速率(或者调整到 3.0)。

参考资料:

MNIST实战:https://blog.csdn.net/xierhacker/article/details/53282060

  • 5
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值