第二节,基础知识之更多的例子
先声明:theano模块的内容大都是参考来源于网上,并亲手实践复现一遍,也有部分内容是自己补充
本文会列出所参考文章,如有版权问题,请联系我,我会及时删除
# -*- coding: utf-8 -*- """ Created on Fri Mar 23 16:53:19 2018 @author: zy """ ''' Theano2.1.3-基础知识之更多的例子 http://www.cnblogs.com/shouhuxianjian/p/4594517.html ''' import theano.tensor as T import theano ''' 现在,是时候开始系统的熟悉theano的基础对象和操作了,可以通过浏览库的部分来详细的了解 Basic Tensor Functionality. 随着这个教程的深入,你可以逐渐的让自己熟悉库的其他相关的部分和文档入口页面的其他相关的主题了。 Basic Tensor Functionality:http://deeplearning.net/software/theano/library/tensor/basic.html#libdoc-basic-tensor ''' ''' 一、Logistic函数 ''' ''' 这是一个简单的例子:虽然回比两个数值相加要难一些。假设你想要设计一个逻辑曲线,首先得到一个如下的式子 s(x) = 1/(1+exp(-x)) 你需要在doubles矩阵上逐元素(elementwise)的计算这个函数,也就是说你想要在矩阵的每个独立的元素上都是用这个函数 代码如下 ''' import numpy as np x = T.dmatrix('x') s = 1/(1+T.exp(-x)) logistic = theano.function([x],s) inpt = np.array([[0,1],[-1,-2]],dtype='float64') print(logistic(inpt)) #[[ 0.5 0.73105858],[ 0.26894142 0.11920292]] ''' 需要逐元素计算是因为它的操作:除法、加法、指数和减法,都是逐元素的操作。在该情况下也是: s(x) = 1/(1+exp(-x)) = (1+tanh(x/2))/2 我们可以验证从这个可代替的式子上得到的结果是一样的 ''' s2 = (1 + T.tanh(x/2))/2 logistic2 = theano.function([x],s2) print(logistic2(inpt)) #[[ 0.5 0.73105858],[ 0.26894142 0.11920292]] ''' 二、在同一时间对多个操作进行运算 ''' ''' Theano支持函数有着多于一个的输出。例如,我们可以在同一时刻计算两个矩阵a和b 之间的逐元素(elementwise )的差, 绝对值的差,平方值的差: ''' a,b =T.dmatrices('a','b') diff = a-b abs_diff = T.abs_(a-b) diff_squared = T.square(a-b) f = theano.function([a,b],[diff,abs_diff,diff_squared]) #note:dmatrices 生成提供的名字一样数量的输出。这是一个用来分配符号变量的快捷方式,在本教程中会经常用到。 #当我们使用函数f 时,它返回三个变量(输出的时候会为了更好的可读性而被重新格式): r1 = f([[1,1],[1,1]],[[0,1],[2,3]]) print(r1) #[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 = theano.function([x,Param(y,default=1)],z) print(f(33)) #34.0 print(f(33,2)) #35.0 ''' 使用的 Param 参数允许你指定你函数的参数有着更详细的值。这里我们通过创建一个Param实例来将y设置其默认值为1。 有着默认值的输入必须在没有默认值的输入的后面(和python的函数一样的顺序)可以对多个输入进行设置默认值。 这些参数可以通过位置或者名称来进行设定,就像标准的python中一样: ''' x, y, w = T.dscalars('x', 'y', 'w') z = (x + y) * w f = theano.function([x, Param(y, default=1), Param(w, default=2, name='w_by_name')], z) #对符号变量w名字重命名 print(f(33)) #68.0 print(f(33,2)) #70.0 print(f(33,0,1)) #33.0 print(f(33,w_by_name=1,y=0)) #33.0 ''' note:Param 不知道作为参数传入的局部变量y和w的名称。这些符号变量对象都有name属性(和上面例子一样通过dscalars来设置) ,这些是我们构建的函数中的关键参数的名称。这就是 Param(y, default=1)中的工作机制。在Param(w, default=2, name='w_by_name') 的情况下 ,我们用在这个函数中使用过的名字来覆盖符号变量的名字属性。 你可以看看库中的 Function 来更详细的了解。 function:http://deeplearning.net/software/theano/library/compile/function.html#usingfunction ''' ''' 四、使用共享变量 ''' ''' 同样的也可以让函数有一个内部状态。例如,我们想要在开始就设置一个累加器,先初始化为0。那么,在每次的函数调用, 该状态就 会被函数的参数递增的。首先定义一个accumulator 函数。然后将参数增加到内部状态上,然后返回增加之前的状态值。 ''' state = theano.shared(0) inc = T.iscalar('inc') accumulator = theano.function([inc],state,updates=[(state,state+inc)]) ''' 该代码引入了一些新的概念。 shared 函数构建所谓的 shared variables。这些都是混合符号和非符号变量,他们的值可以在多个 函数中共享,就像是由dmatrices(...)返回的对象一样,不过他们同样有着一个内部值,这个值是通过这个在所有函数中使用的符号 变量定义的。被称作共享变量是因为它的值在许多函数之间共享的。该值可以被 .get_value() 和 .set_value() 方法所访问和修改。 该代码中另一个新事物就是function. updates的参数 updates 必须被以(shared-variable, new expression)这种对形式的列表 所赋值。它同样可以是一个字典,其中的键是共享变量而值是新表达式。。不管怎么说,它表示“不论什么时候运行,它会将.value的 每个共享变量替换成对应的表达式的结果” 。也就是说,我们的累加器会用状态state的和以及递增数来替换状态state的值。 shared variables:http://deeplearning.net/software/theano/library/compile/shared.html#libdoc-compile-shared ''' print(state.get_value()) #0 print(accumulator(1)) #0 print(state.get_value()) #1 state.set_value(-1) print(accumulator(3)) #-1 print(state.get_value()) #2 #正如上面说的,你可以定义超过一个函数来使用相同的共享变量。这些函数都能够更新这个值。 decrementor = theano.function([inc], state, updates=[(state, state-inc)]) print(decrementor(2)) #2 print(state.get_value()) #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 = theano.function([inc, foo], fn_of_state, givens=[(state, foo)]) #使用foo变量替代state共享变量的值,并不更改state的值 print(skip_shared(1, 3)) # we're using 3 for the state, not state.value 3*2+1 = 7 print(state.get_value()) # old state still there, but we didn't use it 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. RandomStreams:http://deeplearning.net/software/theano/library/tensor/shared_randomstreams.html#libdoc-tensor-shared-randomstreams RandomStreamsBase:http://deeplearning.net/software/theano/library/tensor/raw_random.html#libdoc-tensor-raw-random ''' from theano.tensor.shared_randomstreams import RandomStreams srng = RandomStreams(seed = 234) #创建一个随机生成器 rv_u = srng.uniform((2,2)) #表示一个从均匀分布中提取2*2的矩阵的随机流 rv_n = srng.normal((2,2)) #表示一个从标准正太分布中提取的2*2矩阵的一个随机流。 f= theano.function([],rv_u) g = theano.function([], rv_n, no_default_updates=True) #Not updating rv_n.rng nearly_zeros = theano.function([],rv_u+rv_u - 2*rv_u) #现在让我们来使用这些对象。如果我们调用 f(),我们就得到了均匀随机数。 随机数生成器的内部状态是自动进行更新的,所以 #我们在每个时间上得到的是不同的随机数: f_val0 = f() f_val1 = f() #different numbers from f_val0 print(f_val0) #[[ 0.12672381 0.97091597] # [ 0.13989098 0.88754827]] print(f_val1) #[[ 0.31971416 0.47584376] # [ 0.24129163 0.42046082]] #当我们增加额外的参数 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! print(g_val0) #[[ 0.37328446 -0.65746671] # [-0.36302373 -0.97484624]] print(g_val1) #[[ 0.37328446 -0.65746671] # [-0.36302373 -0.97484624]] print(nearly_zeros()) #[[ 0. 0.] # [ 0. 0.]] #一个重要的备注是随机变量在任何单一函数执行中最多被提取一次。所以,即使 rv_u 随机变量在输出表达式中出现了三次, #nearly_zero函数可以保证返回的值可以逼近0 (除了舍入导致的错误): #nearly_zeros = function([], rv_u + rv_u - 2 * rv_u) ''' 随机变量可以被独立或集体的被传入种子。你可以只对随机变量通过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 ''' 在函数之间共享流 ''' #和通常的共享变量一样,用于随机变量的随机数生成器都是函数中常见的。所以我们的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 ''' 在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 from theano.sandbox.rng_mrg import MRG_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] ''' ''' 其他随机分布 other distributions implemented. http://deeplearning.net/software/theano/library/tensor/raw_random.html#libdoc-tensor-raw-random ''' ''' 其他实现 这里有2个基于 CURAND 和 MRG31k3p.的两个实现。该RandomStream 只工作在CPU上, 而 MRG31k3p 既能在CPU 上也能在GPU上。CURAND 只工作在 GPU上。 http://deeplearning.net/software/theano/library/sandbox/cuda/op.html#module-theano.sandbox.cuda.rng_curand http://deeplearning.net/software/theano/library/sandbox/rng_mrg.html#libdoc-rng-mrg ''' ''' 六、逻辑回归案例 ''' rng = np.random N=400 #实例个数 feats = 784 #特征个数 #随机生成训练集数据 #numpy.random.randn(d0, d1, …, dn)是从标准正态分布中返回一个或多个样本值。 #numpy.random.rand(d0, d1, …, dn)的随机样本位于[0, 1)中。 #rng.randint(size=N,low=0,high=2) 生成0或者1的类别 D = [np.asarray(rng.randn(N,feats),dtype='float32') ,np.asarray(rng.randint(size=N,low=0,high=2),dtype='float32')] #迭代次数 training_steps = 10000 #声明theano符号变量 x = T.matrix('x') #float32 y = T.vector('y') #float32 w = theano.shared(np.asarray(rng.randn(feats),dtype='float32'),name = 'w') #float32 print(w.type) b = theano.shared(np.float32(0.0),name = 'b') #float32 类型最好一致 print(b.type) print('Initial model') #print(w.get_value(),b.get_value()) #定义代价函数,预测函数表达式 p_1 = 1/(1 + T.exp(-T.dot(x,w)-b)) #目标为1的概率 prediction = p_1 > 0.5 #预测阈值 >0.5 预测类别为1 否则为0 xent = -y*T.log(p_1) - (1-y)*T.log(1-p_1) #交叉熵代价函数 维数为:实例个数*1 cost = xent.mean() + 0.01*(w**2).sum() #加入正则项 求平均 gw,gb = T.grad(cost,[w,b]) #定义编译函数 #训练函数 train = theano.function( inputs = [x,y], outputs = [prediction,xent], updates=[(w,w-0.1*gw),(b,b-0.1*gb)]) #预测函数 predict = theano.function([x],prediction) #准确率 acc = T.mean(T.eq(T.cast(y,'bool') ,prediction)) accuracy = theano.function([x,y],acc) 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])) print('accuracy:',accuracy(D[0],D[1]))