卷积神经网络全面解析代码详解
本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自Convolutional Neural Networks (LeNet)
一、CNN卷积神经网络原理简介
要讲明白卷积神经网络,估计得长篇大论,网上有很多博文已经写得很好了,所以本文就不重复了,如果你了解CNN,那可以往下看,本文主要是详细地解读CNN的实现代码。
CNN的最大特点就是稀疏连接(局部感受)和权值共享,如下面两图所示,左为稀疏连接,右为权值共享。稀疏连接和权值共享可以减少所要训练的参数,减少计算复杂度。
       
至于CNN的结构,以经典的LeNet5来说明:
这个图真是无处不在,一谈CNN,必说LeNet5,这图来自于这篇论文:Gradient-Based Learning Applied to Document Recognition,论文很长,第7页那里开始讲LeNet5这个结构,建议看看那部分。
我这里简单说一下,LeNet5这张图从左到右,先是input,这是输入层,即输入的图片。input-layer到C1这部分就是一个卷积层(convolution运算),C1到S2是一个子采样层(pooling运算),关于卷积和子采样的具体过程可以参考下图:
然后,S2到C3又是卷积,C3到S4又是子采样,可以发现,卷积和子采样都是成对出现的,卷积后面一般跟着子采样。S4到C5之间是全连接的,这就相当于一个MLP的隐含层了(如果你不清楚MLP,参考《DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解》)。C5到F6同样是全连接,也是相当于一个MLP的隐含层。最后从F6到输出output,其实就是一个分类器,这一层就叫分类层。
ok,CNN的基本结构大概就是这样,由输入、卷积层、子采样层、全连接层、分类层、输出这些基本“构件”组成,一般根据具体的应用或者问题,去确定要多少卷积层和子采样层、采用什么分类器。当确定好了结构以后,如何求解层与层之间的连接参数?一般采用向前传播(FP)+向后传播(BP)的方法来训练。具体可参考上面给出的链接。
二、CNN卷积神经网络代码详细解读(基于python+theano)
代码来自于深度学习教程:Convolutional Neural Networks (LeNet)这个代码实现的是一个简化了的LeNet5,具体如下:
没有实现location-specific gain and bias parameters
用的是maxpooling,而不是average_pooling
分类器用的是softmax,LeNet5用的是rbf
LeNet5第二层并不是全连接的,本程序实现的是全连接
另外,代码里将卷积层和子采用层合在一起,定义为“LeNetConvPoolLayer“(卷积采样层),这好理解,因为它们总是成对出现。但是有个地方需要注意,代码中将卷积后的输出直接作为子采样层的输入,而没有加偏置b再通过sigmoid函数进行映射,即没有了下图中fx后面的bx以及sigmoid映射,也即直接由fx得到Cx。
最后,代码中第一个卷积层用的卷积核有20个,第二个卷积层用50个,而不是上面那张LeNet5图中所示的6个和16个。
了解了这些,下面看代码:
(1)导入必要的模块
import cPickle
import gzip
import os
import sys
import time
import numpy
import theano
sor as T
sor.signal import downsample
import conv
(2)定义CNN的基本"构件"
CNN的基本构件包括卷积采样层、隐含层、分类器,如下
定义LeNetConvPoolLayer(卷积+采样层)
见代码注释:
"""
卷积+下采样合成一个层LeNetConvPoolLayer
rng:随机数生成器,用于初始化W
input:4维的向量,sor.dtensor4
filter_shape:(number of filters, num input feature maps,filter height, filter width)
image_shape:(batch size, num input feature maps,image height, image width)
poolsize: (#rows, #cols)
"""
class LeNetConvPoolLayer(object):
    def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
#assert condition,condition为True,则继续往下执行,condition为False,中断程序
#image_shape[1]和filter_shape[1]都是num input feature maps,它们必须是一样的。
        assert image_shape[1] == filter_shape[1]
        self.input = input
#每个隐层神经元(即像素)与上一层的连接数为num input feature maps * filter height * filter width。
#可以用numpy.prod(filter_shape[1:])来求得
        fan_in = numpy.prod(filter_shape[1:])
#lower layer上每个神经元获得的梯度来自于:"num output feature maps * filter height * filter width" /pooling size
        fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) /
                  numpy.prod(poolsize))
#以上求得fan_in、fan_out ,将它们代入公式,以此来随机初始化W,W就是线性卷积核
        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),
                fig.floatX
            ),
            borrow=True
        )
numpy库不具有的功能有# the bias is a 1D tensor -- one bias per output feature map
#偏置b是一维向量,每个输出图的特征图都对应一个偏置,
#而输出的特征图的个数由filter个数决定,因此用filter_shape[0]即number of filters来初始化
        b_values = s((filter_shape[0],), fig.floatX)
        self.b = theano.shared(value=b_values, borrow=True)
#将输入图像与filter卷积,v2d函数
#卷积完没有加b再通过sigmoid,这里是一处简化。
        conv_out = v2d(
            input=input,
            filters=self.W,
            filter_shape=filter_shape,