TensorFlow实战Google深度学习框架

时间:2022-12-23 13:50:32

深度学习介绍

传统机器学习算法:
输入—>人工特征提取—>权重学习—>预测结果
深度学习算法
输入—>基础特征提取—>多层复杂特征提取—>权重学习—>预测结果
Tensorflow的安装,个人认为比较好的安装教程:
http://www.jianshu.com/p/fd27a586f8c2
tensorflow安装成功的测试样例

import tensorflow as tf;
a=tf.constant([1.0,2.0],name="a");
b=tf.constant([2.0,3.0],name="b");
result=a+b;
sess=tf.Session()
print(sess.run(result))

Tensorflow入门

我用的tensorflow 1.4.0
计算图的概念
tensor:张量,用人话说:多维数组;表明了它的数据结构;
flow:表明了计算模型;直观地表达了张量之间通过计算相互转化的过程;Tensorflow中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系;
计算图的使用
Tensorflow一般分为两个阶段:

  1. 定义计算图中所有的计算
  2. 执行计算
    tf在加载过程中会自动地将定义的计算转化为计算图上的节点。在tf程序中,系统会自动维护一个默认的计算图。
    示例:获取默认计算图 tf.get_default_graph() 以及查看一个运算所属的计算图 a.graph
print(a.graph is tf.get_default_graph())   //输出为True

tf支持通过tf.Graph函数来生成新的计算图。不同计算图上的张量和运算都不会共享。
示例:在不同计算图上定义和使用变量

import tensorflow as tf
g1=tf.Graph()
g2=tf.Graph()
with g1.as_default():
    v=tf.get_variable("v1",[1],initializer=tf.zeros_initializer())

with g2.as_default():
    v=tf.get_variable("v2",[1],initializer=tf.ones_initializer())

with tf.Session(graph=g1) as sess:
    tf.global_variables_initializer().run() #初始化全局变量,这是一个坑,已经错过一次了
    with tf.variable_scope("",reuse=True):
        print(sess.run(tf.get_variable("v1")))

with tf.Session(graph=g2) as sess:
    tf.global_variables_initializer().run() #初始化全局变量,这是一个坑,已经错过一次了
    with tf.variable_scope("",reuse=True):
        print(sess.run(tf.get_variable("v2")))

Cannot execute operation using run(): No default session is registered. 初始化全局变量只有在指定会话时才需要使用
tf中的计算图不仅仅可以用来隔离张量和计算,还提供了管理张量和计算的机制

import tensorflow as tf

a=tf.constant([1.0,2.0],name="a");
b=tf.constant([2.0,3.0],name="b");
g=tf.Graph()
with g.device('gpu:0'):
    result=a+b#以上计算定义
    sess=tf.Session()
    print(sess.run(result)) #执行计算

数据模型——张量

张量是tf中管理数据的形式
张量的概念
tf中所有的数据都通过张量的形式来表示的;
从功能上来看:张量就是多维数组;零阶张量表示标量,一个数;第一阶张量为向量,一维数组;第n阶张量为一个n维数组;但是——》张量中没有真正保存数字,它保存的是如何得到这些数字的计算过程。
示例:得到对结果的一个引用

import tensorflow as tf
a=tf.constant([1.0,2.0],name="a");
b=tf.constant([2.0,3.0],name="b");
result=tf.add(a,b,name="add")
print(result)

Tensor(“add:0”, shape=(2,), dtype=float32)
张量中保存了:名字、维度、类型

  • 名字不仅是标量的唯一标识符,同样也给出了这个张量是如何计算出来的;node:src_output中node为节点的名称,src_output为当前张量来自节点的第几个输出;上例中result这个张量是计算节点add输出的第一个结果。
  • 张量的维度描述了一个张量的维度信息, shape=(2,)说明张量是一个一维数组,这个数组的长度为2。
  • 类型,每一个张量会有一个唯一的类型。tf会对所有参与计算的张量进行类型检查,类型不匹配时就会报错。一般建议通过dtype来明确指出变量或者常量的类型。tf支持的数据类型:实数(tf.float32,tf.float64),整数(tf.int8,tf.int16,tf.int32,tf.int64,tf.uint8),布尔型(tf.bool),复数(tf.complex64、tf.complex128)

如果想要得到结果:

import tensorflow as tf
a=tf.constant([1.0,2.0],name="a");
b=tf.constant([2.0,3.0],name="b");
result=tf.add(a,b,name="add")
#方法1
# sess=tf.Session()
# print(sess.run(result))
# sess.close()

# 方法2
# with tf.Session() as sess:
# print(sess.run(result))

#方法3
sess=tf.Session()
with sess.as_default():
    print(sess.run(result))
    print(result.eval(session=sess))

#方法4
# sess=tf.InteractiveSession()
# print(result.eval())
# sess.close()

