★tensorflow中优化算子详解(optimizer)

tensorfllow优化算子总结:

tensorflow的各种优化算子(optimizer)主要完成以下功能:

  • 对函数的变量求梯度(导数)
  • 根据“计算的梯度”计算“对应优化算子的变量更新量”,将“变量更新量”作用于变量,实现变量的更新
  • 自动更新变量(这一步很关键)
  • 算子自动分析函数的变量之间的关系,可进行链式法则推导(梯度)
  • 如果用户不指定函数对哪个变量求导数,算子会对所有可训练变量求导数
  • 执行一次优化算子,只进行一次梯度计算和变量更新,对变量的多次更新需要多次执行算子,可通过多次调用sess.run(optimizer)实现,实际中通常都在session中将sess.run(optimizer)写在for循环中以实现多次执行优化算子
  • 如果待拟合(训练)的数据长度大于1(多个独立数据)。则更新的过程可以分两种方式进行:1)将多个数据形成“数组”一次性喂给对象,这种情况下优化算子会自动对每个数据点计算出相应结果,然后对每个数据点的结果相加(这相当于对数据点的循环由优化算子自动进行),再一次性更新变量;2)用户增加一个for循环来逐点将每个数据显式喂给对象,算子一次执行只计算该数据,并更新一次变量。这两种情况的结果相同,但前者效率更高。

实例

下例中用“y=x1*x2”来详解tensorflow中各种优化器的计算过程,以“梯度下降法”为为例,其余优化器的计算过程类似,只有细节的差别。


★tensorflow中优化算子详解(optimizer)_第1张图片
梯度下降法.jpg

1. 先计算梯度,再更新变量(两大步分两个函数执行)

当需要对梯度进行干涉时,可用该方法。
用到的主要方法
my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义优化算子
my_gradent = my_opt.compute_gradients(y ,var_list=[x1]) #利用优化算子计算y对x1的梯度(导数)
my_train = my_opt.apply_gradients(my_gradent) #将前一步计算的梯度,应用于梯度下降公式,并更新对应变量。

import tensorflow as tf

x1 = tf.Variable(10.,trainable=True) #变量
x2 = tf.Variable(2.,trainable=True) #系数
y = tf.multiply(x1,x2) #y=x1*x2,一次函数

上述代码中定义了两个变量x1和x2(注意是tf.Variable()),同时定义函数y=x1*x2。

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个最小梯度优化器
my_gradent = my_opt.compute_gradients(y, var_list=[x1])
# do something about gradent
my_train = my_opt.apply_gradients(my_gradent)
  • my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1):定义了一个“梯度下降优化算子,且学习率α=0.1”;
  • my_gradent = my_opt.compute_gradients(y, [x1]):调用梯度下降优化算子的compute_gradients()方法计算函数y对x1的梯度(导数),返回结果是一个“由元组组成的列表”,形如[(gradent_x1,x1)],即[(,x1)],每个元组由两个元素组成,前一个是梯度,后一个是对应变量。如果将[x1]换成[x1,x2]则表示同时求y对x1和x2的梯度,输出就是my_gradent = [(,x1),(,x2)];之所以要输出计算过的梯度,是为了可以在某些情况下查看或者修改梯度,然后再执行后面的步骤
  • my_train = my_opt.apply_gradients(my_gradent):前面两步只是计算了梯度,这一步将计算x1-α×gradent_x1,并将结果赋值给x1,这就完成了一次对x1的更新,如果有多个变量,则都会更新。需要注意的是,对不同的优化算子,这一步的计算是不同的。
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    
    for i in range(2):
        print("第{}次计算:".format(i+1))

        _, gradent= sess.run([my_train,my_gradent]) #运行优化算子

        print("[(y/x1,x1)]={}".format(gradent))
        print("x1={},x2={}".format(sess.run(x1),sess.run(x2)))

#输出
第1次计算:
[(偏导数y/x1,x1)]=[(2.0, 9.8)]
x1=9.8, x2=2.0
第2次计算:
[(偏导数y/x1,x1)]=[(2.0, 9.6)]
x1=9.6, x2=2.0

上述代码中两次调用优化算子来更新变量x1,其中_, gradent= sess.run([my_train,my_gradent])是执行my_train(注意:my_gradent虽然显式写在run()内,但只运行一次,因为在计算my_train时,会计算my_gradent,这里只是为了输出中间结果。)。两次优化的过程如下图,可见变量x1确实被不断优化刷新,而x2没有变化,如果在前面步骤中将[x1]换成[x1,x2],则同样可以看到变量x2也在被优化刷新:

★tensorflow中优化算子详解(optimizer)_第2张图片
两次优化过程.jpg

2. 梯度计算和更新变量在一个函数中进行

实际上就是将前面方法的后两步合并,当不需要对中间梯度进行干预的时候用该方法。
用到的主要方法
my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义优化算子
my_train = **my_opt.minimize(y,var_list=[x1]) ** #直接得到优化结果,实际上他执行了两步操作,计算梯度(compute_gradients(y ,var_list=[x1]))和根据梯度下降公式更新变量(apply_gradients(my_gradent))。
如下例所示,比较上下两个例子,结果是相同的。:

import tensorflow as tf

x1 = tf.Variable(10.,trainable=True) #变量
x2 = tf.Variable(2.,trainable=True) #系数
y = tf.multiply(x1,x2) #y=x1*x2,一次函数

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个梯度下降优化器
my_train = my_opt.minimize(y,var_list=[x1])

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    
    for i in range(2):
        print("第{}次计算:".format(i+1))

        sess.run(my_train)

        print("x1={},x2={}".format(sess.run(x1),sess.run(x2)))
#输出:
第1次计算:
x1=9.8,x2=2.0
第2次计算:
x1=9.6,x2=2.0

需要注意的是:my_train = my_opt.minimize(y,var_list=[x1]),如果不指定var_list=[x1],那么优化算子将对所有可训练变量进行求导和结果更新

3. 对于隐函数,用链式法则求梯度(导数)

tensorflow可以对隐函数进行链式求导并更新变量,见下例:

import tensorflow as tf

x1 = tf.Variable(10.,trainable=True) #变量
x2 = tf.multiply(3.,x1)
x3 = tf.Variable(2.,trainable=True) #系数
y = tf.multiply(x2,x3) #y=x1*x2,一次函数

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个最小梯度优化器
my_gradent = my_opt.compute_gradients(y,var_list=[x1])
# do something about gradent
my_train = my_opt.apply_gradients(my_gradent)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    
    for i in range(2):
        print("第{}次计算:".format(i+1))
        _, gradent= sess.run([my_train,my_gradent])
        print("[(偏导数y/x1,x1)]={}".format(gradent))
        print("x1={},x2={},x3={}".format(sess.run(x1),sess.run(x2),sess.run(x3)))

#输出:
第1次计算:
[(偏导数y/x1,x1)]=[(6.0, 9.4)]
x1=9.399999618530273,x2=28.19999885559082,x3=2.0
第2次计算:
[(偏导数y/x1,x1)]=[(6.0, 8.799999)]
x1=8.799999237060547,x2=26.39999771118164,x3=2.0

上述程序的第一个循环过程如下,第二个过程完全相同不再赘述。


★tensorflow中优化算子详解(optimizer)_第3张图片
链式法则求导.jpg

4. 一个变量有多个维度的情况(注意不是数据有多个取值)

import tensorflow as tf

