深度学习(4)--手写数字mnist实现

       前面两节,讲述了梯度下降和方向传播的原理,这里我通过mnist训练来讲述下python的实现方法

  头文件

        numpy用于矩阵运算,random用于数据集的shuffle,mnist_loader 用于加载数据集

import numpy as np
import random
import mnist_loader

  初始化网络

         其中参数是sizes 格式是[784,30,10] 代表每层神经元的个数,self.weights 格式[34*784],[10*34],self.biases格式是[34*1]和[10*1] 前者代表每两层神经元之间连线的权重,后者代表每个神经元上的偏向值。

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

SGD 

     原理部分第二节已经说过了,我们来解读一下参数

        train_data:我们输入的训练集可以抽象成[(x,y),(x1,y1)....(xn,yn)]

        epoches:训练的轮数由我们自己指定

        mini_size:每次随机抽样训练的训练样本数量

        eta(\eta):表示learning rate 学习率,可以理解为步长

        test_data:格式与train_data相同,用于测试(注意本实例中没有介绍验证集) 

        程序中先对数据全集进行shuffle打乱操作,之后按mini_size 进行分堆,下面分别对每个mini_batch 进行训练,也就是随机抽样的过程,这样做极大的加快了学习效率。 

  def SGD(self,train_data,epoches,mini_size,eta,test_data):
        if test_data: n_test = len(test_data)
        n = len(train_data) #表示 训练样本数量
        for epoch in range(epoches):
            random.shuffle(train_data)

            mini_batches = [train_data[k:k+mini_size] for k in xrange(0,n,mini_size)] #xrange 返回的是生成器
            for mini_batch in mini_batches:
                self.update_minibatch(mini_batch,eta)
            if test_data:
                print "epoches {0} acc {1}/{2} ".format(epoch,self.cal_acc(test_data),n_test)
            else:
                print "no test data"

     update_minibatch 主要完成的操作是,求出样本集中每个实例的偏导数累加求和,在根据下面的公式进行公式对weights和biases进行更新

              

    def update_minibatch(self,mini_batch,eta):

        nabla_b = [np.zeros(b.shape) for b in self.biases] #随机梯度下降算法,用于存储我们指定的minibatch中的数据的bias的总和
        nabla_w = [np.zeros(w.shape) for w in self.weights]

        for x,y in mini_batch:
            new_nabla_b ,new_nabla_w = self.back(x,y)

            nabla_w = [nw+w for nw,w in zip(new_nabla_w,nabla_w)]
            nabla_b = [nb+b for nb,b in zip(new_nabla_b,nabla_b)]

        self.weights = [w-((eta/len(mini_batch))*nabla_w) for w,nabla_w in zip(self.weights,nabla_w)]
        self.biases = [b-((eta/len(mini_batch))*nabla_b) for b,nabla_b in zip(self.biases,nabla_b)]

反向传播

梯度下降和反向传播的核心就是在于学习参数的偏导数计算,下图是反向传播求偏导数的过程。

    1,首先初始化各层的参数为zero 和Pytorch 中optimizer.zero_grad() 操作大体相似,避免之前梯度的累加,用于存储各个层偏导数结果

        nabla_b = [np.zeros(b.shape) for b in self.biases] #随机梯度下降算法,用于存储我们指定的minibatch中的数据的bias的总和
        nabla_w = [np.zeros(w.shape) for w in self.weights]

    2,z 在原始的公式中相当于 多个wx+b 的累加和,待激活的值, 第一层默认等于原始输入的图片数据的tensor值

            z = np.dot(w,activation)+b

   activation=\sigma (wx+b),都是和神经元的个数一致。

                 

      3,进行前向传播,计算所有的z和activation的值

 for w,b in zip(self.weights,self.biases):
           # print w.shape,b.shape
            z = np.dot(w,activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)

      4,根据公式计算最后一层的error    

        delta = cost(activations[-1],y)*sigmoid_prime(zs[-1])

      5,更新最后一层的bias和weight   ,其中,bias等于最后一层的error值,weight是最后一层的error值与前一层激活值的转置乘积

        nabla_b[-1] = delta
        #delta 目前可能是10*1 的向量,而activation[-2]可能是784*1 无法直接点乘 需要转置后者 最终是10个784维向量 内部相加输出 10*1
        nabla_w[-1] = np.dot(delta,activations[-2].transpose())

     6,根据链式求导法则 ,反向更新各层的weight值与bias值 ,并最终以(nabla_w,nable_w)形式返回给随机梯度下降算法(update_mini_batch)的进行,全局weights和biases的更新

 for l in range(2,self.layers):
            delta = np.dot(self.weights[-l+1].transpose(),delta)*sigmoid_prime(zs[-l])
            nabla_b[-l] =delta
            nabla_w[-l] = np.dot(delta,activations[-l-1].transpose())
        return (nabla_b,nabla_w)

准确率计算 

    测试集是10000个(x,y),首先进行前向传播和softmax,获取最终预测的label值,并计算预测值与真实值的数量来计算准确率。

    def cal_acc(self,test_data):
        test_set = [(self.feedforward(x),y) for x,y in test_data]
        return np.sum(int(np.argmax(x))==y for (x,y) in test_set)
    def feedforward(self,x):
        for w,b in zip(self.weights,self.biases):
            x = np.dot(w,x)+b
            x = sigmoid(x)  #一定不能忘记激活

    下面是工具方法,包含了\sigma的导数:Oj = \sigma z*(1-\sigma z)

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

     激活函数 \sigma

def sigmoid(z):
    return 1/(1+np.exp(-z))

源代码请移步     坚持一件事或许很难,但坚持下来一定很酷!^_^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章