张量的使用
张量使用主要可以总结为两大类

  1. 对中间计算结果的引用
    当一个计算包含很多中间结果时,使用张量可以大大提高代码的可读性(类似与java中设置多个变量,不赘述)。同时通过张量来存储中间结果,可以方便获取中间结果。
    在卷积神经网络中,卷积层或者池化层有可能改变张量的维度,通过result.get_shape来获取结果张量的维度信息可以免去人工计算的麻烦;

  2. 计算图构造完成后,张量可以用来获得计算机结果,也就是得到真实的数字。张量本身没有存储具体的数字,但是通过会话就可以得到这些具体的数字。

    tensorflow运行模型——会话
    会话拥有并管理tf程序运行时的所有资源。当所有计算完成后需要关闭会话来帮助系统系统回收资源,否则会出现资源泄露问题。
    tf使用会话有两种模式:

  3. 需要明确调用会话并生成函数和关闭函数
#方法1
sess=tf.Session()
sess.run()
sess.close()

上面这种模式,如果程序发生异常退出时,关闭会话的函数可能不被执行;此时,可以通过python的上下文管理器来使用会话:

# 方法2
with tf.Session() as sess:
    sess.run()
    #把所有的计算都放在with内部。上下文管理器退出时会自动释放所有资源

如前所述,tf会自动生成一个默认的计算图,但如果没有特殊指定,运算会自动加入这个到这个计算图中;
tf的会话也有类似的机制,但tf不会自动生成默认的对话,需要手动指定。默认的对话被指定后可以tf.Tensor.eval来计算一个张量的取值。
示例:通过设定默认会话计算张量的取值

#方法3
sess=tf.Session()
with sess.as_default():
    print(sess.run(result))
    #print(result.eval(session=sess))

交互式环境下,通过设置默认会话的方式来获取张量的取值更加方便。所以tf提供了一种在交互式环境下直接构建默认会话的函数——tf.InteractiveSession().这个函数会自动将生成的会话注册为默认会话

#方法4
sess=tf.InteractiveSession()
print(result.eval())
sess.close()

tf实现神经网络
介绍神经网络的主要功能以及计算流程,前向传播算法及其代码实现,通过tf中的参数表达神经网络中的参数,神经网络方向传播算法及原理,tf对反向传播算法的支持,一个完整的神经网络的例子;
tf游乐场及神经网络介绍
http://playground.tensorflow.org
特征提取(把实际问题变成平面上的一个点)。
在机器学习中,用于描述实体的数字的组合就是一个实体的特征向量,特征向量是神经网络的输入。
目前的主流神经网络都是分层的结构;第一层是输入层,代表特征向量中每一个特征的取值。每一层只和下一层连接,直到最后一层作为输出层得到计算结果;
输入层和输出层之间称为隐藏层;隐藏层的每一层有节点数,此外,还有学习率、激活函数、正则化都与学习效果密切相关。
使用神经网络解决分类问题的4个步骤:

  1. 提取问题中实体的特征向量作为神经网络的输入,不同实体可以提取不同的特征向量;
  2. 前向传播算法:定义神经网络的结构,并定义如何从神经网络的输入得到输出;
  3. 通过训练数据来调整神经网络中参数的取值,即训练神经网络。 后面会介绍表示神经网络参数的方法,神经网络优化算法的框架,如何通过tf实现这个框架;
  4. 使用训练好的神经网络来预测未知的数据。

前向传播算法简介
神经网络最后的输出是通过前向传播算法得到最后的输出的;
不同的神经网络结构前向传播的方式也不一样,本节介绍最简单的全连接网络结构的前向传播算法。
一个最简单的神经元结构:
TensorFlow实战Google深度学习框架

一个神经元有多个输入和一个输出/没个神经元既可以是其他神经元的输出,也可以是整个神经网络的输入。神经网络的结构就是不同神经元之间的连接结构。
神经网络的优化过程就是优化神经元中参数取值的过程。
图3-5:三层全连接神经网络:相邻两层之间任意两个节点之间都有连接
TensorFlow实战Google深度学习框架
上图中除了输入层之外的所有节点都代表了一个神经元的结构

计算神经网络的前向传播结果需要三部分信息:

  1. 神经网络的输入,即从实体中提取出的特征向量;
  2. 神经网络的连接结构。神经网络是由神经元构成的,神经网络的结构给出不同神经元之间输入输出的关系。后面把神经元统称为节点;
  3. 每个节点的参数
    给定神经网络的输入,神经网络的结构以及边上权重,就可以通过前向传播算法来计算出神经网络的输出。
    【小tip】
    tf中的矩阵乘法:
a=tf.matual(x,w1)

示例:tf实现三层全连接神经网络

import tensorflow as tf

#定义变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
# w1:[[-0.81131822 1.48459876 0.06532937] 2行3列,标准差为1,以1为种子生成矩阵 # [-2.4427042 0.0992484 0.59122431]]
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
# [[-0.81131822] 3行1列,标准差为1,以1为种子生成矩阵 # [ 1.48459876] # [ 0.06532937]]
x=tf.constant([[0.7,0.9]])  #创建一个常数张量

