Kaka Chen 2017-12-05T07:17:25+00:00 silver.accc@gmail.com 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

]]>
Iris species python数据可视化 2017-11-17T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/11/Iris Species Python数据可视化 Iris Speciessklearn里很常用而简单的一个分类数据集,数据除了id和label外,还有四个属性:

  • sepal length (cm)
  • sepal width (cm)
  • petal length (cm)
  • petal width (cm)

为了能在二维图像中展示,以下就暂时只有petal length和petal width两个属性进行展示。

import pandas as pd
import numpy as np
from sklearn import datasets

iris = datasets.load_iris()
# 从sklearn.datasets导入数据,格式是sklearn.unit形式的

X = iris.data[:, [2, 3]]
y = iris.target
iris_df = pd.DataFrame(iris.data[:, [2, 3]], 
	columns=iris.feature_names[2:])
	
# 读取iris的data和target存入X和y,存入pandas.DataFrame里
# 暂时只保留petal length和petal width两个属性

print(iris_df.head())
print('\n' + 'The unique labels in this data are ' + 
	str(np.unique(y)))
# 打印前5个数据查看内容

输出结果:

   petal length (cm)  petal width (cm)
0                1.4               0.2
1                1.4               0.2
2                1.3               0.2
3                1.5               0.2
4                1.4               0.2

The unique labels in this data are [0 1 2]

将完整的数据集以7:3的比例分割成训练集和测试集

X_train, X_test, y_train, y_test = 
	train_test_split(X, y, test_size=.3, random_state=0)

print('There are {} samples in the training set 
	and {} samples in the test set'.
	format(X_train.shape[0], X_test.shape[0]))

输出结果:

There are 105 samples in the training set 
	and 45 samples in the test set

在常见的数据预处理中,将数据标准化是很重要的,能把被处理的数据缩放到一个合适的范围,有助于之后算法的收敛和准确性。

from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(X_train)
# 用StandardScaler来fit训练数据

X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

print('After standardizing our features, 
	the first 5 rows of our data now look like this:\n')
print(pd.DataFrame(X_train_std, 
	columns=iris_df.columns).head())
	
# 查看标准化结果

输出结果

After standardizing our features, 
	the first 5 rows of our data now look like this:

   petal length (cm)  petal width (cm)
0          -0.182950         -0.291459
1           0.930661          0.737219
2           1.042022          1.637313
3           0.652258          0.351465
4           1.097702          0.737219

然后通过matplotlib.pyplot包来生成原始的分类图像

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

markers = ('s', 'x', 'o')
# 设定标记类型
colors = ('red', 'blue', 'lightgreen')
# 设定颜色
cmap = ListedColormap(colors[:len(np.unique(y_test))])
for idx, cl in enumerate(np.unique(y)):
	plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], c=cmap(idx), 
		marker=markers[idx], label=cl)
plt.show()

在正式应用模型之前,先定义一个之后展示的方法。

def versiontuple(v):
    return tuple(map(int, (v.split("."))))


def plot_decision_regions(X, y, classifier, 
	test_idx=None, resolution=0.02):

    # setup marker generator and color map
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # plot the decision surface
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
                    alpha=0.8, c=cmap(idx),
                    marker=markers[idx], label=cl)
    plt.show()

使用SVM模型

svm = SVC(kernel='rbf', random_state=0, gamma=.10, C=1.0)
# 用rbf核
svm.fit(X_train_std, y_train)

print('The accuracy of the svm classifier on training data is 
	{:.2f} out of 1'.format(svm.score(X_train_std, y_train)))
print('The accuracy of the svm classifier on test data is 
	{:.2f} out of 1'.format(svm.score(X_test_std, y_test)))
# 打印svm模型在训练集和测试集上的得分

plot_decision_regions(X_test_std, y_test, svm)
# 展示图像

输出结果:

The accuracy of the svm classifier 
	on training data is 0.95 out of 1
The accuracy of the svm classifier 
	on test data is 0.98 out of 1

使用KNN模型

knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')
knn.fit(X_train_std, y_train)

print('The accuracy of the knn classifier is 
	{:.2f} out of 1 on training data'.
	format(knn.score(X_train_std, y_train)))
print('The accuracy of the knn classifier is 
	{:.2f} out of 1 on test data'.
	format(knn.score(X_test_std, y_test)))

plot_decision_regions(X_test_std, y_test, knn)

输出结果:

The accuracy of the knn classifier 
	is 0.95 out of 1 on training data
The accuracy of the knn classifier 
	is 1.00 out of 1 on test data

使用XGB

xgb_clf = xgb.XGBClassifier()
xgb_clf = xgb_clf.fit(X_train_std, y_train)

print('The accuracy of the xgb classifier is 
	{:.2f} out of 1 on training data'.
	format(xgb_clf.score(X_train_std, y_train)))
print('The accuracy of the xgb classifier is 
	{:.2f} out of 1 on test data'.
	format(xgb_clf.score(X_test_std, y_test)))

plot_decision_regions(X_test_std, y_test, xgb_clf)

输出结果:

The accuracy of the xgb classifier 
	is 0.98 out of 1 on training data
The accuracy of the xgb classifier 
	is 0.98 out of 1 on test data

在测试集上,KNN表现最好,而在训练集上,SVM和XGB表现更加优越。

]]>
来自塞外那渺茫的歌声——历险大西北 2017-10-10T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/10/来自塞外那渺茫的歌声——历险大西北 从西北回来已经两天了,趁着这几天上班没什么事情,就把这趟旅行的游记写了。原则上我们是去敦煌研究院帮忙验收,但实际上还是旅游为主。很久没经历那么赶路的旅行了,我看了一眼地图,将近2500km的环线行程,我们就这么开了下来,也是佩服自己,可能以后再也不会这么玩命开车了。


从西宁到青海湖

因为机票贵的缘故,选择了杭州往返兰州,然后落地兰州之后马上租了车前往西宁过夜。因为晚上到白天一早就走的缘故,我并没有在西宁多逗留很久。西宁给我的印象就是一个很普通的标准的中国城市,区别仅在于这边确实有浓郁的回族风味,比如满大街的羊肉烧烤和牛肉拉面店。第一天我确实很新奇,对他们这的饮食充满了好奇,尤其是发现他们居然馆子里面没有勺子或者调羹,喝羊杂汤都是端起大碗嘴对嘴长流水。西北民风之彪悍也可见一斑,跟我们江南一口三抿的喝汤风格截然不同。

第一站是西宁著名的塔尔寺,典型的藏传佛教寺庙,白塔、唐卡、金顶、转轮和宽大的讲经堂。我是着实感受到了十一旅游的狂潮,寺庙里人山人海,很难真正静下心来跟当地的和尚喇嘛聊聊天感受一下他们信仰和文化,只能走马观花地拍照。可惜的是我从不喜欢在宗教场所室内拍照,感觉是对宗教的不尊重,所以也只能拍拍外景。

当然,在寺庙里还是看到了很震撼我的一幕,很多上了年纪的牧民,在我裹得严严实实的时候,穿着露袖的宗教服饰,匍匐在地,连续不断地行跪拜礼。我虽然是个无神主义者,但是我尊重他人的信仰,虽然我也觉得某一些宗教里确实有很值得商榷的教义。

青海湖的景色确实让我惊讶,这边的一切都显得那么苍茫而空旷,如果没有那么多旅游人群的话确实是一片仙境。在湖边我第一次骑了牦牛,比我想像的乖很多,任我肆意撸。平静的湖面倒映着远山和云彩,我这时候突然反应过来“青海”这个名字的由来,或许在那个信息不那么发达的年代,这些住在距离最近的海岸线近2500公里外的牧民,看到这一望无际的湖面,难免会觉得是找到了大海的方向。

我知道青海是著名的长江、黄河、澜沧江三江发源地,沿途的山谷里,到处都是这样的小溪流慢慢从高原流下,我不知道它们会流向哪里,是汇入江海还是湖泊,虽然这次我们没有机会去一睹三江源的风采,或许黄河长江在源头也是这样的小溪。


西出阳关无故人

因为行程的缘故,我们需要在一天内从柴达木盆地内的德令哈赶到这次最主要的目的地敦煌,然后参观当天下午的莫高窟,所以只能很早出发赶路,然后遗憾地没能去著名的阳关、玉门关和汉长城遗址,虽说我一直以来很想亲眼看看这些见证着我们民族兴衰的标志,无奈只能看看别人的照片了。

祁连山脉绵延千里,是青海和甘肃的交界,景色绮丽壮美,蜿蜒巍峨的山峰和雪山耸立在苍茫天地间。

