市面上的深度学习框架不断发布,包括:ConvNet,Caffe(图像),Torch以及Tensorflow,其中最引入瞩目的莫过于来自Google的Tensorflow,在这篇文章中,将对提供Python API的Tensorflow以及Theano作简要介绍。

1.背景

    Theano最初是被设计成一套符号表达系统,Tensorflow类似于Theano,也属于符号编程框架 (微软开源的CNTK也是如此)。

    选择合适的层数,每层的神经元数量,激活函数,损失函数,正则化的参数,然后使用validation数据来判定这次训练的效果。

2.简介

    下面将以Theano为例,通过官方给出的Tutorial介绍其基本框架:

2.1 Theano

2.1.1 代数基础

    在Theano中所有的数据对象都必须是Theano类型(Theano Type)的,这里在代数运算中引入标量(scalar)的概念,将每一个标量数据通过theano.tensor定义成与python存储数据类型(int,float,double等)相一致的Theano类型(iscalar,fscalar,dscalar等),且这种对应是一对一(即Python存在的数据类型,Theano中均有实现)。

    除了标量(scalar)之外,Theano中还包含vector,matrix,在定义这两种类型的Theano变量时,使用相应的dvector(vector contains double elements),dmatrix即可。

theano.function方法,第一个参数是函数的输入参数列表,第二个参数是返回值的列表,且当列表元素数目为1时,可以省去[].

import numpy
import theano.tensor as T
from theano import function
from theano import In
x,y = T.dscalar('x','y')
z = x + y
f = function([x,y],z)
#using In to set default value: set default value 5 for y, taking place of y by In(y, default=5) 
#shared value, to define a value that can be 

#Shared变量
from theano import shared
state = shared(0)
inc = T.iscalar('inc')
accumulator = function([inc], state, updates=[(state, state+inc)])
#UPDATES: An expression which indicates updates to the Value after each function call, also means that expressions for new SharedVariable values
#UPDATES: (iterable over pairs (shared_variable, new_expression). List, tuple or dict.) 

这里要提一下,在代数运算系统中的一些tips:

  1. 缺省值
        首先就是给function中的变量定义缺省值,通过使用In(y,default=5)代替原来的变量y.
  2. Shared变量
        当你需要在不同的函数中持续使用某一个变量时,一般会定义一个shared变量,通过function中的updates参数来改变shared变量的值,在Theano中使用updates的主要目的是提高运算速度(在GPU上),Theano对其进行了特别的优化。同时,还可以通过使用function的givens=[(var1,var2)]参数(var1的值被var2替换),来使用state而不改变state.value.
        引入shared变量的原因:在进行大量求导运算时(GPU擅长),需要把gradients数据从GPU传输到CPU,通过shared变量,可以省去这个步骤(计算(GPU)与更新(CPU)可以放在一起进行),一般在训练网络的过程中会将weights定义为shared variable(that persist in the graph between calls).

  3. 随机数
>>> from theano.tensor.shared_randomstreams import RandomStreams
>>> from theano import function
>>> srng = RandomStreams(seed=234) #where rng means that random number generator
>>> rv_u = srng.uniform((2,2))
>>> rv_n = srng.normal((2,2))
>>> f = function([], rv_u)
>>> f()
array([[ 0.70574274,  0.80222456],
       [ 0.25976164,  0.18285402]])
>>> g = function([], rv_n, no_default_updates=True)    #Not updating rv_n.rng
>>> g()
array([[ 0.37328447, -0.65746672],
       [-0.36302373, -0.97484625]])
>>> nearly_zeros = function([], rv_u + rv_u - 2 * rv_u)

    在给随机数设置seed时,一种方法是直接给RandomStreams设置(如上例所示,或srng.seed(234)),另一种方法是对RandomStreams的某一随机分布变量设置seed,比如对rv_u设置seed,需要通过以下的方法,即使用rng.ser_value和rng.get_value():

>>> rng_val = rv_u.rng.get_value(borrow=True)   # Get the rng for rv_u
>>> rng_val.seed(89234)                         # seeds the generator
>>> rv_u.rng.set_value(rng_val, borrow=True)    # Assign back seeded rng

    这里要注意到RandomStream仅工作在CPU环境下(MRG31k3p工作在CPU和GPU下,CURAND仅工作在GPU下)。

