tensorflow学习率、优化函数、正则化总结

学习率

自适应学习率

在模型的初期的时候,往往设置为较大的学习速率比较好,因为距离极值点比较远,较大的学习速率可以快速靠近极值点;而,后期,由于已经靠近极值点,模型快收敛了,此时,采用较小的学习速率较好,较大的学习速率,容易导致在真实极值点附近来回波动,就是无法抵达极值点。

在tensorflow中,提供了一个较为友好的API, tf.train.exponential_decay(learning_rate, global_step, decay_steps, decay_rate, staircase=False, name=None),其数学表达式是这样的:

先解释API中的参数的意思,第一个参数learning_rate即初始学习速率,第二个参数,是用来计算步骤的,每调用一次优化器,即自增1,第三个参数decay_steps通常设为一个常数,如数学公式中所示,与第五个参数配合使用效果较好,第五个参数staircase如果设置为True,那么指数部分就会采用整除策略,表示每decay_step,学习速率变为原来的decay_rate,至于第四个参数decay_rate表示的是学习速率的下降倍率。

1
2
3
4
5
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.1
learning_rate = tf.exponential_decay(starter_learning_rate, global_step, 100000, 0.96, staircase=True)
optimizer = tf.GradientDescent(learning_rate)
optimizer.minimize(...my loss..., global_step=global_step)

意思就是,初始的学习速率是0.1,每经过10万轮次训练后,学习速率变为原来的0.96。

不同层设置不同学习率

先输出层的参数变量(要先进行参数初始化)

1
2
3
4
5
6
variables_names = [v.name for v in tf.trainable_variables()]
values = sess.run(variables_names)
for k, v in zip(variables_names, values):
print "Variable: ", k
print "Shape: ", v.shape
print v

设置前后层的学习率

比如要对前20层的参数使用较低的学习率微调(20层大概有40种参数,20个weight,20个bia)

1
2
3
4
5
var1 = tf.trainable_variables()[0:40]
var2 = tf.trainable_variables()[40:]
train_op1 = GradientDescentOptimizer(0.00001).minimize(loss, var_list=var1)
train_op2 = GradientDescentOptimizer(0.0001).minimize(loss, var_list=var2)
train_op = tf.group(train_op1, train_op2)

另外一种高效的方法:,可以简单的理解为minimize方法包含了gradients和apply_gradients两个步骤,而第一种方法中gradients方法运行了两次

1
2
3
4
5
6
7
8
9
10
var_list1 = [variables from first 5 layers]
var_list2 = [the rest of variables]
opt1 = tf.train.GradientDescentOptimizer(0.00001)
opt2 = tf.train.GradientDescentOptimizer(0.0001)
grads = tf.gradients(loss, var_list1 + var_list2)
grads1 = grads[:len(var_list1)]
grads2 = grads[len(var_list1):]
tran_op1 = opt1.apply_gradients(zip(grads1, var_list1))
train_op2 = opt2.apply_gradients(zip(grads2, var_list2))
train_op = tf.group(train_op1, train_op2)

注意,若需要根据global_step动态调整学习率,只需要在minimizeopt1.apply_gradients中传入一次即可,若传入两次,global_step会自加两次。也就是说:损失函数优化器的minimize()或者apply_gradients()中global_step=global_steps能够提供global_step自动加一的操作。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
global_step = tf.Variable(0, trainable=False)

# Last layer has a 10 times learning rate
self.lr = tf.train.exponential_decay(self.learning_rate, global_step, self.decay_step, self.learning_rate_decay_factor, staircase=True) # 学习率随着global_step而衰减的
# Last layer has a 10 times learning rate
self.lr_last = tf.train.exponential_decay(self.learning_rate*10, global_step, self.decay_step, self.learning_rate_decay_factor, staircase=True)

var_list1 = tf.trainable_variables()[0:28]
var_list2 = tf.trainable_variables()[28:30]
opt1 = tf.train.MomentumOptimizer(learning_rate=self.lr, momentum=0.9)
opt2 = tf.train.MomentumOptimizer(learning_rate=self.lr_last, momentum=0.9)

# for BatchNorm
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
grads = tf.gradients(self.loss, var_list1 + var_list2)
grads1 = grads[:len(var_list1)]
grads2 = grads[len(var_list1):]

# 在这个地方,global_step自加1,但是只需要加一个global_step即可,否则会自加1两次
train_op1 = opt1.apply_gradients(zip(grads1, var_list1), global_step=global_step)
train_op2 = opt2.apply_gradients(zip(grads2, var_list2))
train_op = tf.group(train_op1, train_op2)

优化函数