莫高窟是我这趟的最主要目的地,预约票能参观有解说的八个洞窟,和四个没解说的长期开放的窟。从南北朝十六国开始一直到民国,莫高窟汇聚了这1500多年中国文化的瑰宝,各种不同年代和文明的艺术在这里交织碰撞,看的很是过瘾。可惜的是莫高窟的破坏很严重,所以我也特别有责任感,我们做的数字图书馆能一劳永逸地将这些瑰宝保存呈现给后人看。

鸣沙山和月牙泉比我想象中还美丽,确实是塞上江南的一颗明珠。我第一次尝试攀爬沙漠上的山丘,比我想象的要困难多了,一步一滑,如果没有辅助措施确实是寸步难行,不禁佩服当年往来沙漠的使团和商队,是怎么一步步翻阅崇山峻岭前行的。

当然来这骑骆驼是少不了的,这也是我第一次骑这货,一开始非常的颠簸,之后却在沙丘上保持着动态平衡。由于十一黄金周的缘故,沙丘上的骆驼队络绎不绝,延绵好几公里,可能当初丝绸之路最繁忙的时候也不过如此。


七彩丹霞和马蹄寺

我一直吐槽说,我以前知道河西走廊上有武威郡、张掖郡和嘉峪关,但我确实不知道那边有什么值得玩的景点,直到有次在电影开演前的广告上看到了七彩丹霞地质公园的广告,才觉得应该来这一趟。

丹霞地貌山峦上不太有植被,但是这确实是我印象中西北所应该有的样子。

马蹄寺在张掖南边60公里的小山村里,很遗憾没参观到三十三洞窟,因为人实在是太多了。但是从这边能看到祁连雪山和雪山下的村庄。据说河西走廊之所以能水足土肥就是靠着祁连山脉上的冰川融解灌溉

马蹄寺修筑在悬崖之上,从下往上看特别震撼。


甘肃省博物馆

就为了一睹这马踏飞燕图的风采,我们最后一天一早去了兰州甘肃省博物馆。

这个元代的水晶杯是现存唯一一个蓝色的水晶杯,质地非常好,色泽无比漂亮。

长得像恶魔果实的仰韶文化彩陶。

长得像秦岭神树的青铜树。

从小就在书上看到的曾经的亚洲最大——马门溪龙。


西北人

最后我是真的很想吐槽一下西北的交通和城市道路规划建设。感觉整个甘肃省都在大刀阔斧地道路整修,导致整个城市交通一塌糊涂,处处都是断头路。但是就是在这些被圈围起来的道路里,却诞生着这个城市最有代表性的美食味道。 临行前一天晚上,我坐的摊位是一对回族夫妇经营的。我跟老板娘聊了好久。我一直觉得自己是个看人很准的人,我在她脸上看到了一个我们似乎遗失了很久的纯朴的笑脸,让我特别感动。她一脸开心地说起自己的生活,这不禁让我反思了很多,似乎在网络上我们对回民妖魔化了太多,或许他们其中确实有坏人,或许他们的信仰确实有值得商榷之处,但这不能成为我们否定他们每一个人的理由,因为至少在这对夫妇身上,和那些因为突降大雨跟他们一起互帮互助的商贩和路人身上,我看到了那种人内心深处毫无掩饰的善良。

]]>
【转】kaggle数据挖掘比赛基本流程 2017-09-08T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/【转】Kaggle数据挖掘比赛基本流程 转载【干货】Kaggle 数据挖掘比赛经验分享


简介

Kaggle 于 2010 年创立,专注数据科学,机器学习竞赛的举办,是全球最大的数据科学社区和数据竞赛平台。笔者从 2013 年开始,陆续参加了多场 Kaggle上面举办的比赛,相继获得了 CrowdFlower 搜索相关性比赛第一名(1326支队伍)和 HomeDepot 商品搜索相关性比赛第三名(2125支队伍),曾在 Kaggle 数据科学家排行榜排名全球第十,国内第一。笔者目前在腾讯社交与效果广告部任职数据挖掘工程师,负责 Lookalike 相似人群扩展相关工作。此文分享笔者在参加数据挖掘比赛过程中的一点心得体会。


1.Kaggle 基本介绍

Kaggle 于 2010 年创立,专注数据科学,机器学习竞赛的举办,是全球最大的数据科学社区和数据竞赛平台。在 Kaggle 上,企业或者研究机构发布商业和科研难题,悬赏吸引全球的数据科学家,通过众包的方式解决建模问题。而参赛者可以接触到丰富的真实数据,解决实际问题,角逐名次,赢取奖金。诸如 Google,Facebook,Microsoft 等知名科技公司均在 Kaggle 上面举办过数据挖掘比赛。2017年3月,Kaggle 被 Google CloudNext 收购。

1.1 参赛方式

可以以个人或者组队的形式参加比赛。组队人数一般没有限制,但需要在 Merger Deadline 前完成组队。为了能参与到比赛中,需要在 Entry Deadline 前进行至少一次有效提交。最简单地,可以直接提交官方提供的 Sample Submission。关于组队,建议先单独个人进行数据探索和模型构建,以个人身份进行比赛,在比赛后期(譬如离比赛结束还有 2~3 周)再进行组队,以充分发挥组队的效果(类似于模型集成,模型差异性越大,越有可能有助于效果的提升,超越单模型的效果)。当然也可以一开始就组好队,方便分工协作,讨论问题和碰撞火花。

Kaggle 对比赛的公正性相当重视。在比赛中,每个人只允许使用一个账号进行提交。在比赛结束后 1到2 周内,Kaggle 会对使用多账号提交的 Cheater 进行剔除(一般会对 Top 100 的队伍进行 Cheater Detection)。在被剔除者的 Kaggle 个人页面上,该比赛的成绩也会被删除,相当于该选手从没参加过这个比赛。此外,队伍之间也不能私自分享代码或者数据,除非在论坛上面公开发布。

比赛一般只提交测试集的预测结果,无需提交代码。每人(或每个队伍)每天有提交次数的限制,一般为2次或者5次,在 Submission 页面会有提示。

1.2 比赛获奖

Kaggle 比赛奖金丰厚,一般前三名均可以获得奖金。在最近落幕的第二届 National Data Science Bowl 中,总奖金池高达 100W 美刀,其中第一名可以获得 50W 美刀的奖励,即使是第十名也能收获 2.5W 美刀的奖金。

获奖的队伍需要在比赛结束后 1~2 周内,准备好可执行的代码以及 README,算法说明文档等提交给 Kaggle 来进行获奖资格的审核。Kaggle 会邀请获奖队伍在 Kaggle Blog 中发表 Interview,来分享比赛故事和经验心得。对于某些比赛,Kaggle 或者主办方会邀请获奖队伍进行电话/视频会议,获奖队伍进行 Presentation,并与主办方团队进行交流。

1.3 比赛类型

从 Kaggle 提供的官方分类来看,可以划分为以下类型(如下图1所示):

  • Featured:商业或科研难题,奖金一般较为丰厚;
  • Recruitment:比赛的奖励为面试机会;
  • Research:科研和学术性较强的比赛,也会有一定的奖金,一般需要较强的领域和专业知识;
  • Playground:提供一些公开的数据集用于尝试模型和算法;
  • Getting Started:提供一些简单的任务用于熟悉平台和比赛;
  • In Class:用于课堂项目作业或者考试。

从领域归属划分:包含搜索相关性,广告点击率预估,销量预估,贷款违约判定,癌症检测等。

从任务目标划分:包含回归,分类(二分类,多分类,多标签),排序,混合体(分类+回归)等。

从数据载体划分:包含文本,语音,图像和时序序列等。

从特征形式划分:包含原始数据,明文特征,脱敏特征(特征的含义不清楚)等。

1.4 比赛流程

一个数据挖掘比赛的基本流程如下图2所示,具体的模块我将在下一章进行展开陈述。

这里想特别强调的一点是,Kaggle 在计算得分的时候,有Public Leaderboard (LB)和 Private LB 之分。具体而言,参赛选手提交整个测试集的预测结果,Kaggle 使用测试集的一部分计算得分和排名,实时显示在 Public LB上,用于给选手提供及时的反馈和动态展示比赛的进行情况;测试集的剩余部分用于计算参赛选手的最终得分和排名,此即为 Private LB,在比赛结束后会揭晓。用于计算 Public LB 和 Private LB 的数据有不同的划分方式,具体视比赛和数据的类型而定,一般有随机划分,按时间划分或者按一定规则划分。