2.1.2 导数

    在Theano中,使用T.grad(y,x)来计算表达式y(代价函数)关于x(自变量)的导数。
    此外可以使用theano.gradient.jacobian()来计算雅可比矩阵(多元函数的一阶偏导数矩阵),使用theano.gradient.hessian()来计算海森矩阵(二阶偏导数矩阵)。

2.1.3 条件

    由于Theano是一种类似于函数式编程的语言,在使用中,Python的if语句只在编译时起作用,编译时会将if判断后的结果进行编译,所以这里需要单独引入条件函数IfElse和Switch。
    theano.ifelse(cond, ift, iff)有三个参数,一个boolean类型的表达式和两个返回变量,tensor.switch(cond, ift, iff)则为一个tensor和两个返回变量。

from theano import tensor as T
from theano.ifelse import ifelse
import theano, time, numpy

a,b = T.scalars('a', 'b')
x,y = T.matrices('x', 'y')

z_switch = T.switch(T.lt(a, b), T.mean(x), T.mean(y))
z_lazy = ifelse(T.lt(a, b), T.mean(x), T.mean(y))

f_switch = theano.function([a, b, x, y], z_switch,
                           mode=theano.Mode(linker='vm'))
f_lazyifelse = theano.function([a, b, x, y], z_lazy,
                               mode=theano.Mode(linker='vm'))

2.1.4 scan

    设计scan的目的是为了实现循环(递归)地影响一个对象,其主要有四个参数,fn为每次迭代要进行的操作,是一个函数;sequences(y,p)为迭代序列(for(i in range(10)) 中的range(10)),其中y为要迭代的次数(如果sequences=None,要通过n_steps参数来指定迭代次数);outputs_info描述使用到前几次迭代结果作为lambda的参数,non_sequences是非序列化的输入,通常用来存储固定的指定值。(good for RNNs)

results, updates = theano.scan(fn=lambda y, p, x_tm2, x_tm1, A: y+p+x_tm2+x_tm1+A,
                    sequences=[Y, P[::-1]],
                    outputs_info=[dict(initial=X, taps=[-2, -1])],
                    non_sequences=A)

2.1.5 稀疏

    Theano专门对稀疏矩阵制定了处理策略,稀疏矩阵中,只有非0元素才会被存储。这里,稀疏矩阵的存储格式有两种csc和csr,当矩阵的行比较多时,建议使用csc:基于矩阵列的存储格式;当行数较少时,则应选择csr:基于矩阵列的存储格式,两者的区别仅在于存储数据的位置。

from theano import sparse
x = sparse.csc_matrix(name='x', dtype='float32')  
y = sparse.dense_from_sparse(x) #将稀疏矩阵转换为密集矩阵
data, indices, indptr, shape = sparse.csm_properties(x)  #使用sparse.csm_properties来返回一个tensor变量的元组,来表示稀疏矩阵的内部特征。
#data 属性是一个一维的 ndarray ,它包含稀疏矩阵所有的非0元素。
#indices 和indptr 属性是用来存储稀疏矩阵中数据的位置的。
#shape 属性,准确的说是和密集矩阵的shape属性一样的。如果从前三个属性上没法推断,那么它可以在稀疏矩阵创建的时候显式指定 。

2.1.6 Other key words:

theano.tensor.ones_like(x)
Parameters: x – tensor that has same shape as output
Returns a tensor filled with 1s that has same shape as x.

theano.tensor.dot(x,y)
tensor 变量的点乘操作

theano.tensor.nnet.softmax(x) or theano.tensor.nnet.sigmoid(x) or theano.tensor.nnet.relu()
Returns the standard sigmoid nonlinearity applied to x
Returns the softmax function of x:
Compute the element-wise rectified linear activation function(激活函数为Rectified Linear Units,Relu易于求导便于反向传播求误差梯度,由于会使一部分神经元输出为0,从而造成网络的稀疏性,减少了参数的相互依存关系).

此外使用Lasagne、Keras等建立在theano基础上的library可以作为theano的部分或全部替代,此外这些包支持Pretrained model。

2.2 Tensorflow

    Tensorflow和theano非常相似,它的一些亮点包括:可视化(TensorBoard),multi-GPU and multi-node training.

placeholder来代替tensor Variables来代替shared variables

Reference

  1. LISA lab Theano 0.7 documentation
  2. 赵孽 如何评价 Theano?
  3. 松尾丰 人工智能狂潮
  4. Kenneth Tran Evaluation of Deep Learning Toolkits
  5. Stanford/vision CS231n Winter 2016: Convolutional Neural Networks for Visual Recognition(Lecture 12)