精华内容
下载资源
问答
  • Faster R-CNN教程

    2016-12-07 09:18:08
    转载自:Faster R-CNN教程 - CarryPotMan的博客 - 博客频道 - CSDN.NET http://blog.csdn.net/u012891472/article/details/51282809 本教程主要基于Python版本的faster R-CNN,因为python layer的使用,...

    转载自:Faster R-CNN教程 - CarryPotMan的博客 - 博客频道 - CSDN.NET http://blog.csdn.net/u012891472/article/details/51282809


    本教程主要基于Python版本的faster R-CNN,因为python layer的使用,这个版本会比matlab的版本速度慢10%,但是准确率应该是差不多的。

    目前已经实现的有两种方式:

    1. Alternative training
    2. Approximate joint training

    推荐使用第二种,因为第二种使用的显存更小,而且训练会更快,同时准确率差不多甚至略高一点。

    Contents

    1. 配置环境
    2. 安装步骤
    3. Demo
    4. 建立自己的数据集
    5. 训练和检测

    配置环境

    1配置python layers

    #In your Makefile.config, make sure to have this line uncommented
    WITH_PYTHON_LAYER := 1
    # Unrelatedly, it's also recommended that you use CUDNN
    USE_CUDNN := 1
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    2安装几个依赖cython, python-OpenCV, easydict

    sudo apt-get install python-opencv
    sudo pip install cython easydict
    • 1
    • 2
    • 1
    • 2

    安装步骤

    1克隆工程

    git clone --recursive https://github.com/rbgirshick/py-faster-rcnn.git
    • 1
    • 1

    2编译Cython模块

    cd $FRCN_ROOT/lib
    make
    • 1
    • 2
    • 1
    • 2

    3编译caffe和pycaffe

    cd $FRCN_ROOT/caffe-fast-rcnn
    # Now follow the Caffe installation instructions here:
    #   http://caffe.berkeleyvision.org/installation.html
    
    # If you're experienced with Caffe and have all of the requirements installed
    # and your Makefile.config in place, then simply do:
    make -j8 && make pycaffe
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Demo

    安装步骤完成后,就可以运行一下demo了。

    cd $FRCN_ROOT
    ./tools/demo.py
    • 1
    • 2
    • 1
    • 2

    训练自己的训练集

    工程目录简介

    首先工程的根目录简单的称为 FRCN_ROOT,可以看到根目录下有以下几个文件夹

    • caffe-fast-rcnn

    这里是caffe框架目录

    • data

    用来存放pretrained模型,比如imagenet上的,以及读取文件的cache缓存

    • experiments

    存放配置文件以及运行的log文件,另外这个目录下有scripts可以用end2end或者alt_opt两种方式训练。

    • lib

    用来存放一些python接口文件,如其下的datasets主要负责数据库读取,我们接下来的文件都是放在这个目录下的;config负责cnn一些训练的配置选项,建议根据自己的需要在experiment/cfgs/faster_rcnn_end2end.yml文件中进行设置覆盖config.py中的设置。

    • models

    里面存放了三个模型文件,小型网络的ZF,大型网络VGG16,中型网络VGG_CNN_M_1024。推荐使用VGG16,如果使用端到端的approximate joint training方法,开启CuDNN,只需要3G的显存即可。

    • output

    这里存放的是训练完成后的输出目录,默认会在faster_rcnn_end2end文件夹下

    • tools

    里面存放的是训练和测试的Python文件。

    创建数据集

    接下来我们就要创建自己的数据集了,这部分主要在lib目录里操作。这里下面存在3个目录:

    • datasets

    在这里修改读写数据的接口主要是datasets目录下

    • fast_rcnn

    主要存放的是python的训练和测试脚本,以及训练的配置文件config.py

    • nms

    做非极大抑制的部分,有gpu和cpu两种实现方式

    • roi_data_layer

    主要是一些ROI处理操作

    • rpn

    这就是RPN的核心代码部分,有生成proposals和anchor的方法

    • transform

    • utils

    1构建自己的IMDB子类

    1.1文件概述 
    可有看到datasets目录下主要有三个文件,分别是

    • factory.py
    • imdb.py
    • pascal_voc.py

    factory.py 是个工厂类,用类生成imdb类并且返回数据库共网络训练和测试使用;imdb.py 这里是数据库读写类的基类,封装了许多db的操作,但是具体的一些文件读写需要继承继续读写;pascal_voc.py Ross在这里用pascal_voc.py这个类来操作。

    1.2读取文件函数分析 
    接下来我来介绍一下pasca_voc.py这个文件,我们主要是基于这个文件进行修改,里面有几个重要的函数需要修改

    • def init(self, image_set, year, devkit_path=None) 
      这个是初始化函数,它对应着的是pascal_voc的数据集访问格式,其实我们将其接口修改的更简单一点。
    • def image_path_at(self, i) 
      根据第i个图像样本返回其对应的path,其调用了image_path_from_index(self, index)作为其具体实现
    • def image_path_from_index(self, index) 
      实现了 image_path的具体功能
    • def _load_image_set_index(self) 
      加载了样本的list文件
    • def _get_default_path(self) 
      获得数据集地址
    • def gt_roidb(self) 
      读取并返回ground_truth的db
    • def selective_search_roidb 
      读取并返回ROI的db,这个是fast rcnn用的,faster版本的不用管这个函数。
    • def _load_selective_search_roidb(self, gt_roidb) 
      加载预选框的文件
    • def selective_search_IJCV_roidb(self) 
      在这里调用读取Ground_truth和ROI db并将db合并
    • def _load_selective_search_IJCV_roidb(self, gt_roidb) 
      这里是专门读取作者在IJCV上用的dataset
    • def _load_pascal_annotation(self, index) 
      这个函数是读取gt的具体实现
    • def _write_voc_results_file(self, all_boxes) 
      voc的检测结果写入到文件
    • def _do_matlab_eval(self, comp_id, output_dir=’output’) 
      根据matlab的evluation接口来做结果的分析
    • def evaluate_detections 
      其调用了_do_matlab_eval
    • def competition_mode 
      设置competitoin_mode,加了一些噪点

    1.3训练数据格式

    在我的检测任务里,我主要是在SED数据集上做行人检测,因此我这里只有background 和person 两类物体,为了操作方便,我像pascal_voc数据集里面一样每个图像用一个xml来标注。如果大家不知道怎么生成xml文件,可以用这个工具 labelImg?

    这里我要特别提醒一下大家,一定要注意坐标格式,一定要注意坐标格式,一定要注意坐标格式,重要的事情说三遍!!!要不然你会犯很多错误都会是因为坐标不一致引起的报错。

    1.4修改读取接口 
    这里是原始的pascal_voc的init函数,在这里,由于我们自己的数据集往往比voc的数据集要更简单的一些,在作者代码里面用了很多的路径拼接,我们不用去迎合他的格式,将这些操作简单化即可,在这里我会一一列举每个我修改过的函数。这里按照文件中的顺序排列。

    修改后的初始化函数:

    class hs(imdb):
        def __init__(self, image_set, devkit_path=None):  # modified
            imdb.__init__(self, image_set)
            self._image_set = image_set
            self._devkit_path =  devkit_path   #datasets路径
            self._data_path = os.path.join(self._devkit_path,image_set)   #图片文件夹路径
            self._classes = ('__background__', # always index 0
                             'person')   #two classes
            self._class_to_ind = dict(zip(self.classes, xrange(self.num_classes))) # form the dict{'__background__':'0','person':'1'}
            self._image_ext = '.jpg'
            self._image_index = self._load_image_set_index('ImageList.txt')
            # Default to roidb handler
            self._roidb_handler = self.selective_search_roidb
            self._salt = str(uuid.uuid4())
            self._comp_id = 'comp4'
    
            # PASCAL specific config options
            self.config = {'cleanup'     : True,
                           'use_salt'    : True,
                           'use_diff'    : False,
                           'matlab_eval' : False,
                           'rpn_file'    : None,
                           'min_size'    : 16}  #小于16个像素的框扔掉
    
            assert os.path.exists(self._devkit_path), \
                    'VOCdevkit path does not exist: {}'.format(self._devkit_path)
            assert os.path.exists(self._data_path), \
                    'Path does not exist: {}'.format(self._data_path)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    修改后的image_path_from_index:

    def image_path_from_index(self, index): #modified
        """
        Construct an image path from the image's "index" identifier.
        """
        image_path = os.path.join(self._data_path,index +'.jpg')
        assert os.path.exists(image_path), \
                'Path does not exist: {}'.format(image_path)
        return image_path
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    修改后的_load_image_set_index:

    def _load_image_set_index(self,imagelist): # modified
        """
        Load the indexes listed in this dataset's image set file.
        """
        # Example path to image set file:
        # self._devkit_path + /VOCdevkit2007/VOC2007/ImageSets/Main/val.txt
        image_set_file = os.path.join(self._devkit_path, imagelist)
        assert os.path.exists(image_set_file), \
                'Path does not exist: {}'.format(image_set_file)
        with open(image_set_file) as f:
            image_index = [x.strip() for x in f.readlines()]
        return image_index
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    gt_roidb(self):

    这个函数里有个生成ground truth的文件,我需要特别说明一下,如果你再次训练的时候修改了数据库,比如添加或者删除了一些样本,但是你的数据库名字函数原来那个,必须要在data/cache/目录下把数据库的缓存文件.pkl给删除掉,否则其不会重新读取相应的数据库,而是直接从之前读入然后缓存的pkl文件中读取进来,这样修改的数据库并没有进入网络,而是加载了老版本的数据。

    修改的_load_pascal_annotation(self, index):

    def _load_pascal_annotation(self, index):    #modified
        """
        Load image and bounding boxes info from XML file in the PASCAL VOC
        format.
        """
        filename = os.path.join(self._devkit_path, 'Annotations', index + '.xml')
        tree = ET.parse(filename)
        objs = tree.findall('object')
        if not self.config['use_diff']:
            # Exclude the samples labeled as difficult
            non_diff_objs = [
                obj for obj in objs if int(obj.find('difficult').text) == 0]
            # if len(non_diff_objs) != len(objs):
            #     print 'Removed {} difficult objects'.format(
            #         len(objs) - len(non_diff_objs))
            objs = non_diff_objs
        num_objs = len(objs)
    
        boxes = np.zeros((num_objs, 4), dtype=np.uint16)
        gt_classes = np.zeros((num_objs), dtype=np.int32)
        overlaps = np.zeros((num_objs, self.num_classes), dtype=np.float32)
        # "Seg" area for pascal is just the box area
        seg_areas = np.zeros((num_objs), dtype=np.float32)
    
        # Load object bounding boxes into a data frame.
        for ix, obj in enumerate(objs):
            bbox = obj.find('bndbox')
            # Make pixel indexes 0-based
            x1 = float(bbox.find('xmin').text)
            y1 = float(bbox.find('ymin').text)
            x2 = float(bbox.find('xmax').text)
            y2 = float(bbox.find('ymax').text)
            cls = self._class_to_ind[obj.find('name').text.lower().strip()]
            boxes[ix, :] = [x1, y1, x2, y2]
            gt_classes[ix] = cls
            overlaps[ix, cls] = 1.0
            seg_areas[ix] = (x2 - x1 + 1) * (y2 - y1 + 1)
    
        overlaps = scipy.sparse.csr_matrix(overlaps)
    
        return {'boxes' : boxes,
                'gt_classes': gt_classes,
                'gt_overlaps' : overlaps,
                'flipped' : False,
                'seg_areas' : seg_areas}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    因为我和Pascal用了一样的xml格式,所以这个函数我的改动不多。如果你想用txt文件保存ground truth,做出相应的修改即可。 
    想采用txt方式存储的童鞋,可以参考文末博客的写法。 
    坐标的顺序强调一下,要左上右下,并且x1必须要小于x2,这个是基本,反了会在坐标水平变换的时候会出错,坐标从0开始,如果已经是0,则不需要再-1。如果怕出错,可以直接把出界的的直接置0.

    记得在最后的main下面也修改相应的路径

    from datasets.hs import hs
    d = hs('hs', '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/')
    res = d.roidb
    from IPython import embed; embed()
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    OK,在这里我们已经完成了整个的读取接口的改写。

    2修改factory.py 
    当网络训练时会调用factory里面的get方法获得相应的imdb, 
    首先在文件头import 把pascal_voc改成hs

    # --------------------------------------------------------
    # Fast R-CNN
    # Copyright (c) 2015 Microsoft
    # Licensed under The MIT License [see LICENSE for details]
    # Written by Ross Girshick
    # --------------------------------------------------------
    
    """Factory method for easily getting imdbs by name."""
    
    __sets = {}
    
    from datasets.hs import hs
    import numpy as np
    
    # # Set up voc_<year>_<split> using selective search "fast" mode
    # for year in ['2007', '2012']:
    #     for split in ['train', 'val', 'trainval', 'test']:
    #         name = 'voc_{}_{}'.format(year, split)
    #         __sets[name] = (lambda split=split, year=year: pascal_voc(split, year))
    #
    # # Set up coco_2014_<split>
    # for year in ['2014']:
    #     for split in ['train', 'val', 'minival', 'valminusminival']:
    #         name = 'coco_{}_{}'.format(year, split)
    #         __sets[name] = (lambda split=split, year=year: coco(split, year))
    #
    # # Set up coco_2015_<split>
    # for year in ['2015']:
    #     for split in ['test', 'test-dev']:
    #         name = 'coco_{}_{}'.format(year, split)
    #         __sets[name] = (lambda split=split, year=year: coco(split, year))
    
    name = 'hs'
    devkit = '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/'
    __sets['hs'] = (lambda name = name,devkit = devkit: hs(name,devkit))
    
    def get_imdb(name):
        """Get an imdb (image database) by name."""
        if not __sets.has_key(name):
            raise KeyError('Unknown dataset: {}'.format(name))
        return __sets[name]()
    
    def list_imdbs():
        """List all registered imdbs."""
        return __sets.keys()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    训练和检测

    1.预训练模型介绍 
    首先在data目录下,有两个目录

    • faster_rcnn_models/

    • imagenet_models/

    faster_rcnn_model文件夹下面是作者用faster rcnn训练好的三个网络,分别对应着小、中、大型网络,大家可以试用一下这几个网络,看一些检测效果,他们训练都迭代了80000次,数据集都是pascal_voc的数据集。

    imagenet_model文件夹下面是在Imagenet上训练好的通用模型,在这里用来初始化网络的参数.

    在这里我比较推荐大型网络,训练也挺快的,差不多25小时(titan black 6G)。 
    还有一个比较奇怪的现象,开启CuDNN一般情况是可以加速的,但是在训练ZF模型的时候,开启CuDNN反而会特别慢,所以大家如果训练特别慢,可以尝试关掉CuDNN。

    2.修改模型文件配置 
    模型文件在models下面对应的网络文件夹下,在这里我用大型网络的配置文件修改为例子 
    比如:我的检测目标物是person ,那么我的类别就有两个类别即 background 和 person 
    因此,首先打开网络的模型文件夹,打开train.prototxt 
    修改的地方重要有三个 
    分别是这几个地方

    1. 首先在data层把num_classes 从原来的21类 20类+背景 ,改成 2类 人+背景
    2. 把RoI Proposal的’roi-data’层的 num_classes 改为 2
    3. 接着在cls_score层把num_output 从原来的21 改成 2
    4. 在bbox_pred层把num_output 从原来的84 改成8, 为检测类别个数乘以4,比如这里是2类那就是2*4=8

    测试的时候,test.prototxt也需要做相应的修改。

    OK,如果你要进一步修改网络训练中的学习速率,步长,gamma值,以及输出模型的名字,需要在同目录下的solver.prototxt中修改。

    3.启动Fast RCNN网络训练

    python ./tools/train_net.py --gpu 1 --solver models/hs/faster_rcnn_end2end/solver.prototxt --weights data/imagenet_models/VGG16.v2.caffemodel --imdb hs --iters 80000 --cfg experiments/cfgs/faster_rcnn_end2end.yml
    • 1
    • 1

    参数讲解:

    • 这里的–是两个-,不要输错

    • train_net.py是网络的训练文件,之后的参数都是附带的输入参数

    • –gpu 代表机器上的GPU编号,如果是nvidia系列的tesla显卡,可以在终端中输入nvidia-smi来查看当前的显卡负荷,选择合适的显卡

    • –solver 代表模型的配置文件,train.prototxt的文件路径已经包含在这个文件之中

    • –weights 代表初始化的权重文件,这里用的是Imagenet上预训练好的模型,大型的网络我们选择用VGG16.v2.caffemodel

    • –imdb 这里给出的训练的数据库名字需要在factory.py的_sets中,我在文件里面有_sets[‘hs’],train_net.py这个文件会调用factory.py再生成hs这个类,来读取数据

    4.启动Fast RCNN网络检测 
    可以参考tools下面的demo.py 文件,来做检测,并且将检测的坐标结果输出到相应的txt文件中。


    展开全文
  • Faster R-CNN教程最后更新日期:2016年4月29日本教程主要基于python版本的faster R-CNN,因为python layer的使用,这个版本会比matlab的版本速度慢10%,但是准确率应该是差不多的。目前已经实现的有两种方式:...

    Faster R-CNN教程

    最后更新日期:2016年4月29日

    本教程主要基于python版本的faster R-CNN,因为python layer的使用,这个版本会比matlab的版本速度慢10%,但是准确率应该是差不多的。

    目前已经实现的有两种方式:

    Alternative training

    Approximate joint training

    推荐使用第二种,因为第二种使用的显存更小,而且训练会更快,同时准确率差不多甚至略高一点。

    Contents

    配置环境

    安装步骤

    Demo

    建立自己的数据集

    训练和检测

    配置环境

    1配置python layers

    #In your Makefile.config, make sure to have this line uncommented

    WITH_PYTHON_LAYER := 1

    # Unrelatedly, it's also recommended that you use CUDNN

    USE_CUDNN := 1

    2安装几个依赖cython, python-opencv, easydict

    sudo apt-get install python-opencv

    sudo pip install cython easydict

    安装步骤

    1克隆工程

    git clone --recursive https://github.com/rbgirshick/py-faster-rcnn.git

    2编译Cython模块

    cd $FRCN_ROOT/lib

    make

    3编译caffe和pycaffe

    cd $FRCN_ROOT/caffe-fast-rcnn

    # Now follow the Caffe installation instructions here:

    # http://caffe.berkeleyvision.org/installation.html

    # If you're experienced with Caffe and have all of the requirements installed

    # and your Makefile.config in place, then simply do:

    make -j8 && make pycaffe

    Demo

    安装步骤完成后,就可以运行一下demo了。

    cd $FRCN_ROOT

    ./tools/demo.py

    训练自己的训练集

    工程目录简介

    首先工程的根目录简单的称为 FRCN_ROOT,可以看到根目录下有以下几个文件夹

    caffe-fast-rcnn

    这里是caffe框架目录

    data

    用来存放pretrained模型,比如imagenet上的,以及读取文件的cache缓存

    experiments

    存放配置文件以及运行的log文件,另外这个目录下有scripts可以用end2end或者alt_opt两种方式训练。

    lib

    用来存放一些python接口文件,如其下的datasets主要负责数据库读取,config负责cnn一些训练的配置选项。

    models

    里面存放了三个模型文件,小型网络的ZF,大型网络VGG16,中型网络VGG_CNN_M_1024。推荐使用VGG16,如果使用端到端的approximate joint training方法,开启CuDNN,只需要3G的显存即可。

    output

    这里存放的是训练完成后的输出目录,默认会在faster_rcnn_end2end文件夹下

    tools

    里面存放的是训练和测试的Python文件。

    创建数据集

    接下来我们就要创建自己的数据集了,这部分主要在lib目录里操作。这里下面存在3个目录:

    datasets

    在这里修改读写数据的接口主要是datasets目录下

    fast_rcnn

    主要存放的是python的训练和测试脚本,以及训练的配置文件config.py

    nms

    做非极大抑制的部分,有gpu和cpu两种实现方式

    roi_data_layer

    主要是一些ROI处理操作

    rpn

    这就是RPN的核心代码部分,有生成proposals和anchor的方法

    transform

    utils

    1构建自己的IMDB子类

    1.1文件概述

    可有看到datasets目录下主要有三个文件,分别是

    factory.py

    imdb.py

    pascal_voc.py

    factory.py 是个工厂类,用类生成imdb类并且返回数据库共网络训练和测试使用;imdb.py 这里是数据库读写类的基类,分装了许多db的操作,但是具体的一些文件读写需要继承继续读写;pascal_voc.py Ross在这里用pascal_voc.py这个类来操作。

    1.2读取文件函数分析

    接下来我来介绍一下pasca_voc.py这个文件,我们主要是基于这个文件进行修改,里面有几个重要的函数需要修改

    def init(self, image_set, year, devkit_path=None)

    这个是初始化函数,它对应着的是pascal_voc的数据集访问格式,其实我们将其接口修改的更简单一点。

    def image_path_at(self, i)

    根据第i个图像样本返回其对应的path,其调用了image_path_from_index(self, index)作为其具体实现

    def image_path_from_index(self, index)

    实现了 image_path的具体功能

    def _load_image_set_index(self)

    加载了样本的list文件

    def _get_default_path(self)

    获得数据集地址

    def gt_roidb(self)

    读取并返回ground_truth的db

    def selective_search_roidb

    读取并返回ROI的db,这个是fast rcnn用的,faster版本的不用管这个函数。

    def _load_selective_search_roidb(self, gt_roidb)

    加载预选框的文件

    def selective_search_IJCV_roidb(self)

    在这里调用读取Ground_truth和ROI db并将db合并

    def _load_selective_search_IJCV_roidb(self, gt_roidb)

    这里是专门读取作者在IJCV上用的dataset

    def _load_pascal_annotation(self, index)

    这个函数是读取gt的具体实现

    def _write_voc_results_file(self, all_boxes)

    voc的检测结果写入到文件

    def _do_matlab_eval(self, comp_id, output_dir='output')

    根据matlab的evluation接口来做结果的分析

    def evaluate_detections

    其调用了_do_matlab_eval

    def competition_mode

    设置competitoin_mode,加了一些噪点

    1.3训练数据格式

    在我的检测任务里,我主要是在SED数据集上做行人检测,因此我这里只有background 和person 两类物体,为了操作方便,我像pascal_voc数据集里面一样每个图像用一个xml来标注。如果大家不知道怎么生成xml文件,可以用这个工具 labelImg?

    这里我要特别提醒一下大家,一定要注意坐标格式,一定要注意坐标格式,一定要注意坐标格式,重要的事情说三遍!!!要不然你会犯很多错误都会是因为坐标不一致引起的报错。

    1.4修改读取接口

    这里是原始的pascal_voc的init函数,在这里,由于我们自己的数据集往往比voc的数据集要更简单的一些,在作者代码里面用了很多的路径拼接,我们不用去迎合他的格式,将这些操作简单化即可,在这里我会一一列举每个我修改过的函数。这里按照文件中的顺序排列。

    修改后的初始化函数:

    class hs(imdb):

    def __init__(self, image_set, devkit_path=None): # modified

    imdb.__init__(self, image_set)

    self._image_set = image_set

    self._devkit_path = devkit_path #datasets路径

    self._data_path = os.path.join(self._devkit_path,image_set) #图片文件夹路径

    self._classes = ('__background__', # always index 0

    'person') #two classes

    self._class_to_ind = dict(zip(self.classes, xrange(self.num_classes))) # form the dict{'__background__':'0','person':'1'}

    self._image_ext = '.jpg'

    self._image_index = self._load_image_set_index('ImageList.txt')

    # Default to roidb handler

    self._roidb_handler = self.selective_search_roidb

    self._salt = str(uuid.uuid4())

    self._comp_id = 'comp4'

    # PASCAL specific config options

    self.config = {'cleanup' : True,

    'use_salt' : True,

    'use_diff' : False,

    'matlab_eval' : False,

    'rpn_file' : None,

    'min_size' : 16} #小于16个像素的框扔掉

    assert os.path.exists(self._devkit_path), \

    'VOCdevkit path does not exist: {}'.format(self._devkit_path)

    assert os.path.exists(self._data_path), \

    'Path does not exist: {}'.format(self._data_path)

    修改后的image_path_from_index:

    def image_path_from_index(self, index): #modified

    """

    Construct an image path from the image's "index" identifier.

    """

    image_path = os.path.join(self._data_path,index +'.jpg')

    assert os.path.exists(image_path), \

    'Path does not exist: {}'.format(image_path)

    return image_path

    修改后的_load_image_set_index:

    def _load_image_set_index(self,imagelist): # modified

    """

    Load the indexes listed in this dataset's image set file.

    """

    # Example path to image set file:

    # self._devkit_path + /VOCdevkit2007/VOC2007/ImageSets/Main/val.txt

    image_set_file = os.path.join(self._devkit_path, imagelist)

    assert os.path.exists(image_set_file), \

    'Path does not exist: {}'.format(image_set_file)

    with open(image_set_file) as f:

    image_index = [x.strip() for x in f.readlines()]

    return image_index

    gt_roidb(self):

    这个函数里有个生成ground truth的文件,我需要特别说明一下,如果你再次训练的时候修改了数据库,比如添加或者删除了一些样本,但是你的数据库名字函数原来那个,必须要在data/cache/目录下把数据库的缓存文件.pkl给删除掉,否则其不会重新读取相应的数据库,而是直接从之前读入然后缓存的pkl文件中读取进来,这样修改的数据库并没有进入网络,而是加载了老版本的数据。

    修改的_load_pascal_annotation(self, index):

    def _load_pascal_annotation(self, index): #modified

    """

    Load image and bounding boxes info from XML file in the PASCAL VOC

    format.

    """

    filename = os.path.join(self._devkit_path, 'Annotations', index + '.xml')

    tree = ET.parse(filename)

    objs = tree.findall('object')

    if not self.config['use_diff']:

    # Exclude the samples labeled as difficult

    non_diff_objs = [

    obj for obj in objs if int(obj.find('difficult').text) == 0]

    # if len(non_diff_objs) != len(objs):

    # print 'Removed {} difficult objects'.format(

    # len(objs) - len(non_diff_objs))

    objs = non_diff_objs

    num_objs = len(objs)

    boxes = np.zeros((num_objs, 4), dtype=np.uint16)

    gt_classes = np.zeros((num_objs), dtype=np.int32)

    overlaps = np.zeros((num_objs, self.num_classes), dtype=np.float32)

    # "Seg" area for pascal is just the box area

    seg_areas = np.zeros((num_objs), dtype=np.float32)

    # Load object bounding boxes into a data frame.

    for ix, obj in enumerate(objs):

    bbox = obj.find('bndbox')

    # Make pixel indexes 0-based

    x1 = float(bbox.find('xmin').text)

    y1 = float(bbox.find('ymin').text)

    x2 = float(bbox.find('xmax').text)

    y2 = float(bbox.find('ymax').text)

    cls = self._class_to_ind[obj.find('name').text.lower().strip()]

    boxes[ix, :] = [x1, y1, x2, y2]

    gt_classes[ix] = cls

    overlaps[ix, cls] = 1.0

    seg_areas[ix] = (x2 - x1 + 1) * (y2 - y1 + 1)

    overlaps = scipy.sparse.csr_matrix(overlaps)

    return {'boxes' : boxes,

    'gt_classes': gt_classes,

    'gt_overlaps' : overlaps,

    'flipped' : False,

    'seg_areas' : seg_areas}

    因为我和Pascal用了一样的xml格式,所以这个函数我的改动不多。如果你想用txt文件保存ground truth,做出相应的修改即可。

    坐标的顺序强调一下,要左上右下,并且x1必须要小于x2,这个是基本,反了会在坐标水平变换的时候会出错,坐标从0开始,如果已经是0,则不需要再-1。如果怕出错,可以直接把出界的的直接置0.

    记得在最后的main下面也修改相应的路径

    from datasets.hs import hs

    d = hs('hs', '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/')

    res = d.roidb

    from IPython import embed; embed()

    OK,在这里我们已经完成了整个的读取接口的改写。

    2修改factory.py

    当网络训练时会调用factory里面的get方法获得相应的imdb,

    首先在文件头import 把pascal_voc改成hs

    # --------------------------------------------------------

    # Fast R-CNN

    # Copyright (c) 2015 Microsoft

    # Licensed under The MIT License [see LICENSE for details]

    # Written by Ross Girshick

    # --------------------------------------------------------

    """Factory method for easily getting imdbs by name."""

    __sets = {}

    from datasets.hs import hs

    import numpy as np

    # # Set up voc__ using selective search "fast" mode

    # for year in ['2007', '2012']:

    # for split in ['train', 'val', 'trainval', 'test']:

    # name = 'voc_{}_{}'.format(year, split)

    # __sets[name] = (lambda split=split, year=year: pascal_voc(split, year))

    #

    # # Set up coco_2014_

    # for year in ['2014']:

    # for split in ['train', 'val', 'minival', 'valminusminival']:

    # name = 'coco_{}_{}'.format(year, split)

    # __sets[name] = (lambda split=split, year=year: coco(split, year))

    #

    # # Set up coco_2015_

    # for year in ['2015']:

    # for split in ['test', 'test-dev']:

    # name = 'coco_{}_{}'.format(year, split)

    # __sets[name] = (lambda split=split, year=year: coco(split, year))

    name = 'hs'

    devkit = '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/'

    __sets['hs'] = (lambda name = name,devkit = devkit: hs(name,devkit))

    def get_imdb(name):

    """Get an imdb (image database) by name."""

    if not __sets.has_key(name):

    raise KeyError('Unknown dataset: {}'.format(name))

    return __sets[name]()

    def list_imdbs():

    """List all registered imdbs."""

    return __sets.keys()

    训练和检测

    1.预训练模型介绍

    首先在data目录下,有两个目录

    faster_rcnn_models/

    imagenet_models/

    faster_rcnn_model文件夹下面是作者用faster rcnn训练好的三个网络,分别对应着小、中、大型网络,大家可以试用一下这几个网络,看一些检测效果,他们训练都迭代了80000次,数据集都是pascal_voc的数据集。

    imagenet_model文件夹下面是在Imagenet上训练好的通用模型,在这里用来初始化网络的参数.

    在这里我比较推荐先用中型网络训练,中型网络训练和检测的速度都比较快,效果也都比较理想,大型网络的话训练速度比较慢,中型网络训练大概半天,大型网络的话用25个小时。

    2.修改模型文件配置

    模型文件在models下面对应的网络文件夹下,在这里我用中型网络的配置文件修改为例子

    比如:我的检测目标物是person ,那么我的类别就有两个类别即 background 和 person

    因此,首先打开网络的模型文件夹,打开train.prototxt

    修改的地方重要有三个

    分别是个地方

    首先在data层把num_classes 从原来的21类 20类+背景 ,改成 2类 人+背景

    接在在cls_score层把num_output 从原来的21 改成 2

    在bbox_pred层把num_output 从原来的84 改成8, 为检测类别个数乘以4,比如这里是2类那就是2*4=8

    OK,如果你要进一步修改网络训练中的学习速率,步长,gamma值,以及输出模型的名字,需要在同目录下的solver.prototxt中修改。

    3.启动Fast RCNN网络训练

    python ./tools/train_net.py --gpu 1 --solver models/hs/faster_rcnn_end2end/solver.prototxt --weights data/imagenet_models/VGG_CNN_M_1024.v2.caffemodel --imdb hs --iters 80000 --cfg experiments/cfgs/faster_rcnn_end2end.yml

    参数讲解:

    这里的–是两个-,不要输错

    train_net.py是网络的训练文件,之后的参数都是附带的输入参数

    --gpu 代表机器上的GPU编号,如果是nvidia系列的tesla显卡,可以在终端中输入nvidia-smi来查看当前的显卡负荷,选择合适的显卡

    --solver 代表模型的配置文件,train.prototxt的文件路径已经包含在这个文件之中

    --weights 代表初始化的权重文件,这里用的是Imagenet上预训练好的模型,中型的网络我们选择用VGG_CNN_M_1024.v2.caffemodel

    --imdb 这里给出的训练的数据库名字需要在factory.py的_sets中,我在文件里面有_sets[‘hs’],train_net.py这个文件会调用factory.py再生成hs这个类,来读取数据

    4.启动Fast RCNN网络检测

    可以参考tools下面的demo.py 文件,来做检测,并且将检测的坐标结果输出到相应的txt文件中。

    最后

    鉴于之前我用的版本是15年11月的版本,有些小伙伴在使用此教程时会有一些错误,所以我重新做了部分修订,目前能够在2016年4月29日版本的版本上成功运行,如果有问题,随时联系我。

    展开全文
  • 零基础CNN教程

    千次阅读 2018-08-21 11:04:49
    全连接网络 VS 卷积网络 全连接神经网络之所以不太适合图像识别任务,主要有以下几个方面的问题: 参数数量太多 考虑一个输入1000*1000像素的图片(一百万像素,现在已经不能算大图了),输入层有1000*1000=100万...

    全连接网络 VS 卷积网络

    全连接神经网络之所以不太适合图像识别任务,主要有以下几个方面的问题:

    • 参数数量太多 考虑一个输入1000*1000像素的图片(一百万像素,现在已经不能算大图了),输入层有1000*1000=100万节点。假设第一个隐藏层有100个节点(这个数量并不多),那么仅这一层就有(1000*1000+1)*100=1亿参数,这实在是太多了!我们看到图像只扩大一点,参数数量就会多很多,因此它的扩展性很差。
    • 没有利用像素之间的位置信息 对于图像识别任务来说,每个像素和其周围像素的联系是比较紧密的,和离得很远的像素的联系可能就很小了。如果一个神经元和上一层所有神经元相连,那么就相当于对于一个像素来说,把图像的所有像素都等同看待,这不符合前面的假设。当我们完成每个连接权重的学习之后,最终可能会发现,有大量的权重,它们的值都是很小的(也就是这些连接其实无关紧要)。努力学习大量并不重要的权重,这样的学习必将是非常低效的。
    • 网络层数限制 我们知道网络层数越多其表达能力越强,但是通过梯度下降方法训练深度全连接神经网络很困难,因为全连接神经网络的梯度很难传递超过3层。因此,我们不可能得到一个很深的全连接神经网络,也就限制了它的能力。

    那么,卷积神经网络又是怎样解决这个问题的呢?主要有三个思路:

    • 局部连接 这个是最容易想到的,每个神经元不再和上一层的所有神经元相连,而只和一小部分神经元相连。这样就减少了很多参数。
    • 权值共享 一组连接可以共享同一个权重,而不是每个连接有一个不同的权重,这样又减少了很多参数。
    • 下采样 可以使用Pooling来减少每层的样本数,进一步减少参数数量,同时还可以提升模型的鲁棒性。

    对于图像识别任务来说,卷积神经网络通过尽可能保留重要的参数,去掉大量不重要的参数,来达到更好的学习效果。

    接下来,我们将详述卷积神经网络到底是何方神圣。

    卷积神经网络是啥

    首先,我们先获取一个感性认识,下图是一个卷积神经网络的示意图:

    图1 卷积神经网络

    网络架构

    图1所示,一个卷积神经网络由若干卷积层Pooling层全连接层组成。你可以构建各种不同的卷积神经网络,它的常用架构模式为:

    INPUT -> [[CONV]*N -> POOL?]*M -> [FC]*K

    也就是N个卷积层叠加,然后(可选)叠加一个Pooling层,重复这个结构M次,最后叠加K个全连接层。

    对于图1展示的卷积神经网络:

    INPUT -> CONV -> POOL -> CONV -> POOL -> FC -> FC

    按照上述模式可以表示为:

    INPUT -> [[CONV]*1 -> POOL]*2 -> [FC]*2

    也就是:N=1, M=2, K=2

    三维的层结构

    图1我们可以发现卷积神经网络的层结构和全连接神经网络的层结构有很大不同。全连接神经网络每层的神经元是按照一维排列的,也就是排成一条线的样子;而卷积神经网络每层的神经元是按照三维排列的,也就是排成一个长方体的样子,有宽度高度深度

    对于图1展示的神经网络,我们看到输入层的宽度和高度对应于输入图像的宽度和高度,而它的深度为1。接着,第一个卷积层对这幅图像进行了卷积操作(后面我们会讲如何计算卷积),得到了三个Feature Map。这里的"3"可能是让很多初学者迷惑的地方,实际上,就是这个卷积层包含三个Filter,也就是三套参数,每个Filter都可以把原始输入图像卷积得到一个Feature Map,三个Filter就可以得到三个Feature Map。至于一个卷积层可以有多少个Filter,那是可以自由设定的。也就是说,卷积层的Filter个数也是一个超参数。我们可以把Feature Map可以看做是通过卷积变换提取到的图像特征,三个Filter就对原始图像提取出三组不同的特征,也就是得到了三个Feature Map,也称做三个通道(channel)

    继续观察图1,在第一个卷积层之后,Pooling层对三个Feature Map做了下采样(后面我们会讲如何计算下采样),得到了三个更小的Feature Map。接着,是第二个卷积层,它有5个Filter。每个Fitler都把前面下采样之后的3个**Feature Map卷积在一起,得到一个新的Feature Map。这样,5个Filter就得到了5个Feature Map。接着,是第二个Pooling,继续对5个Feature Map进行下采样**,得到了5个更小的Feature Map。

    图1所示网络的最后两层是全连接层。第一个全连接层的每个神经元,和上一层5个Feature Map中的每个神经元相连,第二个全连接层(也就是输出层)的每个神经元,则和第一个全连接层的每个神经元相连,这样得到了整个网络的输出。

    至此,我们对卷积神经网络有了最基本的感性认识。接下来,我们将介绍卷积神经网络中各种层的计算和训练。

    卷积神经网络输出值的计算

    卷积层输出值的计算

    我们用一个简单的例子来讲述如何计算卷积,然后,我们抽象出卷积层的一些重要概念和计算方法。

    假设有一个5*5的图像,使用一个3*3的filter进行卷积,想得到一个3*3的Feature Map,如下所示:

    为了清楚的描述卷积计算过程,我们首先对图像的每个像素进行编号,用表示图像的第行第列元素;对filter的每个权重进行编号,用表示第行第列权重,用表示filter的偏置项;对Feature Map的每个元素进行编号,用表示Feature Map的第行第列元素;用表示激活函数(这个例子选择relu函数作为激活函数)。然后,使用下列公式计算卷积:

     

     

    例如,对于Feature Map左上角元素来说,其卷积计算方法为:

     

     

    计算结果如下图所示:

    接下来,Feature Map的元素的卷积计算方法为:

     

     

    计算结果如下图所示:

    可以依次计算出Feature Map中所有元素的值。下面的动画显示了整个Feature Map的计算过程:

    图2 卷积计算

    上面的计算过程中,步幅(stride)为1。步幅可以设为大于1的数。例如,当步幅为2时,Feature Map计算如下:

    我们注意到,当步幅设置为2的时候,Feature Map就变成2*2了。这说明图像大小、步幅和卷积后的Feature Map大小是有关系的。事实上,它们满足下面的关系:

     

    式式

     

    在上面两个公式中,是卷积后Feature Map的宽度;是卷积前图像的宽度;是filter的宽度;是Zero Padding数量,Zero Padding是指在原始图像周围补几圈0,如果的值是1,那么就补1圈0;是步幅;是卷积后Feature Map的高度;是卷积前图像的宽度。式2式3本质上是一样的。

    以前面的例子来说,图像宽度,filter宽度,Zero Padding步幅,则

     

     

    说明Feature Map宽度是2。同样,我们也可以计算出Feature Map高度也是2。

    前面我们已经讲了深度为1的卷积层的计算方法,如果深度大于1怎么计算呢?其实也是类似的。如果卷积前的图像深度为D,那么相应的filter的深度也必须为D。我们扩展一下式1,得到了深度大于1的卷积计算公式:

     

     

    式4中,D是深度;F是filter的大小(宽度或高度,两者相同);表示filter的第层第行第列权重;表示图像的第层第行第列像素;其它的符号含义和式1是相同的,不再赘述。

    我们前面还曾提到,每个卷积层可以有多个filter。每个filter和原始图像进行卷积后,都可以得到一个Feature Map。因此,卷积后Feature Map的深度(个数)和卷积层的filter个数是相同的。

    下面的动画显示了包含两个filter的卷积层的计算。我们可以看到7*7*3输入,经过两个3*3*3filter的卷积(步幅为2),得到了3*3*2的输出。另外我们也会看到下图的Zero padding是1,也就是在输入元素的周围补了一圈0。Zero padding对于图像边缘部分的特征提取是很有帮助的。

    以上就是卷积层的计算方法。这里面体现了局部连接权值共享:每层神经元只和上一层部分神经元相连(卷积计算规则),且filter的权值对于上一层所有神经元都是一样的。对于包含两个3*3*3的fitler的卷积层来说,其参数数量仅有(3*3*3+1)*2=56个,且参数数量与上一层神经元个数无关。与全连接神经网络相比,其参数数量大大减少了。

    用卷积公式来表达卷积层计算

    不想了解太多数学细节的读者可以跳过这一节,不影响对全文的理解。

    式4的表达很是繁冗,最好能简化一下。就像利用矩阵可以简化表达全连接神经网络的计算一样,我们利用卷积公式可以简化卷积神经网络的表达。

    下面我们介绍二维卷积公式

    设矩阵,,其行、列数分别为、、、,则二维卷积公式如下:

     

     

    且,满足条件。

    我们可以把上式写成

     

     

    如果我们按照式5来计算卷积,我们可以发现矩阵A实际上是filter,而矩阵B是待卷积的输入,位置关系也有所不同:

    从上图可以看到,A左上角的值与B对应区块中右下角的值相乘,而不是与左上角的相乘。因此,数学中的卷积和卷积神经网络中的『卷积』还是有区别的,为了避免混淆,我们把卷积神经网络中的『卷积』操作叫做互相关(cross-correlation)操作。

    卷积互相关操作是可以转化的。首先,我们把矩阵A翻转180度,然后再交换A和B的位置(即把B放在左边而把A放在右边。卷积满足交换率,这个操作不会导致结果变化),那么卷积就变成了互相关

    如果我们不去考虑两者这么一点点的区别,我们可以把式5代入到式4

     

     

    其中,是卷积层输出的feature map。同式4相比,式6就简单多了。然而,这种简洁写法只适合步长为1的情况。

    Pooling层输出值的计算

    Pooling层主要的作用是下采样,通过去掉Feature Map中不重要的样本,进一步减少参数数量。Pooling的方法很多,最常用的是Max PoolingMax Pooling实际上就是在n*n的样本中取最大值,作为采样后的样本值。下图是2*2 max pooling:

    除了Max Pooing之外,常用的还有Mean Pooling——取各样本的平均值。

    对于深度为D的Feature Map,各层独立做Pooling,因此Pooling后的深度仍然为D。

    全连接层

    全连接层输出值的计算和上一篇文章零基础入门深度学习(3) - 神经网络和反向传播算法讲过的全连接神经网络是一样的,这里就不再赘述了。

    卷积神经网络的训练

    全连接神经网络相比,卷积神经网络的训练要复杂一些。但训练的原理是一样的:利用链式求导计算损失函数对每个权重的偏导数(梯度),然后根据梯度下降公式更新权重。训练算法依然是反向传播算法。

    我们先回忆一下上一篇文章零基础入门深度学习(3) - 神经网络和反向传播算法介绍的反向传播算法,整个算法分为三个步骤:

    1. 前向计算每个神经元的输出值(表示网络的第个神经元,以下同);
    2. 反向计算每个神经元的误差项,在有的文献中也叫做敏感度(sensitivity)。它实际上是网络的损失函数对神经元加权输入的偏导数,即;
    3. 计算每个神经元连接权重的梯度(表示从神经元连接到神经元的权重),公式为,其中,表示神经元的输出。

    最后,根据梯度下降法则更新每个权重即可。

    对于卷积神经网络,由于涉及到局部连接下采样的等操作,影响到了第二步误差项的具体计算方法,而权值共享影响了第三步权重梯度的计算方法。接下来,我们分别介绍卷积层和Pooling层的训练算法。

    卷积层的训练

    对于卷积层,我们先来看看上面的第二步,即如何将误差项传递到上一层;然后再来看看第三步,即如何计算filter每个权值的梯度

    卷积层误差项的传递

    最简单情况下误差项的传递

    我们先来考虑步长为1、输入的深度为1、filter个数为1的最简单的情况。

    假设输入的大小为3*3,filter大小为2*2,按步长为1卷积,我们将得到2*2的feature map。如下图所示:

    在上图中,为了描述方便,我们为每个元素都进行了编号。用表示第层第行第列的误差项;用表示filter第行第列权重,用表示filter的偏置项;用表示第层第行第列神经元的输出;用表示第行神经元的加权输入;用表示第层第行第列的误差项;用表示第层的激活函数。它们之间的关系如下:

     

     

    上式中,、、都是数组,是由组成的数组,表示卷积操作。

    在这里,我们假设第中的每个值都已经算好,我们要做的是计算第层每个神经元的误差项

    根据链式求导法则:

     

     

    我们先求第一项。我们先来看几个特例,然后从中总结出一般性的规律。

    例1,计算,仅与的计算有关:

     

     

    因此:

     

     

    例2,计算,与和的计算都有关:

     

     

    因此:

     

     

    例3,计算,与、、和的计算都有关:

     

     

    因此:

     

     

    从上面三个例子,我们发挥一下想象力,不难发现,计算,相当于把第层的sensitive map周围补一圈0,在与180度翻转后的filter进行cross-correlation,就能得到想要结果,如下图所示:

    因为卷积相当于将filter旋转180度的cross-correlation,因此上图的计算可以用卷积公式完美的表达:

     

     

    上式中的表示第层的filter的权重数组。也可以把上式的卷积展开,写成求和的形式:

     

     

    现在,我们再求第二项。因为

     

     

    所以这一项极其简单,仅求激活函数的导数就行了。

     

     

    将第一项和第二项组合起来,我们得到最终的公式:

     

     

    也可以将式7写成卷积的形式:

     

     

    其中,符号表示element-wise product,即将矩阵中每个对应元素相乘。注意式8中的、、都是矩阵

    以上就是步长为1、输入的深度为1、filter个数为1的最简单的情况,卷积层误差项传递的算法。下面我们来推导一下步长为S的情况。

    卷积步长为S时的误差传递

    我们先来看看步长为S与步长为1的差别。

    如上图,上面是步长为1时的卷积结果,下面是步长为2时的卷积结果。我们可以看出,因为步长为2,得到的feature map跳过了步长为1时相应的部分。因此,当我们反向计算误差项时,我们可以对步长为S的sensitivity map相应的位置进行补0,将其『还原』成步长为1时的sensitivity map,再用式8进行求解。

    输入层深度为D时的误差传递

    当输入深度为D时,filter的深度也必须为D,层的通道只与filter的通道的权重进行计算。因此,反向计算误差项时,我们可以使用式8,用filter的第通道权重对第层sensitivity map进行卷积,得到第层通道的sensitivity map。如下图所示:

    filter数量为N时的误差传递

    filter数量为N时,输出层的深度也为N,第个filter卷积产生输出层的第个feature map。由于第层每个加权输入都同时影响了第层所有feature map的输出值,因此,反向计算误差项时,需要使用全导数公式。也就是,我们先使用第个filter对第层相应的第个sensitivity map进行卷积,得到一组N个层的偏sensitivity map。依次用每个filter做这种卷积,就得到D组偏sensitivity map。最后在各组之间将N个偏sensitivity map 按元素相加,得到最终的N个层的sensitivity map:

     

     

    以上就是卷积层误差项传递的算法,如果读者还有所困惑,可以参考后面的代码实现来理解。

    卷积层filter权重梯度的计算

    我们要在得到第层sensitivity map的情况下,计算filter的权重的梯度,由于卷积层是权重共享的,因此梯度的计算稍有不同。

    如上图所示,是第层的输出,是第层filter的权重,是第层的sensitivity map。我们的任务是计算的梯度,即。

    为了计算偏导数,我们需要考察权重对的影响。权重项通过影响的值,进而影响。我们仍然通过几个具体的例子来看权重项对的影响,然后再从中总结出规律。

    例1,计算:

     

     

    从上面的公式看出,由于权值共享,权值对所有的都有影响。是、、...的函数,而、、...又是的函数,根据全导数公式,计算就是要把每个偏导数都加起来:

     

     

    例2,计算:

    通过查看与的关系,我们很容易得到:

     

     

    实际上,每个权重项都是类似的,我们不一一举例了。现在,是我们再次发挥想象力的时候,我们发现计算规律是:

     

     

    也就是用sensitivity map作为卷积核,在input上进行cross-correlation,如下图所示:

    最后,我们来看一看偏置项的梯度。通过查看前面的公式,我们很容易发现:

     

     

    也就是偏置项梯度就是sensitivity map所有误差项之和。

    对于步长为S的卷积层,处理方法与传递**误差项*是一样的,首先将sensitivity map『还原』成步长为1时的sensitivity map,再用上面的方法进行计算。

    获得了所有的梯度之后,就是根据梯度下降算法来更新每个权重。这在前面的文章中已经反复写过,这里就不再重复了。

    至此,我们已经解决了卷积层的训练问题,接下来我们看一看Pooling层的训练。

    Pooling层的训练

    无论max pooling还是mean pooling,都没有需要学习的参数。因此,在卷积神经网络的训练中,Pooling层需要做的仅仅是将误差项传递到上一层,而没有梯度的计算。

    Max Pooling误差项的传递

    如下图,假设第层大小为4*4,pooling filter大小为2*2,步长为2,这样,max pooling之后,第层大小为2*2。假设第层的值都已经计算完毕,我们现在的任务是计算第层的值。

    我们用表示第层的加权输入;用表示第层的加权输入。我们先来考察一个具体的例子,然后再总结一般性的规律。对于max pooling:

     

     

    也就是说,只有区块中最大的才会对的值产生影响。我们假设最大的值是,则上式相当于:

     

     

    那么,我们不难求得下面几个偏导数:

     

     

    因此:

     

     

    而:

     

     

    现在,我们发现了规律:对于max pooling,下一层的误差项的值会原封不动的传递到上一层对应区块中的最大值所对应的神经元,而其他神经元的误差项的值都是0。如下图所示(假设、、、为所在区块中的最大输出值):

    Mean Pooling误差项的传递

    我们还是用前面屡试不爽的套路,先研究一个特殊的情形,再扩展为一般规律。

    如上图,我们先来考虑计算。我们先来看看如何影响。

     

     

    根据上式,我们一眼就能看出来:

     

     

    所以,根据链式求导法则,我们不难算出:

     

     

    同样,我们可以算出、、:

     

     

    现在,我们发现了规律:对于mean pooling,下一层的误差项的值会平均分配到上一层对应区块中的所有神经元。如下图所示:

    上面这个算法可以表达为高大上的克罗内克积(Kronecker product)的形式,有兴趣的读者可以研究一下。

     

     

    其中,是pooling层filter的大小,、都是矩阵。

    至此,我们已经把卷积层Pooling层的训练算法介绍完毕,加上上一篇文章讲的全连接层训练算法,您应该已经具备了编写卷积神经网络代码所需要的知识。为了加深对知识的理解,接下来,我们将展示如何实现一个简单的卷积神经网络

    卷积神经网络的实现

    完整代码请参考GitHub: https://github.com/hanbt/learn_dl/blob/master/cnn.py (python2.7)

    现在,我们亲自动手实现一个卷积神经网络,以便巩固我们所学的知识。

    首先,我们要改变一下代码的架构,『层』成为了我们最核心的组件。这是因为卷积神经网络有不同的层,而每种层的算法都在对应的类中实现。

    这次,我们用到了在python中编写算法经常会用到的numpy包。为了使用numpy,我们需要先将numpy导入:

    
     
    1. import numpy as np

    卷积层的实现

    卷积层初始化

    我们用ConvLayer类来实现一个卷积层。下面的代码是初始化一个卷积层,可以在构造函数中设置卷积层的超参数

    
     
    1. class ConvLayer(object):
    2. def __init__(self, input_width, input_height,
    3. channel_number, filter_width,
    4. filter_height, filter_number,
    5. zero_padding, stride, activator,
    6. learning_rate):
    7. self.input_width = input_width
    8. self.input_height = input_height
    9. self.channel_number = channel_number
    10. self.filter_width = filter_width
    11. self.filter_height = filter_height
    12. self.filter_number = filter_number
    13. self.zero_padding = zero_padding
    14. self.stride = stride
    15. self.output_width = \
    16. ConvLayer.calculate_output_size(
    17. self.input_width, filter_width, zero_padding,
    18. stride)
    19. self.output_height = \
    20. ConvLayer.calculate_output_size(
    21. self.input_height, filter_height, zero_padding,
    22. stride)
    23. self.output_array = np.zeros((self.filter_number,
    24. self.output_height, self.output_width))
    25. self.filters = []
    26. for i in range(filter_number):
    27. self.filters.append(Filter(filter_width,
    28. filter_height, self.channel_number))
    29. self.activator = activator
    30. self.learning_rate = learning_rate

    calculate_output_size函数用来确定卷积层输出的大小,其实现如下:

    
     
    1. @staticmethod
    2. def calculate_output_size(input_size,
    3. filter_size, zero_padding, stride):
    4. return (input_size - filter_size +
    5. 2 * zero_padding) / stride + 1

    Filter类保存了卷积层的参数以及梯度,并且实现了用梯度下降算法来更新参数。

    
     
    1. class Filter(object):
    2. def __init__(self, width, height, depth):
    3. self.weights = np.random.uniform(-1e-4, 1e-4,
    4. (depth, height, width))
    5. self.bias = 0
    6. self.weights_grad = np.zeros(
    7. self.weights.shape)
    8. self.bias_grad = 0
    9.  
    10. def __repr__(self):
    11. return 'filter weights:\n%s\nbias:\n%s' % (
    12. repr(self.weights), repr(self.bias))
    13.  
    14. def get_weights(self):
    15. return self.weights
    16.  
    17. def get_bias(self):
    18. return self.bias
    19.  
    20. def update(self, learning_rate):
    21. self.weights -= learning_rate * self.weights_grad
    22. self.bias -= learning_rate * self.bias_grad

    我们对参数的初始化采用了常用的策略,即:权重随机初始化为一个很小的值,而偏置项初始化为0。

    Activator类实现了激活函数,其中,forward方法实现了前向计算,而backward方法则是计算导数。比如,relu函数的实现如下:

    
     
    1. class ReluActivator(object):
    2. def forward(self, weighted_input):
    3. #return weighted_input
    4. return max(0, weighted_input)
    5.  
    6. def backward(self, output):
    7. return 1 if output > 0 else 0

    卷积层前向计算的实现

    ConvLayer类的forward方法实现了卷积层的前向计算(即计算根据输入来计算卷积层的输出),下面是代码实现:

    
     
    1. def forward(self, input_array):
    2. '''
    3. 计算卷积层的输出
    4. 输出结果保存在self.output_array
    5. '''
    6. self.input_array = input_array
    7. self.padded_input_array = padding(input_array,
    8. self.zero_padding)
    9. for f in range(self.filter_number):
    10. filter = self.filters[f]
    11. conv(self.padded_input_array,
    12. filter.get_weights(), self.output_array[f],
    13. self.stride, filter.get_bias())
    14. element_wise_op(self.output_array,
    15. self.activator.forward)

    上面的代码里面包含了几个工具函数。element_wise_op函数实现了对numpy数组进行按元素操作,并将返回值写回到数组中,代码如下:

    
     
    1. # 对numpy数组进行element wise操作
    2. def element_wise_op(array, op):
    3. for i in np.nditer(array,
    4. op_flags=['readwrite']):
    5. i[...] = op(i)

    conv函数实现了2维和3维数组的卷积,代码如下:

    
     
    1. def conv(input_array,
    2. kernel_array,
    3. output_array,
    4. stride, bias):
    5. '''
    6. 计算卷积,自动适配输入为2D和3D的情况
    7. '''
    8. channel_number = input_array.ndim
    9. output_width = output_array.shape[1]
    10. output_height = output_array.shape[0]
    11. kernel_width = kernel_array.shape[-1]
    12. kernel_height = kernel_array.shape[-2]
    13. for i in range(output_height):
    14. for j in range(output_width):
    15. output_array[i][j] = (
    16. get_patch(input_array, i, j, kernel_width,
    17. kernel_height, stride) * kernel_array
    18. ).sum() + bias

    padding函数实现了zero padding操作:

    
     
    1. # 为数组增加Zero padding
    2. def padding(input_array, zp):
    3. '''
    4. 为数组增加Zero padding,自动适配输入为2D和3D的情况
    5. '''
    6. if zp == 0:
    7. return input_array
    8. else:
    9. if input_array.ndim == 3:
    10. input_width = input_array.shape[2]
    11. input_height = input_array.shape[1]
    12. input_depth = input_array.shape[0]
    13. padded_array = np.zeros((
    14. input_depth,
    15. input_height + 2 * zp,
    16. input_width + 2 * zp))
    17. padded_array[:,
    18. zp : zp + input_height,
    19. zp : zp + input_width] = input_array
    20. return padded_array
    21. elif input_array.ndim == 2:
    22. input_width = input_array.shape[1]
    23. input_height = input_array.shape[0]
    24. padded_array = np.zeros((
    25. input_height + 2 * zp,
    26. input_width + 2 * zp))
    27. padded_array[zp : zp + input_height,
    28. zp : zp + input_width] = input_array
    29. return padded_array

    卷积层反向传播算法的实现

    现在,是介绍卷积层核心算法的时候了。我们知道反向传播算法需要完成几个任务:

    1. 误差项传递到上一层。
    2. 计算每个参数梯度
    3. 更新参数

    以下代码都是在ConvLayer类中实现。我们先来看看将误差项传递到上一层的代码实现。

    
     
    1. def bp_sensitivity_map(self, sensitivity_array,
    2. activator):
    3. '''
    4. 计算传递到上一层的sensitivity map
    5. sensitivity_array: 本层的sensitivity map
    6. activator: 上一层的激活函数
    7. '''
    8. # 处理卷积步长,对原始sensitivity map进行扩展
    9. expanded_array = self.expand_sensitivity_map(
    10. sensitivity_array)
    11. # full卷积,对sensitivitiy map进行zero padding
    12. # 虽然原始输入的zero padding单元也会获得残差
    13. # 但这个残差不需要继续向上传递,因此就不计算了
    14. expanded_width = expanded_array.shape[2]
    15. zp = (self.input_width +
    16. self.filter_width - 1 - expanded_width) / 2
    17. padded_array = padding(expanded_array, zp)
    18. # 初始化delta_array,用于保存传递到上一层的
    19. # sensitivity map
    20. self.delta_array = self.create_delta_array()
    21. # 对于具有多个filter的卷积层来说,最终传递到上一层的
    22. # sensitivity map相当于所有的filter的
    23. # sensitivity map之和
    24. for f in range(self.filter_number):
    25. filter = self.filters[f]
    26. # 将filter权重翻转180度
    27. flipped_weights = np.array(map(
    28. lambda i: np.rot90(i, 2),
    29. filter.get_weights()))
    30. # 计算与一个filter对应的delta_array
    31. delta_array = self.create_delta_array()
    32. for d in range(delta_array.shape[0]):
    33. conv(padded_array[f], flipped_weights[d],
    34. delta_array[d], 1, 0)
    35. self.delta_array += delta_array
    36. # 将计算结果与激活函数的偏导数做element-wise乘法操作
    37. derivative_array = np.array(self.input_array)
    38. element_wise_op(derivative_array,
    39. activator.backward)
    40. self.delta_array *= derivative_array

    expand_sensitivity_map方法就是将步长为S的sensitivity map『还原』为步长为1的sensitivity map,代码如下:

    
     
    1. def expand_sensitivity_map(self, sensitivity_array):
    2. depth = sensitivity_array.shape[0]
    3. # 确定扩展后sensitivity map的大小
    4. # 计算stride为1时sensitivity map的大小
    5. expanded_width = (self.input_width -
    6. self.filter_width + 2 * self.zero_padding + 1)
    7. expanded_height = (self.input_height -
    8. self.filter_height + 2 * self.zero_padding + 1)
    9. # 构建新的sensitivity_map
    10. expand_array = np.zeros((depth, expanded_height,
    11. expanded_width))
    12. # 从原始sensitivity map拷贝误差值
    13. for i in range(self.output_height):
    14. for j in range(self.output_width):
    15. i_pos = i * self.stride
    16. j_pos = j * self.stride
    17. expand_array[:,i_pos,j_pos] = \
    18. sensitivity_array[:,i,j]
    19. return expand_array

    create_delta_array是创建用来保存传递到上一层的sensitivity map的数组。

    
     
    1. def create_delta_array(self):
    2. return np.zeros((self.channel_number,
    3. self.input_height, self.input_width))

    接下来,是计算梯度的代码。

    
     
    1. def bp_gradient(self, sensitivity_array):
    2. # 处理卷积步长,对原始sensitivity map进行扩展
    3. expanded_array = self.expand_sensitivity_map(
    4. sensitivity_array)
    5. for f in range(self.filter_number):
    6. # 计算每个权重的梯度
    7. filter = self.filters[f]
    8. for d in range(filter.weights.shape[0]):
    9. conv(self.padded_input_array[d],
    10. expanded_array[f],
    11. filter.weights_grad[d], 1, 0)
    12. # 计算偏置项的梯度
    13. filter.bias_grad = expanded_array[f].sum()

    最后,是按照梯度下降算法更新参数的代码,这部分非常简单。

    
     
    1. def update(self):
    2. '''
    3. 按照梯度下降,更新权重
    4. '''
    5. for filter in self.filters:
    6. filter.update(self.learning_rate)

    卷积层的梯度检查

    为了验证我们的公式推导和代码实现的正确性,我们必须要对卷积层进行梯度检查。下面是代吗实现:

    
     
    1. def init_test():
    2. a = np.array(
    3. [[[0,1,1,0,2],
    4. [2,2,2,2,1],
    5. [1,0,0,2,0],
    6. [0,1,1,0,0],
    7. [1,2,0,0,2]],
    8. [[1,0,2,2,0],
    9. [0,0,0,2,0],
    10. [1,2,1,2,1],
    11. [1,0,0,0,0],
    12. [1,2,1,1,1]],
    13. [[2,1,2,0,0],
    14. [1,0,0,1,0],
    15. [0,2,1,0,1],
    16. [0,1,2,2,2],
    17. [2,1,0,0,1]]])
    18. b = np.array(
    19. [[[0,1,1],
    20. [2,2,2],
    21. [1,0,0]],
    22. [[1,0,2],
    23. [0,0,0],
    24. [1,2,1]]])
    25. cl = ConvLayer(5,5,3,3,3,2,1,2,IdentityActivator(),0.001)
    26. cl.filters[0].weights = np.array(
    27. [[[-1,1,0],
    28. [0,1,0],
    29. [0,1,1]],
    30. [[-1,-1,0],
    31. [0,0,0],
    32. [0,-1,0]],
    33. [[0,0,-1],
    34. [0,1,0],
    35. [1,-1,-1]]], dtype=np.float64)
    36. cl.filters[0].bias=1
    37. cl.filters[1].weights = np.array(
    38. [[[1,1,-1],
    39. [-1,-1,1],
    40. [0,-1,1]],
    41. [[0,1,0],
    42. [-1,0,-1],
    43. [-1,1,0]],
    44. [[-1,0,0],
    45. [-1,0,1],
    46. [-1,0,0]]], dtype=np.float64)
    47. return a, b, cl
    48.  
    49.  
    50. def gradient_check():
    51. '''
    52. 梯度检查
    53. '''
    54. # 设计一个误差函数,取所有节点输出项之和
    55. error_function = lambda o: o.sum()
    56.  
    57. # 计算forward值
    58. a, b, cl = init_test()
    59. cl.forward(a)
    60.  
    61. # 求取sensitivity map,是一个全1数组
    62. sensitivity_array = np.ones(cl.output_array.shape,
    63. dtype=np.float64)
    64. # 计算梯度
    65. cl.backward(a, sensitivity_array,
    66. IdentityActivator())
    67. # 检查梯度
    68. epsilon = 10e-4
    69. for d in range(cl.filters[0].weights_grad.shape[0]):
    70. for i in range(cl.filters[0].weights_grad.shape[1]):
    71. for j in range(cl.filters[0].weights_grad.shape[2]):
    72. cl.filters[0].weights[d,i,j] += epsilon
    73. cl.forward(a)
    74. err1 = error_function(cl.output_array)
    75. cl.filters[0].weights[d,i,j] -= 2*epsilon
    76. cl.forward(a)
    77. err2 = error_function(cl.output_array)
    78. expect_grad = (err1 - err2) / (2 * epsilon)
    79. cl.filters[0].weights[d,i,j] += epsilon
    80. print 'weights(%d,%d,%d): expected - actural %f - %f' % (
    81. d, i, j, expect_grad, cl.filters[0].weights_grad[d,i,j])

    上面代码值得思考的地方在于,传递给卷积层的sensitivity map是全1数组,留给读者自己推导一下为什么是这样(提示:激活函数选择了identity函数:)。读者如果还有困惑,请写在文章评论中,我会回复。

    运行上面梯度检查的代码,我们得到的输出如下,期望的梯度和实际计算出的梯度一致,这证明我们的算法推导和代码实现确实是正确的。

    以上就是卷积层的实现。

    Max Pooling层的实现

    max pooling层的实现相对简单,我们直接贴出全部代码如下:

    
     
    1. class MaxPoolingLayer(object):
    2. def __init__(self, input_width, input_height,
    3. channel_number, filter_width,
    4. filter_height, stride):
    5. self.input_width = input_width
    6. self.input_height = input_height
    7. self.channel_number = channel_number
    8. self.filter_width = filter_width
    9. self.filter_height = filter_height
    10. self.stride = stride
    11. self.output_width = (input_width -
    12. filter_width) / self.stride + 1
    13. self.output_height = (input_height -
    14. filter_height) / self.stride + 1
    15. self.output_array = np.zeros((self.channel_number,
    16. self.output_height, self.output_width))
    17.  
    18. def forward(self, input_array):
    19. for d in range(self.channel_number):
    20. for i in range(self.output_height):
    21. for j in range(self.output_width):
    22. self.output_array[d,i,j] = (
    23. get_patch(input_array[d], i, j,
    24. self.filter_width,
    25. self.filter_height,
    26. self.stride).max())
    27.  
    28. def backward(self, input_array, sensitivity_array):
    29. self.delta_array = np.zeros(input_array.shape)
    30. for d in range(self.channel_number):
    31. for i in range(self.output_height):
    32. for j in range(self.output_width):
    33. patch_array = get_patch(
    34. input_array[d], i, j,
    35. self.filter_width,
    36. self.filter_height,
    37. self.stride)
    38. k, l = get_max_index(patch_array)
    39. self.delta_array[d,
    40. i * self.stride + k,
    41. j * self.stride + l] = \
    42. sensitivity_array[d,i,j]

    全连接层的实现和上一篇文章类似,在此就不再赘述了。至此,你已经拥有了实现了一个简单的卷积神经网络所需要的基本组件。对于卷积神经网络,现在有很多优秀的开源实现,因此我们并不需要真的自己去实现一个。贴出这些代码的目的是为了让我们更好的了解卷积神经网络的基本原理。

    卷积神经网络的应用

    MNIST手写数字识别

    LeNet-5是实现手写数字识别的卷积神经网络,在MNIST测试集上,它取得了0.8%的错误率。LeNet-5的结构如下:

    关于LeNet-5的详细介绍,网上的资料很多,因此就不再重复了。感兴趣的读者可以尝试用我们自己实现的卷积神经网络代码去构造并训练LeNet-5(当然代码会更复杂一些)。

    小节

    由于卷积神经网络的复杂性,我们写出了整个系列目前为止最长的一篇文章,相信读者也和作者一样累的要死。卷积神经网络是深度学习最重要的工具(我犹豫要不要写上『之一』呢),付出一些辛苦去理解它也是值得的。如果您真正理解了本文的内容,相当于迈过了入门深度学习最重要的一到门槛。在下一篇文章中,我们介绍深度学习另外一种非常重要的工具:循环神经网络,届时我们的系列文章也将完成过半。每篇文章都是一个过滤器,对于坚持到这里的读者们,入门深度学习曙光已现,加油。

    参考资料

    1. CS231n Convolutional Neural Networks for Visual Recognition
    2. ReLu (Rectified Linear Units) 激活函数
    3. Jake Bouvrie, Notes on Convolutional Neural Networks, 2006
    4. Ian Goodfellow, Yoshua Bengio, Aaron Courville, Deep Learning, MIT Press, 2016
      本文转载自:https://www.zybuluo.com/hanbingtao/note/485480
    展开全文
  • 点云深度学习系列二 PointCNN 一主要思想 从前一层的数据中取 K 个候选点 (p1,p2.pK) 使用 MLP(多层感知器 ) 来 学习一个 KK 的变换矩阵 X-transformation X 变换也就是说 X=MLP(p1,p2.pK) 然后用它同时对输入特征...
  • Tensorflow的CNN教程解析

    2016-06-19 23:09:00
    之前的博客我们已经对RNN模型...今天的讨论主要是基于Tensorflow的CIFAR10教程,不过作为对比,我们也会对Tensorflow的MINST教程作解析以及对比。很快大家就会发现,逻辑上考虑,其实内容都是大同小异的。由于所对应...

    之前的博客我们已经对RNN模型有了个粗略的了解。作为一个时序性模型,RNN的强大不需要我在这里重复了。今天,让我们来看看除了RNN外另一个特殊的,同时也是广为人知的强大的神经网络模型,即CNN模型。今天的讨论主要是基于Tensorflow的CIFAR10教程,不过作为对比,我们也会对Tensorflow的MINST教程作解析以及对比。很快大家就会发现,逻辑上考虑,其实内容都是大同小异的。由于所对应的目标不一样,在数据处理方面可能存在着些许差异,这里我们以CIFAR10的为基准,有兴趣的朋友欢迎去阅读并学习MNIST的过程,地址点击这里。CIFAR10的英文教程在Tensorflow官网上可以获得,教程代码地址点击这里

    CNN简介

    CNN是一个神奇的深度学习框架,也是深度学习学科里的一个异类。在被誉为AI寒冬的90年末到2000年初,在大部分学者都弃坑的情况下,CNN的效用却不减反增,感谢Yann LeCun!CNN的架构其实很符合其名,Convolutional Neural Network,CNN在运做的开始运用了卷积(convolution)的概念,外加pooling等方式在多次卷积了图像并形成多个特征图后,输入被平铺开进入一个完全连接的多层神经网络里(fully connected network)里,并由输出的softmax来判断图片的分类情况。该框架的发展史也很有趣,早在90年代末,以LeCun命名的Le-Net5就已经闻名。在深度学习火热后,更多的框架变种也接踵而至,较为闻名的包括多伦多大学的AlexNet,谷歌的GoogLeNet,牛津的OxfordNet外还有Network in Network(NIN),VGG16等多个network。最近,对物体识别的研究开发了RCNN框架,可见在深度学习发展迅猛的今天,CNN框架依然是很多著名研究小组的课题,特别是在了解了Alpha-Go的运作里也可以看到CNN的身影,可见其能力!至于CNN模型的基础构架,这方面的资源甚多,就不一一列举了。

    CIFAR10代码分析

    在运行CIFAR10代码时,你只需要下载该代码,然后cd到代码目录后直接输入python cifar10_train.py就可以了。默认的迭代步骤为100万步,每一步骤需要约3~4秒,运行5小时可以完成近10万步。由于根据cifar10_train.py的描述10万步的准确率为86%左右,我们运行近5个小时左右就可以了,没必要运行全部的100万步。查看结果时,运行python cifar_10_eval.py就可以了。由于模型被存储在了tmp目录里,eval文件可以找寻到最近保存的模型并运行该模型,所以还是很方便的。这个系统在运行后可以从照片里识别10种不同的物体,包括飞机等。这么好玩的系统,快让我们来看一看是怎么实现的吧!

    首先,让我们来看下cifar1_train.py文件。文件里的核心为train函数,它的表现如下:

    def train():
      """Train CIFAR-10 for a number of steps."""
      with tf.Graph().as_default():
        global_step = tf.Variable(0, trainable=False)
    
        # Get images and labels for CIFAR-10.
        # 输入选用的是distored_inputs函数
        images, labels = cifar10.distorted_inputs()
    
        # Build a Graph that computes the logits predictions from the
        # inference model.
        logits = cifar10.inference(images)
    
        # Calculate loss.
        loss = cifar10.loss(logits, labels)
    
        # Build a Graph that trains the model with one batch of examples and
        # updates the model parameters.
        train_op = cifar10.train(loss, global_step)
    
        # Create a saver.
        saver = tf.train.Saver(tf.all_variables())
    
        # Build the summary operation based on the TF collection of Summaries.
        summary_op = tf.merge_all_summaries()
    
        # Build an initialization operation to run below.
        init = tf.initialize_all_variables()
    
        # Start running operations on the Graph.
        sess = tf.Session(config=tf.ConfigProto(
            log_device_placement=FLAGS.log_device_placement))
        sess.run(init)
    
        # Start the queue runners.
        tf.train.start_queue_runners(sess=sess)
    
        summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, sess.graph)
        
        # 在最高的迭代步骤数里进行循环迭代
        for step in xrange(FLAGS.max_steps):
          start_time = time.time()
          _, loss_value = sess.run([train_op, loss])
          duration = time.time() - start_time
    
          assert not np.isnan(loss_value), 'Model diverged with loss = NaN'
          # 每10个输入数据显示次step,loss,时间等运行数据
          if step % 10 == 0:
            num_examples_per_step = FLAGS.batch_size
            examples_per_sec = num_examples_per_step / duration
            sec_per_batch = float(duration)
    
            format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                          'sec/batch)')
            print (format_str % (datetime.now(), step, loss_value,
                                 examples_per_sec, sec_per_batch))
          # 每100个输入数据将网络的状况体现在summary里
          if step % 100 == 0:
            summary_str = sess.run(summary_op)
            summary_writer.add_summary(summary_str, step)
    
          # Save the model checkpoint periodically.
          # 每1000个输入数据保存次模型
          if step % 1000 == 0 or (step + 1) == FLAGS.max_steps:
            checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
            saver.save(sess, checkpoint_path, global_step=step)

    这个训练函数本身逻辑很清晰,除了它运用了大量的cifar10.py文件里的函数外,一个值得注意的地方是输入里应用的是distorded_inputs函数。这个很有意思,因为据论文表达,对输入数据进行一定的处理后可以得到新的数据,这是增加数据存储量的一个简便的方法,那么具体它是如何做到的呢?让我们来看看这个distorded_inputs函数。在cifar10.py文件里,distorded_inputs函数实质上是一个wrapper,包装了来自cifar10_input.py函数里的distorted_inputs()函数。这个函数的逻辑如下:

    def distorted_inputs(data_dir, batch_size):
      """Construct distorted input for CIFAR training using the Reader ops.
      Args:
        data_dir: Path to the CIFAR-10 data directory.
        batch_size: Number of images per batch.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      """
      filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
                   for i in xrange(1, 6)]
      for f in filenames:
        if not tf.gfile.Exists(f):
          raise ValueError('Failed to find file: ' + f)
    
      # Create a queue that produces the filenames to read.
      filename_queue = tf.train.string_input_producer(filenames)
    
      # Read examples from files in the filename queue.
      read_input = read_cifar10(filename_queue)
      reshaped_image = tf.cast(read_input.uint8image, tf.float32)
    
      height = IMAGE_SIZE
      width = IMAGE_SIZE
    
      # Image processing for training the network. Note the many random
      # distortions applied to the image.
    
      # Randomly crop a [height, width] section of the image.
      # 步骤1:随机截取一个以[高,宽]为大小的图矩阵。
      distorted_image = tf.random_crop(reshaped_image, [height, width, 3])
    
      # Randomly flip the image horizontally.
      # 步骤2:随机颠倒图片的左右。概率为50%
      distorted_image = tf.image.random_flip_left_right(distorted_image)
    
      # Because these operations are not commutative, consider randomizing
      # the order their operation.
      #  步骤3:随机改变图片的亮度以及色彩对比。
      distorted_image = tf.image.random_brightness(distorted_image,
                                                   max_delta=63)
      distorted_image = tf.image.random_contrast(distorted_image,
                                                 lower=0.2, upper=1.8)
    
      # Subtract off the mean and divide by the variance of the pixels.
      float_image = tf.image.per_image_whitening(distorted_image)
    
      # Ensure that the random shuffling has good mixing properties.
      min_fraction_of_examples_in_queue = 0.4
      min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN *
                               min_fraction_of_examples_in_queue)
      print ('Filling queue with %d CIFAR images before starting to train. '
             'This will take a few minutes.' % min_queue_examples)
    
      # Generate a batch of images and labels by building up a queue of examples.
      return _generate_image_and_label_batch(float_image, read_input.label,
                                             min_queue_examples, batch_size,
                                             shuffle=True)
    

    这里每一张图片被随机的截取一片图后有一定的概率被翻转,改变亮度对比等步骤。另外,最后一段的意思为在queue里有了不少于40%的数据的时候训练才能开始。那么在测试的时候,我们需要经过这个步骤么?答案是非也。在cifar10_input.py文件里,distorded_inputs函数的下方,一个名为inputs的函数代表了输入被运用在eval时的逻辑。在输入参数方面,这个inputs函数在保留了distorded_inputs的同时增加了一个名为eval_data的参数,一个bool参数代表了是运用训练的数据还是测试的数据。下面,让我们来大概看下这个函数的逻辑。

    def inputs(eval_data, data_dir, batch_size):
      """Construct input for CIFAR evaluation using the Reader ops.
      Args:
        eval_data: bool, indicating if one should use the train or eval data set.
        data_dir: Path to the CIFAR-10 data directory.
        batch_size: Number of images per batch.
      Returns:
        images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
        labels: Labels. 1D tensor of [batch_size] size.
      """
      if not eval_data:
        filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
                     for i in xrange(1, 6)]
        num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN
      else:
        filenames = [os.path.join(data_dir, 'test_batch.bin')]
        num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_EVAL
    
      for f in filenames:
        if not tf.gfile.Exists(f):
          raise ValueError('Failed to find file: ' + f)
    
      # Create a queue that produces the filenames to read.
      filename_queue = tf.train.string_input_producer(filenames)
    
      # Read examples from files in the filename queue.
      read_input = read_cifar10(filename_queue)
      reshaped_image = tf.cast(read_input.uint8image, tf.float32)
    
      height = IMAGE_SIZE
      width = IMAGE_SIZE
    
      # Image processing for evaluation.
      # Crop the central [height, width] of the image.
    # 截取图片中心区域 resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, width, height) # Subtract off the mean and divide by the variance of the pixels.
    # 平衡图片的色差 float_image = tf.image.per_image_whitening(resized_image) # Ensure that the random shuffling has good mixing properties. min_fraction_of_examples_in_queue = 0.4 min_queue_examples = int(num_examples_per_epoch * min_fraction_of_examples_in_queue) # Generate a batch of images and labels by building up a queue of examples. return _generate_image_and_label_batch(float_image, read_input.label, min_queue_examples, batch_size, shuffle=False)

    这里,我们看到截取只有图片的中心,另外处理也只有平衡色差。但是,聪明的读者朋友一定能想到,如果一张关于飞机的图片是以飞机头为图片中心的,而训练集合里所有的飞机图片都是以机翼为图片中心的话,我们之前的distorded_inputs函数将有机会截取飞机头的区域,从而给我们的测试图片提供相似信息。另外,随机调整色差也包含了平均色差,所以我们的训练集实质上包含了更广,更多种的可能性,故可想而之会有机会得到更好的效果。

    那么,讲了关于输入的小窍门,我们应该来看看具体的CNN模型了。如何制造一个CNN模型呢?让我们先来看一个简单的版本,即MNIST教程里的模型:

      # The variables below hold all the trainable weights. They are passed an
      # initial value which will be assigned when we call:
      # {tf.initialize_all_variables().run()}
      conv1_weights = tf.Variable(
          tf.truncated_normal([5, 5, NUM_CHANNELS, 32],  # 5x5 filter, depth 32.
                              stddev=0.1,
                              seed=SEED, dtype=data_type()))
      conv1_biases = tf.Variable(tf.zeros([32], dtype=data_type()))
      conv2_weights = tf.Variable(tf.truncated_normal(
          [5, 5, 32, 64], stddev=0.1,
          seed=SEED, dtype=data_type()))
      conv2_biases = tf.Variable(tf.constant(0.1, shape=[64], dtype=data_type()))
      fc1_weights = tf.Variable(  # fully connected, depth 512.
          tf.truncated_normal([IMAGE_SIZE // 4 * IMAGE_SIZE // 4 * 64, 512],
                              stddev=0.1,
                              seed=SEED,
                              dtype=data_type()))
      fc1_biases = tf.Variable(tf.constant(0.1, shape=[512], dtype=data_type()))
      fc2_weights = tf.Variable(tf.truncated_normal([512, NUM_LABELS],
                                                    stddev=0.1,
                                                    seed=SEED,
                                                    dtype=data_type()))
      fc2_biases = tf.Variable(tf.constant(
          0.1, shape=[NUM_LABELS], dtype=data_type()))
    
      # We will replicate the model structure for the training subgraph, as well
      # as the evaluation subgraphs, while sharing the trainable parameters.
      def model(data, train=False):
        """The Model definition."""
        # 2D convolution, with 'SAME' padding (i.e. the output feature map has
        # the same size as the input). Note that {strides} is a 4D array whose
        # shape matches the data layout: [image index, y, x, depth].
        conv = tf.nn.conv2d(data,
                            conv1_weights,
                            strides=[1, 1, 1, 1],
                            padding='SAME')
        # Bias and rectified linear non-linearity.
        relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))
        # Max pooling. The kernel size spec {ksize} also follows the layout of
        # the data. Here we have a pooling window of 2, and a stride of 2.
        pool = tf.nn.max_pool(relu,
                              ksize=[1, 2, 2, 1],
                              strides=[1, 2, 2, 1],
                              padding='SAME')
        conv = tf.nn.conv2d(pool,
                            conv2_weights,
                            strides=[1, 1, 1, 1],
                            padding='SAME')
        relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases))
        pool = tf.nn.max_pool(relu,
                              ksize=[1, 2, 2, 1],
                              strides=[1, 2, 2, 1],
                              padding='SAME')
        # Reshape the feature map cuboid into a 2D matrix to feed it to the
        # fully connected layers.
        pool_shape = pool.get_shape().as_list()
        reshape = tf.reshape(
            pool,
            [pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]])
        # Fully connected layer. Note that the '+' operation automatically
        # broadcasts the biases.
        hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)
        # Add a 50% dropout during training only. Dropout also scales
        # activations such that no rescaling is needed at evaluation time.
        if train:
          hidden = tf.nn.dropout(hidden, 0.5, seed=SEED)
        return tf.matmul(hidden, fc2_weights) + fc2_biases
    
      # Training computation: logits + cross-entropy loss.
      logits = model(train_data_node, True)
      loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(
          logits, train_labels_node))
    
      # L2 regularization for the fully connected parameters.
      regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) +
                      tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases))
      # Add the regularization term to the loss.
      loss += 5e-4 * regularizers
    
      # Optimizer: set up a variable that's incremented once per batch and
      # controls the learning rate decay.
      batch = tf.Variable(0, dtype=data_type())
      # Decay once per epoch, using an exponential schedule starting at 0.01.
      learning_rate = tf.train.exponential_decay(
          0.01,                # Base learning rate.
          batch * BATCH_SIZE,  # Current index into the dataset.
          train_size,          # Decay step.
          0.95,                # Decay rate.
          staircase=True)
      # Use simple momentum for the optimization.
      optimizer = tf.train.MomentumOptimizer(learning_rate,
                                             0.9).minimize(loss,
                                                           global_step=batch)
    
      # Predictions for the current training minibatch.
      train_prediction = tf.nn.softmax(logits)
    
      # Predictions for the test and validation, which we'll compute less often.
      eval_prediction = tf.nn.softmax(model(eval_data))
    

    这段代码很直白,在定义了convolution1,convolution2,fully_connected1和fully_connected2层神经网络的weight和biases参数后,在模型函数里,我们通过conv2d, relu, max_pool等方式在两次重复后将得到的结果重新整理后输入那个fully connected的神经网络中,即matmul(reshape,fc1_weights) + fc1_biases。之后再经历了第二层的fully connected net后得到logits。定义loss以及optimizer等常见的过程后结果是由softmax来取得。这个逻辑我们在CIFAR10里也会见到,它的表达如下:

    def inference(images):
      """Build the CIFAR-10 model.
      Args:
        images: Images returned from distorted_inputs() or inputs().
      Returns:
        Logits.
      """
      # We instantiate all variables using tf.get_variable() instead of
      # tf.Variable() in order to share variables across multiple GPU training runs.
      # If we only ran this model on a single GPU, we could simplify this function
      # by replacing all instances of tf.get_variable() with tf.Variable().
      #
      # conv1
      with tf.variable_scope('conv1') as scope:
        # 输入的图片由于是彩图,有三个channel,所以在conv2d中,我们规定
        # 输出为64个channel的feature map。
        kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64],
                                             stddev=1e-4, wd=0.0)
        conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
        biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope.name)
        _activation_summary(conv1)
    
      # pool1
      pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                             padding='SAME', name='pool1')
      # norm1
      norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                        name='norm1')
    
      # conv2
      with tf.variable_scope('conv2') as scope:
        # 由于之前的输出是64个channel,即我们这里的输入,我们的shape就会
        # 是输入channel数为64,输出,我们也规定为64
        kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64],
                                             stddev=1e-4, wd=0.0)
        conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
        bias = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(bias, name=scope.name)
        _activation_summary(conv2)
    
      # norm2
      norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                        name='norm2')
      # pool2
      pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                             strides=[1, 2, 2, 1], padding='SAME', name='pool2')
    
      # local3
      with tf.variable_scope('local3') as scope:
        # Move everything into depth so we can perform a single matrix multiply.
        reshape = tf.reshape(pool2, [FLAGS.batch_size, -1])
        dim = reshape.get_shape()[1].value
        # 这里之前在reshape时的那个-1是根据tensor的大小自动定义为batch_size和
        # 剩下的,所以我们剩下的就是一张图的所有内容,我们将它训练并map到384
        # 个神经元节点上
        weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                              stddev=0.04, wd=0.004)
        biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
        local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
        _activation_summary(local3)
    
      # local4
      with tf.variable_scope('local4') as scope:
        #由于我们之前的节点有384个,这里我们进一步缩减为192个。
        weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                              stddev=0.04, wd=0.004)
        biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
        local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
        _activation_summary(local4)
    
      # softmax, i.e. softmax(WX + b)
      with tf.variable_scope('softmax_linear') as scope:
        # 这是softmax输出时的网络,我们由192个节点map到输出的不同数量上,这里假设
        # 有10类,我们就输出10个num_classes。
        weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                              stddev=1/192.0, wd=0.0)
        biases = _variable_on_cpu('biases', [NUM_CLASSES],
                                  tf.constant_initializer(0.0))
        softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
        _activation_summary(softmax_linear)
    
      return softmax_linear
    

    这里的逻辑跟之前的在框架上基本一样,不同在哪里呢?首先,这次我们的输入是彩图。学过图片处理的朋友肯定知道彩图有3个channel,而之前MNIST只是单个channel的灰白图。所以,在我们制作feature map的时候,由1个channel map到了32个(注,那个NUM_CHANNELS是1)。这里我们不过把NUM_CHANNELS给直接写为了3而已。另外,我们还运用了variable scope,这是一种很好的方式来界定何时对那些变量进行分享,同时,我们也不需要反复定义weight和biases的名字了。

    对Loss的定义由loss函数写明,其内容无非是运用了sparse_softmax_corss_entropy_with_logits,基本流程同于MNIST,这里将不详细描述。最后,cifar10.py里的train函数虽然逻辑很简单,但是也有值得注意的地方。代码如下:

    def train(total_loss, global_step):
      """Train CIFAR-10 model.
      Create an optimizer and apply to all trainable variables. Add moving
      average for all trainable variables.
      Args:
        total_loss: Total loss from loss().
        global_step: Integer Variable counting the number of training steps
          processed.
      Returns:
        train_op: op for training.
      """
      # Variables that affect learning rate.
      num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
      decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)
    
      # Decay the learning rate exponentially based on the number of steps.
      lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                      global_step,
                                      decay_steps,
                                      LEARNING_RATE_DECAY_FACTOR,
                                      staircase=True)
      tf.scalar_summary('learning_rate', lr)
    
      # Generate moving averages of all losses and associated summaries.
      loss_averages_op = _add_loss_summaries(total_loss)
    
      # Compute gradients.
      # control dependencies的运用。这里只有loss_averages_op完成了
      # 我们才会进行gradient descent的优化。
      with tf.control_dependencies([loss_averages_op]):
        opt = tf.train.GradientDescentOptimizer(lr)
        grads = opt.compute_gradients(total_loss)
    
      # Apply gradients.
      apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)
    
      # Add histograms for trainable variables.
      for var in tf.trainable_variables():
        tf.histogram_summary(var.op.name, var)
    
      # Add histograms for gradients.
      for grad, var in grads:
        if grad is not None:
          tf.histogram_summary(var.op.name + '/gradients', grad)
    
      # Track the moving averages of all trainable variables.
      variable_averages = tf.train.ExponentialMovingAverage(
          MOVING_AVERAGE_DECAY, global_step)
      variables_averages_op = variable_averages.apply(tf.trainable_variables())
    
      with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
        train_op = tf.no_op(name='train')
    
      return train_op
    

    这里多出的一些内容为收集网络运算时的一些临时结果,如记录所有的loss的loss_averages_op = _add_loss_summaries(total_loss)以及对参数的histogram:tf.histogram_summary(var.op.name, var)。值得注意的地方是这里多次地使用了control_dependency概念,即dependency条件没有达成前,dependency内的代码是不会运行的。这个概念在Tensorflow中有着重要的意义,这里是一个实例,给大家很好的阐述了这个概念,建议有兴趣的朋友可以多加研究。至此,图片的训练便到此为止。

    那么eval文件是如何评价模型的好坏的呢?让我们来简单的看下eval文件的内容。我们首先通过evaluate函数中的cifar10.inputs函数得到输入图片以及其对应的label,之后,通过之前介绍的inference函数,即CNN框架得到logits,之后我们通过tensorflow的in_top_k函数来判断我们得到的那个logit是否在我们label里。这里的k被设置为1并对结果做展示以及记录等工作。有兴趣的朋友可以仔细阅读这段代码,这里将不详细说明。

    至此,系统完成,我们对于如何建立一个CNN系统有了初步了解。

    转载于:https://www.cnblogs.com/edwardbi/p/5598931.html

    展开全文
  • http://www.cnblogs.com/Finley/p/7655445.html http://www.cnblogs.com/lipings/p/7331855.html 转载于:https://www.cnblogs.com/gaoyang2018/p/8717919.html
  • tensorflow cnn教程

    2018-12-06 16:00:24
    tensorflow cnn教程 无论在nlp还是cv领域,cnn都是非常好用的工具,它擅长提取局部特征。 最简单的例子 import tensorflow as tf tf.enable_eager_execution() picture = tf.reshape([0.0, 0, 0, 0, 0, 1, 3...
  • tiny-cnn安装教程

    千次阅读 2017-09-14 22:18:39
    tiny-cnn最近在看caffe源码,看得有点吃力,可能还是因为c++的基础不够。上知乎的时候发现有一个轻量级别的cnn框架,称为tiny-cnn,不过经过多个版本的更迭,现在已经取名为tiny-dnn,增加了包括RNN在内的多种神经...
  • Faster R-CNN教程 Faster R-CNN教程 最后更新日期:2016年4月29日 本教程主要基于python版本的faster R-CNN,因为python layer的使用,这个版本会比matlab的版本速度慢10%,但是准确率应该是差不多的。 目前...
  • Faster R-CNN基础教程

    2017-01-13 16:18:27
    教程主要基于python版本的faster R-CNN,因为python layer的使用,这个版本会比matlab的版本速度慢10%,但是准确率应该是差不多的。 目前已经实现的有两种方式: Alternative trainingApproximate joint ...
  • CNN入门教程与实战

    千次阅读 2018-01-11 21:51:05
    filter:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数...
  • 卷积神经网络(CNN)简易教程

    千次阅读 2020-05-25 00:39:29
    我们将学习什么是CNN, CNN如何利用大脑的启发进行物体识别,CNN是如何工作的。 让我们来了解一下我们的大脑是如何识别图像的 根据诺贝尔奖获得者Hubel和Wiesel教授的说法,视觉区域V1由简单细胞和复杂细胞组成。简单...
  • CNN一些比较有用的教程 吴恩达deeplearning之CNN—卷积神经网络入门 https://blog.csdn.net/ice_actor/article/details/78648780 详解卷积神经网络(CNN) ...cnn(卷积神经网络)比较系统的讲解 ...
  • SPARK-CNN 安装及使用教程
  • ubuntu16.04环境下,基于caffe框架,使用GPU。Faster R-CNN编译和搭建教程,包括问题和报错的解决,demo运行。相关配套的源码和模型有下载地址
  • CNN快速教程1-3

    2011-07-23 22:16:26
    在网上搜集整合的版本,有重点标记。 读后很有启发,对入门的同学可以看看
  • Tensorflow2教程-CNN变体网络

    千次阅读 2019-03-30 17:43:15
    Tensorflow 2.0 教程持续更新 :...完整tensorflow2.0教程代码请看tensorflow2.0:中文教程tensorflow2_tutorials_chinese(欢迎star) 入门教程: TensorFlow 2.0 教程- Keras 快速入门 Tenso...
  • CNN识别验证码的实用教程, tensorflow captcha recognization practical tutorial本文重在实用,让你半天能搞定验证码的识别,包括从训练材料的获取、预处理和训练,以及predict服务器的搭建。本文举例的对象(为某...
  • CNN识别验证码的实用教程,从数据标定到搭建服务, tensorflow captcha recognization practical tutorial
  • Tensorflow2教程-基础CNN网络

    千次阅读 2019-03-30 17:41:09
    tensorflow2-基础CNN网络 1.构造数据 (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() x_train = x_train.reshape((-1,28,28,1)) x_test = x_test.reshape((-1,28,28,1)) print...
  • 配置Faster R-CNN教程 写给自己的话:最近买了个TITAN RTX,所以要重新配下电脑,但这个过程就很坑了,所以要记下来,不然以后忘了再来盲目配一次,估计要自闭 1.重装系统 做好系统盘后,重启,按DEL键进入,...
  • 用 Tensorflow 建立 CNN

    千次阅读 2017-05-01 11:58:20
    还有 google 在 udacity 上的 CNN 教程CNN(Convolutional Neural Networks) 卷积神经网络简单讲就是把一个图片的数据传递给CNN,原涂层是由RGB组成,然后CNN把它的厚度加厚,长宽变小,每做一层都这样被拉长,最后...
  • NLP教程 TF_IDF 词向量 句向量 Seq2Seq 语言生成模型 CNN的语言模型 目录NLP教程怎么卷积翻译秀代码局限性全部代码 一想到用深度学习解决语言问题,我们自然而然的就能想到使用循环神经网络RNN这一系列的模型。 而...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,380
精华内容 552
关键字:

cnn教程