【从零开始学习Tensorflow】(二)第4章 深层神经网络

转载请注明作者和出处: https://blog.csdn.net/weixin_37392582
开发平台: Win10 + Python3.6 + Anaconda3
编  者: 无尾



一、深度学习与深层神经网络


维基百科对于深度学习的定义:“一类通过多层非线性变换对高复杂性数据建模算法的集合”,关键词:“多层”和“非线性”。


1、线性模型的局限性 - 为什么要在深度学习的定义中强调“复杂问题”


在线性可分问题中,现行模型能够很好区分不同颜色的点,而对于无法通过直线或高位平面划分的复杂问题,只能通过非线性变化解决

线性不可分
线性不可分

线性可分
线性可分

非线性激活函数 ReLUc
非线性激活函数


2、激活函数实现去线性化 - 如何去线性化,程序实现


这里写图片描述

对神经网络结构加上激活函数和偏置项,使每个节点的输出在加权和的基础上还做了一个非线性变换。

几种常用的非线性激活函数的函数图像为:
这里写图片描述

表示为代码:

#tf.nn.relu 为 ReLU函数 → f(x) = max(x,0)
a = tf.nn.relu(tf.matmul(x, w1) + biases1)
y = tf.nn.relu(tf.matmul(a, w2) + biases2)

3、 多层网络解决异或运算 - 深层网络比浅层网络可以解决更多的问题


通过一个实际问题来讲解深度学习的另外一个重要性质——多层变换。在1958年由Frank Rosenblatt提出的感知机(perceptron)模型中,可以简单的理解为单层神经网络,但无法模拟异或运算。

通过神经网络游乐场,模拟感知机的异或问题 (无隐藏层)

左下输入为负负,右上为正正。
这里写图片描述

经过500+次训练,发现这个感知机不能将两种不同颜色的点分开,也就是说感知机无法模拟异或运算的功能。
这里写图片描述

加入隐藏层后,异或问题就可以得到很好的解决。
这里写图片描述

这四个隐藏节点可以被认为代表了从输入特征中抽取的更高维的特征。

从这个例子中可以看到,深层神经网络实际上有组合特征提取的功能,这个特性对于解决不易提取特征向量的问题(比如图片识别、语音识别等)有很大帮助。这也是深度学习在这些问题上更加容易取得突破性进展的原因。


二、损失函数定义 - 如何设定神经网络的优化目标


1、经典损失函数 - 适用于分类和回归问题


分类问题和回归问题使监督学习的两大种类。

(1)分类问题

通过神经网络解决多分类问题最常用的方法使设置n个输出节点,其中n为类别的个数。对每一个样例,神经网络可以得到一个n维数组作为输出结果。数组中的每一个维度(也就是每一个输出节点)对应一个类别。在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出都为0。如MNIST手写识别one_hot编码,对应位数的数值为1, [0,0,1,0,0,0,0,0,0,0,0],神经网络模型的输出结果越接近 [0,0,1,0,0,0,0,0,0,0,0]越好。

那么判断一个输出向量和期望的向量有多接近,交叉熵(cross entropy)是常用的评判方法之一。交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广的一种损失函数。

给定两个概率分布p和q,通过q来表示p的交叉熵为:

H(p,q)=xp(x)log q(x) H ( p , q ) = − ∑ x p ( x ) l o g   q ( x )

交叉熵刻画的是两个概率分布之间的距离,然而神经网络的输出却不一定是一个概率分布。概率分布刻画了不同事件发生的概率。当时间总数是有限的情况下,概率分布函数 p(X=x) p ( X = x ) 满足:

x p(X=x)[0,1]xp(X=x)=1 ∀ x   p ( X = x ) ∈ [ 0 , 1 ] 且 ∑ x p ( X = x ) = 1

也就是说,任意事件发生的概率都在0和1之间,且总有某一个事件发生(概率的和为1),那么训练数据的正确答案就符合一个概率分布。将神经网络前向传播得到的结果也变为概率分布,Softmax回归就是一个非常常用的方法。

在Tensorflow中,Softmax回归的参数被去掉了,它只是一层额外的处理曾,将神经网络的输出变成一个概率分布。

[y1,y2,...,yn]Softmax[Softmax]eyinj=1eyj 原 始 输 出 [ y 1 , y 2 , . . . , y n ] → S o f t m a x 层 [ S o f t m a x ] → e y i ∑ j = 1 n e y j

将神经网络的输出转化为概率分布,从而可以通过交叉熵来计算预测的概率分布和真实答案的概率分布之间的距离了。

交叉熵函数不是对称的( H(p,q)!=H(q,p) H ( p , q ) ! = H ( q , p ) ),它刻画的是通过概率分布q来表达概率分布p的困难程度。当交叉熵作为神经网络的损失函数时,p代表的时正确答案,q代表的是预测值。熵值越小,两个概率分布越接近。

交叉熵函数表示:

cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
#tf.clip_by_value(y, 1e-10, 1.0) : 防止出现log0的情况以及大于1的概率
#tf.reduce_mean : 取所有元素的平均值

因为交叉熵一般会与Softmax回归一起使用,所以有 tf.nn.softmax_cross_entropy_with_logits 函数对这两个功能进行了统一封装,可通过下面代码实现使用了softmax回归之后的交叉熵损失函数:

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)
#y:原始神经网络的输出结果
#y_:标准答案

(2)回归问题

回归问题解决的是对具体数值的预测,比如房价、销量预测等都是回归问题。解决回归问题的神经网络一般只有一个输出节点(与多分类问题相对应),这个节点的输出值就是预测值。对于回归问题,最长采用的损失函数是均方误差(MSE,mean squard error)。它的定义如下:

MSE(y,y)=ni=1(yiy)2n M S E ( y , y ′ ) = ∑ i = 1 n ( y i − y ′ ) 2 n

其中 yi y i 为一个batch中第 i i 个数据的正确答案 。而yi为神经网络给出的预测值。

mse = tf.reduce_mean(tf.square(y_ - y))#均方误差
#y_ : 标准答案 y : 神经网络的输出答案

2、自定义损失函数 - 具体问题具体定义损失函数以及不同损失函数对训练结果的影响


当涉及到最大化问题时,比如最大化利润,需要对预测结果在大于真实值或小与真实值时,有不同损失系数的损失函数:

这里写图片描述

yi y i 为第一个batch中第 i i 个数据的正确答案,y为神经网络得到的预测值, a a b是常量。两个常量作为惩罚系数。如下代码实现:

loss = tf.reduce_sum(tf.where(tf.greater(v1, v2), (v1 - v2) * a, (v2 - v1) * b))
#tf.greater:如果第一个值大于第二个值,返回True,否则返回False
#tf.where:以第一个参数为选择条件根据,当选择条件为True时,tf.selecth桉树会选择第二个参数中的值,否则使用第三个参数中的值(tf.select是旧版本函数,已废弃)

定义了损失函数之后,下面通过一个简单的神经网络程序来讲解损失函数对模型训练结果的影响。下面的程序中,实现了一个拥有两个输入结点、一个输出节点,没有隐藏层的神经网络,用来预测商品销量,如果预测多了,商家损失的是生产商品的成本;如果预测少了,损失的是商品利润。最小化该损失函数。

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)
#加入随机变量rdm.rand()/10.0 - 0.05,表示不可预测的噪音。这里的噪音设置为 -0.05~0.05的随机数
#rand-生成 0~1之间均值为0符合均匀分布的数值
Y = [[x1 + x2 + rdm.rand()/10.0 - 0.05] for (x1, x2) in X]

with tf.Session() as sess:
     tf.global_variables_initializer().run()
     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))

OUT:
[[ 1.01934695]
 [ 1.04280889]]

上述代码w1的值为[ 1.01934695,1.04280889],得到的结果会更大,因为在损失函数中指定预测少了的损失更大(loss_less > loss_more),所以模型更加偏向于预测多一些。

如果将损失函数换为MSE,模型会更加接近真实值,不会产生偏向。

In [29]: loss = tf.reduce_mean(tf.square(y - y_))

In [30]: train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

In [31]: with tf.Session() as sess:
    ...:     tf.global_variables_initializer().run()
    ...:     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))
    ...:
[[ 0.97437561]
 [ 1.0243336 ]]

三、神经网络优化算法 - 详细介绍反向传播算法(backpropagation)和梯度下降算法(gradient decent)


梯度下降:优化单个参数的取值
反向传播:给出了一个高效的方式在所有参数上使用梯度下降算法,是训练神经网络的核心算法。

梯度下降算法参数更新公式:

θn+1=θnηθnJ(θn) θ n + 1 = θ n − η ∂ ∂ θ n J ( θ n )

例,损失函数为 J(θ)=x2 J ( θ ) = x 2 ,对x的梯度为 =J(x)x=2x ∇ = ∂ J ( x ) ∂ x = 2 x ,那么梯度下降算法每次对参数x的更新公式为 xn+1=xnηn x n + 1 = x n − η ∇ n 。嘉定参数初始值为5,学习率为0.3,那么这个优化过程总结如下表:

这里写图片描述

这是一个简单样例,可以类推神经网络的优化过程。分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比得出两者之间的差距。然后再第二个阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。

神经网络优化框架:

batch_size = n

#每次读取一小部分数据作为当前的训练数据来执行反向传播算法
x = tf.placeholder(tf.float32, shape = (batch_size,2), name = 'x_input')
y = tf.placeholder(tf.float32, shape = (batch_size,1), name = 'y_input')

#定义神经网络结构和优化算法
loss = ...
train_step = tf.train.AdaOptimizer(0.001).minimize(loss)

#训练神经网络
with tf.Session() as sess:
    #参数初始化
    ...

四、神经网络进一步优化 - 在神经网络优化中经常遇到的几个问题


1、学习率的设置 - 指数衰减的方法设置学习率


指数衰减法。 tf.train.exponential_decay函数实现了指数衰减学习率。通过这个函数,可以先使用较大的学习率来加速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。该函数实现了以下代码的功能:

decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
#deayed_learning_rate : 每一轮优化时使用的学习率
#learning_rate : 初始学习率
#decay_rate : 衰减系数
#decay_steps : 衰减速度
#globel_step : 变量学习率

tf.train.exponential_decay(learning_rate, global_, decay_steps, decay_rate, staircase=False)

staircase = True时,成阶梯状衰减(将“global_step / decay_steps”设为整型);默认False为连续衰减

无尾

由下面代码进行展示:

import tensorflow as tf;  
import numpy as np;  
import matplotlib.pyplot as plt;  

learning_rate = 0.1  
decay_rate = 0.96  
global_steps = 1000  
decay_steps = 100  

global_ = tf.Variable(tf.constant(0))  
c = tf.train.exponential_decay(learning_rate, global_, decay_steps, decay_rate, staircase=True)  
d = tf.train.exponential_decay(learning_rate, global_, decay_steps, decay_rate, staircase=False)  

T_C = []  
F_D = []  

with tf.Session() as sess:  
    for i in range(global_steps):  
        T_c = sess.run(c,feed_dict={global_: i})  
        T_C.append(T_c)  
        F_d = sess.run(d,feed_dict={global_: i})  
        F_D.append(F_d)   
plt.figure(1)  
plt.plot(range(global_steps), F_D, 'r-')  
plt.plot(range(global_steps), T_C, 'b-')  

plt.show()  

2、过拟合问题的影响及解决它的主要方法


一个非常常用的方法是正则化(regularization)。正则化的思想就是在损失函数中加入刻画模型复杂程度的指标。假设用于刻画模型在训练数据上表现的损失函数为 J(θ) J ( θ ) ,那么不直接优化 J(θ) J ( θ ) ,而是优化 J(θ)+λR(w) J ( θ ) + λ R ( w ) 。其中 R(w) R ( w ) 刻画的是模型的复杂程度,而 λ λ 表示模型复杂损失在总损失中的比例。 θ θ 表示神经网络中的所有参数(包括边上的权重 w w 和偏置项 b )。一般来说模型复杂度只有权重 w w 决定。常用的刻画复杂度的函数 R(w) 有两种,一种是 L1 L 1 正则化,计算公式是:

R(w)=||w||1=i|wi| R ( w ) = | | w | | 1 = ∑ i | w i |

另外一种是 L2 L 2 正则化,计算公式是:

R(w)=||w||22=i|w2i| R ( w ) = | | w | | 2 2 = ∑ i | w i 2 |

无论哪一种正则化方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。在使用上 L2 L 2 相对更佳,一是 L2 L 2 正则化公式可导,对含有 L2 L 2 正则化损失函数的优化要更加简洁;二是 L2 L 2 正则化不会让参数变得更加稀疏(更多参数变为0)【这一点还未理解】

在实践中,也可以将 L1 L 1 正则化和 L2 L 2 正则化同时使用:

R(w)=iα|wi|+(1α)w2i R ( w ) = ∑ i α | w i | + ( 1 − α ) w i 2

TensorFlow同样可以优化带正则化的损失函数,以下代码给出了一个简单的带 L2 L 2 正则化的损失函数定义:

w = tf.Variable(tf.random_normal([2,1]), stddev = 1, seed = 1)
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l2_regularizer(lambda)(w)

#loss损失函数:均方误差 + $L2$ 正则化函数(用于防止模型过度拟合训练数据中的随机噪音)
#lambda : 正则化项权重
#w : 需要计算的正则化损失的参数

