一 深度学习与深层神经网络
深度学习有两个非常重要的特性:多层和非线性。
1 线性模型的局限性
线性模型的最大特点是任意线性模型的组合仍然还是线性模型。
只通过线性变换,任意层的全连接神经网络和单层的神经网络模型的表达能力没有任何区别,他们都是线性模型。
如果一个问题可以通过一条直线来划分,那么线性模型就可以用来解决这个问题。
2 激活函数
激活函数的主要作用就是去线性化。
3 多层网络
深层神经网络实际上有组合特征提取的功能,这个特性对于解决不易提取特征向量的问题(比如图片识别、语音识别等)有很大帮助。
二 损失函数
1 经典损失函数
(1) 交叉熵(cross entropy)
可以参考这篇博客帮助理解。
交叉熵刻画了两个概率分布之间的距离。给定两个概率分布\(p\)和\(q\),通过\(q\)来表示\(p\)的交叉熵为:
$$H(p, q)=-\sum_{x} p(x) \log q(x)$$
任意事件发生的概率都在0到1之间,且总有某一件事情发生。
交叉熵的公式不是对称的,它刻画的是通过概率分布\(q\)来表达概率分布\(p\)的困难程度。
$$H(p, q) \neq H(q, p)$$
(2) softmax
$$\operatorname{softmax}(y)_{i}=y_{i}^{\prime}=\frac{e^{y i}}{\sum_{j=1}^{n} e^{y j}}$$
softmax为归一化指数函数。
(3) tensorflow一些函数的用法
- tf.clip_by_value() 函数可以将一个张量中的数值限制在一个范围内,避免出现\(log0\)的错误。
import tensorflow as tf
sess = tf.InteractiveSession()
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())
sess.close()
# 输出为[[2.5 2.5 3.] [4. 4.5 4.5]]
- tf.log() 函数完成了对张量中所有元素依次求对数的功能。
import tensorflow as tf
sess = tf.InteractiveSession()
v = tf.constant([1.0, 2.0, 3.0])
print(tf.log(v).eval())
sess.close()
#输出为 [0. 0.6931472 1.0986123]
- tf.reduce_mean() 函数可以直接对整个矩阵做平均。
import tensorflow as tf
sess = tf.InteractiveSession()
v = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print(tf.reduce_mean(v).eval())
sess.close()
#输出 3.5
- 直接使用softmax回归之后的交叉熵损失函数:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(lables=y_, logits=y)
y为神经网络的输出结果,y_ 为标准答案。
(4) 回归问题常用损失函数:均方误差(MSE, mean aquared error)
$$
\operatorname{MSE}\left(y, y^{\prime}\right)=\frac{\sum_{i=1}^{n}\left(y_{i}-y_{i}^{\prime}\right)^{2}}{n}
$$
mse = tf.reduce_mean(tf.square(y_ - y))
2 自定义损失函数
$$
\operatorname{Loss}\left(\mathrm{y}, \mathrm{y}^{\prime}\right)=\sum_{i=1}^{n} f\left(y_{i}, y_{i}^{\prime}\right), \quad f(x, y)=\left\{\begin{array}{ll}{a(x-y)} & {x>y} \\ {b(y-x)} & {x \leqslant y}\end{array}\right.
$$
loss = tf.reduce_sum(tf.where(tf.greater(v1, v2), (v1 - v2) * a, (v2 - v1) * b))
tf.greater() 的输入是两个张量,此函数会比较这两个输入张量中每一个元素的大小,并返回比较结果。
tf.where() 函数有三个参数,第一个为条件选择依据,当选择条件为True时,tf.where() 函数会选择第二个参数中的值,否则使用第三个参数中的值。有点类似逗号表达式。
三 神经网络优化算法
梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法。
神经网络的优化过程可以分为两个阶段:
-
使用前向传播算法计算得到的预测值,并将预测值和真实值做对比得出两者之间的差距;
-
通过反向传播算法计算损失函数对每一个参数的梯度,再通过梯度和学习率使用梯度下降算法跟新每一个参数。
梯度下降算法并不能保证被优化的函数达到全局最优,只有当损失函数为凸函数时,梯度下降算法才能达到全局最优解。梯度下降算法的另一个问题是计算时间太长,因为每次要在全部训练数据上最小化损失。为了加速训练过程,可以使用随机梯度下降算法(stochastic gradient descent),在每一轮迭代中,随机优化某一条训练数据上的损失函数,它的问题也很明显,可能无法达到局部最优。
为了综合梯度下降算法和随机梯度下降算法的优缺点,在实际中每次计算一小部分训练数据的损失函数,这一小部分数据被称之为一个batch。
四 神经网络的进一步优化
1 学习率设置
学习率决定了参数每次跟新的幅度。
指数衰减法:先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率。
decayed_learning_rate = learning_rate * decay_rate ^ (golbal_step / decay_steps)
learning_rate = tf.train.exponential_decay(0.1, golbal_step, 100, 0.96, staircase=True)
# 初始学习率为0.1,每训练100轮后学习率乘以0.96
2 过拟合问题
所谓过拟合,是指当一个模型过为复杂之后,它可以很好的“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。
为了避免过拟合问题,一个非常有用的方法是正则化(regularization)。在损失函数中加入刻画模型复杂程度的指标,优化\(J(\theta)+\lambda R(w)\) 。其中\(R(w)\)刻画的是模型的复杂程度,而\(\lambda\)表示模型复杂损失在总损失中的比例。这里\(\theta\)表示的是神经网络中的所有参数,它包括边上的权重\(w\)和偏置项\(b\)。一般来说模型的复杂程度只有权重\(w\)来决定。
L1正则化:
$$
R(w)=\|w\|_{1}=\sum_{i}\left|w_{i}\right|
$$
L2正则化:
$$
R(w)=\|w\|_{2}^{2}=\sum_{i}\left|w_{i}^{2}\right|
$$
正则化方式的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。
L1正则话会让参数变得稀疏,会使得很多参数变为0,L2则不会。
L1正则化的计算公式不可导,而L2正则化公式可导。
或者L1与L2可以同时使用:
$$
R(w)=\sum_{i} \alpha\left|w_{i}\right|+(1-\alpha) w_{i}^{2}
$$
import tensorflow as tf
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
weights = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
with tf.Session() as sess:
print(sess.run(tf.contrib.layers.l1_regularizer(.5)(weights)))
print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))
上面为Tensorflow正则化函数,输出为:
(tensorflow会将L2正则化损失值除以2使得求导得到的结果更加简洁。)
3 滑动平均模型
在Tensorflow中提供了 tf.train.ExponentialMovingAverage() 来实现滑动平均模型。
初始化ExponentialMovingAverage时,需要提供一个衰减率(decay),这个衰减率将用于控制模型跟新的速度。
ExponentialMovingAverage对每一个变量会维护一个影子变量(shadow varialbe), 这个影子变量的初始值就是相应变量的初始值,每次跟新时:
$$
\text { shadow }_{-} \text {variable }=\text { decay } \times \text { shadow }_{-} \text {variable }+(1-\text { decay }) \times \text { variable }
$$
shadow_variable为影子变量,variable为待跟新的变量,decay为衰减率。我个人理解这个公式的意思就是在shadow_variable和varialbe中间取一个值来跟新shadow_variable。
为了使模型在训练前期可以跟新得更快,每次使用的衰减率为:
$$
\min \left\{\operatorname{decay}, \frac{1+\operatorname{num}_{-} \text {updates }}{10+\operatorname{num}_{-} \text {updates }}\right\}
$$
###滑动平均模型的例子
import tensorflow as tf
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
#定义一个变量用于计算滑动平均,这个变量必须为实数型
v1 = tf.Variable(0, dtype=tf.float32)
#定义神经网络中迭代的轮数,这个数值较低时训练前期可以跟新更快,当超过一定数值后,decay会会使用初始给定值。
step = tf.Variable(0, trainable=False)
#定义滑动平均的类
ema = tf.train.ExponentialMovingAverage(0.99, step)
#定义了一个跟新滑动平均的操作,每次执行这个操作时,这个列表中的变量都会被跟新
maintain_average_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)]))
# 输出为 [0.0, 0.0]
# 将v1的值变更到5
sess.run(tf.assign(v1, 5))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
# v1的滑动平均会被更新为 0.1 * 0 + 0.9 × 5 = 4.5
#输出为 [5.0, 4.5]
#变更v1和step的值
sess.run(tf.assign(step, 1000))
sess.run(tf.assign(v1, 10))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
# v1的滑动平均被跟新为 0.99 × 4.5 + 0.1 × 10 = 4.555
# 输出为 [10.0, 4.555]
# 再次跟新滑动平均值
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
# v1的滑动平均被跟新为 0.99 × 4.555 + 0.01 × 10 = 4.609449999999
# 输出为 [10.0, 4.60945]
总结: