目录:
1. 什么是深度学习
2. 损失函数
3. 梯度下降算法
4. 学习率
5. 滑动平均模型
6. 过拟合及解决方法
正文:
什么是深度学习(深层神经网络)
概念
深度学习:一类通过多层 、非线性变换对高复杂性数据建模算法的合集。-
线性模型的局限性
- 线性模型为输出的加权和,线性模型的组合仍为线性;
- 线性模型只可以解决线性可分问题,绝大多数实际问题是无法通过线性分割的。
- 导致20世纪70年代神经网络研究的重大低潮原因:感知机无法解决异或问题(简单理解为什么感知机不能解决异或问题)
-
解决线性局限性的方法:激活函数去线性化
3.1. 如果将一个神经元的输出通过一个非线性函数,整个神经网络的模型就不再是线性的,这个非线性函数就是激活函数。下图是加入偏置项(bias)和激活函数的神经元示意图。
3.2. 常见的激活函数
- ReLU函数: f(x) = max(x,0)
- sigmoid函数:
- tanh函数:
-
隐藏层
每通过一次隐藏层会经过一次特征抽取,逐层抽取更高维度的特征。这可以很好的解决异或问题。下图摘自《TensorFlow实战Google深度学习框架》。神经网络的层数概念:隐藏层 + 输出层,输入层为第0层,不计在内。
损失函数
-
经典损失函数
1.1. 交叉熵
交叉熵的理解请参考:如何通俗的解释交叉熵-CyberRep的回答#交叉熵TensorFlow实现 cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
-
y_
代表正确结果,y
代表预测结果; -
tf.clip_by_value()
函数将张量的数值限定在一个范围内,可以避免如log0
的运算错误; -
tf.log()
函数实现了对张量中所有元素依次求对数的功能; -
交叉熵刻画的是两个概率分布之间的距离,然而神经网络的输出不一定是一个概率分布。
Softmax回归
可以解决这个问题。在TensorFlow中,他只是一个额外的处理层,假设原始输出为y1, y2, … , yn, 经过Softmax回归处理后输出为:
因为交叉熵一般会与softmax回归一起使用,TensorFlow对他们进行了封装:# TensorFlow实现 cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_) # 只有一个正确答案的问题中,使用如下语句加速处理: cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(y, y_)
1.2. 均方误差
预测值和真值差值的平方和的平均数。#均方误差TensorFlow实现 mse = tf.reduce_mean(tf.square(y - y_))
-
-
自定义损失函数
举例:如某商品,成本1元,利润10元,少预测一个就少挣10元,多预测一个就少挣1元。为了最大化利润,定义损失函数如下(y为真实值,y’为预测值):
TensorFlow实现:
loss = tf.reduce_sum(tf.select(tf.greater(v1, v2),(v1 - v2) * a, (v2 - v1) * b))
例程说明损失函数对模型训练结果的影响:
#coding:utf-8 import tensorflow as tf from numpy.random import RandomState #1. 生成模拟数据集 BATCH_SIZE = 8 rdm = RandomState(1) X = rdm.rand(128, 2) #设置回归的正确值为两个输入的和加上一个随机量,随机量是为了加入不可预测的噪音。 #否则不同损失函数的意义就不大了,因为不同损失函数都在能完全预测正确的时候最低。 #一般来说噪音为一个均值为0的小量。 Y = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in X] #2. 定义神经网络的相关参数和变量 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) #3. 定义损失函数 #定义损失函数使得预测少了的损失大,于是模型应该偏向多的方向预测。 LOSS_LESS = 10 LOSS_MORE = 1 loss = tf.reduce_sum(tf.where(tf.greater(y, y_), LOSS_MORE * (y - y_), LOSS_LESS * (y_ - y))) train_step = tf.train.AdamOptimizer(0.001).minimize(loss) #4. 训练模型 with tf.Session() as sess: init_op = tf.initialize_all_variables() 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]] """ #5. 重新定义损失函数,使得预测多了的损失大,于是模型应该偏向少的方向预测。 LOSS_LESS = 1 LOSS_MORE = 10 loss = tf.reduce_sum(tf.where(tf.greater(y, y_), LOSS_MORE * (y - y_), LOSS_LESS * (y_ - y))) train_step = tf.train.AdamOptimizer(0.001).minimize(loss) #输出结果 """ 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 ]] """ #6. 定义损失函数为MSE。 loss = tf.reduce_mean(tf.square(y_ - y)) train_step = tf.train.AdamOptimizer(0.001).minimize(loss) #输出结果 """ 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 ]] """
梯度下降算法
梯度下降算法主要用于优化单个参数的取值,参数更新的公式为:
为学习率,即每次参数更新的幅度;J(θ)为损失函数。
需要注意的是,梯度下降算法并不能够保证被优化的函数达到全局最优解,如下图所示:
由此可见,在训练神经网络时,参数的初值会很大程度影响最后得到的结果。只有当损失函数为凸函数时,梯度下降算法才能保证达到全局最优解。海量数据下,计算所有组训练数据非常耗时。
随机梯度下降算法:在每一轮迭代中,随即优化某一条训练数据上的损失函数。
实际应用中,采用梯度下降和随机梯度下降结合的方法:每次计算一个batch的训练数据的损失函数。
学习率的设置
学习率过小,会导致梯度衰减过慢;学习率过大,会导致参数在极优值两侧来回移动,不收敛。
未解决设定学习率问题,TensorFlow提供了指数衰减法,tf.train.exponential_decay
函数,会指数级减小学习率。通过此方法可以先使用较大的学习率快速得到较优的解,后期趋于稳定。
# TensorFlow实现
learning_rate = tf.train.exponentid_decay(state_rate, global_step, decay_steps, decay_rate, staircase)
其中,state_rate为初始学习率,decay_rate为衰减系数,global_step为运行了几轮,decay_steps为每轮的学习步数。staircase为bool值,默认为false,取true时会被转化为整数,学习率将以阶梯形式下降。
实例说明学习率设置对优化结果的影响:
#coding:utf-8
#假设我们要最小化函数 y=x^2, 选择初始点 x_0=5
#1. 学习率为1的时候,x在5和-5之间震荡。
import tensorflow as tf
TRAINING_STEPS = 10
LEARNING_RATE = 1.0
x = tf.Variable(tf.constant(5, dtype=tf.float32), name="x")
y = tf.square(x)
train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(y)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
for i in range(TRAINING_STEPS):
sess.run(train_step)
x_value = sess.run(x)
print("After %s iteration(s): x%s is %f."% (i+1, i+1, x_value))
# 打印结果
""" After 1 iteration(s): x1 is -5.000000. After 2 iteration(s): x2 is 5.000000. After 3 iteration(s): x3 is -5.000000. After 4 iteration(s): x4 is 5.000000. After 5 iteration(s): x5 is -5.000000. After 6 iteration(s): x6 is 5.000000. After 7 iteration(s): x7 is -5.000000. After 8 iteration(s): x8 is 5.000000. After 9 iteration(s): x9 is -5.000000. After 10 iteration(s): x10 is 5.000000. """
#coding:utf-8
#2. 学习率为0.001的时候,下降速度过慢,在901轮时才收敛到0.823355。
import tensorflow as tf
TRAINING_STEPS = 1000
LEARNING_RATE = 0.001
x = tf.Variable(tf.constant(5, dtype=tf.float32), name="x")
y = tf.square(x)
train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(y)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
for i in range(TRAINING_STEPS):
sess.run(train_step)
if i % 100 == 0:
x_value = sess.run(x)
print("After %s iteration(s): x%s is %f."% (i+1, i+1, x_value))
# 打印结果
""" After 1 iteration(s): x1 is 4.990000. After 101 iteration(s): x101 is 4.084646. After 201 iteration(s): x201 is 3.343555. After 301 iteration(s): x301 is 2.736923. After 401 iteration(s): x401 is 2.240355. After 501 iteration(s): x501 is 1.833880. After 601 iteration(s): x601 is 1.501153. After 701 iteration(s): x701 is 1.228794. After 801 iteration(s): x801 is 1.005850. After 901 iteration(s): x901 is 0.823355. """
#coding:utf-8
#3. 使用指数衰减的学习率,在迭代初期得到较高的下降速度,可以在较小的训练轮数下取得不错的收敛程度。
import tensorflow as tf
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_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(y, global_step=global_step)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
for i in range(TRAINING_STEPS):
sess.run(train_step)
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.
"""
滑动平均模型
滑动平均模型(ExponentialMovingAverage)可以使模型在测试数据上更加健壮(robust)。tensorflow 下的 tf.train.ExponentialMovingAverage 需要提供一个衰减率(decay),该衰减率用于控制模型更新的速度,ExponentialMovingAverage 对每一个(待更新训练学习的)变量(variable)都会维护一个影子变量(shadow variable)。影子变量的初始值就是这个变量的初始值,变量值会更新为:
shadow_variable=decay×shadow_variable+(1−decay)×variable
由上述公式,decay决定了模型的更新速度,decay越大,模型越趋于稳定。ExponentialMovingAverage 还提供了 num_updates 参数来动态设置 decay 的大小:
其中num_updates为神经网络中已经迭代的轮数。
下面以一个实例介绍滑动平均模型用法:
#coding:utf-8
import tensorflow as tf
#1. 定义变量及滑动平均类
# 定义一个32位浮点变量,初始值为0.0 这个代码就是不断更新v1参数,优化v1参数,滑动平均做了个v1的影子。
v1 = tf.Variable(0, dtype=tf.float32)
# 定义num_updates(模拟NN中迭代的迭代轮数),初始值为0,不可被优化(训练),这个参数不训练
steps = tf.Variable(0, trainable=False)
# 实例化滑动平均类(class),给删减率delay0.99,当前轮数steps
ema = tf.train.ExponentialMovingAverage(0.99, steps)
# 定义更新变量滑动平均的操作,这里需要给定一个列表,
# 每次运行刷新列表中的元素被更新,即每次sess.run(maintain_avaerages_op)更新v1。
ema_op = ema.apply([v1])
#2. 查看不同迭代中变量取值的变化。
with tf.Session() as sess:
# 初始化
init_op = tf.initialize_all_variables()
sess.run(init_op)
#ema.average(v1)获取滑动平均后的取值,初始值为0
print(sess.run([v1, ema.average(v1)]))
# 输出: [0.0, 0.0]
# 更新变量v1的值为5
sess.run(tf.assign(v1, 5))
sess.run(ema_op)
print(sess.run([v1, ema.average(v1)]))
# 输出: [5.0, 4.5]
# 更新模拟步数step的值为10000,更新v1值为10
sess.run(tf.assign(steps, 10000))
sess.run(tf.assign(v1, 10))
# 此时decay = min{0.99, (1 + step) / (10 + step) = 0.1} = 0.1
sess.run(ema_op)
print(sess.run([v1, ema.average(v1)]))
# 输出: [10.0, 4.5549998]
# 更新一次v1的滑动平均值
sess.run(ema_op)
print(sess.run([v1, ema.average(v1)]))
# 输出: [10.0, 4.6094499]
sess.run(ema_op)
print(sess.run([v1, ema.average(v1)]))
# 输出: [10.0, 4.6633554]
sess.run(ema_op)
print(sess.run([v1, ema.average(v1)]))
# 输出: [10.0, 4.7167215]
过拟合
过拟合:当一个模型过为复杂后,他可以很好的“记忆”每个训练数据中随机噪音的部分而忘记去“学习”训练数据中的通用趋势。如下图:
未解决此问题,常用正则化的方法:在损失函数中人为加入刻画模型复杂程度的指标。通过限制权重大小,使得模型不能任意拟合训练数据中的随机噪音。
J(θ) = J(θ) + λR(w)
其中R(w)刻画模型的复杂程度,λ表示模型复杂损失在总损失中的比例。
常用R(w)有两种:
-
L1正则化:
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l1_regularizer(lamda)(w)
-
L2正则化:
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l1_regularizer(lamda)(w)
两种正则化方式的区别:
- L1正则化会让参数变得更稀疏,即会有更多的参数变为0,可以达到类似特征筛选的目的。L2不会有此功能,因为当参数很小时,如0.01,此参数的平方基本上可以忽略,于是模型不会进一步将此参数调为0.
- L1正则化计算公式不可导,L2可导。因为在优化时需要计算损失函数的偏导数,所以L2优化更加便捷。
实例说明正则化解决过拟合问题:
# coding:utf-8
# 通过集合计算一个5层神经网络,解决非线性分类问题
#1. 生成模拟数据集。
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
data = []
label = []
np.random.seed(0)
# 以原点为圆心,半径为1的圆把散点划分成红蓝两部分,并加入随机噪音。
for i in range(150):
x1 = np.random.uniform(-1,1)
x2 = np.random.uniform(0,2)
if x1**2 + x2**2 <= 1:
data.append([np.random.normal(x1, 0.1),np.random.normal(x2,0.1)])
label.append(0)
else:
data.append([np.random.normal(x1, 0.1), np.random.normal(x2, 0.1)])
label.append(1)
# hstack()按列顺序将数组水平堆叠
# reshape()创建改变了尺寸的新数组
# scatter()画散点图
# 具体用法见下文链接
data = np.hstack(data).reshape(-1,2)
label = np.hstack(label)#.reshape(-1, 1)
plt.scatter(data[:,0], data[:,1], c=label,
cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.show()
nlabel = np.hstack(label).reshape(-1, 1)
#2. 定义一个获取权重,并自动加入正则项到损失的函数。
def get_weight(shape, lambda1):
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
# tf.add_to_collection将新生成变量的L2正则化损失项加入集合
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda1)(var))
return var
#3. 定义神经网络。
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]
# 通过一个循环来生成5层全连接的神经网络结构
for i in range(1, n_layers):
# 下一层的节点个数
out_dimension = layer_dimension[i]
# 生成当前层中权重的变量,并将这个变量的L2正则化损失加入计算图上的集合
weight = get_weight([in_dimension, out_dimension], 0.003)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
# 使用ReLU激活函数
cur_layer = tf.nn.elu(tf.matmul(cur_layer, weight) + bias)
# 更新节点个数
in_dimension = layer_dimension[i]
# 预测值
y= cur_layer
# 损失函数的定义
# 在定义神经网络前向传播的同时已经将所有的L2正则化损失加入了图上的集合
# 这里只需计算刻画模型在训练数据上表现的损失函数并加入集合。
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'))
#4. 训练不带正则项的损失函数mse_loss
# 定义训练的目标函数mse_loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(mse_loss)
TRAINING_STEPS = 40000
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: nlabel})
if i % 2000 == 0:
print("After %d steps, mse_loss: %f" % (i,sess.run(mse_loss, feed_dict={x: data, y_: nlabel})))
# 画出训练后的分割曲线
xx, yy = np.mgrid[-1.2:1.2:.01, -0.2:2.2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)
plt.scatter(data[:,0], data[:,1], c=label,
cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()
#5. 训练带正则项的损失函数loss
# 定义训练的目标函数loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(loss)
TRAINING_STEPS = 40000
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: nlabel})
if i % 2000 == 0:
print("After %d steps, loss: %f" % (i, sess.run(loss, feed_dict={x: data, y_:nlabel})))
# 画出训练后的分割曲线
xx, yy = np.mgrid[-1:1:.01, 0:2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
print(probs)
probs = probs.reshape(xx.shape)
plt.scatter(data[:,0], data[:,1], c=label,
cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()
初始散点图:
过拟合分类图:
正常分类图:
hstack()
函数参考Numpy中stack(),hstack(),vstack()函数详解
reshape()
函数参考Python中reshape的用法
scatter
参考PYthon——plt.scatter各参数详解