一般神经网络的训练过程可分为两个阶段:第一阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比,得出两者之间的距离;第二个阶段,通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。
1. 基于梯度的优化
基于梯度的优化就是优化一个函数的最终取值,假设 x 是函数的输入参数,J(x) 是需要优化的函数,则基于梯度的优化就是改变 x 的值得到最小化或最大化的 J(x) (通常是最小化J(x),最大化可经由最小化算法 最小化 -J(x) 来实现)。
在具体的深度神经网络的设计中, J(x) 通常指网络在训练数据集上的损失函数,x 泛指神经网络的参数。使用梯度下降优化网络的大概思路就是寻找一个参数 x ,使得损失函数 J(x) 的值最小。 学习率 (Learning Rate,就相当于对输入所作的一个微小的变化)来定义每次参数更新的幅度。
学习率可以值观的理解为每次参数移动的幅度,通过计算函数在 处的梯度以及设定的学习率就能得到以下的参数更新公式:
如果损失函数非常复杂,比如含有许多不是最有的全局极小点,或者存在多个局部极小点等都将使我们的优化变得十分困难。因此,我们通常寻找使 J(x) 非常小的点,将这一点作为一个近似的最小点。对于一个深度学习任务,即使找到的点不是最优的点,但只要他们显著低于损失函数的绝大部分值,我们就能够接受这样的解。同时,梯度下降算法的计算时间太长,在实际训练时,参与训练的数据往往很多,并且损失函数 J(x) 是在所有训练数据上的损失和,这样计算速度太慢。
2. 随机梯度下降
加快每一轮参数更新的速度可以使用随机梯度下降算法(SGD),其优点在于并不会对全部训练数据上的损失函数进行优化,而是在每一轮迭代中随机选择某一个或多个训练数据上的损失函数进行梯度下降优化。
随机梯度下降算法也存在某些问题,即在某些训练数据上损失函数更小并不代表在全部训练数据上损失函数更小。为保持平衡梯度下降算法和随机梯度下降算法的性能,在实际应用中一般会每次计算一小部分训练数据的总损失函数,这一小部分数据就是一个batch。使用每次在一个batch上优化神经网络参数的方法并不会比每次优化单个数据慢太多,此外还可以大大减少收敛所需要的迭代次数,最终结果更加接近梯度下降的效果。
3. 反向传播
如果说梯度下降算法优化了单个参数的取值,那么反向出阿波算法则给出了一种高效地所有参数上使用梯度下降算法地方式。反向传播(Back Propagation) 算法 则允许来自损失函数的信息通过网络向后流动,以便计算梯度。
反向传播算法的实现需要递归地使用微积分中的链式法则,如果将输入、输出都为向量的函数的所有偏导数汇总为一个矩阵,那么得到的矩阵可以被称为 Jacobian矩阵。向量 x 的梯度可以通过 (Jocabian矩阵)和梯度
相乘而得到。
TensorFlow提供了一些优化器,这些优化器会帮助我们实现反向传播的过程。
4 自适应学习率算法
在使用基本的梯度下降优化算法时,会遇到一个困难——要优化的参数对于目标函数的依赖各不相同。对于不同参数,如果学习率太小,则梯度大的参数会有一个很慢的收敛速度;如果学习率太大,则已经优化得差不多得参数可能会出现不稳定的情况。
早期,Delta-ba-Delta算法实现了在训练时自适应模型的参数各自的学习率。该算法的思路大体上可以描述为:如果损失与某一指定参数的偏导符号相同,那么学习率应该增加;如果损失与该参数的偏导的符号不同,那么学习率应该减小。受到 Delta-ba-Delta 算法的起法,后续出现了许多优化算法:
AdaGrad算法:在实验中发现该算法容易导致学习率过早和过量的减小;
RMSProp算法:在优化深度神经网络时有效且实用;
Adam算法:依据RMSProp算法所改良的优化算法。
5 TensorFlow提供的优化器
5.1 train.Optimizer()
这是一个基本的优化器类,通常使用其子类:AdagradOptimizer、GradientDescentOptimizer等。对于一个具体的优化器类,我们一般会调用在基本优化器类中的 minimize()函数来指定最小化的目标。
5.2 train.GradientDescentOptimizer()
该函数是梯度下降优化器,用得较为普遍,不过训练速度很慢,因为需要计算全部的训练样本。
train.AdagradOptimizer() :Adagrad自适应学习率优化器;
train.RMSPropOptimizer():RMSProp自适应学习率优化器
train.AdamOptimizer():Adam算法;
train.AdagradDAOptimizer():Adagrad升级版本;
train.AdadeltaOptimizer():Adadelta算法的优化器;
train.ProximalGradientDescentOptimizer():Optimizer类的子类;
train.ProximalAdagradOptimizer():Optimizer类的子类;
train.FtrlOptimizer():FTRL算法的优化器
6 学习率的独立设置
学习率通常用于控制梯度下降中参数更新的幅度。
6.1 指数衰减的学习率
学习率既不能过大,也不能过小。为了更好的设置学习率,我们可逐步减小已经设好的学习率。TensorFlow提供了 train.exponential_decay()函数,可以对学习率进行指数形式的衰减。
exponential_decay(learning_rate,global_step,decay_steps,decay_rate,staircase,name)
#learning_rate是初是学习率
#global_step是当前训练的轮数
#decay_steps是衰减速度
#decay_rate是衰减系数
#staircase参数指定了衰减方式
学习率如果连续的衰减,那么不同的训练数据就有不同的学习率。下面代码展示exponential_decay()函数使用:
import tensorflow as tf
#这里设training_rate代表目前正在进行的训练轮数
training_step = tf.Variable(0)
#使用exponential_decay()函数设置学习率
decayed_learning_rate = tf.train.exponential_decay(0.8, training_step, 100, 0.9, staircase=True)
#使用一个梯度下降优化器,其中损失函数loss是目标函数
learning_step = tf.train.GradientDescentOptimizer(decayed_learning_rate).minimize(loss, global_step=training_step)
6.2 其他优化学习率方法
train.inverse_time_decay():反时限学习率衰减;
train.natrual_exp_decay():自然指数衰减学习率;
train.piecewise_constant():分片常数学习率衰减;
train.polynomial_decay():多项式学习率衰减。
7 拟合
7.1 过拟合和欠拟合
可以通过调整机器学习算法模型的容量来控制模型是否偏向于过拟合或欠拟合。模型的容量是指其拟合各种函数的能力,如果容量适合于任务的复杂度和所提供的训练数据的规模,算法会表现出不错的效果。容量不足的模型因为难以拟合训练集而出现欠拟合的现象;容量高得模型能够解决复杂的任务,但是其当容量高于任务所需时,可能会因为很好地记忆了每一个训练数据中随机噪音的分布,导致忽略了对训练数据中通用趋势的学习,从而出现过拟合现象。
7.2 正则化的方法
正则化是避免过拟合问题而采用的方法,其思想就是在损失函数中加入被称为正则化项(Regularizer)的惩罚。假设模型在训练集上的损失函数是 J(w) (注意这里的 w 表示的是整个神经网络的所有参数,包括边上的权重w和偏置项b),那么在优化时不是直接优化 J(w),而是优化 。
就是我们在损失函数中加入的正则化项,他通过对权重参数求解范数的方式对模型的复杂程度进行了刻画(一般而言权重w决定了模型的复杂程度)。
是提前挑选的值,控制我们偏好小范数权重的程度(越大的
偏好范数越小的权重)。
常用的刻画模型复杂度的函数 有两种,一种是 L1正则化;另一种是L2 正则化。
最小化 意味着需要在偏好小范数权重和拟合训练数据之间找到一个平衡,其基本思想是通过限制权重的大小,降低模型拟合训练集中存在的噪音的概念,从而减轻过拟合。
Tensorflow提供了计算L2正则化的函数——contrib.layers.l2_regularizer()函数,这个函数可以计算一个给定参数的L2正则化项的值。也提供了计算L1正则化的函数——contrib.layers.l1_regularizer()函数,他可以返回一个函数,该函数可以计算一个给定参数的L1正则化的值。以下代码给出使用这两个函数的样例:
import tensorflow as tf
weights = tf.constant([[1.0,2.0],[-3.0,-4.0]])
#.5 表示正则化项的权重,对应 λ
regularizer_l2 = tf.contrib.layers.l2_regularizer(.5)
regularizer_l1 = tf.contrib.layers.l1_regularizer(.5)
with tf.Session() as sess:
print (sess.run(regularizer_l2(weights)))
print (sess.run(regularizer_l1(weights)))
只要能够达到模型优化的目的,都可以根据公式 自定义损失函数,Tensorflow当然也可以优化带正则化项的损失函数。假设
代表了交叉熵损失函数,λ=0.01,
参数为 weight1 和 weight2, 则计算总损失的过程大概就如下面的代码所示:
#求解平均交叉熵损失
cross_entropy_mean = tf.reduce_mean(cross_entropy)
#返回L2正则化计算函数
regularizer_l2 = tf.contrib.layers.l2_regularizer(.01)
#需要计算L2正则化的参数为weight1和weight2
regularization = regularizer_l2(weight1)+regularizer_l2(weight2)
#总损失定义为交叉熵损失和正则化损失
loss = cross_entropy_mean + regularization
当神经网络的参数增多后,使用计算图和张量来计算总损失。以下代码实现了一个4层全连接神经网络带L2正则化损失函数的功能:
import tensorflow as tf
import numpy as np
#定义训练轮数
training_steps = 30000
#定义输入的数据和对应的标签并在for循环内进行填充
data = []
label = []
for i in range(200):
x1 = np.random.uniform(-1, 1)
x2 = np.random.uniform(0, 2)
#这里对产生的x1和x2进行判断,如果产生的点落在半径为1的圆内,则label值为0,否则为1
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)
#numpy的hstack()函数用于在水平方向将元素堆起来
#函数原型numpy.hstack(tup),参数tup可以是元组、列表或者numpy数组
#返回结果为numpy的数组,reshape()函数的参数为-1表示行列进行翻转
#这样处理的结果是data变成了200*2大小的数组,而label是200*1
data = np.hstack(data).reshape(-1,2)
label = np.hstack(label).reshape(-1,1)
#定义完成前向传播的隐层
def hidden_layer(input_tensor,weight1,bias1,weight2,bias2,weight3,bias3):
layer1 = tf.nn.relu(tf.matmul(input_tensor,weight1)+bias1)
layer2 = tf.nn.relu(tf.matmul(layer1, weight2)+bias2)
return tf.nn.relu(tf.matmul(layer2, weight3)+bias3)
x = tf.placeholder(tf.float32, shape=(None, 2),name="x-input")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y-output")
#定义权重参数和偏置参数
weight1 = tf.Variable(tf.truncated_normal([2,10], stddev=0.1))
bias1 = tf.Variable(tf.constant(0.1, shape=[10]))
weight2 = tf.Variable(tf.truncated_normal([10,10], stddev=0.1))
bias2 = tf.Variable(tf.constant(0.1, shape=[10]))
weight3 = tf.Variable(tf.truncated_normal([10, 1], stddev=0.1))
bias3 = tf.Variable(tf.constant(0.1, shape=[1]))
#用len()函数计算data数组的长度
sample_size = len(data)
#得到隐层前向传播的结果
y = hidden_layer(x, weight1, bias1,weight2,bias2,weight3,bias3)
#自定义损失函数,pow()函数用于计算幂函数
#返回结果为x的y次幂,这里返回结果为(y_ - y)^2,用于衡量计算值与实际值的差距
error_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / sample_size
tf.add_to_collection("losses",error_loss) #加入集合的操作
#在权重参数上的实现L2正则化
regularizer = tf.contrib.layers.l2_regularizer(0.01)
regularization = regularizer(weight1)+regularizer(weight2)+regularizer(weight3)
tf.add_to_collection("losses", regularization) #加入集合操作
#get_collection()函数获取指定集合中的所有个体,这里是获取所有损失值
#并在add_n()函数中进行加和运算
loss = tf.add_n(tf.get_collection("losses"))
#定义一个优化器,学习率固定为0.01,注意在实际应用中这个学习率数值应该大于0.01
train_op = tf.train.AdamOptimizer(0.01).minimize(loss)
with tf.Session() as sess:
tf.global_variables_initializer().run()
#在for循环内进行30000轮训练
for i in range(training_steps):
sess.run(train_op, feed_dict={x:data, y_:label})
#训练30000轮,但每隔2000轮就输出一次loss值
if i % 2000 == 0:
loss_value = sess.run(loss, feed_dict={x:data, y_:label})
print ("After %d steps, mes_loss: %f"%(i, loss_value))
7.3 Bagging方法
Bagging思想:会分别训练几个不同的模型(假设为k个),之后使用相同的测试集在这些模型上进行测试,并收集所有模型在测试集上的输出。
7.4 Dropout方法
Dropout集成方法需要训练的是从原始网络去掉一些不属于输出层的单元后形成的子网络。丢弃(删除)某些单元并不是指真的构建出这种结构的网络,为了能有效地删除一个单元,我们需要将该单元地输出乘以0即可(结果就是该单元的值变为了0)。
TensorFlow提供了实现Dropout功能的函数,即 nn.dropout()函数,以下代码演示使用:
import tensorflow as tf
x = tf.Variable(tf.ones[10, 10])
dro = tf.placeholder(tf.float32)
y = tf.nn.dropout(x, dro)
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
print (sess.run(y, feed_dict={dro: 0.5}))