• theano学习指南4(翻译) 卷积神经网络


    动机

    卷积神经网络是一种特殊的MLP,这个概念是从生物里面演化过来的. 根据Hubel和Wiesel早期在猫的视觉皮层上的工作 [Hubel68], 我们知道在视觉皮层上面存在一种细胞的复杂分布,这些细胞对一些局部输入是很敏感的,它们被成为感知野, 并通过这种特殊的组合方式来覆盖整个视野. 这些过滤器对输入空间是局部敏感的,因此能够更好得发觉自然图像中不同物体的空间相关性.

    进一步讲, 视觉皮层存在两类不同的细胞,简单细胞S和复杂细胞C. 简单细胞尽可能得可视野中特殊的类似边缘这种结构进行相应.复杂细胞具有更大的感知范围,它们可以对刺激的空间位置进行精确的定位.

    作为已知的最强大的视觉系统,视觉皮层也成为了科学研究的对象. 很多神经科学中提出的模型,都是基于对其进行的研究,比如, NeoCognitron [Fukushima], HMAX [Serre07] 以及本文讨论的重点 LeNet-5 [LeCun98]

    稀疏连接性

    CNN通过增强相邻两层中神经元的局部的连接来发掘局部空间相关性. m层的隐输入单元和m-1层的一部分空间相邻,并具有连续可视野的神经元相连接. 它们的关系如下图所示:

     _images/sparse_1D_nn.png
    我们可以假设m-1层为输入视网膜, 在它之上,m层的视觉神经元具有宽度为3的可视野,因此一个单元可以连接视网膜层的三个相邻的神经元. m层的神经元和m-1层具有类似的连接属性. 因此m+1层的神经元对于m层,仍具有宽度为3的可视野,但是相对于m-1层,可视野的宽度更大(结果为5). 这种结构把训练好的过滤器构建成一种局部空间模式. 如上图所示, 过滤器由多个感知层堆积而成,它变得更加地全局. 比如,m+1层的一个神经元可以对m-1层的宽度为5的特征进行编码.

    共享权重

    在CNN中,每一个稀疏的过滤器$h_i$在整个可视野上是叠加的重复的. 这些重复的单元形成了一种特征图,它可以共享相同的参数,比如共同的权向量和偏差.

    _images/conv_1D_nn.png

    上图中, 属于同一个的特征图的三个隐单元,因为需要共享相同颜色的权重, 他们的被限制成相同的. 梯度下降算法,在进行了一个轻微的改动之后, 仍然可以用来学习这些共享的参数.  共享权重的梯度可以对共享参数的梯度进行简单的求和得到.

    为什么要对共享权重感兴趣呢? 在这种方式中,重复单元可以检测特征,无论他们在可视野中的位置在什么地方. 而权重的共享为此提供了一种非常有效的方法, 因为这样做可以在很大程度上减少需要学习的参数. 通过控制模型的容量,CNN在视觉问题上达到了更好的泛化.

    具体细节

    从概念上讲,特征图通过对输入图像在一个线性滤波器上的卷积运算,增加一个便宜量,在结果上作用一个非线性函数得到.如果我们把某层的第k个的特征图记为$h^k$,其过滤器由权重$W$和偏移量$b_k$决定, 那么,特征图可以通过下面的函数得到:

    $$h^k_{ij} = \tanh ( (W^k * x)_{ij} + b_k ).$$

    为了更好的表达数据, 隐层由一系列的多个特征图构成${h^{(k)}, k= 0 .. K}$. 其权重$W$由四个参数决定: 目标特征图的索引,源特征图的索引,源水平位置索引和源垂直位置索引. 偏移量为一个向量,其中每一个元素对应目标特征图的一个索引. 其逻辑关系通过下图表示: 

    _images/cnn_explained.png

    Figure 1: 卷积层实例 (这个图和下面的说明有点冲突,下面的特征权重表示成了$W^0$,$W^1$,图中是 $W^1$,$W^2$)

    这里是一个两层的CNN,它有 m-1层的四个特征图和m层的两个特征图($h^0, h^1$)构成. 神经元在$h^0$和$h^1$的输出(蓝色和红色的框所示)是由m-1层落入其相应的2*2的可视野的像素计算得到, 这里需要注意可视野如何地跨四个特征图.其权重为3D张量,分别表示了输入特征图的索引,以及像素的坐标.

    整合以上概念, $W_{ij}^{kl}$表示了连接m层第k个特征图的特征图上每一个像素的权重, 像素为m-1层的第l个特征图,其位置为 $(i,j)$. 

    ConvOp

    Convop是Theano中实现卷积的函数, 它主要重复了scipy工具包中signal.convolve2d的函数功能. 总的来讲,ConvOp包含两个参数:

    • 对应输入图像的mini-batch的4D张量. 其每个张量的大小为:[mini-batch的大小, 输入的特征图的数量, 图像的高度,图像的宽度]
    • 对应权重矩阵$W$的4D张量,其每个张量的大小为:[m层的特征图的数量,m-1层的特征图的数量,过滤器的高度,过滤器的宽度].

    下面的代码实现了一个类似图1里面的卷积层. 输入图像包括大小为120*160的三个特征图(对应RGB). 我们可以用两个具有9*9的可视野的卷积过滤器.

    from theano.tensor.nnet import conv
    rng = numpy.random.RandomState(23455)
    
    # instantiate 4D tensor for input
    input = T.tensor4(name='input')
    
    # initialize shared variable for weights.
    w_shp = (2, 3, 9, 9)
    w_bound = numpy.sqrt(3 * 9 * 9)
    W = theano.shared( numpy.asarray(
                rng.uniform(
                    low=-1.0 / w_bound,
                    high=1.0 / w_bound,
                    size=w_shp),
                dtype=input.dtype), name ='W')
    
    # initialize shared variable for bias (1D tensor) with random values
    # IMPORTANT: biases are usually initialized to zero. However in this
    # particular application, we simply apply the convolutional layer to
    # an image without learning the parameters. We therefore initialize
    # them to random values to "simulate" learning.
    b_shp = (2,)
    b = theano.shared(numpy.asarray(
                rng.uniform(low=-.5, high=.5, size=b_shp),
                dtype=input.dtype), name ='b')
    
    # build symbolic expression that computes the convolution of input with filters in w
    conv_out = conv.conv2d(input, W)
    
    # build symbolic expression to add bias and apply activation function, i.e. produce neural net layer output
    # A few words on ``dimshuffle`` :
    #   ``dimshuffle`` is a powerful tool in reshaping a tensor;
    #   what it allows you to do is to shuffle dimension around
    #   but also to insert new ones along which the tensor will be
    #   broadcastable;
    #   dimshuffle('x', 2, 'x', 0, 1)
    #   This will work on 3d tensors with no broadcastable
    #   dimensions. The first dimension will be broadcastable,
    #   then we will have the third dimension of the input tensor as
    #   the second of the resulting tensor, etc. If the tensor has
    #   shape (20, 30, 40), the resulting tensor will have dimensions
    #   (1, 40, 1, 20, 30). (AxBxC tensor is mapped to 1xCx1xAxB tensor)
    #   More examples:
    #    dimshuffle('x') -> make a 0d (scalar) into a 1d vector
    #    dimshuffle(0, 1) -> identity
    #    dimshuffle(1, 0) -> inverts the first and second dimensions
    #    dimshuffle('x', 0) -> make a row out of a 1d vector (N to 1xN)
    #    dimshuffle(0, 'x') -> make a column out of a 1d vector (N to Nx1)
    #    dimshuffle(2, 0, 1) -> AxBxC to CxAxB
    #    dimshuffle(0, 'x', 1) -> AxB to Ax1xB
    #    dimshuffle(1, 'x', 0) -> AxB to Bx1xA
    output = T.nnet.sigmoid(conv_out + b.dimshuffle('x', 0, 'x', 'x'))
    
    # create theano function to compute filtered images
    f = theano.function([input], output)

    首先我们用得到的函数f做点有意思的事情.

    import pylab
    from PIL import Image
    
    # open random image of dimensions 639x516
    img = Image.open(open('images/3wolfmoon.jpg'))
    img = numpy.asarray(img, dtype='float64') / 256.
    
    # put image in 4D tensor of shape (1, 3, height, width)
    img_ = img.swapaxes(0, 2).swapaxes(1, 2).reshape(1, 3, 639, 516)
    filtered_img = f(img_)
    
    # plot original image and first and second components of output
    pylab.subplot(1, 3, 1); pylab.axis('off'); pylab.imshow(img)
    pylab.gray();
    # recall that the convOp output (filtered image) is actually a "minibatch",
    # of size 1 here, so we take index 0 in the first dimension:
    pylab.subplot(1, 3, 2); pylab.axis('off'); pylab.imshow(filtered_img[0, 0, :, :])
    pylab.subplot(1, 3, 3); pylab.axis('off'); pylab.imshow(filtered_img[0, 1, :, :])
    pylab.show()

    运行代码,可以得到如下结果:

    _images/3wolfmoon_output.png

    我们可以注意到,随机初始化的滤波器能够产生边缘检测算子的作用。另外,我们用和MLP中相同的权重对公式进行初始化。这些权重是从均匀分布[-1/fan-in, 1/fan-in]随机采样得到的。这里 fan-in是输入层到隐层单元的数量。对于MLP来说,这正是下一层的单元的数目。而对于CNNs,我们需要考虑到输入特征图的数量,以及可视野的大小。

    共用最大化

    CNN的另外一个重要特征是共用最大化,这其实是一种非线性向下采样的方法。共用最大化把输入图像分割成不重叠的矩形,然后对于每个矩形区域,输出最大化的结果。

    这个技术在视觉上的好处主要有两个方面 (1)它降低了上层的计算复杂度 (2)它提供了一种变换不变量的。对于第二种益处,我们可以假设把一个共用最大化层和一个卷积层组合起来,对于单个像素,输入图像可以有8个方向的变换。如果共有最大层在2*2的窗口上面实现,这8个可能的配置中,有3个可以准确的产生和卷积层相同的结果。如果窗口变成3*3,则产生精确结果的概率变成了5/8.

    可见,共有最大化对位置信息提供了附加的鲁棒性,它以一种非常聪明的方式减少了中间表示的维度。

    在Theano中,这种技术通过函数 theano.tensor.signal.downsample.max_pool_2d 实现,这个函数的输入是一个N维张量(N>2), 和一个缩放因子来对这个张量进行共用最大化的变换。下面的例子说明了这个过程:

    from theano.tensor.signal import downsample
    
    input = T.dtensor4('input')
    maxpool_shape = (2, 2)
    pool_out = downsample.max_pool_2d(input, maxpool_shape, ignore_border=True)
    f = theano.function([input],pool_out)
    
    invals = numpy.random.RandomState(1).rand(3, 2, 5, 5)
    print 'With ignore_border set to True:'
    print 'invals[0, 0, :, :] =\n', invals[0, 0, :, :]
    print 'output[0, 0, :, :] =\n', f(invals)[0, 0, :, :]
    
    pool_out = downsample.max_pool_2d(input, maxpool_shape, ignore_border=False)
    f = theano.function([input],pool_out)
    print 'With ignore_border set to False:'
    print 'invals[1, 0, :, :] =\n ', invals[1, 0, :, :]
    print 'output[1, 0, :, :] =\n ', f(invals)[1, 0, :, :]
    

      这段代码的输出为类似下面的内容:

    With ignore_border set to True:
        invals[0, 0, :, :] =
        [[  4.17022005e-01   7.20324493e-01   1.14374817e-04   3.02332573e-01 1.46755891e-01]
         [  9.23385948e-02   1.86260211e-01   3.45560727e-01   3.96767474e-01 5.38816734e-01]
         [  4.19194514e-01   6.85219500e-01   2.04452250e-01   8.78117436e-01 2.73875932e-02]
         [  6.70467510e-01   4.17304802e-01   5.58689828e-01   1.40386939e-01 1.98101489e-01]
         [  8.00744569e-01   9.68261576e-01   3.13424178e-01   6.92322616e-01 8.76389152e-01]]
        output[0, 0, :, :] =
        [[ 0.72032449  0.39676747]
         [ 0.6852195   0.87811744]]
    
    With ignore_border set to False:
        invals[1, 0, :, :] =
        [[ 0.01936696  0.67883553  0.21162812  0.26554666  0.49157316]
         [ 0.05336255  0.57411761  0.14672857  0.58930554  0.69975836]
         [ 0.10233443  0.41405599  0.69440016  0.41417927  0.04995346]
         [ 0.53589641  0.66379465  0.51488911  0.94459476  0.58655504]
         [ 0.90340192  0.1374747   0.13927635  0.80739129  0.39767684]]
        output[1, 0, :, :] =
        [[ 0.67883553  0.58930554  0.69975836]
         [ 0.66379465  0.94459476  0.58655504]
         [ 0.90340192  0.80739129  0.39767684]]

    注意到和大部分代码不同的是,这个函数max_pool_2d 在创建Theano图的时候,需要一个向下采样的因子ds (长度为2的tuple变量,表示了图像的宽和高的缩放. 这个可能在以后的版本中升级。

    LeNet模型

    稀疏,卷积层和共有最大化是LeNet的核心概念。因为模型的细节会有很大的变换,我们用下面的图来诠释LeNet的模型。

    _images/mylenet.png

    模型的低层由卷积和共有最大化层组成,高层是全连接的一个MLP 神经网络,它包含了隐层和对数回归。高层的输入是下层特征图的结合。

    从实现的角度讲,这意味着低层操作了4D的张量,这个张量被压缩到了一个2D矩阵表示的光栅化的特征图上,以便于和前面的MLP的实现兼容。

    综合所有

    现在我们有了实现LeNet模型的所有细节,我们创建一个LeNetConvPoolLayer类,用了表示一个卷积和共有最大化层:

    class LeNetConvPoolLayer(object):
    
        def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
            """
            Allocate a LeNetConvPoolLayer with shared variable internal parameters.
    
            :type rng: numpy.random.RandomState
            :param rng: a random number generator used to initialize weights
    
            :type input: theano.tensor.dtensor4
            :param input: symbolic image tensor, of shape image_shape
    
            :type filter_shape: tuple or list of length 4
            :param filter_shape: (number of filters, num input feature maps,
                                  filter height,filter width)
    
            :type image_shape: tuple or list of length 4
            :param image_shape: (batch size, num input feature maps,
                                 image height, image width)
    
            :type poolsize: tuple or list of length 2
            :param poolsize: the downsampling (pooling) factor (#rows,#cols)
            """
            assert image_shape[1] == filter_shape[1]
            self.input = input
    
            # initialize weight values: the fan-in of each hidden neuron is
            # restricted by the size of the receptive fields.
            fan_in =  numpy.prod(filter_shape[1:])
            W_values = numpy.asarray(rng.uniform(
                  low=-numpy.sqrt(3./fan_in),
                  high=numpy.sqrt(3./fan_in),
                  size=filter_shape), dtype=theano.config.floatX)
            self.W = theano.shared(value=W_values, name='W')
    
            # the bias is a 1D tensor -- one bias per output feature map
            b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
            self.b = theano.shared(value=b_values, name='b')
    
            # convolve input feature maps with filters
            conv_out = conv.conv2d(input, self.W,
                    filter_shape=filter_shape, image_shape=image_shape)
    
            # downsample each feature map individually, using maxpooling
            pooled_out = downsample.max_pool_2d(conv_out, 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'))
    
            # store parameters of this layer
            self.params = [self.W, self.b]

    应该注意的是,在初始化权重的时候,fan-in是由感知野的大小和输入特征图的数目决定的。

    最后,采用前面章节定义的LogisticRegression和HiddenLayer类,LeNet就可以工作了。

    class LeNetConvPoolLayer(object):
    
        def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
            """
            Allocate a LeNetConvPoolLayer with shared variable internal parameters.
    
            :type rng: numpy.random.RandomState
            :param rng: a random number generator used to initialize weights
    
            :type input: theano.tensor.dtensor4
            :param input: symbolic image tensor, of shape image_shape
    
            :type filter_shape: tuple or list of length 4
            :param filter_shape: (number of filters, num input feature maps,
                                  filter height,filter width)
    
            :type image_shape: tuple or list of length 4
            :param image_shape: (batch size, num input feature maps,
                                 image height, image width)
    
            :type poolsize: tuple or list of length 2
            :param poolsize: the downsampling (pooling) factor (#rows,#cols)
            """
            assert image_shape[1] == filter_shape[1]
            self.input = input
    
            # initialize weight values: the fan-in of each hidden neuron is
            # restricted by the size of the receptive fields.
            fan_in =  numpy.prod(filter_shape[1:])
            W_values = numpy.asarray(rng.uniform(
                  low=-numpy.sqrt(3./fan_in),
                  high=numpy.sqrt(3./fan_in),
                  size=filter_shape), dtype=theano.config.floatX)
            self.W = theano.shared(value=W_values, name='W')
    
            # the bias is a 1D tensor -- one bias per output feature map
            b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
            self.b = theano.shared(value=b_values, name='b')
    
            # convolve input feature maps with filters
            conv_out = conv.conv2d(input, self.W,
                    filter_shape=filter_shape, image_shape=image_shape)
    
            # downsample each feature map individually, using maxpooling
            pooled_out = downsample.max_pool_2d(conv_out, 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'))
    
            # store parameters of this layer
            self.params = [self.W, self.b]

    这里我们忽略了具体的训练和提前结束的代码,这些代码和前面MLP里面的是完全一样的。感兴趣的读者可以查阅DeeplearningTutoirals下面code目录的代码。

    运行算法

    算法运行很简单,通过一个命令:

    python code/convolutional_mlp.py

    下面的结果为在i7-2600K CPU的机器上面,采用默认参数和‘floatX=float32’的输出

    Optimization complete.
    Best validation score of 0.910000 % obtained at iteration 17800,with test
    performance 0.920000 %
    The code for file convolutional_mlp.py ran for 380.28m

    在GeForce GTX 285的平台上面,结果略有不同

    Optimization complete.
    Best validation score of 0.910000 % obtained at iteration 15500,with test
    performance 0.930000 %
    The code for file convolutional_mlp.py ran for 46.76m

    结果中的细小差别来自于不同硬件下不同的圆整机制,这些差别可以忽略。

  • 相关阅读:
    通用权限管理设计 之 数据库结构设计
    jQuery LigerUI 插件介绍及使用之ligerDateEditor
    jQuery LigerUI 插件介绍及使用之ligerTree
    jQuery LigerUI V1.01(包括API和全部源码) 发布
    jQuery liger ui ligerGrid 打造通用的分页排序查询表格(提供下载)
    jQuery LigerUI V1.1.5 (包括API和全部源码) 发布
    jQuery LigerUI 使用教程表格篇(1)
    jQuery LigerUI V1.0(包括API和全部源码) 发布
    jQuery LigerUI V1.1.0 (包括API和全部源码) 发布
    nginx keepalived
  • 原文地址:https://www.cnblogs.com/xueliangliu/p/3127197.html
Copyright © 2020-2023  润新知