# with tf.Session() as sess:
#     tf.global_variables_initializer().run()
#     print(sess.run(x))
    # print(sess.run(w1))
    # print(sess.run(w2))


#定义前向传播的神经网络
a=tf.matmul(x,w1) #1*3矩阵
y=tf.matmul(a,w2) #1*1矩阵

#调用会话输出结果
sess=tf.Session()
sess.run(w1.initializer)
sess.run(w2.initializer)
print(sess.run(y))
sess.close()

print("-------------------------------------------------------------------------")
#使用placeholder
x=tf.placeholder(tf.float32,shape=(1,2),name="input")
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)
with tf.Session() as sess:
    init=tf.global_variables_initializer()
    sess.run(init)
    print(sess.run(y,feed_dict={x:[[0.7,0.9]]}))

print("-------------------------------------------------------------------------")
#增加多个输入
x=tf.placeholder(tf.float32,shape=(3,2),name="aaa")
a=tf.matmul(x,w1)#3*3矩阵
y=tf.matmul(a,w2)#3*1矩阵
sess=tf.Session()
init=tf.global_variables_initializer()
sess.run(init)
print(sess.run(y,feed_dict={x:[[0.7,0.9],[0.1,0.4],[0.5,0.8]]}))
sess.close()

文中的函数功能:

placeholder(
    dtype,
    shape=None,
    name=None
) 
Inserts a placeholder for a tensor that will be always fed.

Important: This tensor will produce an error if evaluated. Its value must be fed using the feed_dict optional argument to Session.run(), Tensor.eval(), or Operation.run().

神经网络参数与tf变量
本小节介绍tf如何组织、保存以及使用神经网络中的参数;
tf中的变量就是保存和更新神经网络中的参数的,变量也需要指定初始值,一般使用随机数给tf的变量初始化

x=tf.Variable(tf.random_normal([2,3],stddev=2)) #定义一个2*3矩阵,矩阵元素服从均值0,标准差为2的的正态分布
# [[-0.72172183 0.02641761 1.29989016] # [ 0.13485801 -1.26796138 -2.43703055]]

tf中的一些随机数生成器(注意:平均值指的是正态分布的均值,不是生成的元素的均值)
TensorFlow实战Google深度学习框架

tf也支持通过常数来设置初始值

import tensorflow as tf
from numpy import *

a=tf.zeros([2,3],int32);
b=tf.ones([2,2],float64)
c=tf.fill([2,2],88)#2*2矩阵,元素均为88
d=tf.constant([[1,2,3],[3,4,5]]) #产生给定值的常量


with tf.Session() as sess:
    print(sess.run(a))
    print(sess.run(b))
    print(sess.run(c))
    print(sess.run(d))

在神经网络中,偏置项通常会使用常数来设置初始值

import tensorflow as tf

bias=tf.Variable(tf.zeros([3]))

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

示例:通过变量实现神经网络的参数并实现前向传播的过程

import tensorflow as tf

#声明两个变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

#暂时将输入的特征向量定义为一个常量
x=tf.constant([[0.7,0.9]])

#前向传播算法获得神经网络的输出
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

# sess=tf.Session()
#
# sess.run(w1.initializer)#初始化w1
# sess.run(w2.initializer)#初始化w2
#
# print(sess.run(y))
# sess.close()

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

在计算y之前,需要将所有用到的变量初始化(上面的两个方法)

变量和张量的关系
变量是一种特殊的张量。
维度和类型是变量最重要的两个属性。
一个变量在构建后,它的类型就不能再改变了。

import tensorflow as tf

#声明两个变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1),name="yyx")
w2=tf.Variable(tf.random_normal([3,1],stddev=1,dtype=tf.float64,seed=1),name="wyy")
w1.assign(w2)  #tf.assign(A, new_number): 这个函数的功能主要是把A的值变为new_number

TypeError: Input 'value' of 'Assign' Op has type float64 that does not match type float32 of argument 'ref'.

维度可以在程序运行过程中改变,但需要通过设置参数validate_shape=False
示范:将维数不同的两个变量赋值

import tensorflow as tf

#声明两个变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1),name="yyx")
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1),name="wyy")
tf.assign(w1,w2,validate_shape=False)

改变维度在实践中很少使用,看看就好。。。。