这个过程可以概括如下图3所示,其目的是避免模型过拟合,以得到泛化能力好的模型。如果不设置 Private LB(即所有的测试数据都用于计算 Public LB),选手不断地从 Public LB(即测试集)中获得反馈,进而调整或筛选模型。这种情况下,测试集实际上是作为验证集参与到模型的构建和调优中来。Public LB上面的效果并非是在真实未知数据上面的效果,不能可靠地反映模型的效果。划分 Public LB 和 Private LB 这样的设置,也在提醒参赛者,我们建模的目标是要获得一个在未知数据上表现良好的模型,而并非仅仅是在已知数据上效果好。


2.数据挖掘比赛基本流程

从上面图2可以看到,做一个数据挖掘比赛,主要包含了数据分析,数据清洗,特征工程,模型训练和验证等四个大的模块,以下来一一对其进行介绍。

2.1 数据分析

数据分析可能涉及以下方面:

◆ 分析特征变量的分布

◇ 特征变量为连续值:如果为长尾分布并且考虑使用线性模型,可以对变量进行幂变换或者对数变换。

◇ 特征变量为离散值:观察每个离散值的频率分布,对于频次较低的特征,可以考虑统一编码为“其他”类别。

◆ 分析目标变量的分布

◇ 目标变量为连续值:查看其值域范围是否较大,如果较大,可以考虑对其进行对数变换,并以变换后的值作为新的目标变量进行建模(在这种情况下,需要对预测结果进行逆变换)。一般情况下,可以对连续变量进行Box-Cox变换。通过变换可以使得模型更好的优化,通常也会带来效果上的提升。

◇ 目标变量为离散值:如果数据分布不平衡,考虑是否需要上采样/下采样;如果目标变量在某个ID上面分布不平衡,在划分本地训练集和验证集的时候,需要考虑分层采样(Stratified Sampling)。

◆ 分析变量之间两两的分布和相关度

◇ 可以用于发现高相关和共线性的特征。

通过对数据进行探索性分析(甚至有些情况下需要肉眼观察样本),还可以有助于启发数据清洗和特征抽取,譬如缺失值和异常值的处理,文本数据是否需要进行拼写纠正等。

2.2 数据清洗

数据清洗是指对提供的原始数据进行一定的加工,使得其方便后续的特征抽取。其与特征抽取的界限有时也没有那么明确。常用的数据清洗一般包括:

◆ 数据的拼接

◇ 提供的数据散落在多个文件,需要根据相应的键值进行数据的拼接。

◆ 特征缺失值的处理

◇ 特征值为连续值:按不同的分布类型对缺失值进行补全:偏正态分布,使用均值代替,可以保持数据的均值;偏长尾分布,使用中值代替,避免受 outlier 的影响;

◇ 特征值为离散值:使用众数代替。

◆ 文本数据的清洗

◇ 在比赛当中,如果数据包含文本,往往需要进行大量的数据清洗工作。如去除HTML 标签,分词,拼写纠正, 同义词替换,去除停词,抽词干,数字和单位格式统一等。

2.3 特征工程

有一种说法是,特征决定了效果的上限,而不同模型只是以不同的方式或不同的程度来逼近这个上限。这样来看,好的特征输入对于模型的效果至关重要,正所谓”Garbage in, garbage out”。要做好特征工程,往往跟领域知识和对问题的理解程度有很大的关系,也跟一个人的经验相关。特征工程的做法也是Case by Case,以下就一些点,谈谈自己的一些看法。

2.3.1 特征变换

主要针对一些长尾分布的特征,需要进行幂变换或者对数变换,使得模型(LR或者DNN)能更好的优化。需要注意的是,Random Forest 和 GBDT 等模型对单调的函数变换不敏感。其原因在于树模型在求解分裂点的时候,只考虑排序分位点。

2.3.2 特征编码

对于离散的类别特征,往往需要进行必要的特征转换/编码才能将其作为特征输入到模型中。常用的编码方式有 LabelEncoder,OneHotEncoder(sklearn里面的接口)。譬如对于”性别”这个特征(取值为男性和女性),使用这两种方式可以分别编码为{0,1}和{[1,0], [0,1]}。

对于取值较多(如几十万)的类别特征(ID特征),直接进行OneHotEncoder编码会导致特征矩阵非常巨大,影响模型效果。可以使用如下的方式进行处理:

◆ 统计每个取值在样本中出现的频率,取 Top N 的取值进行 One-hot 编码,剩下的类别分到“其他“类目下,其中 N 需要根据模型效果进行调优;

◆ 统计每个 ID 特征的一些统计量(譬如历史平均点击率,历史平均浏览率)等代替该 ID 取值作为特征,具体可以参考 Avazu 点击率预估比赛第二名的获奖方案;

◆ 参考 word2vec 的方式,将每个类别特征的取值映射到一个连续的向量,对这个向量进行初始化,跟模型一起训练。训练结束后,可以同时得到每个ID的Embedding。具体的使用方式,可以参考 Rossmann 销量预估竞赛第三名的获奖方案,entron/entity-embedding-rossmann。

对于 Random Forest 和 GBDT 等模型,如果类别特征存在较多的取值,可以直接使用 LabelEncoder 后的结果作为特征。

2.4 模型训练和验证

2.4.1 模型选择

在处理好特征后,我们可以进行模型的训练和验证。

◆ 对于稀疏型特征(如文本特征,One-hot的ID类特征),我们一般使用线性模型,譬如 Linear Regression 或者 Logistic Regression。Random Forest 和 GBDT 等树模型不太适用于稀疏的特征,但可以先对特征进行降维(如PCA,SVD/LSA等),再使用这些特征。稀疏特征直接输入 DNN 会导致网络 weight 较多,不利于优化,也可以考虑先降维,或者对 ID 类特征使用 Embedding 的方式;

◆ 对于稠密型特征,推荐使用 XGBoost 进行建模,简单易用效果好;

◆ 数据中既有稀疏特征,又有稠密特征,可以考虑使用线性模型对稀疏特征进行建模,将其输出与稠密特征一起再输入 XGBoost/DNN 建模,具体可以参考2.5.2节 Stacking 部分。

2.4.2 调参和模型验证

对于选定的特征和模型,我们往往还需要对模型进行超参数的调优,才能获得比较理想的效果。调参一般可以概括为以下三个步骤:

一,训练集和验证集的划分。根据比赛提供的训练集和测试集,模拟其划分方式对训练集进行划分为本地训练集和本地验证集。划分的方式视具体比赛和数据而定,常用的方式有:

a) 随机划分:譬如随机采样 70% 作为训练集,剩余的 30% 作为测试集。在这种情况下,本地可以采用 KFold 或者 Stratified KFold 的方法来构造训练集和验证集。
b) 按时间划分:一般对应于时序序列数据,譬如取前 7 天数据作为训练集,后 1 天数据作为测试集。这种情况下,划分本地训练集和验证集也需要按时间先后划分。常见的错误方式是随机划分,这种划分方式可能会导致模型效果被高估。
c) 按某些规则划分:在 HomeDepot 搜索相关性比赛中,训练集和测试集中的 Query 集合并非完全重合,两者只有部分交集。而在另外一个相似的比赛中(CrowdFlower 搜索相关性比赛),训练集和测试集具有完全一致的 Query 集合。对于 HomeDepot 这个比赛中,训练集和验证集数据的划分,需要考虑 Query 集合并非完全重合这个情况,其中的一种方法可以参考第三名的获奖方案,https://github.com/ChenglongChen/Kaggle_HomeDepot。

二,指定参数空间。在指定参数空间的时候,需要对模型参数以及其如何影响模型的效果有一定的了解,才能指定出合理的参数空间。譬如DNN或者XGBoost中学习率这个参数,一般就选 0.01 左右就 OK 了(太大可能会导致优化算法错过最优化点,太小导致优化收敛过慢)。再如 Random Forest,一般设定树的棵数范围为 100~200 就能有不错的效果,当然也有人固定数棵数为 500,然后只调整其他的超参数。

三,按照一定的方法进行参数搜索。常用的参数搜索方法有,Grid Search,Random Search以及一些自动化的方法(如 Hyperopt)。其中,Hyperopt 的方法,根据历史已经评估过的参数组合的效果,来推测本次评估使用哪个参数组合更有可能获得更好的效果。有关这些方法的介绍和对比,可以参考文献 [2]。

2.4.3 适当利用 Public LB 的反馈

在2.4.2节中我们提到本地验证(Local Validation)结果,当将预测结果提交到 Kaggle 上时,我们还会接收到 Public LB 的反馈结果。如果这两个结果的变化趋势是一致的,如 Local Validation 有提升,Public LB 也有提升,我们可以借助 Local Validation 的变化来感知模型的演进情况,而无需靠大量的 Submission。如果两者的变化趋势不一致,需要考虑2.4.2节中提及的本地训练集和验证集的划分方式,是否跟训练集和测试集的划分方式一致。

