【手把手TensorFlow】二、神经网络优化

版权声明:欢迎转载,转载请注明出处:土豆洋芋山药蛋 https://blog.csdn.net/qq_33414271/article/details/86082993

往期回顾:
【手把手TensorFlow】一、从开始使用TensorFlow到弄清楚“搭建神经网络套路”

Q1:有多少参数需要优化?

在这里插入图片描述
在该神经网络中,包含 1 个输入层、 1 个隐藏层和 1 个输出层,该神经网络的层数为 2 层。

在该神经网络中, 参数的个数是所有参数 w 的个数加上所有参数 b 的总数, 第一层参数用三行四列的二阶张量表示(即 12 个线上的权重 w) 再加上 4 个偏置 b; 第二层参数是四行两列的二阶张量( 即8 个线上的权重 w) 再加上 2 个偏置 b。

总参数 = 34+4 + 42+2 = 26。

Q2:有多少激活函数可供选择?

常用的激活函数有relu、sigmoid、tanh等。
①激活函数relu:在Tensorflow中,用tf.nn.relu()表示
在这里插入图片描述

②激活函数sigmoid:在Tensorflow中,用tf.nn.sigmoid()表示
在这里插入图片描述

③激活函数tanh:在Tensorflow中,用tf.nn.tanh()表示
在这里插入图片描述

Q3:损失函数如何选择?

用来表示预测值(y)与已知答案(y_)的差距。在训练神经网络时,通过不断改变神经网络中所有参数,使损失函数不断减小,从而训练出更高准确率的神经网络模型。

常用的损失函数有均方误差、自定义和交叉熵等。

1.均方误差mse

n个样本的预测值y与已知答案y_之差的平方和,再求平均值。
在这里插入图片描述
在Tensorflow中用loss_mse=tf.reduce_mean(tf.square(y_-y))

预测酸奶日销量 yx1 和 x2 是影响日销量的两个因素。在本例中用销量预测产量, 最优的产量应该等于销量。 由于目前没有数据集,所以拟造了一套数据集。利用 Tensorflow 中函数随机生成 x1、 x2, 制造标准答案 y_ = x1 + x2, 为了更真实, 求和后还加了正负 0.05 的随机噪声.

我们把这套自制的数据集喂入神经网络,构建一个一层的神经网络,拟合预测酸奶日销量的函数。

# -*- coding: utf-8 -*-
"""
Created on Tue Jan  8 16:25:06 2019

@author: liang
"""

import tensorflow as tf 
import numpy as np
BATCH_SIZE=8
SEED=23455

rdm=np.random.RandomState(SEED)
X=rdm.rand(32,2)
Y_=[[x1+x2+(rdm.rand()/10.0-0.05)] for (x1,x2) in X]

'''
1.定义神经网络的输入、参数和输出,定义前向传播过程
'''
x=tf.placeholder(tf.float32,shape=(None,2))
y_=tf.placeholder(tf.float32,shape=(None,1))
#定义参数:2行1列,说明有2个参数w1和w2
w=tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y=tf.matmul(x,w)#预测的y值,此时肯定是不准确的

'''
2.定义损失函数及反向传播方法
'''
#定义损失函数为MSE,反向传播算法
loss_mse = tf.reduce_mean(tf.square(y_-y))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss_mse)

'''
3.生成会话,训练STEPS
'''
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    STEPS=20000
    #共经历20000轮,每轮训练BATCH_SIZE=8大小的数据
    for i in range(STEPS):
        start = (i*BATCH_SIZE)%32
        end = start+BATCH_SIZE
        sess.run(train_step,feed_dict={x:X[start:end],y_:Y_[start:end]})
        if i % 500==0:
            print("After %d training steps , w is : "%(i))
            print(sess.run(w))
    print("Final w is : ",sess.run(w))
'''
神经网络模型得到最终参数 w1=0.98, w2=1.02, 销量预测结果为 y =0.98 * x1 + 1.02 * x2。
由于在生成数据集时,标准答案为 y = x1 + x2,因此,销量预测结果和标准答案已非常接近, 说明该神经网络预测酸奶日销量正确。
'''      

2.自定义损失函数

对于预测销量问题:如果预测多了,会损失成本。如果预测少了,会损失利润。
① 若酸奶成本为 1 元,酸奶销售利润为 9 元,我们当然更不愿意损失利润。所以我们应该使模型尽量预测多一点。
损失函数表示:
若预测结果 y < y_,损失函数 = PROFIT(利润) * (y - y_ ) (取绝对值);
若预测结果 y > y_,损失函数 = COST(成本) * (y - y_ ) (取绝对值)。
在这里插入图片描述
用 Tensorflow 函数表示为:
loss = tf.reduce_sum(tf.where( tf.greater(y,y_) , (y-y_)*COST , (y_-y)*PROFIT ))

