本文是“七小时Theano入门”系列的第二篇文章,第一篇请看这里:七小时 Theano 入门(Day 1)

本文大部分参考资料来源于(其中介绍为网络评论):

  1. theano_exercise:适合从零开始的学习,每个exercise带你认识theano的一个模块
  2. Theano-Tutorials:都说是见过的最好的theano tutorial
  3. 官方tutorial
  4. 官方deep learning tutorial
  5. ipython:一系列数据科学的ipy notebook

第二天里,我们会尝试用Theano从头实现几个常见的神经网络模型,并且对其中用到的几个函数进行重点学习。这次的难度会比之前稍有增加,预计总共用三个小时,坚持就是胜利喔!

0、回顾Logistic回归

在Logistic回归中,我们使用交互熵(cross entropy)作为损失函数,其实现方法如下:

xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1)    # cross entropy loss

其实,Theano中有定义好的交互熵等常用函数的实现,我们可以通过加载nnet来使用他们,如下面代码:

xent = T.nnet.binary_crossentropy(p_1, y)

接下来我们便来学习一下nnet有什么方便的函数可用。

1、学习nnet

nnet中实现了很多常用的损失函数和激活函数,如softmax和ReLU等。虽然自己实现这些函数并不困难,但是调用nnet的这些函数可以使程序更加简洁和健壮,也能增加程序的可读性。推荐学习官方网站上nnet的介绍。需要注意的是,由于是调用定义好的函数,其接口对输入变量自然有一定的限制(如tensor的shape等),实现的时候注意一下就是了。

2、Check point:简单神经网络

好了,现有的知识已经足够我们写一个简单的神经网络模型了。在这里我们将实现一个具有一个输入层、一个隐含层和一个输出层的两层神经网络,激活函数用经典的sigmoid函数,输出层为softmax层,网络用SGD进行训练。完整代码在这里,修改于参考资料[2]。

为了展示训练的神经网络,在这个例子里我用了scikit-learn生成分类数据并且可视化。最终训练得到的神经网络可视化结果如下图:

net

3、学习生成随机数

由于在Theano里所有东西都是以符号表达式的形式表示的,因此加入随机变量的方法与一般numpy中的有所不同。特别是刚接触时,会发现不同的教程中出现了五花八门的随机数定义方法,看得人眼花缭乱。这里我们主要来学习“MGR随机数生成器”。就本人的经验看来,这是人们普遍比较常用的随机数生成方法,其原因或许是该函数可以同时用于CPU和GPU中。推荐学习官方文档中的这个页面。其实用起来十分简单:

from theano.sandbox.rng_mrg import MRG_RandomStreams as RandomStreams
...
srng = RandomStreams()
a = srn.normal(shape)

4、Check point:复杂神经网络

在这个小节中,我们会运用刚学到的生成随机数的知识来实现神经网络中的“Dropout 层”,并且用它来构建一个更加复杂的神经网络。具体来说,我们要实现一个三层神经网络,其中两个隐藏层的激活函数使用ReLU函数,并且在之后接dropout层,输出层同样为softmax层。网络的训练使用RMSprop算法。具体代码见这里,修改于参考资料[2]。

5、学习scan函数

在这个小节里,我们将学习这个系列专题涉及的最后一个函数——scan。scan函数提供了在Theano图结构内部实现循环的机制,简单来想象可以把它看做for循环的替代品。利用scan函数我们可以实现简单的循环操作(如两个向量按位相加)、可以实现对于序列的计算操作(如斐波拉契数列),scan函数也是实现Recurrent Neural Network的重要组成部分。

总体而言,scan函数比之前学习的函数稍微复杂一些,因为其中设计到循环以及对历史数据的处理。推荐先看看基于ipy notebook的这个教程,由浅入深地对scan函数的使用进行了讲解。主要掌握以下几个参数的用法:outputs_info, sequences, non_sequences。其中介绍“Reusing output”的部分中dictionary的两个变量:taps 和initial 的用法值得注意。

理解了上述教程之后对scan的用法感兴趣的同学,可以继续看官方教程这里的例子。这些例子都十分有趣,会让你找到刷题的快感。

6、Check point: 递归神经网络

今天的最后,我们来用scan函数实现递归神经网络(RNN)。老实说,RNN的实现比想象中要复杂不少(主要是序列的处理和loss的定义问题,Day3里会详细讲解),为了提供一个简洁但比较贴近实际的例子,本人花了不少时间调试这个部分的代码。这小节的内容参考了:参考文献[5]中的rnn例子、网上热门博客及其代码

Kapathy博客的启发,我们用Theano实现一个基于RNN的字符级语言模型(Character-Level Language Model)。具体代码见这里,例外该文件夹中还包含了所用到的“莎士比亚文本”数据集。其中需要注意的是网络中循环的实现方法,即用scan函数来将序列展开成所需的长度,具体代码如下:

def step(x_w_xh, previous, w_hh):
    hidden = T.tanh(T.dot(previous, w_hh) + x_w_xh)
    output = T.nnet.softmax(T.dot(hidden, w_o) + b_o)
    return [hidden, output]


def model(X, init_state, w_xh, w_hh, b_h, w_o, b_o):
    x_w_xh = T.dot(X, w_xh) + b_h
    [hidden, output], _ = theano.scan( step, sequences=[x_w_xh], outputs_info=[init_state, None], non_sequences=[w_hh])

我建议读者认真敲一次代码,力求理解其中每一行的用处。关于其中RNN的实现细节,我们会在Day3里详细讨论。

小结

今天内容就到这里啦,大家是否觉得从头实现各种神经网络非常有意思呢?我们Day3继续!