来自:http://deeplearning.net/software/theano/tutorial/examples.html
More Examples
现在,是时候开始系统的熟悉theano的基础对象和操作了,可以通过浏览库的部分来详细的了解 Basic Tensor Functionality. 随着这个教程的深入,你可以逐渐的让自己熟悉库的其他相关的部分和文档入口页面的其他相关的主题了。
一、Logistic 函数
这是一个简单的例子,虽然会比两个数值相加要难一些。假设你想要计算一个逻辑曲线,首先得到一个如下的式子:
上图是一个逻辑函数,x轴表示x值,y轴表示s(x)值。
你需要在doubles矩阵上逐元素( elementwise )的计算这个函数,也就是说你是想要在矩阵的每个独立的元素上都使用该函数。代码如下:
- >>> x = T.dmatrix('x')
- >>> s = 1 / (1 + T.exp(-x))
- >>> logistic = function([x], s)
- >>> logistic([[0, 1], [-1, -2]])
- array([[ 0.5 , 0.73105858],
- [ 0.26894142, 0.11920292]])
需要逐元素计算是因为它的操作:除法、加法、指数和减法,都是逐元素的操作。在该情况下也是:
我们可以验证从这个可代替的式子上得到的结果是一样的:
- >>> s2 = (1 + T.tanh(x / 2)) / 2
- >>> logistic2 = function([x], s2)
- >>> logistic2([[0, 1], [-1, -2]])
- array([[ 0.5 , 0.73105858],
- [ 0.26894142, 0.11920292]])
二、在同一时间对多个操作进行计算
Theano支持函数有着多于一个的输出。例如,我们可以在同一时刻计算两个矩阵a和b 之间的逐元素(elementwise )的差,绝对值的差,平方值的差:
- >>> a, b = T.dmatrices('a', 'b')
- >>> diff = a - b
- >>> abs_diff = abs(diff)
- >>> diff_squared = diff**2
- >>> f = function([a, b], [diff, abs_diff, diff_squared])
note:dmatrices 生成提供的名字一样数量的输出。这是一个用来分配符号变量的快捷方式,在本教程中会经常用到。
当我们使用函数f 时,它返回三个变量(输出的时候会为了更好的可读性而被重新格式):
- >>> f([[1, 1], [1, 1]], [[0, 1], [2, 3]])
- [array([[ 1., 0.],
- [-1., -2.]]),
- array([[ 1., 0.],
- [ 1., 2.]]),
- array([[ 1., 0.],
- [ 1., 4.]])]
三、对参数设置默认值
假设你想要定义一个相加两个数的函数,如果你定义完之后,在调用的时候,只提供了一个参数,那么另一个输入可以假设默认为1,可以如下所示:
- >>> from theano import Param
- >>> x, y = T.dscalars('x', 'y')
- >>> z = x + y
- >>> f = function([x, Param(y, default=1)], z)
- >>> f(33)
- array(34.0)
- >>> f(33, 2)
- array(35.0)
使用的 Param 参数允许你指定你函数的参数有着更详细的值。这里我们通过创建一个Param实例来将y设置其默认值为1。
有着默认值的输入必须在没有默认值的输入的后面(和python的函数一样的顺序)可以对多个输入进行设置莫仍只。这些蚕食可以通过位置或者名称来进行设定,就像标准的python中一样:
- >>> x, y, w = T.dscalars('x', 'y', 'w')
- >>> z = (x + y) * w
- >>> f = function([x, Param(y, default=1), Param(w, default=2, name='w_by_name')], z)
- >>> f(33)
- array(68.0)
- >>> f(33, 2)
- array(70.0)
- >>> f(33, 0, 1)
- array(33.0)
- >>> f(33, w_by_name=1)
- array(34.0)
- >>> f(33, w_by_name=1, y=0)
- array(33.0)
note:Param 不知道作为参数传入的局部变量y 和w 的名称。这些符号变量对象都有name属性(和上面例子一样通过dscalars来设置),这些是我们构建的函数中的关键参数的名称。这就是 Param(y, default=1)中的工作机制。在Param(w, default=2, name='w_by_name')的情况下 ,我们用在这个函数中使用过的名字来覆盖符号变量的名字属性。
你可以看看库中的 Function 来更详细的了解。
四、使用共享变量
同样的也可以让函数有一个内部状态。例如,我们想要在开始就设置一个累加器,先初始化为0。那么,在每次的函数调用, 该状态就会被函数的参数递增的。首先定义一个accumulator 函数。然后将参数增加到内部状态上,然后返回增加之前的状态值。
该代码引入了一些新的概念。 shared 函数构建所谓的 shared variables。这些都是混合符号和非符号变量,他们的值可以在多个函数*享,就像是由dmatrices(...)返回的对象一样,不过他们同样有着一个内部值,这个值是通过这个在所有函数中使用的符号变量定义的。被称作共享变量是因为它的值在许多函数之间共享的。该值可以被 .get_value() 和 .set_value() 方法所访问和修改。
该代码中另一个新事物就是function. updates的参数 updates 必须被以(shared-variable, new expression)这种对形式的列表所赋值。它同样可以是一个字典,其中的键是共享变量而值是新表达式。。不管怎么说,它表示“不论什么时候运行,它会将.value的每个共享变量替换成对应的表达式的结果” 。也就是说,我们的累加器会用状态state的和以及递增数来替换状态state的值。
- >>> state.get_value()
- array(0)
- >>> accumulator(1)
- array(0)
- >>> state.get_value()
- array(1)
- >>> accumulator(300)
- array(1)
- >>> state.get_value()
- array(301)
可以使用 .set_value() 方法来重置状态state:
- >>> state.set_value(-1)
- >>> accumulator(3)
- array(-1)
- >>> state.get_value()
- array(2)
正如上面说的,你可以定义超过一个函数来使用相同的共享变量。这些函数都能够更新这个值。
- >>> decrementor = function([inc], state, updates=[(state, state-inc)])
- >>> decrementor(2)
- array(2)
- >>> state.get_value()
- array(0)
你也许会惊讶为什么这个更新机制会存在。你总可以通过返回一个新的表达式来得到一个相似的结果,然后在NumPy里面使用它们。该更新机制是一个语法上的方便,不过在这里主要是因为效率问题。对共享变量的更新有时候可以使用in-place算法更快的完成(了例如: low-rank矩阵更新).。同样的,theano有着更多有关在哪和如何来分配共享权重的函数,这些都是在需要使用在 GPU上很重要的组成部分.
有时候你想要使用一个共享变量来表示一些公式,却不想要使用它们的值。在这种情况下,你可以使用function函数的givens的参数,这个用来代替这种情况下graph中的特定的节点。
- >>> fn_of_state = state * 2 + inc
- >>> # The type of foo must match the shared variable we are replacing
- >>> # with the ``givens``
- >>> foo = T.scalar(dtype=state.dtype)
- >>> skip_shared = function([inc, foo], fn_of_state,
- givens=[(state, foo)])
- >>> skip_shared(1, 3) # we're using 3 for the state, not state.value
- array(7)
- >>> state.get_value() # old state still there, but we didn't use it
- array(0)
givens 参数可以用来代替任何符号变量,不只是共享变量。你还可以用来代替常量、表达式。不过要注意,不要让由givens替换的表达式之间有着相互依赖关系,替换的顺序是没法保证的,所以替换之后是有可能以任意顺序来执行的。
在实际中,有关使用givens的一个好的方法就是替换公式的任何部分的时候使用的是不同的表达式,只不过该表达式有着相同shape和dtype的张量而已。
note:Theano 共享变量的广播模式默认情况下对于每个维度来说都是False。共享变量的size可以随着时间变化,所以我们没法使用shape来找到可广播的模式。如果你想要一个不同的模式,只要将它像参数一样传递 theano.shared(..., broadcastable=(True, False))。
五、使用随机数
因为在theano中,你首先会将任何事情进行符号化,然后编译这个表达式来得到函数,然后使用伪随机数不是和Numpy中一样简单的,当然也不会太复杂。
将随机放入theano的计算中就是将随机变量放到你的graph中。theano将会对每个这样的变量分配一个 NumPy RandomStream 对象 (一个随机生成器) ,然后必要的时候提取出来。我们称这类随机数序列为a random stream. 随机流的核心也是共享变量,所以对共享变量的观察在这里也是一样的。theano的随机对象的定义和实现在 RandomStreams 更低的版本,也就是其父类RandomStreamsBase.
5.1 一个简单的例子
这里是一个简短的例子,构建代码为:
- from theano.tensor.shared_randomstreams import RandomStreams
- from theano import function
- srng = RandomStreams(seed=234)
- rv_u = srng.uniform((2,2))
- rv_n = srng.normal((2,2))
- f = function([], rv_u)
- g = function([], rv_n, no_default_updates=True) #Not updating rv_n.rng
- nearly_zeros = function([], rv_u + rv_u - 2 * rv_u)
这里 ‘rv_u’ 表示一个从均匀分布中提取的2*2的矩阵的随机流。同样的, ‘rv_n’ 表示一个从正太分布中提取的2*2矩阵的一个随机流。该分布实现和定义都在 RandomStreams 中,其父类为 raw_random. 他们只在CPU上工作。 Other Implementations 上面有GPU版本的。
现在让我们来使用这些对象。如果我们调用 f(),我们就得到了均匀随机数。 随机数生成器的内部状态是自动进行更新的,所以我们在每个时间上得到的是不同的随机数:
- >>> f_val0 = f()
- >>> f_val1 = f() #different numbers from f_val0
当我们增加额外的参数 no_default_updates=True 到 function (as in g),那么随机数生成器状态不会受到返回函数调用的影响。所以,例如,调用g 函数多次,而返回的是同样的数值:
- >>> g_val0 = g() # different numbers from f_val0 and f_val1
- >>> g_val1 = g() # same numbers as g_val0!
一个重要的备注是随机变量在任何单一函数执行中最多被提取一次。所以,即使 rv_u 随机变量在输出表达式中出现了三次,nearly_zero函数可以保证返回的值可以逼近0 (除了舍入导致的错误):
- >>> nearly_zeros = function([], rv_u + rv_u - 2 * rv_u)
5.2 Seeding Streams
随机变量可以被独立或集体的被传入种子。你可以只对随机变量通过seeding或者使用.rng.set_value()的.rng 属性来指定传入种子:
- >>> 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
你同样可以对所有的随机变量通过RandomStreams对象的seed方法来分配种子。该种子将会被用来传递给一个临时随机数生成器,然后对每个随机变量生成种子。
- >>> srng.seed(902340) # seeds rv_u and rv_n with different seeds each
5.3 在函数之间共享流
和通常的共享变量一样,用于随机变量的随机数生成器都是函数中常见的。所以我们的nearly_zeros 函数将会使用上面介绍的函数 f 来更新生成器的状态。例如:
- >>> state_after_v0 = rv_u.rng.get_value().get_state()
- >>> nearly_zeros() # this affects rv_u's generator
- >>> v1 = f()
- >>> rng = rv_u.rng.get_value(borrow=True)
- >>> rng.set_state(state_after_v0)
- >>> rv_u.rng.set_value(rng, borrow=True)
- >>> v2 = f() # v2 != v1
- >>> v3 = f() # v3 == v1
5.4 在theano graphs之间复制随机状态
在一些使用的情况中,用户有可能想要将所有随机数生成器的“state”从一个给定的theano graph (例如, g1,和下面编译的函数f1一起的) 到另一个 graph (例如 g2,和函数f2一起的).。如果你想要从之前模型的pickled版本的参数中初始化模型的state,那么这个问题就会出现。 theano.tensor.shared_randomstreams.RandomStreams 和 theano.sandbox.rng_mrg.MRG_RandomStreams 可以通过复制state_updates参数的元素来获得
每次从一个RandomStreams对象中得到一个随机变量,一个元组就会被添入到 state_updates 列表中。第一个元素就是一个共享变量,用来表示与这个具体的变量相关联的随机数生成器的状态,第二个元素用来表示对应于随机数生成过程(即RandomFunction{uniform}.0)的theano graph。
一个关于如何“random states”可以从一个theano function迁移到另一个的例子如下:
- import theano
- import numpy
- import theano.tensor as T
- from theano.sandbox.rng_mrg import MRG_RandomStreams
- from theano.tensor.shared_randomstreams import RandomStreams
- class Graph():
- def __init__(self, seed=123):
- self.rng = RandomStreams(seed)
- self.y = self.rng.uniform(size=(1,))
- g1 = Graph(seed=123)
- f1 = theano.function([], g1.y)
- g2 = Graph(seed=987)
- f2 = theano.function([], g2.y)
- print 'By default, the two functions are out of sync.'
- print 'f1() returns ', f1()
- print 'f2() returns ', f2()
- def copy_random_state(g1, g2):
- if isinstance(g1.rng, MRG_RandomStreams):
- g2.rng.rstate = g1.rng.rstate
- for (su1, su2) in zip(g1.rng.state_updates, g2.rng.state_updates):
- su2[0].set_value(su1[0].get_value())
- print 'We now copy the state of the theano random number generators.'
- copy_random_state(g1, g2)
- print 'f1() returns ', f1()
- print 'f2() returns ', f2()
会有如下的输出:
- # By default, the two functions are out of sync.
- f1() returns [ 0.72803009]
- f2() returns [ 0.55056769]
- # We now copy the state of the theano random number generators.
- f1() returns [ 0.59044123]
- f2() returns [ 0.59044123]
5.5 其他随机分布
5.7 一个真实的例子:逻辑回归
前面的内容会在下面这个更实际的例子中见到,而且该例子会被多次使用。
- import numpy
- import theano
- import theano.tensor as T
- rng = numpy.random
- N = 400
- feats = 784
- D = (rng.randn(N, feats), rng.randint(size=N, low=0, high=2))
- training_steps = 10000
- # Declare Theano symbolic variables声明theano符号变量
- x = T.matrix("x")
- y = T.vector("y")
- w = theano.shared(rng.randn(feats), name="w")
- b = theano.shared(0., name="b")
- print "Initial model:"
- print w.get_value(), b.get_value()
- # Construct Theano expression graph构建theano表达式的图
- p_1 = 1 / (1 + T.exp(-T.dot(x, w) - b)) # Probability that target = 1
- prediction = p_1 > 0.5 # The prediction thresholded
- xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1) # Cross-entropy loss function
- cost = xent.mean() + 0.01 * (w ** 2).sum()# The cost to minimize
- gw, gb = T.grad(cost, [w, b]) # Compute the gradient of the cost
- # (we shall return to this in a
- # following section of this tutorial)
- # Compile编译
- train = theano.function(
- inputs=[x,y],
- outputs=[prediction, xent],
- updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb)))
- predict = theano.function(inputs=[x], outputs=prediction)
- # Train训练
- for i in range(training_steps):
- pred, err = train(D[0], D[1])
- print "Final model:"
- print w.get_value(), b.get_value()
- print "target values for D:", D[1]
- print "prediction on D:", predict(D[0])
参考资料:
[1]官网:http://deeplearning.net/software/theano/tutorial/examples.html