一、深度学习与深层神经网络
深度学习的精确定义——一类通过多层非线性变换对高复杂性数据建模算法的合集。深层神经网络实际上有组合特征提取的功能,对于不易提取特征向量的问题(比如图片识别、语音识别等)有很大帮助,故可以解决异或问题,同时这也是深度学习能够在图像、语言等方向取得突破性进展的原因。这里涉及到几个概念:
- 多层和非线性
这是深度学习两个非常重要的特性。线性模型的局限性在于任意线性模型的组合仍然还是线性模型。只通过线性变化,任意层的全连接神经网络和单层神经网络的表达能力没有任何区别,而且它们都是线性模型。
- 复杂问题
所谓复杂问题,至少是无法通过线性可分的,现实世界的绝大多数问题都不是线性可分的。
通过**函数实现去线性化:
当每一个神经元节点的输出通过一个非线性函数时,那么整个神经网络的模型就不再是线性了。常见的**函数有ReLU、sigmoid和tanh函数。
以ReLU为例,TensorFlow中很好的支持了**函数和偏置项:
1. a= tf.nn .relu (tf.matmul(x, wl) + biasesl)
2. y = tf.nn.relu(tf.matmul(a, w2) + biases2)
二、损失函数
神经网络模型的效果以及优化的目标是通过损失函数来定义的。二分类问题中通常用0.5作为阈值,但是很难直接推广到多分类问题中。多分类问题中通常是设置n(类别个数)个节点,这样每次输出为一个n维数组,数组中的每一个维度对应一个类别。以识别数字1为例,模型输出越接近[0,1,0,0,0,0,0,0,0,0]越好。判断一个输出向量与期望向量之间接近程度通常用交叉熵。交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广的一种损失函数。
其中p(x)对应期望向量,q(x)对应实际向量。
TensorFlow中的交叉熵API:
cross_entropy = -tf.reduce_mean(y_ * tf.clip_by_value(y,1e-10,1.0)))
通过tf.clip_by_value函数可以将一个张量中的数值限制在一个范围内,可以避免一些运算错误(比如log0是无效的)。
使用softmax回归之后的交叉熵损失函数API:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,logits=y)
与分类问题不同的是,回归问题解决的是对具体数值的预测,最常用的损失函数是均方误差(MSE):
mse = tf.reduce_mean(tf.square(y_-y))
当预测值相对真实值的大小带来的成本是不均等代价时,往往就需要自定义一个损失函数,定义的损失韩式应该刻画成本或者代价。下面的公式给出了一个当预测大于真实值和预测小于真实值时有不同损失系数的损失函数:
1. import tensorflow as tf
2. from numpy.random import RandomState
3. import os
4. os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
5.
6. batch_size = 8
7.
8. #两个输入节点
9. x = tf.placeholder(tf.float32,shape=(None,2),name="x_input")
10. #回归一般只有一个输出节点
11. y_ = tf.placeholder(tf.float32,shape=(None,1),name="y_input")
12.
13. #定义一个单层的神经网络前向传播过程,即简单加权和
14. w1 = tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
15. y = tf.matmul(x,w1)
16.
17. #定义预测多了和预测少了的成本
18. loss_less = 10
19. loss_more = 1
20. loss = tf.reduce_sum(tf.where(tf.greater(y,y_),
21. (y - y_) * loss_more,
22. (y_ - y) * loss_less))
23.
24. train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
25.
26. #通过随机数生成一个模拟数据集
27. rdm = RandomState(1)
28. dataset_size = 128
29. X = rdm.rand(dataset_size,2)
30. Y = [[x1 + x2 + rdm.rand()/10.0 - 0.05] for (x1,x2) in X]
31.
32. #训练神经网络
33. with tf.Session() as sess:
34. tf.global_variables_initializer().run()
35. steps = 5000
36. for i in range(steps):
37. start = (i * batch_size) % dataset_size
38. end = min(start + batch_size,dataset_size)
39. sess.run(train_step,
40. feed_dict={
41. x:X[start:end],y_:Y[start:end]
42. })
43. if i % 1000 == 0:
44. print(sess.run(w1))
三、神经网络优化算法
神经网络中参数的取值一般通过反向传播算法和梯度下降算法来调整。梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法。反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。
神经网络的优化可以分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比得出两者之间的差距。然后在第二阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。
需要注意的是,梯度下降只有当损失函数为凸函数时才会达到全局最优解。相比之下,随机梯度下降算法效率更好一些,但是可能无法达到局部最优解。因此,在实际应用中一般采用这两种算法的折中——每次计算一小部分训练数据(batch)的损失函数。
1. """
2. 神经网络的大致训练过程
3. """
4. import tensorflow as tf
5.
6. batch_size = n
7.
8. #每次读取一小部分数据作为当前的训练数据来执行反向传播算法
9. x = tf.placeholder(tf.float32,shape=(batch_size,2),name="x_input")
10. y_ = tf.placeholder(tf.float32,shape=(batch_size,1),name="y_input")
11.
12. #定义神经网络结构和优化算法
13. loss = ...
14. train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
15.
16. #训练神经网络
17. with tf.Session() as tf:
18. #参数初始化
19. ...
20. #迭代更新参数
21. for i in range(steps):
22. current_X,current_Y = ...
23. sess.run(train_step,feed_dict={
24. x:current_X,y:current_Y
25. })
四、学习率的设置
学习率的设置不能过大也不能过小,过大的话会在极优值的两侧来回移动,过小的话则会大大降低优化的速度。这个问题可以通过指数衰减法解决,指数衰减学习率可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。
一般情况下,初始学习率、衰减系数和衰减速度都是根据经验设置的。
1. global_step = tf.Variable(0)
2.
3. #通过exponential_decay函数生成学习率
4. #初始学习率设置为0.1,因为指定了staircase为True,所以每训练100轮后学习率乘以0.96
5. #一般来说初始学习率、衰减系数和衰减速度都是根据经验设置的
6. learning_rate = tf.train.exponential_decay(
7. 0.1,global_step,100,0.96,staircase=True
8. )
9.
10. #使用指数衰减的学习率。在minimize函数中传入global_step将自动更新global_step
11. #从而使得学习率也得到相应的更新
12. #learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(
13. # ...my loss...,global_step=global_step
14. # )
五、过拟合问题
解决过拟合问题一个常用的方法是正则化。其思想就是在损失函数中加入刻画模型复杂程度的指标。有L1、L2正则化两种,实践中也可以同时使用。无论哪一种方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。
1. import tensorflow as tf
2. import os
3.
4. os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
5.
6. #定义一个简单的带L2正则化的损失函数
7. # w = tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
8. # y = tf.matmul(x,w)
9.
10. # loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.l2_regularizer(lambda1)(w)
11.
12.
13. #获取一层神经网络边上的权重,并将这个权重的L2正则化损失加入名称为'losses'的集合中
14. def get_weight(shape,lambda1):
15. #生成一个变量
16. var = tf.Variable(tf.random_normal(shape),dtype=tf.float32)
17. #add_to_collection函数将这个新生成变量的L2正则化损失项加入集合
18. #第一个参数是集合的名字,第二个参数是要加入这个集合的内容
19. tf.add_to_collection("losses",tf.contrib.l2_regularizer(lambda1)(var))
20. #返回生成的变量
21. return var
22.
23. #定义输入的数据
24. x = tf.placeholder(tf.float32,shape=(None,2))
25. y_ = tf.placeholder(tf.float32,shape=(None,1))
26.
27.
28. batch_size = 8
29. #定义了每一层网络中节点的个数
30. layers_dimention = [2,10,10,10,1]
31. n_layers = len(layers_dimention)
32.
33. #维护前向传播时最深的节点,初始时即为输入层
34. cur_layer = x
35. #当前层的节点数
36. in_dimention = layers_dimention[0]
37.
38.
39. #通过一个循环来生成5层全连接的神经网络
40. for i in range(1,n_layers):
41. #lay_dimention[i]为下一层的节点数
42. out_dimention = layers_dimention[i]
43. #生成当前层中权重的变量,并将这个变量的L2正则化损失加入计算图上的集合
44. weight = get_weight([in_dimention,out_dimention],0.001)
45. bias = tf.Variable(tf.constant(0.1,shape=[out_dimention]))
46. #使用ReLU**函数
47. cur_layer = tf.nn.relu(tf.matmul(cur_layer,weight) + bias)
48. #进入下一层之前将下一层的节点个数更新为当前层节点个数
49. in_dimention = layers_dimention[i]
50.
51. #在定义神经网络前向传播的同时已经将所有的L2正则化损失加入了图上的集合
52. #这里只需要计算刻画模型在训练数据上表现的损失函数
53. mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))
54.
55. #将均方误差损失函数加入损失集合
56. tf.add_to_collection('loss',mse_loss)
57.
58. #get_collection返回一个列表,这个列表是所有这个集合中的元素
59. #在这个样例中,这些元素就是损失函数的不同部分,将它们加起来就可以得到最终的损失函数
60. loss = tf.add_n(tf.get_collection('losses'))