Kaka Chen 2018-08-26T14:18:58+00:00 silver.accc@gmail.com 南方海岸上最明亮的那颗星 2018-08-26T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/08/南方海岸上最明亮的那颗星 儿时记忆中的深圳

]]>
初窥capsnet 2018-08-22T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/08/初窥CapsNet #

]]>
从selective search到faster rcnn 2018-08-15T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/08/从Selective Search到Faster RCNN 前言

换工作后,开始有了真正的实际应用业务量和比较系统而聚焦的研究调研方向,过去两周主要针对基于卷积神经网络和其衍生网络,本文主旨在于简单介绍一下这一整套update pipeline:Selective Search, Spartial Pyramid Pooling,R-CNN,Fast R-CNN,Faster R-CNN,以供之后参考,暂不涉及模型的具体调优选参细节。


Selective Search

Selective Search for Object Recognition Jasper R. R. Uijlings, Koen E. A. van de Sande, Theo Gevers, Arnold W. M. Smeulders

总体而言是用了segmentation和grouping的方法来进行object proposal,然后用了一个SVM进行物体识别。

需求来源是,在物体识别时候,往往需要根据多种特征模式来进行物体层级之间的区分以找出独立存在的各个物体,区分依据诸如颜色、纹理、嵌套结构等。因此SS采取了Multiscale的办法,即将原图分割成多个region,再由多个region grouping成更大的region,重复该过程,直到最后生成multiscale的region了。

流程概述:

  • 输入:彩色图片(三通道)
  • 输出:物体位置的可能结果L

  • 使用Efficient GraphBased Image Segmentation中的方法来得到初始化region(Kinds of greedy method)
  • 得到所有region之间两两的相似度
  • 合并最像的两个region
  • 重新计算新合并region与其他region的相似度
  • 重复上述过程直到整张图片都聚合成一个大的region
  • 使用一种随机的计分方式给每个region打分,按照分数进行ranking,取出top k的子集,获取每个区域的Bounding Boxes,就是selective search的结果

其中涉及到的相似度有:颜色相似度、纹理相似度、大小相似度、吻合相似度、综合各种距离

Object Recognition

特征提取+SVM

  • 特征用了HoG和BoW
  • SVM用的是SVM with a histogram intersection kernel
  • 训练时候:正样本:groundtruth,负样本,seletive search出来的region中overlap在20%-50%的。
  • 迭代训练:一次训练结束后,选择分类时的false positive放入了负样本中,再次训练

IOU

IOU,aka Intersection-over-Union,简单来讲就是模型产生的目标窗口和原来标记窗口的交叠率。具体我们可以简单的理解为: 即检测结果(DetectionResult)与 Ground Truth 的交集比上它们的并集,即为检测的准确率 IoU :

或者

其中所谓的ground truth即正确的标注物体位置。

IoU是一个简单的测量标准,只要是在输出中得出一个预测范围(bounding boxex)的任务都可以用IoU来进行测量。为了可以使IoU用于测量任意大小形状的物体检测,我们需要:

  1. ground-truth bounding boxes(人为在训练集图像中标出要检测物体的大概范围);

  2. 我们的算法得出的结果范围。

也就是说,这个标准用于测量真实和预测之间的相关度,相关度越高,该值越高。


SSP

Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

SSP,aka Spatial Pyramid Pooling,需要解决的问题有二:

  1. 现有的CNN做图像识别要求输入图像尺寸固定,因此在喂入数据时会对图像做crop或wrap等操作,丢失一定图像信息。
  2. 一个CNN通常分为卷基层conv和全连接层fc两部分,所以事实上单张图片只需要计算一遍卷积特征就可以,而将所有region proposals都做缩放会产生大量计算。

根据这两个原则,使用Spatial Pyramid Pooling Layer层代替掉原先卷积层中最后一层Pooling层,使得任意尺寸的卷积层输出经过SSP层之后,都输出固定大小的向量。这个SSP利用的是一种SPM(Statistical Parametric Mapping)的思想,即将一幅图分解成若干个份,如1份、4份、8份,然后将其每一块提取特征后融合在一起,就能兼容多个尺度的特征了。而在SSP中,将份数固定为自然数的平方次份,如1、4、9、16等,其数学依据在于任何一个自然数都可以写成若干个自然数的平方和。

SSP的优点:

  • 不论输入尺寸如何,都可以输出固定尺寸
  • 使用多个窗口Pooling Window
  • 可以用同一个图片的不同scale作为输入
  • 降低Overfitting,更容易收敛,可自检

R-CNN

Rich Feature Hierarchies for Accurate Object Detection and Semantic Segmentation R. Girshick, J. Donahue, T. Darrell, J. Malik

RCNN所解决的两个问题:

  1. 传统目标识别算法以sliding windows的办法,而RCNN采取Selective Search预先生成region proposal,之后只处理这些proposals
  2. 传统目标识别需要在区域中人工设定特征(Haar, HOG),而RCNN则使用深度神经网络进行特征提取。

整体框架:(其组成部分包括生成类独立的region proposal模块、提取特征的CNN模块和一组SVM分类器)

  1. 用selective search方法划分2k-45k个region;
  2. 分别对每个region提取特征,最后将提取到的特征送到k(k的取值与类别数相等)个svm分类器中识别以及送到一个回归器中去调节检测框的位置;
  3. 将k个SVM分类器中得分最高的类作为分类结果,将所有得分都不高的region作为背景;
  4. 通过回归器调整之后的结果即为检测到的位置。

其中关键步骤有:

  • 候选区域:使用selective search:过分分割->穷举依靠相似度合并->输出最大IOU的region proposal
  • 特征提取:通过训练好的Alex-Net,先将每个region固定到227x227的尺寸,然后对于每个region都提取一个4096维的特征。在resize到227x227的过程中,在region外面扩大了一个16像素宽度的边框,region的范围就相应扩大了,考虑了更多的背景信息。
  • CNN训练:首先拿到Alex-Net在imagenet上训练的CNN最为预训练,然后将网络的最后一个全连接层的1000类改为N+1(N为类别的数量,1是背景)来微调同于提取特征的CNN。为了保证训练只是对网络的微调而不是大幅度变化,调整学习率为0.001。计算每个region proposals与ground-truth的IoU,IoU阈值设为0.5,大于0.5的IoU作为正样本,小于0.5的为负样本。在训练的每一次迭代中都使用32个正样本(包括所有类别)和96个背景样本组成的128张图片的batch进行训练(这么做是因为正样本图片太少了)。
  • SVM分类器训练:训练N(N为类别数)个线性svm分类器,分别对每一个类做一个二分类。在这里,作者将IoU大于0.5的作为正样本,小于0.3的作为负样本。(为什么不是0.5:因为当设为0.5的时候,MAP下降5%,设为0的时候,MAP下降4%,所以取中间值0.3)。后面说到softmax在VOC 2007上测试的MAP比SVM的MAP低,因为softmax的负样本(背景样本)是随机选择的即在整个网络中是共享的,而SVM的负样本是相互独立的,每个类别都分别有自己的负样本,SVM的负样本更“hard”,所以SVM的分类准确率更高。
  • Bounding Box Regression:通过训练一个回归器来对region的范围进行一个调整,因为region最开始只是用selective search的方法粗略得到的,通过调整以后可以得到更加精确的位置。

Fast RCNN

Fast R-CNN Ross Girshick

Fast R-CNN主要解决R-CNN的以下问题:

  1. 训练、测试时速度慢: R-CNN的一张图像内候选框之间存在大量重叠,提取特征操作冗余。而Fast R-CNN将整张图像归一化后直接送入深度网络,紧接着送入从这幅图像上提取出的候选区域。这些候选区域的前几层特征不需要再重复计算。
  2. 训练所需空间大: R-CNN中独立的分类器和回归器需要大量特征作为训练样本。Fast R-CNN把类别判断和位置精调统一用深度网络实现,不再需要额外存储。

训练过程:

  1. 网络首先用几个卷积层(conv)和最大池化层处理整个图像以产生conv特征图,同时利用selective search在任意尺寸的图像上提取约2k个region proposals。
  2. 然后,对于每个对象建议框(object proposals ),感兴趣区域(region of interest——RoI)池层从特征图提取固定长度H*W的特征向量。
  3. 每个H*W大小的特征向量被输送到分支成两个同级输出层的全连接(fc)层序列中(由SVD分解实现)。
  4. 其中一层进行分类,对 目标关于K个对象类(包括全部“背景background”类)产生softmax概率估计,即输出每一个RoI的概率分布;
  5. 另一层进行bbox regression,输出K个对象类中每一个类的四个实数值。每4个值编码K个类中的每个类的精确边界盒(bounding-box)位置,即输出每一个种类的的边界盒回归偏差。

整个结构是使用多任务损失的端到端训练(trained end-to-end with a multi-task loss)。

其中Fast RCNN最重要的创新点在于:

  1. 规避R-CNN中冗余的特征提取操作,只对整张图像全区域进行一次特征提取;
  2. 用RoI pooling层取代最后一层max pooling层,同时引入建议框信息,提取相应建议框特征;
  3. Fast R-CNN网络末尾采用并行的不同的全连接层,可同时输出分类结果和窗口回归结果,实现了end-to-end的多任务训练【建议框提取除外】,也不需要额外的特征存储空间【R-CNN中这部分特征是供SVM和Bounding-box regression进行训练的】;
  4. 采用SVD对Fast R-CNN网络末尾并行的全连接层进行分解,减少计算复杂度,加快检测速度。

Faster RCNN

Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks Shaoqing Ren Kaiming He Ross Girshick Jian Sun

Faster R-CNN可以简单地看做“区域生成网络RPNs + Fast R-CNN”的系统,用区域生成网络代替FastR-CNN中的Selective Search方法。Faster R-CNN这篇论文着重解决了这个系统中的三个问题:

  1. 如何设计区域生成网络;
  2. 如何训练区域生成网络;
  3. 如何让区域生成网络和Fast RCNN网络共享特征提取网络。

在整个Faster R-CNN算法中,有三种尺度:

  1. 原图尺度:原始输入的大小。不受任何限制,不影响性能。
  2. 归一化尺度:输入特征提取网络的大小,在测试时设置,源码中opts.test_scale=600。anchor在这个尺度上设定。这个参数和anchor的相对大小决定了想要检测的目标范围。
  3. 网络输入尺度:输入特征检测网络的大小,在训练时设置,源码中为224*224。