另外,在以下一些情况下,往往 Public LB 反馈亦会提供有用信息,适当地使用这些反馈也许会给你带来优势。如图4所示,(a)和(b)表示数据与时间没有明显的关系(如图像分类),(c)和(d)表示数据随时间变化(如销量预估中的时序序列)。(a)和(b)的区别在于,训练集样本数相对于 Public LB 的量级大小,其中(a)中训练集样本数远超于 Public LB 的样本数,这种情况下基于训练集的 Local Validation 更可靠;而(b)中,训练集数目与 Public LB 相当,这种情况下,可以结合 Public LB 的反馈来指导模型的选择。一种融合的方式是根据 Local Validation 和 Public LB 的样本数目,按比例进行加权。譬如评估标准为正确率,Local Validation 的样本数为 N_l,正确率为 A_l;Public LB 的样本数为 N_p,正确率为 A_p。则可以使用融合后的指标:(N_l * A_l + N_p * A_p)/(N_l + N_p),来进行模型的筛选。对于(c)和(d),由于数据分布跟时间相关,很有必要使用 Public LB 的反馈来进行模型的选择,尤其对于(c)图所示的情况。

2.5 模型集成

如果想在比赛中获得名次,几乎都要进行模型集成(组队也是一种模型集成)。关于模型集成的介绍,已经有比较好的博文了,可以参考 [3]。在这里,我简单介绍下常用的方法,以及个人的一些经验。

2.5.1 Averaging 和 Voting

直接对多个模型的预测结果求平均或者投票。对于目标变量为连续值的任务,使用平均;对于目标变量为离散值的任务,使用投票的方式。

2.5.2 Stacking

上图展示了使用 5-Fold 进行一次 Stacking 的过程(当然在其上可以再叠加 Stage 2, Stage 3 等)。其主要的步骤如下:

  1. 数据集划分。将训练数据按照5-Fold进行划分(如果数据跟时间有关,需要按时间划分,更一般的划分方式请参考3.4.2节,这里不再赘述);

  2. 基础模型训练 I(如图5第一行左半部分所示)。按照交叉验证(Cross Validation)的方法,在训练集(Training Fold)上面训练模型(如图灰色部分所示),并在验证集(Validation Fold)上面做预测,得到预测结果(如图黄色部分所示)。最后综合得到整个训练集上面的预测结果(如图第一个黄色部分的CV Prediction所示)。

  3. 基础模型训练 II(如图5第二和三行左半部分所示)。在全量的训练集上训练模型(如图第二行灰色部分所示),并在测试集上面做预测,得到预测结果(如图第三行虚线后绿色部分所示)。

  4. Stage 1 模型集成训练 I(如图5第一行右半部分所示)。将步骤 2 中得到的 CV Prediction 当作新的训练集,按照步骤 2 可以得到 Stage 1模型集成的 CV Prediction。

  5. Stage 1 模型集成训练 II(如图5第二和三行右半部分所示)。将步骤 2 中得到的 CV Prediction 当作新的训练集和步骤 3 中得到的 Prediction 当作新的测试集,按照步骤 3 可以得到 Stage 1 模型集成的测试集 Prediction。此为 Stage 1 的输出,可以提交至 Kaggle 验证其效果。

在图5中,基础模型只展示了一个,而实际应用中,基础模型可以多种多样,如SVM,DNN,XGBoost 等。也可以相同的模型,不同的参数,或者不同的样本权重。重复4和5两个步骤,可以相继叠加 Stage 2, Stage 3 等模型。

2.5.3 Blending

Blending 与 Stacking 类似,但单独留出一部分数据(如 20%)用于训练 Stage X 模型。

2.5.4 Bagging Ensemble Selection

Bagging Ensemble Selection [5] 是我在 CrowdFlower 搜索相关性比赛中使用的方法,其主要的优点在于可以以优化任意的指标来进行模型集成。这些指标可以是可导的(如 LogLoss 等)和不可导的(如正确率,AUC,Quadratic Weighted Kappa等)。它是一个前向贪婪算法,存在过拟合的可能性,作者在文献 [5] 中提出了一系列的方法(如 Bagging)来降低这种风险,稳定集成模型的性能。使用这个方法,需要有成百上千的基础模型。为此,在 CrowdFlower 的比赛中,我把在调参过程中所有的中间模型以及相应的预测结果保留下来,作为基础模型。这样做的好处是,不仅仅能够找到最优的单模型(Best Single Model),而且所有的中间模型还可以参与模型集成,进一步提升效果。

2.6 自动化框架

从上面的介绍可以看到,做一个数据挖掘比赛涉及到的模块非常多,若有一个较自动化的框架会使得整个过程更加的高效。在 CrowdFlower 比赛较前期,我对整一个项目的代码架构进行了重构,抽象出来特征工程,模型调参和验证,以及模型集成等三大模块,极大的提高了尝试新特征,新模型的效率,也是我最终能斩获名次的一个有利因素。这份代码开源在 Github 上面,目前是 Github 有关 Kaggle 竞赛解决方案的 Most Stars,地址:ChenglongChen/Kaggle_CrowdFlower

其主要包含以下部分:

一,模块化特征工程

a) 接口统一,只需写少量的代码就能够生成新的特征;

b) 自动将单独的特征拼接成特征矩阵。

二,自动化模型调参和验证

a) 自定义训练集和验证集的划分方法;

b) 使用 Grid Search / Hyperopt 等方法,对特定的模型在指定的参数空间进行调优,并记录最佳的模型参数以及相应的性能。

三,自动化模型集成

a) 对于指定的基础模型,按照一定的方法(如Averaging/Stacking/Blending 等)生成集成模型。 

3.Kaggle竞赛方案盘点

到目前为止,Kaggle 平台上面已经举办了大大小小不同的赛事,覆盖图像分类,销量预估,搜索相关性,点击率预估等应用场景。在不少的比赛中,获胜者都会把自己的方案开源出来,并且非常乐于分享比赛经验和技巧心得。这些开源方案和经验分享对于广大的新手和老手来说,是入门和进阶非常好的参考资料。以下笔者结合自身的背景和兴趣,对不同场景的竞赛开源方案作一个简单的盘点,总结其常用的方法和工具,以期启发思路。

3.1 图像分类

3.1.1 任务名称

National Data Science Bowl

3.1.2 任务详情

随着深度学习在视觉图像领域获得巨大成功,Kaggle 上面出现了越来越多跟视觉图像相关的比赛。这些比赛的发布吸引了众多参赛选手,探索基于深度学习的方法来解决垂直领域的图像问题。NDSB就是其中一个比较早期的图像分类相关的比赛。这个比赛的目标是利用提供的大量的海洋浮游生物的二值图像,通过构建模型,从而实现自动分类。

3.1.3 获奖方案

● 1st place:Cyclic Pooling + Rolling Feature Maps + Unsupervised and Semi-Supervised Approaches。值得一提的是,这个队伍的主力队员也是Galaxy Zoo行星图像分类比赛的第一名,其也是Theano中基于FFT的Fast Conv的开发者。在两次比赛中,使用的都是 Theano,而且用的非常溜。方案链接:Classifying plankton with deep neural networks

● 2nd place:Deep CNN designing theory + VGG-like model + RReLU。这个队伍阵容也相当强大,有前MSRA 的研究员Xudong Cao,还有大神Tianqi Chen,Naiyan Wang,Bing XU等。Tianqi 等大神当时使用的是 CXXNet(MXNet 的前身),也在这个比赛中进行了推广。Tianqi 大神另外一个大名鼎鼎的作品就是 XGBoost,现在 Kaggle 上面几乎每场比赛的 Top 10 队伍都会使用。方案链接:National Data Science Bowl

● 17th place:Realtime data augmentation + BN + PReLU。方案链接:ChenglongChen/caffe-windows

3.1.4 常用工具

▲ Theano: Welcome - Theano 0.9.0 documentation

▲ Keras: Keras Documentation

▲ Cuda-convnet2: akrizhevsky/cuda-convnet2

