Kaka Chen 2018-05-21T12:08:22+00:00 silver.accc@gmail.com 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的课程。谢谢!

]]>
深入浅出了解卷积神经网络(part 2) 2017-11-23T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/11/深入浅出了解卷积神经网络(Part-2) 特别鸣谢:A Beginner’s Guide To Understanding Convolutional Neural Networks Part 2

简介

Part1

在这篇文章中,我们将进一步介绍卷积神经网络(ConvNets)的更多细节。 声明:现在我明白了其中的一些话题是相当复杂的,可以用通篇文章来解释这些话题。 为了保持行文的简洁和全面性,我将提供有关这些主题的更详细的研究论文的链接。


Stride和Padding(步幅和填充)

好的,让我们回头看看之前的conv层。我们知道有过滤器,接受的领域,卷积处理,那么现在还有两个主要参数可以修改每个层的行为。在我们选择过滤器大小之后,我们还必须选择步幅(Stride)和填充(Padding)。

步幅控制过滤器如何在输入体量周围进行卷积。在Part1的例子中,滤波器在输入体量周围一次转换一个单位。 步幅指的就是过滤器每次移动的数量。 在这种情况下,步幅隐式设置为1。步幅通常设置为使得输出体量是整数而不是分数。我们来看一个例子,假设有一个7x7的输入体量和一个3x3的滤波器(为了简单起见,不考虑第三维),步幅为1。这就是我们习惯的情况。

和原先的一样,当步幅增长为2的时候,可以看看输出体量变化如何。

所以,正如你所看到的那样,接受域现在每次移动2个单位,输出体量也会随之缩小。请注意,当我们试图将步幅设置为3,那么我们会遇到间距问题,所以必须确保接受域能适合当前的输入体量。通常情况下,如果希望接受域重叠较少,并且获得更小的空间维度的话,那么程序员们会适当增大步幅。

接下去,让我们来看看填充(Padding)。在开始之前,让我们来思考一个场景。当你将三个5x5x3的过滤器应用到一个32x32x3输入体量时会发生什么情况?输出体量将会是28x28x3。请注意,空间维度减小了。当我们继续应用转换层时,体量的大小会比我们想要的要减小地更快。在我们的网络的早期的层中,我们希望保留尽可能多的有关原始输入体量的信息,以便我们可以提取这些低级特征。假设我们想要应用相同的conv层,但是我们希望输出体量依然保持为32x32x3,为此,我们可以将大小为2的零填充应用于该层。将输入体量的边界填充为零。如果我们考虑一个大小为2的零填充,那么这将让我们获得一个新的36x36x3的输入体量。

如果你的步长为1,并且要设定零填充:

其中K是指过滤器的尺寸,然后输入和输出体量将会一直都是相同的空间维度。

对于任何给定的卷积层,计算输出尺寸公式如下:

其中O是输出的高度/长度, W是输入的高度/长度,K是过滤器尺寸,P是填充大小,S是步长。


选择超参数

那么我们如何知道要使用多少个图层,多少个卷积层,过滤器尺寸多大,或者步长和填充的值是多少。 这些问题并不是微不足道的,而且也没有研究人员能给出一个设定标准值。这是因为网络性能的好坏很大程度上取决于您拥有的数据类型。数据可能因大小,图像的复杂程度,图像处理任务的类型等而不同。当查看数据集时,考虑如何选择超参数的一种方法是找到正确的组合,以适当的比例创建图像的抽象。

— - -

ReLU (Rectified Linear Units,纠正线性单元) 层

在每个conv层之后,按照惯例,紧随其后的是应用一个非线性层(或激活层)。该层的目的是将非线性特性引入刚刚在conv层期间计算线性运算后得到的的系统中(正好是元素乘法和summations)。过去,tanhsigmoid等非线性函数被使用十分广泛,但是研究人员发现ReLU层的效果要好得多,因为网络能够训练很快(由于计算效率)而没有产生显着的准确性差异。这也有助于缓解消除梯度问题,这也就是下层网络训练非常缓慢的原因,因为梯度在各层之间呈指数级下降(解释这可能超出了本文的范围,但在这里这里看到为了好的描述)。 ReLU层将函数f(x)= max(0,x)应用于输入体量中的所有值。从基本的角度来说,这个层只是将所有的小于零的激活值变为0。这个层增加了模型和整个网络的非线性特性,而不影响conv层的感知域。

深度学习之父Geoffrey Hinton的论文


池化层(Pooling Layers)

在一些ReLU层之后,程序员可以选择应用一个池化层(Pooling Layer)。它也被称为下采样层(downsampling layer)。在这个类别中,还有几个图层选项,其中maxpooling是最流行的。 这基本上需要有一个过滤器(通常大小为2x2)和一个相同长度的步幅,然后将其应用于输入体量上,并输出过滤器卷积的每个子区域中的最大值,详情如下图。