流程概要:输入图片-生成region proposal-特征提取-分类-位置精修

主要由PRN候选框提取模块和Fast R-CNN检测模块组成。其中,RPN是全卷积神经网络,用于提取候选框;Fast R-CNN基于RPN提取的proposal检测并识别proposal中的目标。

RPN

RPN,aka Region Proposal Network,是一个全卷积神经网络,用来提取检测区域,它能和整个检测网络共享全图的卷积特征,使得区域建议几乎不花时间,大大提高了原先用SSP net做区域建议时的计算效率。RPN可以针对生成检测建议框的任务端到端地训练,能够同时预测出object的边界和分数。只是在CNN上额外增加了2个卷积层(全卷积层cls和reg)。

  • reg层:预测proposal的anchor对应的proposal的(x,y,w,h)
  • cls层:判断该proposal是前景(object)还是背景(non-object)

RPN的具体流程如下:使用一个小网络在最后卷积得到的特征图上进行滑动扫描,这个滑动网络每次与特征图上n*n(论文中n=3)的窗口全连接(图像的有效感受野很大,ZF是171像素,VGG是228像素),然后映射到一个低维向量(256d for ZF / 512d for VGG),最后将这个低维向量送入到两个全连接层,即bbox回归层(reg)和box分类层(cls)。sliding window的处理方式保证reg-layer和cls-layer关联了conv5-3的全部特征空间。

Bounding-Box Regression

如图所示,绿色的框为飞机的Ground Truth,红色的框是提取的Region Proposal。那么即便红色的框被分类器识别为飞机,但是由于红色的框定位不准(IoU<0.5),那么这张图相当于没有正确的检测出飞机。如果我们能对红色的框进行微调,使得经过微调后的窗口跟Ground Truth更接近,这样岂不是定位会更准确。确实,Bounding-box regression 就是用来微调这个窗口的。

RPN与Fast R-CNN特征共享

Faster-R-CNN算法由两大模块组成:PRN候选框提取模块;Fast R-CNN检测模块。

RPN和Fast R-CNN都是独立训练的,要用不同方式修改它们的卷积层。因此需要开发一种允许两个网络间共享卷积层的技术,而不是分别学习两个网络。注意到这不是仅仅定义一个包含了RPN和Fast R-CNN的单独网络,然后用反向传播联合优化它那么简单。原因是Fast R-CNN训练依赖于固定的目标建议框,而且并不清楚当同时改变建议机制时,学习Fast R-CNN会不会收敛。

RPN在提取得到proposals后,作者选择使用Fast-R-CNN实现最终目标的检测和识别。RPN和Fast-R-CNN共用了13个VGG的卷积层,显然将这两个网络完全孤立训练不是明智的选择,作者采用交替训练(Alternating training)阶段卷积层特征共享:

第一步,我们依上述训练RPN,该网络用ImageNet预训练的模型初始化,并端到端微调用于区域建议任务;

第二步,我们利用第一步的RPN生成的建议框,由Fast R-CNN训练一个单独的检测网络,这个检测网络同样是由ImageNet预训练的模型初始化的,这时候两个网络还没有共享卷积层;

第三步,我们用检测网络初始化RPN训练,但我们固定共享的卷积层,并且只微调RPN独有的层,现在两个网络共享卷积层了;

第四步,保持共享的卷积层固定,微调Fast R-CNN的fc层。这样,两个网络共享相同的卷积层,构成一个统一的网络。


Conclusion

RCNN、Fast RCNN和Faster RCNN三者关系:

  使用方法 缺点 改进

R-CNN
(Region-based ConvolutionalNeural Networks)

1、SS提取RP;
2、CNN提取特征;
3、SVM分类;
4、BB盒回归。

1、 训练步骤繁琐(微调网络+训练SVM+训练bbox);
2、 训练、测试均速度慢 ;
3、 训练占空间

1、 从DPM HSC的34.3%直接提升到了66%(mAP);
2、 引入RP+CNN

Fast R-CNN

(Fast Region-based Convolutional Neural Networks)

1、SS提取RP;
2、CNN提取特征;
3、softmax分类;
4、多任务损失函数边框回归。

1、 依旧用SS提取RP(耗时2-3s,特征提取耗时0.32s);
2、 无法满足实时应用,没有真正实现端到端训练测试;
3、 利用了GPU,但是区域建议方法是在CPU上实现的。

1、 由66.9%提升到70%;
2、 每张图像耗时约为3s。

Faster R-CNN

(Fast Region-based Convolutional Neural Networks)

1、RPN提取RP;
2、CNN提取特征;
3、softmax分类;
4、多任务损失函数边框回归。

1、 还是无法达到实时检测目标;
2、 获取region proposal,再对每个proposal分类计算量还是比较大。

1、 提高了检测精度和速度;
2、 真正实现端到端的目标检测框架;
3、 生成建议框仅需约10ms。
]]>
Adaboost简析 2018-05-03T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/05/Adaboost简析 AdaBoost的主要用途是将若干个‘弱分类器(weak classifier)’结合起来行程一个‘强分类器(strong classifier)’,主要用于二元分类上。其中,弱分类器的意思是性能较差的分类器,大概只比随机分类好一丢丢,比如简单地根据一个人的身高来判断性别,假定判别依据是172以上是男性,以下是女性,那最后的正确率应该也仅仅是过50%。

AdaBoost可以应用在所有类型的分类算法上,因此它是建立于其他所有优秀的分类算法上,而它自身并不能算作一种分类算法。

AdaBoost真正做的是两件事情:

一,为你将要训练的分类器提供合适的训练数据集,这个训练集的选择是根据上一个分类器的结果而定。

二,在得到联合的分类结果时,为这个结果中各个分类器得到的结果选定合适的权重


数据集选择

对于要被结合的每一个弱分类器而言,都必须在总训练集的一个随机的子集上进行训练,这些自己是可以互相重叠的,并非是被划分成等大小的若干个分片。AdaBoost给每个训练样本都分配一个权重weight,这个权重决定了每一个样本可能在训练集中出现的概率,越高权重的样本越可能被选进到训练集中。当训练结束之后,AdaBoost将会提高那些被错分类的样本的权重,使得它们之后在分类器训练过程中发挥更大的作用,希望之后的分类器能在这些样本上能有更好的表现。具体的公式会在之后定义。


分类器输出权重

当每一个分类器都训练完毕,这些分类器各自的结果的权重会根据它们的正确率来计算,越精确的分类器会得到越高的权重。一个正确率50%的分类器会得到权重0,认为其对正确分类没什么作用;而正确率低于50%的分类器则会被赋予负值作为权重,认为其对正确分类具有反面作用。


正式定义

AdaBoost的最终结果是T个弱分类器结果的线性组合取正负号,其中ht(x)弱分类器t的输出结果(在论文中姑且认为位于区间(-1, 1)),αt弱分类器t的权重(由AdaBoost计算得到)。

每个弱分类器是一次训练一个,当一个弱训练器训练完毕,我们会根据其结果来更新下一个弱分类器训练所需的训练集中,各个训练样本可能出现的概率。

其中第一个弱分类器所训练的训练集中,各个训练样本出现概率是相等的,在这之后我们更新得到这个训练集结果在最终结果中所占的权重值:

αt是各个输出值的权重,基于分类器的错误率εtεt是训练集Dt中,分类结果与样本标签不等的概率。

下图是αt和错误率εt之间的关系:

  1. 当错误率为0时,权重值急速增长,表示越好的分类器会被赋予越大的权重值。
  2. 当错误率为50%时,会被赋予权重0,也就是不需要几乎随机猜测的分类器。
  3. 当错误率接近1时,权重值急速减少变成负值。

当权重值计算结束后,我们依据下图公式更新训练样本的权重值:

其中Dt是表示权重的向量,每一个训练样本都有一个权重。其中i是当前样本序号。在论文中,Dt被描述成一种分布,其中的D(i)表示了编号为i的训练样本会被下一个训练集选中的概率。

为了使得Dt成为一种分布形式,其中所有的概率之和需要等于恒值——1。为了确保这一点,我们在权重上进行正则化,将每一个权重除以所有权重的和。

]]>
Python yield关键字 2018-04-17T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/04/Python yield关键字 1. 可迭代对象

Python中有很多可迭代对象,包含迭代器(iterator)生成器(generator),后者就是yield关键字所返回的对象。其他可迭代对象包括序列(包括字符串、列表和tuple)和字典。其中所有可迭代对象都必须实现—__iter__方法,而所有迭代器都必须实现__iter__next方法。

迭代器

迭代器是抽象的一个数据流对象,调用next方法可以获得被迭代的下一个元素,直到没有元素后抛出StopIteration异常。迭代器的 __iter__() 方法返回迭代器自身;因此迭代器也是可迭代的。

迭代器协议

迭代器协议(iterator protocol)指的是容器类需要包含一个特殊方法。如果一个容器类提供了 __iter__() 方法,并且该方法能返回一个能够逐个访问容器内所有元素的迭代器,则我们说该容器类实现了迭代器协议。

生成器函数

与普通函数不同,生成器函数被调用后,其函数体内的代码并不会立即执行,而是返回一个生成器(generator-iterator)。当返回的生成器调用成员方法时,相应的生成器函数中的代码才会执行。

def square():
    for x in range(4):
        yield x ** 2
# square()是一个生成器函数,
# 每次返回后再次调用函数状态都会与从上次返回时一致
square_gen = square()
for x in square_gen:
    print(x)

生成器的方法

  • generator.next():从上一次在 yield 表达式暂停的状态恢复,继续执行到下一次遇见 yield 表达式。当该方法被调用时,当前 yield 表达式的值为 None,下一个 yield 表达式中的表达式列表会被返回给该方法的调用者。若没有遇到 yield 表达式,生成器函数就已经退出,那么该方法会抛出 StopIterator 异常。
  • generator.send(value):和 generator.next() 类似,差别仅在与它会将当前 yield 表达式的值设置为 value。
  • generator.throw(type[, value[, traceback]]):向生成器函数抛出一个类型为 type 值为 value 调用栈为 traceback 的异常,而后让生成器函数继续执行到下一个 yield 表达式。其余行为与 generator.next() 类似。
  • generator.close():告诉生成器函数,当前生成器作废不再使用。

下一个

当调用generator.next()时,生成器函数会从当前函数执行到下一个yield为止。如

def next_yield():
    yield 1
    print("first yield")
    yield 2
    print("second yield")
    yield 3
    print("third yield")
#     yield from f123()
m = next_yield()

>>next(m)
# 1
>>next(m)
# first yield
# 2
>>next(m)
# second yield
# 3

用send传入输入信号量

def send_yield():
    x = 1
    while True:
        y = (yield x)
        x += y
        print("x = %d, y = %d" %(x, y))
m = send_yield()

next(m)
>>1
m.send(2)
>>x = 3, y = 2
>>3
m.send(3)
>>x = 6, y = 3
>>6
……

几个常见用法

yield空

用法类似于循环中的中断器,或者返回一个none

def myfun(total):
    for i in range(total):
        print(i + 1)
        yield
a = myfun(3)
for i in a:
    print(i)

# 1
# None
# 2
# None
# 3
# None

yield from

从一个特定集合里做yield

def myfun(total):
    for i in range(total):
        yield i
    yield from ['a', 'b', 'c']
    
m = myfun(3)
next(m)
>>0
next(m)
>>1
next(m)
>>2
next(m)
>>'a'
next(m)
>>'b'
next(m)
>>'c'

可以用此写一个永不溢出的循环yield

def cycle(p):
    yield from p
    yield from cycle(p)
a = cycle('abcde')

return yield

return只是起到终止函数的作用

def myfun(total):
    yield from range(total)

>>> a = myfun(4)
>>> a
<generator object myfun at 0x000001B61CCB9CA8>
>>> for i in a:
...     print(i)
...
0
1
2
3

这样a就是一个生成器

def myfun(total):
	return (yield from range(total))
a = myfun(4)
for i in a:
   print(i)
   
0
1
2
3
]]>
踏着水路去香江水路有多长——从澳门到香港 2018-04-13T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/04/踏着水路去香江水路有多长——从澳门到香港 之前去台湾之前就办了港澳通行证和一次香港签注,结果拖着没用,一直到今年的清明假期借着陪女朋友打针的机会,才真正第一次踏上香港和澳门的土地。出生于90年代的我,在小时候亲眼见证了香港和澳门的回归,所以一直对这两块曾经的西方殖民地充满了憧憬。


小巷子里的澳门

儿时的记忆里,最早到达澳门的葡萄牙殖民者是以曝晒货物为名,用一张细碎的牛皮圈了第一块地,但是当我第一次走在澳门街头的时候,才意识到这地方是真的小。不光地方小,从澳门半岛的西侧到东侧仅仅四五公里,而且澳门岛上的道路也极其狭窄,我甚至都不敢称之为道路,绝大多数都是仅能过一辆车的小巷子。小巷两侧是杂乱的广告牌,像极了家乡90年代的老巷。当走累了一抬头的时候,找到了一家味道极佳的甜品店,第一次吃到桂圆荸荠羹。

而澳门半岛上的几个古迹景点更是这样,路小人多,挤得水泄不通,基本都断绝了我想拍一张冷淡instgram风景照的念头。不过澳门街边高高吊起的红色花盆,很有之前旧金山街头的感觉。


挥金如土的赌场

之前在拉斯维加斯因为时间关系没能进威尼斯人酒店一看,这次来澳门特意选了威尼斯人作为参观,虽然不如永利宫或者维恩等新酒店豪华,但是名声在外实在是不忍错过。

酒店里的购物街沿着人造河而建,河里是威尼斯人著名的贡多拉。在这里终于第一次逛了曼联官方的纪念品店,虽然商品众多,但是看了一下价格普遍偏贵也就没有买什么。

我一直觉得我这个人很不喜欢赌博,因为输赢的大起大落总觉得不是自己能力所能完全控制,这种无助感让我很不习惯,因此每次去大赌场我都只能做壁上观。


香港游乐园

所有描述香港的旅游攻略里,海洋公园和迪斯尼都是位列前二的景点。因为我自己也去过国内外不少类似的游乐园,因此香港海洋乐园除了跨越多个山头的海边缆车外,并没有很吸引我,游乐设施也比较老旧,而且当天天气不好,阴风狂作,因此也没很高的游玩兴致。

唯一吸引我眼球的是海洋公园中的柱状水箱,无数银色的鱼环绕箱体飞速穿梭的样子很是震撼。

香港迪斯尼虽然很小,却十分精致,基本布局和其他几个迪斯尼类似。我女票第一次来迪斯尼,我能感受到她身上在我第一次去迪斯尼时候体会到的感动。那种不管你身处何方,不管你年岁几何,不管你自己是否还能察觉到,隐藏在内心深处孩提时童话的记忆。我第一次在奥兰多迪斯尼看米奇音乐会,那种汹涌而出的泪水和感动这一次出现在了她脸上。

香港迪斯尼夜晚有花灯夜游,别有一番味道。


儒释道大融合的黄大仙祠

香港一直都是一个不断融合的地方,最明显的感觉就是将中国文化和西方制度的融合,仿佛没有一件东西是自上而下亘古不变的。闵粤一代,一直是供奉妈祖天后,但随着中原文化的辐射,妈祖也受到了佛道之学的影响,在澳门的妈祖阁就能同时找到供奉的观音菩萨和太乙字样。在香港最著名的宗教圣地黄大仙祠,更是如此,俨然一个儒释道三教鼎足而立的祠堂,既有佛教诸圣,又有道家太极,还有孔夫子和七十二贤,济济一堂。

我之前很不喜欢求签问佛,因为我害怕抽到下签坏了心情。我一直觉得为人做事尽人事知天命,没必要特意窥探,而这次在女朋友的怂恿下求了一支事业签,万难想到是上上好签,也算幸事。


寸土寸金的维港

从尖沙咀能坐渡轮来到中环,一路上能看到香港著名的天际线,不愧东方之珠美名。

很感激我女朋友在打完针之后还陪我晚上出来,我们还坐了香港摩天轮将维港夜景一览无遗。


夜游兰桂坊

香港最著名的夜店街人头攒动,每个店里都充斥着酒精、荷尔蒙和嘈杂的音乐。可惜打完针的女朋友不能喝酒,只能吃了一些烤物就匆匆离开。


东亚金融中心和粤南小渔村

尽管香港看起来金碧辉煌,但不论是我租住的airbnb还是走在旺角街边,无不能感受到这个表面光鲜的城市背后难以掩盖的落魄。狭小的住宅、未经打扫的街道和老旧的小店是这个城市的常态。

其实香港和澳门不同之处,在于殖民澳门的葡萄牙在大航海时代结束之后就失去了对世界各地殖民地的直接控制,而且自身经济也日渐败落,因此澳门在二十世纪中叶开放合法赌博业后,收获了来自中国大陆的大量资金,让澳门民众越来越享受到来自内地的利益。而香港则不然,大英帝国直到今天依然是全世界影响力最大的国家,拥有当世领先的经济、政治和科技水平,而香港得天独厚的地理位置和十九世纪以来背后的中国内地的衰落,让它得以成为世界金融市场在东八区的代理人,和纽约、伦敦齐名为世界三大经济中心——“纽伦港”。

而随着近三十年来中国的不断开放和发展,香港的地位越来越尴尬。它身后的广州作为中国第三大城市成为了南粤文化中心,深圳借着改革开放的东风成了科技中心,而不论体量还是位置更占优的上海也在一点点分化它金融中心的地位。当年中共之所以在解放战争时期没有进兵港澳,也是想留下一两个窥探世界的窗口。而事实上这个举动也确实很明智,在朝鲜战争之后中国遭到西方世界进出口限制的时候,是澳门和香港作为一个官方默认睁一只眼闭一只眼的“走私小窗”帮助着新中国,当然英政府比葡政府更严格地打击着这一行为。印象中四小龙时期的香港,标榜自己的是“经济”和“科技”为主的硬实力,而现在的香港不再谈这两点,更多把“民主”和“自由”作为标签。我个人来讲,其实还是很认可香港的这个模式,因为我也希望看到一个民主自由的窗口在中国大环境下的发展情况。中国自古以来就缺少发出不同声音的勇气和容忍不同声音的胸怀。但是不得不承认,在中国内地那么大的经济和生产力体量下,香港显得十分瘦弱,这个差距不是说简单的“更好的政治制度和更廉洁的官僚体制”所能弥补的。甚至过于执着于一些意识形态和过程正确的观念,反而会让香港失去和擅长于“集中力量办大事”的内地进行扳手腕的资格。香港的角色边缘化和发展缓慢化是一个不可逆也不可阻挡的过程,其之前的飞速发展很大程度是吃了广袤的中国内地从十九世纪以来的积弱和新中国成立之后的错失发展时机,但随着内地的发展,香港的生态位必然逐渐减少,然而很多这个时代的香港人会把香港的衰落怪罪于回归中国和北京政府的干预,其实我们也不过只是瓜田李下罢了。所幸今现在的香港一有社会红利能吃,二有金融中心底子能抗,三有“墙外”这一吸引西方金融巨头设立远东机构得天独厚的条件,因此还是能间于齐楚,左右逢源很长一段时间。

可能是在作为金融中心城市的人习惯了压抑而快节奏的生活,和其他国家和地区人相比,香港人显得很冷漠而孤傲,同样纽约如是,芝加哥如是,香港也如是。无耐心的用粤语搪塞他人成了我在香港习以为常的境遇。而对内地人的敌视则随处可见,尽管我也不清楚明明是大量的内地人消费着香港原本略显溢出的零售业,却被冠以“蝗虫”之名。

]]>
Python regex正则表达式 2018-03-29T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/03/Python RegEx正则表达式 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。

Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。

re 模块使 Python 语言拥有全部的正则表达式功能。

compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。

re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。

本章节主要介绍Python中常用的正则表达式处理函数。


re.match和re.search函数

  • re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
  • re.match(pattern, string, flags=0)
参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
pos 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
endpos 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
lastindex 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
lastgroup 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。

方法:

  • group([group1, …]): 获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
  • groups([default]): 以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
  • groupdict([default]): 返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
  • start([group]): 返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
  • end([group]): 返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
  • span([group]): 返回(start(group), end(group))。
  • expand(template): 将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符'0',只能使用\g<1>0。
import re
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')
 
print("m.string:", m.string)
print("m.re:", m.re)
print("m.pos:", m.pos)
print("m.endpos:", m.endpos)
print("m.lastindex:", m.lastindex)
print("m.lastgroup:", m.lastgroup)
 
print("m.group(1,2):", m.group(1, 2))
print("m.groups():", m.groups())
print("m.groupdict():", m.groupdict())
print("m.start(2):", m.start(2))
print("m.end(2):", m.end(2))
print("m.span(2):", m.span(2))
print(r"m.expand(r'\2 \1\3'):", m.expand(r'\2 \1\3'))
 
### output ###
# m.string: hello world!
# m.re: <_sre.SRE_Pattern object at 0x016E1A38>
# m.pos: 0
# m.endpos: 12
# m.lastindex: 3
# m.lastgroup: sign
# m.group(1,2): ('hello', 'world')
# m.groups(): ('hello', 'world', '!')
# m.groupdict(): {'sign': '!'}
# m.start(2): 6
# m.end(2): 11
# m.span(2): (6, 11)
# m.expand(r'\2 \1\3'): world hello!

  • re.search 扫描整个字符串并返回第一个成功的匹配。
  • re.search(pattern, string, flags=0)
参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
groups 表达式中分组的数量。
groupindex 以表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。
p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)
 
print("p.pattern:", p.pattern)
print("p.flags:", p.flags)
print("p.groups:", p.groups)
print("p.groupindex:", p.groupindex)
 
### output ###
# p.pattern: (\w+) (\w+)(?P<sign>.*)
# p.flags: 16
# p.groups: 3
# p.groupindex: {'sign': 3}

re.match与re.search的区别:re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。


范围匹配

如果需要找到潜在的多个可能性文字, 我们可以使用 [] 将可能的字符囊括进来. 比如 [ab] 就说明我想要找的字符可以是 a 也可以是 b. 这里我们还需要注意的是, 建立一个正则的规则, 我们在 pattern“” 前面需要加上一个 r 用来表示这是正则表达式, 而不是普通字符串. 通过下面这种形式, 如果字符串中出现 “run” 或者是 “ran”, 它都能找到.

# multiple patterns ("run" or "ran")
ptn = r"r[au]n"       
# start with "r" means raw string

print(re.search(ptn, "dog runs to cat"))    
# <_sre.SRE_Match object; span=(4, 7), match='run'>

同样, 中括号 [] 中还可以是以下这些或者是这些的组合. 比如 [A-Z] 表示的就是所有大写的英文字母. [0-9a-z] 表示可以是数字也可以是任何小写字母.

print(re.search(r"r[A-Z]n", "dog runs to cat"))     
# None

print(re.search(r"r[a-z]n", "dog runs to cat"))    
# <_sre.SRE_Match object; span=(4, 7), match='run'>

print(re.search(r"r[0-9]n", "dog r2ns to cat"))     
# <_sre.SRE_Match object; span=(4, 7), match='r2n'>

print(re.search(r"r[0-9a-z]n", "dog runs to cat"))  
# <_sre.SRE_Match object; span=(4, 7), match='run'>

类型匹配

  • \d : 任何数字
  • \D : 不是数字
  • \s : 任何 white space, 如 [\t\n\r\f\v]
  • \S : 不是 white space
  • \w : 任何大小写字母, 数字和 “” [a-zA-Z0-9]
  • \W : 不是 \w
  • \b : 空白字符 (只在某个字的开头或结尾)
  • \B : 空白字符 (不在某个字的开头或结尾)
  • \\ : 匹配 \
  • . : 匹配任何字符 (除了 \n)
  • ^ : 匹配开头
  • $ : 匹配结尾
  • ? : 前面的字符可有可无
# \d : decimal digit
print(re.search(r"r\dn", "run r4n"))           
# <_sre.SRE_Match object; span=(4, 7), match='r4n'>

# \D : any non-decimal digit
print(re.search(r"r\Dn", "run r4n"))           
# <_sre.SRE_Match object; span=(0, 3), match='run'>

# \s : any white space [\t\n\r\f\v]
print(re.search(r"r\sn", "r\nn r4n"))          
# <_sre.SRE_Match object; span=(0, 3), match='r\nn'>

# \S : opposite to \s, any non-white space
print(re.search(r"r\Sn", "r\nn r4n"))          
# <_sre.SRE_Match object; span=(4, 7), match='r4n'>

# \w : [a-zA-Z0-9_]
print(re.search(r"r\wn", "r\nn r4n"))          
# <_sre.SRE_Match object; span=(4, 7), match='r4n'>

# \W : opposite to \w
print(re.search(r"r\Wn", "r\nn r4n"))          
# <_sre.SRE_Match object; span=(0, 3), match='r\nn'>

# \b : empty string (only at the start or end of the word)
print(re.search(r"\bruns\b", "dog runs to cat"))    
# <_sre.SRE_Match object; span=(4, 8), match='runs'>

# \B : empty string (but not at the start or end of a word)
print(re.search(r"\B runs \B", "dog   runs  to cat"))  
# <_sre.SRE_Match object; span=(8, 14), match=' runs '>

# \\ : match \
print(re.search(r"runs\\", "runs\ to me"))     
# <_sre.SRE_Match object; span=(0, 5), match='runs\\'>

# . : match anything (except \n)
print(re.search(r"r.n", "r[ns to me"))        
# <_sre.SRE_Match object; span=(0, 3), match='r[n'>

# ^ : match line beginning
print(re.search(r"^dog", "dog runs to cat"))   
# <_sre.SRE_Match object; span=(0, 3), match='dog'>

# $ : match line ending
print(re.search(r"cat$", "dog runs to cat"))   
# <_sre.SRE_Match object; span=(12, 15), match='cat'>

# ? : may or may not occur
print(re.search(r"Mon(day)?", "Monday"))       
# <_sre.SRE_Match object; span=(0, 6), match='Monday'>
print(re.search(r"Mon(day)?", "Mon"))          
# <_sre.SRE_Match object; span=(0, 3), match='Mon'>

如果一个字符串有很多行, 我们想使用 ^ 形式来匹配行开头的字符, 如果用通常的形式是不成功的. 比如下面的 “I” 出现在第二行开头, 但是使用 r"^I" 却匹配不到第二行, 这时候, 我们要使用 另外一个参数, 让 re.search() 可以对每一行单独处理. 这个参数就是 flags=re.M, 或者这样写也行 flags=re.MULTILINE.

string = """
dog runs to cat.
I run to dog.
"""
print(re.search(r"^I", string))                 
# None

print(re.search(r"^I", string, flags=re.M))     
# <_sre.SRE_Match object; span=(18, 19), match='I'>


重复匹配

如果我们想让某个规律被重复使用, 在正则里面也是可以实现的, 而且实现的方式还有很多. 具体可以分为这三种:

  • * : 重复零次或多次
  • + : 重复一次或多次
  • {n, m} : 重复 n 至 m 次
  • {n} : 重复 n 次
# * : occur 0 or more times

print(re.search(r"ab*", "a"))             
# <_sre.SRE_Match object; span=(0, 1), match='a'>

print(re.search(r"ab*", "abbbbb"))        
# <_sre.SRE_Match object; span=(0, 6), match='abbbbb'>

# + : occur 1 or more times

print(re.search(r"ab+", "a"))             
# None

print(re.search(r"ab+", "abbbbb"))        
# <_sre.SRE_Match object; span=(0, 6), match='abbbbb'>

# {n, m} : occur n to m times

print(re.search(r"ab{2,10}", "a"))        
# None

print(re.search(r"ab{2,10}", "abbbbb"))   
# <_sre.SRE_Match object; span=(0, 6), match='abbbbb'>

Group

我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。

通过分组, 我们能轻松定位所找到的内容。比如在这个 (\d+) 组里, 需要找到的是一些数字, 在 (.+) 这个组里, 我们会找到 “Date: “ 后面的所有内容。 当使用 match.group() 时, 他会返回所有组里的内容, 而如果给 .group(2) 里加一个数, 它就能定位你需要返回哪个组里的信息。

match = 
	re.search(r"(\d+), Date: (.+)", 
	"ID: 021523, Date: Feb/12/2017")
print(match.group())                   
# 021523, Date: Feb/12/2017

print(match.group(1))                  
# 021523

print(match.group(2))                  
# Date: Feb/12/2017

有时候, 组会很多, 光用数字可能比较难找到自己想要的组, 这时候, 如果有一个名字当做索引, 会是一件很容易的事. 我们字需要在括号的开头写上这样的形式 ?P<名字> 就给这个组定义了一个名字. 然后就能用这个名字找到这个组的内容.

match = 
	re.search(r"(?P<id>\d+), Date: (?P<date>.+)", 
	"ID: 021523, Date: Feb/12/2017")
print(match.group('id'))                
# 021523
print(match.group('date'))              
# Date: Feb/12/2017

检索和替换

re.sub(pattern, repl, string, count=0, flags=0)

参数:

  • pattern : 正则中的模式字符串。
  • repl : 替换的字符串,也可为一个函数。
  • string : 要被查找替换的原始字符串。
  • count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
print(re.sub(r"r[au]ns", "catches", "dog runs to cat"))     
# dog catches to cat

# 其中repl也可以是一个函数,比如

# 将匹配的数字乘以 2
def double(matched):
    value = int(matched.group('value'))
    return str(value * 2)
 
s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s))

# A46G8HFD1134


编译正则表达式

compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数重复使用。

re.compile(pattern[, flags])

  • pattern : 一个字符串形式的正则表达式

  • flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,可以用|操作符表示为同时生效,如re.I|re.M,具体参数为:

    • re.I 忽略大小写
    • re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
    • re.M 多行模式
    • re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
    • re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
    • re.X 为了增加可读性,忽略空格和 # 后面的注释
compiled_re = re.compile(r"r[ua]n")
print(compiled_re.search("dog ran to cat"))  
# <_sre.SRE_Match object; span=(4, 7), match='ran'>

findall

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

注意: match 和 search 是匹配一次 findall 匹配所有。

findall(string[, pos[, endpos]])

参数:

  • string : 待匹配的字符串。
  • pos : 可选参数,指定字符串的起始位置,默认为 0。
  • endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
# findall
print(re.findall(r"r[ua]n", "run ran ren"))    
# ['run', 'ran']

# | : or
print(re.findall(r"(run|ran)", "run ran ren")) 
# ['run', 'ran']

split

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

re.split(pattern, string[, maxsplit=0, flags=0])

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串。
maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
print(re.split(r"[,;\.]", "a;b,c.d;e")) 
# ['a', 'b', 'c', 'd', 'e']


总结图

]]>
百度ai视觉技术案例 2018-03-09T00:00:00+00:00 Kaka Chen http://kakack.github.io/2018/03/百度AI视觉技术案例 利用百度AI平台实现简单的视觉技术。

