之前用的都是torch,tf倒是没怎么用过,最近由于种种原因,需要学一学tensorflow,不过tf中的大多数函数类方法都无法自动进行int—>float类型强制转换,还得自行cast转换,着实麻烦。深度学习的核心所在就是梯度下降,各种优化器都是基于导数的梯度下降来的,所以自动求导更新就是至关重要的,这一节就来讲一讲tensorflow中的参数更新机制。
tf和torch中一样,各类参数都是以tensor即张量类型进行存储,由于模型中并不是所有的量都需要计算梯度,所以tf中会用tf.Variable(x)将tensor标记为可训练即可求导的变量。其实如果直接从少量参数和少量代码来看,似乎引入Variable意义不大,我们可以直接对需要求导的参数进行求导,得到梯度后,进行w=w-r*grad进行更新即可(w代表需要训练的参数,r为学习率,grad代表参数),但是神经网络模型中参数巨量,这样操作会导致参数混乱,而且需要我们自己更新参数,远没有模块化的便利,所以我们希望的模块化,以函数或者方法的形式,可以反复的复用,极大的提高便捷性,所以才会出现一系列函数和方法来辅助简便化参数自动更新。
值得注意的是,如果搜过以前的很多博客,应该知道图变量Variable没法自动数据流更新,也就是说你赋值过后,对图变量进行一些运算后,其数据流并没有运转起来(可以理解为没有进行运算),需要如下才能使得数据流运转起来进行计算:
1 | x = tf.Variable(3, name='x') |
也就是使用了交互接口,然后运行初始化,在运行y在能得到x*5后操作,显得特别臃肿,但在现在的tensorflow2.0版本中,终于取消了这种颇为愚昧的操作,直接简单的像np一样运算操作就好了,即有了自动更新机制。(网上大多数以前的博客都没更新,其实已经不需要使用run来操作了),我们可以看一下1.0版本时候的操作,那个时候tf中的数据流定义和运算是分开的(定义一个操作和执行这个操作是分开的),
在通过tf.Variable对tensor标记后,我们可以通过tf.GradientTape()对这些可训练参数进行导数计算,然后将这些计算得到的梯度信息都存在磁盘里(“Tape”),磁盘名可以自取。用法如下:
1 | with tf.GradientTape(persistent=True,watch_accessed_variables=True) as www: |
其中tf.GradientTape有两个参数,含义入下:
上述的代码中,如果a是可训练参数,那么tf.GrandientTape就会自动计算a的所有操作步骤的梯度,(通过这些所有操作步骤的梯度,链式法则就可以知道各个操作之间的导数关系)。
而 with A as B是什么意思呢?其实样式和for很类似,是控制流语句,for是不断遍历可迭代对象,将数据取出来(通过__next__ 操作的),而with是把 A中的返回送给B,不过B可以接收A的任何返回(可以是方法类等等,取决自己如何用),在with下的语句执行完毕后,还会执行魔法函数__exit__(应该是在该退出函数时候将所有结果保存在了Tape中),具体功能其实和try except是很像的,具体分析链接如下:
下面贴一个具体例子:
1 | a=tf.constant([1,2,3,4,5,6],shape=(2,3),dtype=tf.float32) |
结果如下:
1 | tf.Tensor( |
上述将tf.GradientTape的两个属性设为True,是可以连续使用gradient和自动纪录可训练参数的导数,而由于tensor类型不是可训练参数,所以需要手动指定watch来纪录其操作是的导数计算,如果tf.GradientTape的第二个参数设定为False,即使是可训练参数也需要手动进行指定。而www.gradient(A,B)就是从磁盘中提取出A关于B的导数。
本文参考链接如下:
TensorFlow学习(四):梯度带(GradientTape),优化器(Optimizer)和损失函数(losses)