【theano-windows】学习笔记二——theano中的函数和共享参数

xiaoxiao2021-02-28  100

前言

上一篇博客中学到了theano中的变量类型,也就是dscalar、dvector之类的, 然后还有一个theano.function和eval函数, 将我们所定义的操作转换成theano可执行的函数,类似于def, 还有就是简单的线性代数运算操作。

在神经网络(NN)中, 我们声明了权重、偏置,剩下的就是激活函数,权重和偏置更新之类的东西了,而权重我们一直存储在一个变量(比如w)中,不会说每次梯度更新都声明一个变量(w1,w2,w3…..), 因而这个变量是共享的. 这个学习笔记主要就是解决这两个问题(激活函数,权重和偏置共享),当然还会稍微看看函数拷贝以及随机数生成的操作。

国际惯例,参考网址

官方tutorial

程序运行预先导入的模块有:

#-*- coding:utf-8 -*- #http://www.deeplearning.net/software/theano/tutorial/examples.html import theano import theano.tensor as T import numpy as np import matplotlib.pyplot as plt

对率函数

logistic函数又称为对数几率函数(周志华《机器学习》58页),是一种重要的sigmoid函数, 注意与对数函数 ln() 区分开,logistic函数公式如下:

s(x)=11+ex 用 theano可执行的函数来实现就是

x=T.dvector('x') y=1/(1+T.exp(-x)) logistic=theano.function([x],y) x_tick=np.arange(-6,6,0.1) y_tick=logistic(x_tick)

画出来瞅瞅

#把坐标轴移到中间 ax=plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.spines['bottom'].set_position(('data',0)) ax.spines['left'].set_position(('data',0)) #画图 plt.plot(x_tick,y_tick,'r-') plt.show()

需要了解的是,logistic函数是逐元素进行的,因为这些操作(除,加,指数)本身就是逐元素运算。

此外,由于

s(x)=11+ex=1+tanh(x/2)2 所以函数也可以这样写

y2=(1+T.tanh(x/2))/2 logistic2=theano.function([x],y2) y2_tick=logistic2(x_tick) plt.plot(x_tick,y_tick,'r-') plt.show()

同时进行多种运算

上面的theano.function都只定义了一种运算操作,然而theano还支持多输出, 比如我们可以同时计算两个矩阵的差, 差的绝对值, 差的平方

#多个操作同时进行 a,b=T.dmatrices('a','b') diff=a-b diff_abs=abs(diff) diff_square=diff**2 f=theano.function([a,b],[diff,diff_abs,diff_square]) x1=np.array([[1,1],[1,1]]) y1=np.array([[0,1],[2,3]]) c,d,e=f(x1,y1) print 'a-b=',c print 'abs(a-b)=',d print 'square(a-b)=',e

可以发现theano.function在执行多输出时, 按照第二个参数的顺序返回结果,所以我们可以用相同数量的变量接收结果

设置函数的默认参数值

跟C++差不多, 就是在function启用定义操作的时候, 对输入参数(也就是第一个参数列表)的某(几)个预先赋值, 需要使用theano.compile.io模块中的In函数

In(variable, name=None, value=None, update=None, mutable=False, strict=False, autoname=True, implicit=None)

value: 变量名,类似于上面用T.dvector声明的变量x

name: 默认就是变量名,也可将此变量设置为其它名字,后面就可以用这个名字访问或者赋值了, 其实就是方便了你不用严格按照function中参数的输入顺序也能使用变量名来进行输入

value: 变量的默认值, 当update=None的时候, 这个值就是参数默认值,反之updata是一个操作, 而非None的时候, 改变为默认值将会被’stick around’(停留), 主要还是因为一个update或者用户的显式操作

update: 每次调用update指向的操作函数,都会将返回值赋给value

mutable: 允许编译函数改变被用于默认值得python对象,感觉可以类比一下c++的mutable,戳这里. 默认是False, 也就是不允许编译过的函数改变value所指向的python对象(这个以后实战操作看看具体用法)

strict: 检查你的输入是否为正确的类型,如果不是,就自动转换为合适的类型

后面几个用到再看,毕竟现在是新手, 好像很少用到后面几个参数

