卷积神经网络Lenet-5实现
原文地址:http://blog.csdn.net/hjimce/article/details/47323463
作者:hjimce
卷积神经网络算法是n年前就有的算法,仅仅是近年来由于深度学习相关算法为多层网络的训练提供了新方法,然后如今电脑的计算能力已非当年的那种计算水平,同一时候如今的训练数据非常多,于是神经网络的相关算法又又一次火了起来,因此卷积神经网络就又活了起来,再開始前,我们须要明白的是网上讲的卷积神经网络的相关教程一般指的是神经网络的前向传导过程。反向传播都是用梯度下降法进行训练。
一、理论阶段
解说这个算法,没有打算啰嗦太多的东西,由于什么权值共享、局部感受野什么的,讲那么多。都是那些生物学的相关理论。
卷积神经网络的相关博文也是一大堆。可是讲的。基本上都是抄过来抄过去,就像我之前不理解从S2层到C3层是怎么实现的,网上看了一大堆教程,没有一个解答这个问题的。我的个人感觉整个过程,就仅仅有S2到C3是最难理解的。接着我将结合我的理解进行解说。
1、卷积
卷积的概念这个我想仅仅要学过图像处理的人都懂的概念了,这个不解释。
我们知道对于给定的一幅图像来说。给定一个卷积核,卷积就是依据卷积窗体,进行像素的加权求和。
卷积神经网络与我们之前所学到的图像的卷积的差别,我的理解是:我们之前学图像处理遇到卷积。一般来说。这个卷积核是已知的。比方各种边缘检測算子、高斯模糊等这些。都是已经知道卷积核。然后再与图像进行卷积运算。然而深度学习中的卷积神经网络卷积核是未知的,我们训练一个神经网络,就是要训练得出这些卷积核,而这些卷积核就相当于我们学单层感知器的时候的那些參数W,因此你能够把这些待学习的卷积核看成是神经网络的训练參数W。
2、池化
刚開始学习CNN的时候,看到这个词。好像高大上的样子,于是查了非常多资料。理论一大堆。可是实践、算法实现却都没讲到,也不懂池化要怎么实现?事实上所谓的池化。就是图片下採样。这个时候,你会发现CNN每一层的构建跟图像高斯金字塔的构建有点类似。因此你假设已经懂得了图像金字塔融合的相关算法,那么就变的easy理解了。在高斯金子塔构建中。每一层通过卷积。然后卷积后进行下採样。而CNN也是相同的过程。废话不多说。这里就讲一下,CNN的池化:
CNN的池化(图像下採样)方法非常多:Mean pooling(均值採样)、Max pooling(最大值採样)、Overlapping (重叠採样)、L2 pooling(均方採样)、Local Contrast Normalization(归一化採样)、Stochasticpooling(随即採样)、Def-pooling(形变约束採样)。
当中最经典的是最大池化,因此我就解释一下最大池化的实现:
原图片
为了简单起见,我用上面的图片作为样例,如果上面的图片大小是4*4的,如上图所看到的,然后图片中每一个像素点的值是上面各个格子中的数值。
然后我要对这张4*4的图片进行池化,池化的大小为(2,2)。跨步为2,那么採用最大池化也就是对上面4*4的图片进行分块,每一个块的大小为2*2,然后统计每一个块的最大值。作为下採样后图片的像素值。详细计算例如以下图所看到的:
也就是说我们最后得到下採样后的图片为:
这就是所谓的最大池化。当然以后你还会遇到各种池化方法。比方均值池化。也就是对每一个块求取平均值作为下採样的新像素值。还有重叠採样的池化。我上面这个样例是没有重叠的採样的,也就是每一个块之间没有相互重叠的部分。上面我说的跨步为2,就是为了使得分块都非重叠,等等。这些以后再跟大家解释池化经常用法。这里就先记住最大池化就好了,由于这个眼下是最经常使用的。
3、feature maps
这个单词国人把它翻译成特征图。挺起来非常专业的名词。
那么什么叫特征图呢?事实上一张图片经过一个卷积核进行卷积运算。我们能够得到一张卷积后的结果图片,而这张图片就是特征图。在CNN中,我们要训练的卷积核并非只唯独一个。这些卷积核用于提取特征,卷积核个数越多。提取的特征越多,理论上来说精度也会更高。然而卷积核一堆,意味着我们要训练的參数的个数越多。在LeNet-5经典结构中,第一层卷积核选择了6个,而在AlexNet中,第一层卷积核就选择了96个,详细多少个合适,还有待学习。
回到特征图概念,CNN的每个卷积层我们都要人为的选取合适的卷积核个数。及卷积核大小。
每个卷积核与图片进行卷积。就能够得到一张特征图了,比方LeNet-5经典结构中,第一层卷积核选择了6个,我们能够得到6个特征图。这些特征图也就是下一层网络的输入了。我们也能够把输入图片看成一张特征图。作为第一层网络的输入。
4、CNN的经典结构
对于刚入门CNN的人来说。我们首先须要如今的一些经典结构:
(1)LeNet-5。这个是n多年前就有的一个CNN的经典结构,主要是用于手写字体的识别,也是刚入门须要学习熟悉的一个网络,我的这篇博文主要就是要讲这个网络
(2)AlexNet。
在imagenet上的图像分类challenge上大神Alex提出的alexnet网络结构模型赢得了2012届的冠军,振奋人心。利用CNN实现了图片分类,别人用传统的神经网络调參跳到半死也就那样,Alex利用CNN精度远超传统的网络。
其他的还有什么《Network In Network》,GoogLeNet、Deconvolution Network,在以后的学习中我们会遇到。比方利用Deconvolution Network反卷积网络实现图片的去模糊。牛逼哄哄。
OK。理论阶段就啰嗦到这里就好了,接着就解说 LeNet-5, LeNet-5是用于手写字体的识别的一个经典CNN:
LeNet-5结构
输入:32*32的手写字体图片,这些手写字体包括0~9数字,也就是相当于10个类别的图片
输出:分类结果。0~9之间的一个数
因此我们能够知道。这是一个多分类问题,总共同拥有十个类。因此神经网络的最后输出层必定是SoftMax问题,然后神经元的个数是10个。LeNet-5结构:
输入层:32*32的图片。也就是相当于1024个神经元
C1层:paper作者,选择6个特征卷积核,然后卷积核大小选择5*5,这样我们能够得到6个特征图,然后每一个特征图的大小为32-5+1=28,也就是神经元的个数由1024减小到了28*28=784。
S2层:这就是下採样层,也就是使用最大池化进行下採样,池化的size,选择(2,2)。也就是相当于对C1层28*28的图片,进行分块,每一个块的大小为2*2,这样我们能够得到14*14个块,然后我们统计每一个块中,最大的值作为下採样的新像素,因此我们能够得到S1结果为:14*14大小的图片。共同拥有6个这种图片。
C3层:卷积层,这一层我们选择卷积核的大小依然为5*5,据此我们能够得到新的图片大小为14-5+1=10,然后我们希望能够得到16张特征图。那么问题来了?这一层是最难理解的,我们知道S2包括:6张14*14大小的图片,我们希望这一层得到的结果是:16张10*10的图片。这16张图片的每一张,是通过S2的6张图片进行加权组合得到的。详细是怎么组合的呢?问题例如以下图所看到的:
为了解释这个问题,我们先从简单的開始。我如今如果输入6特征图的大小是5*5的,分别用6个5*5的卷积核进行卷积。得到6个卷积结果图片大小为1*1。例如以下图所看到的:
为了简便起见,我这里先做一些标号的定义:我们如果输入第i个特征图的各个像素值为x1i,x2i……x25i,由于每一个特征图有25个像素。因此第I个特征图经过5*5的图片卷积后,得到的卷积结果图片的像素值Pi能够表示成:
这个是卷积公式。不解释。因此对于上面的P1~P6的计算方法,这个就是直接依据公式。然后我们把P1~P6相加起来,也就是:
P=P1+P2+……P6
把上面的Pi的计算公式,代入上式。那么我们能够得到:
P=WX
当中X就是输入的那6张5*5特征图片的各个像素点值,而W就是我们须要学习的參数,也就相当于6个5*5的卷积核。当然它包括着6*(5*5)个參数。因此我们的输出特征图就是:
Out=f(P+b)
这个就是从S2到C3的计算方法。当中b表示偏置项。f为激活函数。
我们回归到原来的问题:有6张输入14*14的特征图片。我们希望用5*5的卷积核,然后最后我们希望得到一张10*10的输出特征图片?
依据上面的过程,也就是事实上我们用5*5的卷积核去卷积每一张输入的特征图,当然每张特征图的卷积核參数是不一样的,也就是不共享。因此我们就相当于须要6*(5*5)个參数。对每一张输入特征图进行卷积后。我们得到6张10*10。新图片。这个时候,我们把这6张图片相加在一起,然后加一个偏置项b,然后用激活函数进行映射,就能够得到一张10*10的输出特征图了。
而我们希望得到16张10*10的输出特征图,因此我们就须要卷积參数个数为16*(6*(5*5))=16*6*(5*5)个參数。
总之,C3层每一个图片是通过S2图片进行卷积后。然后相加。而且加上偏置b,最后在进行激活函数映射得到的结果。
S4层:下採样层,比較简单。也是知己对C3的16张10*10的图片进行最大池化,池化块的大小为2*2。
因此最后S4层为16张大小为5*5的图片。
至此我们的神经元个数已经降低为:16*5*5=400。
C5层:我们继续用5*5的卷积核进行卷积,然后我们希望得到120个特征图。这样C5层图片的大小为5-5+1=1。也就是相当于1个神经元,120个特征图,因此最后仅仅剩下120个神经元了。这个时候,神经元的个数已经够少的了,后面我们就能够直接利用全连接神经网络,进行这120个神经元的兴许处理,后面详细要怎么搞,仅仅要懂多层感知器的都懂了,不解释。
上面的结构。仅仅是一种參考,在现实使用中。每一层特征图须要多少个,卷积核大小选择。还有池化的时候採样率要多少,等这些都是变化的,这就是所谓的CNN调參。我们须要学会灵活多变。
比方我们能够把上面的结构改为:C1层卷积核大小为7*7。然后把C3层卷积核大小改为3*3等,然后特征图的个数也是自己选,说不定得到手写字体识别的精度比上面那个还高,这也是有可能的,总之中的一个句话:须要学会灵活多变,须要学会CNN的调參。
二、实战阶段
1、训练数据获取
在theano学习库中有手写字体的库,能够从网上下载到,名为:mnist.pkl.gz的手写字体库。里面包括了三个部分的数据,训练数据集train_set:50000个训练样本,验证集valid_set,我们能够用例如以下的代码读取这些数据,然后用plot显示当中的一张图片:
- <span style="font-size:18px;">import cPickle
- import gzip
- import numpy as np
- import matplotlib.pyplot as plt
- f = gzip.open('mnist.pkl.gz', 'rb')
- train_set, valid_set, test_set = cPickle.load(f)
- f.close()
- tx,ty=train_set;
- #查看训练样本
- print np.shape(tx)#能够看到tx大小为(50000,28*28)的二维矩阵
- print np.shape(ty)#能够看到ty大小为(50000,1)的矩阵
- #图片显示
- A=tx[8].reshape(28,28)#第八个训练样本
- Y=ty[8]
- print Y
- plt.imshow(A,cmap='gray')#显示手写字体图片</span>
在上面的代码中我显示的是第8张图片。能够看到例如以下结果:
第八个样本是数字1。
2、LeNet-5实现
首先你要知道mnist.pkl.gz这个库给我们的图片的大小是28*28的,因此我们能够第一步选择5*5的卷积核进行卷积得到24*24,同一时候我们希望C1层得到20张特征图,等等,详细的代码实现例如以下;
- import os
- import sys
- import timeit
- import numpy
- import theano
- import theano.tensor as T
- from theano.tensor.signal import downsample
- from theano.tensor.nnet import conv
- from logistic_sgd import LogisticRegression, load_data
- from mlp import HiddenLayer
- #卷积神经网络的一层。包括:卷积+下採样两个步骤
- #算法的过程是:卷积-》下採样-》激活函数
- class LeNetConvPoolLayer(object):
- #image_shape是输入数据的相关參数设置 filter_shape本层的相关參数设置
- def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
- """
- :type rng: numpy.random.RandomState
- :param rng: a random number generator used to initialize weights
- 3、input: 输入特征图数据,也就是n幅特征图片
- 4、參数 filter_shape: (number of filters, num input feature maps,
- filter height, filter width)
- num of filters:是卷积核的个数。有多少个卷积核,那么本层的out feature maps的个数
- 也将生成多少个。num input feature maps:输入特征图的个数。
- 然后接着filter height, filter width是卷积核的宽高,比方5*5,9*9……
- filter_shape是列表。因此我们能够用filter_shape[0]获取卷积核个数
- 5、參数 image_shape: (batch size, num input feature maps,
- image height, image width),
- batch size:批量训练样本个数 。num input feature maps:输入特征图的个数
- image height, image width各自是输入的feature map图片的大小。
- image_shape是一个列表类型,所以能够直接用索引,訪问上面的4个參数,索引下标从
-
0~3。
比方image_shape[2]=image_heigth image_shape[3]=num input feature maps
- 6、參数 poolsize: 池化下採样的的块大小,一般为(2,2)
- """
- assert image_shape[1] == filter_shape[1]#推断输入特征图的个数是否一致,假设不一致是错误的
- self.input = input
- # fan_in=num input feature maps *filter height*filter width
- #numpy.prod(x)函数为计算x各个元素的乘积
- #也就是说fan_in就相当于每一个即将输出的feature map所须要链接參数权值的个数
- fan_in = numpy.prod(filter_shape[1:])
- # fan_out=num output feature maps * filter height * filter width
- fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) /
- numpy.prod(poolsize))
- # 把參数初始化到[-a,a]之间的数,当中a=sqrt(6./(fan_in + fan_out)),然后參数採用均匀採样
- #权值须要多少个?卷积核个数*输入特征图个数*卷积核宽*卷积核高?这样没有包括採样层的链接权值个数
- W_bound = numpy.sqrt(6. / (fan_in + fan_out))
- self.W = theano.shared(
- numpy.asarray(
- rng.uniform(low=-W_bound, high=W_bound, size=filter_shape),
- dtype=theano.config.floatX
- ),
- borrow=True
- )
- # b为偏置,是一维的向量。每一个输出特征图i相应一个偏置參数b[i]
- #,因此以下初始化b的个数就是特征图的个数filter_shape[0]
- b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
- self.b = theano.shared(value=b_values, borrow=True)
- # 卷积层操作。函数conv.conv2d的第一个參数为输入的特征图。第二个參数为随机出事化的卷积核參数
- #第三个參数为卷积核的相关属性,输入特征图的相关属性
- conv_out = conv.conv2d(
- input=input,
- filters=self.W,
- filter_shape=filter_shape,
- image_shape=image_shape
- )
- # 池化操作,最大池化
- pooled_out = downsample.max_pool_2d(
- input=conv_out,
- ds=poolsize,
- ignore_border=True
- )
- #激励函数,也就是说是先经过卷积核再池化后,然后在进行非线性映射
- # add the bias term. Since the bias is a vector (1D array), we first
- # reshape it to a tensor of shape (1, n_filters, 1, 1). Each bias will
- # thus be broadcasted across mini-batches and feature map
- # width & height
- self.output = T.tanh(pooled_out + self.b.dimshuffle('x', 0, 'x', 'x'))
- # 保存參数
- self.params = [self.W, self.b]
- self.input = input
- #測试函数
- def evaluate_lenet5(learning_rate=0.1, n_epochs=200,
- dataset='mnist.pkl.gz',
- nkerns=[20, 50], batch_size=500):
- """ Demonstrates lenet on MNIST dataset
- :learning_rate: 梯度下降法的学习率
- :n_epochs: 最大迭代次数
- :type dataset: string
- :param dataset: path to the dataset used for training /testing (MNIST here)
- :nkerns: 每一个卷积层的卷积核个数。第一层卷积核个数为 nkerns[0]=20,第二层卷积核个数
- 为50个
- """
- rng = numpy.random.RandomState(23455)
- datasets = load_data(dataset)#载入训练数据,训练数据包括三个部分
- train_set_x, train_set_y = datasets[0]#训练数据
- valid_set_x, valid_set_y = datasets[1]#验证数据
- test_set_x, test_set_y = datasets[2]#測试数据
- # 计算批量训练能够分多少批数据进行训练。这个仅仅要是知道批量训练的人都知道
- n_train_batches = train_set_x.get_value(borrow=True).shape[0]#训练数据个数
- n_valid_batches = valid_set_x.get_value(borrow=True).shape[0]
- n_test_batches = test_set_x.get_value(borrow=True).shape[0]
- n_train_batches /= batch_size#批数
- n_valid_batches /= batch_size
- n_test_batches /= batch_size
- # allocate symbolic variables for the data
- index = T.lscalar() # index to a [mini]batch
- # start-snippet-1
- x = T.matrix('x') # the data is presented as rasterized images
- y = T.ivector('y') # the labels are presented as 1D vector of
- # [int] labels
- # Reshape matrix of rasterized images of shape (batch_size, 28 * 28)
- # to a 4D tensor, compatible with our LeNetConvPoolLayer
- # (28, 28) is the size of MNIST images.
- layer0_input = x.reshape((batch_size, 1, 28, 28))
- '''''构建第一层网络:
- image_shape:输入大小为28*28的特征图,batch_size个训练数据。每一个训练数据有1个特征图
- filter_shape:卷积核个数为nkernes[0]=20。因此本层每一个训练样本即将生成20个特征图
- 经过卷积操作。图片大小变为(28-5+1 , 28-5+1) = (24, 24)
- 经过池化操作。图片大小变为 (24/2, 24/2) = (12, 12)
- 最后生成的本层image_shape为(batch_size, nkerns[0], 12, 12)'''
- layer0 = LeNetConvPoolLayer(
- rng,
- input=layer0_input,
- image_shape=(batch_size, 1, 28, 28),
- filter_shape=(nkerns[0], 1, 5, 5),
- poolsize=(2, 2)
- )
- '''''构建第二层网络:输入batch_size个训练图片,经过第一层的卷积后,每一个训练图片有nkernes[0]个特征图,每一个特征图
- 大小为12*12
- 经过卷积后,图片大小变为(12-5+1, 12-5+1) = (8, 8)
- 经过池化后,图片大小变为(8/2, 8/2) = (4, 4)
- 最后生成的本层的image_shape为(batch_size, nkerns[1], 4, 4)'''
- layer1 = LeNetConvPoolLayer(
- rng,
- input=layer0.output,
- image_shape=(batch_size, nkerns[0], 12, 12),
- filter_shape=(nkerns[1], nkerns[0], 5, 5),
- poolsize=(2, 2)
- )
- # the HiddenLayer being fully-connected, it operates on 2D matrices of
- # shape (batch_size, num_pixels) (i.e matrix of rasterized images).
- # This will generate a matrix of shape (batch_size, nkerns[1] * 4 * 4),
- # or (500, 50 * 4 * 4) = (500, 800) with the default values.
- layer2_input = layer1.output.flatten(2)
- '''''全链接:输入layer2_input是一个二维的矩阵,第一维表示样本,第二维表示上面经过卷积下採样后
- 每一个样本所得到的神经元,也就是每一个样本的特征。HiddenLayer类是一个单层网络结构
- 以下的layer2把神经元个数由800个压缩映射为500个'''
- layer2 = HiddenLayer(
- rng,
- input=layer2_input,
- n_in=nkerns[1] * 4 * 4,
- n_out=500,
- activation=T.tanh
- )
- # 最后一层:逻辑回归层分类判别。把500个神经元,压缩映射成10个神经元,分别相应于手写字体的0~9
- layer3 = LogisticRegression(input=layer2.output, n_in=500, n_out=10)
- # the cost we minimize during training is the NLL of the model
- cost = layer3.negative_log_likelihood(y)
- # create a function to compute the mistakes that are made by the model
- test_model = theano.function(
- [index],
- layer3.errors(y),
- givens={
- x: test_set_x[index * batch_size: (index + 1) * batch_size],
- y: test_set_y[index * batch_size: (index + 1) * batch_size]
- }
- )
- validate_model = theano.function(
- [index],
- layer3.errors(y),
- givens={
- x: valid_set_x[index * batch_size: (index + 1) * batch_size],
- y: valid_set_y[index * batch_size: (index + 1) * batch_size]
- }
- )
- #把全部的參数放在同一个列表里,可直接使用列表相加
- params = layer3.params + layer2.params + layer1.params + layer0.params
- #梯度求导
- grads = T.grad(cost, params)
- # train_model is a function that updates the model parameters by
- # SGD Since this model has many parameters, it would be tedious to
- # manually create an update rule for each model parameter. We thus
- # create the updates list by automatically looping over all
- # (params[i], grads[i]) pairs.
- updates = [
- (param_i, param_i - learning_rate * grad_i)
- for param_i, grad_i in zip(params, grads)
- ]
- train_model = theano.function(
- [index],
- cost,
- updates=updates,
- givens={
- x: train_set_x[index * batch_size: (index + 1) * batch_size],
- y: train_set_y[index * batch_size: (index + 1) * batch_size]
- }
- )
- # end-snippet-1
- ###############
- # TRAIN MODEL #
- ###############
- print '... training'
- # early-stopping parameters
- patience = 10000 # look as this many examples regardless
- patience_increase = 2 # wait this much longer when a new best is
- # found
- improvement_threshold = 0.995 # a relative improvement of this much is
- # considered significant
- validation_frequency = min(n_train_batches, patience / 2)
- # go through this many
- # minibatche before checking the network
- # on the validation set; in this case we
- # check every epoch
- best_validation_loss = numpy.inf
- best_iter = 0
- test_score = 0.
- start_time = timeit.default_timer()
- epoch = 0
- done_looping = False
- while (epoch < n_epochs) and (not done_looping):
- epoch = epoch + 1
- for minibatch_index in xrange(n_train_batches):#每一批训练数据
- cost_ij = train_model(minibatch_index)
- iter = (epoch - 1) * n_train_batches + minibatch_index
- if (iter + 1) % validation_frequency == 0:
- # compute zero-one loss on validation set
- validation_losses = [validate_model(i) for i
- in xrange(n_valid_batches)]
- this_validation_loss = numpy.mean(validation_losses)
- print('epoch %i, minibatch %i/%i, validation error %f %%' %
- (epoch, minibatch_index + 1, n_train_batches,
- this_validation_loss * 100.))
- # if we got the best validation score until now
- if this_validation_loss < best_validation_loss:
- #improve patience if loss improvement is good enough
- if this_validation_loss < best_validation_loss *
- improvement_threshold:
- patience = max(patience, iter * patience_increase)
- # save best validation score and iteration number
- best_validation_loss = this_validation_loss
- best_iter = iter
- # test it on the test set
- test_losses = [
- test_model(i)
- for i in xrange(n_test_batches)
- ]
- test_score = numpy.mean(test_losses)
- print((' epoch %i, minibatch %i/%i, test error of '
- 'best model %f %%') %
- (epoch, minibatch_index + 1, n_train_batches,
- test_score * 100.))
- if patience <= iter:
- done_looping = True
- break
- end_time = timeit.default_timer()
- print('Optimization complete.')
- print('Best validation score of %f %% obtained at iteration %i, '
- 'with test performance %f %%' %
- (best_validation_loss * 100., best_iter + 1, test_score * 100.))
- print >> sys.stderr, ('The code for file ' +
- os.path.split(__file__)[1] +
- ' ran for %.2fm' % ((end_time - start_time) / 60.))
- if __name__ == '__main__':
- evaluate_lenet5()
- def experiment(state, channel):
- evaluate_lenet5(state.learning_rate, dataset=state.dataset)
训练结果:
參考文献:
1、http://blog.csdn.net/zouxy09/article/details/8775360/
2、http://www.deeplearning.net/tutorial/lenet.html#lenet
**********************作者:hjimce 时间:2015.8.6 联系QQ:1393852684 地址:http://blog.csdn.net/hjimce 转载请保留本行信息********************