一、深度学习与深层神经网络
维基百科对深度学习的定义为“一层通过多层非线性变换对高复杂性数据建模算法的合集”。实际上可以认为深度学习就是深层神经网络的代名词。深度学习有两个非常重要的特性,多层和非线性。
1. 线性模型的局限性
线性模型的输出为输入的加权和。当模型的输入只有一个时,x和y形成了二维坐标系上的一条直线。当模型有n个输入时,x和y形成了n+1维空间中的一个平面。线性模型的最大特点是任意线性模型的组合仍然是线性模型。
因此如果只经过线性变换,那么任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别。
2. 激活函数去线性化
为神经网络加入激活函数和偏置项后,整个神经网络的模型就不再是线性的,能够处理非常复杂的问题。常用的非线性激活函数有ReLu、sigmoid、tanh等。
以下代码展示了神经网络加入了激活函数后的前向传播算法。
a = tf.nn.relu(tf.matmul(x,w1)+biases1)
y = tf.nn.relu(tf.matmul(a,w2)+biases2)
二、损失函数的定义
分类和回归是监督学习的两大种类。分类问题希望将不同样本分到事先定义好的类别中,回归问题则是希望通过找到一个函数拟合数据集,达到预测未知样本的目的。
神经网络解决多分类问题最常用的方法是设置n个输出节点,那么这个类别所对应的输出节点的输出值为1,其他节点的输出都为0。
交叉熵是常用的判断一个输出向量和期望向量的接近程度,它是分类问题中使用比较广的损失函数。给定两个概率分布p和q,通过q来表示p的交叉熵为:
交叉熵刻画的是两个概率分布之间的距离,但神经网络的输出不一定是一个概率分布,因此需要将神经网络的输出转变成一种概率分布。Softmax就是一个常用的方法。假设神经网络的输出为
y1,y2,...,yn
,那么经过Softmax回归处理之后的输出为:
交叉熵函数不是对称的,它刻画的是用概率分布q来表达概率分布p的困难程度,p代表正确答案而q代表预测值。交叉熵的值越小,两个概率分布越接近。
通过TensorFlow实现交叉熵的代码如下:
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y,1e-10,1.0)))
其中y_代表正确结果,y代表预测结果。tf.clip_by_value函数可以将一个张量的数值限制在一个范围之内。这样可以避免一些运算错误。
v = tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
print tf.clip_by_value(v,2.5,4.5).eval()
上述代码里,小于2.5的数被换成2.5,大于4.5的数被换成4.5。
实现乘法运算主要有两种情况,一种直接通过‘*’操作相乘,另一种使用tf.matmul函数完成。两者的区别是,第一种指矩阵对应之间的相乘,第二种指矩阵相乘。
tf.reduce_mean函数主要用于求矩阵的平均数。
v = tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
print tf.reduce_mean(v).eval() # 输出3.5
因为交叉熵一般会与softmax一起使用,所以TensorFlow对这两个功能进行了统一封装,提供了tf.nn.softmax_cross_entropy_with_logits函数。比如可以直接通过以下代码实现:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y,y_)
y代表原始神经网络的输出结果,y_给出了标准答案。
与分类问题不同,回归问题解决对具体数值的预测。比如房价、销量等预测。解决回归问题的神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方误差(MSE),它的定义如下:
以下代码展示如何通过TensorFlow实现均方误差损失函数:
mse = tf.reduce_mean(tf.square(y_-y))
三、神经网络优化算法
梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能的小。
梯度下降法的问题:1. 不能保证被优化的函数达到全局最优解;2. 计算时间太长。通常为了加速训练过程,会使用随机梯度下降法,这个算法是每次优化某一条数据上的损失函数,但某一条数据上的损失函数无法代表全数据集上的损失函数,因此随机梯度下降优化得到的神经网络甚至无法达到局部最优。
综合梯度下降和随机梯度下降法,实际应用中一般使用一个batch数据上的损失。
神经网络的训练大都遵循以下过程。
batch_size = n
x = tf.placeholder(tf.float32, shape=(batch_size,2),name='x-input')
y_ = tf.placeholder(tf.float32, shape=(batch_size,1),name='y-input')
loss = ...
train_step = tf.tarin.AdamOptimizer(0.01)minimize(loss)
with tf.Session() as sess:
...
for i in range(STEPS):
current_X, current_Y = ...
sess.run(train_step, feed_dict={x:current_X,y_:current_Y})
四、神经网络的优化
1. 学习率的设置
如果学习率设置的过大,会出现参数在最优点两侧摇摆,无法收敛到全局最优;若学习率设置过小,那么收敛的速度将会非常慢。TensorFlow提供了一种非常灵活的学习率设置的方法,即指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率。通过这个函数,先使用较大的学习率快速得到一个比较优的解,随着迭代的继续逐渐减小学习率,使得模型在训练后期更加稳定。代码如下:
decayed_learning_rate = learning_rate * decay_rate^(global_step / decay_steps)
其中decayed_learning_rate为每一轮优化使用的学习率,learning_rate为事先设定的初始学习率,decay_rate为衰减系数,decay_steps为衰减速度。
tf.train.exponential_decay函数有一个设置参数staircas,它是用来选择不同的衰减方式。若staircase为False,那么学习率随着迭代轮数的变化呈指数下降的趋势,为连续值;若其值为True,global_step/decay_steps 会转变成整数,则是按照阶梯状下降的趋势。代码如下:
learning_rate = tf.train_exponential_decay(0.1,global_step,100,0.96,staircase=True)
上述代码中初始学习率为0.1,衰减曲线呈阶梯状下降趋势,每训练100轮后学习率乘以0.96。
2. 过拟合问题
在实际情况中,所获得的训练集总是存在一些噪声,因此我们并不是希望模型能够完全将训练数据集的信息非常好的学习到,而是希望它能够对未知数据有很好的预测能力。过拟合就是导致这一差距的很重要的因素。过拟合就是训练得到的模型过于复杂,它可以很好的拟合每一个训练数据,但是由于训练的有些过了,它可以很好的记忆每一个训练数据中随机噪音,而忘记了去学习训练数据中通用的趋势。因此我们需要避免过拟合。
防止过拟合的最常用的方法就是正则化。正则化的思想就是在损失函数中加入刻画模型复杂度的指标,比较常用的是L1正则和L2正则。
无论哪一种正则化的方式,都是希望限制权重大小,使得模型不能任意拟合训练数据中的随机噪声。但是这两种正则化的方法也具有很大区别。
比如L1正则化会让参数更加稀疏,而L2正则化不会。参数稀疏是指会有更多的参数变为0,这样可以达到类似特征选择的功能。L2正则不会让参数稀疏化是由于当参数很小时,这个参数的平方就可以忽略,模型不会进一步把这个参数调整为0。
其次,L1正则化的计算公式不可导,但L2正则化公式可导。因为在优化时需要计算损失函数的偏导数,所以L2正则化损失函数的优化要更简洁。
简单的L2正则化的损失函数如下:
w = tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y = tf.matmul(x,w)
loss = tf.reduce_mean(tf.square(y_-y)) + tf.contrib.layers.l2_regularizer(lambda)(w)
3. 滑动平均模型
在采用随机梯度下降算法训练神经网络时,使用滑动平均模型可以在一定程度上提高最终模型在测试数据上的表现。
TensorFlow中提供tf.train.ExponentialMovingAverage来实现滑动平均模型。初始化ExponentialMovingAverage时,需要提供一个衰减率。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每一个变量会维护一个影子变量,这个影子变量的初始值就是相应变量的初始值,每次运行变量更新时,变量的值就会更新为:
shadow_variable = decay * shadow_variable + (1 - decay) * variable
shadow_variable为影子变量,variable为待更新的变量,decay为衰减率。从公式可以看出,decay决定了模型更新的速度,dacay越大模型越趋于稳定。实际应用中,decay会设置为接近1的数。为了使模型在训练前期可以更新的更快,ExponentialMovingAverage还提供了num_updates参数来设置decay的大小。若在ExponentialMovingAverage初始化时提供了num_updates参数,那么每次使用的衰减率将是: