Chainer是一个专门为高效研究和开发深度学习算法而设计的开源框架。 这篇博文会通过一些例子简要地介绍一下Chainer,同时把它与其他一些框架做比较,比如Caffe、Theano、Torch和Tensorflow。

大多数现有的深度学习框架是在模型训练之前构建计算图。 这种方法是相当简单明了的,特别是对于结构固定且分层的神经网络(比如卷积神经网络)的实现。

然而,现在的复杂神经网络(比如循环神经网络或随机神经网络)带来了新的性能改进和新的应用。虽然现有的框架可以用于实现这些复杂神经网络,但是它们有时需要一些(不优雅的)编程技巧,这可能会降低代码的开发效率和可维护性。

而Chainer的方法是独一无二的:即在训练时“实时”构建计算图。

这种方法可以让用户在每次迭代时或者对每个样本根据条件更改计算图。同时也很容易使用标准调试器和分析器来调试和重构基于Chainer的代码,因为Chainer使用纯Python和NumPy提供了一个命令式的API。 这为复杂神经网络的实现提供了更大的灵活性,同时又加快了迭代速度,提高了快速实现最新深度学习算法的能力。

以下我会介绍Chainer是如何工作的,以及用户可以从中获得什么样的好处。

Chainer 基础

Chainer 是一个基于Python的独立的深度学习框架。

不同于其它基于Python接口的框架(比如Theano和TensorFlow),Chainer通过支持兼容Numpy的数组间运算的方式,提供了声明神经网络的命令式方法。Chainer 还包括一个名为CuPy的基于GPU的数值计算库。

>>> from chainer import Variable

>>> import numpy as np

Variable 类是把numpy.ndarray数组包装在内的计算模块(numpy.ndarray存放在.data中)。

>>> x = Variable(np.asarray([[0, 2],[1, -3]]).astype(np.float32))

>>> print(x.data)

[[ 0.      2.]

[ 1.     -3.]]

用户可以直接在Variables上定义各种运算和函数(Function的实例)。

>>> y = x ** 2 – x + 1

>>> print(y.data)

[[  1.   3.]

[  1.  13.]]

因为这些新定义的Varriable类知道他们是由什么类生成的,所以Variable y跟它的父类有一样的加法运算(.creator)。

>>> print(y.creator)

<chainer.functions.math.basic_math.AddConstant at 0x7f939XXXXX>

利用这种机制,可以通过反向追踪从最终损失函数到输入的完整路径来实现反向计算。完整路径在执行正向计算的过程中存储,而不预先定义计算图。

在chainer.functions类中给出了许多数值运算和激活函数。 标准神经网络的运算在Chainer类中是通过Link类实现的,比如线性全连接层和卷积层。Link可以看做是与其相应层的学习参数的一个函数(例如权重和偏差参数)。你也可以创建一个包含许多其他Link的Link。这样的一个link容器被命名为Chain。这允许Chainer可以将神经网络建模成一个包含多个link和多个chain的层次结构。Chainer还支持最新的优化方法、序列化方法以及使用CuPy的由CUDA驱动的更快速计算。

>>> import chainer.functions as F

>>> import chainer.links as L

>>> from chainer import Chain, optimizers, serializers, cuda

>>> import cupy as cp

Chainer的设计:边运行边定义

训练一个神经网络一般需要三个步骤:(1)基于神经网络的定义来构建计算图;(2)输入训练数据并计算损失函数;(3)使用优化器迭代更新参数直到收敛。

通常,深度学习框架在步骤2之前先要完成步骤1。 我们称这种方法是“先定义再运行”。

图1. 所有图片由Shohei Hido友情提供

对于复杂神经网络,这种“先定义再运行”的方法简单直接,但并不是最佳的,因为计算图必须在训练前确定。 例如,在实现循环神经网络时,用户不得不利用特殊技巧(比如Theano中的scan()函数),这就会使代码变的难以调试和维护。

与之不同的是,Chainer使用一种“边运行边定义”的独特方法,它将第一步和第二步合并到一个步骤中去。

计算图不是在训练之前定义的,而是在训练过程中获得的。 因为正向计算直接对应于计算图并且也通过计算图进行反向传播,所以可以在每次迭代甚至对于每个样本的正向计算中对计算图做各种修改。

举一个简单的例子,让我们看看使用两层感知器进行MNIST数字分类会发生什么。

下面是Chainer中两层感知器的实现代码:

# 2-layer Multi-Layer Perceptron (MLP)

# 两层的多层感知器(MLP)

class MLP(Chain):

def __init__(self):