#import theano.compile.io as TIO#这个也可以 from theano import In x,y,w=T.dscalars('x','y','w') z=(x+y)*w #为y设置默认值1,为w设置默认值2和名字w_by_name # f = theano.function([x, TIO.In(y, value=1), TIO.In(w, value=2, name='w_by_name')], z) f = theano.function([x, In(y, value=1), In(w, value=2, name='w_by_name')], z) #使用y和z的默认值计算 f(33) #使用z的默认参数 f(33,2) #利用变量名不按照function中参数顺序赋值 f(33,w_by_name=1,y=0)

使用共享变量

例子: 做一个加法器, 每次调用函数就会加上所输入的值

思路: 先定义输入变量(每次需要加的数), 将每次返回的加和以后的数设置为共享变量, 利用update更新当前加和值,在取结果的时候要注意使用get_value()函数

from theano import shared state=shared(0.0)#和的初始值为0,并且为共享变量 inc=T.dscalar('inc')#每次的增量加法减法共享这个和 accumulator=theano.function([inc],state,updates=[(state,state+inc)])#加法 decrementor=theano.function([inc],state,updates=[(state,state-inc)])#减法 #使用get_value()方法获取初始值 print state.get_value() #加法 accumulator(3.0) #不要直接返回或者直接输出,要用get_value()取值 print state.get_value() #减法 decrementor(2.0) #不要直接返回或者直接输出,要用get_value()取值 print state.get_value()

【注1】一定要注意state和所加数字inc变量类型一定要相同,如果把state=shared(0.0)改成state=shared(0),就会报错typeError: ('An update must have the same type as the original shared variable (shared_var=<TensorType(int32, scalar)>, shared_var.type=TensorType(int32, scalar), update_val=Elemwise{add,no_inplace}.0, update_val.type=TensorType(float64, scalar)).', 'If the difference is related to the broadcast pattern, you can call the tensor.unbroadcast(var, axis_to_unbroadcast[, ...]) function to remove broadcastable dimensions.')

【注2】我刚开始以为theano.function中第二个参数对应的是一个操作, 因为前面的theano.function([x],z)中的z一般就是定义的一个操作, 比如z=x**2之类的, 但是这种想法可能是错的. 因为在这节的实例中state仅仅是一个共享变量而已, 其操作都在update中, 所以目前可以把第二个参数当成返回的结果值,不管此结果值是一个变量(如共享变量)还是一个表达式结果(比如z=x+y)

这里接触了function中的另一个参数update, 此参数必须提供以对的形式提供(共享变量, 新表达式), 它也可以是一个字典, 字典的keys就是共享变量, 字典的values就是新的表达式. 无论哪种方式,只要函数运行,共享变量的值就会被表达式的结果替代. 注意,一定要用get_value()的方式获取结果值, 否则获取到的就是未进行表达式运算的值

有时候我们在表达式中不想更新某个变量,就可以使用givens(variable1,variable2)用variable2暂时替换variable1,这时候就不会更新variable1了

state.set_value(0.0) foo=T.scalar(dtype=state.dtype)#替换变量必须与被替换变量类型相同 temp_express=state+inc skip_shared=theano.function([inc,foo],temp_express,givens=[(state,foo)]) skip_shared(1,3) print state.get_value() #0.0

这个givens参数可以使用任何符号变量替代,不仅仅是共享变量,也可以使用常量,表达式替换。但是不允许givens的表达式替换具有相互依赖关系.

关于theano.function更多的关于输入输出的介绍,可以戳这里的官网教程

拷贝函数

使用copy()方法能够将定义过的function的功能拷贝过来, 至于里面的输入参数, 可以使用swap={old_variable:new_variable}进行替换. 比如我们想拷贝一下上面的那个加法函数, 原始的state共享变量用new_state替换, 就可以这样写:

new_state=theano.shared(0.0)#定义新的共享变量 new_accmulator=accumulator.copy(swap={state:new_state})#拷贝函数并替换共享变量 new_accmulator(100) print new_state.get_value() #100.0

我们也可以在拷贝的时候把updates删掉

new_state1=theano.shared(1.0) #移除拷贝函数的updates操作 null_accmulator=accumulator.copy(delete_updates=True,swap={state:new_state1}) null_accmulator(20) print new_state1.get_value() #1.0

