一、深度学习 vs 深层神经网络
深度学习:一类通过多层非线性变换对高复杂性数据建模算法的集合。
1、激活函数
线性模型的最大特点:任意线性模型的组合仍是线性模型,能够解决的问题也是有限的,这就是线性模型最大的局限。
==》解决方法:激活函数实现去线性化
如果将前面提到的加权和节点后(注意:添加偏置项)通过一个非线性函数,那么神经网络模型就是非线性的了。这个非线性函数就是激活函数(activation)。
常用激活函数:
- ReLU函数——
tf.nn.relu
- sigmoid函数——
tf.nn.sigmoid
- tanh函数——
tf.nn.tanh
使用激活函数和偏置项的前向传播。
# 前向传播
a = tf.nn.relu(tf.matmul(x, w1) + biases1)
y = tf.nn.relu(tf.matmul(a, w2) + biases2)
2、多层网络解决异或运算
二、损失函数
1、经典损失函数
(1)分类问题
交叉熵:两个概率分布之间的距离,广泛用于分类问题中。交叉熵是信息论中概念,原本用于估算平均编码的长度。
公式:
对于给定两个概率分布
p
p
p 和
q
q
q,通过
q
q
q 来表示
p
p
p 的交叉熵为:
H
(
p
,
q
)
=
−
∑
x
p
(
x
)
l
o
g
q
(
x
)
H(p,q)=-\sum_{x}p(x)log\ q(x)
H(p,q)=−x∑p(x)log q(x)
注意:交叉熵不是对称的,
H
(
p
,
q
)
≠
H
(
q
,
p
)
H(p,q)\neq H(q,p)
H(p,q)̸=H(q,p),交叉熵刻画通过通过概率分布
q
q
q 来表示
p
p
p 的困难程度。所以,
q
q
q 表示预测结果,
p
p
p 表示ground-truth。
交叉熵刻画的是两个概率分布的距离,然而神经网络的输出不一定是一个概率分布(任意事件发生的概率都在0和1之间,且概率总和为1)。Softmax回归是一个常用的将前向传播结果变成概率分布的方法。
Softmax回归本身可以作为一个学习算法来优化分类结果,但在tf中,Softmax回归的参数被去掉了,只作为一个额外的处理层,将输出变成一个概率分布。
s
o
f
t
m
a
x
(
y
)
i
=
y
i
′
=
e
y
i
∑
j
=
1
n
e
y
i
softmax(y)_i=y_i^\prime=\frac{e^{yi}}{\sum_{j=1}^{n}e^{yi}}
softmax(y)i=yi′=∑j=1neyieyi
交叉熵代码实现
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
因为交叉熵一般与Softmax回归一起使用,tf提供了 tf.nn.softmax_cross_entropy_with_logits
函数:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
(2)回归问题
回归问题目标:具体数值的预测问题。
均方误差(MSE, mean squared error)
公式:
M
S
E
(
y
,
y
′
)
=
∑
i
=
1
n
(
y
i
−
y
i
′
)
2
n
MSE(y, y')=\frac{\sum_{i=1}^{n}(y_i-y_i')^2}{n}
MSE(y,y′)=n∑i=1n(yi−yi′)2
其中,
y
i
y_i
yi 为一个 batch 中第 i 个数据的正确答案,
y
i
′
y_i'
yi′ 为预测值
tf 实现:
mse = tf.reduce_mean(tf.square(y_ - y))
2、自定义损失函数
为了让神经网络优化的结果更加接近实际问题,我们需要自定义损失函数。
比如商品销量问题,如果预测值较大(大于真实销量),商家损失生产商品的成本。如果预测值较小,损失商品的利润。因为一般成本和利润不相同,使用均方误差不能够很好地最大化利润。比如成本是1元,利润是10元,那么模型应该偏向预测值较大。下面的公式给出了预测值多于或少于真实值时有不同系数的损失函数,通过这个损失函数,模型可能最大化收益。
L
o
s
s
(
y
,
y
′
)
=
∑
i
=
1
n
f
(
y
i
,
y
i
′
)
,
f
(
x
,
y
)
=
{
a
(
x
−
y
)
,
if
x
>
y
b
(
y
−
x
)
,
if
x
≤
y
Loss(y,y')=\sum_{i=1}^{n}f(y_i,y_i'),f(x,y)=\begin{cases} a(x-y), & \text{if $x>y$} \\ b(y-x), & \text{if $x\leq y$ } \end{cases}
Loss(y,y′)=i=1∑nf(yi,yi′),f(x,y)={a(x−y),b(y−x),if x>yif x≤y
注意:损失函数定义的是损失,所以要将利润最大化,定义的损失函数应该刻画成本或代价。
tf 实现
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_mean(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)
dataset_size = 128
X = rdm.rand(dataset_size, 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) % dataset_size
end = min(start + batch_size, dataset_size)
sess.run(train_step, feed_dict={x: X[start: end], y_: Y[start: end]})
print(sess.run(w1))
输出
2019-03-09 15:21:02.100189: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-03-09 15:21:02.193850: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:897] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-03-09 15:21:02.194763: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1405] Found device 0 with properties:
name: GeForce GTX 1060 major: 6 minor: 1 memoryClockRate(GHz): 1.6705
pciBusID: 0000:01:00.0
totalMemory: 5.94GiB freeMemory: 5.37GiB
2019-03-09 15:21:02.194798: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1484] Adding visible gpu devices: 0
2019-03-09 15:21:02.422253: I tensorflow/core/common_runtime/gpu/gpu_device.cc:965] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-03-09 15:21:02.422279: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0
2019-03-09 15:21:02.422285: I tensorflow/core/common_runtime/gpu/gpu_device.cc:984] 0: N
2019-03-09 15:21:02.422603: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1097] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 5139 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1060, pci bus id: 0000:01:00.0, compute capability: 6.1)
[[1.0193471]
[1.0428091]]
三、神经网络优化算法
关键:通过反向传播算法(backpropagation)和梯度下降算法(Gradient Decent)调整神经网络中的参数。梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法。
1、优化过程
- 前向传播阶段:通过前向传播算法计算得到预测值,并计算损失函数。
- 反向传播阶段:计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。更新公式如下:
θ n + 1 = θ n − η ∂ ∂ θ n J ( θ n ) \theta_{n+1}=\theta_n-\eta\frac{\partial}{\partial\theta_n}J(\theta_n) θn+1=θn−η∂θn∂J(θn)
其中, θ \theta θ 表示需要更新的参数, ∂ \partial ∂ 表示学习率, J ( θ n ) J(\theta_n) J(θn) 表示损失函数值
2、几个概念
- 学习率
- 局部最优
- 随机梯度下降
- mini-batch梯度下降:不会比单个数据慢太多,同时可以大大减小收敛所需的迭代次数,使得收敛到的结果更加接近梯度下降的效果。
3、神经网络训练模式
import tensorflow as tf
batch_size = n
x = tf.placeholder(tf.float32, shape=(None, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
# 定义损失函数和优化算法
loss = ...
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
# 训练神经网络
with tf.Session() as sess:
# 初始化参数
...
# 迭代参数更新
for i in range(STEPS):
current_X, current_Y = ...
sess.run(train_step, feed_dict={x: current_X, y_: current_Y})
四、进一步优化
1、学习率设置
通过指数缩减的方法设置梯度下降算法中的学习率,在 tf 中使用 tf.train.exponential_decay
函数实现。先使用较大的学习率快速得到一个比较优的解,随着迭代的继续逐步减少学习率,使得模型在训练后期更加稳定。它实现了以下代码的功能:
decayed_learning_rate = learning_rate * decay_rate * decay_rate ^ (global_step / decay_steps)
其中,decayed_learning_rate
为每一轮优化时使用的学习率,learning_rate
为设定的初始学习率,decay_rate
为衰减系数,decay_steps
为衰减速度。
tf.train.exponential_decay
可以通过设置参数staircase
选择不同的衰减方式,默认为 False
,此时学习率衰减趋势如下图灰色曲线。当设置为 True
时,global_step/decay_steps
会被转化成整数,使得学习率成为一个阶梯函数,如黑色曲线。在这样的设置下,decay_steps
通常代表完整使用一遍训练数据所需要的迭代轮数,也就是总训练数据/一个batch的样本数。这样可以使得总训练数据中的每一个batch都使用相同的学习率,对模型产生相等的作用。当完整过完一遍训练数据时,学习率就减少一次。
tf 实现
import tensorflow as tf
global_step = tf.Variable(0)
# 通过exponential_decay函数生成学习率
learning_rate = tf.train.exponential_decay(0.1, grobal_step, 100, 0.96, staircase=True)
# 使用指数衰减的学习率。在minimize中传入global_step参数,从而更新学习率
learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
2、过拟合问题
(1)正则化
正则化的思想是在损失函数中加入刻画模型复杂程度的指标。所以一般优化的损失函数为 J ( θ ) + λ R ( w ) J(\theta)+\lambda R(w) J(θ)+λR(w)。一般来说模型复杂度只由权重w决定,和偏置b无关。
常用正则化:
- L1正则化
R ( w ) = ∣ ∣ w ∣ ∣ 1 = ∑ i ∣ w i ∣ R(w)=||w||_1=\sum_{i}|w_i| R(w)=∣∣w∣∣1=i∑∣wi∣ - L2正则化
R ( w ) = ∣ ∣ w ∣ ∣ 2 = ∑ i ∣ w i 2 ∣ R(w)=||w||_2=\sum_{i}|w_i^2| R(w)=∣∣w∣∣2=i∑∣wi2∣ - 同时使用L1正则化和L2正则化
R ( w ) = ∣ ∣ w ∣ ∣ 2 = ∑ i α ∣ w i ∣ + ( 2 − α ) w i 2 R(w)=||w||_2=\sum_{i}\alpha|w_i|+(2-\alpha)w_i^2 R(w)=∣∣w∣∣2=i∑α∣wi∣+(2−α)wi2
L1正则化
会让参数变得更稀疏,可达到类似特征选择功能;而 L2正则
不会。其次,L1正则
的计算公式不可导,L2正则
可导。因为优化时需要计算损失函数的偏导数,所以对 L2正则
损失函数的优化要更加简洁。优化 L1正则
更加复杂,而且优化方法也有很多种。
TF 实现
带 L2 正则项的损失函数
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1)
y = tf.matmul(x, w)
# 损失函数包括:均方误差损失函数 + 正则化项
## lambda参数表示正则化的权重,w为需要计算正则化损失的参数
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l2_regularizer(lambda)(w)
tf.contrib.layers.l2_regularizer
:计算给定参数的L2正则化项的值;tf.contrib.layers.l1_regularizer
:计算L1正则化项的值。
weights = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
with tf.Session() as sess:
# 输出为(|1|+|-2|+|3|+|-4|)*0.5=5. 其中0.5为正则项的权重
print(sess.run(tf.contrib.layers.l1_regularizer(0.5)(weights))
# 输出为(1^2+(-2)^2+3^2+(-4)^2)/2*0.5=7.5。 其中0.5为正则项的权重
print(sess.run(tf.contrib.layers.l2_regularizer(0.5)(weights))
当网络结构复杂后,利用集合(collection)计算损失函数。eg:计算一个 5 层神经网络带 L2 正则化的损失函数计算方法。
import tensorflow as tf
# 获取一层神经网络边上的权重,并将这个权重的L2正则化损失加入“losses”的集合中
def get_weight(shape, lambda1):
# 生成一个变量
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
# add_to_collection函数将这个新生成变量的L2正则化损失项加入集合
# 第一个参数:losses是集合的名字,第二个参数:要加入集合的内容
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))
batch_size = 8
# 定义每一层网络中节点的个数
layer_dimension = [2, 10, 10, 10, 1]
# 定义网络的层数
n_layers = len(layer_dimension)
# 这个变量维护前向传播时最深层的节点,开始的时候就是输入层
cur_layer = x
# 当前层的节点个数
in_dimension = layer_dimension[0]
# 通过一个循环来生成5层的FC层
for i in range(1, n_layers):
# layer_dimension[i] 为下一层的节点个数
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 activation function
cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
# 进入下一层之前将下一层的节点个数更新为当前层节点的个数
in_dimension = layer_dimension[i]
# 定义前向传播的同时已经将所有的L2正则化损失加入图上的集合
# 这里只需要计算刻画模型在训练数据上表现的损失函数
mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))
# 将均方误差损失加入到损失集合
tf.add_to_collection('losses', mse_loss)
# get_collection 返回一个列表,该列表是所有这个集合中的元素。
# 在样例中,这些元素就是损失函数的不同部分,将它们加起来就可以得到最终的损失函数
loss = tf.add_n(tf.get_collection('losses'))
3、滑动平均模型
作用:在使用随机梯度下降算法训练网络时,可以使模型在测试数据上更健壮(robust)。
tf 的实现:tf.train.ExponentialMovingAverage
参数:
-
衰减率(
decay
):该参数用于控制模型更新的速度。ExponentialMovingAverage
对每一个变量会维护一个影子变量(shadow_variable
),这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:
s h a d o w _ v a r i a b l e = d e c a y × s h a d o w _ v a r i a b l e + ( 1 − d e c a y ) × v a r i a b l e shadow\_variable=decay\times shadow\_variable + (1-decay)\times variable shadow_variable=decay×shadow_variable+(1−decay)×variable
其中,shadow_variable
为影子变量,variable
为待更新的变量,decay
为衰减率,衰减率越大模型越稳定。实际中,decay
一般会设置非常接近1(eg: 0.999或0.9999)。 -
num_updates
参数:为了在训练的前期更新得更快,通过设置num_updates
参数来动态设置decay
的大小。若初始化时提供num_updates
参数,则衰减率为:
m i n { d e c a y , 1 + n u m _ u p d a t e s 10 + 1 + n u m _ u p d a t e s } min\{decay,\frac{1+num\_updates}{10+1+num\_updates}\} min{decay,10+1+num_updates1+num_updates}
TF实现
import tensorflow as tf
# 定义一个变量用于计算滑动平均,初始值为实数型的0
v1 = tf.Variable(0, dtype=tf.float32)
# step 变量模拟神经网络中迭代的轮数,可以用于控制衰减率
step = tf.Variable(0, trainable = False)
# 定义一个滑动平均的类。初始化:衰减率(0.99)和控制衰减率的变量 step
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)
# 通过ema.average(v1)获取滑动平均之后变量的取值。
# 在初始化之后变量v1的值和v1的滑动平均都是0
print(sess.run([v1, ema.average(v1)]))
# output: [0.0, 0.0]
# 更新变量v1的值到5
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)]))
# output: [5.0, 4.5]
# 更新变量step的值到10000
sess.run(tf.assign(step, 10000))
# 更新变量v1的值到10
sess.run(tf.assign(v1, 10))
# 更新v1的滑动平均值。衰减率为min{0.99, (1+step)/(10+step)}=0.99,
# 所以v1的滑动平均被更新为0.99*4.5+0.01*10=4.555
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# output: [10.0, 4.5549998]
# 再次更新滑动平均值,得到的新滑动平均值为0.99*4.555+0.01*10 = 4.60945
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# Output: [10.0, 4.6094499]
输出结果
2019-03-13 22:50:23.313337: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-03-13 22:50:23.444235: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:897] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-03-13 22:50:23.445227: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1405] Found device 0 with properties:
name: GeForce GTX 1060 major: 6 minor: 1 memoryClockRate(GHz): 1.6705
pciBusID: 0000:01:00.0
totalMemory: 5.94GiB freeMemory: 5.38GiB
2019-03-13 22:50:23.445260: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1484] Adding visible gpu devices: 0
2019-03-13 22:50:24.216058: I tensorflow/core/common_runtime/gpu/gpu_device.cc:965] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-03-13 22:50:24.216088: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0
2019-03-13 22:50:24.216096: I tensorflow/core/common_runtime/gpu/gpu_device.cc:984] 0: N
2019-03-13 22:50:24.216673: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1097] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 5150 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1060, pci bus id: 0000:01:00.0, compute capability: 6.1)
[0.0, 0.0]
[5.0, 4.5]
[10.0, 4.555]
[10.0, 4.60945]
[Finished in 4.9s]