目前TensorFlow支持11种不同的经典优化器(参考TensorFlow API tf.train文档

1
2
3
4
5
6
7
8
9
10
11
tf.train.Optimizer
tf.train.GradientDescentOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdagtadOptimizer
tf.train.AdagradDAOptimizer
tf.train.MomentumOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
tf.train.ProximalGradientDescentOptimizer
tf.train.ProximalAdagradOptimizer
tf.train.RMSProOptimizer

tf.train.GradientDescentOptimizer

这个优化器主要实现的是 梯度下降算法

1
2
3
4
5
__init__(
learning_rate,
use_locking=False,
name='GradientDescent'
)

  • learning_rate: (学习率)张量或者浮点数
  • use_locking: 为True时锁定更新
  • name: 梯度下降名称,默认为”GradientDescent”.

tf.train.MomentumOptimizer

实现 动量梯度下降算法 ,可参考简述动量Momentum梯度下降

其中, 即momentum,表示要在多大程度上保留原来的更新方向,这个值在0-1之间,在训练开始时,由于梯度可能会很大,所以初始值一般选为0.5;当梯度不那么大时,改为0.9。 是学习率,即当前batch的梯度多大程度上影响最终更新方向,跟普通的SGD含义相同。与之和不一定为1。

1
2
3
4
5
6
7
__init__(
learning_rate,
momentum,
use_locking=False,
name='Momentum',
use_nesterov=False
)

  • learning_rate: (学习率)张量或者浮点数
  • momentum: (动量)张量或者浮点数
  • use_locking: 为True时锁定更新
  • name: 梯度下降名称,默认为 “Momentum”.
  • use_nesterov: 为True时,使用Nesterov Momentum.

tf.train.AdamOptimizer

实现 Adam优化算法(Adam 这个名字来源于 adaptive moment estimation,自适应矩估计。)

可参考博客梯度优化算法Adam

1
2
3
4
5
6
7
8
__init__(
learning_rate=0.001,
beta1=0.9,
beta2=0.999,
epsilon=1e-08,
use_locking=False,
name='Adam'
)

  • learning_rate: (学习率)张量或者浮点数
  • beta1: 浮点数或者常量张量 ,表示 The exponential decay rate for the 1st moment estimates.
  • beta2: 浮点数或者常量张量 ,表示 The exponential decay rate for the 2nd moment estimates.
  • epsilon: A small constant for numerical stability. This epsilon is “epsilon hat” in the Kingma and Ba paper (in the formula just before Section 2.1), not the - epsilon in Algorithm 1 of the paper.
  • use_locking: 为True时锁定更新
  • name: 梯度下降名称,默认为 “Adam”.

正则化

简介

所谓的过拟合问题指的是当一个模型很复杂时,它可以很好的“记忆”每一个训练数据中的随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。

为了避免过拟合问题,一个非常常用的方法就是正则化。也就是在损失函数中加入刻画模型复杂程度的指标。假设用于损失函数的为$J(\theta)$,那此时不直接优化$J(\theta)$,而是优化$J(\theta)+\lambda R(w)$。其中$R(w)$刻画的是模型的复杂度,而$\lambda$表示的是模型复杂损失在总损失中的比例。这里的$\theta$表示的是一个神经网络中所有的参数,它包括边上的权重$w$和偏置项$b$。一般来说模型复杂度只由权重表示。常用的刻画模型复杂度的函数$R(w)$有两种,一种是L1正则化,另一种是L2正则化。这两种都是通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪声。这两种正则化的区别是首先L1正则化会让参数变得稀疏(指会有更多参数变为0),而L2正则化则不会(因为参数的平方后会让小的参数变得更小,大的参数变得更大,同样起到了特征选取的功能,而不会让参数变为0)。其次是L1正则化计算不可导,而L2的正则化损失函数可导。

以上是一些简单的正则花基础,下面是神经网络的搭建:

当网络复杂的时候定义网络的结构部分和计算损失函数的部分可能不在一个函数中,这样通过简单的变量这种计算损失函数就不方便了。此时可以使用Tensorflow中提供的集合,它可以在一个计算图(tf.Graph)中保存一组实体(比如张量)。以下是通过集合计算一个5层的神经网络带L2正则化的损失函数的计算过程。

tensorflow代码

生成模拟数据集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

data = []
label = []
np.random.seed(0) # 设置随机数生成时所用算法开始的整数值

# 以原点为圆心,半径为1的圆把散点划分成红蓝两部分,并加入随机噪音。
for i in range(150):
x1 = np.random.uniform(-1,1) # 随机生成下一个实数,它在 [-1,1) 范围内。
x2 = np.random.uniform(0,2)
if x1**2 + x2**2 <= 1:
data.append([np.random.normal(x1, 0.1),np.random.normal(x2,0.1)])
label.append(0)
else:
data.append([np.random.normal(x1, 0.1), np.random.normal(x2, 0.1)])
label.append(1)

data = np.hstack(data).reshape(-1,2) # 把数据转换成n行2列
label = np.hstack(label).reshape(-1, 1) # 把数据转换为n行1列
plt.scatter(data[:,0], data[:,1], c=label,cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.show()

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#2. 定义一个获取权重,并自动加入正则项到损失的函数。
def get_weight(shape, lambda1):
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32) # 生成一个变量
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda1)(var)) # add_to_collection()函数将新生成变量的L2正则化损失加入集合losses
return var # 返回生成的变量

#3. 定义神经网络。
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
sample_size = len(data)

# 每层节点的个数
layer_dimension = [2,10,5,3,1]
# 神经网络的层数
n_layers = len(layer_dimension)
# 这个变量维护前向传播时最深层的节点,开始的时候就是输入层
cur_layer = x
# 当前层的节点个数
in_dimension = layer_dimension[0]