tf.where(input, a,b),其中a,b均为尺寸一致的tensor,作用是将a中对应input中true的位置的元素值不变,其余元素进行替换,替换成b中对应位置的元素值.
具体而言:
tf.greater(y,y_) 为True就输出a,即(y-y_)*COST
tf.greater(y,y_) 为false就输出b,即 (y_-y)*PROFIT
若tf.greater(y,y_)为多维,则为false对应的维度中,a在那个维度被替换为b的相应维度数据。否则保持a中数据。最终返回a。

②若酸奶成本为 9 元,酸奶销售利润为 1 元,我们当然更不愿意损失成本。所以我们应该使模型尽量预测少一点。

① 对应的程序:

# -*- coding: utf-8 -*-
"""
Created on Tue Jan  8 16:25:06 2019

@author: liang
"""

import tensorflow as tf 
import numpy as np
BATCH_SIZE=8
SEED=23455
#增加COST和PROFIT
COST=1
PROFIT=9


rdm=np.random.RandomState(SEED)
X=rdm.rand(32,2)
Y_=[[x1+x2+(rdm.rand()/10.0-0.05)] for (x1,x2) in X]

'''
1.定义神经网络的输入、参数和输出,定义前向传播过程
'''
x=tf.placeholder(tf.float32,shape=(None,2))
y_=tf.placeholder(tf.float32,shape=(None,1))
#定义参数:2行1列,说明有2个参数w1和w2
w=tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y=tf.matmul(x,w)#预测的y值,此时肯定是不准确的

'''
2.定义损失函数及反向传播方法
'''
#定义损失函数为MSE,反向传播算法
#loss_mse = tf.reduce_mean(tf.square(y_-y))
#自定义
loss=tf.reduce_sum(tf.where(tf.greater(y,y_),(y-y_)*COST,(y_-y)*PROFIT))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)

'''
3.生成会话,训练STEPS
'''
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    STEPS=20000
    #共经历20000轮,每轮训练BATCH_SIZE=8大小的数据
    for i in range(STEPS):
        start = (i*BATCH_SIZE)%32
        end = start+BATCH_SIZE
        sess.run(train_step,feed_dict={x:X[start:end],y_:Y_[start:end]})
        if i % 500==0:
            print("After %d training steps , w is : "%(i))
            print(sess.run(w))
    print("Final w is : ",sess.run(w))

由执行结果可知,预测结果增加了,看出来模型希望我们此时尽量保住利润,而不是成本。

3.交叉熵(Cross Entropy)

表示两个概率分布之间的距离。交叉熵越大,两个概率分布距离越远,两个概率分布越相异;交叉熵越小,两个概率分布距离越近,两个概率分布越相似。
交叉熵计算公式:
在这里插入图片描述
用 Tensorflow 函数表示为
ce= -tf.reduce_mean(y_* tf.log(tf.clip_by_value(y, 1e-12, 1.0)))

在 Tensorflow 中,一般让模型的输出经过 sofemax 函数, 以获得输出分类的概率分布,再与标准答案对比, 求出交叉熵, 得到损失函数,用如下函数实现:

Q4:学习率怎么选?

学习率 learning_rate: 表示了每次参数更新的幅度大小。 学习率过大, 会导致待优化的参数在最小值附近波动,不收敛;学习率过小, 会导致待优化的参数收敛缓慢。
参数的更新公式为:
在这里插入图片描述

程序示范:
假设损失函数为 l o s s = ( w + 1 ) 2 loss = (w + 1)^2 ,梯度是损失函数 loss 的导数为2w+2。如参数初值为 5,学习率为 0.2,则参数和损失函数更新如下:
在这里插入图片描述
损失函数 l o s s = ( w + 1 ) 2 loss = (w + 1)^2 图像为:
在这里插入图片描述
由图可知,损失函数 loss 的最小值会在(-1,0)处得到,此时损失函数的导数为 0,得到最终参数 w =-1。 代码如下:

#coding:utf-8
#设损失函数 loss=(w+1)^2, 令w初值是常数5。反向传播就是求最优w,即求最小loss对应的w值
import tensorflow as tf
#定义待优化参数w初值赋5
w = tf.Variable(tf.constant(5, dtype=tf.float32))
#定义损失函数loss
loss = tf.square(w+1)
#定义反向传播方法
train_step = tf.train.GradientDescentOptimizer(0.2).minimize(loss)
#生成会话,训练40轮
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    for i in range(40):
        sess.run(train_step)
        w_val = sess.run(w)
        loss_val = sess.run(loss)
        print( "After %s steps: w is %f,   loss is %f." % (i, w_val,loss_val))

损失函数收敛于-1:
在这里插入图片描述
由结果可知,随着损失函数值的减小, w 无限趋近于-1,模型计算推测出最优参数 w = -1。

学习率的设置

上述实例的学习率为0.2,如果改为1则参数w会产生震荡,不会收敛
在这里插入图片描述
如果学习率太小,如0.0001,又收敛太慢:
在这里插入图片描述

指数衰减学习率: 学习率随着训练轮数变化而动态更新

学习率计算公式如下:
在这里插入图片描述

用 Tensorflow 的函数表示为:

global_step = tf.Variable(0, trainable=False)
learning_rate = tf.train.exponential_decay(
	LEARNING_RATE_BASE,
	global_step,
	LEARNING_RATE_STEP, LEARNING_RATE_DECAY,
	staircase=True/False)

LEARNING_RATE_BASE :学习率初始值
LEARNING_RATE_DECAY :学习率衰减率
global_step 记录了当前训练轮数,为不可训练型参数。

若 staircase 设置为 True 时,表示 global_step/learning rate step 取整数,学习率阶梯型衰减;若 staircase 设置为 false 时,学习率会是一条平滑下降的曲线。

#coding:utf-8
#设损失函数 loss=(w+1)^2, 令w初值是常数10。反向传播就是求最优w,即求最小loss对应的w值
#使用指数衰减的学习率,在迭代初期得到较高的下降速度,可以在较小的训练轮数下取得更有收敛度。
import tensorflow as tf

LEARNING_RATE_BASE = 0.1 #最初学习率
LEARNING_RATE_DECAY = 0.99 #学习率衰减率
LEARNING_RATE_STEP = 1  #喂入多少轮BATCH_SIZE后,更新一次学习率,一般设为:总样本数/BATCH_SIZE

#运行了几轮BATCH_SIZE的计数器,初值给0, 设为不被训练
global_step = tf.Variable(0, trainable=False)
#定义指数下降学习率
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, LEARNING_RATE_STEP, LEARNING_RATE_DECAY, staircase=True)
#定义待优化参数,初值给10
w = tf.Variable(tf.constant(5, dtype=tf.float32))
#定义损失函数loss
loss = tf.square(w+1)
#定义反向传播方法
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
#生成会话,训练40轮
with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    for i in range(40):
        sess.run(train_step)
        learning_rate_val = sess.run(learning_rate)
        global_step_val = sess.run(global_step)
        w_val = sess.run(w)
        loss_val = sess.run(loss)
        print ("After %s steps: global_step is %f, w is %f, learning rate is %f, loss is %f" % (i, global_step_val, w_val, learning_rate_val, loss_val))

在这里插入图片描述
由结果可以看出,随着训练轮数增加学习率在不断减小。

Q5:参数如何设置?

1.滑动平均——参数设置参照以前的平均值

滑动平均: 记录了一段时间内模型中所有参数 w 和 b 各自的平均值。利用滑动平均值可以增强模型的泛化能力。

目的:用于控制变量的更新幅度,使得模型在训练初期参数更新较快,在接近最优值处参数更新较慢,幅度较小

滑动平均值(影子) 计算公式:
= + 1 影子 = 衰减率 * 影子 +(1 - 衰减率) * 参数
在这里插入图片描述
影子初值=参数初值

用TensorFlow表示:

#OVING_AVERAGE_DECAY 表示滑动平均衰减率,一般会赋接近 1 的值, global_step 表示当前训练了多少轮。
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

#ema.apply()函数实现对括号内参数求滑动平均, tf.trainable_variables()函数实现把所有待训练参数汇总为列表
ema_op = ema.apply(tf.trainable_variables())

#该函数实现将滑动平均和训练过程同步运行
with tf.control_dependencies([train_step, ema_op]):
train_op = tf.no_op(name='train')

举例而言:
在神经网络模型中,将 MOVING_AVERAGE_DECAY 设置为 0.99,参数 w1 设置为 0, w1 的滑动平均值设置为 0。

