线性模型的局限性
线性模型:
y = ∑ I w I x I + b y=\sum_{I}w_{I}x_{I}+b y=∑IwIxI+b
当模型的输入只有一个的时候,x和y形成了二维坐标系上的一条直线,当有n个输入时,x和y形成了n+1维空间中的一个平面,一个线性模型中通过输入得到输出的函数称为一个线性变换。
任意线性模型的组合仍然是线性模型
只通过线性变换,任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别,而且它们都是线性模型。深度学习要强调非线性
在线性可分问题中,线性模型能够很好区分不同颜色的点,因为线性模型能解决线性可分问题,可以用直线区分,在更为复杂的问题中,无法通过直线区分(或者高维空间的平面),绝大部分问题无法线性分割。
在隐藏层中使用激活函数,且激活函数为非线性激活函数,或在激活函数中加入非线性元素就可以很好的区分不同颜色的点了。
激活函数实现去线性化
加入偏置项,每个节点的取值不再是单纯的去线性化。激活函数不是直线,通过激活函数,每个节点不再是线性变换,神经网络也就不是线性的。
常用的激活函数:
- tf.nn.relu
- tf.nn.sigmoid
- tf.nn.tanh
#上图所示的前向传播
a = tf.nn.relu(tf.matmul(x,w1) + biases1)
y = tf.nn.relu(tf.matmul(a,w2) + biases2)
损失函数
神经网络模型的效果以及优化的目标是通过损失函数(loss function)定义。
经典损失函数
监督学习两大种类:分类问题和回归问题
手写数字识别问题可看成十分类问题(0~9),判断零件合格与否是二分类问题(合格&不合格),解决多分类问题一般可设置n个输出节点,其中n为类别的个数,对每个样例,神经网络可得到一个n维数组作为输出结果,数组每个维度对应一个类别。
判断输出值和期望值之间的差距用交叉熵判断,二者差异越小,交叉熵越小。
给定概率p和q,通过p,q来表示p的交叉熵为:
H ( p , q ) = − ∑ x p ( x ) l o g q ( x ) H(p,q)=-\sum_x{}p(x)logq(x) H(p,q)=−∑xp(x)logq(x)
交叉熵刻画的是两个概率分布之间的距离,任意事件发生的概率在0-1之间,且总有某一个事件发生(概率为1)
代码实现交叉熵:
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10,1.0)))
y_: 正确结果
y: 预测结果
通过tf.clip_by_value函数将张量y中的值限制在一个范围内,避免了一些错误的运算(如log0)
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()
#输出:[[2.5,2.5,3.][4.,4.5,4.5]]
小于2.5的数都被替换成2.5,大于4.5的数都被替换成4.5,所以在求交叉熵时,将小于1e-10的数替换成e-10,就可以避免log0的出现。
tf.log是求对数的操作
注意: 操作是相乘,而tf.matmul才是矩阵乘法,前者是对应数字相乘*
import tensorflow as tf
from numpy.random import RandomState
v = tf.constant([[1.0,2.0],[3.0,4.0]])
w = tf.constant([[5.0,6.0],[7.0,8.0]])
print(v * w)#张量:Tensor("mul:0", shape=(2, 2), dtype=float32)
print(tf.matmul(v,w))#张量:Tensor("MatMul:0", shape=(2, 2), dtype=float32)
print((v * w).eval)#<bound method Tensor.eval of <tf.Tensor 'mul_1:0' shape=(2, 2) dtype=float32>>
#print((tf.matmul(v,w).eval())# ) 报错
sess = tf.Session()
print(sess.run((v * w)))
"""[[ 5. 12.]
[21. 32.]]"""
print(sess.run(tf.matmul(v,w)))
"""[[19. 22.]
[43. 50.]]"""
sess.close()
上述得到n x m的矩阵,n为一个batch中样例的数量,m为分类的类别数量
tf.reduce_mean对矩阵作平均而不改变计算结果的意义。
交叉熵一般与回归一起使用,tf提供下列函数:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
y为输出结果,y_为标准数据
对回归问题,最常用的损失函数是MSE:
mse = tf.reduce_mean(tf.square(y_-y))
自定义损失函数
tf.greater:比较输入张量中每个元素的大小,返回比较结果
tf.where:选择条件为True,选择前者;False选择后者
import tensorflow as tf
v1 = tf.constant([1.0,2.0,3.0,4.0])
v2 = tf.constant([4.0,3.0,2.0,1.0])
sess = tf.InteractiveSession()
print(tf.greater(v1,v2).eval())
print(tf.where(tf.greater(v1,v2),v1,v2).eval())#v1 v2比较大小,v1>v2,输出v1,否则v2
"""[False False True True]
[4. 3. 3. 4.]"""
sess.close()
损失函数对模型训练的影响:
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 = 1
loss_more = 10
#自定义损失函数
loss = tf.reduce_sum(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)
'''设置回归的正确值是两个输入的和加上一个随机量,加上随机量可以加入不可预测的噪音,否则不同预测函数的意义不大
损失函数的值在完全预测的时候最低,噪音为一个均值为0的量,所以噪音设置为-0.05~0.05的随机数'''
Y = [[x1 +x2 +rdm.rand()/10.0-0.05] for (x1,x2) in X]
#训练神经网络
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
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]})
if i%100==0:
print(sess.run(w1))
'''最终:[[1.0192484]
[1.0426838]]'''
'''得到的预测函数是1.02x1+1.04x2,这里x1+x2大,是因为损失函数中指定预测少了的损失更大,loss_less>loss_more
将loss_less=1,loss_more=10,得到:[[0.9557179][0.9806985]],
所以,相同的神经网络,不同的损失函数,会对训练的模型产生很大的影响
'''
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_sum(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 = tf.global_variables_initializer()
sess.run(init)
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]})
if i%100==0:
print(sess.run(w1))
神经网络优化算法
梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,反向传播算法是训练神经网络的核心算法,可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。参数的优化直接决定了模型的质量。
学习率定义是每次参数移动的幅度。
优化过程可抽象为:寻找参数w,使得损失函数J(w)最小,梯度下降算法会迭代更新参数w,不断沿着梯度的反方向让参数朝着总损失更小的方向更新。
参数更新公式: w n + 1 = w n − η ∂ ∂ w n J ( w n ) w_{n+1}=w_{n}-\eta \frac{\partial }{\partial w_{n}}J(w_{n}) wn+1=wn−η∂wn∂J(wn)
梯度为: ∂ ∂ w n J ( w n ) \frac{\partial }{\partial w_{n}}J(w_{n}) ∂wn∂J(wn)
神经网络的优化过程可以分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比得到两者之间的差距;第二阶段通过反向传播算法计算损失函数对每个参数的梯度,再根据梯度和学习率使用梯度下降更新每一个参数。
梯度下降可能得到的是局部最优解,参数的初始值会很大程度影响最后得到的结果,只有当损失函数为凸函数时,梯度下降算法才能保证达到全局最优解。
另一个问题是梯度下降算法计算时间过长,每一轮迭代需要计算在全部训练数据上的损失函数。
随机梯度下降是随机优化某一条训练数据上的损失函数,每一轮优化速度加快,但某一条数据上损失函数更小不代表在全部数据上损失函数更小,所以使用随机梯度下降函数也可能无法达到全局最优甚至局部最优。
实际应用中:每次计算一小部分训练数据的损失函数,这一小部分数据称为一个batch
通过矩阵运算,每次在一个batch上优化神经网络的参数并不会比单个数据慢太多,且每次使用一个batch可以大大减少收敛所需要的迭代次数,可以使收敛的结果更接近梯度下降的效果。
神经网络进一步优化
学习率:控制参数更新的速度,学习率不能过大也不能过小,通过指数衰减法设置学习率
tf.train.exponential_decay函数实现了指数衰减学习率,使用该函数可以先用较大的学习率快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使模型在训练后更加稳定。
tf.train.exponential_decay实现了下列代码的功能:
decayed_learning_rate = learning_rate * decay_rate ^ (global_step/decay_steps)
decayed_learning_rate为每一轮优化使用的学习率,learning_rate为初始学习率,decay_steps为衰减速度。
tf.train.exponential_decay可以设置staircase参数,为默认值False时,学习率如阶梯般下降,但间隔减小,为True,则global_step/decay_steps会被转化为整数。
global_step = tf.Variable(0)
#通过tf.train.exponential_decay生成学习率
'''0.1是初始学习率每训练100轮后学习率乘以0.96'''
learing_rate = tf.train.exponential_decay(0.1, global_step, 100,0.96,staircase=True)
#更新学习率和global_step:全局的步数,初始为0
learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
指数衰减的学习率是伴随global_step的变化而衰减的,所以当global_step不改变时,学习率也变成一个定值。
损失函数优化器的minimize()中global_step=global_steps能够提供global_step自动加一的操作。
import tensorflow as tf
import numpy as np
x = tf.placeholder(tf.float32, shape=[None, 1], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
w = tf.Variable(tf.constant(0.0))
global_steps = tf.Variable(0, trainable=False)
learning_rate = tf.train.exponential_decay(0.1, global_steps, 10, 2, staircase=False)
loss = tf.pow(w * x - y, 2)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_steps)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(10):
sess.run(train_step, feed_dict={x: np.linspace(1, 2, 10).reshape([10, 1]),
y: np.linspace(1, 2, 10).reshape([10, 1])})
print(sess.run(learning_rate))
print(sess.run(global_steps))
'''0.10717734
1
0.11486983
2
0.123114444
3
0.1319508
4
0.14142136
5
0.15157166
6
0.16245048
7
0.17411011
8
0.1866066
9
0.2
10'''
过拟合问题
真是应用要通过训练出来的模型对未知的数据做出判断,模型在训练数据上的表现并不一定代表了它在未知数据上的表现。
过拟合指模型过于复杂后,可以很好的"记忆"每一个训练数据中随机噪音的部分而忘记了要去"学习"训练数据中通用的趋势,过度拟合训练数据中的噪音而忽视了问题的整体规律,对未知数据无法做出可靠的判断。
可用正则化避免过拟合问题,正则化是在损失函数中加入刻画模型复杂程度的指标,损失函数 J ( Θ ) J(\Theta ) J(Θ),那么优化时不是直接优化 J ( Θ ) J(\Theta ) J(Θ),而是优化 J ( Θ ) + λ R ( w ) J(\Theta )+\lambda R(w) J(Θ)+λR(w),其中R(w)刻画的是模型的复杂程度, λ \lambda λ表示模型复杂损失在总损失中的比例,这里的 Θ \Theta Θ表示神经网络中所有参数,包括权重w和偏置项b。一般模型复杂度只由权重w决定。
R(w)有两种:
L1正则化: R ( w ) = ∥ w ∥ = ∑ I ∣ w I ∣ R(w)= \left \| w \right \|=\sum_{I}\left |w _{I} \right | R(w)=∥w∥=∑I∣wI∣
L2正则化: R ( w ) = ∥ w ∥ 2 2 = ∑ I ∣ w I 2 ∣ R(w)=\left \| w \right \|_{2}^{2}=\sum_{I}\left | w_{I}^{2} \right | R(w)=∥w∥22=∑I∣∣wI2∣∣
L1正则化会使参数变得稀疏,稀疏是指更多参数变为0,可以达到类似特征选取的功能。
L2不会变得稀疏的原因是当参数很小时,如0.001,这个数的平方可以忽略。
其次,L1正则化计算公式不能求导,L2可以求导,在优化时需要计算损失函数的偏导数,所以L2正则化损失函数更加简洁,优化带L1的损失函数更加复杂,也可将L1和L2正则化同时使用:如 R ( w ) = ∑ I α ∣ w I ∣ + ( 1 − α ) w I 2 R(w)=\sum_{I}\alpha \left |w _{I} \right |+(1-\alpha )w_{I}^{2} R(w)=∑Iα∣wI∣+(1−α)wI2
tf中也定义了带正则化的损失函数:
w = tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y = tf.matmul(w,x)
loss = tf.reduce_mean(tf.square(y_-y))+tf.contrib.layers.l2_regularizer(lambda)(w)
损失函数: J ( Θ ) + λ R ( w ) J(\Theta )+\lambda R(w) J(Θ)+λR(w)
其中loss是损失函数,包含MSE均方误差损失函数,刻画了模型在训练数据上的表现,第二个部分是正则化,防止模型过度模拟训练数据中的随机噪音,lambda参数是公式中的权重 λ \lambda λ,tf.contrib.layers.l2_regularizer可以计算正则化后的值。
import tensorflow as tf
weights = tf.constant([[1.0,-2.0],[-3.0,4.0]])
with tf.Session() as sess:
print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))#7.5,平方和乘以参数0.5
print(sess.run(tf.contrib.layers.l1_regularizer(0.5)(weights)))#5.0,绝对值之和乘以参数0.5
复杂的网络结构中定义网络结构和计算损失函数部分不在同一个函数中,可以使用集合:
import tensorflow as tf
def get_weight(shape, lambda_):
var = tf.Variable(tf.random_normal(shape),dtype=tf.float32)
tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(lambda_)(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]
weight = get_weight([in_dimension,out_dimension],0.001)
bias = tf.Variable(tf.constant(0.1,shape=[out_dimension]))
cur_layer = tf.nn.relu(tf.matmul(cur_layer,weight)+bias)
in_dimension = layer_dimension[i]
mse_loss = tf.reduce_mean(tf.square(y_-cur_layer))
tf.add_to_collection('losses',mse_loss)
loss = tf.add_n(tf.get_collection('losses'))
import tensorflow as tf
#获得神经网络边上的权重,将权重的l2正则化损失加入losses集合中
def get_weight(shape, lambda_):
#变量,正则化损失的参数w
var = tf.Variable(tf.random_normal(shape),dtype=tf.float32)
#将正则化损失加入集合中,
tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(lambda_)(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]
#权重,生成当前层的节点数,并将正则化损失加入losses集合中
weight = get_weight([in_dimension,out_dimension],0.001)
#偏置项
bias = tf.Variable(tf.constant(0.1,shape=[out_dimension]))
#使用rule激活函数tf.nn.relu(tf.matmul(x,w)+bias)
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)
#最终的损失函数
loss = tf.add_n(tf.get_collection('losses'))
'''get_collection返回一个列表,该列表是所有这个集合中的元素,这些元素是损失函数的不同部分,将他们加起来就是最终的损失函数'''
滑动平均模型
滑动模型:使模型在测试数据上更健壮
使用tf.train.ExponentialMovingAverage来实现滑动平均模型,初始化ExponentialMovingAverage要设置一个衰减率decay,decay用于控制模型更新的速度,ExponentialMovingAverage对每个变量都有一个影子变量(shadow variable),影子变量初始值是该变量的初始值,变量更新时,影子变量被更新为:
shadow_variable = decay x shadow_variable+(1-decay)variable
decay越大模型越稳定,一般将decay初始值设为1
为了使模型训练前期更新加快,还有num_updates参数动态设置decay的大小,如果有num_updates,那么每次的衰减率为:
min{decay, (1+num_updates)/(10+num_updates)}
import tensorflow as tf
v1 = tf.Variable(0,dtype=tf.float32)#要指明dtype
step = tf.Variable(0,trainable=False)
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)
print(sess.run([v1,ema.average(v1)]))#[0.0, 0.0]
sess.run(tf.assign(v1,5))
sess.run(maintain_averages_op)
print(sess.run([v1,ema.average(v1)]))#[5.0, 4.5]
sess.run(tf.assign(step,10000))
sess.run(tf.assign(v1,10))
sess.run(maintain_averages_op)
print(sess.run([v1,ema.average(v1)]))#[10.0, 4.555]
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))#[10.0, 4.60945]
import tensorflow as tf
#定义变量用于计算滑动平均,初始值为0,变量必须是实数型
v1 = tf.Variable(0,dtype=tf.float32)#要指明dtype
#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获取滑动之和的平均值,初始都为0
print(sess.run([v1,ema.average(v1)]))#[0.0, 0.0]
#tf.assign为给变量赋值的操作,这里给v1赋值为5
sess.run(tf.assign(v1,5))
#更新v1的滑动平均值,衰减率为min{0.99,(1+step)/(10+step)},{0.99,0.1}-->0.1
sess.run(maintain_averages_op)#v1滑动平均更新为:0*0.1+(1-0.1)*5=4.5
print(sess.run([v1,ema.average(v1)]))#[5.0, 4.5]
#step赋值为10000
sess.run(tf.assign(step,10000))
#v1赋值为10
sess.run(tf.assign(v1,10))
#min{0.99,(1+10000)/(10+10000)}-->0.99
sess.run(maintain_averages_op)#4.5*0.99+0.01*10=4.555
print(sess.run([v1,ema.average(v1)]))#[10.0, 4.555]
#0.99*4.555+0.01*10=4.60945
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))#[10.0, 4.60945]