利用Theano理解深度学习——Convolutional Neural Networks

标签: 深度学习
4181人阅读 评论(0) 收藏 举报
分类:

注:本系列是基于参考文献中的内容,并对其进行整理,注释形成的一系列关于深度学习的基本理论与实践的材料,基本内容与参考文献保持一致,并对这个专题起名为“利用Theano理解深度学习”系列,若文中有任何问题欢迎咨询。本文提供PDF版本,欢迎索取。

“利用Theano理解深度学习”系列分为4个部分,其中第一部分主要包括:

  • 利用Theano理解深度学习——Logistic Regression
  • 利用Theano理解深度学习——Multilayer perceptron
  • 利用Theano理解深度学习——Deep Convolutional Network

一、CNN概述

卷积神经网络(Convolutional Neural Networks, CNN)是多层感知机MLP模型的一个变种,主要是受到生物学的启发。从Hubel和Wiesel早期的有关猫的视觉皮层的工作中我们知道猫的视觉皮层包含了一个复杂的细胞排列。这些细胞对小范围的视觉区域敏感,这样的小范围的视觉区域称为一个感受野(receptive field)。这些小的区域平铺开来可以盖满整个视觉区域。这些细胞就像是一个个的局部过滤器,可以对输入空间的信息进行过滤,并且非常适合在自然图像中发觉在空间上存在极强的局部相关性。

两种基本类型的细胞已经被确认:1、简单的细胞。这些细胞在他们的感受野中最大化的响应特定的类似边缘的模式;2、复杂的细胞。这些细胞拥有较大的感受野,而且对于确切的位置模式是局部不变的。

在现实世界中,动物的视觉皮层是最强大的视觉处理系统,似乎模仿其行为也变得很自然。因此,许多受神经系统启发的模型被发现,如NeoCognitron,HMAX和LeNet-5。

二、CNN的特点

卷积神经网络CNN的特点主要有:1、稀疏连接(局部感受),2、共享权值,3、子采样。其中,稀疏连接主要是通过对数据中的局部区域进行建模以发现局部的一些特性;共享权值的目的为了简化计算的过程,使得需要优化的参数变少;子采样的目的是在解决图像中的平移不变性,即所要识别的内容与其在图像中的具体位置无关。

1、稀疏连接(Sparse Connectivity)

CNN通过在邻接层的神经元之间使用局部连接模式来发现输入特征在空间上存在的局部相关性。如下图所示,第m层的隐层节点的输入是第m1层的神经单元的一个子集,这个子集中的神经单元在空间上是连续的,具有连续的感受野,如下图所示:

假设第m1层是输入层。在上图中,第m层的神经单元具有在输入层上宽度为3的感受野,因此只连接输入层上的3个邻接神经元。在m+1层的神经单元与第m层的神经元之间具有相似的连接特性,即第m+1层的神经单元在第m层上的感受野的宽度也是3,但是他们关于输入层m1层的感受野却变大了,如上图中为5。每一个单元对于感受野范围之外的其他的神经单元是没有反应的。这样的设计架构就保证了学习到的“过滤器”能够对局部空间上的输入产生最强烈的反应。

然而,正如上面所示,堆叠如此多的层导致这些非线性的“过滤器”变得急剧的“全局化”,例如,对更大的像素空间区域产生响应。在m+1个隐含层单元上可以对宽度为5的非线性特征进行编码。

2、共享权值(Shared Weights)

在CNN中,每一个过滤器hi在整个感受野中重复。这些重复的单元共享同样的参数(权重向量和偏置),然后形成一个特征映射。

在上图中有3个隐含层单元,这些隐含层单元都隶属于同样的特征映射。在上图中,对于同样颜色的特征映射的权重是共享的。梯度下降同样可以被应用于求解这些共享的参数,只是需要对原始的算法进行稍微的修改。共享的权重的梯度只是简单的将每一个共享的权重的梯度相加。

每一个单元重复这样的方法。此外,权值共享可以极大的缩减需要学习的参数的数量,这样就可以加速学习的速度。在模型上的这个约束可以使得CNN在视觉问题上可以获得更好的泛化能力。

3、池化(pooling)

在CNN中,另一个比较重要的概念是池化,在这里使用的是最大池化max-pooling,这是非线性下采样的一种形式。max-pooling将输入图像划分成为一系列的不重叠的正方形区域,然后对于每一个子区域,输出其中的最大值。

对于机器视觉而言,max-pooling的策略是非常有效的,主要有两个原因:

  1. 降低计算量。通过消除非最大值(选择最大值的操作),为上层降低了计算量。
  2. 解决了平移不变性。max-pooling提供了一种变换不变性的形式。假设在卷积层的后面串联一个max-pooling层,对于输入图像中的每一个像素点的位置,与其邻接的有8个方向可以变换,如果max-pooling采用的是2×2大小的正方形区域,在这8种可能的结构中,只要有3种就能产生与卷积层的输出一样的结果,如果在max-pooling层使用3×3大小的正方形区域,则只需要5种就能产生于卷积层的输出一样的结果。因为在max-pooling层对位置信息提供了额外的鲁棒性,max-pooling的方法是在中间表示的过程中降低维度的一种极其有效的方法。