①开始时,轮数 global_step 设置为 0, 参数 w1 更新为 1,则 w1 的滑动平均值为:
w1 滑动平均值=min(0.99,1/10)*0+(1– min(0.99,1/10)*1 = 0.9
②再次运行,参数 w1 更新为 1.644,则滑动平均值变为:
w1 滑动平均值=min(0.99,101/110)*1.644+(1– min(0.99,101/110)*10 = 2.328
③再次运行,参数 w1 更新为 2.328,则滑动平均值:
w1 滑动平均值=2.956

#coding:utf-8
import tensorflow as tf

#1. 定义变量及滑动平均类
#定义一个32位浮点变量,初始值为0.0  这个代码就是不断更新w1参数,优化w1参数,滑动平均做了个w1的影子
w1 = tf.Variable(0, dtype=tf.float32)
#定义num_updates(NN的迭代轮数),初始值为0,不可被优化(训练),这个参数不训练
global_step = tf.Variable(0, trainable=False)
#实例化滑动平均类,给衰减率为0.99,当前轮数global_step
MOVING_AVERAGE_DECAY = 0.99
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
#ema.apply后的括号里是更新列表,每次运行sess.run(ema_op)时,对更新列表中的元素求滑动平均值。
#在实际应用中会使用tf.trainable_variables()自动将所有待训练的参数汇总为列表
#ema_op = ema.apply([w1])
ema_op = ema.apply(tf.trainable_variables())

#2. 查看不同迭代中变量取值的变化。
with tf.Session() as sess:
    # 初始化
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
	#用ema.average(w1)获取w1滑动平均值 (要运行多个节点,作为列表中的元素列出,写在sess.run中)
	#打印出当前参数w1和w1滑动平均值
    print ("current global_step:", sess.run(global_step))
    print ("current w1", sess.run([w1, ema.average(w1)]) )
    
    # 参数w1的值赋为1
    sess.run(tf.assign(w1, 1))
    sess.run(ema_op)
    print ("current global_step:", sess.run(global_step))
    print ("current w1", sess.run([w1, ema.average(w1)]) )
    
    # 更新global_step和w1的值,模拟出轮数为100时,参数w1变为10, 以下代码global_step保持为100,每次执行滑动平均操作,影子值会更新 
    sess.run(tf.assign(global_step, 100))  
    sess.run(tf.assign(w1, 10))
    sess.run(ema_op)
    print ("current global_step:", sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))       
    
    # 每次sess.run会更新一次w1的滑动平均值
    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

    sess.run(ema_op)
    print ("current global_step:" , sess.run(global_step))
    print ("current w1:", sess.run([w1, ema.average(w1)]))

#更改MOVING_AVERAGE_DECAY 为 0.1  看影子追随速度

"""

current global_step: 0
current w1 [0.0, 0.0]
current global_step: 0
current w1 [1.0, 0.9]
current global_step: 100
current w1: [10.0, 1.6445453]
current global_step: 100
current w1: [10.0, 2.3281732]
current global_step: 100
current w1: [10.0, 2.955868]
current global_step: 100
current w1: [10.0, 3.532206]
current global_step: 100
current w1: [10.0, 4.061389]
current global_step: 100
current w1: [10.0, 4.547275]
current global_step: 100
current w1: [10.0, 4.9934072]
"""

2.给参数权重

过拟合: 神经网络模型在训练数据集上的准确率较高,在新的数据进行预测或分类时准确率较低, 说明模型的泛化能力差。
正则化: 在损失函数中给每个参数 w 加上权重,引入模型复杂度指标,从而抑制模型噪声, 减小过拟合。

使用正则化后,损失函数 loss 变为两项之和:
l o s s = l o s s ( y y ) + R E G U L A R I Z E R l o s s ( w ) loss = loss(y 与 y_) + REGULARIZER*loss(w)
其中,第一项是预测结果与标准答案之间的差距,如之前讲过的交叉熵、均方误差等;第二项是正则化计算结果。

正则化计算方法:
① L1 正则化: l o s s L 1 = w i loss_{L1}=\sum |w_i|
用 Tesnsorflow 函数表示:loss(w) = tf.contrib.layers.l1_regularizer(REGULARIZER)(w)
② L2 正则化: l o s s L 2 = w i 2 loss_{L2}=\sum |w_i|^2
用 Tesnsorflow 函数表示:loss(w) = tf.contrib.layers.l2_regularizer(REGULARIZER)(w)

用 Tesnsorflow 函数实现正则化:
tf.add_to_collection(‘losses’, tf.contrib.layers.l2_regularizer(regularizer)(w)
loss = cem + tf.add_n(tf.get_collection(‘losses’))

实例:

#coding:utf-8
#0导入模块 ,生成模拟数据集
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
BATCH_SIZE = 30 
seed = 2 
#基于seed产生随机数
rdm = np.random.RandomState(seed)
#随机数返回300行2列的矩阵,表示300组坐标点(x0,x1)作为输入数据集
X = rdm.randn(300,2)
#从X这个300行2列的矩阵中取出一行,判断如果两个坐标的平方和小于2,给Y赋值1,其余赋值0
#作为输入数据集的标签(正确答案)
Y_ = [int(x0*x0 + x1*x1 <2) for (x0,x1) in X]
#遍历Y中的每个元素,1赋值'red'其余赋值'blue',这样可视化显示时人可以直观区分
Y_c = [['red' if y else 'blue'] for y in Y_]
#对数据集X和标签Y进行shape整理,第一个元素为-1表示,随第二个参数计算得到,第二个元素表示多少列,把X整理为n行2列,把Y整理为n行1列
X = np.vstack(X).reshape(-1,2)
Y_ = np.vstack(Y_).reshape(-1,1)
print( X)
print(Y_)
print( Y_c)
#用plt.scatter画出数据集X各行中第0列元素和第1列元素的点即各行的(x0,x1),用各行Y_c对应的值表示颜色(c是color的缩写) 
plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c)) 
plt.show()