通过tf训练神经网络模型
之前的神经网络的前向传播过程,所有变量的取值都是随机的。使用神经网络解决实际问题时,需要更好地设置参数取值。
在使用神经网络解决实际分类或者回归问题时,需要更好地设置参数值。本节使用监督学习的方式来更合理地设置参数取值,同时给出tf程序。
使用监督学习的方式设置神经网络参数需要有一个标注好的训练数据集。
神经网络优化算法中,最常用的方法是反向传播算法;反向传播算法具体工作原理在下节介绍。
这里主要介绍训练神经网络的主要流程以及tf对这个流程的支持。
TensorFlow实战Google深度学习框架
反向传播算法实现了一个迭代的过程;在每次迭代的开始,首先选取一小部分训练数据batch。
然后,batch样例会通过前向传播算法得到神经网络模型的预测结果。训练数据都是有正确答案标注的,所以可以计算出当前神经网络模型的预测答案与正确答案之间的距离。
最后,基于预测值和真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使batch上神经网络模型的预测结果和真实情况更加接近。


通过tf实现反向传播算法的第一步是使用tf表达一个batch数据。前面使用常量来表达batch的:

x=tf.constant([[0.7,0.9]])

如果每轮迭代都通过常量来表示,tf的计算图将会太大。
为了避免上述问题,tf提供了placeholder机制用于提供输入数据。placeholder相当于定义了一个位置,这个位置的数据在程序运行时再指定。这样,就不需要生成大量常量来提供输入数据,只需要将数据通过placeholder传入tf计算图。
定义placeholder时,数据类型需要指定,且类型不可以更改。placeholder中的维度信息可以根据提供的数据推导出来,不一定要给出来。
示例:通过placeholder实现前向传播算法

import tensorflow as tf

#声明两个变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1))

#定义placeholder作为存放输入数据的地方。维度不一定要定义
#如果维度是确定的,给定维度会降低出错概率
x=tf.placeholder(tf.float32,shape=(1,2),name="input")
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

sess=tf.Session()
init=tf.global_variables_initializer()
sess.run(init)
print(sess.run(y,feed_dict={x:[[0.7,0.9]]}))
sess.close()

在新的程序中计算前向传播结果时,需要提供一个feed_dict来指定x的值。feed_dict是一个字典,在字典中需要给出每个用到的placeholder的取值。
在训练神经网络时需要每次提供一个batch的训练样例。
如果将输入的1*2矩阵改为n*2的矩阵,就可以得到n个样例的前向传播结果。其中n*2的矩阵的每一个行为一个样例数据。这样前向传播的结果为n*1的矩阵,每一行就代表了一个样例的前向传播结果。
样例:一次性计算多个样例的前向传播结果。

import tensorflow as tf

#声明两个变量
w1=tf.Variable(tf.random_normal([2,3],stddev=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1))

#定义placeholder作为存放输入数据的地方。维度不一定要定义
#如果维度是确定的,给定维度会降低出错概率
x=tf.placeholder(tf.float32,shape=(3,2),name="input")
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

sess=tf.Session()
init=tf.global_variables_initializer()
sess.run(init)
print(sess.run(y,feed_dict={x:[[0.7,0.9],[0.1,0.4],[0.5,0.8]]}))
sess.close()

在得到一个batch的前向传播结果之后,需要定义一个损失函数来刻画当前的预测值和损失值之间的差距。通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。
损失函数和反向传播算法在第四章介绍
示例:定义简单的损失函数,通过tf定义了反向传播算法

#定义损失函数来刻画预测值和真实值的差距
cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
#定义学习率
learning_rate=0.001
#定义反向传播算法来优化神经网络的参数
train_step=tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

cross_entropy定义了真实值与预测值之间的交叉熵,这是一个常用的损失函数;
train_step定义了反向传播的优化算法,常用的优化算法有三种:tf.train.GradientDescentOptimizer、tf.train.AdamOptimizer、tf.train.MomentumOptimizer
定义完之后,运行sess.run(train_step)就可以对所有变量进行优化

完整神经网络示例
示例:训练神经网络解决二分类问题

import tensorflow as tf
from numpy.random import RandomState
#定义训练数据的大小
batch_size=8

#定义神经网络参数,沿用之前的网络结构
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))

#在shape的一个维度上使用None可以使用不大的batch大小。
#训练数据时需要把数据分成比较小的batch;测试时,需要一次性使用全部数据;
#当数据集比较小时这样比较方便测试
#当数据集比较大时,将大量数据放入一个batch可能会导致内存溢出
x=tf.placeholder(tf.float32,shape=(None,2),name="x-input")
y_=tf.placeholder(tf.float32,shape=(None,1),name="y-input")
#定义神经网络的前向传播过程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)

#定义损失函数和反向传播算法
cross_e=-tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y,1e-10,1.0))) #真实值与预测值之间的交叉熵,这是一个常用的损失函数
train_step=tf.train.AdamOptimizer(0.001).minimize(cross_e)  #定义反向传播的优化算法

#由随机数生成生成一个模拟数据集
rdm=RandomState(1) #RandomState公开了许多用于生成从各种概率分布中抽取的随机数的方法
data_size=128
X=rdm.rand(data_size,2)