▲ Caffe: [Caffe Deep Learning Framework](http://link.zhihu.com/?target=http%3A//caffe.berkeleyvision.org/)

▲ CXXNET: dmlc/cxxnet

▲ MXNet: dmlc/mxnet

▲ PaddlePaddle: PaddlePaddle —- PArallel Distributed Deep LEarning

3.2 销量预估

3.2.1 任务名称

Walmart Recruiting - Store Sales Forecasting

3.2.2 任务详情

Walmart 提供 2010-02-05 到 2012-11-01 期间的周销售记录作为训练数据,需要参赛选手建立模型预测 2012-11-02 到 2013-07-26 周销售量。比赛提供的特征数据包含:Store ID, Department ID, CPI,气温,汽油价格,失业率,是否节假日等。

3.2.3 获奖方案

● 1st place:Time series forecasting method: stlf + arima + ets。主要是基于时序序列的统计方法,大量使用了 Rob J Hyndman 的 forecast R 包。方案链接:Walmart Recruiting - Store Sales Forecasting

● 2nd place:Time series forecasting + ML: arima + RF + LR + PCR。时序序列的统计方法+传统机器学习方法的混合;方案链接:Walmart Recruiting - Store Sales Forecasting

● 16th place:Feature engineering + GBM。方案链接:ChenglongChen/Kaggle_Walmart-Recruiting-Store-Sales-Forecasting

3.2.4 常用工具

▲ R forecast package: https://cran.r-project.org/web/packages/forecast/index.html

▲ R GBM package: https://cran.r-project.org/web/packages/gbm/index.html

3.3 搜索相关性

3.3.1 任务名称

CrowdFlower Search Results Relevance

3.3.2 任务详情

比赛要求选手利用约几万个 (query, title, description) 元组的数据作为训练样本,构建模型预测其相关性打分 {1, 2, 3, 4}。比赛提供了 query, title和description的原始文本数据。比赛使用 Quadratic Weighted Kappa 作为评估标准,使得该任务有别于常见的回归和分类任务。

3.3.3 获奖方案

● 1st place:Data Cleaning + Feature Engineering + Base Model + Ensemble。对原始文本数据进行清洗后,提取了属性特征,距离特征和基于分组的统计特征等大量的特征,使用了不同的目标函数训练不同的模型(回归,分类,排序等),最后使用模型集成的方法对不同模型的预测结果进行融合。方案链接:ChenglongChen/Kaggle_CrowdFlower

● 2nd place:A Similar Workflow

● 3rd place: A Similar Workflow

3.3.4 常用工具

▲ NLTK: Natural Language Toolkit

▲ Gensim: gensim: topic modelling for humans

▲ XGBoost: dmlc/xgboost

▲ RGF: baidu/fast_rgf

3.4 点击率预估 I

3.4.1 任务名称

Criteo Display Advertising Challenge

3.4.2 任务详情

经典的点击率预估比赛。该比赛中提供了7天的训练数据,1 天的测试数据。其中有13 个整数特征,26 个类别特征,均脱敏,因此无法知道具体特征含义。

3.4.3 获奖方案

● 1st place:GBDT 特征编码 + FFM。台大的队伍,借鉴了Facebook的方案 [6],使用 GBDT 对特征进行编码,然后将编码后的特征以及其他特征输入到 Field-aware Factorization Machine(FFM) 中进行建模。方案链接:Display Advertising Challenge Kaggle
● 3rd place:Quadratic Feature Generation + FTRL。传统特征工程和 FTRL 线性模型的结合。方案链接:Display Advertising Challenge Kaggle

● 4th place:Feature Engineering + Sparse DNN

3.4.4 常用工具

▲ Vowpal Wabbit: JohnLangford/vowpal_wabbit

▲ XGBoost: dmlc/xgboost

▲ LIBFFM: LIBFFM: A Library for Field-aware Factorization Machines

3.5 点击率预估 II

3.5.1 任务名称

Avazu Click-Through Rate Prediction

3.5.2 任务详情

点击率预估比赛。提供了 10 天的训练数据,1 天的测试数据,并且提供时间,banner 位置,site, app, device 特征等,8个脱敏类别特征。

3.5.3 获奖方案

● 1st place:Feature Engineering + FFM + Ensemble。还是台大的队伍,这次比赛,他们大量使用了 FFM,并只基于 FFM 进行集成。方案链接:Click-Through Rate Prediction Kaggle

● 2nd place:Feature Engineering + GBDT 特征编码 + FFM + Blending。Owenzhang(曾经长时间雄霸 Kaggle 排行榜第一)的竞赛方案。Owenzhang 的特征工程做得非常有参考价值。方案链接:owenzhang/kaggle-avazu

3.5.4 常用工具

▲ LIBFFM: LIBFFM: A Library for Field-aware Factorization Machines

▲ XGBoost: dmlc/xgboost


4.参考资料

  • [1] Owenzhang 的分享: Tips for Data Science Competitions
  • [2] Algorithms for Hyper-Parameter Optimization
  • [3] MLWave博客:Kaggle Ensembling Guide
  • [4] Jeong-Yoon Lee 的分享:Winning Data Science Competitions
  • [5] Ensemble Selection from Libraries of Models
  • [6] Practical Lessons from Predicting Clicks on Ads at Facebook
]]>
Building a deep neural network 2017-09-07T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/Building a Deep Neural Network Build a Deep Neural Network

1 - Package

  • numpy Python基础科学计算库
  • matplotlib Python绘图库
  • dnn_utils 提供一些必要的函数
  • testCases 为自己的函数提供一些测试集
  • np.random.seed(1) 随机种子
import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v2 import *
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward

%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

%load_ext autoreload
%autoreload 2

np.random.seed(1)

2 - 大纲

  • 为一个2-layer的网络和一个L-layer的神经网络初始化参数
  • 实现Forward propagation模块:
    • 计算一层的线性部分(记Z[l])
    • 获得激活函数(relu/sigmoid)
    • 将上述两步(linear->activation)合并入一个新的Forward函数
    • 将[linear->relu]Forward propagation结合L-1次(在layer 1上),并在后面添加一个[linear->sigmoid]
  • 计算损失
  • 实现Backward propagation模块:
    • 计算backward propagation部分中的线性部分
    • 得到激活函数的梯度(relu_backward/sigmoid_backward)
    • 将上述两步合并到一个新的backward函数[linear->activation]
    • 将[linear->relu] backward L-1次,并在后面添加一个[linear->sigmoid]到一个新的L-model_backward函数中
  • 最终更新参数


3 - 初始化

3.1 2-Layer神经网络

  • 模型结构: linear->relu->linear->sigmoid
  • np.random.randn(shape)*0.01初始化权重w
  • np.zeros(shape)初始化偏移量b
# GRADED FUNCTION: initialize_parameters

def initialize_parameters(n_x, n_h, n_y):
    """
    Argument:
    n_x -- size of the input layer
    n_h -- size of the hidden layer
    n_y -- size of the output layer
    
    Returns:
    parameters -- python dictionary containing your parameters:
                    W1 -- weight matrix of shape (n_h, n_x)
                    b1 -- bias vector of shape (n_h, 1)
                    W2 -- weight matrix of shape (n_y, n_h)
                    b2 -- bias vector of shape (n_y, 1)
    """
    
    np.random.seed(1)
    
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    assert(W1.shape == (n_h, n_x))
    assert(b1.shape == (n_h, 1))
    assert(W2.shape == (n_y, n_h))
    assert(b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters    
parameters = initialize_parameters(2,2,1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[ 0.01624345 -0.00611756] [-0.00528172 -0.01072969]]
**b1** [[ 0.] [ 0.]]
**W2** [[ 0.00865408 -0.02301539]]
**b2** [[ 0.]]

各层的W、b、激活函数尺寸大小

在Python中计算WX+b

  • 这个模型的结构[linear->relu]x(L-1)->Linear->Sigmoid
  • 用random初始化权重w矩阵 np.random.rand(shape) * 0.01
  • 用0初始化偏移量b,np.zeros(shape)
  • 将不同层的units个数n[l]存在一个变量layer_dims
# GRADED FUNCTION: initialize_parameters_deep

def initialize_parameters_deep(layer_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the dimensions of each layer in our network
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
                    bl -- bias vector of shape (layer_dims[l], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)            # number of layers in the network

    for l in range(1, L):

        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
        
        assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
        assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))
      
    return parameters
parameters = initialize_parameters_deep([5,4,3])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[ 0.01788628 0.0043651 0.00096497 -0.01863493 -0.00277388] [-0.00354759 -0.00082741 -0.00627001 -0.00043818 -0.00477218] [-0.01313865 0.00884622 0.00881318 0.01709573 0.00050034] [-0.00404677 -0.0054536 -0.01546477 0.00982367 -0.01101068]]
**b1** [[ 0.] [ 0.] [ 0.] [ 0.]]
**W2** [[-0.01185047 -0.0020565 0.01486148 0.00236716] [-0.01023785 -0.00712993 0.00625245 -0.00160513] [-0.00768836 -0.00230031 0.00745056 0.01976111]]
**b2** [[ 0.] [ 0.] [ 0.]]

4 - Forward Propagation模块

