TensorFlow实战——深层神经网络

TensorFlow——深层神经网络

深度学习与深层神经网络

深度学习有两个非常重要的特性——多层和非线性。

线性模型的局限性

如果不使用激活函数,单纯的用线性函数解决神经网络的问题,那么多层的网络和单层的网络并没有区别,只是线性模型中的系数发生了变化。
也就是说,激活函数若采用线性的,那么神经网络就无法训练出非线性适应模型,即与直接采用线性模型没有差异。只有在激活函数是非线性的情况下,才能用神经网络解决非线性问题。


以下代码展示了如何通过激活函数实现神经网络的前向传播算法。

a = tf.nn.relu(tf.matmul(x, w1) + biases1)
y = tf.nn.relu(tf.matmul(a, w2) + biases2)

损失函数定义

经典损失函数

交叉熵是常用的评判方法之一。交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广的一种损失函数。
给定两个概率分布p和q,通过q来表示p的交叉熵为:

H(p,q)=xp(x)logq(x) H ( p , q ) = − ∑ x p ( x ) log ⁡ q ( x )

将神经网络前向传播得到的结果变为概率分布的一个常用方式是使用Softmax回归。
Softmax回归本身可以作为一个学习算法来优化分类结果,但在TensorFlow中,Softmax回归的参数被去掉了,它只是一层额外的处理层,将神经网络的输出变成一个概率分布。如图所示。

假设原始的神经网络输出为 y1,y2,...,yn y 1 , y 2 , . . . , y n ,那么经过Softmax回归处理之后的输出为:

softmax(y)=yi=eyiΣnj=1eyj s o f t m a x ( y ) = y i ′ = e y i Σ j = 1 n e y j

从上述公式中可以看出,原始神经网络的输出被用作置信度来生成新的输出,而新的输出满足概率分布的所有要求。
因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,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函数可以将一个张量中的数值限制在一个范围之内,这样可以避免一些运算错误(比如log0是无效的)。
一个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 3] [4 4.5 4.5]]

即,小于2.5的数都被换成了2.5,大于4.5的数都被换成了4.5。

第二个函数是tf.log函数,这个函数完成了对张量中所有元素依次求对数的过程。
第三个运算是乘法,在实现交叉熵的代码中,直接将两个矩阵通过”*“操作相乘,这个操作不是矩阵乘法,而是元素之间相乘,矩阵乘法需要使用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函数。比如可以直接通过下面的代码来实现使用了softmax回归之后的交叉熵损失函数:

#只能使用命名参数的方式来调用
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_)

而对于只有一个正确答案的分类问题中,TensorFlow提供了tf.nn.sparse_softmax_cross_entropy_with_logits函数来进一步加快计算过程。

自定义损失函数

TensorFlow还支持任意的自定义损失函数。
比如下面的公式给出了一个当预测多余真实值和预测少于真实值时有不同损失系数的损失函数。

Loss(y,y)=i=1nf(yi,yi) L o s s ( y , y ′ ) = ∑ i = 1 n f ( y i , y i ′ )

其中
f(x,y)=a(xy) b(yx) x>yxy f ( x , y ) = { a ( x − y ) x > y   b ( y − x ) x ≤ y  

在TensorFlow中,可以通过下列代码实现这个损失函数。

loss = tf.reduce_sum(tf.select(tf.greater(v1, v2), (v1 - v2) * a, (v2 - v1) * b))

tf.greater的输入是两个张量,此函数会比较这两个输入张量中每一个元素的大小,并返回比较结果。
tf.select函数有三个参数。第一个为选择条件根据,当选择条件为True时,tf.select函数会选择第二个参数中的值,否则使用第三个参数中的值。
要注意,tf.select函数判断和选择都是在元素级别进行

import tensorflow as tf
v1 = tf.constant([1.0, 2.0, 3.0, 4.0])
v2 = tf.constant([4.0, 3.0, 2.0, 1.0])

sess = tf.InteractiveSession()
print tf.greater(v1, v2).eval()
#输出[Flase, False, True, True]
print tf.select(tf.greater(v1, v2), v1, v2).eval()
#输出[4. 3. 3. 4.]
sess.close()

神经网络进一步优化

介绍了神经网络的基本算法之后,我们还可以针对优化过程中可能遇到的一些问题提出一些常用的解决方法。
1. 通过指数衰减的方法设置梯度下降算法中的学习率。通过指数衰减的学习率即可以让模型在训练的前期快速接近较优解,又可以保证模型在训练后期不会有太大的波动,从而更加接近局部最优。
2. 过拟合问题的具体解决方法
3. 滑动平均模型。滑动平均模型会将每一轮迭代得到的模型综合起来,从而使得最终得到的模型更加健壮(robust)。

指数衰减学习率

TensorFlow提供了一种更加灵活的学习率设置方法——指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率。
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函数可以通过设置参数staircase选择不同的衰减方式。staircase的默认值为False,这时候学习率迭代轮数变化的趋势为连续的。当staircase的值被设置为True时, global_step / decay_steps会被转化成整数。这使得学习率成为一个阶梯函数。在这样的设置下,decay_steps通常代表了完整的使用一遍训练数据所需要的迭代轮数,这个迭代轮数也就是总训练样本数除以每一个batch中的训练样本数。

global_step = tf.Variable(0)

learning_rate = tf.train.exponential_decay(0.1, globla_step, 100, 0.96, staircase=True)
#使用指数衰减的学习率。在minimize函数中传入global_step将自动更新global_step参数,从而使得学习率也得到相应更新。
learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(...my loss..., global_step=global_step)

过拟合问题

为了避免过拟合问题,常用方法是正则化。正则化的思想就是在损失函数中加入刻画模型复杂程度的指标。
常用的刻画模型复杂程度的函数R(w)有两种,一种是L1正则化,计算公式是:

R(w)=||w||1=i|wi| R ( w ) = | | w | | 1 = ∑ i | w i |

另一种是L2正则化,计算公式是:
R(w)=||w||22=i|w2i| R ( w ) = | | w | | 2 2 = ∑ i | w i 2 |

这两种正则化方法有很大区别。首先,L1正则化会让参数变得更稀疏,而L2正则化不会。所谓参数变得更稀疏是指会有更多的参数变为0,这样可以达到类似特征选取的功能。之所以L2正则化不会让参数变得稀疏的原因是当参数很小时,比如0.001,这个参数的平方基本上就可以忽略了,于是模型不会进一步将这个参数调整为0.其次,L1正则化的计算公式不可导,而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)