在平台“控制台”中创建所需应用,然后分别记录appIDApi_KeySecret_Key,用于程序中的访问权限获取(具体创建方法不在此赘述)。

1, 人脸识别

1.1 人脸检测

接口能力

  • 人脸检测:检测图片中的人脸并标记出位置信息;
  • 人脸关键点:展示人脸的核心关键点信息,及72个关键点信息。
  • 人脸属性值:展示人脸属性信息,如年龄、性别等。
  • 人脸质量信息:返回人脸各部分的遮挡、光照、模糊、完整度、置信度等信息。

业务应用

典型应用场景:如人脸属性分析,基于人脸关键点的加工分析,人脸营销活动等。

质量检测

如果需要判断一张图片中的人脸,是否符合后续识别或者对比的条件,可以使用此接口,在请求时在face_fields参数中请求qualities。基于返回结果qualities中,以下字段及对应阈值,进行质量检测的判断,以保证人脸质量符合后续业务操作要求。

简单例子

from aip import AipFace

if __name__ == '__main__':
    """ 设定 APPID AK SK """
    APP_ID = '10895978'
    API_KEY = 'eiHvcrvkVNNYCpszd74bu7XD'
    SECRET_KEY = 'tq258FE4Qop5z1XUmndN76bXAbHSKo4i'

    aipFace = AipFace(APP_ID, API_KEY, SECRET_KEY)

    # 读取图片
    filePath = "./img/db2.jpg"

    def get_file_content(filePath):
        with open(filePath, 'rb') as fp:
            return fp.read()

    # 定义参数变量
    options = {
        'max_face_num': 1,
        'face_fields': "age,beauty,expression,faceshape",
    }

    # 调用人脸属性检测接口
    result = aipFace.detect(get_file_content(filePath), options)

    print(result)

输出结果:

{
    "result_num": 1, 
    "result": [
        {
            "location": {
                "left": 987, 
                "top": 365, 
                "width": 468, 
                "height": 442
            }, 
            "face_probability": 1, 
            "rotation_angle": 9, 
            "yaw": -4.873851776123, 
            "pitch": 11.62779045105, 
            "roll": 10.357309341431, 
            "age": 44, 
            "beauty": 72.05835723877, 
            "expression": 1, 
            "expression_probablity": 0.98539078235626, 
            "faceshape": [
                {
                    "type": "square", 
                    "probability": 0.29037597775459
                }, 
                {
                    "type": "triangle", 
                    "probability": 0.0018628753023222
                }, 
                {
                    "type": "oval", 
                    "probability": 0.66113710403442
                }, 
                {
                    "type": "heart", 
                    "probability": 0.0021002173889428
                }, 
                {
                    "type": "round", 
                    "probability": 0.044523812830448
                }
            ]
        }
    ], 
    "log_id": 3668917883030910
}

返回值含义

参数 类型 必选 说明
log_id uint64 日志id
result_num uint32 人脸数目
result object[] 人脸属性对象的集合
+age double 年龄。face_fields包含age时返回
+beauty double 美丑打分,范围0-100,越大表示越美。face_fields包含beauty时返回
+location object 人脸在图片中的位置
++left uint32 人脸区域离左边界的距离
++top uint32 人脸区域离上边界的距离
++width uint32 人脸区域的宽度
++height uint32 人脸区域的高度
+face_probability double 人脸置信度,范围0-1
+rotation_angle int32 人脸框相对于竖直方向的顺时针旋转角,[-180,180]
+yaw double 三维旋转之左右旋转角[-90(左), 90(右)]
+pitch double 三维旋转之俯仰角度[-90(上), 90(下)]
+roll double 平面内旋转角[-180(逆时针), 180(顺时针)]
+expression uint32 表情,0,不笑;1,微笑;2,大笑。face_fields包含expression时返回
+expression_probability double 表情置信度,范围0~1。face_fields包含expression时返回
+faceshape object[] 脸型置信度。face_fields包含faceshape时返回
++type string 脸型:square/triangle/oval/heart/round
++probability double 置信度:0~1
+gender string male、female。face_fields包含gender时返回
+gender_probability double 性别置信度,范围[0~1],face_fields包含gender时返回
+glasses uint32 是否带眼镜,0-无眼镜,1-普通眼镜,2-墨镜。face_fields包含glasses时返回
+glasses_probability double 眼镜置信度,范围[0~1],face_fields包含glasses时返回
+landmark object[] 4个关键点位置,左眼中心、右眼中心、鼻尖、嘴中心。face_fields包含landmark时返回
++x uint32 x坐标
++y uint32 y坐标
+landmark72 object[] 72个特征点位置,face_fields包含landmark时返回
++x uint32 x坐标
++y uint32 y坐标
+race string yellow、white、black、arabs。face_fields包含race时返回
+race_probability double 人种置信度,范围[0~1],face_fields包含race时返回
+qualities object 人脸质量信息。face_fields包含qualities时返回
++occlusion object 人脸各部分遮挡的概率,范围[0~1],0表示完整,1表示不完整
+++left_eye double 左眼遮挡比例
+++right_eye double 右眼遮挡比例
+++nose double 鼻子遮挡比例
+++mouth double 嘴巴遮挡比例
+++left_cheek double 左脸颊遮挡比例
+++right_cheek double 右脸颊遮挡比例
+++chin double 下巴遮挡比例
++blur double 人脸模糊程度,范围[0~1],0表示清晰,1表示模糊
++illumination - 取值范围在[0~255],表示脸部区域的光照程度
++completeness - 人脸完整度,0或1, 0为人脸溢出图像边界,1为人脸都在图像边界内
++type object 真实人脸/卡通人脸置信度
+++human - 真实人脸置信度,[0~1],大于0.5可以判断为人脸
+++cartoon - 卡通人脸置信度,[0~1]

1.2 人脸对比

接口能力

  • 两张人脸图片相似度对比:比对两张图片中人脸的相似度,并返回相似度分值;
  • 多种图片类型:支持生活照、证件照、身份证芯片照、带网纹照四种类型的人脸对比;
  • 活体检测:基于图片中的破绽分析,判断其中的人脸是否为二次翻拍(举例:如用户A用手机拍摄了一张包含人脸的图片一,用户B翻拍了图片一得到了图片二,并用图片二伪造成用户A去进行识别操作,这种情况普遍发生在金融开户、实名认证等环节。);
  • 质量检测:返回模糊、光照等质量检测信息,用于辅助判断图片是否符合识别要求;

业务应用

用于比对多张图片中的人脸相似度并返回两两比对的得分,可用于判断两张脸是否是同一人的可能性大小。

典型应用场景:如人证合一验证,用户认证等,可与您现有的人脸库进行比对验证。

简单例子

from aip import AipFace

if __name__ == "__main__":
    APP_ID = '10895978'
    API_KEY = 'eiHvcrvkVNNYCpszd74bu7XD'
    SECRET_KEY = 'tq258FE4Qop5z1XUmndN76bXAbHSKo4i'
    aipFace= AipFace(APP_ID, API_KEY, SECRET_KEY)

    def get_file_content(filePath):
        with open(filePath, 'rb') as fp:
            return fp.read()

    images = [
        get_file_content('./img/db1.jpg'),
        get_file_content('./img/db2.jpg')
    ]

    result = aipFace.match(images)

    print(result)

    # """ 如果有可选参数 """
    # options = {}
    # options["ext_fields"] = "qualities"
    # options["image_liveness"] = ",faceliveness"
    # options["types"] = "7,13"
    #
    # result_opt = aipFace.match(images, options)
    #
    # print(result_opt)

输出结果

{
    "result": [
        {
            "index_i": "0", 
            "index_j": "1", 
            "score": 70.682899475098
        }
    ], 
    "result_num": 1, 
    "log_id": 3136201939031014
}

返回值含义

字段 必选 类型 说明
log_id uint64 请求唯一标识码,随机数
result_num uint32 返回结果数目,即:result数组中元素个数
result array(object) 结果数据,index和请求图片index对应。数组元素为每张图片的匹配得分数组,top n。得分范围[0,100.0]
+index_i uint32 比对图片1的index
+index_j uint32 比对图片2的index
+score double 比对得分,推荐80分作为阈值,80分以上可以判断为同一人,此分值对应万分之一误识率
ext_info array(dict) 对应参数中的ext_fields
+qualities string 质量相关的信息,无特殊需求可以不使用
+faceliveness string 活体检测分数,单帧活体检测参考阈值0.393241,超过此分值以上则可认为是活体。注意:活体检测接口主要用于判断是否为二次翻拍,需要限制用户为当场拍照获取图片;推荐配合客户端SDK有动作校验活体使用

1.3 人脸识别

业务能力

  • 1:N人脸识别:也称为1:N识别,在指定人脸集合中,找到最相似的人脸;
  • 1:N人脸认证:基于uid维度的1:N识别,由于uid已经锁定固定数量的人脸,所以检索范围更聚焦;
  • M:N多人脸识别:也称为M:N识别,待识别图片中含有多个人脸时,在指定人脸集合中,找到这多个人脸分别最相似的人脸;

1:N人脸识别与1:N人脸认证的差别在于:人脸识别是在指定人脸集合中进行直接地人脸检索操作,而人脸认证是基于uid,先调取这个uid对应的人脸,再在这个uid对应的人脸集合中进行检索(因为每个uid通常对应的只有一张人脸,所以通常也就变为了1:1对比);实际应用中,人脸认证需要用户或系统先输入id,这增加了验证安全度,但也增加了复杂度,具体使用哪个接口需要视您的业务场景判断。

M:N识别的原理,相当于在多个人脸的图片中,先分别找出所有人脸,然后分别在待查找的人脸集合中,分别做1:N识别,最后将识别结果汇总在一起进行返回。

用于计算指定组内用户,与上传图像中人脸的相似度。识别前提为您已经创建了一个人脸库。

典型应用场景:如人脸闸机,考勤签到,安防监控等。

说明:人脸识别返回值不直接判断是否是同一人,只返回用户信息及相似度分值。

说明:推荐可判断为同一人的相似度分值为80,您也可以根据业务需求选择更合适的阈值。

简单例子

from aip import AipFace

