• 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

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

  • 相关阅读:
    C# List<T>用法(转)
    任务列表 (Visual Studio)
    TSQL行转列、列转行
    HRESULT:0x80070057 (E_INVALIDARG)的异常的解决方案(转)
    JS正则表达式详解(转)
    Windows远程登录命令
    JavaScript中的try...catch和异常处理(转)
    javascript如何触发button 的click事件
    在ASP.NET中利JavaScript实现控件的聚焦(转)
    输入一个字符串,将其逆序后输出。(使用C++,不建议用伪码)
  • 原文地址:https://www.cnblogs.com/xueliangliu/p/3127197.html
Copyright © 2020-2023  润新知