深度神经网络
深度学习与深度神经网络
深度学习:一类通过多层非线性变换对高复杂性数据建模算法的合集。可以看出深度学习有两个非常重要的特性——多层和非线性。
对于非线性而言,因为现实中存在着大量的非线性关系,而线性模型即输入参数的加权组合并不能很好地解决这种关系。而深度学习通过添加激活函数,即添加非线性函数引入了非线性很好的解决了线性模型的局限性。其中 tf.nn.relu、tf.sigmoid、tf.tanh 是其中比较常用的几种。
a = tf.nn.relu(tf.matmul(x, w1) + biases1)
y = tf.nn.relu(tf.matmul(a, w2) + biases2)
对于多层而言,针对某些复杂的逻辑,比如异或操作,单层神经网络可能并不能有效地提取并组合有用的特征。而隐藏层的隐藏节点可以被认为代表了从输入特征中抽取的更高维的特征,而这个特性对于解决不易提取特征向量的问题(比如图片识别,语音识别等)有很大的帮助。
损失函数
分类问题和回归问题是监督学习的两大种类。针对二分类问题,通常定义单一输出节点,并针对激活函数给出分类阈值,对样本进行分类。而对于多分类问题,可以通过设置多个阈值进行处理,但通常通过定义与类别数相同的输出节点数来处理,其中每一个输出节点的值表示了样本属于该类别的可能性。而为了比较神经网络输出结果的优劣,交叉熵是常用的一种评判方法。
交叉熵的定义如下
其刻画的是通过概率分布 来表达概率分布 的困难程度。但是神经网络的输出通常不满足概率分布,因此通常在神经网络的原始输出层后加一个 softmax 层。
通过 tensorflow 实现交叉熵的代码如下
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
其中 tf.clip_by_value 可以将一个张量中的数值限制在一个范围之内,这样可以避免一些运算错误,比如 。而 * 在 tensorlfow 中表示矩阵元素分别相乘,如果需要矩阵相乘必须使用 tf.matmul 函数。最后 tf.reduce_mean 实现了对整个矩阵的求和平均操作。
由于交叉熵通常和 softmax 一起使用,因此 tensorflow 直接提供了对应的函数 tf.nn.softmax_cross_entropy_with_logits。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)
其中 y 代表原始神经网络的输出结果,而 y_ 给出了标准答案。在只有一个正确答案的分类问题中,tensorflow 提供了 tf.nn.sparse_softmax_cross_entropy_with_logits 函数来进一步加速计算过程。
与分类问题不同,回归问题解决的是对具体数值的预测。这些问题需要预测的不是一个实现定义好的类别,而是一个任意实数。解决回归问题的神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方根误差(MSE,mean squared error)。
mse = tf.reduce_mean(tf.square(y_ - y))
自定义损失函数
不过对于某些特殊的问题,预定义的损失函数通常不能满足要求,这是就需要用户自定义损失函数。比如对于如下损失函数
loss = tf.reduce_sum(tf.where(tf.greater(v1, v2), (v1 - v2) * a, (v2 - v1) * b))
实例代码
import tensorflow as tf
from numpy.random import RandomState
batch_size = 8
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
w1= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)
# 定义损失函数使得预测少了的损失大,于是模型应该偏向多的方向预测。
loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
rdm = RandomState(1)
X = rdm.rand(128,2)
Y = [[x1+x2+(rdm.rand()/10.0-0.05)] for (x1, x2) in X]
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i*batch_size) % 128
end = (i*batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print sess.run(w1), "\n"
print "Final w1 is: \n", sess.run(w1)
"""
After 0 training step(s), w1 is:
[[-0.81031823]
[ 1.4855988 ]]
After 1000 training step(s), w1 is:
[[ 0.01247112]
[ 2.1385448 ]]
After 2000 training step(s), w1 is:
[[ 0.45567414]
[ 2.17060661]]
After 3000 training step(s), w1 is:
[[ 0.69968724]
[ 1.8465308 ]]
After 4000 training step(s), w1 is:
[[ 0.89886665]
[ 1.29736018]]
Final w1 is:
[[ 1.01934695]
[ 1.04280889]]
"""
如果修改损失函数将得到相反的结果(最后权重都小于1)
loss_less = 1
loss_more = 10
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i*batch_size) % 128
end = (i*batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print sess.run(w1), "\n"
print "Final w1 is: \n", sess.run(w1)
"""
After 0 training step(s), w1 is:
[[-0.81231821]
[ 1.48359871]]
After 1000 training step(s), w1 is:
[[ 0.18643527]
[ 1.07393336]]
After 2000 training step(s), w1 is:
[[ 0.95444274]
[ 0.98088616]]
After 3000 training step(s), w1 is:
[[ 0.95574027]
[ 0.9806633 ]]
After 4000 training step(s), w1 is:
[[ 0.95466018]
[ 0.98135227]]
Final w1 is:
[[ 0.95525807]
[ 0.9813394 ]]
"""
最后如果将损失函数定义为 MSE,将得到最接近真实值的预测结果
loss = tf.losses.mean_squared_error(y, y_)
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i*batch_size) % 128
end = (i*batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print sess.run(w1), "\n"
print "Final w1 is: \n", sess.run(w1)
"""
After 0 training step(s), w1 is:
[[-0.81031823]
[ 1.4855988 ]]
After 1000 training step(s), w1 is:
[[-0.13337609]
[ 1.81309223]]
After 2000 training step(s), w1 is:
[[ 0.32190299]
[ 1.52463484]]
After 3000 training step(s), w1 is:
[[ 0.67850214]
[ 1.25297272]]
After 4000 training step(s), w1 is:
[[ 0.89473999]
[ 1.08598232]]
Final w1 is:
[[ 0.97437561]
[ 1.0243336 ]]
"""
神经网络优化算法
反向传播算法(backpropagation)和梯度下降算法(gradient decent)用于调整神经网络中参数的取值。梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能小。
但是梯度下降算法存在一些问题。首先梯度下降算法并不能保证获得最优解,如果对应问题不是一个凸问题,算法很可能会陷入到局部最优解中。另一个主要的问题是梯度下降算法的计算时间过长。因为要在全部训练数据上最小化损失,所以损失函数是在所有训练数据上的损失和。这样在每一轮迭代中都需要计算在全部训练数据上的损失函数。在海量训练数据下,要计算所有训练数据的损失函数是非常消耗时间。为了加速训练过程,可以使用随机梯度下降算法(stochastic gradient descent)。这个算法优化的不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数。但由于在某一条数据上损失函数更小并不能代表在全部数据上损失函数更小,因此使用随机梯度下降算法得到的神经网络在最后往往会在(局部)最优解周围徘徊。
对上述两种算法通常进行一种折中——使用 batch 的数据进行优化。
学习率设置
每一步更新的大小——学习率,如果设置过大,无论迭代多少步,最后的结果也将在一定范围内摇摆,而如果设置过小,则会长时间无法收敛。为解决学习率的问题,tensorflow 提供了一种更加灵活的学习率设置方法——指数衰减法 tf.train.exponential_decay。通过这个函数,可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代次数的增加逐步减小学习率,使得模型在训练后更加稳定。
TRAINING_STEPS = 100
global_step = tf.Variable(0)
LEARNING_RATE = tf.train.exponential_decay(0.1, global_step, 1, 0.96, staircase=True)
x = tf.Variable(tf.constant(5, dtype=tf.float32), name="x")
y = tf.square(x)
train_op = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(y, global_step=global_step)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(TRAINING_STEPS):
sess.run(train_op)
if i % 10 == 0:
LEARNING_RATE_value = sess.run(LEARNING_RATE)
x_value = sess.run(x)
print "After %s iteration(s): x%s is %f, learning rate is %f."% (i+1, i+1, x_value, LEARNING_RATE_value)
"""
After 1 iteration(s): x1 is 4.000000, learning rate is 0.096000.
After 11 iteration(s): x11 is 0.690561, learning rate is 0.063824.
After 21 iteration(s): x21 is 0.222583, learning rate is 0.042432.
After 31 iteration(s): x31 is 0.106405, learning rate is 0.028210.
After 41 iteration(s): x41 is 0.065548, learning rate is 0.018755.
After 51 iteration(s): x51 is 0.047625, learning rate is 0.012469.
After 61 iteration(s): x61 is 0.038558, learning rate is 0.008290.
After 71 iteration(s): x71 is 0.033523, learning rate is 0.005511.
After 81 iteration(s): x81 is 0.030553, learning rate is 0.003664.
After 91 iteration(s): x91 is 0.028727, learning rate is 0.002436.
"""
exponential_decay 本质上实现了以下功能
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
其中初始学习率为 0.1,staircase=True 表示 global_step / decay_steps 将被强制转换为整数,decay_steps 通常代表了完整的使用一遍训练数据所需要的迭代轮数,也就是总训练样本数除以每一个 batch 中的训练样本数。这样可以保证每完整过完一遍训练数据,学习率就减小一次。这可以使得训练数据集中的所有数据对模型训练有相等的作用。
过拟合问题
上面讲述了如何在训练数据上优化一个给定的损失函数。然而在真实的应用中想要的并不是让模型尽量模拟训练数据的行为,而是希望通过训练出来的模型对未知的数据给出判断。模型在训练数据上的表现并不一定代表了它在未知数据上的表现。而过拟合问题就是可以导致这个差距的一个很重要的因素。所谓过拟合,指的是当一个模型过于复杂之后,它可以很好地记忆每一个训练数据中随机噪声的部分而忘记了要去学习训练数据中通用的趋势。
为了避免过拟合问题,一种非常常用的方法是正则化(regularization)。其中又主要分为 L1 和 L2 两种正则化方法。
Tensorflow 同样提供了对应的函数
// L2
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l2_regularizer(lambda)(w)
// L1
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l1_regularizer(lambda)(w)
实例程序
import tensorflow as tf
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))
return var
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]
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)
in_dimension = layer_dimension[i]
y= cur_layer
# 损失函数的定义。
mse_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / sample_size
tf.add_to_collection('losses', mse_loss)
loss = tf.add_n(tf.get_collection('losses'))
滑动平均模型
在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最终模型在测试数据上的表现。Tensorflow 提供了 tf.train.ExponentialMovingAverage 来实现滑动平均模型。
shadow_variable = decay_ * shadow_variable + (1 - decay_) * variable
decay_ = min{decay, (1 + num_updates) / (10 + num_updates)}
范例程序
import tensorflow as tf
v1 = tf.Variable(0, dtype=tf.float32)
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(0.99, step)
maintain_averages_op = ema.apply([v1])
with tf.Session() as sess:
# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run([v1, ema.average(v1)]))
# 更新变量v1的取值
sess.run(tf.assign(v1, 5))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]) )
# 更新step和v1的取值
sess.run(tf.assign(step, 10000))
sess.run(tf.assign(v1, 10))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 更新一次v1的滑动平均值
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
"""
[0.0, 0.0]
[5.0, 4.5]
[10.0, 4.5549998]
[10.0, 4.6094499]
"""