if __name__ == "__main__":
    groupId = "davidbeckham"

    APP_ID = '10895978'
    API_KEY = 'eiHvcrvkVNNYCpszd74bu7XD'
    SECRET_KEY = 'tq258FE4Qop5z1XUmndN76bXAbHSKo4i'
    aipFace = AipFace(APP_ID, API_KEY, SECRET_KEY)

    """ 读取图片 """
    def get_file_content(filePath):
        with open(filePath, 'rb') as fp:
            return fp.read()

    image = get_file_content('./img/db3.jpg')

    """ 调用人脸识别 """
    result = aipFace.identifyUser(groupId, image);
    print(result)

    # """ 如果有可选参数 """
    # options = {}
    # options["ext_fields"] = "faceliveness"
    # options["user_top_num"] = 3
    #
    # """ 带参数调用人脸识别 """
    # aipFace.identifyUser(groupId, image, options)

返回结果:

{
    "result": [
        {
            "uid": "DavidBeckham", 
            "scores": [
                72.19522857666
            ], 
            "group_id": "davidbeckham", 
            "user_info": ""
        }
    ], 
    "result_num": 1, 
    "log_id": 3794890709031016
}

返回参数

字段 必选 类型 说明
log_id uint64 请求唯一标识码,随机数
result_num uint32 返回结果数目,即:result数组中元素个数
result array(double) 结果数组,数组元素为匹配得分,top n。 得分范围[0,100.0]。超过80分可认为认证成功
ext_info array 对应参数中的ext_fields
+faceliveness string 活体检测分数,单帧活体检测参考阈值0.393241,超过此分值以上则可认为是活体。活体检测接口主要用于判断是否为二次翻拍,需要限制用户为当场拍照获取图片;推荐配合客户端SDK有动作校验活体使用

关于活体检测faceliveness的判断阈值选择,可参考以下数值信息:

拒绝率(TRR) 误拒率(FRR) 通过率(TAR) 阈值(Threshold)
0.90325733 0.1% 99.9% 0.022403
0.96254072 0.5% 99.5% 0.393241(推荐)
0.97557003 1% 99% 0.649192
0.98990228 2% 98% 0.933801
0.99446254 3% 97% 0.973637
0.99641694 4% 96% 0.988479
0.99739414 5% 95% 0.994058
]]>
Jupyter notebook在mac:linux上的配置和远程访问 2017-12-05T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/12/Jupyter Notebook在Mac:Linux上的配置和远程访问 IPython 和 Jupyter

IPython 通常指的是一个 Python REPL(交互式解释器) shell。提供了远比 Python shell 强大的 shell 环境。IPythonIteractive Python shell 的缩写。 Notebook 是一个基于 IPythonweb 应用。

截止 IPython 3.0IPython 变得越来越臃肿,因此, IPython 4.x 后,IPython被分离成为了IPython kernel's Jupyter(IPython shell combined with jupyter kernel)Jupyter subprojectsNotebookJupyter 的一个 subproject 。官方将此称为The Big Split™。#reference-The Big Split™

IPython 把与语言无关(language-agnostic)componentsPython 解释器剥离,独立为Jupyter发行。因此,Jupyter 可以和 Python 之外的解析器结合,提供新的、强大的服务。比如 Ruby REPL 环境 IRubyJulia REPL 环境 IJulia

因此,JupyterIPyhon 指代同一个项目, Jupyter 特指 Ipython4 后的, 非python kernel框架。

Jupyter Notebook已经逐渐取代IDE成为了多平台上写简单Python脚本或应用的几家选择。

Jupyter Notebook可以通过pip/pip3安装:

sudo pip3 install jupyter

然后在目标文件夹目录下,输入指令jupyter notebook开启服务,可在浏览器地址localhost:8888中访问主页


允许远程访问

我之前在训练一些简单的svm模型的时候,因为数据量大,训练时间慢,导致自己的macbook在训练过程中一直无法做别的事情,就动了在远程Linux虚拟机上装一套Jupyter Notebook然后将耗时很长的训练操作放在远程机器上做。

在本地,我们访问localhost:8888就能看到Jupyter Notebook的本地主页,但是在远程访问中,并不能直接这么做。因此需要以下一些操作:

官方指南

1. 生成一个 notebook 配置文件

默认情况下,配置文件 ~/.jupyter/jupyter_notebook_config.py 并不存在,需要自行创建。使用下列命令生成配置文件:

jupyter notebook --generate-config

如果是 root 用户执行上面的命令,会发生一个问题:

Running as root it not recommended. 
Use --allow-root to bypass.

提示信息很明显,root 用户执行时需要加上 –allow-root 选项。

jupyter notebook --generate-config --allow-config

执行成功后,会出现下面的信息:

Writing default config to: /root/.jupyter/jupyter_notebook_config.py

2. 生成密码

自动生成

从 jupyter notebook 5.0 版本开始,提供了一个命令来设置密码:jupyter notebook password,生成的密码存储在 jupyter_notebook_config.json

$ jupyter notebook password
Enter password:  ****
Verify password: ****
[NotebookPasswordApp] Wrote hashed password to /Users/you/.jupyter/jupyter_notebook_config.json

手动生成

除了使用提供的命令,也可以通过手动安装,我是使用的手动安装,因为jupyter notebook password 出来一堆内容,没耐心看。打开 ipython 执行下面内容:

In [1]: from notebook.auth import passwd
In [2]: passwd()
Enter password:
Verify password:
Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'

sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed 这一串就是要在 jupyter_notebook_config.py 添加的密码。

c.NotebookApp.password = \
u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'

3. 修改配置文件

jupyter_notebook_config.py 中找到下面的行,取消注释并修改。

c.NotebookApp.ip='*'
c.NotebookApp.password = u'sha:ce... # 刚才复制的那个密文'
c.NotebookApp.open_browser = False
c.NotebookApp.port =8888 # 可自行指定一个端口, 访问时使用该端口

以上设置完以后就可以在服务器上启动 jupyter notebook,jupyter notebook, root 用户使用 jupyter notebook --allow-root。打开 IP:指定的端口(默认为8888), 输入密码就可以访问了。


同时支持python2和python3

先安裝Python2和Python3的ipython notebook

pip2 install ipython notebook
pip3 install ipython notebook

分别用各自的ipython执行下面的指令

ipython2 kernelspec install-self
ipython3 kernelspec install-self

就能在ipython notebook里面同时使用两种版本的Python了

附录:27 个Jupyter Notebook的小提示与技巧

]]>
深入浅出了解卷积神经网络(part 3) 2017-11-23T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/11/深入浅出了解卷积神经网络(Part-3) 特别鸣谢:The 9 Deep Learning Papers You Need To Know About (Understanding CNNs Part 3)


简介

Links to Part1

Links to Part2

在这篇文章中,我们将总结计算机视觉和卷积神经网络领域的许多新的重要发展。我们将回顾过去五年来发表的一些最重要的论文,并讨论它们为什么如此重要。所列举的前半部分(AlexNet to ResNet)涉及一般网络架构的进步,而后半部分则是其他子领域的一些有趣的论文。


AlexNet(2012)

这一开始是这样的(虽然有些人可能会说,1998年的Yann LeCun的论文才是真正的开创性的刊物):这篇名为“深度卷积网络的ImageNet分类”的文章总共被引用了6184次,被广泛认为是该领域最有影响力的发表物之一。 Alex Krizhevsky,Ilya Sutskever和Geoffrey Hinton创建了一个“大规模的深度卷积神经网络”,用于赢得2012年ILSVRC(ImageNet大规模视觉识别挑战)。对于那些不熟悉的人来说,这个竞赛可以被认为是计算机视觉年度奥运会,来自世界各地的团队参与竞赛,角逐谁拥有最好的计算机视觉模型,比赛的内容有分类,定位,检测等等。2012年标志着CNN被用来实现top 5达到15.4%的测试错误率的第一年(top 5的错误率是在给定图像的情况下,该模型没能输出top 5的正确标签的比例)。紧随其后的成绩是26.2%的错误率,所以这是一个惊人的改进,几乎震惊了计算机视觉社区。可以这么说,CNN在这个竞争中从此变成了家喻户晓的名字。

在论文中,研究小组讨论了网络的架构(称为AlexNet)。与现代架构相比,他们使用相对简单的布局。网络由5个conv层组成:一个max-pooling层,一个dropout层和3个全连接层。他们设计的网络被用于1000种可能类别的分类。

主要观点

  • 在ImageNet数据上训练网络,其中包含一千五百万张被标记成两万两千个类别的图像。
  • 针对非线性函数使用ReLU(发现能有效减少训练时间,因为ReLU比传统tanh函数快几倍)。
  • 使用数据增强技术,包括图像转换,水平反射和补丁提取。
  • 使用Dropout层,以解决模型过拟合的问题。
  • 使用批量随机梯度下降训练模型,其中动量和权重的衰减有特定的值。
  • 在两个GTX 580 GPU上训练五到六天

重要性

Krizhevsky,Sutskever和Hinton于2012年开发的神经网络是CNN在计算机视觉领域的一次派对。这是历史上第一次有模型能在困难的ImageNet数据集上表现得如此之好。利用这些今天仍在使用的如数据增加和dropout技术,本文真正说明了CNN的好处,并在实际竞争中发挥出了创纪录的表现。


ZF Net(2013)

随着AlexNet于2012年的大放异彩,提交给ILSVRC 2013的CNN模型数量大幅增加。当年的比赛获胜者是来自纽约大学的Matthew Zeiler和Rob Fergus建立的网络,被命名为ZF Net的这个模型实现了仅有11.2%的错误率。这种架构相对于以前的AlexNet结构被调整得更加优秀,但仍然提出了一些关于提高性能的非常关键的想法。这篇论文之所以十分重要的另一个原因,是作者花了很多时间来解释ConvNets背后的直观理解,并展示了如何正确地将过滤器和权重可视化。

在这篇题为“可视化和理解卷积神经网络”的论文中,Zeiler和Fergus首先讨论了这一观点,即重新对CNN产生兴趣是由于大型训练集的可访问性以及随着GPU的使用而增加的计算能力。他们还谈到了研究人员对这些模型的内在机制的有限的知识,他说如果没有这种洞察力,“在开发更好的模型的过程中就会不断碰壁遇到错误”。虽然我们现在比三年前对模型有了更好的了解,但对于很多研究人员来说,这仍然是一个问题!本文的主要贡献是略微修改了AlexNet模型的细节,以及给出了一种非常有趣的可视化特征映射方式。