4.1 Linear Forward

  • LINEAR
  • LINEAR -> ACTIVATION where ACTIVATION will be either ReLU or Sigmoid.
  • [LINEAR -> RELU] ×× (L-1) -> LINEAR -> SIGMOID (whole model)

Linear Forward模块计算这个等式:

其中:

# GRADED FUNCTION: linear_forward

def linear_forward(A, W, b):
    """
    Implement the linear part of a layer's forward propagation.

    Arguments:
    A -- activations from previous layer (or input data): (size of previous layer, number of examples)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    b -- bias vector, numpy array of shape (size of the current layer, 1)

    Returns:
    Z -- the input of the activation function, also called pre-activation parameter 
    cache -- a python dictionary containing "A", "W" and "b" ; stored for computing the backward pass efficiently
    """
    
    Z = np.dot(W, A) + b
    
    assert(Z.shape == (W.shape[0], A.shape[1]))
    cache = (A, W, b)
    
    return Z, cache
A, W, b = linear_forward_test_case()

Z, linear_cache = linear_forward(A, W, b)
print("Z = " + str(Z))

# Z = [[ 3.26295337 -1.23429987]]

4.2 Linear-Activation-Forward

  • Sigmoid

运行:A, activation_cache = sigmoid(Z)

  • ReLu

运行A, activation_cache = relu(Z)

# GRADED FUNCTION: linear_activation_forward

def linear_activation_forward(A_prev, W, b, activation):
    """
    Implement the forward propagation for the LINEAR->ACTIVATION layer

    Arguments:
    A_prev -- activations from previous layer (or input data): (size of previous layer, number of examples)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    b -- bias vector, numpy array of shape (size of the current layer, 1)
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"

    Returns:
    A -- the output of the activation function, also called the post-activation value 
    cache -- a python dictionary containing "linear_cache" and "activation_cache";
             stored for computing the backward pass efficiently
    """
    
    if activation == "sigmoid":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".

        Z, linear_cache = np.dot(W, A_prev) + b, (A_prev, W, b)
        A, activation_cache = sigmoid(Z)
    
    elif activation == "relu":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".

        Z, linear_cache = np.dot(W, A_prev) + b, (A_prev, W, b)
        A, activation_cache = relu(Z)
    
    assert (A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)

    return A, cache
A_prev, W, b = linear_activation_forward_test_case()

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "sigmoid")
print("With sigmoid: A = " + str(A))

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "relu")
print("With ReLU: A = " + str(A))

# With sigmoid: A = [[ 0.96890023  0.11013289]]
# With ReLU: A = [[ 3.43896131  0.        ]]

4.3 L-Layer-Model

# GRADED FUNCTION: L_model_forward

def L_model_forward(X, parameters):
    """
    Implement forward propagation for the [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID computation
    
    Arguments:
    X -- data, numpy array of shape (input size, number of examples)
    parameters -- output of initialize_parameters_deep()
    
    Returns:
    AL -- last post-activation value
    caches -- list of caches containing:
                every cache of linear_relu_forward() (there are L-1 of them, indexed from 0 to L-2)
                the cache of linear_sigmoid_forward() (there is one, indexed L-1)
    """

    caches = []
    A = X
    L = len(parameters) // 2                  # number of layers in the neural network
    
    # Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
    for l in range(1, L):
        A_prev = A 

        A, cache = linear_activation_forward(A_prev, parameters['W' + str(l)], parameters['b' + str(l)], activation = "relu")
        caches.append(cache)
    
    # Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.

    AL, cache = linear_activation_forward(A, parameters['W' + str(L)], parameters['b' + str(L)], activation = "sigmoid")
    caches.append(cache)
        
    assert(AL.shape == (1,X.shape[1]))
            
    return AL, caches
X, parameters = L_model_forward_test_case()
AL, caches = L_model_forward(X, parameters)
print("AL = " + str(AL))
print("Length of caches list = " + str(len(caches)))

# AL = [[ 0.17007265  0.2524272 ]]
# Length of caches list = 2

5 - Cost Function

 # GRADED FUNCTION: compute_cost
def compute_cost(AL, Y):
    """
    Implement the cost function defined by equation (7).
    Arguments:
    AL -- probability vector corresponding to your label predictions, shape (1, number of examples)
    Y -- true "label" vector (for example: containing 0 if non-cat, 1 if cat), shape (1, number of examples)
    Returns:
    cost -- cross-entropy cost
    """
    
    m = Y.shape[1]

    # Compute loss from aL and y.

    cost = -1./m * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1-AL))
    
    cost = np.squeeze(cost)      # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
    assert(cost.shape == ())
    
    return cost
 Y, AL = compute_cost_test_case()

print("cost = " + str(compute_cost(AL, Y)))

# cost = 0.414931599615

6 - Backward propagation module

  • LINEAR backward
  • LINEAR -> ACTIVATION backward where ACTIVATION computes the derivative of either the ReLU or sigmoid activation
  • [LINEAR -> RELU] ×× (L-1) -> LINEAR -> SIGMOID backward (whole model)

6.1 Linear Backward

# GRADED FUNCTION: linear_backward

def linear_backward(dZ, cache):
    """
    Implement the linear portion of backward propagation for a single layer (layer l)

    Arguments:
    dZ -- Gradient of the cost with respect to the linear output (of current layer l)
    cache -- tuple of values (A_prev, W, b) coming from the forward propagation in the current layer

    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    dW = 1./m * np.dot(dZ, A_prev.T)
    db = 1./m * np.sum(np.matrix(dZ), axis = 1)
    dA_prev = np.dot(W.T, dZ)
    
    assert (dA_prev.shape == A_prev.shape)
    assert (dW.shape == W.shape)
    assert (db.shape == b.shape)
    
    return dA_prev, dW, db
# Set up some test inputs
dZ, linear_cache = linear_backward_test_case()

dA_prev, dW, db = linear_backward(dZ, linear_cache)
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

# dA_prev = [[ 0.51822968 -0.19517421]
#  [-0.40506361  0.15255393]
#  [ 2.37496825 -0.89445391]]
# dW = [[-0.10076895  1.40685096  1.64992505]]
# db = [[ 0.50629448]]

6.2 Linear-Activation-Function

  • sigmoid_backwarddZ = sigmoid_backward(dA, activation_cache)
  • relu_backwarddZ = relu_backward(dA, activation_cache)

计算:

# GRADED FUNCTION: linear_activation_backward

def linear_activation_backward(dA, cache, activation):
    """
    Implement the backward propagation for the LINEAR->ACTIVATION layer.
    
    Arguments:
    dA -- post-activation gradient for current layer l 
    cache -- tuple of values (linear_cache, activation_cache) we store for computing backward propagation efficiently
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"
    
    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    linear_cache, activation_cache = cache
    
    if activation == "relu":
        dZ = relu_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
        
    elif activation == "sigmoid":
        dZ = sigmoid_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
    
    return dA_prev, dW, db
AL, linear_activation_cache = linear_activation_backward_test_case()

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "sigmoid")
print ("sigmoid:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db) + "\n")

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "relu")
print ("relu:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

6.3 L-Model Backward

首先计算dAL:

dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL)) 

# derivative of cost with respect to AL

将dA、dW和db都存在grad dictionary里:

# GRADED FUNCTION: L_model_backward

def L_model_backward(AL, Y, caches):
    """
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    
    Arguments:
    AL -- probability vector, output of the forward propagation (L_model_forward())
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat)
    caches -- list of caches containing:
                every cache of linear_activation_forward() with "relu" (it's caches[l], for l in range(L-1) i.e l = 0...L-2)
                the cache of linear_activation_forward() with "sigmoid" (it's caches[L-1])
    
    Returns:
    grads -- A dictionary with the gradients
             grads["dA" + str(l)] = ... 
             grads["dW" + str(l)] = ...
             grads["db" + str(l)] = ... 
    """
    grads = {}
    L = len(caches) # the number of layers
    m = AL.shape[1]
    Y = Y.reshape(AL.shape) # after this line, Y is the same shape as AL
    
    # Initializing the backpropagation

    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    
    # Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "AL, Y, caches". Outputs: "grads["dAL"], grads["dWL"], grads["dbL"]

    current_cache = caches[L-1]
    grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = 'sigmoid')
    
    for l in reversed(range(L-1)):
        # lth layer: (RELU -> LINEAR) gradients.
        # Inputs: "grads["dA" + str(l + 2)], caches". Outputs: "grads["dA" + str(l + 1)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)] 

        current_cache = caches[L - 2 -l]
        dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(L)], current_cache, activation = 'relu')
        grads["dA" + str(l + 1)] = dA_prev_temp
        grads["dW" + str(l + 1)] = dW_temp
        grads["db" + str(l + 1)] = db_temp

    return grads