#定义神经网络的输入、参数和输出,定义前向传播过程 
def get_weight(shape, regularizer):
	w = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
	tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
	return w

def get_bias(shape):  
    b = tf.Variable(tf.constant(0.01, shape=shape)) 
    return b
	
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))

w1 = get_weight([2,11], 0.01)	
b1 = get_bias([11])
y1 = tf.nn.relu(tf.matmul(x, w1)+b1)

w2 = get_weight([11,1], 0.01)
b2 = get_bias([1])
y = tf.matmul(y1, w2)+b2 


#定义损失函数
loss_mse = tf.reduce_mean(tf.square(y-y_))
loss_total = loss_mse + tf.add_n(tf.get_collection('losses'))


#定义反向传播方法:不含正则化
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss_mse)

with tf.Session() as sess:
	init_op = tf.global_variables_initializer()
	sess.run(init_op)
	STEPS = 40000
	for i in range(STEPS):
		start = (i*BATCH_SIZE) % 300
		end = start + BATCH_SIZE
		sess.run(train_step, feed_dict={x:X[start:end], y_:Y_[start:end]})
		if i % 2000 == 0:
			loss_mse_v = sess.run(loss_mse, feed_dict={x:X, y_:Y_})
			print("After %d steps, loss is: %f" %(i, loss_mse_v))
    #xx在-3到3之间以步长为0.01,yy在-3到3之间以步长0.01,生成二维网格坐标点
	xx, yy = np.mgrid[-3:3:.01, -3:3:.01]
	#将xx , yy拉直,并合并成一个2列的矩阵,得到一个网格坐标点的集合
	grid = np.c_[xx.ravel(), yy.ravel()]
	#将网格坐标点喂入神经网络 ,probs为输出
	probs = sess.run(y, feed_dict={x:grid})
	#probs的shape调整成xx的样子
	probs = probs.reshape(xx.shape)
	print( "w1:\n",sess.run(w1))
	print( "b1:\n",sess.run(b1))
	print( "w2:\n",sess.run(w2))
	print( "b2:\n",sess.run(b2))

plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
plt.contour(xx, yy, probs, levels=[.5])
plt.show()



#定义反向传播方法:包含正则化
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss_total)

with tf.Session() as sess:
	init_op = tf.global_variables_initializer()
	sess.run(init_op)
	STEPS = 40000
	for i in range(STEPS):
		start = (i*BATCH_SIZE) % 300
		end = start + BATCH_SIZE
		sess.run(train_step, feed_dict={x: X[start:end], y_:Y_[start:end]})
		if i % 2000 == 0:
			loss_v = sess.run(loss_total, feed_dict={x:X,y_:Y_})
			print("After %d steps, loss is: %f" %(i, loss_v))

	xx, yy = np.mgrid[-3:3:.01, -3:3:.01]
	grid = np.c_[xx.ravel(), yy.ravel()]
	probs = sess.run(y, feed_dict={x:grid})
	probs = probs.reshape(xx.shape)
	print( "w1:\n",sess.run(w1))
	print( "b1:\n",sess.run(b1))
	print( "w2:\n",sess.run(w2))
	print( "b2:\n",sess.run(b2))

plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c)) 
plt.contour(xx, yy, probs, levels=[.5])
plt.show()


在这里插入图片描述
正则化后:
在这里插入图片描述

参考文献:
人工智能实践:Tensorflow笔记

猜你喜欢

转载自blog.csdn.net/qq_33414271/article/details/86082993
今日推荐