#定义规则来给出样本的标签.此处,x1+x2<1的样例被认为是正样本(零件合格)
#其他样本不合格;
#此处0表示负样本,1表示正样本
#大部分解决分类问题的神经网络都会采用0和1的表示方法
Y=[[int(x1+x2 < 1)] for (x1, x2) in X]

with tf.Session() as sess:
    tf.global_variables_initializer().run()
    # print(sess.run(w1))
    # print(sess.run(w2))
    STEPS=5000
    for i in range(STEPS):
        #每次选取batch_size个样本进行训练
        start=(i*batch_size)%data_size #(i*8)%128
        end=(i*batch_size)%data_size+batch_size

        #通过选取的样本训练神经网络并更新参数
        sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
        if i%1000==0:
            #每隔一段时间计算在数据上的交叉熵并输出
            total_cross_en=sess.run(cross_e,feed_dict={x:X,y_:Y})
            print("第%d轮,所有数据上的交叉熵为%g"%(i,total_cross_en))

深层神经网络

本章介绍设计和优化神经网络。
4.1:介绍深度学习和深层神经网络的概念,用样例说明
4.2:介绍如何设定神经网络的优化目标,即损失函数。分别介绍分类问题和回归问题中比较常用的几种损失函数,用样例讲解如何通过对损失函数的设置,使神经网络优化的目标更加接近实际问题的需求。
4.3:介绍神经网络的反向传播算法,用tf框架来实现反向传播的过程
4.4:介绍神经网络优化中经常遇到的问题,并给出解决这些问题的具体方法

深度学习与深层神经网络
深度学习:一类通过多层非线性变换对高复杂性数据建模算法的集合
实际中深度学习就是深度神经网络。
下面将介绍线性变换存在的问题,以及为什么要在深度学习的定义中强调“复杂问题”。之后介绍如何实现去线性化,给出去线性化的tf程序;最后介绍一个具体的样例来说明深度网络可以比浅层网络解决更多问题;
一个线性模型中通过输入得到输出的函数称为线性变换。线性模型的最大特点就是任意线性模型的组合仍然是线性模型。前向传播算法实现的就是一个线性模型。
只通过线性变换,任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别,而且它们都是线性模型。
线性模型只能解决线性可分问题。
激活函数实现去线性化
将每一个神经元的输出通过一个非线性函数,那么整个神经网络的模型不再是线性的,这个非线性函数就是激活函数。
神经网络结构加上激活函数和偏置项后的前向传播算法的定义:
TensorFlow实战Google深度学习框架
上式比之前的前向传播算法的两个改变:
1. 增加了偏置项,偏置项是神经网络常用的一种结构;
2. 每个节点的取值不再是单纯的加权和,每个节点的输出在加权和的基础上还做了一个非线性变换
几种常用的非线性激活函数的函数图像
TensorFlow实战Google深度学习框架
tf目前提供了7中不同的非线性激活函数,tf.nn.relu,tf.sigmoid,tf.tanh是比较常用的
示例:实现神经网络的前向传播算法

tf.nn.relu(tf.matmul(x,w1)+biases1)
tf.nn.relu(tf.matmul(a,w2)+biases2)

由上可知,tf很好地支持了激活函数和偏置项的神经网络;
多层网络解决异或运算
这一节主要介绍多层变化问题。在神经网络的发展史上,异或问题是一个重要的问题。
感知机可以理解为单层的神经网络。感知机先将输入进行加权和,然后再通过激活函数最后得到输出。但是感知机无法模拟异或运算的功能。
异或运算直观来说如果两个输入的符号相同时输出为0,否则为1。
当加入隐藏层后,异或问题可以得到解决。深层神经网络实际上有组合特征提取的功能。这个特性对于解决不易提取特征向量的问题(如图片识别、语音识别)有很大帮助。因此深度学习在这些问题上更易取得突破。

损失函数的定义

神经网络模型的效果以及优化的目标是通过损失函数来定义的。
4.2.1:讲解适用于分类问题和回归问题的经典损失函数,并通过tf实现这些损失函数
4.2.2:根据具体问题定义损失函数,通过具体样例来说明不同损失函数对训练结果的影响
经典损失函数
分类问题和回归问题是监督学习的两大种类。分类问题希望解决的是将不同的样本分到实现定义好的类别中。
在判断零点是否合格的二分类问题时,曾定义过一个有单个输入节点的神经网络。为了给出具体的分类结果,可以将0.5作为阈值。输出大于0.5都是合格的,输出小于0.5时不合格。通过神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到的一个n维数组作为输出节点。数组中的每一个维度对应一个类别。交叉熵是判断一个输出向量和期望的向量的接近程度的判断方法之一。
交叉熵刻画的是两个概率分布之间的距离,然而神经网络的输出却不一定是一个概率分布。交叉熵越小越好。
softmax回归可以将神经网络前向传播得到的结果变成概率分布。
softmax回归本身可以作为一个学习算法来优化分类结果,softmax回归的参数被去掉了,它只是一层额外的处理层,将神经网络的输出变成一个概率分布。
示例图:通过softmax层将神经网络输出变成一个概率分布。
TensorFlow实战Google深度学习框架
通过softmax回归处理后输出如上图所示。
原始神经网络的输出被用作置信度来生成新的输出,而新的输出满足概率分布的所有要求。新的输出可以理解为经过神经网络的推导,一个样例为不同类别的概率分别是多大。
交叉熵函数不是对称的。交叉熵值越小,两个概率分布越接近。
通过tf实现交叉熵的代码为

cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))

y_代表正确结果,y代表预测结果。上式中4个不同tf运算的说明:

  1. 通过tf.clip_by_value函数可以将一个张量中的数值限制在一个范围中,这样可以避免一些运算错误。
    示例:使用tf.clip_by_value的简单示例
import tensorflow as tf

v=tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
with tf.Session() as sess:
    print(tf.clip_by_value(v,2.5,4.5).eval())#v中小于2.5都用2.5来替代,大于4.5都用4.5来替代
#[[ 2.5 2.5 3. ] [ 4. 4.5 4.5]]
  1. tf.log函数,这个函数完成了对张量中所有元素依次求对数的功能
    示例:
import tensorflow as tf

v=tf.constant([[1.0,2.0,3.0]])
with tf.Session() as sess:
    print(tf.log(v).eval())
  1. 乘法: *:两个矩阵的对应元素相乘
    tf.matmul:矩阵乘法
    示例:两个操作的区别
import tensorflow as tf

v1=tf.constant([[1,2],[3,4]])
v2=tf.constant([[5,6],[7,8]])
with tf.Session() as sess:
    print((v1*v2).eval())
    print(tf.matmul(v1,v2).eval())

4.根据交叉熵的公式,应该将每行中的m个结果相加得到所有样例的交叉熵,再对这n行平均得到一个batch的平均交叉熵。分类问题的类别数量是不变的,所以可以直接对整个矩阵做平均而不改变计算结果的意义。这样的方式可以使整个程序更加简洁。
示例:tf.reduce_mean的用法

import tensorflow as tf

v=tf.constant([[1,2,3],[18,0,0]])
with tf.Session() as sess:
    print(tf.reduce_mean(v).eval())
# 4

交叉熵一般会和softmax一起使用,所以tf对这两个功能进行了统一封装,提供了tf.nn.softmax_cross_entropy_with_logits()
示例:实现使用softmax回归后的交叉熵损失函数

cross_entropy=tf.nn.softmax_cross_entropy_with_logits(y,y_)

y代表原始神经网络的输出结果,y_指的是正确答案。通过这样一个命令就可以得到使用了softmax回归之后的交叉熵。在只有一个正确答案的分类问题中,tf提供了tf.nn.sparse_softmax_cross_entropy_with_logits()函数来进一步加速计算过程。(下一章用到这一函数)
与分类问题不同,回归问题需要解决的是对具体数值的预测。这些问题需要预测的不是一个实现定义好的类别,而是一个任意实数。解决回归问题的神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方误差(MSE)。
示例:通过tf实现均方误差损失误差

mse=tf.reduce_mean(tf.square(y_-y))

y代表了神经网络的输出答案,y_代表了标准答案。
自定义损失函数
tf不仅支持经典的损失函数,还可以优化任意的自定义损失函数。
本小节介绍如何通过自定义损失函数的方法,使得神经网络优化的结果更加接近实际问题的需求。(以预测商品销量问题为例)
为了最大化预期利润,需要将损失函数和利润直接联系起来。损失函数定义的是损失。要将利润最大化,定义的损失函数应该刻画成本或者代价。
通过对自定义损失函数的优化,模型提供的预测值更有可能最大化收益。在tf中,可以通过以下代码实现这个损失函数

loss=tf.reduce_sum(tf.where(tf.greater(v1,v2),(v1-v2)*a,(v2-v1)*b))

tf.greater的输入是两个张量,此函数会比较这两个输入张量中每一个元素的大小,并返回比较结果。tf.select()函数中有三个参数。第一个为选择条件依据,True时,tf.where函数会选择第二个参数中的值,False使用第三个参数中的值。
示例:tf.where和tf.greater的用法

import tensorflow as tf
v1=tf.constant([1,2,3,4])
v2=tf.constant([4,3,2,1])
with tf.Session() as sess:
    print(tf.greater(v1,v2).eval())
    print(tf.where(tf.greater(v1,v2),v1,v2).eval())

示例:定义了损失函数后,通过一个简单的神经网络程序来讲解损失函数对模型训练结果的影响。在下列程序中,实现了一个拥有两个输入节点,一个输出节点,没有隐藏层的神经网络。

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)