主要观点

  • 一个与AlexNet非常相似的架构,除了一些小的修改。
  • AlexNet训练了1500万张图片,而ZF Net仅训练了130万张图片。
  • ZF Net没有在第一层使用11x11大小的过滤器(这是AlexNet实现的),而是使用了尺寸为7x7的过滤器和更小的步幅值。这种修改背后的原因是,第一个conv层中的较小的过滤器尺寸有助于在输入体量中保留大量的原始像素信息。过滤大小为11x11被证明是跳过了大量的相关信息,特别是在第一个conv层上。
  • 随着网络的发展,我们也看到了所使用的过滤器数量的增加。
  • 在激活函数上使用ReLUs,在损失函数上使用交叉熵损失,以及使用批次随机梯度下降来进行训练。
  • 在GTX 580 GPU上训练了十二天
  • 开发了一种名为Deconvolutional Network的可视化技术,可以帮助检查不同的特征激活及其与输入空间的关系。因为它将特征映射到像素(与卷积层相反),所以称为“deconvnet(反向卷积)”。

DeConvNet

DeConvNet如何工作背后的基本思想是,在训练有素的CNN的每一层,你都附加一个“去卷积deconvnet”,也就是说它有一条反向映射回到图像像素的路径。输入图像被输入到CNN中,并在每个级别计算激活,这是正向传递。现在,假设我们想要检查第四个conv层中某个特征的激活。我们将存储这个特征映射的激活,但是将该层中的所有其他激活设置为0,然后将该特征映射作为输入传递给去卷积deconvnet。 这个deconvnet和原来的CNN有相同的过滤器。然后,这个输入经过一系列的逆向池化unpool(池化maxpooling的逆操作),整流和滤波操作,直到达到输入空间。

整个过程背后的原因是我们想要检查哪种类型的结构激发给定的特征映射。我们来看看第一层和第二层的可视化。

就像我们在Part1中已经讨论过的那样,ConvNet的第一层总是那些勇于探测低等级特征的,例如简单的边缘或者特定例子中的颜色等。我们可以在第二层中看到,我们有越来越多的圆形特征会被探测到。让我们继续来看第3、4、5层。

这些图层显示了更多更高级别的功能,如探测狗脸或鲜花。 有一点需要注意的是,在第一个conv层之后,我们通常有一个对图像进行下采样的池(例如,将一个32x32x3的数据体量volumes变成一个16x16x3的体量)。这样做的效果是,在第二层中可以探测到在原始图像中更广泛的范围。 有关deconvnet或一般论文的更多信息,请查看Zeiler的presentation的主题。

重要性

ZF Net不仅在2013年的竞争中胜出,而且在CNN的运作方面也提供了很好的直观理解,并说明了更多提高性能的方法。所描述的可视化方法不仅有助于解释CNN的内部工作原理,而且还提供了一些对网络体系结构进行改进的建议。迷人的deconv可视化方法和闭塞实验使得这篇论文成为了我个人最喜欢的论文之一。


VGG Net(2014)

VGG Net,这个2014年创建的模型(不是ILSVRC 2014的获奖者)依靠它7.3%的错误率为我们展现了它简单和深入两大特性。这是牛津大学的Karen Simonyan和Andrew Zisserman创建的一个19层的CNN,严格使用了3x3的过滤器,填充padding为1,最大池化层maxpooling layer为2x2,步幅stride为2。

主要观点

  • 仅使用3x3大小的过滤器,这与应用于第一层中的AlexNet 11x11过滤器和ZF Net的7x7过滤器完全不同。作者的推理是,两个3x3的conv层的组合具有5x5的有效感受域。这反过来可以模拟一个更大的过滤器,同时又能保持较小的过滤器尺寸的好处。其中一个好处是减少了参数的数量。此外,有了两个conv层,我们可以使用两个ReLU层而不是一个。
  • 背靠背的三个conv的层可以实现一个7x7尺寸的接受领域的效果。
  • 随着每层输入体量的空间尺寸减小(conv和pool层输出的结果),体量的深度也会随着过滤器数量的增加而增加。
  • 有趣的是,过滤器数量在每个maxpool层之后都会增加一倍。这强化了缩小空间维度的想法,但增加了深度。
  • 在图像分类和定位任务上效果很好。作者使用了一种定位方法作为回归(详见本篇论文的第10页)。
  • 使用Caffe工具箱(Caffe Toolbox)建立模型。
  • 在训练过程中使用比例抖动(scale jittering)作为一种数据增强技术。
  • 在每个conv层之后使用ReLU层,并使用批梯度下降进行训练。
  • 在4个Nvidia Titan Black GPU上训练2到3周

重要性

VGG Net是我心目中最有影响力的论文之一,因为它进一步强化了卷积神经网络必须具有深层网络才能使视觉数据的分层表示能顺利实现的观点。


GoogLeNet(2015)

刚才我们谈到了简单网络架构的概念。2015年,谷歌推出了Inception模块。GoogLeNet是一个22层的CNN,是2014年ILSVRC的赢家,top 5的错误率为6.7%。据我所知,这是第一个真正不再沿用在一个顺序结构中简单地堆叠conv和pooling层叠的一般方法的CNN。这篇论文的作者还强调,这个新模型对内存和电源使用有一些考虑(重要的是,我有时也会忘记:堆叠所有这些层并添加大量的过滤器会带来计算和存储成本,以及增加过拟合的可能)。

Inception 模块

当我们第一次接触GoogLeNet的时候,我们立即会注意到,不是所有事件都是像之前的架构那样串行顺序发生的。我们的网络中有部分功能是并行进行的。

这个方块被称为Inception模块,让我们仔细看看这是由什么组成的:

最下面的绿色框是我们的输入,最上面的是模型的输出(把这张照片右转90度可以让你看到上一张显示整个网络的模型的图片)。基本上,在传统的ConvNet的每一层,你必须选择是否有池操作pooling operation或卷积操作conv operation(也有筛选器大小的选择)。 Inception模块允许你做的是并行执行所有这些操作。事实上,这正是作者提出的最“朴素”的想法。

那么现在为什么这行不通呢?因为这会导致出现太多的输出。我们最终会得到一个非常大的输出体量的深度频道。作者解决这个问题的方法是在3x35x5层之前添加1x1的conv操作。 1×1卷积(或网络层中的网络)提供了降维的方法。例如,假设你的输入体量为100x100x60(这不一定是图像的尺寸,只是网络任何层的输入)。应用20个1x1卷积滤波器可以让你将体量减小到100x100x20。这意味着3x35x5的卷积将不会有很大的处理量。这可以被认为是“特征池化pooling of features”,因为我们正在减小体量的深度,类似于我们如何使用普通的maxpooling层来减小高度和宽度的尺寸。另外值得注意的是这些1x1的conv层后面跟着的是不能被伤害到的ReLU units(参见Aaditya Prakash关于1x1卷积效果的更多信息)。看看这个视频最后的过滤器连接的可视化。

你可能会问自己:“这个架构如何给我们提供帮助?”好,如果你有一个由网络层组成的网络,一个中等大小的卷积滤波器,一个大尺寸的卷积过滤器和一个池化操作pooling operation。网络中的网络能够提取非常详细的关于体量细节的信息,而5x5滤波器能够覆盖输入的较大接受区域,从而能够提取其信息。你也有一个池化操作,用来减少空间大小和预防过拟合。最重要的是,每个conv层之后都有ReLU,这有助于提高网络的非线性。基本上,网络能够执行这些不同操作的功能,同时仍然保持计算上的一致性。本文也给出了更多的高层次推理,涉及稀疏性和密集联系等主题(请阅读论文的第3和第4部分,但对我来说还不完全清楚,但如果有人有任何见解,我很乐意在评论中交流!)。

主要观点

  • 在整个架构中使用了9个Inception模块,共有100多层。确实非常深。
  • 没有使用全连接层,而使用平均池average pool来代替,从7x7x1024体量到1x1x1024体量。这节省了大量的参数。
  • 使用比AlexNet少12倍的参数。
  • 在测试过程中,创建多个相同的图像,并将其输入网络,并在最终结果上生成平均softmax概率。
  • 使用R-CNN中的概念(我们将在后面讨论)作为检测模型。
  • Inception模块有更新的版本(版本6和7)。
  • 在若干个高端GPU上进行一周的训练

重要性

GoogLeNet是第一个引入了“CNN层次并不总是必须依次叠加”概念的模型之一。 在Inception模块之后,作者们表示,在层次结构上的创造性可以提高性能和计算效率。这篇论文确实为我们在未来几年可以看到的一些令人惊叹的架构奠定了基础。

那么让我们进一步看看更深层次的内容。


Microsoft ResNet(2015)

想象一下深度的CNN架构,接下来,将层数增加一倍,再增加几个层次,但仍然不如微软亚洲研究院在2015年年底提出的ResNet架构那么深。ResNet是一个新的152层网络架构,这是一个新的记录。这是一个可以进行分类,检测和定位的令人难以置信的架构。除了层数方面的新纪录,ResNet以3.6%的惊人错误率赢得了ILSVRC 2015(根据他们的技术和专业知识,人类通常徘徊在5-10%的错误率。可以查阅Andrej Karpathy的文章,里面阐述了他在ImageNet Challenge上与ConvNet竞争的经验)。

残差模块 Residual Block

残差块背后的想法是,当你的输入x通过conv-relu-conv系列层。这会给你一些F(x)。 该结果然后被添加到原始输入x。 我们称之为H(x)= F(x)+x。 在传统的CNN中,你的H(x)就等于F(x)。所以,我们不是只计算这个变换(直接从xF(x)),我们需要计算需要往输入x上添加的部分——F(x)。 基本上,下面显示的迷你模块正在计算delta或着对原始输入x的轻微改变以获得稍微改变的结果表示(当我们考虑传统的CNN时,我们从xF(x),这是一个不保留关于原始x的任何信息的表示)。作者认为,“优化残差映射比优化原始的、未引用的映射更容易”。

残差模块可能会有效的另一个原因在于,当逆向传递的过程中,梯度会随着图很轻易地流动,因为我们有额外添加的分割梯度的操作。

主要观点

  • “非常深” - Yann LeCun。
  • 152层。
  • 有意思的是,在仅仅前两层之后,空间大小从224×224的输入体量被压缩到56×56体量。
  • 作者声称,普通网络中层次的增加会导致更高的训练和测试错误(本文中的图1)。
  • 该研发组织尝试了一个1202层的网络,但测试的准确性较低,大概是由于过配合导致。
  • 在8GPU机器上训练2到3周