TensorFlow提供了 tf.contrib.layer.l2_regurizer 函数可以计算已给给定参数的 L2 L 2 正则化项的值,以下是样例:

weights = tf.constant([[1., -2.],[-3., 4.]])
with tf.Session() as sess:
    #输出 0.5 * (1+2+3+4) = 5
    print(sess.run(tf.contrib.layers_l1_regularizer(.5)(weights)))
    #输出 0.5 * (1 + 4 + 9 + 16)/2 = 7.5
    print(sess.run(tf.contrid.layer_l2_regularizer(.5)(weights)))

使用集合(collection),解决损失函数loss定义很长、可读性差且容易出错i的问题,以及定义网络结构部分和计算损失函数的部分不在同一个函数中的问题。
以下代码给出了通过集合计算一个5层神经网络带 L2 L 2 正则化的损失函数的计算方法。

import tensorflow as tf

#获取一层神经网络边上的权重,并将这个权重的 L2 正则化损失加入名称为‘losses’的集合中

def get_weight(shape, lamba):
    # 生成一个变量
    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(lamba)(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层全连接的神经网络结构.
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 激活函数
    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)的方法 —— 滑动平均模型。在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最终模型在测试数据上的表现。

在 TensorFlow 中提供了 tf.train.ExponentialMovingAverage 来实现滑动平均模型。

初始化参数decay:在初始化 ExponentialMovingAverage 时,需要提供一个衰减率(decay)。这个衰减率将用于控制模型更新的速度。

初始化参数num_updates : 来动态设置decay的大小。如果初始化时提供了num_updates,每次使用的衰减率将是:

min{decay,1+num_updates10+num_updates} m i n { d e c a y , 1 + n u m _ u p d a t e s 10 + n u m _ u p d a t e s }

变量参数shadow_variable : ExponentialMovingAverage 对每一个变量会维护一个影子变量(shadow_variable),这个影子变量的初始值就是相应变量的初始值,每次运行变量更新时,影子变量的值会更新为:

shadow_variable=decayshadow_variable+(1decay)variable_new 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 _ n e w
,其中 variable为待更新的变量。从公式中可以看出,decay决定了模型更新的速度,decay越大模型越趋于稳定。在实际应用中,decay一般会被设成非常接近1的数(like 0.99、0.999 ),为了使模型在训练前期可以更新的更快。

下面通过一段代码解释 ExponentialMovingAverage 如何被使用。

import tensorflow as tf
# 定义一个变量用于计算滑动平均,初始值为0
# 滑动平均的变量必须是实数型
v1 = tf.Variable(0,dtype = tf. float32)

# step变量模拟神经网络中的迭代次数,可以用于动态控制衰减率
step = tf.Variable(0, trainable = False)

#定义一个滑动平均的类(class)。初始化时给定了衰减率(0.99)和控制衰减率的变量 setp
ema = tf.train.ExponentialMovingAverage(0.99, step)

#定义一个更新变量滑动平均的操作,这里需要给定一个列表,每次执行这个操作时这个列表中的变量都会被更新
maintain_averages_op = ema.apply([v1])

with tf.Session() as sess:
    tf.global_variables_initializer().run()

    #通过 ema.average(v1) 获取滑动平均之后变量的取值。 在初始化之后变量 v1 的值和 v1 的滑动平均都为 0
    print(sess.run([v1, ema.average(v1)])) #输出 [0.0, 0.0]

    #更新变量 v1 的值 到 5
    sess.run(tf.assign(v1, 5))

    # 新  shadow_average =  decay * 新average + 1-decay * 旧average(shadw_average)
    #更新 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)])) #输出 [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+10000)/(10+10000) ≈ 0.999} = 0.99
    #所以 v1 的滑动平均会被更新为 0.99 * 4.5 + 0.01 * 10 = 4.555
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)])) # 输出 [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)]))

下一章,将给出真实在应用中使用滑动平均的样例


小节


这一章讲解了使用神经网络模型时需要考虑的主要问题。

从神经网络模型时结构的设计(非线性结构和多层设计)、

损失函数的设计(常用的损失函数、如何设计贴近实际问题的损失函数)、

神经网络的优化(梯度下降和反向传播)和

神经网络的进一步优化调优(指数衰减设置学习率、正则化解决过拟合、滑动平均模型使模型在位置数据上更加健壮)

四个方面覆盖了设计和优化神经网络过程中可能遇到的问题。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值