x1 = tf.Variable([10.,13]) #变量
x2 = tf.Variable([3.,9.])
y = tf.multiply(x1,x2) #y=x1*x2,一次函数

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个最小梯度优化器
my_gradent = my_opt.compute_gradients(y,var_list=[x1,x2])
# do something about gradent
my_train = my_opt.apply_gradients(my_gradent)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for i in range(2):
        print("第{}次计算:".format(i+1))
        _, gradent= sess.run([my_train,my_gradent])
        print("[(偏导数y/x1,x1)]={}".format(gradent))
        print("x1={},x2={}".format(sess.run(x1),sess.run(x2)))

#输出:
第1次计算:
[(偏导数y/x1,x1)]=[(array([3., 9.], dtype=float32), array([ 9.7, 12.1], dtype=float32)), (array([10., 13.], dtype=float32), array([2. , 7.7], dtype=float32))]
x1=[ 9.7 12.1],x2=[2.  7.7]
第2次计算:
[(偏导数y/x1,x1)]=[(array([2. , 7.7], dtype=float32), array([ 9.5 , 11.33], dtype=float32)), (array([ 9.7, 12.1], dtype=float32), array([1.03, 6.49], dtype=float32))]
x1=[ 9.5  11.33],x2=[1.03 6.49]

如上程序:
case1:
x1 = tf.Variable([10.,13])
x2 = tf.Variable([3.,9.])
x1和x2的长度相同,此时算子对每个值一对一进行梯度计算并更新变量,输出结果的长度与输入相同。
case2:
x1 = tf.Variable([10.])
x2 = tf.Variable([3.,9.])
x1只有1个值,x2是多个值,则x1的更新是所有x2的结果的和,输出长度是1;而x2是根据x1分别更新的,输出长度还是2。
case3:
x1 = tf.Variable([10.,11.,13])
x2 = tf.Variable([3.,9.])
x1和x2的长度都大于1,但不相等,这种情况将报错!

4. 如果待拟合(训练)的数据长度大于1,优化算子对每个数据点求出结果,然后对结果相加,再一次性更新变量

import tensorflow as tf

# 准备数据
X_true = np.array([1,2,3])
Y_true = X_true*0.5+1

# 定义模型y=wx+b
w = tf.Variable(0.0, name="weight")
b = tf.Variable(0.0, name="bias")
Y_pre = X_true*w + b

#定义损失函数
loss = Y_true - Y_pre

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个最小梯度优化器
my_gradent = my_opt.compute_gradients(loss,var_list=[w,b])
# do something about gradent
my_train = my_opt.apply_gradients(my_gradent)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for i in range(2):
        print("第{}次计算:".format(i+1))
        _, gradent= sess.run([my_train,my_gradent])
        print("[(偏导数y/w,w),(偏导数y/b,b)]={}".format(gradent))
        print("w={},b={}".format(sess.run(w),sess.run(b)))

#输出:
第1次计算:
[(偏导数y/w,w),(偏导数y/b,b)]=[(-6.0, 0.6), (-3.0, 0.3)]
w=0.6000000238418579,b=0.30000001192092896
第2次计算:
[(偏导数y/w,w),(偏导数y/b,b)]=[(-6.0, 1.2), (-3.0, 0.6)]
w=1.2000000476837158,b=0.6000000238418579

上述代码中:
待拟合数据为X_true = np.array([1,2,3]),Y_true = X_true×0.5+1,是三个独立点;
定义模型Y_pre = X_true×w + b,来拟合这三个点,w和b是待更新的变量
损失函数为:loss = Y_true - Y_pre;
tf.train.GradientDescentOptimizer(learning_rate=0.1),定义梯度下降优化算子,学习率为0.1;
my_opt.compute_gradients(loss,var_list=[w,b]),对w和b同时更新;
session中_, gradent= sess.run([my_train,my_gradent])运行优化算子。
从结果可以看出,在第一个epoch中:
偏导数y/w=-6.0
更新后的w=0.6
偏导数y/b=-3.0
更新后的吧b=0.3
这结果如何得来?
先来求损失函数loss对w和b的梯度(导数),

★tensorflow中优化算子详解(optimizer)_第4张图片
无标题.jpg