重要性

3.6%的错误率,这本身就有足够的说服力。ResNet模型是我们目前拥有的最好的CNN架构,是残留学习理念的一个伟大创新。由于自2012年以来每年的错误率都在下降,我对今年错误率是否会继续下降表示怀疑。我相信我们已经到了即便继续堆叠更多层但结果不会导致大幅提升的地步。但是,相信接下去肯定会继续诞生像我们在过去两年看到的那样有创意的新架构。

ResNets inside of ResNets


基于域的CNN:Region Based CNNs (R-CNN - 2013, Fast R-CNN - 2015, Faster R-CNN - 2015)

有些人可能会认为,R-CNN的出现比以前关于新网络体系结构的任何论文都有更大的影响力。 R-CNN的第一篇论文被引用了1600多次,加州大学伯克利分校的Ross Girshick和他的团队实现了计算机视觉领域最有影响力的创举之一。正如其标题所说的,Fast R-CNN和Faster R-CNN正在使模型更快更好地适用于现代物体检测任务。

R-CNNs的目的是解决目标检测的问题。当给定一个图像,我们希望能够绘制所有对象的边界框。该过程可以分为两个通用的组成部分,区域划分建议步骤和分类步骤。

作者指出,这个模型适合任何未知类区域的检测。选择性搜索是特定用于RCNN的一种方法。选择性搜索执行生成具有包含对象的可能性最高的2000个不同区域的函数。在我们提出了一系列的区域建议后,这些建议被“扭曲warped”成一个可以输入到训练完毕的CNN(在这种情况下是AlexNet)的图像大小,其中这个CNN在每个区域都提取得到了特征向量。这个矢量然后被当作一组线性SVM的输入,这些线性SVM每一个都被训练用于判断一个类,并最终输出这个分类。矢量也被送入边界框回归器以获得其最准确的坐标。

然后使用非最大值抑制Non-maxima suppression来抑制彼此具有显着重叠的边界框。

Fast R-CNN

由于3个主要问题,对原始模型进行了改进。训练经历了多个阶段(ConvNets到SVM到边界框回归),计算成本很高,并且非常慢(RCNN每个图像需要53秒)。Fast R-CNN可以通过共享不同提议之间的conv层的计算结果,并交换生成区域建议的顺序和运行CNN,来解决速度问题。 在这个模型中,图像首先被投入通过一个ConvNet,从ConvNet的最后一个特征映射获得区域建议的特征(更多细节请查看本文2.1节),最后我们还有全连接层来完成我们的回归和分类。

Faster R-CNN

Faster R-CNN正在努力克服R-CNN和Fast R-CNN所展现出的过于复杂的训练渠道所带来的问题。 作者在最后的卷积层之后插入区域建议网络(region proposal network,RPN)。 这个网络能够查看最后的卷积特征映射,并从中产生区域提议。从那个阶段开始,接着使用与R-CNN相同的流水线管道(ROI池,FC,然后是分类和回归开始)。

重要性

能够确定图像中的特定对象是一回事,但是能够确定对象的确切位置是计算机知识的一个巨大的跳跃。 Faster R-CNN已经成为当今物体检测程序的标准。


生成对抗网络 Generative Adversarial Networks (2014)

据Yann LeCun介绍,这些网络可能是下一个巨大飞跃。 在谈论这篇文章之前,我们来谈谈一些关于具有对抗性(adversarial)的例子。例如,让我们设想一个训练好的CNN在ImageNet数据上运行结果良好。让我们有一个图像作为例子,并应用一个摄动干扰,或稍作修改,使预测误差最大化。因此,预测的对象类别被改变,而图像本身与没有摄动的图像相比而言,看上去是相同的。从最高层面来看,具有对抗性的例子本质上就是成功欺骗了ConvNets的图像。

对抗性的例子(论文)肯定使很多研究者感到惊讶,并迅速成为一个有趣的话题。现在让我们来谈谈如何生成对抗网络。我们来考虑两个模型,一个生成模型和一个判别模型。判别模型的任务是确定给定图像是否看起来很自然(来自数据集的图像),还是看起来像是人为创建的。生成器的任务是创建图像,使鉴别器得到训练,产生正确的输出。这可以被认为是一个零和博弈(Zero-sum)或极大极小(minimax)之类的双人游戏。本文所给出的的类比是这样的:生成模型就像“伪造者团队,试图制造和使用假货币”,而判别模型就像“警察试图检测假货币”。发生器试图欺骗鉴别器,而鉴别器试图不被发生器愚弄。随着模型的训练,这两种方法都得到了改进,直到“伪造品与真品无法区分”的程度。

重要性

这听起来似乎很简单,但为什么我们要去关心这些网络?正如Yan LeCun在Quora文章中指出的那样,鉴别者现在已经能意识到“数据的内在表现”,因为它已经被训练去了解数据集中真实图像与人工创建图像之间的差异。因此,它可以在CNN中被当做一个特征提取器来使用。另外,你可以创建非常酷的,对我而言十分自然的人造图像(link)。


生成图像描述Generating Image Descriptions (2014)

当你将CNN和RNN结合起来的时候会发生什么(不,,对不起,你并没有得到R-CNN),但是你确实会得到一个非常了不起的应用。这篇论文由Andrej Karpathy(我个人最喜爱的作者之一)和Fei-Fei Li撰写,将CNN和双向RNN(递归神经网络Recurrent Neural Networks)结合起来,生成不同图像区域的自然语言描述。通常,该模型能够获取图像,并输出以下类似信息:

这真是不可思议!我们来看看这与正常的CNN相比如何。使用传统的CNN,训练数据中每个图像都有一个明确的标签。本文中描述的模型有训练样例,每个图像都有一个句子(或标题)来描述。这种类型的标签被称为弱标签,其中句子片段指图像的(未知)部分。通过这个训练数据,一个深度神经网络可以“推断句子的片段和他们描述的区域之间的潜在校准关系”(引自论文原文)。另一个神经网络将图像作为输入,并在文本中生成描述。让我们分别看看这两个组件——校准和生成。

校准模型Alignment Model

这部分模型的目低是能够校准视觉图像和文本数据(图像及其语句描述)。这个模型通过接收一个图像和一个句子作为输入来进行工作,输出是一个关于匹配程度的得分(现在,Karpathy提到了另一个不同的论文,它涉及了这个原理的具体细节,这个模型同时在兼容和不兼容的图像与句子组合上进行训练)。

现在让我们考虑图像的表示。第一步是将图像输入到R-CNN中以检测单个物体。这个R-CNN在ImageNet数据上进行了训练。顶部十九个(加上原始图像)对象区域被嵌入到500维空间。现在我们有20个不同的500维矢量(在本文中用v表示)用于每个图像。于是我们就有了关于图像的信息。现在,我们需要关于这个句子的信息。我们需要将把语句嵌入到这个相同的多模态空间中。这是通过使用双循环归神经网络(bidirectional recurrent neural network)完成的。从最高层次来看,这用于阐明给定句子中单词的上下文的信息。由于这些关于图片和句子的信息都在同一个空间,所以我们可以通过计算内积inner product来表示相似度。

生成模型Generation Model

校准模型的主要目的是创建一个数据集,其中有一组图像区域(由RCNN找到)和相应的文本(多亏了BRNN)。现在,生成模型将从该数据集中学习,以生成给定图像的描述。该模型需要一个图像,并将其提供给CNN。由于全连接层的输出成为了另一个RNN的输入,所以softmax层被忽略了。对于那些不熟悉RNN的人来说,它们的功能是基本上是产生获得句子中不同单词的概率分布(RNN也需要像CNN一样进行训练)。

重要性

对我来说有趣的是,使用这些看似不同的RNN和CNN模型来创建一个非常有用的应用程序,并能狗结合计算机视觉和自然语言处理两大重要领域。它为之后处理跨越不同领域的任务时,如何使计算机和模型变得更加智能化打开了新的思路。


空间变换神经网络 Spatial Transformer Networks (2015)

最后,让我们来看看这个领域最近的一篇文章。这篇文章是一年多前在Google Deepmind上发表的。主要贡献是引入了一个空间变换Spatial Transformer模块。其基本思想是,该模块以某种方式转换输入图像,以便后续图层能更容易地被进行分类。在不改变主CNN本身体系结构的情况下,作者担心的是如何在将图像输入特定的conv层之前对图像进行更改。这个模块希望纠正的两件事情是姿态归一化pose normalization(对象被倾斜或缩放的场景)和空间注意力spatial attention(在拥挤的图像中注意正确的对象)。对于传统的CNN,如果你想让你的模型对于不同尺度和旋转的图像而言是不变的,那么你需要大量的训练样例才能正确训练模型。我们来详细了解这个变换模块如何帮助解决这个问题。

在传统CNN模型中,处理空间不变性的实体是最大池化层maxpooling layer。 这一层背后的直观推论是,一旦我们知道某个特定特征位于原始输体入量中(高激活值的任何地方),它的确切位置就不如他与其它特征之间的相对位置那么重要。而这种新的空间转换器是动态的,它将为每个输入图像产生不同的行为(不同的失真/变换)。 它不像传统的最大池化层Maxpool那样是很简单并预先定义好的。让我们来看看这个变压器模块是如何工作的。该模块包括:

  • 接入输入体量并输出应该被应用到的空间变换的参数的定位网络。对于仿射变换,参数或θ可以是6维的。
  • 创建一个采样网格,这个网格是由定位网络中产生的仿射变换(θ)扭曲正常的网格而得到的结果。
  • 采样器,其目的是执行输入特征映射的变形。

这个模块在任何时候都可以放入到CNN中,基本上可以帮助网络来学习如何在训练过程中以最小化损失函数的方式来转换特征映射。

重要性

这篇论文引起了我注意的主要原因是,CNN的改进不一定要由网络架构的剧烈变更引起。我们不需要再去创建下一个ResNet或Inception模块。本文实现了对输入图像进行仿射变换的简单思想,以帮助模型在处理平移、缩放和旋转情况下保持自身的不变性。对于那些感兴趣的读者,这里有一个Deepmind的视频,是一个描述CNN上的空间变换器模块工作的动画,同时欢迎加入到Quora的讨论组来。


结语

以上便是关于ConvNet的三部分介绍,欢迎大家评论中交流各自心得体会。如果想了解更多相关概念,我推荐大家去看Stanford CS 231n的课程。谢谢!

]]>