4.神经网络可以计算任何函数的可视化证明
神经网络拥有一定的普遍性,即包含一个隐藏层的神经网络可以被用来按照任意给定的精度来近似任何连续函数。
这一章使用一个实例来阐述神经网络是如何来近似一个一元函数,和一个二元函数,并扩展到一个多元的实值函数。
5.深度神经网络为何很难训练
这一章讲述了在深度网络中,不同的层学习的速度差异很大,尤其是,在网络中后面的层学习的情况很好的情况下,先前的层常常会在训练的时候停滞不变,基本学习不到东西。这根本的原因是因为我们学习的速度下降了,实际上,我们会发现在深度学习中使用基于梯度下降的学习方法本身存在着内在的不稳定性。这种不稳定使得先前或者后面的层学习过于阻滞。
我们研究一下神经网络学习阻滞的原因,借用书中的内容:
消失的梯度问题:当激活函数采用sigmoid函数时,反向传播计算会出现消失的梯度问题,主要是由于|σ('z)|<1/4,而且我们初始化权重ω采用μ(0,1)高斯分布,权重|ω|通常会小于1,导致前面的隐藏层中的神经元学习速度要慢于后面的隐藏层。
梯度激增的问题:如果我们权重初始化选择比较大话,并且σ('z)项不会太小,就会导致前面的隐藏层中的神经元学习速度要快于后面的隐藏层,即出现梯度激增的问题。
不稳定的梯度问题:根本的问题其实并⾮是消失的梯度问题或者激增的梯度问题,⽽是在前⾯的层上的梯度是来⾃后⾯的层上项的乘积。当存在过多的层次时,就出现了内在本质上的不稳定场景。 唯⼀让所有层都接近相同的学习速度的⽅式是所有这些项的乘积都能得到⼀种平衡。如果没有某种机制或者更加本质的保证来达成平衡,那⽹络就很容易不稳定了。简⽽⾔之,真实的问题就是神经⽹络受限于不稳定梯度的问题。所以,如果我们使⽤标准的基于梯度的学习算法,在⽹络中的不同层会出现按照不同学习速度学习的情况。
为了避免出现不稳定的梯度问题,我们可以采用修正线性神经元。因为其σ('z)=1.
6.深度学习
本章主要介绍了卷及神经网络。在前面已经详细介绍了这一块内容
Network3.py的程序与之前的程序有所不同,这里使用到了theano的学习库。Theano的一个非常好的特性是它能够运行于CPU或者GPU上,运行于GPU上可以显著的获得加速。
# -*- coding: utf-8 -*- """ Created on Sun Mar 18 18:25:33 2018 @author: Administrator """ ''' 书籍:神经网络与深度学习 第6章:卷积神经网络 ''' import pickle import gzip import numpy as np import theano import theano.tensor as T from theano.tensor.nnet import conv from theano.tensor.nnet import softmax from theano.tensor import shared_randomstreams #from theano.tensor.signal import downsample from theano.tensor.signal.pool import pool_2d ''' 本程序中神经网络中运算的数据:每层输入inpt,权重w,偏置b,以及网络层输出al全部采用thean.config.floatX类型 (该类型在.theanorc文件之指定),b为TensorType(theano.config.floatX,vector),其它为TensorType(theano.config.floatX,matrix) 由于函数方程式在运算时要求theano,变量类型一致,所以不一致时需要用T.cast(变量,dtype)进行转换 而期望输出y,以及网络输出类别y_out采用int32类型 TensorType(int32,vctor) 并且 w,b,实例数据集(x,y),权重w,b均为shared 变量 ''' #限制 GPU = False if GPU: print('尝试使用GPU加速,如果不想,修改标志位GPU=False') try: theano.config.device = 'gpu' except: pass theano.config.floatX = 'float32' else: print('尝试在CPU下运行,如果想使用GPU加速,修改标志位GPU=True') print(theano.config.floatX) #float32 在.theanorc文件中配置 #定义神经元的激活函数 def linear(z): ''' 线性函数 ''' return z def ReLU(z): ''' 修正后的线性函数 ''' return T.maximum(0.0,z) from theano.tensor.nnet import sigmoid from theano.tensor import tanh #### Load the MNIST data def load_data_shared(filename="data/mnist.pkl.gz"): with gzip.open(filename, 'rb') as f: training_data, validation_data, test_data = pickle.load(f,encoding='bytes') def shared(data): """Place the data into shared variables. This allows Theano to copy the data to the GPU, if one is available. """ shared_x = theano.shared( np.asarray(data[0], dtype=theano.config.floatX), borrow=True) shared_y = theano.shared( np.asarray(data[1], dtype=theano.config.floatX), borrow=True) return shared_x,T.cast(shared_y, "int32") #shared_y由theano.config.floatX转换为int32是因为后面输出准确率的时候输出类别y_out是int32类型,类型需要一致 return [shared(training_data), shared(validation_data), shared(test_data)] class ConvPoolLayer(object): ''' 定义卷积-池化层的类 Used to create a combination of a convolutional and a max-pooling layer. A more sophisticated implementation would separate the two, but for our purposes we'll always use them together, and it simplifies the code, so it makes sense to combine them. ''' def __init__(self,filter_shape,image_shape,poolsize=(2,2),activation_fn=sigmoid): ''' 初始化参数 filter_shape:卷积础参数 (滤波器个数,深度,宽度,高度) 例如filter_shape = (20,3,5,5) image_shape:图片像素维数(mini_batch_size,深度,宽度,高度) poolsize:池化层维数 activation_fn:卷积层激活输出函数 filter_shape` is a tuple of length 4, whose entries are the number of filters, the number of input feature maps, the filter height, and the filter width. `image_shape` is a tuple of length 4, whose entries are the mini-batch size, the number of input feature maps, the image height, and the image width. `poolsize` is a tuple of length 2, whose entries are the y and x pooling sizes. ''' self.filter_shape = filter_shape self.image_shape = image_shape self.poolsize = poolsize self.activation_fn = activation_fn #初始化权重和偏置 n_out = (filter_shape[0]*np.prod(filter_shape[2:])/np.prod(poolsize)) self.w = theano.shared( np.asarray( #转换为数组,并指定数据类型 np.random.normal( loc=0.0,scale=np.sqrt(1.0/n_out),size= filter_shape), dtype=theano.config.floatX), name='w',borrow=True) self.b = theano.shared( np.asarray( np.random.normal( loc=0.0,scale=1.0,size=(filter_shape[0],)), dtype=theano.config.floatX), name='b',borrow=True) self.params = [self.w,self.b] def set_inpt(self,inpt,inpt_dropout,mini_batch_size): ''' 设置该层的输入,并计算该层的输出 inpt:输入数据样本特征值集合 每一行数据对应一个实例 输入数据集的大小为mini_batch_size inpt_dropout:采用弃权后 输入数据样本特征值集合 mini_batch_size:小批量数据个数 ''' self.inpt = inpt.reshape(self.image_shape) #计算卷积层输出(相当于计算w*a) 这里没有经过激活函数 conv_out = conv.conv2d( input = self.inpt,filters = self.w,filter_shape = self.filter_shape, image_shape = self.image_shape) #计算池化层输出 (利用最大池化方法) pooled_out = pool_2d( input = conv_out,ds = self.poolsize,ignore_border = True) #卷积层没有弃权 计算每个样本经过该层激活函数的最终输出 [实例1输出,实例2输出,...实例mini_batch_size输出] self.output = self.activation_fn( pooled_out + self.b.dimshuffle('x',0,'x','x')) self.output_dropout = self.output class FullyConnectedLayer(object): ''' 定义全连接层的类 ''' def __init__(self,n_in,n_out,activation_fn=sigmoid,p_dropout=0.0): ''' 初始化参数 n_in:输入维数 例如 n_in = 40*4**4 n_out:输出维数 例如 n_out = 100 activation_fn:该层激活函数 可选值有 sigmod,tanh,ReLU p_dropout:丢弃的概率(减少过拟合) 0.0~1.0 ''' self.n_in = n_in self.n_out = n_out self.activation_fn = activation_fn self.p_dropout = p_dropout #共享变量 ''' 共享变量时多线程编程中的一个名词,故名此意就是各线程,公用拥有的变量,这个是为了多线程高效计算 、访问而使用的变量。因为深度学习中,我们整个计算过程基本上都是多线程计算,于是就要用到共享变量 在程序中,我们一般把神经网络的参数W、b等定义为共享变量,因为网络的参数,基本上每个线程都需要访问的 #初始化该层权重和偏置 ''' #loc指定均值为0 scale:标准差为1/sqrt(n_out),size:维数为n_in*n_out self.w = theano.shared( np.asarray( #转换为数组,并制定数据类型 np.random.normal( loc=0.0,scale=np.sqrt(1.0/n_out),size=(n_in,n_out)), dtype=theano.config.floatX), name='w',borrow=True) self.b = theano.shared( np.asarray( np.random.normal( loc=0.0,scale=1.0,size=(n_out,)), dtype=theano.config.floatX), name='b',borrow=True) self.params = [self.w,self.b] def set_inpt(self,inpt,inpt_dropout,mini_batch_size): ''' 设置该层的输入,并计算该层的输出 inpt:输入数据样本特征值集合 每一行数据对应一个实例 输入数据集的大小为mini_batch_size inpt_dropout:采用弃权后 输入数据样本特征值集合 mini_batch_size:小批量数据个数 ''' #没有使用弃权 计算该层输出向量 因为输入inpt可能是mini_batch_size个1*28*28维的数据 这里需要重新定义维数 self.inpt = inpt.reshape((mini_batch_size,self.n_in)) self.output = self.activation_fn( T.dot(self.inpt,self.w) + self.b) #按行统计最大值索引 最终得到所有实例的输出类别 [实例1类别,实例2类别,...] #self.y_out = T.argmax(self.output,axis=1) #使用弃权 把该层输入中部分值按照p_dropout概率置为0 self.inpt_dropout = dropout_layer( inpt_dropout.reshape((mini_batch_size,self.n_in)),self.p_dropout)
self.inpt_dropout /= 1 - self.p_dropout self.output_dropout = self.activation_fn(T.dot(self.inpt_dropout,self.w) + self.b) #按行统计最大值索引 最终得到所有实例的输出类别 [实例1类别,实例2类别,...] self.y_out = T.argmax(self.output_dropout,axis=1) def accuracy(self,y): ''' 返回准确率 y:输入对应的目标类别集合 ''' return T.mean(T.eq(y,self.y_out)) class SoftmaxLayer(object): ''' 定义一个柔性最大层的类 ''' def __init__(self,n_in,n_out,p_dropout=0.0): ''' 初始化参数 n_in:输入维数 例如 n_in = 100 n_out:输出维数 例如 n_out = 10 p_dropout:丢弃的概率(减少过拟合) 0.0~1.0 ''' self.n_in = n_in self.n_out = n_out self.p_dropout = p_dropout #初始化权重和偏置 self.w = theano.shared( np.zeros((n_in,n_out),dtype=theano.config.floatX), name='w',borrow=True) self.b = theano.shared( np.zeros((n_out,),dtype=theano.config.floatX), name='b',borrow=True) self.params = [self.w,self.b] def set_inpt(self,inpt,inpt_dropout,mini_batch_size): ''' 设置该层的输入,并计算该层的输出 inpt:输入数据样本特征值集合 inpt_dropout:采用弃权后 输入数据样本特征值集合 mini_batch_size:小批量数据个数 output:不使用弃权该层的输出 output_dropout:使用弃权该层的输出 y_out:该层的输出类别 ''' self.inpt = inpt.reshape((mini_batch_size,self.n_in)) #计算该层输出 self.output = softmax(T.dot(self.inpt,self.w) + self.b) #获取输出类别 #self.y_out = T.argmax(self.output,axis=1) #使用弃权 self.inpt_dropout = dropout_layer( inpt_dropout.reshape((mini_batch_size,self.n_in)),self.p_dropout)
self.inpt_dropout /= 1 - self.p_dropout self.output_dropout = softmax(T.dot(self.inpt_dropout,self.w) + self.b) self.y_out = T.argmax(self.output_dropout,axis=1) def cost(self,net): ''' 返回代价值 net:传入Network对象 ''' #print(net.y.type) #TensorType(int32, vector) #print(self.output_dropout.type) #TensorType(float32, matrix) return -T.mean(T.log(self.output_dropout)[T.arange(net.y.shape[0]),net.y]) def accuracy(self,y): ''' 返回准确率 ''' return T.mean(T.eq(self.y_out,y)) #T.eq:逐元素判断是否相等 相等为True,否则为False def size(data): ''' 返回数据集的数据大小 ''' return data[0].get_value(borrow=True).shape[0] def dropout_layer(layer,p_dropout): ''' 计算使用弃权该层的输出 layer:神经网络下一层输入值,也是该层输出值 p_dropout:弃权概率 ''' #print(layer.type) #分配一个RandomStreams随机流对象 srng = shared_randomstreams.RandomStreams( np.random.RandomState(0).randint(999999)) # np.random.RandomState(0).randint(999999)产生一个小于999999的任意整数 #二项分布 n=1,返回值为1的概率为p,随机生成size 0和1值 要随机地从n个数中以概率p对其进行选择,可以先生成一个掩膜(mask = np.random.binomial(1, p, n)) mask = srng.binomial(n=1,p=1-p_dropout,size=layer.shape) #对layer层中神经元按照p_dropout概率进行屏蔽,即把该神经元输出设置为0 # cast(x, dtype):象征性地投X到张量型D return layer*T.cast(mask,theano.config.floatX) class Network(object): ''' 定义神经网络的类 ''' def __init__(self,layers,mini_batch_size): ''' layers:list类型 神经网络的结构 如[ConvPoolLayer(image_shape = (mini_batch_size,1,28,28), filter_shape=(20,1,5,5), poolsize=(2,2)), FullyConnectedLayer(n_in=20*12*12,n_out=100), SoftmaxLayer=(n_in=100,n_out=10)] mini_batch_size:小批量数据的大小 Takes a list of `layers`, describing the network architecture, and a value for the `mini_batch_size` to be used during training by stochastic gradient descent. ''' self.layers = layers self.mini_batch_size = mini_batch_size #遍历权重和偏置参数 self.params = [param for layer in self.layers for param in layer.params] #声明变量x,x是一个矩阵 self.x = T.matrix('x') #声明变量y,y是一个int类型的向量 self.y = T.ivector('y') #初始化所有层 编写每一层的输出函数方程式 也就是把上一层输出和下一层输入连接起来 init_layer = self.layers[0] init_layer.set_inpt(self.x,self.x,self.mini_batch_size) for j in range(1,len(self.layers)): prev_layer,layer = self.layers[j-1],self.layers[j] layer.set_inpt( prev_layer.output,prev_layer.output_dropout,self.mini_batch_size) self.output = self.layers[-1].output self.output_dropout = self.layers[-1].output_dropout def SGD(self,training_data,epochs,mini_batch_size,eta,validation_data,test_data,lamda=0.0): ''' 随机梯度下降算法:使用小批量训练样本来计算梯度(计算随机选取的小批量数据的梯度来估计整体的梯度) training_data:元素为(x,y)元组的列表 (x,y):表示训练输入以及对应的输出类别 这里的输出类别是二值化后的10*1维向量 epochs:迭代期数量 即迭代次数 mini_batch:小批量数据的大小 eta:学习率 lamda:规范化lamda参数 evaluation_data:评估数据集 validation_data test_data:测试数据集 test_data ''' #把数据集分成 实例样本集合和对应的类别集合 training_x,training_y = training_data validation_x,validation_y = validation_data test_x,test_y = test_data #计算按mini_batch_size大小划分,数据集可以划分为几组 num_training_batches = int(size(training_data)/mini_batch_size) num_validation_batches = int(size(validation_data)/mini_batch_size) num_test_batches = int(size(test_data)/mini_batch_size) #定义(规范化)代价函数,并对代价函数求梯度,然后计算新的更新后的函数表达式 l2_norm_squared = sum([(layer.w**2).sum() for layer in self.layers]) #计算所有权重元素的平方和 #定义规范化代价函数方程式 cost = self.layers[-1].cost(self) + 0.5*lamda*l2_norm_squared/mini_batch_size grads = T.grad(cost,self.params) updates = [(param,param - eta*grad) for param,grad in zip(self.params,grads)] #定义函数方程式用来训练小批量数据,计算在validation_data,和test_data数据集上的准确率 i = T.lscalar() #定义int64类型变量 小批量数据索引 #一次传入mini_batch_size大小的实例,并计算小批量样本的带价值和梯度值,并更新权重和偏置 train_mb = theano.function( [i],cost,updates=updates, givens={ self.x: #training_x[i*self.mini_batch_size:(i+1)*self.mini_batch_size]替代self.x training_x[i*self.mini_batch_size:(i+1)*self.mini_batch_size], self.y: training_y[i*self.mini_batch_size:(i+1)*self.mini_batch_size] }) #一次传入mini_batch_size大小的实例 计算validation_data数据的准确率 validate_mb_accuracy = theano.function( [i],self.layers[-1].accuracy(self.y), givens={ self.x: validation_x[i*self.mini_batch_size:(i+1)*self.mini_batch_size], self.y: validation_y[i*self.mini_batch_size:(i+1)*self.mini_batch_size] }) #一次传入mini_batch_size大小的实例 计算test_data数据的准确率 test_mb_accuracy = theano.function( [i],self.layers[-1].accuracy(self.y), givens={ self.x: test_x[i*self.mini_batch_size:(i+1)*self.mini_batch_size], self.y: test_y[i*self.mini_batch_size:(i+1)*self.mini_batch_size] }) ##一次传入mini_batch_size大小的实例 计算test_data数据的预测类别 self.test_mb_predictions = theano.function( [i],self.layers[-1].y_out, givens={ self.x: test_x[i*self.mini_batch_size:(i+1)*self.mini_batch_size] }) #开始训练 传入参数 best_validation_accuracy = 0.0 #循环迭代周期,每个周期迭代完一轮training_data数据 for epoch in range(epochs): #把训练集数据按照mini_batch-size划分为num_training_batches组,迭代每一组数据 for minibatch_index in range(num_training_batches): #当前组数 iteration = num_training_batches*epoch + minibatch_index #迭代组数为1000的倍数 if iteration % 1000 == 0: print('Training mini_batch number {0}'.format(iteration)) #计算每一组mini_batch数据的代价值 cost_ij = train_mb(minibatch_index) #一个周期数据迭代完成(即一轮迭代),并对校验数据进行测试,记录准确率最高时的迭代次数,并计算在该参数下(w,b),测试数据的准确率为多少 if (iteration+1) % num_training_batches == 0: #计算validation_data数据的平均准确率(计算每一组的准确率求平均) validation_accuracy = np.mean( [validate_mb_accuracy(j) for j in range(num_validation_batches)]) print('Epoch {0}:validation accuracy {1:.2%}'.format(epoch,validation_accuracy)) #更新最大的校验准确率,并记录准确率最高的迭代次数,并计算在该参数下(w,b),测试数据的准确率为多少 if validation_accuracy >= best_validation_accuracy: print('This is the best validation accuracy to date.') best_validation_accuracy = validation_accuracy best_iteration = iteration #计算test_data数据的平均准确率(计算每一组的准确率求平均) if test_data: test_accuracy = np.mean( [test_mb_accuracy(j) for j in range(num_test_batches)]) print('The corresponding test accuracy is {0:.2%}'.format(test_accuracy)) print('Finished training network.') print('Best validation accuracy of {0:.2%} obtained at iteraion {1}'.format( best_validation_accuracy,best_iteration)) print('Corresponding test accuracy of {0:.2%}'.format(test_accuracy)) #开始测试 training_data,validation_data,test_data = load_data_shared() mini_batch_size = 10 net = Network([ ConvPoolLayer(image_shape=(mini_batch_size,1,28,28), #image_shape:小批量数据大小为mini_batch_size,图像1*28*28 filter_shape=(20,1,5,5), #filter_shape:滤波器个数为20,共享权重为1*5*5 poolsize=(2,2), #poolsize:池化层参数为2*2 activation_fn=ReLU), #经过卷积-池化层输出的是 20*12*12的图像 ConvPoolLayer(image_shape=(mini_batch_size,20,12,12), #image_shape:小批量数据大小为mini_batch_size,图像20*12*12 filter_shape=(40,20,5,5), #filter_shape:滤波器个数为40,共享权重为20*5*5 poolsize=(2,2), #poolsize:池化层参数为2*2 activation_fn=ReLU), #经过卷积-池化层输出的是40*4*4的图像 FullyConnectedLayer(n_in=40*4*4,n_out=1000,activation_fn=ReLU,p_dropout=0.5), FullyConnectedLayer(n_in=1000,n_out=100,activation_fn=ReLU,p_dropout=0.5), SoftmaxLayer(n_in=100,n_out=10) ],mini_batch_size) net.SGD(training_data,40,mini_batch_size,0.015,validation_data,test_data)