前言
上一篇博客中学到了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
函数公式如下:
用
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函数是逐元素进行的,因为这些操作(除,加,指数)本身就是逐元素运算。
此外,由于
所以函数也可以这样写
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.rstatefor (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