super(MLP, self).__init__(

l1=L.Linear(784, 100),  # From 784-dimensional input to hidden unit with 100 nodes

# 从784维输入向量到100个节点的隐藏单元

l2=L.Linear(100, 10),  # From hidden unit with 100 nodes to output unit with 10 nodes  (10 classes)

# 从100个节点的隐藏单元到10个节点的输出单元(10个类)

)

# Forward computation

# 正向计算

def __call__(self, x):

h1 = F.tanh(self.l1(x))     # Forward from x to h1 through activation with tanh function

# 用tanh激活函数,从输入x正向算出h1

y = self.l2(h1)                 # Forward from h1to y

# 从h1正向计算出y

return y

在构造函数(__init__)中,我们分别定义了从输入单元到隐藏单元,和从隐藏单元到输出单元的两个线性变换。要注意的是,这时并没有定义这些变换之间的连接,这意味着计算图没有生成,更不用说固定它了。

跟“先定义后运行”方法不同的是,它们之间的连接关系会在后面的正向计算中通过定义层之间的激活函数(F.tanh)来定义。一旦对MNIST上的小批量训练数据集(784维)完成了正向计算,就可以通过从最终节点(损失函数的输出)回溯到输入来实时获得下面的计算图(注意这里使用SoftmaxCrossEntropy做为损失函数):

这里的关键点是神经网络是直接用Python来定义的而不是领域特定语言,因此用户可以在每次迭代(正向计算)中对神经网络进行更改。

这种神经网络的命令性声明允许用户使用标准的Python语法进行网络分支计算,而不用研究任何领域特定语言(DSL)。这跟TensorFlow、 Theano使用的符号方法以及Caffe和CNTK依赖的文本DSL相比是一个优势。

此外,可以使用标准调试器和分析器来查找错误、重构代码以及调整超参数。 另一方面,尽管Torch和MXNet也允许用户使用神经网络的命令性建模,但是他们仍然使用“先定义后运行”的方法来构建计算图对象,因此调试时需要特别小心。

实现复杂的神经网络

上面只是一个简单且固定的神经网络的例子。 接下来,让我们看看如何在Chainer中实现复杂的神经网络。

循环神经网络是一种以序列为输入的神经网络,因此它经常用于自然语言处理中,例如序列到序列的翻译和问答系统。 它不仅根据来自输入序列的每个元组而且还基于其前序状态,来更新内部状态,因此它把元组序列间的依赖关系也考虑进去了。

由于循环神经网络的计算图包含前序时间和当前时间之间的有向边,所以其构造方法和反向传播方法不同于那些固定神经网络的方法(例如卷积神经网络)。在当前实践中,这种循环计算图在每次模型更新时,通过一个称为“时间截断反向传播”的方法被转化为有向无环图。

下面的示例的目标任务是预测给定句子的下一个词。训练好的神经网络可以产生语法上正确的词而不是随机的词,即使整个句子对人类来说没有什么意义。以下代码展示了包含一个循环隐藏单元的简单循环神经网络:

# Definition of simple recurrent neural network

# 定义简单的循环神经网络

class SimpleRNN(Chain):

def __init__(self, n_vocab, n_nodes):

super(SimpleRNN, self).__init__(

embed=L.EmbedID(n_vocab, n_nodes),  # word embedding

#嵌入词

x2h=L.Linear(n_nodes, n_nodes),  # the first linear layer

#第一线性层

h2h=L.Linear(n_nodes, n_nodes),  # the second linear layer

#第二线性层

h2y=L.Linear(n_nodes, n_vocab),  # the feed-forward output layer

)

#前馈输出层

self.h_internal=None # recurrent state

#循环状态

def forward_one_step(self, x, h):

x = F.tanh(self.embed(x))

if h is None: # branching in network

#网络的分支扩展

h = F.tanh(self.x2h(x))

else:

h = F.tanh(self.x2h(x) + self.h2h(h))

y = self.h2y(h)

return y, h

def __call__(self, x):

# given the current word ID, predict the next word ID.

#给定当前词的ID,预测下一个词的ID

y, h = self.forward_one_step(x, self.h_internal)

self.h_internal = h # update internal state

#更新内部状态

return y

在构造函数中以及在多层感知器中,只定义层的类型和大小。 根据输入词和当前状态参数,forward_one_step()方法返回输出词和新状态。 在正向计算(__call__)的每个步骤中,调用forward_one_step()方法,并用新状态更新隐藏循环层的状态。