可见,loss对w梯度就是-X_true,而loss对b梯度就是-1。如果X_true只有一个值,这问题不大。但程序中X_true是一个由三个独立数据组成的向量,在显式计算过程中,相当于是[,,]=[-x1,-x2,-x3] =[-1,-2,-3],然后分三次独立更新w=w-α(-x1),w=w-α(-x2),w=w-α(-x3),这个过程用户通过for循环进行;而程序中X_ture是作为一个整体输入的,这种情况下,优化算子会将每个独立数据的梯度首先相加即,=(-x1)+(-x2)+(-x3)=-1-2-3=-6(梯度之和,程序中的输出结果),然后一次性更新w=w-α(-1-2-3)=0-0.1×(-6)=0.6(程序中的输出结果),这个结果跟分别对每个独立数据进行一次更新是完全相同的。对于b而言,他的梯度是-1,但程序中输出是-3,这里优化算子自动根据数据的长度3计算了三次,其实就是[,,]=[-1,-1,-1],然后对其求和即=(-1)+(-1)+(-1)=-3(程序中的输出结果),同理对b的更新也一次进行b=b-α(-3)=0-0.1(-3)=0.3(程序中的输出结果)。
可见,优化算子会自动根据输入数据的长度分别计算梯度等其他结果,再一次性将结果更新于变量。实际中,我们既可以显示将每个数据点通过循环的方式喂给对象,优化算子进行一次单一的计算并更新变量;同时我们也可以将数据一次性喂给对象,然后算子自动对每个数据进行一次计算,然后对结果求和,最后一次性用于更新变量。这两个过程的结果是一样的,但显然一次性输入数据的方式速度更快。

前面程序为了说明问题,在graph中定义了原始数据X_true = np.array([1,2,3]),Y_true = np.array([1.5,2.,2.5]),而在实际中通常是在session内运行程序时通过run()的feed_dict变量喂入数据,以便于处理不同的数据(不同大小、维度),同时对大批量数据也更有利。因此,在设计graph时,应该将输入数据的位置用tf.placeholder()代替。我们将前面程序重新设计成可喂入数据的方式,如下:

import tensorflow as tf

# 定义模型y=wx+b
X_true = tf.placeholder(tf.float32)
Y_true = tf.placeholder(tf.float32)
w = tf.Variable(0.0, name="weight")
b = tf.Variable(0.0, name="bias")
Y_pre = X_true*w + b

#定义损失函数
loss = Y_true - Y_pre

my_opt = tf.train.GradientDescentOptimizer(learning_rate=0.1) #定义一个最小梯度优化器
my_gradent = my_opt.compute_gradients(loss,var_list=[w,b])
# do something about gradent
my_train = my_opt.apply_gradients(my_gradent)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    X_input = np.array([1,2,3])
    Y_input = np.array([1.5,2.,2.5])
    for i in range(2):
        
        
        print("第{}次计算:".format(i+1))
        _, gradent= sess.run([my_train,my_gradent],feed_dict={X_true:X_input, Y_true:Y_input})
        print("[(偏导数y/w,w),(偏导数y/b,b)]={}".format(gradent))
        print("w={},b={}".format(sess.run(w),sess.run(b)))

#输出:
第1次计算:
[(偏导数y/w,w),(偏导数y/b,b)]=[(-6.0, 0.6), (-3.0, 0.3)]
w=0.6000000238418579,b=0.30000001192092896
第2次计算:
[(偏导数y/w,w),(偏导数y/b,b)]=[(-6.0, 1.2), (-3.0, 0.6)]
w=1.2000000476837158,b=0.6000000238418579

参考链接

https://www.cnblogs.com/marsggbo/p/10056057.html
https://www.cnblogs.com/weiyinfu/p/9973022.html
https://blog.csdn.net/qq_36330643/article/details/76711581

你可能感兴趣的:(★tensorflow中优化算子详解(optimizer))