其他用于池化层的选择是平均池化(Average Pooling)L2范式池化(L2-norm pooling)。 这一层背后的直观推理是,一旦我们知道原始输入体l量中有一个特定的特征(将有一个高激活值),其确切位置就不如它相对其它特征的相对位置那么重要。你可以想象,这个层大大减少了输入体积的空间维度(长度和宽度的变化,但不是深度)。这有两个主要目的:

  • 首先是参数或权重的数量减少了75%,从而节约了计算成本。
  • 二是控制过度拟合(Overfitting)。 这个术语是指当一个模型能够很好地适应训练样例的同时,却不能很好地推广到验证和测试集。过度拟合的一个症状是在训练集上有100%或99%准确率的模型,但测试数据上只有50%甚至更低的表现。

缺失层(Dropout Layer)

现在,缺失层(Dropout layer)在神经网络中具有非常特殊的功能,在上一节中,我们讨论了过度拟合的问题,在训练之后,网络的权重会根据给定的训练样例进行调整,但是网络在新的例子上却会表现不佳。Dropout的想法本质上很简单,该层通过将值设置成零的方式,来随机“退出”该层中的一组激活。那么这样一个简单的,看似不必要的又违反直觉的过程有什么好处呢?从某种意义上说,这就迫使网络看上去变得十分冗余。但是我的意思是,即使中间的一些激活被剔除,网络依然应该能够为特定的例子提供正确的分类或输出。它确保网络对现有的训练数据不至于太“适合”,从而有助于缓解过拟合问题。一个重要的注意事项是这个层只能在训练期间使用,而不是在测试期间使用。

详见Geoffrey Hinton的paper


网络层中的网络

一个网络层中的网络(Network in Network layer)是指使用1×1大小的过滤器的conv层。乍看之下,你可能会想知道为什么这种类型的层会对我们的神经网络有所帮助,因为接受的字段通常比它们映射的空间大。但是,我们必须记住,这些1x1卷积跨越了一定的深度,所以我们可以把它看作是一个1x1xN卷积,其中N是层中应用的过滤器的数量。实际上,该层正在执行一个N-D元素方式(N-D element-wise)的乘法,其中N是进入该层的输入体量的深度。

详见Min Lin的paper


分类、定位、检测和分割(Classification, Localization, Detection, Segmentation)

在Part1中使用的示例中,我们研究了图像分类(Image Classification)的任务,这是一个从获取输入图像并从一组类别中输出类别编号的过程。然而,当我们完成像对象定位(Object localization)这样的任务时,我们的工作不仅仅是生成一个类标签,还有需要生成一个描述对象在图片中位置的边界框。

我们也有对象检测(object detection)的任务,其中需要对图像中的所有对象进行本定位。因此,你将需要有多个边界框和多个类标签。

最后,我们还有对象分割( object segmentation),其中任务是输出类标签以及标记出输入图像中每个对象的轮廓。

更多细节将在Part3中提及。

当然,还有很多其他内容。


转换学习(Transfer Learning)

现在,深度学习社区一个常见的误解是,如果没有Google-esque级别的数据量,你不可能指望能创建有效的深度学习模型。虽然数据确实是创建网络的关键部分,但转移学习(Transfer Learning)的想法有助于减少数据需求。转移学习是指一个采用预先训练好的模型(由其他人对大型数据集进行训练的网络的权重和参数)以及用你自己的数据集“微调”模型的过程。这个想法指的是,这个预先训练的模型将作为一个特征提取器,你将删除网络的最后一层,并将其替换为自己的分类器(具体取决于你的实际问题)。然后冻结所有其他层的权重并正常训练网络(冻结层意味着在梯度下降/优化期间不改变权重)。

让我们来看看为什么这个方法是可行的。假设我们正在讨论的预训练模型是在ImageNet上进行训练的(对于那些不熟悉的人,ImageNet是一个包含超过1,000个类的1400万图像的数据集)。当我们考虑网络的较低层时,我们知道它们将会检测到边缘和曲线等特征。现在,除非您有一个非常独特的问题或者数据集,否则您的网络都将会需要去检测曲线和边缘。我们可以使用预先训练好的模型的权重(并冻结它们),并把重点放在更重要的层面上(比较高的层面)进行训练,而不是通过对权重的随机初始化来训练整个网络。如果你的数据集与ImageNet的数据集完全不同,那么你需要训练更多的层并冻结数量较少的一些低级层。


数据增强技术(Data Augmentation Techniques)

到目前为止,我们都可能任然对ConvNets数据的重要性感到麻木,所以让我们来谈谈如何让现有的数据集更大,这只需要一些简单的转换即可。就像我们之前提到的那样,当一台计算机将图像作为输入时,它将采用一组像素值。假设整个图像左移1个像素。对你我来说,这种变化是不可察觉的。然而,对于计算机来说,这种转变可能相当重要,因为图像的分类或标签没有改变,但是数组却改变了。通过微调表示数组的方法改变训练数据,但仍保持标签相同的方式称为数据增强技术Data Augmentation Techniques)。这是一种人为地扩展数据集的方法。人们常使用的一些流行的增强方法是灰度,水平翻转,垂直翻转,随机修剪,颜色抖动,翻转,旋转等等。通过将这些转换应用到当前的训练数据中,你可以轻松地将训练示例的数量增加一到三倍。