随机数

生成随机数

只需要记住随机数的实现存在于RandomStreames模块中, function是用于执行功能的, 再考虑到numpy的随机数使用预先得有一个随机数种子seed, 那么可以很清晰得到theano中随机数的生成方法

#导入模块 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)#加入了no_default_updates每次生成的随机数都一样 nearly=theano.function([],rv_u+rv_u-2*rv_u) #分别利用f和g生成随机数 f_val0=f() f_val1=f()#与f_val0不一样 g_val0=g() g_val1=g()#与g_val0一样 nearly() ''' array([[ 0., 0.], [ 0., 0.]], dtype=float32) '''

no_default_updates表示每次生成的随机数都一样。需要注意的一点是,在任意一个函数执行的时候, 一个随机变量最多获取一次, 也就是说虽然rv_u在输出表达式中出现了三次, nearly函数仍能够保证返回接近0的值(除去化整误差)

随机数种子

单独为每个变量设置随机数种子, 比如为rv_u设置随机数种子需要三步, 先获取其种子, 随后赋予其一个种子值, 最后将此种子值设置入此rv_u的种子流中即可

rng_val=rv_u.rng.get_value(borrow=True) rng_val.seed(89234) rv_u.rng.set_value(rng_val,borrow=True)

也可以为设置所有变量的随机数种子一样

srng.seed(902340)#这个srng是一个RandomStreams变量

当然这样还不足够, 因为我们说过,所有的更新都必须放入到function中走一遍才能生效,紧接着的下个例子能体现出这一点

函数间共享随机流

下面的几行代码实现的就是:先第一次使用随机数生成器生成一组数据, 然后将第一次使用的随机数状态赋给下一次生成随机数的生成器,所以第一次执行function f()只是将随机数种子重置为第一次的种子,而它生成随机变量时,此重置效果并未执行,所以虽然接连两次执行f(), 却看到了v2!=v1而v3=v1的结果

state_after_v0=rv_u.rng.get_value().get_state()#获取随机数生成器状态 nearly()#执行函数影响当前rv_u的随机数生成器 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()#调用一次函数,使重新赋值的随机数生成器种子生效 v3=f()#利用重新赋值的种子生成随机数 print v2==v1 print v3==v1 ''' [[False False] [False False]] [[ True True] [ True True]] '''

上面生成的随机数分布分别是均匀分布和正太分布,当然theano中还提供了其他分布,戳这里

图结构间的随机流状态复制

关于什么是图结构,姑且把它先当做一个类吧, 然后这部分想要实现的就是类实例之间的随机种子复制

我们需要使用的模块有

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) a=f1() b=f2() print a #[ 0.72803009] print b #[ 0.55056769]

接着定义一个拷贝随机数生成器种子的函数并生效, 然后再分别生成两个实例的随机数, 看看是否相等

#共享g1和g2的随机数种子 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()) #赋值 copy_random_state(g1,g2) c=f1() d=f2() print c==d #True

【注】这一部分消化不了没关系,毕竟这么多乱糟糟的随机数定义和生成器种子拷贝方法,还设计到变量或者图之间的各种函数,熟悉一点点,再后面看到它知道就行了,以后哪个用的多就记哪个

总结

这一章节首先注意的是共享变量的定义, 然后是function比较重要的两个参数updates()和givens(),它俩都是后一个值取代前一个值,只不过前者用于共享变量的更新,后者是不想更新共享变量时候用其它东西替代共享变量进行运算。还有一个就是copy()函数用于拷贝定义的函数, swap替换变量和`delete_updates取消操作的使用要记住。还有就是随机数生成以及变量或者图间随机数生成器的种子共享,也就是说如何让两次调用随机数生成器得到相同的随机数

从NN的设计来看,我们大概已经知道了如何定义权重、偏置,以及它们的随机初始化赋值, 如何进行它们之间的函数操作(加减乘除, 函数激活), 如何让之前计算的权重、偏置共享到下一次计算中,也就是说用新的权重覆盖旧的权重, 剩下的就是损失函数的梯度计算问题了。 代码地址:链接: https://pan.baidu.com/s/1pLMBQYZ 密码: f6f8

转载请注明原文地址: https://www.6miu.com/read-43683.html

最新回复(0)