深度学习和深层神经网络
深度学习:一种通过多层非线性变换对高复杂性数据建模算法的合集
特点: 多层和非线性
激活函数和偏置项
$$
A_1 = [a_{11}, a_{12}, a_{12}] = f(xW^{(1)} + b)
$$
新的公式增加了偏置项(bias), 并且每个节点取值不再是单纯的加权和, 每个节点的输出在加权和的基础上还做了一个非线性变换
常见的几种非线性激活函数的函数图像:


ReLU函数: $f(x)= max(x, 0)$
sigmoid函数: $f(x) = \frac{1}{1+e^{-x}}$
tanh函数: $f(x) = \frac{1-e^{-2x}}{1+e^{-2x}}$
多层网络
感知机可以简单理解为单层神经网络, 无法模拟亦或运算, 加入隐藏层后, 亦或问题就可以很好的解决.
损失函数
交叉熵
通过神经网络解决多分类问题最常用的方法是设置n个输出节点, 其中n为类别的个数. 对于每个样例, 神经网络可以得到一个n维的数组作为输出结果. 数组每个元素属于一个类别. 在理想情况下, k类别的样本对应类别的输出节点为1
, 其它都为0
交叉熵可以用来刻画两个概率分布的距离
给定两个概率分布p和q, 通过q来表示p的交叉熵为: $$ H(p, q) = -\sum_{x}p(x)\log{q(x)} $$ 它刻画的是通过概率分布q来表达概率分布p的困难程度
Softmax回归
Softmax回归可以将神经网络的输出变成一个概率分布 $$ softmax(y) _i = y’ _i = \frac{e^{yi}}{\sum^{n} _{j=1}e^{yj}} $$
代码实现
cross_entropy = -tf.reduce_mean(
y_*tf.log(tf.clip_by_value(y, 1e-10, 1.0))
)
tf.clip_by_value()
可以将一个张量中的数值限制在一个范围, 超出这个范围的将被替换为最近的边界值
将两个矩阵通过*
相乘不是矩阵乘法, 而是元素之间一一直接相乘
因为交叉熵通常会和softmax一起使用, 所以tf对这两个功能提供了统一封装函数tf.nn.softmax_cross_entropy_with_logits()
(已经废弃, 使用cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2()
代替
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)
# y代表神经网络输出结果, y_代表标准答案
# 如果只有一个正确答案, 可以使用sparse开头的函数加快过程
tf.nn.sparse_softmax_cross_entropy_with_logits()
均方误差(MSE)
回归问题需要解决的是对具体数值的预测, 解决回归问题的神经网络一般只有一个输出节点(预测值), 这类问题常用的损失函数是均方误差(MSE, mean squared error)(均方误差也是分类问题的常用损失函数)
公式: $$ MSE(y, y’) = \frac{\sum^{n}_{i-1}{(y_i-y_i’)}^2}{n} $$ 代码:
mse = tf.reduce_mean(tf.square(y_-y))
自定义损失函数
可以自定义损失函数, 使结果更接近实际问题的需求, 比如使得神经网络更偏向向偏多的方向, 可以给偏多和偏少不同的损失系数
tf.select(tf.greater(v1, v2), (v1-v2)*a, (v2-v1)*b)
tf.select()
类似c中的三目运算符
神经网络优化算法
[反向传播算法解析](<https://www.cnblogs.com/charlotte77/p/5629865.html)
各种优化算法:https://zhuanlan.zhihu.com/p/27449596
https://juejin.im/entry/5b922cddf265da0aa664a02e
梯度下降算法(gradient decent)主要用于优化单个参数的取值, 而反向传播算法(backpropagation)给出了一个高效的方式在所有参数啥时候使用梯度下降算法.
反向传播算法是训练神经网络的核心算法, 它可以根据定义好的损失函数优化神经网络中的参数的取值, 从而使神经网络模型在训练集上的损失函数达到一个较小值
梯度下降算法
假设用$\theta$表示神经网络中的参数, $J(\theta)$表示在给定参数取值下, 训练数据集上损失函数的大小, 整个优化过程可以抽象为寻找一个$\theta$, 使得$J(\theta)$最小
目前并没有通用的方法可以对任意损失函数求解最佳值, 实践中, 梯度下降算法是最常用的神经网络优化算法.


参数的梯度通过求偏导的方式计算, 对于参数$\theta$, 其梯度为$\frac{\delta}{\delta \theta}J(\theta)$
学习率$\eta$可以认为是每次参数移动的幅度
参数更新的公式为: $$ \theta_{n+1} = \theta_n – \eta \frac{\delta}{\delta \theta}J(\theta_n) $$ 缺点:
- 梯度下降算法并不能保证被优化的函数达到全局最优解, 只有当损失函数为凸函数的时候才能保证达到全局最优解, 参数的初始值会很大程度影响最后的结果
- 计算时间太长, 需要计算每一轮迭代中全部训练数据上的损失函数
随机梯度下降算法
每一轮迭代中, 只随机优化某一条数据上的损失函数
速度加快但是问题也很明显, 某一条数据可能无法代表全部数据, 于是随机算法优化得到的参数甚至无法都达到局部最优
折中
实际上一般采用上面两种算法的折中, 每次计算一小部分训练数据的损失函数
各种优化算法:https://zhuanlan.zhihu.com/p/27449596
https://juejin.im/entry/5b922cddf265da0aa664a02e
进一步优化
学习率的设置 – 指数衰减法
学习率(learnig rate)用于控制参数更新的速度, 决定了参数每次更新的幅度
过小会导致优化速度, 过大会导致无法收敛
为此TF提供了一种灵活的学习率设置方法, 指数衰减法:
tf.train.exponential_decay(
learning_rate=0.1, global_step=global_step,
decay_steps=100, decay_rate=0.96, staircase=True)
函数实现了以下代码的功能:
decayed_learning_rate =
learning_rate * decay_rate ^ (global_step / decay_steps)
# 相当于每decay_steps轮迭代学习率*decay_rate
其中learning_rate
为初始学习率, decay_rate
为衰减系数, decay_steps
为衰减速度(通常为完整使用一边训练数据所需要的迭代轮数, 也就是dataset_size/batch_size
), global_step
为当前迭代轮数
staircase
=True
时, global_step / decay_steps
会被转换成整数, 也就是说只有迭代完一轮全部数据后, 学习率才会变化, 使得数据集中所有数据对模型训练有相同的作用(不然后面的数据影响能力更小)
过拟合问题 – 正则化
模型在训练数据上的表现并不一定代表它在未知数据上的表现
过拟合指的是, 当一个模型过于复杂后, 它可以很好的"记忆"每一个训练数据中随机噪声的的部分而没有很好的"学习"训练数据中通用的趋势


为了避免过拟合问题, 一个常用的方法是正则化(regularization), 其思想是在损失函数中增加刻画模型复杂度的指标
损失函数为$J(\theta)$, $R(w)$刻画模型复杂程度, 优化时不是直接优化$J(\theta)$而是优化$J(\theta) + \lambda R(w)$, $\lambda$表示模型复杂损失占总损失的权重, $\theta$表示神经网络中所有参数, 包括权重$w$和偏置项$b$, 一般模型复杂度只由权重$w$决定
常用的刻画模型复杂度的函数$R(w)$有两种, 一种是L1正则化: $$ R(w) = \|w\| _1 = \sum _{i}{|w _i|} $$
L2正则化: $$ R(w) = \|w\|^2 _2 = \sum _{i}{w _i^2} $$ (两竖加一个下表x代表向量的x-范数, 1-范数为所有元素绝对值之和, 2-范数代表所有元素平方和再开方)
两种正则化有很大的区别, 首先L1会让参数变得稀疏(更多参数变为0), 这样可以达到特征选取的功能, 而L2不会, 因为L2有一个平方, 但参数变得很小的时候, 它的平方会变得更小而基本可以忽略, 于是模型不会将其进一步调整为0
其次L1计算公式不可导, 而L2可导, 优化是需要计算损失函数的偏导, 所以对L2优化要更加简洁
在实践中, 也可以将L1和L2正则化同时使用: $$ R(w) = \sum_{i}\alpha|w_i|+(1-\alpha)w_i^2 $$ 代码:
loss = tf.reduce_mean(tf.square(y_-y)) + tf.contrib.layers.l2_regularizer(0.5)(w)
当神经网络参数增多后, 这样的定义方式会很麻烦, 为此可以利用TF中提供的集合(collection), 将损失函数所有项都放入集合(tf.add_to_colletion
), 最后使用tf.add_n
获取所有项的和
import tensorflow as tf
# 将权重的L2正则化损失加入名为'losses'的collection中
def get_weight(shape, lam):
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lam)(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]
# 当前层权重变量 并将其L2正则化损失加入集合
weight = get_weight([in_dimension, out_dimension], 0.001)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
# 使用ReLU激活函数
cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
in_dimension = layer_dimension[i]
# mse均方差, 刻画模型在训练数据上的损失函数
mse_loss = tf.reduce_mean(tf.square(y_-cur_layer))
# 将均方差损失函数也加入losses
tf.add_to_collection('losses', mse_loss)
# get_collection返回一个列表包含集合所有元素, 在这里就是损失函数的所有项
loss = tf.add_n(tf.get_collection('losses'))
滑动平均模型
理解滑动平均(exponential moving average)
滑动平均模型可以使模型在测试数据上更健壮
tf中提供了tf.train.ExponentialMovingAverage
来实现滑动平均模型, 初始化ema
(ExponentialMovingAverage
)时需要提供一个衰减率(decay
)用于控制模型更新速率, ema
对每一个变量会维护一个影子变量(shadow_variable
), 其初始值是相应变量的初始值, 每次变量更新时, 影子变量更新为:
shadow_variable = decay * shadow_variable + (1-decay)*variable
影子变量可以当作变量variable
的最近$\frac{1}{1-decay}$次取值的平均值, 例如decay=0.99, 那么影子变量就相当于变量的最近100次取值的均值
测试时, 使用影子变量代替原本的变量可以使模型更健壮, 因为影子变量更新更平滑, 最后的$\frac{1}{1-decay}$训练中, 模型训练早已完成, 处于抖动阶段, 影子变量相当于最后那几次抖动的平均
ema
还提供了num_updates
参数来动态设置decay
大小, 使用此参数后, 每次使用的衰减率为:
$$
\min{\left\{
decay, \frac{1+num\_updates}{10+num\_updates}
\right\}}
$$
代码:
# 测试ema
import tensorflow as tf
# 定义了一个变量用于计算滑动平均
v1 = tf.Variable(0, dtype=tf.float32)
# step模拟迭代轮数, 用于动态控制衰减率
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(decay=0.99, num_updates=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为5
sess.run(tf.assign(v1, 5))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 更新step为10000
sess.run(tf.assign(step, 10000))
# 更新v1为10
sess.run(tf.assign(v1, 10))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))