三、CNN的详细说明和符号描述

特征映射的过程是通过对整个图像的局部区域重复应用一个函数得到的,即对于一个输入图像进行卷积操作指的是利用一个线性过滤器,加上一个偏置项,之后再利用一个非线性函数。

假设hk为某一层上的第k个特征映射,这个特征映射hk由权重Wk和偏置bk唯一决定,则特征映射hk可以表示为(其中,非线性映射为tanh):

hki,j=tanh((Wkx)ij+bk)

对于一维信号,有以下的卷积定义:

o[n]=f[n]g[n]=u=f[u]g[nu]=u=f[nu]g[u]

推广到二维的形式:

o[m,n]=f[m,n]g[m,n]=u=v=f[u,v]g[mu,nv]

为了能够对数据进行更丰富的表示,每一个隐含层都由多个特征映射组成,如{h(k),k=0,K}。隐含层的权重W可以被表示成一个4维的张量形式,这个4维的张量包含了目标特征映射,源特征映射,源垂直位置和源水平位置的组合。偏置b对每一个目标特征映射可以表示成一个包含一个元素的向量,卷积操作的过程可以通过下图表示:

上图是一个卷积神经网络中的两层。第m1层包含了4个特征映射,隐含层m包含了两个特征映射,即h0h1。在h0h1(图中的蓝色和红色的正方形)中的像素点(神经元的输出)是从第m1层的像素点计算得来的。感受野的大小为2×2。注意:感受野是如何旋转所有的输入特征映射。h0h1中的权重W0W1因此是3维的权重张量。主要的维表示的是输入特征映射,然而另外两个表示的是像素点的坐标。

将他们放在一起,Wklij表示的连接第m层的第k个特征映射的像素点和第m1层的第l个特征映射的坐标为(i,j)的像素点之间的权重。

四、LeNet

稀疏连接,卷积操作和最大池化是LeNet型的卷积神经网络模型的关键,然而,对于模型的具体细节会差别很大。下图展示的是LeNet模型:

下层主要是由卷积层和最大池化层交替形成的。上层是全连接的,对应为传统的MLP(即隐含层+Logistic回归)。第一个全连接层的输入是所有特征平铺后组成的,这些特征有下层映射得到。

从实现的角度来看,这就意味着哎下层操作的是是4维的张量,为了使得这些张量能够被后续的MLP处理,故徐将其按照指定的大小平铺称为2维的矩阵形式。

五、Tips和Tricks

1、超参数的选择

CNN的训练是比较棘手的问题,因为相比较于MLP来说,CNN比标准的MLP有更多的超参数。一个经验的法则是:对于学习率和正则化参数通常选择常数,这在优化CNN的过程中要记住。

2、卷积层的过滤器的个数

当需要确定每一层过滤器的个数的时候,记住计算单个卷积过滤器的激活函数时的代价要远比传统的MLP的代价要高。

假设l1层包含Kl1个特征映射,每个特征映射的输出大小为M×N,假设在第l层存在Kl个大小为m×n的过滤器,在计算特征映射时,需要的计算量为:

(Mm)×(Nn)×m×n×Kl1×Kl

然而对于标准的MLP,计算量为:Kl1×Kl

特征映射的大小会随着深度减少,越靠近输入层,卷积层的过滤器个数越少。

3、卷积层的过滤器的大小

对于卷积层的过滤器的大小,一般取决于具体的数据集。对于数据集MNIST,其大小为28×28,在该数据集上的最好的结果的过滤器大小为:第一层是5×5,然而对于自然图像,这些图像的像素点的维度要高于数据集MNIST的图像的大小,一般更趋向于在第一层取更大的过滤器大小,如12×12或者15×15

因此,对于卷积层的过滤器的大小的选择一般是根据给定的数据集中图像的大小而决定的。

4、池化层的最大池化的大小

在池化层,池化的目的是利用图像数据固有的特点,在计算的中间过程中降低数据的维度,一般比较常用的最大池化的大小为2×2或者不采用最大池化,即设置为0,对于特别大的输入图像,在低层,可采用4×4的池化大小,但是过大的池化大小会在计算过程中丢弃过多的信息,对模型的准确性会造成影响。

六、Theano实现CNN的代码解析

1、导入数据集

导入数据集的过程与Logistic回归中使用的一致。前面已经比较细致的描述 。代码如下:

def load_data(dataset):
'''导入数据
:type dataset: string
:param dataset: MNIST数据集
'''
    #1、处理文件目录
    data_dir, data_file = os.path.split(dataset)#把路径分割成dirname和basename,返回一个元组
    if data_dir == "" and not os.path.isfile(dataset):
        new_path = os.path.join(
                os.path.split(__file__)[0],#__file__表示的是当前的路径
                ".",
                "data",
                dataset
        )
    if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz':
        dataset = new_path#文件所在的目录

    if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':
        import urllib
        origin = (
                'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz'
    )
    print 'Downloading data from %s' % origin
    urllib.urlretrieve(origin, dataset)

    print '... loading data'
    #2、打开文件
    f = gzip.open(dataset, 'rb')# 打开一个gzip已经压缩好的gzip格式的文件,并返回一个文件对象:file object.
    train_set, valid_set, test_set = cPickle.load(f)#载入本地的文件
    f.close()
    '''训练集train_set,验证集valid_set和测试集test_set的格式:元组(input, target)
    其中,input是一个矩阵(numpy.ndarray),每一行代表一个样本;target是一个向量(numpy.ndarray),大小与input的行数对应
    '''

    def shared_dataset(data_xy, borrow=True):
        data_x, data_y = data_xy
        shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX), borrow=borrow)
        shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX), borrow=borrow)

        return shared_x, T.cast(shared_y, 'int32')#将shared_y转换成整型

    #3、将数据处理成需要的形式
    test_set_x, test_set_y = shared_dataset(test_set)
    valid_set_x, valid_set_y = shared_dataset(valid_set)
    train_set_x, train_set_y = shared_dataset(train_set)

    #4、返回数据集
    rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y), (test_set_x, test_set_y)]
    return rval

2、建立模型

建立模型阶段主要是定义一个卷积+池化的过程,具体代码如下:

class LeNetConvPoolLayer(object):
    def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
    """
    :type rng: numpy.random.RandomState
    :param rng:用于随机数生成权重 

    :type input: theano.tensor.dtensor4
    :param input:卷积+池化层的输入,是一个4维的张量

    :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: 池化的参数(#rows, #cols)
    """

    assert image_shape[1] == filter_shape[1]
    self.input = input

    #fan_in和fan_out主要用于初始化权重参数
    #卷积层的输入的大小 "num input feature maps * filter height * filter width"
    fan_in = numpy.prod(filter_shape[1:])
    #池化层的输出的大小 "num output feature maps * filter height * filter width" /pooling size
    fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) /
               numpy.prod(poolsize))

    #初始化权重
    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_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
    self.b = theano.shared(value=b_values, borrow=True)

    #定义卷积层
    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
    )

    #加上偏置的过程,最终计算非线性映射
    self.output = T.tanh(pooled_out + self.b.dimshuffle('x', 0, 'x', 'x'))

    #模型中的参数,权重和偏置
    self.params = [self.W, self.b]

    # keep track of model input
    self.input = input

在代码中,注意在卷积层的输出阶段只是将权重乘以输入,然后计算池化,在最终的结果上加上偏置,并进行非线性函数的映射,即利用tanh函数对其进行非线性的映射,这样的操作,主要是因为可以省去很多的计算。

3、训练模型

在模型的训练阶段,主要是将数据通过两个卷积+池化层,最终用过一个MLP计算其分类,并通过梯度下降法对其模型中的参数进行调整,具体的代码如下:

# 三、训练模型
##############################################################################
print '... training'

# early-stopping参数
patience = 10000
patience_increase = 2

improvement_threshold = 0.995

validation_frequency = min(n_train_batches, patience / 2)

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):

        iter = (epoch - 1) * n_train_batches + minibatch_index

        if iter % 100 == 0:
            print 'training @ iter = ', iter

        # 计算训练的误差
        cost_ij = train_model(minibatch_index)

        if (iter + 1) % validation_frequency == 0:

            # 在验证集上计算误差
            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 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_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.))

七、实验的结果

部分实验的结果如下:

若需要PDF版本,请关注我的新浪博客@赵_志_勇,私信你的邮箱地址给我。

参考文献

Convolutional Neural Networks (LeNet)http://www.deeplearning.net/tutorial/lenet.html#tips-and-tricks

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人声明

    欢迎大家加群,探讨与机器学习相关技术相关的话题:


    101620539


    博客的主要内容主要是自己的学习笔记,并结合个人的理解,供各位在学习过程中参考,若有疑问,欢迎提出;若有侵权,请告知博主删除,原创文章转载还请注明出处。

    -----------------

    我写的书:

    Python机器学习算法

    购买链接:

    京东-Python机器学习算法
    个人资料
    • 访问:1138900次
    • 积分:10219
    • 等级:
    • 排名:第1857名
    • 原创:155篇
    • 转载:1篇
    • 译文:1篇
    • 评论:533条
    博客专栏
    联系我
    Email:zhaozhiyong1989@126.com

    最新评论