使用流行的文本数据集Penn Treebank(PTB),我们训练了一个模型用来从可能的词汇中预测下一个词。 然后使用加权采样方法来让这个训练好的模型预测后续词汇。

“If you build it,” => “would a outlawed a out a tumor a colonial a”

“If you build it, they” => ” a passed a president a glad a senate a billion”

“If you build it, they will” => ” for a billing a jerome a contracting a surgical a”

“If you build it, they will come” => “a interviewed a invites a boren a illustrated a pinnacle”

这个模型已经学会了(并能产生)许多重复的“a”和一个名词对或“a”和一个形容词的词对。 这意味着“a”是最可能的词之一,并且名词或形容词倾向于跟在“a”之后。

对人类而言,结果看起来几乎相同。即使使用不同的输入,结果都是语法错误和毫无意义的。 然而,这些推测确实是基于数据集的真实句子,通过训练词的类型和词之间的关系得到的。

因为在SimpleRNN模型中缺乏表达性,造成了上述不可避免的结果。但这里的重点是:用户可以实现像SimpleRNN一样的各类循环神经网络。

作为比较,在使用了现成的叫做“长短时记忆网络”的循环神经网络模型后,生成的文本从语法上看就比较正确了。

“If you build it,” => “pension say computer ira <EOS> a week ago the japanese”

“If you buildt it, they” => “were jointly expecting but too well put the <unknown> to”

“If you build it, they will” => “see the <unknown> level that would arrive in a relevant”

“If you build it, they will come” => “to teachers without an mess <EOS> but he says store”

由于流行的RNN组件(如LSTM和门控循环单元(GRU))已经在大多数框架中被实现了,因此用户不需要关心它们的底层实现。 尽管如此,如果要对它们做重大的改动或者创建一个全新的算法和组件,Chainer跟其他框架相比就有更大的灵活性。

随机变化的神经网络

同样的,使用Chainer实现随机变化的神经网络是非常容易的。

以下是实现随机神经网络ResNet的模拟代码。 在__call__函数中,以概率p投掷一个不均匀的硬币,并根据有没有单元f来改变正向路径。这在每批训练集的每次迭代时完成,并且计算图每次都不同,它们是在计算完损失函数之后相应地用反向传播算法更新的。

# Mock code of Stochastic ResNet in Chainer

# Chainer中的随机神经网络ResNet的模拟代码

class StochasticResNet(Chain):

def __init__(self, prob, size, **kwargs):

super(StochasticResNet, self).__init__(size, **kwargs)

self.p = prob # Survival probabilities

#生存概率

def __call__ (self, h):

for i in range(self.size):

b = numpy.random.binomial(1, self.p[i])

c = self.f[i](h) + h if b == 1 else h

h = F.relu(c)

return h

总结

除了上述内容,Chainer还有许多功能可以帮助用户容易高效地实现他们自己的神经网络。

CuPy是包含在Chainer中的为GPU使用的 NumPy等效数组后端。它支持独立于CPU / GPU的编码,就像基于NumPy的运算一样。训练循环神经网络和处理数据集可以用Trainer抽象化,这使得用户不用每次都编写这样的常规代码,他们可以专注于编写创新的算法。尽管可扩展性和性能不是Chainer的主要关注点,但它通过充分利用NVIDIA的CUDA和cuDNN仍然可以与其他框架相竞争(可参考公开的基准测试结果)。

Chainer已经在许多学术论文中被使用,包括计算机视觉、语音处理、自然语言处理和机器人等领域。此外,Chainer在许多行业中越来越受欢迎,因为它有利于新产品和服务的研发。丰田汽车、松下FANUC公司广泛使用Chainer,并且已经和Preferred
Networks公司的Chainer的开发团队合作展示了一些案例。

欢迎有兴趣的读者访问Chainer网站以了解更多详情。我希望Chainer可以为更多基于深度学习的前沿研究和产品做出贡献!

This article originally appeared in English: "Complex
neural networks made easy by Chainer
".