AL, Y_assess, caches = L_model_backward_test_case()
grads = L_model_backward(AL, Y_assess, caches)
print ("dW1 = "+ str(grads["dW1"]))
print ("db1 = "+ str(grads["db1"]))
print ("dA1 = "+ str(grads["dA1"]))

6.4 更新参数

# GRADED FUNCTION: update_parameters

def update_parameters(parameters, grads, learning_rate):
    """
    Update parameters using gradient descent
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    grads -- python dictionary containing your gradients, output of L_model_backward
    
    Returns:
    parameters -- python dictionary containing your updated parameters 
                  parameters["W" + str(l)] = ... 
                  parameters["b" + str(l)] = ...
    """
    
    L = len(parameters) // 2 # number of layers in the neural network

    # Update rule for each parameter. Use a for loop.

    for l in range(L):
        parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate * grads["dW" + str(l+1)]
        parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate * grads["db" + str(l+1)]
        
    return parameters
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads, 0.1)

print ("W1 = "+ str(parameters["W1"]))
print ("b1 = "+ str(parameters["b1"]))
print ("W2 = "+ str(parameters["W2"]))
print ("b2 = "+ str(parameters["b2"]))
]]>
Planar data classification with one hidden layer 2017-09-06T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/Planar Data Classification with One Hidden Layer Planar data classification with one hidden layer

1 - Package

  • numpy Python的基础科学计算库
  • sklearn 提供简单有效的数据挖掘和分析工具
  • matplotlib Python图像绘制库
  • testCases 提供为我们的函数判断正确率的测试例子
  • planar_utils 提供其他一些有用的函数

# Package imports
import numpy as np
import matplotlib.pyplot as plt
from testCases import *
import sklearn
import sklearn.datasets
import sklearn.linear_model
from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets

%matplotlib inline

np.random.seed(1) # set a seed so that the results are consistent


2 - 数据集

载入一个花瓣状的2-class数据集,坐标轴分别为X和Y。

X, Y = load_planar_dataset()

# Visualize the data:
plt.scatter(X[0, :], X[1, :], c=Y, s=40, cmap=plt.cm.Spectral);

现在数据中有的是:

  • 一个numpy-array (matrix) X,包含了特征值(X1, X2)
  • 一个numpy-array (vector) Y,包含了标签(红色:0;蓝色:1)

更直观地感受一下数据:


shape_X = X.shape
shape_Y = Y.shape
m = shape_Y[1]  # training set size

print ('The shape of X is: ' + str(shape_X))
print ('The shape of Y is: ' + str(shape_Y))
print ('I have m = %d training examples!' % (m))

# The shape of X is: (2, 400)
# The shape of Y is: (1, 400)
# I have m = 400 training examples!


3 - 一个简单的Logistic Regression

sklearn自带的线性logistic regression分类器做简单分类:


# Train the logistic regression classifier
clf = sklearn.linear_model.LogisticRegressionCV();
clf.fit(X.T, Y.T);

# Plot the decision boundary for logistic regression
plot_decision_boundary(lambda x: clf.predict(x), X, Y)
plt.title("Logistic Regression")

# Print accuracy
LR_predictions = clf.predict(X.T)
print ('Accuracy of logistic regression: %d ' % float((np.dot(Y,LR_predictions) + np.dot(1-Y,1-LR_predictions))/float(Y.size)*100) +
       '% ' + "(percentage of correctly labelled datapoints)")
       
# Accuracy of logistic regression: 47 % (percentage of correctly labelled datapoints)

显然这个数据集不是线性可分的


4 - 神经网络模型

以下是我们希望迅雷的神经网络模型

在数学上,对于一个例子:

当有了所有例子上的预测值之后,可以得到损失函数

通常获得一个神经网络模型的方法是:

  • 定义神经网络结构(包括输入个数、隐藏层数等)
  • 初始化模型参数
  • 循环:
    • 执行forward propagation
    • 计算损失cost
    • 执行backward propagation,获得梯度
    • 更新参数(梯度下降)

4.1 定义神经网络结构

  • n_x: 输入层大小
  • n_h: 隐藏层大小
  • n_y: 输出层大小

# GRADED FUNCTION: layer_sizes

def layer_sizes(X, Y):
    """
    Arguments:
    X -- input dataset of shape (input size, number of examples)
    Y -- labels of shape (output size, number of examples)
    
    Returns:
    n_x -- the size of the input layer
    n_h -- the size of the hidden layer
    n_y -- the size of the output layer
    """
    
    n_x = X.shape[0] # size of input layer
    n_h = 4
    n_y = Y.shape[0] # size of output layer
    return (n_x, n_h, n_y)


X_assess, Y_assess = layer_sizes_test_case()
(n_x, n_h, n_y) = layer_sizes(X_assess, Y_assess)
print("The size of the input layer is: n_x = " + str(n_x))
print("The size of the hidden layer is: n_h = " + str(n_h))
print("The size of the output layer is: n_y = " + str(n_y))

# The size of the input layer is: n_x = 5
# The size of the hidden layer is: n_h = 4
# The size of the output layer is: n_y = 2

4.2 初始化参数

  • np.random.randn(a,b) * 0.01来初始化w
  • np.zeros((a,b))来初始化b

# GRADED FUNCTION: initialize_parameters

def initialize_parameters(n_x, n_h, n_y):
    """
    Argument:
    n_x -- size of the input layer
    n_h -- size of the hidden layer
    n_y -- size of the output layer
    
    Returns:
    params -- python dictionary containing your parameters:
                    W1 -- weight matrix of shape (n_h, n_x)
                    b1 -- bias vector of shape (n_h, 1)
                    W2 -- weight matrix of shape (n_y, n_h)
                    b2 -- bias vector of shape (n_y, 1)
    """
    
    np.random.seed(2) 
    
    # we set up a seed so that your output matches ours although the initialization is random.
    
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    assert (W1.shape == (n_h, n_x))
    assert (b1.shape == (n_h, 1))
    assert (W2.shape == (n_y, n_h))
    assert (b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters


n_x, n_h, n_y = initialize_parameters_test_case()

parameters = initialize_parameters(n_x, n_h, n_y)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))


**W1** [[-0.00416758 -0.00056267] [-0.02136196 0.01640271] [-0.01793436 -0.00841747] [ 0.00502881 -0.01245288]]
**b1** [[ 0.] [ 0.] [ 0.] [ 0.]]
**W2** [[-0.01057952 -0.00909008 0.00551454 0.02292208]]
**b2** [[ 0.]]

4.3 循环

# GRADED FUNCTION: forward_propagation

def forward_propagation(X, parameters):
    """
    Argument:
    X -- input data of size (n_x, m)
    parameters -- python dictionary containing your parameters (output of initialization function)
    
    Returns:
    A2 -- The sigmoid output of the second activation
    cache -- a dictionary containing "Z1", "A1", "Z2" and "A2"
    """
    # Retrieve each parameter from the dictionary "parameters"
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Implement Forward Propagation to calculate A2 (probabilities)
    Z1 = np.dot(W1,X) + b1
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2,A1) + b2
    A2 = sigmoid(Z2)
    
    assert(A2.shape == (1, X.shape[1]))
    
    cache = {"Z1": Z1,
             "A1": A1,
             "Z2": Z2,
             "A2": A2}
    
    return A2, cache


X_assess, parameters = forward_propagation_test_case()

A2, cache = forward_propagation(X_assess, parameters)

# Note: we use the mean here just to make sure that your output matches ours. 
print(np.mean(cache['Z1']) ,np.mean(cache['A1']),np.mean(cache['Z2']),np.mean(cache['A2']))

# -0.000499755777742 -0.000496963353232 0.000438187450959 0.500109546852

既然已经得到了包含所有a[2](i)A2,那么可以计算损失函数:

为了方便起见,可以这样计算:

logprobs = np.multiply(np.log(A2),Y)
cost = - np.sum(logprobs)              
# no need to use a for loop!
# GRADED FUNCTION: compute_cost

def compute_cost(A2, Y, parameters):
    """
    Computes the cross-entropy cost given in equation 
    
    Arguments:
    A2 -- The sigmoid output of the second activation, of shape (1, number of examples)
    Y -- "true" labels vector of shape (1, number of examples)
    parameters -- python dictionary containing your parameters W1, b1, W2 and b2
    
    Returns:
    cost -- cross-entropy cost given equation 
    """
    
    m = Y.shape[1] # number of example

    # Compute the cross-entropy cost

    logprobs = np.multiply(np.log(A2), Y)
    cost = - np.sum(np.multiply(np.log(A2), Y) + np.multiply(np.log(1. - A2), 1. - Y)) / m
    
    cost = np.squeeze(cost)     # makes sure cost is the dimension we expect. 
                                # E.g., turns [[17]] into 17 
    assert(isinstance(cost, float))
    
    return cost