TensorFlow提供了tf.contrib.layers.l2_regularizer函数,它可以返回一个函数,这个函数可以计算一个给定参数的L2正则化项的值。lambda参数表示了正则化项的权重,w为需要计算正则化损失的参数。
类似的,tf.contrib.layers.l1_regularizer可以计算L1正则化项的值。
但是,当神经网络的参数增多之后,这样的方式首先可能导致损失函数loss的定义很长,可读性差且容易出错。当网络结构复杂之后定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,这样通过变量这种方式计算损失函数就不方便了。于是我们可以使用TensorFlow中提供的集合概念。
以下代码给出通过集合计算一个五层神经网络带L2正则化的损失函数的计算方法。

import tensorflow as tf
#获取一层神经网络的权重,并将L2损失加入到loss的集合中
def get_weight(shape, lambda):
    var = tf.Variable(tf.random_normal(shape), dtype = tf.float32)
    tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda)(var))#将第二个参数的内容加入到名称为第一个参数的集合中
    return var
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
batch_size = 8
layer_dimension = [2, 10, 10, 10, 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]
    weight = get_weight([in_dimension, out_dimension], 0.001)
    cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
    in_dimension = layer_dimension[i]
mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))#均方误差损失(不含正则误差)
tf.add_to_collection('losses', mse_loss)
loss = tf.add_n(tf.get_collection('losses'))## 最终的损失函数

滑动平均模型

在TensorFlow中提供了tf.train.ExponentialMovingAverage来实现滑动平均模型。
在初始化ExponentialMovingAverage时,需要提供一个衰减率decay。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每一个变量会维护一个影子变量,这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:
shadow_variable = decay × shadow_variable + (1 - decay) × variable
shadow_variable为影子变量,variable为更新后的变量值,decay为衰减率。
decay决定了模型更新的速度,decay越大模型越趋于稳定。
为了使得模型在训练前期可以更新得更快,ExponentialMovingAverage还提供了num_updates参数来动态设置decay的大小。如果在ExponentialMovingAverage初始化时提供了num_updates参数,那么每次使用的衰减率将是
min{decay,1+num_updates/10+num_updates}
下面通过代码来理解滑动平均模型

import tensorflow as tf
v1 = tf.Variable(0, dtype = tf.float32)#定义一个变量用于计算滑动平均,初始值为0
step = tf.Variable(0, trainable=Flase)#step变量模拟神经网络迭代次数用于动态控制衰减率)

ema = tf.train.ExponentialMovingAverage(0.99, step)#定义滑动平均的类
maintain_averages_op = ema.apple([v1])
with tf.Session() as sess:
    init_op = tf.initialize_all_avriables()
    sess.run(init_op)
    #通过ema.average获取滑动平均之后变量的取值。
    print sess.run([v1, ema.average(v1)])#输出[0.0, 0.0]
    sess.run(tf.assign(v1, 5))#更新v1的值到5
    #更新v1的滑动平均值。衰减率为min{0.99, (1+step)/(10+step)=0.1}=0.1
    #所以v1的滑动平均会被更新为0.1×0+0.9×5=4.5
    sess.run(maintain_averages_op)
    print sess.run([v1, ema.average(v1)])#输出[5.0, 4.5]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值