上节我们介绍了如何把一个符号表达式转化为符号计算图,这节我们介绍函数的功能,函数是Theano的一个核心设计模块,它提供一个接口,把函数计算图编译为可调用的函数对象。前面介绍了如何定义自变量x(不需要赋值),这节介绍如何编写函数方程。
先来看一下函数格式示例:
theano.function(inputs, outputs, mode=None, updates=None, givens=None, no_default_updates=False, accept_inplace=False, name=None,rebuild_strict=True, allow_input_downcast=None, profile=None, on_unused_input='raise')。
这里参数看起来很多,但一般只用到三个:inputs表示自变量;outputs表示函数的因变量(也就是函数的返回值);还有一个比较常用的是updates参数,它一般用于神经网络共享变量参数更新,通常以字典或元组列表的形式指定。此外,givens是一个字典或元组列表,记为[(var1,var2)],表示在每一次函数调用时,在符号计算图中,把符号变量var1节点替换为var2节点,该参数常用来指定训练数据集的batch大小。
下面我们看一个有多个自变量,同时又有多个因变量的函数定义例子:
import theano x, y =theano.tensor.fscalars('x', 'y') z1= x + y z2=x*y #定义x、y为自变量,z1、z2为函数返回值(因变量) f =theano.function([x,y],[z1,z2]) #返回当x=2,y=3的时候,函数f的因变量z1,z2的值 print(f(2,3))
打印结果:
[array(5.0, dtype=float32), array(6.0, dtype=float32)]
在执行theano.function()时,Theano进行了编译优化,得到一个end-to-end的函数,传入数据调用f(2,3)时,执行的是优化后保存在图结构中的模型,而不是我们写的那行z=x+y,尽管二者结果一样。这样的好处是Theano可以对函数f进行优化,提升速度;坏处是不方便开发和调试,由于实际执行的代码不是我们写的代码,所以无法设置断点进行调试,也无法直接观察执行时中间变量的值。
有了符号计算,自动计算导数就很容易了。tensor.grad()唯一需要做的就是从outputs逆向遍历到输入节点。对于每个op,它都定义了怎么根据输入计算出偏导数。使用链式法则就可以计算出梯度了。利用Theano求导时非常方便,可以直接利用函数theano.grad(),比如求s函数的导数:
以下代码实现当x=3的时候,求s函数的导数:
import theano x =theano.tensor.fscalar('x')#定义一个float类型的变量x y= 1 / (1 + theano.tensor.exp(-x))#定义变量y dx=theano.grad(y,x)#偏导数函数 f= theano.function([x],dx)#定义函数f,输入为x,输出为s函数的偏导数 print(f(3))#计算当x=3的时候,函数y的偏导数
打印结果:
0.045176658779382706
在深度学习中通常需要迭代多次,每次迭代都需要更新参数。Theano如何更新参数呢?在theano.function函数中,有一个非常重要的参数updates。updates是一个包含两个元素的列表或tuple,一般示例为updates=[old_w,new_w],当函数被调用的时候,会用new_w替换old_w,具体看下面这个例子。
import theano w= theano.shared(1)#定义一个共享变量w,其初始值为1 x=theano.tensor.iscalar('x') f=theano.function([x], w, updates=[[w, w+x]])#定义函数自变量为x,因变量为w,当函数执行完毕后,更新参数w=w+x print(f(3))#函数输出为w print(w.get_value())#这个时候可以看到w=w+x为4
打印结果:
1、4
在求梯度下降的时候,经常用到updates这个参数。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是梯度下降时,代价函数对参数w的偏导数,α是学习速率。为便于大家更全面地了解Theano函数的使用方法,下面我们通过一个逻辑回归的完整实例来说明:
import numpy as np import theano import theano.tensor as T rng = np.random # 我们为了测试,自己生成10个样本,每个样本是3维的向量,然后用于训练 N = 10 feats = 3 D = (rng.randn(N, feats).astype(np.float32), rng.randint(size=N, low=0, high=2).astype(np.float32)) # 声明自变量x、以及每个样本对应的标签y(训练标签) x = T.matrix("x") y = T.vector("y") #随机初始化参数w、b=0,为共享变量 w = theano.shared(rng.randn(feats), name="w") b = theano.shared(0., name="b") #构造代价函数 p_1 = 1 / (1 + T.exp(-T.dot(x, w) - b)) # s激活函数 xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1) # 交叉商代价函数 cost = xent.mean() + 0.01 * (w ** 2).sum()# 代价函数的平均值+L2正则项以防过拟合,其中权重衰减系数为0.01 gw, gb = T.grad(cost, [w, b]) #对总代价函数求参数的偏导数 prediction = p_1 > 0.5 # 大于0.5预测值为1,否则为0. 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)#测试阶段函数 #训练 training_steps = 1000 for i in range(training_steps): pred, err = train(D[0], D[1]) print (err.mean())#查看代价函数下降变化过程