#设置回归的正确值为两个输入的和加上一个随机量。加上随机量是为了加入不可预测的噪音,否则不同损失函数的意义就不大了
#因为不同损失函数都会在能完全预测正确的时候最低。一般来说,噪音为一个均值为0的小量,所以这里噪声设置为-0.05~0.05的随机数
Y=[[x1+x2+rdm.rand()/10.0-0.05] for (x1,x2) in X]

with tf.Session() as sess:
    tf.global_variables_initializer().run()
    for i in range(5000):
        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))

对于相同的神经网络,不同的损失函数会对训练得到的模型产生重要影响。
神经网络优化算法
本节介绍反向传播算法和梯度下降算法调整神经网络中参数的取值。
梯度下降算法主要用于优化单个参数的取值,反向传播算法是在所有参数上使用梯度下降法,使神经网络模型在训练数据上的损失函数尽可能小。
反向传播算法可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。
—>用人话说:
梯度下降法:优化单个参数值
反向传播算法:在所有参数上使用梯度下降法
本节将给出一个具体的样例解释使用梯度下降法优化参数取值的过程,还会介绍神经网络优化过程中可能遇到的问题和解决方法。
梯度下降法的思想:调整参数使损失值达到最小值。参数的梯度可以用求偏导的方法求得。此外,还需要定义一个学习率来定义每次更新的幅度。直观上说,学习率定义的就是每次参数移动的幅度。参数更新的公式为
TensorFlow实战Google深度学习框架

神经网络的优化过程可以分为两个阶段:

  1. 通过前向传播算法得到预测值,并将预测值和真实值做对比得到两者之间的差距
  2. 通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降法更新每一个参数。

注意: 梯度下降法不能确保被优化的函数达到全局最优解。在训练神经网络时,参数的初始化值会很大程度影响最后的结果。只有当损失函数为凸函数时,梯度下降法才能确保全局最优解。

梯度下降法另一个问题是它要在全部训练数据上最小化损失,因此它的计算时间太长。为了加速训练过程,可以使用随机梯度下降的算法。即在每一轮的迭代中,随机优化某一条训练数据上的损失函数。但是,在某一条数据上损失函数更小并不代表在全部数据上损失函数更小,于是使用随机梯度下降优化得到的神经网络甚至可能无法达到全局最优最优。
实际应用中,一般采取两种算法的折中——每次计算一小部分训练数据的损失函数。这一小部分数据称之为batch。通过矩阵运算,每次在一个batch上优化神经网络的参数并不会比单个数据慢太多,另一方面,使用一个batch可以大大减少收敛所需要的迭代次数,同时可以使收敛的结果更加接近梯度下降的效果。
示例:在tf中实现神经网络训练的一般过程

import tensorflow as tf

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.AdamOptimizer(0.001).minimize(loss)

#训练神经网络
with tf.Session() as sess:
    tf.global_variables_initializer().run() #参数初始化
    #迭代更新参数
    for i in range(Steps):
        #准备batch_size个训练数据,一般将所有训练数据随机打乱后再选取可以得到
        #更好的优化效果
        sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})

神经网络的进一步优化
本节介绍神经网络优化过程中可能遇到的一些问题,以及解决这些问题的常用方法。
4.4.1:介绍通过指数衰减的方法设置梯度下降算法中的学习率。通过指数衰减的学习率可以让模型在训练的前期较快接近较优解,又可以保证模型在训练后期不会有太大的波动,从而更加接近局部最优。
4.4.2:介绍过拟合问题的影响以及解决这个问题的主要方法
4.4.3:滑动平均模型。滑动平均模型会将每一轮迭代得到的模型综合起来,从而使得到的模型更加健壮。
学习率的设置
学习率决定了参数每次更新的幅度。学习率既不能过大,也不能过小。
tf提供了一种灵活的学习率设置方法——指数衰减法。tf.train.exponential_decay实现了指数衰减学习率。它可以先使用较大的学习率来快速获得一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。
tf.train.exponential_decay可以通过设置参数staircase选择不同的衰减方式。staircase默认值False,设置为True时,学习率会成为一个阶梯函数。
示例:tf中tf.train.exponential_decay的用法

global_step=tf.Variable(0)
#使用exponential_decay函数生成学习率
learning_rate=tf.train.exponential_decay(学习率,global_step,轮数,学习率乘的倍数(0.96),staircase=True)