A2, Y_assess, parameters = compute_cost_test_case()

print("cost = " + str(compute_cost(A2, Y_assess, parameters)))

# cost = 0.692919893776

通过在forward propagation中计算得到的cache,可以用在backward propagation的计算当中。

# GRADED FUNCTION: backward_propagation

def backward_propagation(parameters, cache, X, Y):
    """
    Implement the backward propagation using the instructions above.
    
    Arguments:
    parameters -- python dictionary containing our parameters 
    cache -- a dictionary containing "Z1", "A1", "Z2" and "A2".
    X -- input data of shape (2, number of examples)
    Y -- "true" labels vector of shape (1, number of examples)
    
    Returns:
    grads -- python dictionary containing your gradients with respect to different parameters
    """
    m = X.shape[1]
    
    # First, retrieve W1 and W2 from the dictionary "parameters".

    W1 = parameters['W1']
    W2 = parameters['W2']
        
    # Retrieve also A1 and A2 from dictionary "cache".

    A1 = cache['A1']
    A2 = cache['A2']
    
    # Backward propagation: calculate dW1, db1, dW2, db2. 

    dZ2= A2 - Y
    dW2 = np.dot(dZ2, A1.T) / m
    db2 = np.sum(dZ2, axis = 1, keepdims = True) / m
    dZ1 = np.dot(W2.T, dZ2) * (1 - A1**2)
    dW1 = np.dot(dZ1, X.T) / m
    db1 = np.sum(dZ1, axis = 1, keepdims = True) / m
    
    grads = {"dW1": dW1,
             "db1": db1,
             "dW2": dW2,
             "db2": db2}
    
    return grads
parameters, cache, X_assess, Y_assess = backward_propagation_test_case()

grads = backward_propagation(parameters, cache, X_assess, Y_assess)
print ("dW1 = "+ str(grads["dW1"]))
print ("db1 = "+ str(grads["db1"]))
print ("dW2 = "+ str(grads["dW2"]))
print ("db2 = "+ str(grads["db2"]))
**dW1** [[ 0.01018708 -0.00708701] [ 0.00873447 -0.0060768 ] [-0.00530847 0.00369379] [-0.02206365 0.01535126]]
**db1** [[-0.00069728] [-0.00060606] [ 0.000364 ] [ 0.00151207]]
**dW2** [[ 0.00363613 0.03153604 0.01162914 -0.01318316]]
**db2** [[ 0.06589489]]

通过更新规则,使用梯度下降算法,可以利用(dW1, db1, dW2, db2)来更新参数(W1, b1, W2, b2)

计算方法:

其中,学习率α选取的好坏对于模型的质量有很大的影响:

# GRADED FUNCTION: update_parameters

def update_parameters(parameters, grads, learning_rate = 1.2):
    """
    Updates parameters using the gradient descent update rule given above
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    grads -- python dictionary containing your gradients 
    
    Returns:
    parameters -- python dictionary containing your updated parameters 
    """
    # Retrieve each parameter from the dictionary "parameters"

    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Retrieve each gradient from the dictionary "grads"

    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']
    
    # Update rule for each parameter

    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters

parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads)

print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[-0.00643025 0.01936718] [-0.02410458 0.03978052] [-0.01653973 -0.02096177] [ 0.01046864 -0.05990141]]
**b1** [[ -1.02420756e-06] [ 1.27373948e-05] [ 8.32996807e-07] [ -3.20136836e-06]]
**W2** [[-0.01041081 -0.04463285 0.01758031 0.04747113]]
**b2** [[ 0.00010457]]

4.4 将所有函数汇聚到nn_model()

# GRADED FUNCTION: nn_model

def nn_model(X, Y, n_h, num_iterations = 10000, print_cost=False):
    """
    Arguments:
    X -- dataset of shape (2, number of examples)
    Y -- labels of shape (1, number of examples)
    n_h -- size of the hidden layer
    num_iterations -- Number of iterations in gradient descent loop
    print_cost -- if True, print the cost every 1000 iterations
    
    Returns:
    parameters -- parameters learnt by the model. They can then be used to predict.
    """
    
    np.random.seed(3)
    n_x = layer_sizes(X, Y)[0]
    n_y = layer_sizes(X, Y)[2]
    
    # Initialize parameters, then retrieve W1, b1, W2, b2. Inputs: "n_x, n_h, n_y". Outputs = "W1, b1, W2, b2, parameters".

    parameters = initialize_parameters(n_x, n_h, n_y)
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Loop (gradient descent)

    for i in range(0, num_iterations):
         
        # Forward propagation. Inputs: "X, parameters". Outputs: "A2, cache".
        A2, cache = forward_propagation(X, parameters)
        
        # Cost function. Inputs: "A2, Y, parameters". Outputs: "cost".
        cost = compute_cost(A2, Y, parameters)
 
        # Backpropagation. Inputs: "parameters, cache, X, Y". Outputs: "grads".
        grads = backward_propagation(parameters, cache, X, Y)
 
        # Gradient descent parameter update. Inputs: "parameters, grads". Outputs: "parameters".
        parameters = update_parameters(parameters, grads)
                
        # Print the cost every 1000 iterations
        if print_cost and i % 1000 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))

    return parameters
X_assess, Y_assess = nn_model_test_case()

parameters = nn_model(X_assess, Y_assess, 4, num_iterations=10000, print_cost=False)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[-4.18494056 5.33220609] [-7.52989382 1.24306181] [-4.1929459 5.32632331] [ 7.52983719 -1.24309422]]
**b1** [[ 2.32926819] [ 3.79458998] [ 2.33002577] [-3.79468846]]
**W2** [[-6033.83672146 -6008.12980822 -6033.10095287 6008.06637269]]
**b2** [[-52.66607724]]

4.5 预测

预测方法:

# GRADED FUNCTION: predict

def predict(parameters, X):
    """
    Using the learned parameters, predicts a class for each example in X
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    X -- input data of size (n_x, m)
    
    Returns
    predictions -- vector of predictions of our model (red: 0 / blue: 1)
    """
    
    # Computes probabilities using forward propagation, and classifies to 0/1 using 0.5 as the threshold.

    A2, cache = forward_propagation(X, parameters)
    predictions = np.array([0 if i <= 0.5 else 1 for i in np.squeeze(A2)])
    
    return predictions
parameters, X_assess = predict_test_case()

predictions = predict(parameters, X_assess)
print("predictions mean = " + str(np.mean(predictions)))

# predictions mean = 0.666666666667

接下来在一个有h层隐藏层的神经网络中做测试:

# Build a model with a n_h-dimensional hidden layer
parameters = nn_model(X, Y, n_h = 4, num_iterations = 10000, print_cost=True)

# Plot the decision boundary
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))

# Cost after iteration 0: 0.693048
# Cost after iteration 1000: 0.288083
# Cost after iteration 2000: 0.254385
# Cost after iteration 3000: 0.233864
# Cost after iteration 4000: 0.226792
# Cost after iteration 5000: 0.222644
# Cost after iteration 6000: 0.219731
# Cost after iteration 7000: 0.217504
# Cost after iteration 8000: 0.219471
# Cost after iteration 9000: 0.218612

# Print accuracy
predictions = predict(parameters, X)
print ('Accuracy: %d' % float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100) + '%')

# Accuracy: 90%

4.6 优化隐藏层大小

# This may take about 2 minutes to run

plt.figure(figsize=(16, 32))
hidden_layer_sizes = [1, 2, 3, 4, 5, 20, 50]
for i, n_h in enumerate(hidden_layer_sizes):
    plt.subplot(5, 2, i+1)
    plt.title('Hidden Layer of size %d' % n_h)
    parameters = nn_model(X, Y, n_h, num_iterations = 5000)
    plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
    predictions = predict(parameters, X)
    accuracy = float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100)
    print ("Accuracy for {} hidden units: {} %".format(n_h, accuracy))
    
# Accuracy for 1 hidden units: 67.5 %
# Accuracy for 2 hidden units: 67.25 %
# Accuracy for 3 hidden units: 90.75 %
# Accuracy for 4 hidden units: 90.5 %
# Accuracy for 5 hidden units: 91.25 %
# Accuracy for 20 hidden units: 90.0 %
# Accuracy for 50 hidden units: 90.25 %

  • 较大的模型能更好的契合训练集,直到出现过拟合现象
  • 上述例子中最好的隐藏层个数约为5个,既可以有很高的契合度,也不出现过拟合
  • 也可以引入正则化的办法来防止很大的模型过拟合
]]>