Link to Part3

]]>
深入浅出了解卷积神经网络(part 1) 2017-11-23T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/11/深入浅出了解卷积神经网络(Part-1) 特别鸣谢:A Beginner’s Guide To Understanding Convolutional Neural Networks

简介

卷积神经网络(Convolutional neural networks),听起来像是一个奇怪的生物学和数学的组合,但是这些网络已经成为计算机视觉领域最有影响力的创新之一。2012年是神经网络成长的第一年,Alex Krizhevsky用它们赢得了当年的ImageNet竞赛(基本上是计算机视觉年度奥运会),把分类错误记录从26%降低到了15%,这是一个惊人的提高。从那以后,许多公司一直在以深度学习作为他们的服务核心。Facebook使用神经网络作为自动标记算法,谷歌用作照片搜索,亚马逊用作产品推荐,Pinterest用作主页需求个性化,Instagram则用作搜索基础设施。

然而,这些网络最经典最流行的用例是用于图像处理。在图像处理中过程中,让我们来看看如何使用这些CNN进行图像分类。


问题空间

图像分类是通过输入图像来输出类别(猫,狗等)或能描述图像类别的最大概率的任务。对于人类来说,我们必须承认,这项任务是我们从出生那一刻起学到的第一个技能之一,并且是成年人自然而然毫不费力的技能。即使不假思索,我们也能够快速无缝地识别我们所处的环境以及周围的物体。当我们看到一张图像或者只是看着周围的世界时,部分时间我们都能够立刻刻画这个场景,给每个对象一个标签,甚至所有这些都没有自觉地注意到。这些能够快速识别模式,从先前的知识进行概括并适应不同的图像环境的技能却是我们不能与我们的机器同伴共享的技能。


输入和输出