【神经网络与深度学习】chainer边运行边定义的方法使构建深度学习网络变的灵活简单的更多相关文章

  1. Deep Learning 1_深度学习UFLDL教程:Sparse Autoencoder练习(斯坦福大学深度学习教程)

    1前言 本人写技术博客的目的,其实是感觉好多东西,很长一段时间不动就会忘记了,为了加深学习记忆以及方便以后可能忘记后能很快回忆起自己曾经学过的东西. 首先,在网上找了一些资料,看见介绍说UFLDL很不 ...

  2. GAN︱生成模型学习笔记(运行机制、NLP结合难点、应用案例、相关Paper)

    我对GAN"生成对抗网络"(Generative Adversarial Networks)的看法: 前几天在公开课听了新加坡国立大学[机器学习与视觉实验室]负责人冯佳时博士在[硬 ...

  3. Sony深度学习框架 - Neural Network Console - 教程(1)- 原来深度学习可以如此简单

    “什么情况!?居然不是黑色背景+白色文字的命令行.对,今天要介绍的是一个拥有白嫩的用户界面的深度学习框架.” 人工智能.神经网络.深度学习,这些概念近年已经涌入每个人的生活中,我想很多人早就按捺不住想 ...

  4. PyTorch如何构建深度学习模型?

    简介 每过一段时间,就会有一个深度学习库被开发,这些深度学习库往往可以改变深度学习领域的景观.Pytorch就是这样一个库. 在过去的一段时间里,我研究了Pytorch,我惊叹于它的操作简易.Pyto ...

  5. Apple的Core ML3简介——为iPhone构建深度学习模型(附代码)

    概述 Apple的Core ML 3是一个为开发人员和程序员设计的工具,帮助程序员进入人工智能生态 你可以使用Core ML 3为iPhone构建机器学习和深度学习模型 在本文中,我们将为iPhone ...

  6. 深度学习炼丹术 —— Taoye不讲码德,又水文了,居然写感知器这么简单的内容

    手撕机器学习系列文章就暂时更新到此吧,目前已经完成了支持向量机SVM.决策树.KNN.贝叶斯.线性回归.Logistic回归,其他算法还请允许Taoye在这里先赊个账,后期有机会有时间再给大家补上. ...

  7. Deep Learning 10_深度学习UFLDL教程:Convolution and Pooling_exercise(斯坦福大学深度学习教程)

    前言 理论知识:UFLDL教程和http://www.cnblogs.com/tornadomeet/archive/2013/04/09/3009830.html 实验环境:win7, matlab ...

  8. 一位ML工程师构建深度神经网络的实用技巧

    一位ML工程师构建深度神经网络的实用技巧 https://mp.weixin.qq.com/s/2gKYtona0Z6szsjaj8c9Vg 作者| Matt H/Daniel R 译者| 婉清 编辑 ...

  9. 转:浅谈深度学习(Deep Learning)的基本思想和方法

    浅谈深度学习(Deep Learning)的基本思想和方法  参考:http://blog.csdn.net/xianlingmao/article/details/8478562 深度学习(Deep ...

随机推荐

  1. C# static readonly 修饰符初始化变量

    同事问了一个问题,readonly和static啥区别? 我就写了个demo运行了下: /*** * 验证初始化次数:static只初始化一次,无论多少用户访问,在内存中只有一份 * readonly ...

  2. 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。

    问题: 该错误是因为应用程序需要jQuery,但是当前项目中并没有jQuery,或者存在jQuery但是程序不知道jQuery的存放路径. 解决方案: 一.下载jQuery,引入必要的jquery-X ...

  3. SQL索引管理器 - 用于SQL Server和Azure上的索引维护的免费GUI工具

    我作为SQL Server DBA工作了8年多,管理和优化服务器的性能.在我的空闲时间,我想为宇宙和我的同事做一些有用的事情.这就是我们最终为SQL Server和Azure 提供免费索引维护工具的方 ...

  4. git commit 统计

    git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; lo ...

  5. ES6入门系列 ----- 对象的遍历

    工作中遍历对象是家常便饭了,遍历数组的方法五花八门, 然而很多小伙伴是不是和我之前一样只会用for ...in.... 来遍历对象呢, 今天给大家介绍五种遍历对象属性的方法: 1, 最常用的for  ...

  6. vmware安装后设置网络

    CentOS安装无法ping 出现Name or service not known   [root@www ~]# ping www.baidu.comping: www.baidu.com: Na ...

  7. 记一次删除ocr与dbfile的恢复记录

    自己造成的一个案例: 场景:ocr磁盘组被我dd掉了,dbfile磁盘组也被我dd掉了.Rac起不来.之前ocr的DATA磁盘组被替换到了ABC磁盘.所幸的是有备份. 重新加载OCR磁盘 [root@ ...

  8. js 字符串换行 显示 使用 \ 转义

     js 字符串 有没有 像C# @ 那种 换行也可以显示的方法\ 

  9. Linux高级运维 第八章 部署docker容器虚拟化平台

    8.1  Docker概述 实验环境: CENTOS7.4-63 64位 Dcoker概述 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到 ...

  10. HDU 1253 胜利大逃亡 题解

    胜利大逃亡 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submi ...