# 循环生成网络结构
for i in range(1, n_layers):
out_dimension = layer_dimension[i] # layer_dimension[i]为下一层的节点个数
# 生成当前层中权重的变量,并将这个变量的L2正则化损失加入计算图上的集合
weight = get_weight([in_dimension, out_dimension], 0.003)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension])) # 偏置
cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias) # 使用Relu激活函数
in_dimension = layer_dimension[i] # 进入下一层之前将下一层的节点个数更新为当前节点个数

y= cur_layer

# 在定义神经网络前向传播的同时已经将所有的L2正则化损失加入了图上的集合,这里是损失函数的定义。
mse_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / sample_size # 也可以写成:tf.reduce_mean(tf.square(y_ - y`))
tf.add_to_collection('losses', mse_loss) # 将均方误差损失函数加入损失集合
# get_collection()返回一个列表,这个列表是所有这个集合中的元素,在本样例中这些元素就是损失函数的不同部分,将他们加起来就是最终的损失函数
loss = tf.add_n(tf.get_collection('losses'))

# 4. 训练不带正则项的损失函数mse_loss。
# 定义训练的目标函数mse_loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(mse_loss)
TRAINING_STEPS = 40000

with tf.Session() as sess:
tf.global_variables_initializer().run() # 初始化所有的变量
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: label})
if i % 2000 == 0:
print("After %d steps, mse_loss: %f" % (i,sess.run(mse_loss, feed_dict={x: data, y_: label})))

# 画出训练后的分割曲线
xx, yy = np.mgrid[-1.2:1.2:.01, -0.2:2.2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)

plt.scatter(data[:,0], data[:,1], c=label, cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
After 0 steps, mse_loss: 0.588501
After 2000 steps, mse_loss: 0.039796
After 4000 steps, mse_loss: 0.018524
After 6000 steps, mse_loss: 0.018494
After 8000 steps, mse_loss: 0.018374
After 10000 steps, mse_loss: 0.018358
After 12000 steps, mse_loss: 0.018356
After 14000 steps, mse_loss: 0.018355
After 16000 steps, mse_loss: 0.016440
After 18000 steps, mse_loss: 0.013988
After 20000 steps, mse_loss: 0.013142
After 22000 steps, mse_loss: 0.012886
After 24000 steps, mse_loss: 0.012700
After 26000 steps, mse_loss: 0.012550
After 28000 steps, mse_loss: 0.006441
After 30000 steps, mse_loss: 0.006439
After 32000 steps, mse_loss: 0.006438
After 34000 steps, mse_loss: 0.006438
After 36000 steps, mse_loss: 0.006445
After 38000 steps, mse_loss: 0.006438

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#5. 训练带正则项的损失函数loss。
# 定义训练的目标函数loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(loss)
TRAINING_STEPS = 40000

with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: label})
if i % 2000 == 0:
print("After %d steps, loss: %f" % (i, sess.run(loss, feed_dict={x: data, y_: label})))

# 画出训练后的分割曲线
xx, yy = np.mgrid[-1:1:.01, 0:2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)

plt.scatter(data[:,0], data[:,1], c=label,cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
After 0 steps, loss: 0.705000
After 2000 steps, loss: 0.056949
After 4000 steps, loss: 0.045995
After 6000 steps, loss: 0.041472
After 8000 steps, loss: 0.040165
After 10000 steps, loss: 0.039961
After 12000 steps, loss: 0.039916
After 14000 steps, loss: 0.039912
After 16000 steps, loss: 0.039912
After 18000 steps, loss: 0.038334
After 20000 steps, loss: 0.038128
After 22000 steps, loss: 0.037962
After 24000 steps, loss: 0.037932
After 26000 steps, loss: 0.037921
After 28000 steps, loss: 0.037918
After 30000 steps, loss: 0.037910
After 32000 steps, loss: 0.037908
After 34000 steps, loss: 0.037910
After 36000 steps, loss: 0.037907
After 38000 steps, loss: 0.037905

tf.add_to_collection

  • tf.add_to_collection(‘list_name’, element):将元素element添加到列表list_name中
  • tf.get_collection(‘list_name’):返回名称为list_name的列表
  • tf.add_n(list):将列表元素相加并返回
1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf
tf.add_to_collection('losses', tf.constant(2.2))
tf.add_to_collection('losses', tf.constant(3.))
with tf.Session() as sess:
print(sess.run(tf.get_collection('losses')))
print(sess.run(tf.add_n(tf.get_collection('losses'))

结果:
[2.2, 3.0]
5.2
注意:
使用tf.add_n对列表元素进行相加时,列表内元素类型必须一致,否则会报错。

参考

Tensorflow 自适应学习速率
TensorFlow三种常用的优化器
【tensorflow】在不同层上设置不同的学习率,fine-tuning
how-to-set-layer-wise-learning-rate-in-tensorflow
TensorFlow中global_step的简单分析
tensorflow中的正则化解决过拟合问题
tf.add_to_collection

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道