当一台电脑看到一个图像(以图像作为输入)时,它会看到的是一个由像素值组成的数组。根据图像的分辨率和大小,它可能看到的是一个32×32×3的数字数组(3指的是RGB值)。为了说明这一点,假设我们有一个JPG格式的彩色图像,它的大小是480x480,那么代表它的数组将是480x480x3`。这些数字中的每一个都是一个0到255之间的值,它描述的是该点的像素强度。这些数字对于我们人类自身进行图像分类时毫无意义,但这却是计算机唯一可用的输入。这个概念就是,你给计算机这串数字组成的数组,它会输出一个结果数字,描述了图像是某一个类的概率(比如0.80的可能性为猫,0.15为狗,0.05为鸟等)。


我们希望计算机做什么

既然我们知道这个问题以及输入和输出了,我们来思考如何解决这个问题。我们希望计算机做的是能够区分所有的图像,并找出使狗成为狗或使猫成为猫的独特功能。这也是下意识地在我们的脑海中继续的过程。当我们看一张狗的照片时,如果照片具有可识别的特征,例如爪子或四条腿,我们可以将其分类。以类似的方式,计算机能够通过查找诸如边缘和曲线等低级特征来执行图像分类,然后通过一系列卷积层来构建更抽象的概念。这是一个CNN的一般概述。 我们来详细说明一下。


生物学上的联系

但首先,有一点背景知识需要介绍,当你第一次听说卷积神经网络这个术语的时候,你可能已经想到了一些与神经科学或生物学有关的东西。是的,CNNs最初确实从视觉皮层中获得生物启发。视觉皮层是具有对视野特定区域敏感的细胞区域。这个想法在Hubel和Wiesel于1962年进行的一个迷人的实验中得到了扩展(视频),他们发现大脑中的某些单个神经元细胞只有在一定方向的边缘存在的情况下才会响应(或发射)。例如,一些神经元在暴露于垂直边缘时响应,而另一些在显示水平或对角边缘时响应。 Hubel和Wiesel发现,所有这些神经元都是以柱状结构组织的,并且能够产生视觉感受,在具有特定任务的系统内的特定组件(在视觉皮层中寻找特定特征的神经元细胞)的这种想法也是机器适用的,并且是CNN背后的基础。


结构

回到具体细节上,有关对CNN做的更详细的概述是:你将图像传递给一系列非线性的卷积,经过池化(向下采样)和全连接的层,并获得输出。正如我们前面所说的那样,输出可以是一个类或一个最能描述图像的类的概率。现在,困难的部分是了解每个层次都做了什么。所以先让我们进入最重要的一个层。


第一层-数学部分

CNN中的第一层始终是一个卷积层(Convolutional Layer)。首先你要确保记得这个conv(我将使用这个缩写很多)的输入是什么。就像我们之前提到的那样,输入是一个32×32×3的像素值数组。现在,解释一个conv层的最好方法就是想象一个闪烁在图像左上角的手电筒。假设这个手电筒照射的光线覆盖了5×5的区域。现在,让我们想象这个手电筒滑过输入图像的所有区域。在机器学习方面,这种手电筒被称为过滤器filter(有时也称为神经元neuron内核kernel),它所照射的区域称为接收域receptive field。现在,这个过滤器也是一个由数字组成的数组(这些数字称为权重weight参数parameters)。一个非常重要的注意事项是,这个过滤器的深度必须和输入的深度相同(这可以确保数学运算顺利进行),所以这个过滤器的尺寸是5x5x3。现在,我们来以过滤器的第一个位置举例子:这将是输入图像的左上角,当过滤器在输入图像周围滑动或卷积convolving时,它将过滤器中的值与图像的原始像素值相乘(也称为计算元素智能乘法element-wise multiplications)。所有这些乘法都被求出来并计算总和(从数学上讲,这将是总共75次乘法)。所以,现在你将会获得一个单一的数字。记住,这个数字只是过滤器位于图像左上角的代表。现在,我们对输入体量上的每个位置重复这个过程。(下一步将过滤器向右移动1个单位,然后再向右移动1,依此类推)。输入体量上的每个唯一位置都会生成一个数字。在所有位置上滑动过滤器后,你将发现剩下的是一个28x28x1的数字数组,我们称之为激活图activation map特征图feature map。之所以会是一个28×28阵列的原因是因为一个5×5的滤波器可以放在一个32×32输入图像上的784个不同的位置。这784个数字被映射到一个28×28数组。

(Quick Note: Some of the images, including the one above, I used came from this terrific book, “Neural Networks and Deep Learning” by Michael Nielsen. Strongly recommend.)

假设我们现在使用两个5x5x3过滤器,那么我们的输出量将是28x28x2,通过使用更多的过滤器,我们能够更好地保留空间尺寸。在数学上,这是卷积层中发生的事情。


第一层-高层透视图

现在,让我们从更高层次上来谈论这个卷积实际上在做什么。这里每个过滤器都可以被认为是一个特征标识器feature identifiers。当我提到特征时,事实上指的就是直线边缘、简单的颜色和曲线等。试想所有图像最简单的共同点。假设我们的第一个过滤器是7x7x3并且将成为曲线检测器。(在本节中,为了简单起见,让我们忽略过滤器深度为3单位的事实,并且只考虑过滤器和图像的顶部深度切片)。作为曲线检测器,过滤器将具有像素结构,沿曲线形状的区域是更高的数值(请记住,我们正在讨论的这些滤波器只是数字!)。

现在,让我们回到将这个数学问题可视化上。当我们在输入体量的左上角有这样一个滤波器时,它将计算该区域的过滤器和像素值之间的乘积。现在让我们举一个需要进行分类的图像的例子,让我们先把过滤器定位在左上角。

记住,我们要做的事情就是将过滤器中的数字和图像上的原始像素点的数值做乘积。

通常情况下,在输入图像上,如果有一个类似于这个过滤器所代表的曲线的形状,那么所覆盖的像素点和过滤器上的点的乘积相加在一起将会产生一个大的值!现在让我们看看当我们移动过滤器时会发生什么。

这个值降低了很多!这是因为图像部分中没有任何东西响应曲线检测器过滤器。记住,这个conv层的输出是一个激活图。所以在只有一个单一过滤器卷积的简单情况下(如果该过滤器是曲线检测器),激活图将显示图片中最有可能是曲线的区域。在这个例子中,我们的26x26x1激活图的左上角(26是因为7x7滤镜而不是5x5)将是6600。这个高值意味着在输入中可能有某种曲线导致过滤器产生较高的激活量。在我们的激活图中,右上角的值将是0,因为在输入体量中没有任何东西导致过滤器激活(或者更简单地说,在原始图像的该区域中没有相应的曲线)。请记住,这只是一个过滤器,这只是一个能检测到向右和向外弯曲的线条的过滤器。我们还可以使用其他的检测向左曲线或直线的过滤器。如果用了更多的过滤器,那么激活图的深度越大,我们对输入量的了解也越多。

声明:本节中描述的过滤器只是对卷积过程中数学的主要目的简单化描述。 在下面的图片中,将看到一些经过训练的网络的第一个conv层过滤器的实际可视化示例。 尽管如此,主要论点仍然是一样的。第一层上的过滤器在输入图像周围进行卷积,并在其正在查找的特定功能在输入体量中被检测到时“激活”(或计算高值)。

(Quick Note: The above image came from Stanford’s CS231N course taught by Andrej Karpathy and Justin Johnson. Recommend for anyone looking for a deeper understanding of CNNs.)


深入到网络中

现在在传统的卷积神经网络架构中,在这些conv层之间还有其他层。我强烈推荐有兴趣的读者去了解他们的功能和效果,从一般意义上说,他们为模型提供了非线性特性和保持了维度,有助于提高网络的鲁棒性和控制过拟合。一个经典的CNN架构看起来就像这样。

然而,最后一层是一个重要的层,我们稍后会介绍。让我们退一步看看迄今为止我们已经学到了什么。我们讨论了第一个conv层中的过滤器是用来检测的。他们检测低级功能,如边缘和曲线。正如人们所想象的,为了预测图像是否是一种对象,我们需要网络能够识别更高层次的特征,如手或爪子或耳朵。那么让我们来思考第一个conv层之后的网络输出结果。这将是一个28×28×3的体量(假设我们使用三个5×5×3滤波器)。当我们经过另一个conv层时,第一个conv层的输出成为第二个conv层的输入。现在,这看起来有点难以想象。当我们在谈论第一层时,输入只是原始图像。但是,当我们谈论第二个conv层时,输入是第一层产生的激活图。因此,输入的每一层都基本上描述了原始图像中某些低级特征出现的位置。现在,当你在上面应用一组过滤器(通过第二个conv层传递它)时,输出将是代表更高级别特征的激活值。这些特征的类型可以是半圆(曲线和直边的组合)或正方形(几个直边的组合)。当你经过整个网络并通过更多的conv层时,将获得代表越来越复杂功能的激活映射图。在网络最末端,你可能会通过一些过滤器获得在图像中有手写时激活,在看到粉红色的物体时激活,等等。如果你想要了解关于在ConvNets中可视化过滤器的更多信息,Matt Zeiler和Rob Fergus有一个很好的研究论文来讨论这个话题。 Jason Yosinski在YouTube上也有一个视频,提供了一个很棒的视觉表现。另一个值得注意的事情是,当你深入到网络中时,过滤器开始具有越来越大的接受范围,这意味着他们能够从原始输入量的较大区域考虑信息(另一种支持这个观点的依据是它们对越来越大的像素空间区域越来越敏感)。


全连接层

既然我们可以检测到这些高级功能,那么锦上添花的就是将一个完全连接的层连接到网络的末端。原则上,这个层需要一个输入量(无论是在其之前的conv或ReLU还是pool层输出),并输出一个N维向量,其中N是程序必须从中选择的类的数量。例如,如果你想要一个数字分类程序,N将是10,因为有10个数字。这个N维向量中的每个数字表示某个类别的概率。例如,如果用于数字分类程序的结果向量是[0.1, 0.1, 0.75, 0, 0, 0, 0, 0, 0.05, 0],那么这代表有10%的概率图像是1,10%的概率图像是2,图像是3的概率是75%,图像是9的概率是5%(注意:还有其他方法可以表示输出,但我只是展示了softmax方法)。这个全连接层的工作方式是查看上一层的输出(我们记得它应该代表高级特征的激活图),并确定哪些特征与特定类最相关。例如,如果程序预测某些图像是狗,则在激活图中将具有高值,例如爪子或四条腿等的高级特征。类似地,如果程序预测某图像是鸟,那么代表像翅膀或喙等高级特征将在激活图中具有很高的值。基本上,全连接层会考虑什么高层特征与特定类最强关联,并具有特定的权重,以便只要你计算权重和前一层之间的乘积,就可以得到不同类别的正确概率。


训练(如何让这一切运作起来)

这是我故意没有提到的神经网络的一个方面,但它可能是最重要的部分。在阅读时可能有很多疑问:第一个conv层中的过滤器如何知道要去查找边和曲线?全连接层如何知道要查看什么样的激活图?每层中的过滤器如何知道应该被设置成什么值?计算机能够调整其过滤值(或权重)的方式是通过称为反向传播(backpropagation)的训练过程。

在我们进入反向传播之前,我们必须先退后一步来讨论神经网络的工作需求。在我们出生的那一刻,我们的思想是全新的,我们不知道什么是猫、狗或鸟。同样,在CNN开始之前,权重或过滤值都是随机的,过滤器不知道去寻找边缘和曲线,在更高层的过滤器不知道去寻找爪子和喙。然而,随着年龄的增长,我们的父母和老师向我们展示了不同的图片和照片,并给了我们相应的标签。被赋予图像和标签的想法同样也是CNN经历的训练过程所需要经历的。在深入研究之前,我们假设我们有一套训练集,里面有成千上万的狗、猫和鸟的图像,而且每张图片都有一个这个图片是什么动物的标签。然后在让我们回到回到反向传播的话题上来。

所以反向传播可以分为4个不同的部分:

  • 正向传递
  • 损失函数
  • 反向传递
  • 权重更新

正向传递forward pass过程中,首先将选择一张训练图像,我们记得这是一个32x32x3的数字数组,并将其传递给整个网络。在我们的第一个训练样例中,由于所有的权值和过滤值都是随机初始化的,因此输出结果可能类似[.1 .1 .1 .1 .1 .1 .1 .1 .1],基本上输出都没有特定的数字。在当前的权重下,网络无法查找到这些低级特征,因此无法就分类的可能性得出任何合理的结论。这就转到了反向传播的损失函数Loss Function部分。请记住,我们现在使用的是训练数据,这个数据有一个图像和一个标签。例如,假设输入的第一个训练图像是3,图像的标签是[0 0 0 1 0 0 0 0 0 0]。损失函数可以用许多不同的方式来定义,但常见的是MSE(均方误差),即(实际值-预测值)的平方的1/2倍。

假设变量L等于该值。正如你可以想象的那样,第一对训练图像的损失值将非常高。 现在,让我们直观地思考这个问题。我们希望达到预测的标签(ConvNet的输出)与训练标签相同的点(这意味着我们的网络做出了正确的预测)。为了达到这个目的,我们希望最小化我们得到的损失值。可以将这看作是微积分中的一个优化问题,我们想要找出哪些输入(在我们的例子中是权重)最直接地造成了网络的损失(或错误)。

这是dL/dW的数学等式,其中W是特定层的权重。现在,我们要做的是通过网络进行反向传递,即确定哪些权重对损失贡献最大,并设法调整这些权重,从而减少损失。一旦我们计算出这个导数,我们就会进入最后一步——权重更新weight update。这就是我们如何获得所有过滤器的权重,并更新它们,使它们向梯度的相反方向变化。

学习率Learning rate是由程序员选择的参数。高学习率意味着在权重更新中采取更大的步幅,因此,模型可能花费更少的时间来收敛到最优权重集。但是,如果学习率过高,可能会导致跳跃过大,不够精确,无法达到最佳点。

正向传递,损失函数,反向传递和参数更新的过程是一次训练迭代。程序将不断重复这个过程,对每组训练图像(通常称为批次batch)进行固定次数的迭代。 一旦你完成了最后一个训练样例的参数更新,希望你的网络已经被训练得足够好,这样才能让各个层的权重被正确地调整。


测试

最后,为了看看我们的CNN是否有效,我们有一套不同的图像和标签集合(在训练和测试之间不能一蹴而就),并将这个测试结合通过CNN。我们将输出与实际情况进行比较,看看我们的网络是否正常工作!


大公司如何使用CNN

数据,数据,数据。那些拥有数据的公司就是那些比其他竞争者更具有内在优势的公司。你可以为网络提供的训练数据越多,你就可以进行越多的训练迭代次数,就可以进行越多的权重更新,那么在投入生产之后网络的调整就越好。Facebook(和Instagram)可以使用目前拥有的十亿用户的所有照片,Pinterest可以使用其网站上500亿个pins的信息,Google可以使用搜索数据,而Amazon可以使用每天被购买的数百万种产品。现在你知道他们如何使用它的魔法了。


声明

尽管这篇文章应该是理解CNN的好开始,但这并不是一个全面的概述。在这篇文章中没有讨论的东西包括非线性和合并层以及网络的超参数,如过滤器大小,步长和填充。 还没有讨论网络架构,批量归一化,梯度减小,dropout,初始化技术,非凸优化,偏差,损失函数选择,数据增强,正则化方法,计算考虑,反向传播修改等主题。

Links to Part2

]]>
Top 10 deep learning algorithm 2017-11-22T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/11/Top 10 Deep Learning Algorithm 原文链接


过去10年,人们对机器学习的兴趣激增。几乎每天,你都可以在各种各样的计算机科学课程、行业会议、华尔街日报等等看到有关机器学习的讨论。在所有关于机器学习的讨论中,许多人把机器学习能做的事情和他们希望机器学习做的事情混为一谈。从根本上讲,机器学习是使用算法从原始数据中提取信息,并在某种类型的模型中表示这些信息。我们使用这个模型来推断还没有建模的其他数据。

神经网络是机器学习的一种模型,它们至少有50年历史了。神经网络的基本单元是节点(node),基本上是受哺乳动物大脑中的生物神经元启发。神经元之间的连接也以生物的大脑为模型,这些连接随着时间的推移而发展的方式是为“训练”。

在20世纪80年代中期和90年代初期,许多重要的架构进步都是在神经网络进行的。然而,为了得到好的结果需要大量时间和数据,这阻碍了神经网络的采用,因而人们的兴趣也减少了。在21世纪初,计算能力呈指数级增长,计算技术出现了“寒武纪大爆发”。在这个10年的爆炸式的计算增长中,深度学习成为这个领域的重要的竞争者,赢得了许多重要的机器学习竞赛。直到2017年,这种兴趣也还没有冷却下来;今天,我们看到一说机器学习,就不得不提深度学习。

本文中,作者总结了10个强大的深度学习方法,这是AI工程师可以应用于他们的机器学习问题的。首先,下面这张图直观地说明了人工智能、机器学习和深度学习三者之间的关系。

人工智能的领域很广泛,深度学习是机器学习领域的一个子集,机器学习又是人工智能的一个子领域。将深度学习网络与“经典的”前馈式多层网络区分开来的因素如下:

  • 比以前的网络有更多的神经元
  • 更复杂的连接层的方法
  • 用于训练网络的计算机能力的“寒武纪大爆炸”
  • 自动特征提取

这里说的“更多的神经元”时,是指神经元的数量在逐年增加,以表达更复杂的模型。层(layers)也从多层网络中的每一层都完全连接,到在卷积神经网络中层之间连接局部的神经元,再到在循环神经网络中与同一神经元的循环连接( recurrent connections)。

深度学习可以被定义为具有大量参数和层的神经网络,包括以下四种基本网络结构:

  • 无监督预训练网络
  • 卷积神经网络
  • 循环神经网络
  • 递归神经网络

在本文中,主要介绍后三种架构。基本上,卷积神经网络(CNN)是一个标准的神经网络,通过共享的权重在空间中扩展。CNN设计用于通过内部的卷积来识别图像,它可以看到图像中待识别的物体的边缘。循环神经网络(RNN)被设计用于识别序列,例如语音信号或文本。它的内部有循环,这意味着网络上有短的记忆。递归神经网络更像是一个层级网络,在这个网络中,输入必须以一种树的方式进行分层处理。下面的10种方法可以应用于所有这些架构。


1,反向传播

反向传播(Back-prop)是一种计算函数偏导数(或梯度)的方法,具有函数构成的形式(就像神经网络中)。当使用基于梯度的方法(梯度下降只是方法之一)解决优化问题时,你需要在每次迭代中计算函数梯度。

对于神经网络,目标函数具有组合的形式。如何计算梯度呢?有两种常用的方法:(i)解析微分(Analytic differentiation)。你已经知道函数的形式,只需要用链式法则(基本微积分)来计算导数。(ii)利用有限差分进行近似微分。这种方法在计算上很昂贵,因为函数值的数量是O(N),N指代参数的数量。不过,有限差分通常用于在调试时验证back-prop实现。


2,随机梯度下降法

一种直观理解梯度下降的方法是想象一条河流从山顶流下的路径。梯度下降的目标正是河流努力达到的目标——即,到达最底端(山脚)。

现在,如果山的地形是这样的,在到达最终目的地之前,河流不会完全停下来(这是山脚的最低点,那么这就是我们想要的理想情况。)在机器学习中,相当从初始点(山顶)开始,我们找到了解决方案的全局最小(或最佳)解。然而,可能因为地形的性质迫使河流的路径出现几个坑,这可能迫使河流陷入困境。在机器学习术语中,这些坑被称为局部极小值,这是不可取的。有很多方法可以解决这个问题。

因此,梯度下降很容易被困在局部极小值,这取决于地形的性质(用ML的术语来说是函数的性质)。但是,当你有一种特殊的地形时(形状像一个碗,用ML的术语来说,叫做凸函数),算法总是保证能找到最优解。凸函数对ML的优化来说总是好事,取决于函数的初始值,你可能会以不同的路径结束。同样地,取决于河流的速度(即,梯度下降算法的学习速率或步长),你可能以不同的方式到达最终目的地。这两个标准都会影响到你是否陷入坑里(局部极小值)。


3,学习率衰减

根据随机梯度下降的优化过程调整学习率(learning rate)可以提高性能并减少训练时间。 有时这被称为学习率退火( learning rate annealing)或自适应学习率(adaptive learning rates)。训练过程中最简单,也是最常用的学习率适应是随着时间的推移而降低学习度。 在训练过程开始时使用较大学习率具有进行大的改变的好处,然后降低学习率,使得后续对权重的训练更新更小。这具有早期快速学习好权重,后面进行微调的效果。

两种常用且易于使用的学习率衰减方法如下:

  • 逐步降低学习率。
  • 在特定的时间点较大地降低学习率。

4,Dropout

具有大量参数的深度神经网络是非常强大的机器学习系统。然而,过拟合在这样的网络中是一个严重的问题。大型网络的使用也很缓慢,这使得在测试时将许多不同的大型神经网络的预测结合起来变得困难。Dropout是解决这个问题的一种方法。

Dropout 的关键想法是在训练过程中随机地从神经网络中把一些units(以及它们的连接)从神经网络中删除。这样可以防止单元过度适应。在训练过程中,从一个指数级的不同的“稀疏”网络中删除一些样本。在测试时,通过简单地使用一个具有较小权重的单一网络,可以很容易地估计所有这些“变瘦”了的网络的平均预测效果。这显著减少了过拟合,相比其他正则化方法有了很大改进。研究表明,在视觉、语音识别、文档分类和计算生物学等监督学习任务中,神经网络的表现有所提高,在许多基准数据集上获得了state-of-the-art的结果。


5, Max Pooling

最大池化(Max pooling)是一个基于样本的离散化过程。目标是对输入表示(图像,隐藏层输出矩阵等)进行下采样,降低其维度,并允许对包含在分区域中的特征进行假设。

这在一定程度上是为了通过提供一种抽象的表示形式来帮助过拟合。同时,它通过减少学习的参数数量,并为内部表示提供基本的平移不变性(translation invariance),从而减少计算成本。最大池化是通过将一个最大过滤器应用于通常不重叠的初始表示的子区域来完成的。


6,批量归一化

当然,包括深度网络在内的神经网络需要仔细调整权重初始化和学习参数。而批量标准化有助于实现这一点。

权重问题:无论权重的初始化如何,是随机的也好是经验性的选择也罢,都距离学习到的权重很遥远。考虑一个小批量(mini batch),在最初时,在所需的特征激活方面将会有许多异常值。

深度神经网络本身是有缺陷的,初始层中一个微小的扰动,就会导致后面层巨大的变化。在反向传播过程中,这些现象会导致对梯度的分散,这意味着在学习权重以产生所需输出之前,梯度必须补偿异常值,而这将导致需要额外的时间才能收敛。

批量归一化将梯度从分散规范化到正常值,并在小批量范围内向共同目标(通过归一化)流动。

学习率问题:一般来说,学习率保持较低,只有一小部分的梯度校正权重,原因是异常激活的梯度不应影响学习的激活。通过批量归一化,减少异常激活,因此可以使用更高的学习率来加速学习过程。


7,长短时记忆

LSTM网络在以下三个方面与RNN的神经元不同:

  • 能够决定何时让输入进入神经元;
  • 能够决定何时记住上一个时间步中计算的内容;
  • 能够决定何时让输出传递到下一个时间步长。

LSTM的优点在于它根据当前的输入本身来决定所有这些。所以,你看下面的图表:

当前时间标记处的输入信号x(t)决定所有上述3点。输入门从点1接收决策,遗忘门从点2接收决策,输出门在点3接收决策,单独的输入能够完成所有这三个决定。这受到我们的大脑如何工作的启发,并且可以基于输入来处理突然的上下文/场景切换。


8,Skip-gram

词嵌入模型的目标是为每个词汇项学习一个高维密集表示,其中嵌入向量之间的相似性显示了相应词之间的语义或句法相似性。Skip-gram是学习单词嵌入算法的模型。

Skip-gram模型(以及许多其他的词语嵌入模型)的主要思想是:如果两个词汇项(vocabulary term)共享的上下文相似,那么这两个词汇项就相似。

换句话说,假设你有一个句子,比如“猫是哺乳动物”。如果你用“狗”去替换“猫”,这个句子仍然是一个有意义的句子。因此在这个例子中,“狗”和“猫”可以共享相同的上下文(即“是哺乳动物”)。

基于上述假设,你可以考虑一个上下文窗口(context window,一个包含k个连续项的窗口),然后你跳过其中一个单词,试着去学习一个能够得到除跳过项外所有项的神经网络,并预测跳过的项是什么。如果两个词在一个大语料库中反复共享相似的语境,则这些词的嵌入向量将具有相近的向量。


9,连续词袋(Continuous Bag Of Words)

在自然语言处理问题中,我们希望学习将文档中的每个单词表示为一个数字向量,使得出现在相似的上下文中的单词具有彼此接近的向量。在连续的单词模型中,我们的目标是能够使用围绕特定单词的上下文并预测特定单词。

我们通过在一个庞大的语料库中抽取大量的句子来做到这一点,每当我们看到一个单词时,我们就会提取它周围的单词。然后,我们将上下文单词输入到一个神经网络,并预测位于这个上下文中心的单词。

当我们有成千上万的这样的上下文单词和中心词以后,我们就有了一个神经网络数据集的实例。训练神经网络,最后编码的隐藏层输出表示特定单词的嵌入。而当我们对大量的句子进行训练时也能发现,类似语境中的单词得到的是相似的向量。


10,迁移学习

让我们考虑图像如何穿过卷积神经网络。假设你有一个图像,你应用卷积,并得到像素的组合作为输出。假设这些输出是边缘(edge)。现在再次应用卷积,现在你的输出就是边或线的组合。然后再次应用卷积,你的输出是线的组合,以此类推……你可以把它看作是每一层寻找一个特定的模式。神经网络的最后一层往往会变得非常特异化。如果你在ImageNet上工作,你的网络最后一层大概就是在寻找儿童、狗或飞机等整体图案。再往后倒退几层,你可能会看到网络在寻找眼睛或耳朵或嘴巴或轮子这样的组成部件。

深度CNN中的每一层都逐步建立起越来越高层次的特征表征。最后几层往往是专门针对输入模型的数据。另一方面,早期的图层更为通用。而迁移学习就是当你在一个数据集上训练CNN时,切掉最后一层,在不同的数据集上重新训练最后一层的模型。直观地说,你正在重新训练模型以识别不同的高级特征。因此,训练时间会减少很多,所以当你没有足够的数据或者训练需要太多的资源时,迁移学习是一个有用的工具。


Reference

]]>