Theano 基础

In [1]:

  1. %matplotlib inline
  2. import numpy as np
  3. import matplotlib.pyplot as plt

首先导入 theano 及其 tensor 子模块(tensor,张量):

In [2]:

  1. import theano
  2.  
  3. # 一般都把 `tensor` 子模块导入并命名为 T
  4. import theano.tensor as T
  1. Using gpu device 1: Tesla K10.G2.8GB (CNMeM is disabled)

tensor 模块包含很多我们常用的数学操作,所以为了方便,将其命名为 T。

符号计算

theano 中,所有的算法都是用符号计算的,所以某种程度上,用 theano 写算法更像是写数学(之前在04.06 积分一节中接触过用 sympy 定义的符号变量)。

T.scalar 来定义一个符号标量:

In [3]:

  1. foo = T.scalar('x')

In [4]:

  1. print foo
  1. x

支持符号计算:

In [5]:

  1. bar = foo ** 2
  2.  
  3. print bar
  1. Elemwise{pow,no_inplace}.0

这里定义 foo 是 $x$,bar 就是变量 $x^2$,但显示出来的却是看不懂的东西。

为了更好的显示 bar,我们使用 theano.pp() 函数(pretty print)来显示:

In [6]:

  1. print theano.pp(bar)
  1. (x ** TensorConstant{2})

查看类型:

In [7]:

  1. print type(foo)
  2. print foo.type
  1. <class 'theano.tensor.var.TensorVariable'>
  2. TensorType(float32, scalar)

theano 函数

有了符号变量,自然可以用符号变量来定义函数,theano.function() 函数用来生成符号函数:

  1. theano.function(input, output)

其中 input 对应的是作为参数的符号变量组成的列表,output 对应的是输出,输出可以是一个,也可以是多个符号变量组成的列表。

例如,我们用刚才生成的 foobar 来定义函数:

In [8]:

  1. square = theano.function([foo], bar)

使用 square 函数:

In [9]:

  1. print square(3)
  1. 9.0

也可以使用 bareval 方法,将 x 替换为想要的值,eval 接受一个字典作为参数,键值对表示符号变量及其对应的值:

In [10]:

  1. print bar.eval({foo: 3})
  1. 9.0

theano.tensor

除了 T.scalar() 标量之外,Theano 中还有很多符号变量类型,这些都包含在 tensor(张量)子模块中,而且 tensor 中也有很多函数对它们进行操作。

  • T.scalar(name=None, dtype=config.floatX)
    • 标量,shape - ()
  • T.vector(name=None, dtype=config.floatX)
    • 向量,shape - (?,)
  • T.matrix(name=None, dtype=config.floatX)
    • 矩阵,shape - (?,?)
  • T.row(name=None, dtype=config.floatX)
    • 行向量,shape - (1,?)
  • T.col(name=None, dtype=config.floatX)
    • 列向量,shape - (?,1)
  • T.tensor3(name=None, dtype=config.floatX)
    • 3 维张量,shape - (?,?,?)
  • T.tensor4(name=None, dtype=config.floatX)
    • 4 维张量,shape - (?,?,?,?) shape 中为 1 的维度支持 broadcast 机制。

除了直接指定符号变量的类型(默认 floatX),还可以直接在每类前面加上一个字母来定义不同的类型:

  • b int8
  • w int16
  • i int32
  • l int64
  • d float64
  • f float32
  • c complex64
  • z complex128 例如 T.dvector() 表示的就是一个 float64 型的向量。

除此之外,还可以用它们的复数形式一次定义多个符号变量:

  1. x,y,z = T.vectors('x','y','z')
  2. x,y,z = T.vectors(3)

In [11]:

  1. A = T.matrix('A')
  2. x = T.vector('x')
  3. b = T.vector('b')

T.dot() 表示矩阵乘法:y = Ax+b

In [12]:

  1. y = T.dot(A, x) + b

T.sum() 表示进行求和:z = \sum{i,j} A{ij}^2

In [13]:

  1. z = T.sum(A**2)

来定义一个线性函数,以 $A,x,b$ 为参数,以 $y,z$ 为输出:

In [14]:

  1. linear_mix = theano.function([A, x, b],
  2. [y, z])

使用这个函数:

A = \begin{bmatrix}1 & 2 & 3 \4 & 5 & 6\end{bmatrix}, x = \begin{bmatrix}1 \ 2 \ 3\end{bmatrix},b = \begin{bmatrix}4 \ 5\end{bmatrix}

In [15]:

  1. print linear_mix(np.array([[1, 2, 3],
  2. [4, 5, 6]], dtype=theano.config.floatX), #A
  3. np.array([1, 2, 3], dtype=theano.config.floatX), #x
  4. np.array([4, 5], dtype=theano.config.floatX)) #b
  1. [array([ 18., 37.], dtype=float32), array(91.0, dtype=float32)]

这里 dtype=theano.config.floatX 是为了与 theano 设置的浮点数精度保持一致,默认是 float64,但是在 GPU 上一般使用 float32 会更高效一些。

我们还可以像定义普通函数一样,给 theano 函数提供默认值,需要使用 theano.Param 类:

In [16]:

  1. linear_mix_default = theano.function([A, x, theano.Param(b, default=np.zeros(2, dtype=theano.config.floatX))],
  2. [y, z])

计算默认参数下的结果:

In [17]:

  1. print linear_mix_default(np.array([[1, 2, 3],
  2. [4, 5, 6]], dtype=theano.config.floatX), #A
  3. np.array([1, 2, 3], dtype=theano.config.floatX)) #x
  1. [array([ 14., 32.], dtype=float32), array(91.0, dtype=float32)]

计算刚才的结果:

In [18]:

  1. print linear_mix_default(np.array([[1, 2, 3],
  2. [4, 5, 6]], dtype=theano.config.floatX), #A
  3. np.array([1, 2, 3], dtype=theano.config.floatX), #x
  4. np.array([4, 5], dtype=theano.config.floatX)) #b
  1. [array([ 18., 37.], dtype=float32), array(91.0, dtype=float32)]

共享的变量

Theano 中可以定义共享的变量,它们可以在多个函数中被共享,共享变量类似于普通函数定义时候使用的全局变量,同时加上了 global 的属性以便在函数中修改这个全局变量的值。

In [19]:

  1. shared_var = theano.shared(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=theano.config.floatX))
  2.  
  3. print shared_var.type
  1. CudaNdarrayType(float32, matrix)

可以通过 set_value 方法改变它的值:

In [20]:

  1. shared_var.set_value(np.array([[3.0, 4], [2, 1]], dtype=theano.config.floatX))

通过 get_value() 方法返回它的值:

In [21]:

  1. print shared_var.get_value()
  1. [[ 3. 4.]
  2. [ 2. 1.]]

共享变量进行运算:

In [22]:

  1. shared_square = shared_var ** 2
  2.  
  3. f = theano.function([], shared_square)
  4.  
  5. print f()
  1. [[ 9. 16.]
  2. [ 4. 1.]]

这里函数不需要参数,因为共享变量隐式地被认为是一个参数。

得到的结果会随这个共享变量的变化而变化:

In [23]:

  1. shared_var.set_value(np.array([[1.0, 2], [3, 4]], dtype=theano.config.floatX))
  2.  
  3. print f()
  1. [[ 1. 4.]
  2. [ 9. 16.]]

一个共享变量的值可以用 updates 关键词在 theano 函数中被更新:

In [24]:

  1. subtract = T.matrix('subtract')
  2.  
  3. f_update = theano.function([subtract], shared_var, updates={shared_var: shared_var - subtract})

这个函数先返回当前的值,然后将当前值更新为原来的值减去参数:

In [25]:

  1. print 'before update:'
  2. print shared_var.get_value()
  3.  
  4. print 'the return value:'
  5. print f_update(np.array([[1.0, 1], [1, 1]], dtype=theano.config.floatX))
  6.  
  7. print 'after update:'
  8. print shared_var.get_value()
  1. before update:
  2. [[ 1. 2.]
  3. [ 3. 4.]]
  4. the return value:
  5. <CudaNdarray object at 0x7f7f3c16a6f0>
  6. after update:
  7. [[ 0. 1.]
  8. [ 2. 3.]]

导数

Theano 的一大好处在于它对符号变量计算导数的能力。

我们用 T.grad() 来计算导数,之前我们定义了 foobar (分别是 $x$ 和 $x^2$),我们来计算 bar 关于 foo 的导数(应该是 $2x$):

In [26]:

  1. bar_grad = T.grad(bar, foo) # 表示 bar (x^2) 关于 foo (x) 的导数
  2.  
  3. print bar_grad.eval({foo: 10})
  1. 20.0

再如,对之前的 $y = Ax + b$ 求 $y$ 关于 $x$ 的雅可比矩阵(应当是 $A$):

In [27]:

  1. y_J = theano.gradient.jacobian(y, x)
  2.  
  3. print y_J.eval({A: np.array([[9.0, 8, 7], [4, 5, 6]], dtype=theano.config.floatX), #A
  4. x: np.array([1.0, 2, 3], dtype=theano.config.floatX), #x
  5. b: np.array([4.0, 5], dtype=theano.config.floatX)}) #b
  1. [[ 9. 8. 7.]
  2. [ 4. 5. 6.]]

theano.gradient.jacobian 用来计算雅可比矩阵,而 theano.gradient.hessian 可以用来计算 Hessian 矩阵。

R-op 和 L-op

Rop 用来计算 $\frac{\partial f}{\partial x}v$,Lop 用来计算 $v\frac{\partial f}{\partial x}$:

一个是雅可比矩阵与列向量的乘积,另一个是行向量与雅可比矩阵的乘积。

In [28]:

  1. W = T.dmatrix('W')
  2. V = T.dmatrix('V')
  3. x = T.dvector('x')
  4. y = T.dot(x, W)
  5. JV = T.Rop(y, W, V)
  6. f = theano.function([W, V, x], JV)
  7.  
  8. print f([[1, 1], [1, 1]], [[2, 2], [2, 2]], [0,1])
  1. [ 2. 2.]

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/09-theano/09.02-theano-basics.ipynb