过拟合问题
在真实的应用中,希望通过训练出来的模型对未知的数据给出判断。过拟合指的是模型很好的记忆了每一个训练数据中随机噪声的部分而忘记了要去学习训练数据中通用的趋势。
—>用人话说:过度拟合训练数据中的随机噪声虽然可以得到非常小的损失函数,但是对于未知数据可能无法做出可靠的判断
为了避免正则化,常用的方法是正则化。正则化的思想就是在损失函数中加入刻画模型复杂度的指标。刻画模型复杂度的函数有两种。
TensorFlow实战Google深度学习框架
两种正则化方法都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。实践中,常将两种正则化方法结合起来使用。
前面提过tf可以优化任意形式的损失函数,所以tf也可以优化带正则化的损失函数。
示例:带L2正则化的损失函数定义

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为定义的损失函数,有两个部分组成。第一个部分是均方误差损失函数;第二个部分是正则化,防止模型过度模拟训练数据中的随机噪声。lambda参数表示了正则化项的权重。tf提供了tf.contrib.layers.l2_regularizer函数,这个函数可以计算一个给定参数的L2正则化项的值。tf.contrib.layers.l1_regularizer函数可以计算L1正则化项的值。
示例:计算L1,L2正则化项的值

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.l1_regularizer(.5)(weights)))
    print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))

上述过程可以计算简单的神经网络中的正则化的损失函数。但是当神经网络中的参数增多之后,可能会导致损失函数loss的定义很长,可读性差而且容易出错。更糟糕的是,当网络结构变复杂后,定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,这样通过变量这种方式计算损失函数就不方便了。
此时,可以通过tf提供的集合。集合可以在一个计算图(tf.Graph)中保存一组实体(比如张量).
示例:通过集合计算一个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,5,3,1]
n_layers=len(layer_dimension)

#这个变量维护前向传播时最深层的节点,开始的时候就是输入层
cur_layer=x
# 当前层的节点个数
in_dimension=layer_dimension[0]

#通过一个循环来生成5层全连接的神经网络结构
for i in range(1,n_layers):
    # layer_demension[i]为下一层的节点数
    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.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'))

滑动平均模型
在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以提高性能。
tf中提供了tf.train.ExponentialMovingAverage来实现滑动平均模型。初始化这个函数时,需要提供一个衰减率。衰减率决定了模型更新的速度,衰减率越大模型越稳定。一般设为0.999或者0.9999
示例: ExponentialMovingAverage如何被使用

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:
    tf.global_variables_initializer().run()
    #通过ema.average(v1)获取滑动平均之后变量的取值。在初始化之后变量v1的值和v1的滑动平均都为0
    print(sess.run([v1,ema.average(v1)]))

    #更新变量v1的值为5
    sess.run(tf.assign(v1,5))
    #更新v1的滑动平均值。衰减率为min(0.99,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.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)]))
    #[10.0, 4.6094499]

5.MNIST数字识别问题

5.1:介绍mnist手写识别数据集,给出tf程序;
5.2:对比之前的神经网络结构设计和参数优化的不同方法,从实际问题中验证不同优化方法带来的性能提升。
5.3:介绍tf变量重用的问题和变量的命名空间
5.4:介绍如何将一个神经网络模型持久化,使得之后可以直接使用训练好的模型。
5.3,5.4:指出之前神经网络的不足之处,并介绍tf的最佳实践来解决这些不足。
5.5:通过一个完整的tf程序解决minst问题
MNIST数据处理
MNIST是著名的手写体数字识别数据集。这个数据集只提供了训练和测试数据,但为了验证模型训练的结果,一般会从训练数据中划分一部分数据作为验证数据。tf中专门提供了一个类来处理来处理MNIST数据。
示例:tf处理MNIST数据

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist=input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)

# #打印训练数据长度
print(mnist.train.num_examples)
#打印验证数据的长度
print(mnist.validation.num_examples)
#打印测试数据的长度
print(mnist.test.num_examples)

#查看training数据集中某个成员的像素矩阵生成的一位数组
print(mnist.train.images[0])
#查看training数据集中某个成员的像素矩阵属于的数字标签
print(mnist.train.labels[0])#属于数字7

训练数据和验证数据组成了MNIST本身提供的训练数据集
示例:input_data.read_data_sets中为了方便使用梯度下降法儿提供的函数,它可以从所有的训练数据中读取一小部分作为一个训练batch

from tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)
batch_size=100
#从训练数据中选取batch_size个训练数据
xs,ys=mnist.train.next_batch(batch_size)
#输出X的shape
print(xs.shape) #(100, 784)
#输出Y的shape
print(ys.shape) #(100, 10)

5.2 神经网络模型训练及不同模型结果对比

5.2.1:给出一个完整的tf程序来解决MNIST问题
5.2.2:介绍验证数据集在训练神经网络过程中的作用。神经网络在验证数据集上的表现可以近似作为评价不同神经网络模型的标准或者决定迭代轮数的依据。
5.2.3:通过MNIST数据集验证讲过的每一个优化方法
tf训练神经网络
在神经网络的结构上,深度学习一方面需要使用激活函数实现神经网络模型的去线性化,另一方面需要使用一个或多个隐藏层使得神经网络的结构更深,以解决复杂问题。前面,使用了带指数衰减的学习率设置、使用正则化来避免过度拟合以及使用滑动平均模型来使最终模型更加健壮。
示例:在MNIST数据集上实现上述功能