TensorFlow的使用1
1 TensorFlow 计算模型一一计算图
1.1 计算图的概念
-
TensorFlow 是一个通过计算图的形式来表述计算的编程系统 。计算图是 TensorFlow 中最基本的一个概念, TensorFlow 中的所有计算都会被转化为计算图上的节点 ,而节点之间的边描述了计算之间的依赖关系。TensorFlow 的基本计算模型如下图所示:
- 图 3 -1 中的每一个节点都是一个运算,而每一条边代表了计算之间的依赖关系 。如果一个运算的输入依赖于另 一个运算的输出,那么这两个运算有依赖关系。
- 每一个节点代表着一个操作(operation,Op),一般用来表示施加的的数学运算,也可以表示数据输入的起点以及输出的终点,或者是读取/写入持久变量(persistent veriable)的终点,Op也可用于状态初始化的任务.
-
TensorFlow 的名字中己经说明了它最重要的两个概念一一Tensor 和 Flow 。 Tensor 就是张量。 Flow 翻译成中文就是“流” 。
- 在 TensorFlow 中,张量可以被简单地理解为多维数组。
- Flow直观地表达了张量之间通过计算相互转化的过程 。
1.2 计算图的使用
- TensorFlow 程序一般可以分为两个阶段。在第一个阶段需要定义计算图中所有的计算 。 第二个阶段为执行计算 。
- 在 TensorFlow 程序中,系统会自动维护一个默认的计算图,通过 tf.get_default_graph 函数可以获取当前默认的计算图。 如果想查看一个张量a所属的计算图,可以使用a.graph。
- 除了使用默认的计算图, TensorFlow 支持通过tf.Graph函数来生成新的计算图。不同计算图上的张量和运算都不会共享。
- TensorFlow 中的计算图不仅仅可以用来隔离张量和计算,它还提供了管理张量和计算的机制。计算图可以通过 tf.Graph.device 函数来指定运行计算的设备。这为 TensorFlow 使用 GPU 提供了机制。 如果要指定计算运行的设备,如下所示:
a = tf.Graph()
# 指定计算运行的设备
with g.device('/gpu:0'):
result = a + b - 有效地整理 TensorFlow 程序中的资源也是计算图的一个重要功能。在一个计算图中,可以通过集合 (collection )来管理不同类别的资源。
- 通过 tf.add_to_collection 函数可以将资源加入一个或多个集合中。
- 通过 tf.get_collection 可以获取一个集合中的所有资源。
- 这里的资源可以是张量、变量或者运行TensorFlow程序所需的队列资源。
- 为了方便使用, TenorsFlow 自动管理了一些也最常用的集合如下表总结了最常用的几个 自动维护的集合。
集合名称 集合内容 使用场景
tf.GraphKeys.VARIABLES 所有变量 持久化 TensorFlow 模型
tf.GraphKeys.TRAINABLE_VARIABLES 可学习的变量(一般指神经网络中的参数) 模型训练、生成模型可视化内容
tf.GraphKeys.SUMMARIES 日志生成相关的张量 TensorFlow计算可视化
tf.GraphKeys.QUEUE_RUNNERS 处理输入的 QueueRunner 输入处理
tf.GraphKeys.MOVING_AVERAGE_VARIABLES 所有计算了滑动平均值的变量 计算变量的滑动平均值
2 TensorFlow 数据模型一一张量
2.1 张量的概念
- 在 TensorFlow程序中,所有的数据都通过张量的形式来表示。从功能的角度上看,张量可以简单的理解为多维数组。其中:
- 零阶张量表示标量( scalar ),也就是一个数;
- 第一阶张量为向量 (vector),也就是一个一维数组;
- 第 n 阶张量可以理解为一个 n 维数组 。
- 张量在TensorFlow中的实现并不是直接采用数组的形式,它只是对TensorFlow中运算结果的引用。在张量中并没有真正保存数字,它保存的是如何得到这些数字的计算过程。
- 以向量的加法为例,当运行如下代码时,并不会得到加法的结果,而会得到对结果的一个引用。
import tensorflow as tf
# tf.constant 是一个计算,这个计算的结果为一个张量 , 保存在变量 a 中。
a= tf .constant([l . O, 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)
""" - 从以上代码可以看出 TensorFlow 中的张量和 NumPy 中 的数组不同, TensorFlow 计算的结果不是一个具体的数字 , 而是一个张量的结构。
- 从上面代码的运行结果可以看出, 一个张量中主要保存了三个属性 : 名字( name )、维度( shape )和类型( type )。
- 张量的第一个属性name不仅是一个张量的唯一标识符 , 它同样也给出了这个张量是如何计算出来的。 因为TensorFlow 的计算都可以通过计算图 的模型来建立,而计算图上的每一个节点代表了 一个计算,计算的结果就保存在张量之中。所以张量和计算图上节点所代表的计算结果是对应的。 这样张量的命名就可以通过 “ node:src_output”的形式来给出。其中node为节点的名称,src一output 表示当前张量来自节点的第几个输出。
- 张量的第二个属性是张量的维度(shape)。这个属性描述了 一个张量的维度信息。比如上面样例中 shape=(2,)说明了张量 result 是一个一维数组 , 这个数组的长度为 2。
- 张量的第三个属性是类型(type ) ,每一个张量会有一个唯一的类型。TensorFlow 会对参与运算的所有张量进行类型的检查,当发现类型不匹配时会报错。
- 如果不指定类型, TensorFlow 会给出默认的类型,比如不带小数点的数会被默认为 int32,带小数点的会默认为 float32 。
- 因为使用默认类型有可能会导致潜在的类型不匹配问题,所以一般建议通过指定 dtype 来明确指出变量或者常量的类型 。
- TensorFlow 支持 14 种不同的类型, 主要包括了实数( tf.float32 、 tf.float64 )、整数( tf.int8 、 tf.intl 6 、 tf.int32 、 tf.int64 、 tf.uint8 )、布尔型(tf.bool) 和复数( tf.complex64 、tf.complex128 )。
- 以向量的加法为例,当运行如下代码时,并不会得到加法的结果,而会得到对结果的一个引用。
2.2 张量的使用
- 张量使用主要可以总结为两大类 :
- 第一类用途是对中间计算结果的引用。当一个计算包含很多中间结果时,使用张量可以大大提高代码的可读性。
- 当计算的复杂度增加时(比如在构建深层神经网络时〉通过张量来引用计算的中间结果可以使代码的可阅读性大大提升 。
- 同时,通过张量来存储中间结果可以方便获取中间结果 。 比如在卷积神经网络中,卷积层或者池化层有可能改变张量的维度,通过 result.get_shape 函数来获取结果张量的维度信息可以免去人工计算的麻烦。
- 第二类用途是当计算图构造完成之后,张量可以用来获得计算结果,也就是得到真实的数字。 虽然张量本身没有存储具体的数字 ,但是通过会话,就可以得到具体的数字。比如可以使用tf.Session().run()语句得到计算结果。
- 第一类用途是对中间计算结果的引用。当一个计算包含很多中间结果时,使用张量可以大大提高代码的可读性。
3 TensorFlow 运行模型一一会话
- TensorFlow中的会话(session)用来执行定义好的运算。会话拥有并管理TensorFlow程序运行时所有的资源。
- 在会话中,所有计算完成之后需要关闭会话来帮助系统回收资源,否则就可能出现资源泄露的问题。
- TensorFlow 中使用会话的模式一般有两种:
- 第一种模式需要明确调用会话生成函数和关闭会话函数,这种模式的代码流程如下。
# 创建一个会话。
sess = tf.Session()
# 使用这个创建好的会话来得到关心的运算的结果。比如可以调用 sess. run (result) ,
# 来得到张量 result 的取值。
sess.run (... )
# 关闭会话使得本次运行巾使用到的资源可以被释放。
sess.close ()- 使用这种模式时,在所有计算完成之后,需要明确调用 Session.close 函数来关闭会话并释放资源。 然而,当程序因为异常而退出时,关闭会话的函数可能就不会被执行从而导致资源泄漏 。
- 为了解决异常退出时资源释放的问题, TensorFlow 可以通过 Python 的上下文管理器来使用会话。以下代码展示了如何使用这种模式。
# 创建一个会话,并通过 Python 中的上下文管理器来管理这个会话。
with tf. Session() as sess :
#使用创建好的会话来计算关心的结果。
sess.run()
# 不需要再调用“ Session.close()”函数来关闭会话,
# 当上下文退出时会话关闭和资源释放也自动完成了。- 通过 Python 上下文管理器的机制,只要将所有的计算放在 “ with”的内部就可以 。 当上下文管理器退出时候会自动释放所有资源。这样既解决了因为异常退出时资源释放的问题,同时也解决了忘记调用 Session.close 函数而产生的资源泄漏。
- 第一种模式需要明确调用会话生成函数和关闭会话函数,这种模式的代码流程如下。
- TensorFlow 不会自动生成默认的会话,而是需要手动指定 。 当默认的会话被指定之后可以通过 tf.Tensor.eval 函数来计算一个张量的取值 。 实例代码如下:
# 创建一个会话
sess = tf.Session()
# 手动指定会话为默认会话
with sess.as_default():
print(result.eval()) - TensorFlow 提供了 一种在交互式环境下直接构建默认会话的函数。这个函数就是 tf.lnteractiveSession。使用这个函数会自动将生成的会话注册为默认会话。 实列如下:
# 在交互式环境下直接构建默认会话
sess = tf.InteractiveSession()
print(result.eval())
# 关闭会话
sess.close() - 通过 tf.InteractiveSession 函数可以省去将产生的会话注册为默认会话的过程。无论使用哪种方法都可以通过 ConfigProto来配置需要生成的会话 。下面给出了通过ConfigProto配置会话的方法 :
# 定义配置
config = tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True)
# 在交互式环境下直接构建默认会话,配置生成的会话
sessl = tf.InteractiveSession(config=config)
sess2 = tf.Session(config=config)- 通过 ConfigProto 可以配置类似并行的线程数、 GPU 分配策略、运算超时时间等参数。在这些参数中,最常使用的有两个 :
- 第一个是 allow_soft_placement,这是一个布尔型的参数,当它为 True 时, 在以下任意一个条件成立时, GPU 上的运算可以放到 CPU 上进行 :
- 运算无法在 GPU 上执行 。
- 没有 GPU 资源(比如运算被指定在第 二个 GPU 上运行 ,但是机器只有一个 GPU ) 。
- 运算输入包含对 CPU 计算结果的引用 。
- 这个参数的默认值为 False,但是为了使得代码的可移植性更强,在有 GPU 的环境下
这个参数一般会被设置为 True。 通过将 allow_soft_placement 参数设为 True , 当某些运算无法被当前 GPU 支持时,可 以自动调整到 CPU 上,而不是报错。类似地,通过将这个参数设置为 True,可以让程序在拥有不同数量的 GPU 机器上顺利运行。 - 第二个使用得比较多的配置参数是 log_device_placement 这也是一个布尔型的参数, 当它为 True 时日志中将会记录每个节点被安排在哪个设备上以方便调试。而在生产环境中将这个参数设置为 False 可以减少日志量。
- 第一个是 allow_soft_placement,这是一个布尔型的参数,当它为 True 时, 在以下任意一个条件成立时, GPU 上的运算可以放到 CPU 上进行 :
- 通过 ConfigProto 可以配置类似并行的线程数、 GPU 分配策略、运算超时时间等参数。在这些参数中,最常使用的有两个 :
4 TensorFlow变量
4.1 TensorFlow中变量的概念
- 变量维护图执行过程中的状态信息。变量通过tf.Variable 类以及tf.get_variable()函数进行操作。变量的特点有:
- 存储持久化
- 可修改值
- 可指定被训练
4.2 创建和使用变量
-
tf.Variable(initial_value=None,trainable=True,collections=None,name=None)
- initial_value:初始化的值
- trainable:是否被训练
- collections:新变量将添加到列出的图的集合中
-
在 TensorFlow 中,所有的变量都会被自动地加入到 GraphKeys .VARIABLES这个集合中。
-
通过tf.global_variables()函数可以拿到当前计算图上所有的变量。拿到计算图上所有的变量有助于持久化整个计算图的运行状态 。
-
当构建机器学习模型时,比如神经网络,可以通过变量声明函数中的 trainable参数来区分需要优化的参数(比如神经网络中的参数)和其他参数(比如选代的轮数)。如果声明变量时参数 trainable 为 True ,那么这个变量将会被加入到GraphKeys.TRAINABLE_VARiABLES 集合。
-
在TensorFlow中可以通过tf.trainable_variables函数得到所有需要优化的参数 。 TensorF!ow 中提供的神经网络优化算法会将GraphKeys.TRAINABLE_VARIABLES集合中的变量作为默认的优化对象 。
-
在 TensorFlow 中,类 (tf.Variable )的作用就是保存和更新神经网络中的参数 。和其他编程语言类似,TensorFlow中的变量也需要指定初始值 。 因为在神经网络中, 给参数赋予随机初始值最为 常见,所以一般也使用随机数给 TensorFlow 中的变量初始化 。
- 下面一段代码给出了 一种在 TensorFlow中声明一个2X3的矩阵变量的方法 :
weights = tf.Variable(tf.random_normal([2,3],mean=0,stddev=2)) - tf.random_normal([2, 3],mean=0, stddev=2)会产生一个2X3的矩阵,矩阵中的元素是均值为0,标准差为2的随机数。
- 下面一段代码给出了 一种在 TensorFlow中声明一个2X3的矩阵变量的方法 :
-
除了使用随机数或者常数,TensorFlow 也支持通过其他变量 的初始值来初始化新的变量。 以下代码给出了具体的方法 。
w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value()*2.0)- 以上代码中, w2 的初始值被设置成了与 weights 变量相同。 w3 的初始值则是 weights初始值的两倍。
-
在 TensorFlow 中,一个变量的值在被使用之前,这个变量的初始化过程需要被明确地调用 ,即变量在使用之前需要初始化。 TensorFlow 提供了一种便捷的方式来完成变量初始化过程。以下程序展示了通过 tf.global_variables_initializer 函数实现初始化所有变量的过程 。
init_op = tf.global_variables_initializer()
sess.run(init_op)- 通过tf.global_variables_initializer 函数,就不需要将变量一个一个初始化了 。 这个函数也会自动处理变量之间的依赖关系。
4.3 TensorFlow目前支持的所有随机数生成器
函数名称 随机数分布 主要参数
tf.random_normal 正太分布 平均值、标准差、取值类型·
tf.truncated_normal 正太分布,但如果随机出来的值偏离平均值超过 2个标准差,那么这个数将会被重新随机 平均值、标准差、取值类型
tf.random.uniform 均匀分布 最小、最大取值、取值类型
tf.random_gamma Gamma 分布 形状参数 alpha、尺度参数 beta 、 取值类型
4.4 TensorFlow中常用的常量声明方法
函数名称 功能 样例
tf.zeros 产生全0的数组 tf.zeros([2, 3), int32) > ([0, 0, 0], [0, 0, 0]]
tf.ones 产生全1的数组 tf.ones([2, 3], int32) -> [[1,1,1],[1,1,1]]
tf.fill 产生一个全部为给定数字的数组 tf.fill([2, 3), 9) ->[ [9, 9, 9],[9, 9, 9]]
tf.constant 产生一个给定值的常量 tf.constant([1,2,3])—>[1,2,3]
4.5 案例:使用变量实现一个简单的计数器.
# 创建一个变量为计数器, 初始化为标量 0.
state = tf.Variable(0, name="counter")
# 创建一个变量, 其作用是使state增加1
one = tf.constant(1)
new_value = tf.add(state, one)
# 更新计数器
update = tf.assign(state, new_value)
# 启动图后, 变量必须先经过变量初始化,
init_op = tf.global_variables_initializer()
# 启动图, 运行会话
with tf.Session() as sess:
# 运行变量初始化
sess.run(init_op)
# 打印 'state' 的初始值
print sess.run(state)
# 运行更新 'state', 并打印 'state'
for _ in range(3):
sess.run(update)
print sess.run(state)
- 常用的变量操作
- tf.assign(ref, value, validate_shape=None, use_locking=None, name=None)
- 将value 赋值给ref,并输出 ref
- tf.assign_add(ref,value,use_locking=None,name=None)
- 更新ref的值,通过增加value
- tf.assign(ref, value, validate_shape=None, use_locking=None, name=None)
4.6 命名空间与共享变量
-
共享变量的主要用途在一些网络当中的参数共享, 由于在TensorFlow当中,只要我们定义的OP,name参数指定一样,其实并不是同一个变量。如果想要达到重复利用变量的效果,我们就要使用tf.variable_scope()结合tf.get_variable()一起使用.
-
定义一个相同名字的变量
var = tf.Variable(name='var', initial_value=[4], dtype=tf.float32)
var_double = tf.Variable(name='var', initial_value=[4], dtype=tf.float32)<tf.Variable 'var:0' shape=() dtype=float32_ref>
<tf.Variable 'var_1:0' shape=() dtype=float32_ref> -
使用tf.variable_scope()修改OP命名空间
with tf.variable_scope("name"):
var = tf.Variable(name='var', initial_value=[4], dtype=tf.float32)
var_double = tf.Variable(name='var', initial_value=[4], dtype=tf.float32)<tf.Variable 'name/var:0' shape=() dtype=float32_ref>
<tf.Variable 'name/var_1:0' shape=() dtype=float32_ref> -
tf.get_variable共享变量
通过tf.get_variable的初始化与Variable参数一样,但是要是实现共享需要打开tf.variable_scope("name")中的reuse=tf.AUTO_REUSE参数。
# 打开共享参数
# 或者
# with tf.variable_scope("name") as scope:
# 在需要使用共享变量的前面定义: scope.reuse_variables()
with tf.variable_scope("name", reuse=tf.AUTO_REUSE):
var = tf.Variable(initial_value=4.0, name="var", dtype=tf.float32)
var_double = tf.Variable(initial_value=4.0, name="var", dtype=tf.float32)
var1 = tf.get_variable(initializer=tf.random_normal([2, 2], mean=0.0, stddev=1.0),
name="var1",
dtype=tf.float32)
var1_double = tf.get_variable(initializer=tf.random_normal([2, 2], mean=0.0, stddev=1.0),
name="var1",
dtype=tf.float32)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(var1)
print(var1_double)
5 TensorFlow模型的保存和恢复
5.1 什么是Tensorflow模型?
- 训练神经网络后,需要将它保存以备将来使用和部署到生产环境。Tensorflow模型主要包含我们神经网络的网络图和已经训练好的变量参数。因此,Tensorflow模型主要有两个文件:
- 元数据图(meta graph):
- 它保存了tensorflow完整的网络图结构。这个文件以.meta为扩展名。
- 检查点文件(checkpoint file):
- *.ckpt文件:这是一个二进制文件,它包含的权重变量,biases变量和其他变量。
- *.data文件:包含我们的训练的变量的文件
- 名为checkpoint的文件:它不断的保存最新的检查点文件的记录。
- 元数据图(meta graph):
5.2 为什么要保存Tensorflow模型:
-
比如对图像分类训练卷积神经网络。作为一种标准的做法,在训练模型的时候,需要一直关注着模型损失值和模型准确度。一旦我们发现,网络已经收敛,就可以手动停止训练。训练完成后,我们希望将所有的变量和网络模型保存下来,供以后使用。
-
例如,假设您可以访问经过训练的 DNN,将图片分为 100 个不同的类别,包括动物,植物,车辆和日常物品。 您现在想要训练一个 DNN 来对特定类型的车辆进行分类。 这些任务非常相似,因此您应该尝试重新使用第一个网络的一部分(请参见图 11-4)。
- 如果新任务的输入图像与原始任务中使用的输入图像的大小不一致,则必须添加预处理步骤以将其大小调整为原始模型的预期大小。
5.3 如何保存Tensorflow模型:
-
Tensorflow中要保存训练好的网络模型和所有的变量和网络结构图,使用tf.train.Saver()
-
Tensorflow变量的作用范围是在一个session里面。在保存模型的时候,应该在session里面通过save方法保存。
saver.save(sess,'路径+名称'):sess是session对象,第二个参数是要保存的模型的路径与名称 -
如果我们希望在迭代i次之后保存模型,可以把当前的迭代步数传进去:
每隔一百步保存一次模型
if i % 100 == 0:
saver.save(sess, 'my_test_model',global_step=i)利用global_step参数可以把迭代的步数追加到文件名中
-
在多次保存模型时,若保存的文件中变化的只是变量,而网络结构没有变化,则没有必要重复保存.meta文件,可以使用如下方式,让网络结构只保存一次。
saver.save(sess,'my_model',global_step=step,write_meta_graph=False)- write_meta_graph 参数设置成False:可以让网络结构只保存一次,不重复保存.meta 文件。
-
如果只想保存最新的m个模型,并希望每n小时保存一次,可以使用
saver = tf.train.Saver(max_to_keep=m, keep_checkpoint_every_n_hour= n)- max_to_keep参数:设置保留最新的模型的个数
- keep_checkpoint_every_n_hours参数:设置间隔多少小时保存一次模型。
-
如果我们没有在tf.train.Saver()指定任何参数,这样就表示要保存所有的变量。如果我们不希望保存所有的变量,只是其中的一部分。我们可以指定要保存的变量或者集合。
- 在创建tf.train.Saver()的时候,我们把一个列表或者要保存变量的字典作为参数传进去。代码如下:
import tensorflow as tf定义变量OP
w1 = tf.Variable(tf.random_normal(shape=[2]),name='w1')
w2 = tf.Variable(tf.random_normal([5]),name='w2')创建模型保存对象,指定要保存的变量
saver = tf.train.Saver([w1,w2])
with tf.Session() as sess:
# 显示初始化变量OP
sess.run(tf.global_variables_initializer())
........
- 在创建tf.train.Saver()的时候,我们把一个列表或者要保存变量的字典作为参数传进去。代码如下:
5.4 如何导入已经保存的模型
如果想用别人的预先训练好的模型作为我们的模型的一部分,那么需要做两件事情:
- 创建网络结构图:
- 我们可以通过编写Python代码手动创建每一层和原始模型一样的网络结构图。但是,不要做重复的工作。
- 因为在.meta文件中,原始的网络结构已经保存在其中,我们只需要导入即可,无需重写一遍。
- 导入原始网络结构图的方式如下:
saver = tf.train.import_meta_graph('模型名.meta')
- 加载的(变量)参数
- 使用restore()方法恢复模型的变量参数
with tf.Session() as sess:
new_saver = tf.train.import_meta_graph('my_test_model-1000.meta')
new_saver.restore(sess, tf.train.latest_checkpoint('./'))
- 使用restore()方法恢复模型的变量参数
5.5 复用TensorFlow模型
-
如果原始模型使用 TensorFlow 进行训练,则可以简单地将其恢复并在新任务上进行训练:
[...] # 构建原始模型with tf.Session() as sess: # 还原模型 saver.restore(sess, "./my_model_final.ckpt") # 在新任务中继续训练模型... -
然而,通常我们只想要重用原有模型的一部分(我们马上就会讨论这种状况),一种简单的解决方案就是配置Saver使之在还原原模型时只还原所有参数的一个子集。举例来说,下面的代码就只还原了隐藏层1、隐藏层2和隐藏层3:
"""
首先我们建立新的模型,确保复制原始模型的隐藏层 1 到 3
"""
n_inputs = 28 * 28 # MNIST
n_hidden1 = 300 # reused
n_hidden2 = 50 # reused
n_hidden3 = 50 # reused
n_hidden4 = 20 # new!
n_outputs = 10 # new!X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X") y = tf.placeholder(tf.int64, shape=(None), name="y") with tf.name_scope("dnn"): hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1") # reused hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2") # reused hidden3 = tf.layers.dense(hidden2, n_hidden3, activation=tf.nn.relu, name="hidden3") # reused hidden4 = tf.layers.dense(hidden3, n_hidden4, activation=tf.nn.relu, name="hidden4") # new! logits = tf.layers.dense(hidden4, n_outputs, name="outputs") # new! with tf.name_scope("loss"): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits) loss = tf.reduce_mean(xentropy, name="loss") with tf.name_scope("eval"): correct = tf.nn.in_top_k(logits, y, 1) accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy") with tf.name_scope("train"): optimizer = tf.train.GradientDescentOptimizer(learning_rate) training_op = optimizer.minimize(loss) # 获取保存的模型中用trainable = True(这是默认值)创建的所有变量的列表 reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="hidden[123]") # 我们只保留那些范围与正则表达式hidden [123]相匹配的变量(即,我们得到所有可训练的隐藏层 1 到 3 中的变量)。 # 我们创建一个字典,将原始模型中每个变量的名称映射到新模型中的名称中(通常需要保持完全相同的名称) reuse_vars_dict = dict([(var.op.name, var) for var in reuse_vars]) # 创建一个SaverOP用来还原模型中的参数 restore_saver = tf.train.Saver(reuse_vars_dict) # to restore layers 1-3 init = tf.global_variables_initializer() # 创建一个新的SaverOP用来保存新模型 new_saver = tf.train.Saver() with tf.Session() as sess: init.run() # 从原始模型的层 1 到 3中恢复变量值 restore_saver.restore(sess, "./my_model_final.ckpt") for epoch in range(n_epochs): for iteration in range(mnist.train.num_examples // batch_size): X_batch, y_batch = mnist.train.next_batch(batch_size) sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images, y: mnist.test.labels}) print(epoch, "Test accuracy:", accuracy_val) # 保存新模型 save_path = new_saver.save(sess, "./my_new_model_final.ckpt") -
首先我们建立新的模型,确保复制原始模型的隐藏层 1 到 3。我们还创建一个节点来初始化所有变量。 然后我们得到刚刚用trainable = True(这是默认值)创建的所有变量的列表,我们只保留那些范围与正则表达式hidden [123]相匹配的变量(即,我们得到所有可训练的隐藏层 1 到 3 中的变量)。 接下来,我们创建一个字典,将原始模型中每个变量的名称映射到新模型中的名称(通常需要保持完全相同的名称)。 然后,我们创建一个Saver,它将只恢复这些变量,并且创建另一个Saver来保存整个新模型,而不仅仅是第 1 层到第 3 层。然后,我们开始一个会话并初始化模型中的所有变量,然后从原始模型的层 1 到 3中恢复变量值。最后,我们在新任务上训练模型并保存。
-
任务越相似,您可以重复使用的层越多(从较低层开始)。 对于非常相似的任务,您可以尝试保留所有隐藏的层,只替换输出层。
-
案例:识别手势数据集
"""
一天下午,我们和一些朋友决定教我们的电脑破译手语。我们花了几个小时在白色的墙壁前拍照,于是就有了了以下数据集。现在,你的任务是建立一个算法,使有语音障碍的人与不懂手语的人交流。训练集:有从0到5的数字的1080张图片(64x64像素),每个数字拥有180张图片。
测试集:有从0到5的数字的120张图片(64x64像素),每个数字拥有5张图片。
需要注意的是这是完整数据集的一个子集,完整的数据集包含更多的符号。下面是每个数字的样本,以及我们如何表示标签的解释。这些都是原始图片,我们实际上用的是64 * 64像素的图片。
"""
import numpy as np
import h5py
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.python.framework import ops
import time
import math
from functools import partial%matplotlib inline #如果你使用的是jupyter notebook取消注释
np.random.seed(1)
class GestureSymbolRecognition(object):
def init(self):
self.learning_rate = 0.0001
self.num_epochs = 1000
self.minibatch_size = 32
self.regularazion_rate = 0.001self.n_inputs = 12288 self.n_hidden1 = 200 self.n_hidden2 = 100 self.n_hidden3 = 100 self.n_outputs = 6 self.batch_norm_momentum = 0.9 def load_data(self): """ 加载数据集 :return: """ # 从文件中读取训练集数据 train_dataset = h5py.File('datasets/train_signs.h5', "r") # 从训练集数据中提取特征值与标签值数据 train_set_x_orig = np.array(train_dataset["train_set_x"][:]) train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # 从文件中读取测试集数据 test_dataset = h5py.File('datasets/test_signs.h5', "r") # 从测试集数据中提取特征值与标签值数据 test_set_x_orig = np.array(test_dataset["test_set_x"][:]) test_set_y_orig = np.array(test_dataset["test_set_y"][:]) classes = np.array(test_dataset["list_classes"][:]) # 类别列表 classes_num = len(classes) # 数据集标签分为几类 train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0])) test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0])) X_train_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1) # 每一列就是一个样本 X_test_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1) # 归一化数据 X_train = X_train_flatten / 255 X_test = X_test_flatten / 255 # 转换为one-hot编码矩阵 Y_train = self.convert_to_one_hot(train_set_y_orig, len(classes)).T Y_test = self.convert_to_one_hot(test_set_y_orig, len(classes)).T return X_train, Y_train, X_test, Y_test, classes_num def convert_to_one_hot(self, Y, C): """ 实现one-hot编码 :param Y:标签矩阵 :param C:分类数量 :return:one_hot:one-hot矩阵 """ one_hot = np.eye(C)[Y.reshape(-1)].T return one_hot def random_mini_batches(self, X, Y, mini_batch_size, seed=0): """ 从数据集中创建一个随机的mini-batch列表 :param X: 输入数据,维度为(输入节点数量,样本数量) :param Y: 对应的目标值,维度为(1,样本数量) :param mini_batch_size: 每个mini-batch的样本数量 :param seed: 随机数种子 :return: mini_batches:一个同步列表,维度为(mini_batch_X, mini_batch_Y) """ # 指定随机数种子 np.random.seed(seed) # 获取样本数 m = X.shape[0] # print('样本数量',m) # 创建一个同步列表 mini_batches = [] # 第一步:打乱样本的顺序 permutation = list(np.random.permutation(m)) # 返回一个长度为m的随机数组,且里面的数是0到m-1 shuffled_X = X[permutation, :] # 将每一列特征值数据按permutation的顺序来重新排列 shuffled_Y = Y[permutation, :] # .reshape((Y.shape[0], m)) # 将每一列的目标值数据按permutation的顺序来重新排列 # 第二步:分割训练集数据 num_complete_minibatches = math.floor(m / mini_batch_size) # 迭代完成整个训练集的次数 for k in range(0, num_complete_minibatches): mini_batch_X = shuffled_X[k * mini_batch_size:(k + 1) * mini_batch_size, :] mini_batch_Y = shuffled_Y[k * mini_batch_size:(k + 1) * mini_batch_size, :] mini_batch = (mini_batch_X, mini_batch_Y) mini_batches.append(mini_batch) # 如果训练集的大小不是mini_batch_size的整数倍,那么最后肯定会剩下一些,取剩余的数据 if m % mini_batch_size != 0: # 获取剩余部分的数据 mini_batch_X = shuffled_X[mini_batch_size * num_complete_minibatches:, :] mini_batch_Y = shuffled_Y[mini_batch_size * num_complete_minibatches:, :] mini_batch = (mini_batch_X, mini_batch_Y) mini_batches.append(mini_batch) # 返回同步列表 return mini_batches def train(self, print_cost=True, is_plot=True): """ 实现一个三层的TensorFlow神经网络训练逻辑:LINEAR->RELU->LINEAR->RELU->LINEAR->SOFTMAX 参数: print_cost - 是否打印成本,每100代打印一次 is_plot - 是否绘制曲线图 """ # 加载数据集 x_train, y_train, x_test, y_test, label_classes_num = self.load_data() print("训练集样本数 = " + str(x_train.shape[0])) print("测试集样本数 = " + str(x_test.shape[0])) print("X_train.shape: " + str(x_train.shape)) print("Y_train.shape: " + str(y_train.shape)) print("X_test.shape: " + str(x_test.shape)) print("Y_test.shape: " + str(y_test.shape)) # 设置图级别的随机数种子 tf.set_random_seed(1) # 设置操作级别的随机数种子 seed = 3 # 获取输入节点数量和样本数 (m, n_x) = x_train.shape # 成本函数值列表 costs = [] # 给X和Y创建占位符节点 X = tf.placeholder(tf.float32, shape=(None, self.n_inputs), name="X") Y = tf.placeholder(tf.int32, shape=(None, label_classes_num), name="y") # 定义神经网络全连接层 with tf.name_scope("dnn"): # 使用He权重初始化方法初始化权重 he_init = tf.contrib.layers.variance_scaling_initializer(mode='FAN_AVG') # 定义全连接层,使用elu激活函数 hidden1 = tf.layers.dense(X, self.n_hidden1, name="hidden1", activation=tf.nn.elu, kernel_initializer=he_init) hidden2 = tf.layers.dense(hidden1, self.n_hidden2, name="hidden2", activation=tf.nn.elu, kernel_initializer=he_init) hidden3 = tf.layers.dense(hidden2, self.n_hidden3, name="hidden3", activation=tf.nn.elu, kernel_initializer=he_init) outputs = tf.layers.dense(hidden3, self.n_outputs, name="outputs") # 输出预测的类别 y_proba = tf.nn.softmax(outputs) # 定义损失函数计算损失 with tf.name_scope('loss'): # 前向传播要在Z3处停止,因为在TensorFlow中最后的线性输出层的输出作为计算损失函数的输入,所以不需要A3. xentropy = tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=outputs) loss = tf.reduce_mean(xentropy, name="loss") # 定义优化器 with tf.name_scope("train_optimizer"): optimizer = tf.train.AdamOptimizer(self.learning_rate) training_op = optimizer.minimize(loss) # 评估模型,使用准确性作为我们的绩效指标 with tf.name_scope("eval"): # 计算当前的预测结果 # 检测使用了滑动平静模型的神经网络前向传播是否正确。tf.argmax(average_y,1) # 计算每一个样例的预测答案。其中average_y是一个batch_size*10的二维数组,每 # 一行表示案例向前传播的结果。tf.argmax的第二个参数为1,表示选取最大值的 # 操作只在第一个维度上进行(x轴上),也就是说只在每一行选取最大值对应的下标 # 于是得到的结果是一个长度为batch的一维数组,这个一维数组中的值就表示了每 # 一个数字对应的样例识别的结果.tf.equal()判断每个Tensor的每一维度是否相同 # 如果相等返回True,否则返回False. correct_prediction = tf.equal(tf.argmax(y_proba, 1), tf.argmax(Y, 1)) # 计算准确率 # 这个运算首先将一个布尔型的值转换为实数型,然后计算平均值。这一个平均值 # 就代表模型在这一组数据上的正确率 accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) """ 首先我们建立新的模型,确保复制原始模型的隐藏层 1 到 2 """ # 获取保存的模型中用trainable = True(这是默认值)创建的所有变量的列表 # 我们只保留那些范围与正则表达式hidden [123]相匹配的变量(即,我们得到所有可训练的隐藏层 1 到 3 中的变量)。 reuse_vars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='hidden[12]') # 我们创建一个字典,将原始模型中每个变量的名称映射到新模型中的名称中(通常需要保持完全相同的名称) reuse_vars_dict = dict([(var.op.name, var) for var in reuse_vars]) # 创建一个SaverOP用来还原模型中的参数 restore_saver = tf.train.Saver(reuse_vars_dict) # 创建初始化所有变量的节点 init = tf.global_variables_initializer() # 创建一个新的SaverOP用来保存新模型 new_saver = tf.train.Saver(max_to_keep=1) # 开始会话并计算 with tf.Session() as sess: # 初始化所有变量 sess.run(init) # 加载模型,从模型中找出与当前训练的模型代码当中(名字一样的OP操作),覆盖原来的值 checkpoin = tf.train.latest_checkpoint('./ckpt/') # 如果模型已经保存则加载模型 if checkpoin: # 从原始模型恢复变量值 restore_saver.restore(sess, './ckpt/my_test1_model.ckpt') # 正常训练模型 for epoch in range(self.num_epochs): epoch_cost = 0 # 每轮的成本函数值 num_minibatches = math.floor(m / self.minibatch_size) # 分成多少个mini-batch组 seed = seed + 1 minibatches = self.random_mini_batches(x_train, y_train, self.minibatch_size, seed) for minibatch in minibatches: # 选择一个minibatch (minibatch_X, minibatch_Y) = minibatch # 数据已经准备好了,开始运行session _, minibatch_cost = sess.run([training_op, loss], feed_dict={X: minibatch_X, Y: minibatch_Y}) # 计算这个minibatch在这一代中所占的误差 epoch_cost = epoch_cost + minibatch_cost / num_minibatches if epoch % 5 == 0: # 将每轮迭代的代价函数值存入列表 costs.append(epoch_cost) # 打印每轮迭代的代价函数值: if print_cost and epoch % 100 == 0: print("epoch = " + str(epoch) + " epoch_cost = " + str(epoch_cost)) # 保存学习后的参数 new_saver.save(sess, './ckpt/my_test1_model.ckpt') # 绘制代价函数值的学习曲线 if is_plot: plt.plot(np.squeeze(costs)) plt.ylabel('cost') plt.xlabel('iterations (per tens)') plt.title("Learning rate =" + str(self.learning_rate)) plt.show() # 计算模型学习完成后的训练集及测试集准确率 train_acc = accuracy.eval({X: x_train, Y: y_train}) test_acc = accuracy.eval({X: x_test, Y: y_test}) print("训练集的准确率:", train_acc) print("测试集的准确率:", test_acc)if name == 'main':
# 开始时间
start_time = time.clock()
# 开始训练
nn = GestureSymbolRecognition()
nn.train()
# 结束时间
end_time = time.clock()
# 计算时差
print("CPU的执行时间 = " + str(end_time - start_time) + " 秒")
5.6 冻结底层
- 如果一个深度神经网络的的低层已经学会了检测图像中的低级特征,这对于另一个图像分类任务来说是有用的。因此可以重用这些图层。训练新的DNN时,冻结图层的权重是一个比较好的方法。
- 为了在训练中冻结低层,最简单的方法就是给优化器列出要训练的变量列表,列表之外的变量即被冻结,不会被训练。代码如下:
# 获取所有的需要训练的隐藏层的权重变量
train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope='hidden[34]|outputs')
# 把可训练变量的列表传给优化器的minimize()函数,未出现在列表中的图层即被冻结了。
training_op = optimizer.minimize(loss,var_list=train_vars)
5.7 缓存冻结层
-
因为冻结层不会变化,所以就有可能将每一个训练实例的最高冻结层的输出缓存起来。由于训练会轮询整个数据集很多次,所以你将获得巨大的速度提升,因为你只需要在一个训练实例中遍历一次冻结层(而不是每个全数据集一次)。举个例子,你可以第一次在低层跑完整个训练集(假设你有足够的RAM):
hidden2_outputs = sess.run(hidden2, feed_dict={X: X_train}) -
然后在训练中,你要做的不是构建批量的训练实例,而是批量构建隐藏层2的输出,并且将它们喂给训练操作:
import numpy as np
n_epochs = 100
n_batches = 500
for epoch in range(n_epochs):
shuffled_idx = rnd.permutation(len(hidden2_outputs))
hidden2_batches = np.array_split(hidden2_outputs[shuffled_idx], n_batches)
y_batches = np.array_split(y_train[shuffled_idx], n_batches)
for hidden2_batch, y_batch in zip(hidden2_batches, y_batches):
sess.run(training_op, feed_dict={hidden2: hidden2_batch, y: y_batch})- 最后一行运行的是之前定义好的训练操作(冻结层1和层2),然后把隐藏层2(对应批量目标)的批量输出传给它。因为我们把隐藏层2的输出传给TensorFlow,所以它不用尝试去评估它。
5.8 调整、丢弃或替换层
- 模型复用时,原始模型的输出层经常会被替换,因为对于新的任务,它基本没有用,甚至没有办法提供正确的输出个数。
- 同样,模型复用时,原始模型的高隐藏层没有低隐藏层的用处多,因为对于新的任务来说,最有效的高级特性可能和原始模型中的最有效特性差别很大。所以,模型复用时,我们应该找到正确的层数来重用。
- 首先应尝试冻结所有的复制的层,然后训练模型观察效果。接着尝试解冻一到两个顶部的隐藏层, 用反向传播进行调整来观察是否有改善。 训练数据越多, 越能解冻更多的层。
- 如果你一直不能获得好的效果, 而且训练数据很少, 那就尝试丢弃最高的一层或多层, 然后重新冻结剩下的隐藏层。 你可以一直迭代直到找到正确的重用层数。 如果你有很多训练数据, 你可以尝试替换顶部的隐藏层而不是丢弃它们, 甚至可以添加一些隐藏层
5.9 模型动物园
- 在哪里可以找到一个训练好的类似神经网络来完成一个你想要解决的任务呢? 首选当然是你自己已有的模型目录。 这是一个很好的方式, 可以保存你所有的模型, 方便你整理, 也方便你日后随时调用。另一个选择就是在模型动物园(Model Zoo) 里搜索。 很多人针对各种任务训练了很多机器学习的模型, 并且很慷慨地公开这些预训练的模型。
- TensorFlow在https://github.com/tensorflow/models上公开了自己的
模型动物园。 特别需要指出的是, 这个模型动物园包含了先进图片识
别的网络, 比如VGG、 Inception和ResNet(见第13章, 同时查看
models/slim目录) , 包括代码、 预训练模型, 以及下载流行图片数据集的工具 。 - 另一个流行的模型动物园是Caffe模型动物园(https://goo.gl/XI02X3) 。 其中也包括很多计算机视觉模型(比如:LeNet、 AlexNet、 ZFNet、 GoogLeNet、 VGGNet和inception) , 也都在各种数据集(比如ImageNet、 Places Database、 CIFAR10等) 上经过训练。
- Dasgupta写了一个转换器, 可以在https://github.com/ethereon/caffe-tensorflow找到。
- TensorFlow在https://github.com/tensorflow/models上公开了自己的
5.10 无监督的预训练
-
假设你要完成一个没有太多标记训练数据的复杂任务, 并且没有找到在近似任务上训练过的模型。
- 首先, 你肯定需要努力去收集更多的标记过的训练数据, 但是如果这个代价很高或者很难, 你仍然可以运行无监督的预训练 ,也就是说, 如果 你有一堆未标记的训练数据,那你可以逐层训练它们,从最低层开
始,然后向上,利用一种非监督特性检测算法比如受限玻尔兹曼机 或者自动编码器 。 - 每一层都是基于提前训练好的图层(除去被冻结的训练层)的输出进行训练。一旦所有层都用这个方式训练过之后,你就可以用监督学习的方式(即反向传播)来微调网络。
- 首先, 你肯定需要努力去收集更多的标记过的训练数据, 但是如果这个代价很高或者很难, 你仍然可以运行无监督的预训练 ,也就是说, 如果 你有一堆未标记的训练数据,那你可以逐层训练它们,从最低层开
-
在面对复杂任务处理、没有相似模型可重用、有极少标记过的训练数据但
是却有很多未标记的训练数据的情况下,无监督的预处理(现在通常使用自动编码器而不是RBM)是一个非常不错的选择。
5.11 辅助任务中的预训练
- 最后一个选择是在辅助任务中训练第一个神经网络, 你可以轻松获得或者生成标记过的训练数据, 然后重用该网络的低层来实现你的实际任务。 第一个神经网络的低层会学习可能被第二个神经网络重用的特征检测器。
- 举个例子, 如果你想构建一个人脸识别系统, 但是你只有每个人的几张照片, 很明显没办法训练一个好的分类器。 收集每个人成百上千张照片根本不可行。 但是, 你可以在网上收集很多随机的人像照片, 用它们可以训练第一个神经网络来检测两张不同的照片是否属于相同的人。 这个网络将学习优质的人脸特征检测器, 然后重用这个网络的低层,这样你就可以用很少的训练数据训练出一个优质的人脸分类器。
- 通常情况下,收集未标记训练示例会廉价很多,但是标记它们却很贵。针对这种情况,常用的技术是将所有示例都标记为“好”,然后通过破坏好的训练示例来生成新的训练实例,并把那些被破坏的实例标记为“坏”。接着你就可以训练第一个神经网络来区分好的实例和坏的实例。
- 举个例子,你可以下载数百万条句子,标记它们为“好”,然后随机修改每一句中的一个单词,并标记这些修改后的句子为“坏”。如果一个神经网络可以识别出“The dog sleeps”是好, “The dog they”是坏,那么它可能已经学会不少语言了。重用这个网络的低层可能有助于很多语言处理任务。
- 另一种方法是训练第一个神经网络,让它输出每一个训练实例的得分,然后利用成本函数,确保每一个好的实例的得分都比坏的实例的得分至少高一些。我们称其为最大边界学习。
6 TensorBoard:可视化学习
- TensorBoard简介
- 为了更方便 TensorFlow 程序的理解、调试与优化,有了TensorBoard 的可视化工具。
- TensorBoard 是TensorFlow 的可视化工具,它可以通过TensorFlow 程序运行过程中输出的日志文件可视化Te nsorFlow 程序的运行状态。
- TensorBoard 和TensorFlow 程序跑在不同的进程中。
- Tensor Board 会自动读取最新的TensorFlow 日志文件,并呈现当前TensorFlow程序运行的最新状态。
- 为了更方便 TensorFlow 程序的理解、调试与优化,有了TensorBoard 的可视化工具。
- 使用可视化学习的流程
- 数据序列化-events文件
-
返回filewriter,写入事件文件到指定目录(最好用绝对路径),以提供给tensorboard使用
-
TensorBoard 通过读取 TensorFlow 的事件文件来运行,需要将数据生成一个序列化的 Summary protobuf 对象。
-
注意:路径不能出现中文,否则会打不开!!!
实现加法
con1 = tf.constant(11.0, name='input1')
con2 = tf.constant(12.0, name='input2')
sum_ = tf.add(con1, con2, name='add')
with tf.Session() as sess:
# 在会话中序列化events文件
file_writer = tf.summary.FileWriter('f:logs/', tf.get_default_graph())
file_writer.close() -
将在指定目录中生成一个envent文件,格式为:
events.out.tfevents.{timestamp}.
-
- 启动TensorBoard
- 在终端中到当前程序所在目录,使用命令:
tensorboard --logdir='f:logs/'
在浏览器中打开 TensorBoard 的图页面 http://localhost:6006会在GRAPHS模块加载我们可以看到图结构。
- 在终端中到当前程序所在目录,使用命令:
- 数据序列化-events文件
- 变量Tensorboard显示
- 目的:在TensorBoard当中观察模型的参数、损失值等变量值的变化
- 1、收集变量
- tf.summary.scalar(name='',tensor):收集如损失函数和准确率等单值变量。name为变量名称,tensor为值。
- tf.summary.histogram(name='',tensor):收集高维度的变量参数,如线性回归的偏置,权重等。
- tf.summary.image(name='',tensor):收集输入的图片张量,能显示图片。
- 2、合并变量
- merged = tf.summary.merge_all()
- 3、生成事件文件,观察图结构
- file_writer = tf.summary.FileWriter('路径',graph=sess.graph)
- 注意路径中不要出现中文
- file_writer = tf.summary.FileWriter('路径',graph=sess.graph)
- 4、运行合并:summary=sess.run(merged)
- 每次迭代都需要运行
- 5、把summary,张量的值写入到events文件当中
- file_writer.add_summary(summary, i)
- i表示第几次的值
- file_writer.add_summary(summary, i)
7 Tensorflow案例—实现线性回归
7.1 线性回归原理复习
- 根据数据建立回归模型,w_1x_1+w_2x_2+...+w_nx_n+b = y通过真实值与预测值之间建立损失误差,使用梯度下降优化得到损失最小对应的权重和偏置。最终确定模型的权重和偏置参数。最后用这些参数进行预测。
7.2 案例确定
-
假设随机指定100个点,只有一个特征
-
数据本身的分布为 y = 0.7 * x + 0.8
这里将数据分布的规律确定,是为了使我们训练出的参数跟真实的参数(即0.7和0.8)比较是否训练准确。
7.3 使用的API
-
运算
- 矩阵运算:tf.matmul(x,w) 注:x与w的维数要相同
- 平方:tf.square(error)
- 求均值:tf.reduce_mean(error)
-
梯度下降优化
- tf.train.GradientDescentOptimizer(learning_rate)
- 梯度下降优化
- learning_rate:学习率,一般为0~1之间较小的值
- method:minimize(loss)
- return:梯度下降OP
- tf.train.GradientDescentOptimizer(learning_rate)
-
定义变量以及初始化
- 定义变量:
tf.Variable(initial_value=None,trainable=True,collection=None,name=None)- 模型的要优化参数必须选择tf.Variable定义,并且可以指定trainable参数指定是否被训练
- inital_value:初始化的值
- trainable:是否被训练,默认为True
- collections:新变量将添加到列出的图的集合collections中
- 变量初始化
- 变量op只有经过显式初始化才能被运行
- 显式初始化:sess.run(tf.global_variables_initializer())
- 定义变量:
-
获取默认的计算图并在默认计算图中操作
- 获取默认计算图:g = tf.get_default_graph()
- 使用默认的计算图:with g.as_default():
-
增加命名空间
- 作用:使代码结构更加清晰,Tensorboard图结构清楚,有助于代码的模块化。
- with tf.variable_scope('lr_model'):
-
变量Tensorboard显示
- 目的:在TensorBoard当中观察模型的参数、损失值等变量值的变化
- 1、收集变量
- tf.summary.scalar(name='',tensor):收集如损失函数和准确率等单值变量。name为变量名称,tensor为值。
- tf.summary.histogram(name='',tensor):收集高维度的变量参数,如线性回归的偏置,权重等。
- tf.summary.image(name='',tensor):收集输入的图片张量,能显示图片。
- 2、合并变量
- merged = tf.summary.merge_all()
- 3、生成事件文件,观察图结构
- TensorFlow1.x中的API:file_writer = tf.summary.FileWriter('路径',graph=sess.graph)
- 注意路径中不要出现中文
- TensorFlow1.x中的API:file_writer = tf.summary.FileWriter('路径',graph=sess.graph)
- 4、运行合并:summary=sess.run(merged)
- 每次迭代都需要运行
- 5、把summary,张量的值写入到events文件当中
- file_writer.add_summary(summary, i)
- i表示第几次的值
- file_writer.add_summary(summary, i)
-
模型的保存与加载
- 创建一个保存文件的saveOP: saver = tf.train.Saver(var_list=None,max_to_keep=5)
- 保存和加载模型(保存文件格式:checkpoint文件)
- var_list:指定将要保存和还原的变量。它可以作为一个dict或一个列表传递.
- max_to_keep:设置要保留的最近检查点文件的最大数量。创建新文件时,会删除较旧的文件。默认为5(即保留最新的5个检查点文件。)
- 保存训练好的模型:saver.save(sess,'ckpt/myregression.ckpt',global_step=step)
- sess:当前会话名称
- 第二个参数是设定保存的路径+名字
- 第三个参数将训练的次数作为后缀加入到模型名字中
- 加载模型:checkpoint = tf.train.latest_checkpoint("./ckpt/")
- 从模型当中找出与当前训练的模型代码当中(名字一样的op操作),覆盖原来的值。
- 判读模型是否存在,若存在则恢复模型:
if ckpt:
saver.restore(sses,ckpt)
- 创建一个保存文件的saveOP: saver = tf.train.Saver(var_list=None,max_to_keep=5)
-
命令行参数设置
- 目的:使用终端命令行的方式去运行程序,并设置参数
- 定义命令行参数:tf.app.flags.DEFINE_integer('train_step',0,'迭代次数')
- 用于定义值是整型的参数
- 第一个参数是定义的参数名
- 第二个参数是默认值
- 第三个参数是注释(不能省略)
- tf.app.flags.DEFINE_string('model_dir', '', '模型的保存路径+模型名字')
- 定义值是字符串的参数
- 定义获取命令行的参数:FLAGS = tf.app.flags.FLAGS
- 使用命令行使用的参数:FLAGS.参数名称
- 例如:FLAGS.model_dir---->获取模型的保存路径+模型名字
7.4 示例代码
"""
使用Tensorflow实现线性回归
"""
import tensorflow as tf
class MyLinearRegression(object):
"""
实现一个线性回归
"""
def __init__(self, FLAGS):
self.FLAGS = FLAGS
# 定义学习率
self.learning_rate = 0.1
# 定义迭代次数
self.train_step = 1000
# 定义并初始化线性回归模型的权重与偏置参数,由于权重与偏置需要被训练与优化,因此需用使用变量OP定义
self.height = tf.Variable(tf.random_normal(shape=[1, 1], mean=0.0, stddev=1.0, name='height'))
self.bais = tf.Variable(tf.random_normal(shape=[1], mean=0.0, stddev=1.0, name='bais'))
def inputs(self):
"""
获取需要训练的数据
:return: x_feature,y_target
"""
# 定义一个命名空间
with tf.variable_scope('get_inputs'):
# 公式:y = x*0.7 + 0.8
# 生成符合正态分布的特征值
x_feature = tf.random_normal(shape=[100, 1], mean=0.0, stddev=1.0, name='x_feature')
# 利用矩阵乘法计算得出目标值,注意矩阵相乘,维数要相同
y_target = tf.matmul(x_feature, [[0.7]]) + 0.8
return x_feature, y_target
def inference(self, x_feature):
"""
根据数据建立线性回归模型
:param x_feature: 特征值
:return: y_predic:预测结果
"""
# 定义一个命名空间
with tf.variable_scope('linear_model'):
# 计算预测值
y_predict = tf.matmul(x_feature, self.height) + self.bais
return y_predict
def loss(self, y_predict, y_target):
"""
由线性回归模型的损失函数计算损失值
:param y_predict: 预测值
:param y_target: 目标值
:return: 损失值
"""
# 定义一个命名空间
with tf.variable_scope('get_loss'):
# 损失函数使目标值与预测值的均方差公式
# 先用tf.square()计算差的平方
# 再用tf.reduce_mean()对列表数据求和后再求平均值
loss = tf.reduce_mean(tf.square(y_predict - y_target))
return loss
def sgd_op(self, loss):
"""
使用随机梯度下降优化器来优化损失(优化模型参数)
:param loss: 损失大小
:return: 梯度下降OP
"""
# 定义一个命名空间
with tf.variable_scope("sgd_op"):
# 使用随机梯度下降API得出优化结果
train_op = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate).minimize(loss)
return train_op
def merge_summary(self, loss):
"""
增加变量显示:在TensorBoard当中观察模型的参数、损失值等变量值的变化
:param loss:损失
:return: merged:合并的变量
"""
# 1. 收集变量
tf.summary.scalar('loss', loss)
tf.summary.histogram('w', self.height)
tf.summary.histogram('b', self.bais)
# 2. 合并变量
merged = tf.summary.merge_all()
return merged
def train(self):
"""
用于实现线性回归模型训练逻辑
:return:
"""
# 开启一个默认的图
g = tf.get_default_graph()
# 在默认的计算图中进行操作
with g.as_default():
# 得到输入数据
x_feature, y_target = self.inputs()
# 建立线性回归模型,得到预测值
y_predict = self.inference(x_feature)
# 根据损失函数求解损失值
loss = self.loss(y_predict, y_target)
# 使用梯度下降优化器优化损失
train_op = self.sgd_op(loss)
# 增加变量显示:在TensorBoard当中观察模型的参数、损失值等变量值的变化
merged = self.merge_summary(loss)
# 定义一个保存模型的saveOP,设置要保留的最近检测点的模型的数量
saver = tf.train.Saver(max_to_keep=5)
# 开启一个会话
with tf.Session() as sess:
# 初始化变量,初始化后才可以在会话中运行
sess.run(tf.global_variables_initializer())
# 生成事件文件观察图结构
file_writer = tf.summary.FileWriter('F:/tensorflow/logs', graph=sess.graph)
print("模型随机初始化的权重参数:{},偏置参数:{}".format(self.height.eval(), self.bais.eval()))
# 加载模型,从模型当中找出与当前训练的模型代码当中(名字一样的op操作),覆盖原来的值
checkpoint = tf.train.latest_checkpoint('./ckpt/')
# 判断模型是否存在,若存在则提取保存的模型
if checkpoint:
saver.restore(sess, checkpoint)
print("第一次加载保存模型的权重参数:{},偏置参数:{}".format(self.height.eval(), self.bais.eval()))
# 使用梯度下降优化损失
for i in range(1, self.FLAGS.train_step + 1):
# 运行梯度下降OP与合并OP
_, summary = sess.run([train_op, merged])
print("第{}步优化,损失为{},权重参数:{},偏置参数:{}".format(i, loss.eval(), self.height.eval(), self.bais.eval()))
# 将优化的变量写入事件文件
file_writer.add_summary(summary, i)
# 每隔一百步保存一次模型
if i % 100 == 0:
saver.save(sess, self.FLAGS.model_dir, global_step=i) # './ckpt/myregression.ckpt'
if __name__ == '__main__':
# 定义命令行参数
tf.app.flags.DEFINE_integer('train_step', 0, '训练步数')
tf.app.flags.DEFINE_string('model_dir', '', "模型的保存路径")
# 定义获取命令行的参数
FLAGS = tf.app.flags.FLAGS
lr = MyLinearRegression(FLAGS)
lr.train()
8 Tensorflow文件读取
8.1 获取数据到Tensorflow的方法
-
TensorFlow程序读取数据一共有3种方法:
-
供给数据(Feeding): Python产生数据,再把数据喂给后端。
import tensorflow as tf
# 设计Graph
x1 = tf.placeholder(tf.int16)
x2 = tf.placeholder(tf.int16)
y = tf.add(x1, x2)
# 用Python产生数据
li1 = [2, 3, 4]
li2 = [4, 0, 1]
# 打开一个session --> 喂数据 --> 计算y
with tf.Session() as sess:
print sess.run(y, feed_dict={x1: li1, x2: li2})""" 说明:在这里x1, x2只是占位符,没有具体的值,那么运行的时候去哪取值呢?这时候就要用到sess.run()中的feed_dict参数,将Python产生的数据喂给后端,并计算y。 """- 缺点是:用占位符替代数据,待运行的时候填充数据。中间环节的增加也是不小的开销,遇到大型数据的时候就会很吃力。
-
预加载数据: 在TensorFlow图中定义常量或变量来保存所有数据(仅适用于数据量比较小的情况)。
import tensorflow as tf
# 设计Graph
x1 = tf.constant([2, 3, 4])
x2 = tf.constant([4, 0, 1])
y = tf.add(x1, x2)
# 打开一个session --> 计算y
with tf.Session() as sess:
print sess.run(y)- 通过预加载的方式加载数据的缺点在于,将数据直接内嵌到Graph中,再把Graph传入Session中运行。当数据量比较大时,Graph的传输会遇到效率问题。
-
从文件读取数据(QueueRunner):简单来说就是将数据读取模块的图搭好
8.2 TensorFlow文件读取数据流程
-
步骤:
- 第一阶段:生成文件名+路径的列表。读取该列表并将其放入文件名队列。
- 第二阶段:从队列当中读取文件内容,并且进行解码操作。
- 第三阶段:解码之后,我们可以直接获取默认的一个样本内容了,但是如果想要获取多个样本,这个时候需要结合管道进行批处理。
-
第一阶段
- 我们称之为构造文件队列,将需要读取的文件装入到一个固定的队列当中
- tf.train.string_input_producer(string_tensor,num_epochs,shuffle=True)
- string_tensor:含有文件名+路径的一阶张量
- num_epochs:将数据集中的数据全部计算几遍
- shuffle:设置在一个epochs内文件顺序是否被打乱
- return:一个文件队列
- tf.train.string_input_producer(string_tensor,num_epochs,shuffle=True)
- 我们称之为构造文件队列,将需要读取的文件装入到一个固定的队列当中
-
第二阶段:读取文件内容,并进行解码操作
-
读取文件内容:
- TensorFlow默认每次只读取一个样本,具体到:文本文件读取一行、二进制文件读取指定字节数(最好一个样本)、图片文件默认读取一张图片、TFRecords默认读取一个example。
- tf.TextLineReader:
- 阅读文本文件,默认按行读取
- return:读取器实例
- tf.WholeFileReader:用于读取图片文件
- tf.TFRecordReader:读取TFRecords文件
- tf.FixedLengthRecordReader:读取二进制文件
- 要读取每个记录是固定数量字节的二进制文件
- record_bytes:整型,指定每次读取(一个样本)的字节数
- return:读取器实例
1、他们有共同的读取方法:read(file_queue):从队列中指定数量内容返回一个Tensors元组(key文件名字,value默认的内容(一个样本))
2、由于默认只会读取一个样本,所以通常想要进行批处理。使用tf.train.batch或tf.train.shuffle_batch进行多样本获取,便于训练时候指定每批次多个样本的训练
-
对文件内容进行解码
- 对于读取不通的文件类型,内容需要解码操作,解码成统一的Tensor格式
- tf.decode_csv:解码文本文件内容
- 与tf.TextLineReader搭配使用
- tf.decode_raw:解码二进制文件内容
- 与tf.FixedLengthRecordReader搭配使用,二进制读取为uint8格式
- tf.image.decode_jpeg(contents):解码图片文件
- 将JPEG编码的图像解码为uint8张量
- return:unit8张量,3-D形状[height,width,channels]
- tf.image_decode_png(contents):解码图片文件
- 将PNG编码的图像解码为uint8张量
- return:张量类型,3-D形状[height,width,channels]
解码阶段,默认所有的内容都解码成tf.uint8格式,如果需要后续的类型处理继续处理
-
-
第三阶段:结合管道进行批处理
- 在解码之后,我们可以直接获取默认的一个样本内容了,但是如果想要获取多个样本,这个时候需要结合管道的末尾进行批处理
- tf.train_batch(tensors,batch_size,num_threads=n,capacity=m,name='')
- 作用:读取指定大小(个数)的张量
- tensors:列表形式,需要批处理的张量放到列表中
- batch_size:设置从队列中读取批处理的大小
- num_threads:设置进入队列的线程数
- capacity:设置队列中元素的最大数量
- return:tensors
- tf.train.shuffle_batch(tensors,batch_size,num_threads=n,capacity=m,name='')
- 作用:对队列中的样本进行乱序处理,读取指定大小(个数)的张量
- tensors:列表形式,需要批处理的张量放到列表中
- batch_size:设置从队列中读取批处理的大小
- num_threads:设置进入队列的线程数
- capacity:设置队列中元素的最大数量
- return:tensors
- tf.train_batch(tensors,batch_size,num_threads=n,capacity=m,name='')
- 在解码之后,我们可以直接获取默认的一个样本内容了,但是如果想要获取多个样本,这个时候需要结合管道的末尾进行批处理
8.3 TensorFlow读取机制图解
首先需要思考的一个问题是,什么是数据读取?以图像数据为例,读取数据的过程可以用下图来表示:
假设我们的硬盘中有一个图片数据集0001.jpg,0002.jpg,0003.jpg……我们只需要把它们读取到内存中,然后提供给GPU或是CPU进行计算就可以了。这听起来很容易,但事实远没有那么简单。
事实上,我们必须要把数据先读入后才能进行计算,假设读入用时0.1s,计算用时0.9s,那么就意味着每过1s,GPU都会有0.1s无事可做,这就大大降低了运算的效率。
如何解决这个问题?方法就是将读入数据和计算分别放在两个线程中,将数据读入内存的一个队列,如下图所示:
读取线程源源不断地将文件系统中的图片读入到一个内存的队列中,而负责计算的是另一个线程,计算需要数据时,直接从内存队列中取就可以了。这样就可以解决GPU因为IO而空闲的问题!
而在tensorflow中,为了方便管理,在内存队列前又添加了一层所谓的“文件名队列”。
为什么要添加这一层文件名队列?我们首先得了解机器学习中的一个概念:epoch。对于一个数据集来讲,运行一个epoch就是将这个数据集中的图片全部计算一遍。如一个数据集中有三张图片A.jpg、B.jpg、C.jpg,那么跑一个epoch就是指对A、B、C三张图片都计算了一遍。两个epoch就是指先对A、B、C各计算一遍,然后再全部计算一遍,也就是说每张图片都计算了两遍。
tensorflow使用文件名队列+内存队列双队列的形式读入文件,可以很好地管理epoch。下面我们用图片的形式来说明这个机制的运行方式。如下图,还是以数据集A.jpg, B.jpg, C.jpg为例,假定我们要跑一个epoch,那么我们就在文件名队列中把A、B、C各放入一次,并在之后标注队列结束。
程序运行后,内存队列首先读入A(此时A从文件名队列中出队):
再依次读入B和C:
此时,如果再尝试读入,系统由于检测到了“结束”,就会自动抛出一个异常(OutOfRange)。外部捕捉到这个异常后就可以结束程序了。这就是tensorflow中读取数据的基本机制。如果我们要跑2个epoch而不是1个epoch,那只要在文件名队列中将A、B、C依次放入两次再标记结束就可以了。
8.4 TensorFlow读取数据机制的对应函数
如何在tensorflow中创建文件名队列与内存队列呢?
对于文件名队列,我们使用tf.train.string_input_producer函数。这个函数需要传入一个文件名list,系统会自动将它转为一个文件名队列。
此外tf.train.string_input_producer还有两个重要的参数,一个是num_epochs,它就是我们上文中提到的epoch数。另外一个就是shuffle,shuffle是指在一个epoch内文件的顺序是否被打乱。若设置shuffle=False,如下图,每个epoch内,数据还是按照A、B、C的顺序进入文件名队列,这个顺序不会改变:
如果设置shuffle=True,那么在一个epoch内,数据的前后顺序就会被打乱,如下图所示:
在tensorflow中,内存队列不需要我们自己建立,我们只需要使用reader对象从文件名队列中读取数据就可以了,具体实现可以参考下面的实战代码。
除了tf.train.string_input_producer外,我们还要额外介绍一个函数:tf.train.start_queue_runners。在我们使用tf.train.string_input_producer创建文件名队列后,整个系统其实还是处于“停滞状态”的,也就是说,我们文件名并没有真正被加入到队列中(如下图所示)。此时如果我们开始计算,因为内存队列中什么也没有,计算单元就会一直等待,导致整个系统被阻塞。
而使用tf.train.start_queue_runners之后,才会启动填充队列的线程,这时系统就不再“停滞”。此后计算单元就可以拿到数据并进行计算,整个程序也就跑起来了,这就是函数tf.train.start_queue_runners的用处。
8.5 TensorFlow读取数据的线程操作
- 创建队列和排队操作称之为tf.train.QueueRunner。每个QueueRunner都负责一个阶段,并拥有需要在线程中运行的排队操作列表。一旦图形被构建, tf.train.start_queue_runners函数就会要求图中的每个QueueRunner启动它运行排队操作的线程。(这些操作需要在会话中开启)
- tf.train.start_queue_runners(sess=,coord=)
- 作用:收集图中的所有队列线程,并启动线程
- sess:设置所在的会话
- coord:设置线程协调器
- return:返回所有线程
- coord = tf.train.Coordinator()
- 作用:创建一个线程协调器实例,实现一个简单的机制来协调一组线程的终止
- request_stop():请求停止
- should_stop():询问是否结束
- join(threads=,stop_grace_period_secs=120):回收线程
- return:返回一个线程调节器实例
- tf.train.start_queue_runners(sess=,coord=)
8.6 TensorFlow读取图片数据
图像基本知识
对于图像文件,我们怎么进行转换成机器学习能够理解的数据?对于图片来讲,组成图片的最基本单位是像素,所以我们获取的是每张图片的像素值。接触的图片有两种,一种是黑白图片,另一种是彩色图片。
- 图片三要素
- 组成一张图片特征值是所有的像素值,有如下几个要素:
- 图片长度(height)
- 图片宽度(width)
- 图片通道数(channel)
- 什么是图片的通道数?
- 灰度图:单通道
- 彩色图片:三通道
- 假设一张彩色图片的长200,宽200,通道数为3,那么总的像素数量为200X200X3
- 组成一张图片特征值是所有的像素值,有如下几个要素:
- 张量形状
- 读取图片之后,怎么用张量形状来表示?
- 一张图片就是一个3D张量,[height, width, channel]我们会经常遇到3D和4D的表示
- 单个图片:[height, width, channel]
- 多个图片:[batch,height, width, channel],batch表示批数量
- 读取图片之后,怎么用张量形状来表示?
- 图片的特征值处理
- 为什么要进行特征值处理?
- 一方面,在进行图片识别的时候,每个图片样本的特征数量要保持相同。所以需要将所有图片张量大小统一转换。
- 另一方面,如果图片的像素量太大,也可以通过这种方式适当减少像素的数量,减少训练的计算开销。
- 为什么要进行特征值处理?
- 特征值处理的常用API:
- 数据类型转化:tf.convert_image_dtype(image,dtype,name=None)
- 作用:把图片元素类型,转成想要的类型,返回转换后的图片,注意,要是转成了float类型之后,像素值会在 [0,1)这个范围内。
- 参数:
image: 图像
dtype: 待转换类型
name: 操作名
- 形状变换:
- tf.image.resize_images(images,size)
- 作用:将输入图像重新缩放或者放大到固定的尺寸
- images:形状为[batch, height, width, channels]的4-D张量或形状为[height, width, channels]的3-D张量.
- size:1维 int32类型的 Tensor,包含两个元素:new_height, new_width.
- method:改变形状的方法,默认是ResizeMethod.BILINEAR双线性插值
- align_corners:布尔型,如果为True,则输入和输出张量的4个拐角像素的中心对齐,并且保留角落像素处的值;默认为False.
可能引发的异常:ValueError - 如果images的形状与此函数的形状参数不兼容.
- 如果size有无效的形状或类型.
- 如果指定了不支持的调整大小方法.
- resize_image_with_crop_or_pad(…):裁剪或将图像填充到目标宽度和高度
- central_crop(…): 裁剪图像的中心区域
- crop_and_resize(…): 从输入图像张量中提取裁剪物并对其进行双线性的调整
- crop_to_bounding_box(…): 将图像裁剪到指定的边框。
- tf.image.resize_images(images,size)
- 图像翻转
神经网络其实是很”笨”的,要是你提供的图片都是像上面那样朝向左边,那么当出现一幅朝向右边的图片的时候,它很可能无法识别. 所以,可以人为的多加一些翻转上去,使得各个角度的图片都有一点,无形中,扩充了数据量,还缓解了识别错误的问题.- flip_left_right(image): 左右翻转
- 作用:左右翻转一幅图片,返回一个形状和原图片相同的图片(翻转后)
image:3维tensor,形状为[height, width, channels].
- 作用:左右翻转一幅图片,返回一个形状和原图片相同的图片(翻转后)
- flip_up_down(…): 上下翻转
- transpose_image(…): 对角线翻转
- random_flip_left_right(…): 随机左右翻转
- flip_left_right(image): 左右翻转
- 颜色变换
使用翻转可以来”增加数据集”以外,调节颜色属性也是同样很有用的方法,这里主要有调整亮度,对比度,饱和度,色调等方法.如下:- adjust_brightness(…): 调整亮度
- 作用:调节亮度
参数:
image: tensor,原图片
delta: 标量,待加到像素值上面的值.
- 作用:调节亮度
- random_brightness(…): 随机调整亮度
- adjust_contrast(…): 调整对比度
- random_contrast(…): 随机调整亮度
- adjust_saturation(…): 调整饱和度
- random_saturation(…): 随机调整饱和度
- adjust_hue(…): 调整色调
- random_hue(…): 随机调整色调
- adjust_brightness(…): 调整亮度
- 数据类型转化:tf.convert_image_dtype(image,dtype,name=None)
案例:狗图片读取
-
读取流程:
- 构造图片文件名队列
- 创造一个图片读取器,利用图片读取器去读取队列中的内容
- 对读取到的图片内容进行解码
- 对解码后的图片内容进行特征值处理,进行形状和大小的固定
- 对图片数据进行批处理
- 手动开启子线程去进行批处理读取到队列的操作
- 创建用于线程回收的协调器
- 开启子线程去读取数据
- 获取样本数据
- 回收子线程
-
代码
"""
狗图片读取案例"""
import tensorflow as tfimport os
def picture_read(file_list):
"""
读取狗图片数据到张量
:param file_list:路径+文件名的列表
:return:
"""
# 1. 构造图片文件名队列
# 返回文件名队列
file_queue = tf.train.string_input_producer(file_list)
# 2. 构造一个图片读取器,利用图片读取器去读取队列中的内容
reader = tf.WholeFileReader()
# 默认一次读取一张图片,没有形状
key, value = reader.read(file_queue)
# 3. 对读取到的图片内容进行解码
# 数据类型从string---->unit8
# 形状从()---->(???)
image = tf.image.decode_jpeg(value)
# 4. 图片特征值处理:对图片进行批处理之前,进行形状与大小的固定
# 把图片固定成统一大小的目的是因为训练的数据集的每个样本的特征值应该相同。
# 设置成固定的大小
image_resized = tf.image.resize_images(image, size=[200, 200])
# 设置固定的通道数,使用静态改变形状的方法:set_shape()
image_resized.set_shape([200, 200, 3])
# 5. 对图片数据进行批处理,批处理之前,每个样本的形状必须固定
image_batch = tf.train.batch([image_resized], batch_size=10, num_threads=4, capacity=10)# 在默认图中进行会话操作 g = tf.get_default_graph() with g.as_default(): # 开启会话 with tf.Session() as sess: # 1 手动开启子线程去进行批处理读取到队列操作 # 1.1 创建用于线程回收的协调器 coord = tf.train.Coordinator() # 1.2 开启子线程去读取数据,返回子线程实例 threads = tf.train.start_queue_runners(sess=sess, coord=coord) # 2. 获取样本数据 pic_data = sess.run([image_batch]) print(pic_data) # 3. 回收子线程 coord.request_stop() coord.join(threads)if name == 'main':
# 生成文件名列表
file_name_list = os.listdir('../data/dog/')
# 构造文件名+路径列表
file_list = [os.path.join('../data/dog/', file) for file in file_name_list]
# 图片文件读取
picture_read(file_list)
8.7 TensorFlow利用二进制方式读取CIFAR10数据集
-
CIFAR10数据集的二进制版本介绍
-
二进制版本包含文件data_batch_1.bin,data_batch_2.bin,...,data_batch_5.bin以及test_batch.bin。这些文件中的每一个格式如下,数据中每个样本包含了特征值和目标值:
-
<1×标签> <3072×像素> ... <1×标签> <3072×像素>
-
-
第一个字节是第一个图像的标签,它是一个0-9范围内的数字。接下来的3072个字节是图像像素的值。前1024个字节是红色通道值,下1024个绿色,最后1024个蓝色。值以行优先顺序存储,因此前32个字节是图像第一行的红色通道值。 每个文件都包含10000个这样的3073字节的“行”图像,但没有任何分隔行的限制。因此每个文件应该完全是30730000字节长。
-
-
CIFAR10 二进制数据读取
-
读取流程:
- 构造文件名队列
- 生成文件名列表:os.listdir(path)
- 生成文件名+路径列表:os.path.join(path,file_name)
- 构造文件名队列:tf.train.string_input_producer(file_name_list)
- 使用二进制读取器读取内容
- 创建二进制读取器实例:tf.FixedLengthRecordReader(一个样本占的字节数)
- 读取内容:reader.read(file_queue)
- 对读取的数据进行解码:tf.decode_raw(value,tf.uint8)
- 标签、图片的数据类型转换、形状转换
- 数据类型转换:tf.cast(需要转换的数据,转换的数据类型)
- 固定图片的特征值形状:
- depth_major=tf.shape(image,[channel,height,width])从(3072,)--->[channel,height,width]
- tf.transpose(depth_major,[1,2,0])[channel,height,width]--->[heigt,width,channel]
- 批处理图片数据:image_batch,label_batch=tf.train.batch([image_reshaped,label_cast],batch_size=,num_threads=,capacity=)
- 手动开启子线程去进行数据的批处理读取到队列的操作
- 创建用于线程回收的协调器:coord=tf.train.Coordinator()
- 开启子线程读取数据到队列:threads=tf.train.start_queue_runners(sess=sess,coord=coord)
- 获取图片样本数据:pic_data = sess.run(image_batch,label_batch)
- 回收子线程:
- coord.request_stop()
- coord.join(threads)
- 构造文件名队列
-
代码
"""
CIFAR10数据集二进制版本的读取
"""
import tensorflow as tf
import osclass CifarRead(object):
"""
CIFAR二进制文件的读取
"""def __init__(self, filename_list): """ 定义一些图片的样本属性 :param filename_list: """ self.filename_list = filename_list self.height = 32 self.width = 32 self.channel = 3 self.label_bytes = 1 self.image_bytes = self.height * self.width * self.channel self.bytes = self.label_bytes + self.image_bytes def read_and_decode(self): """ 读取二进制原始数据并解码成张量 :return: """ # 1. 构造文件队列 file_queue = tf.train.string_input_producer(self.filename_list) # 2. 使用二进制读取器读取内容 # 2.1 创建二进制读取器实例,实在一次读取的字节数 reader = tf.FixedLengthRecordReader(self.bytes) # 2.2 读取内容,一共读取1+3072=3073个字节,此时的value是某个文件的某个样本 key, value = reader.read(file_queue) # 3. 对二进制数据进行解码 decode_raw label_image = tf.decode_raw(value, tf.uint8) # 4. 切分图片的特征值与目标值:使用tf.slice(数据集,[起始下标],[结束下标])切片 label = tf.slice(label_image, [0], [self.label_bytes]) image = tf.slice(label_image, [self.label_bytes], [self.image_bytes]) # 5. 标签、图片的类型转换、形状转换 # 5.1 标签数据类型转换 tf.cast() label_cast = tf.cast(label, tf.int32) image = tf.cast(image, tf.float32) # 5.2 固定图片的特征值形状 # 5.2.1 shape:(3072,)--->[channel,height,width] depth_major = tf.reshape(image, [self.channel, self.height, self.width]) print(depth_major) # 5.2.2 tf.transpose:[channel,height,width]--->[heigt,width,channel] image_reshaped = tf.transpose(depth_major, [1, 2, 0]) print(image_reshaped) # 6.批处理图片数据 image_batch, label_batch = tf.train.batch([image_reshaped, label_cast], batch_size=10, num_threads=4, capacity=10) print(image_batch) print(label_batch) return image_batch, label_batch def run(self): """ 执行文件读取逻辑 :return: """ # 读取二进制原始数据并解码成张量,形状固定于批处理 image_batch, label_batch = self.read_and_decode() # 开启会话 with tf.Session() as sess: # 1. 手动开启子线程去进行数据的批处理读取到队列的操作 # 1.1 创建用于线程回收的协调器 coord = tf.train.Coordinator() # 1.2 开启子线程去读取数据 threads = tf.train.start_queue_runners(sess=sess, coord=coord) # 2. 获取样本数据 pic_data = sess.run([image_batch, label_batch]) print(pic_data) # 3. 回收子线程 coord.request_stop() coord.join(threads)if name == 'main':
# 生成文件名列表
filename_list = os.listdir('../data/cifar10/cifar-10-batches-bin/')
# 生成文件名加路径列表
filename_list = [os.path.join('../data/cifar10/cifar-10-batches-bin/', file) for file in filename_list if
file[-3:] == 'bin']
# 实例化类
cr = CifarRead(filename_list)
cr.run()
-
-
注意:
-
1、image (3072, ) —>tf.reshape(image, [])里面的shape默认是[channel, height, width], 所以得先从[depth height width] to [depth, height, width]。
-
2、然后使用tf.transpose, 将刚才的数据[depth, height, width],变成Tensorflow默认的[height, width, channel]
- 假设1, 2, 3, 4-红色 5, 6, 7, 8-绿色 9, 10, 11, 12-蓝色
如果通道在最低维度0[channel, height, width],RGB三颜色分成三组,在第一维度上找到三个RGB颜色
如果通道在最高维度2[height, width, channel],在第三维度上找到RGB三个颜色
1、想要变成:[2 height, 2width, 3channel],但是输出结果不对
In [7]: tf.reshape([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [2, 2, 3]).eval()
Out[7]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],[[ 7, 8, 9], [10, 11, 12]]], dtype=int32)2、所以要这样去做
In [8]: tf.reshape([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [3, 2, 2]).eval()
Out[8]:
array([[[ 1, 2],
[ 3, 4]],[[ 5, 6], [ 7, 8]], [[ 9, 10], [11, 12]]], dtype=int32)接着使用tf.transpose ,0,1,2代表三个维度标记
Convert from [depth, height, width] to [height, width, depth].
0,1,2-----> 1, 2, 0
In [17]: tf.transpose(depth_major, [1, 2, 0]).eval()
Out[17]:
array([[[ 1, 5, 9],
[ 2, 6, 10]],[[ 3, 7, 11], [ 4, 8, 12]]], dtype=int32) - 假设1, 2, 3, 4-红色 5, 6, 7, 8-绿色 9, 10, 11, 12-蓝色
-
8.8 CIFAR10类图片的数据的TFRecords存储和读取
数据存入TFRecords文件
什么是TFRecords文件
- TFRecords其实是一种二进制文件,它能更好的利用内存,更方便复制和移动,并且不需要单独的标签文件。
- TFRecords文件是TensorFlow的标准格式,使用TFRecords文件保存记录的方法可以允许你将任意数据转换为TensorFlow所支持的格式,这种方法可以使TensorFlow的数据集更容易与网络应用架构相匹配。
- 文件格式: *.tfrecords
什么是Example结构?
- tf.train.Example为协议内存块(protocol buffer)
-
协议内存块包含了字段Features
-
Features包含了一个Feature字段
-
Feature字段中包含要写入的数据、并指明数据类型。这是一个样本的结构,批数据需要循环的存入这样的结构。
example = tr.train.Example(features=tf.train.Features(feature={
"特征值字段名":tf.train.Feature(特征值数据类型列表=tf.train.特征值数据类型列表类(value=[特征值字段名])),
"标签值字段名":tf.train.Feature(标签值数据类型列表=tf.train.标签值数据类型列表类(value=[标签值字段名]))
})) -
tf.train.Example(features=)
- 作用:生成协议内存块
- features:tf.train.Features类型的特征实例
-
tf.train.Features(feature=)
- 作用:构建每个样本的信息键值对
- feature:字典数据,key为要保存的名字
- value:为tf.train.Feature实例
-
tf.train.Feature(options)
- options:特征值数据类型列表,例如
- bytes_list=tf.train. BytesList(value=[Bytes])
- int64_list=tf.train. Int64List(value=[Value])
- 属性的取值可以为字符串(BytesList ),浮点数列表(FloatList )或整数列表(Int64List )。例如我们可以将图片转换为字符串进行存储,图像对应的类别标号作为整数存储,而用于回归任务的ground-truth可以作为浮点数存储。
- 支持存入的类型如下:
- tf.train.Int64List(value=[Value])
- tf.train.BytesList(value=[Bytes])
- tf.train.FloatList(value=[value])
- options:特征值数据类型列表,例如
-
- 协议内存块结构很好的解决了数据和标签或者其它属性数据存储到同一个文件中的问题。
写入数据到TFRecords中的主要步骤:
- 获取数据, 将数据填入到tf.train.Example协议内存块(protocol buffer)。
- 将协议内存块序列化为一个字符串。example.SerializeToString()
- 通过tf.python_io.TFRecordWriter写入到TFRecords文件。
写入数据到TFRecords中的示例代码:
def wite_to_tfrecords(self, image_batch, label_batch):
"""
将文件写入到TFRecords文件中
:param image_batch:
:param label_batch:
:return:
"""
# 1. 建立TFRecords文件存储器
writer = tf.python_io.TFRecordWriter('./1.tfrecords') # 设置路径及文件名
# 2. 循环取出每个样本的值,构造example协议块
for i in range(10): # 因为每批量有10个样本数据
# 2.1 取出图片的值
# 写入文件的是值,而不是tensor类型,写入example需要bytes类型数据,需要用tostring()来转化
image = image_batch[i].eval().tostring()
# 2.2 取出标签值,写入example中需要int类型,所以需要强制转换成int类型
label = int(label_batch[i].eval()[0])
# 2.3 构造每个样本的example协议块
feature = {
"image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
}
features = tf.train.Features(feature=feature)
example = tf.train.Example(features=features)
# 2.4 将example协议块序列化
example_serialized = example.SerializeToString()
# 2.5 写入序列化后的值
writer.write(example_serialized) # 此处其实是将其压缩成一个二进制数据
# 3. 关闭文件存储器
writer.close()
return None
8.9 从TFRecords文件中读取数据
解析example
feature = tr.parse_single_example(values,features={
"image":tf.FixedLenFeature([],tf.string),
"label":tf.FixedLenFeature([],tf.int64)
})
- tf.parse_single_example(serialized,features=None,name=None)
- 作用:解析一个单一的Example原型
- serialize:标量字符串Tensor,一个序列化的Example
- features:dict字典数据,键为读取的名字,值为FixedLenFeature
- tf.FixedLenFeature(shape,dtype)
- shape:输入数据的形状,一般不指定,为空列表
- dtype:输入数据类型,与存储进文件的类型要一致
- 类型只能是float32,int64,string
从TFRecords文件中读取数据的主要步骤:
- 构造文件队列:tf.train.string_input_producer([文件名列表])
- 实例化TFRecords文件阅读器:reader = tf.TFRecordReader()
- 读取数据:key, value = reader.read(file_queue)
- 解析example协议块:feature=tf.parse_single_example(serialized,features=None,name=None)
- 解析一个单一的Example原型
- serialized:标量字符串Tensor,一个序列化的Example
- features:dict字典数据,键为读取的名字,值为FixedLenFeature
- return:一个键值对组成的字典,键为读取的名字
- 处理数据
- 处理标签数据:label = tf.cast(feature["label"], tf.int32)
- cast()只能在int和float之间进行转换,将数据类型int64 转换为int32
- 处理特征值数据:image = tf.decode_raw(feature['image'], tf.uint8)
- 如果之前用了tostring(),那么必须要用decode_raw()转换为最初的int类型
- decode_raw()可以将数据从string,bytes转换为int,float类型
- 处理标签数据:label = tf.cast(feature["label"], tf.int32)
- 转换特征值的形状(形状未固定,静态方式:tf.set_shape;形状已固定,动态方式:tf.reshape):
- image_tensor = tf.reshape(image,[self.height,self.width,self.channel])
- 批处理:image_batch,label_batch = tf.train.batch([image_tensor,label],batch_size=10,num_threads=4,capacity=10)
从TFRecords文件中读取数据示例代码:
def read_to_tfrecords(self):
"""
从TFRecords文件中读取图片数据(解析example)
:return:
"""
pass
# 1. 构造文件队列
file_queue = tf.train.string_input_producer(['./1.tfrecords']) # 参数为文件名列表
# 2. 实例化TFRecords文件阅读器
reader = tf.TFRecordReader()
# 读取数据
key, value = reader.read(file_queue)
# 3. 解析example协议块,返回值是字典
features = {
"image": tf.FixedLenFeature([], tf.string),
"label": tf.FixedLenFeature([], tf.int64)
}
feature = tf.parse_single_example(value, features=features)
# 4. 处理标签数据
# cast()只能在int和float之间进行转换,将数据类型int64 转换为int32
label = tf.cast(feature["label"], tf.int32)
# 处理图片数据
# 由于是一个string,要进行解码,
# 将字节转换为数字向量表示,字节为一字符串类型的张量
# 如果之前用了tostring(),那么必须要用decode_raw()转换为最初的int类型
# decode_raw()可以将数据从string,bytes转换为int,float类型
image = tf.decode_raw(feature['image'], tf.uint8)
# 5. 转换图片的形状,此处需要用动态形状进行转换
image_tensor = tf.reshape(image, [self.height, self.width, self.channel])
# 6. 批处理
image_batch, label_batch = tf.train.batch([image_tensor, label], batch_size=10, num_threads=4, capacity=10)
return image_batch, label_batch
CIFAR10类图片的数据的TFRecords存储和读取示例代码
import tensorflow as tf
import os
"""
读取二进制文件转换成张量,写进TFRecords,同时读取TFRcords
"""
# 命令行参数
FLAGS = tf.app.flags.FLAGS # 获取值
tf.app.flags.DEFINE_string("tfrecord_dir", "./tmp/cifar10.tfrecords", "写入图片数据文件的文件名")
# 读取二进制转换文件
class CifarRead(object):
"""
读取二进制文件转换成张量,写进TFRecords,同时读取TFRcords
"""
def __init__(self, file_list):
"""
初始化图片参数
:param file_list:图片的路径名称列表
"""
# 文件列表
self.file_list = file_list
# 图片大小,二进制文件字节数
self.height = 32
self.width = 32
self.channel = 3
self.label_bytes = 1
self.image_bytes = self.height * self.width * self.channel
self.bytes = self.label_bytes + self.image_bytes
def read_and_decode(self):
"""
解析二进制文件到张量
:return: 批处理的image,label张量
"""
# 1.构造文件队列
file_queue = tf.train.string_input_producer(self.file_list)
# 2.阅读器读取内容
reader = tf.FixedLengthRecordReader(self.bytes)
key, value = reader.read(file_queue) # key为文件名,value为元组
print(value)
# 3.进行解码,处理格式
label_image = tf.decode_raw(value, tf.uint8)
print(label_image)
# 处理格式,image,label
# 进行切片处理,标签值
# tf.cast()函数是转换数据格式,此处是将label二进制数据转换成int32格式
label = tf.cast(tf.slice(label_image, [0], [self.label_bytes]), tf.int32)
# 处理图片数据
image = tf.slice(label_image, [self.label_bytes], [self.image_bytes])
print(image)
# 处理图片的形状,提供给批处理
# 因为image的形状已经固定,此处形状用动态形状来改变
depth_major = tf.reshape(image, [self.channel, self.height, self.width])
image_tensor = tf.transpose(depth_major, [1, 2, 0])
print(image_tensor)
# 批处理图片数据
image_batch, label_batch = tf.train.batch([image_tensor, label], batch_size=10, num_threads=1, capacity=10)
return image_batch, label_batch
def write_to_tfrecords(self, image_batch, label_batch):
"""
将文件写入到TFRecords文件中
:param image_batch:
:param label_batch:
:return:
"""
# 建立TFRecords文件存储器
writer = tf.python_io.TFRecordWriter('./1.tfrecords') # 传进去命令行参数
# 循环取出每个样本的值,构造example协议块
for i in range(10):
# 取出图片的值, #写进去的是值,而不是tensor类型,
# 写入example需要bytes文件格式,将tensor转化为bytes用tostring()来转化
image = image_batch[i].eval().tostring()
# 取出标签值,写入example中需要使用int形式,所以需要强制转换int
label = int(label_batch[i].eval()[0])
# 构造每个样本的example协议块
example = tf.train.Example(features=tf.train.Features(feature={
"image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
}))
# 写进去序列化后的值
writer.write(example.SerializeToString()) # 此处其实是将其压缩成一个二进制数据
writer.close()
return None
def read_from_tfrecords(self):
"""
从TFRecords文件当中读取图片数据(解析example)
:param self:
:return: image_batch,label_batch
"""
# 1.构造文件队列
file_queue = tf.train.string_input_producer(['./1.tfrecords']) # 参数为文件名列表
# 2.构造阅读器
reader = tf.TFRecordReader()
key, value = reader.read(file_queue)
# 3.解析协议块,返回的值是字典
feature = tf.parse_single_example(value, features={
"image": tf.FixedLenFeature([], tf.string),
"label": tf.FixedLenFeature([], tf.int64)
})
# feature["image"],feature["label"]
# 处理标签数据 ,cast()只能在int和float之间进行转换
label = tf.cast(feature["label"], tf.int32) # 将数据类型int64 转换为int32
# 处理图片数据,由于是一个string,要进行解码, #将字节转换为数字向量表示,字节为一字符串类型的张量
# 如果之前用了tostring(),那么必须要用decode_raw()转换为最初的int类型
# decode_raw()可以将数据从string,bytes转换为int,float类型的
image = tf.decode_raw(feature["image"], tf.uint8)
# 转换图片的形状,此处需要用动态形状进行转换
image_tensor = tf.reshape(image, [self.height, self.width, self.channel])
# 4.批处理
image_batch, label_batch = tf.train.batch([image_tensor, label], batch_size=10, num_threads=1, capacity=10)
return image_batch, label_batch
def run(self):
"""
实现读取数据的主逻辑
:return:
"""
# 读取二进制文件
# image_batch,label_batch = self.read_and_decode()
# 从已经存储的TFRecords文件中解析出原始数据
image_batch, label_batch = self.read_from_tfrecords()
with tf.Session() as sess:
# 线程协调器
coord = tf.train.Coordinator()
# 开启线程
threads = tf.train.start_queue_runners(sess, coord=coord)
print(sess.run([image_batch, label_batch]))
print("存进TFRecords文件")
# self.write_to_tfrecords(image_batch,label_batch)
print("存进文件完毕")
# 回收线程
coord.request_stop()
coord.join(threads)
if __name__ == '__main__':
# 找到文件路径,名字,构造路径+文件名的列表,"A.csv"...
# os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表
filename = os.listdir('../data/cifar10/cifar-10-batches-bin/')
# 加上路径
file_list = [os.path.join('../data/cifar10/cifar-10-batches-bin/', file) for file in filename if file[-3:] == "bin"]
# 创建一个文件读取对象
cr = CifarRead(file_list)
# 开始运行
cr.run()
9 Tensorflow中其它基础API与高级API
9.1 基础API
- tf.app:用于命令行参数设置
- 这个模块相当于为 TensorFlow 进行的脚本撮供了一个 main 函数入口,可以定义脚本运行的 flags。
- 使用终端命令行的方式去运行程序,并设置参数
- tf.image
- TensorFlow 的图像处理操作。主要是一些颜色变换、变形和图像的编码和解码。
- tf.gfile
- 这个模块提供了一组文件操作函数。
- tf.summary
- 用来生成 TensorBoard 可用的统计日志,目前 Summary 主要提供了 4 种类型:audio、image、histogram、scalar
- tf.python_io
- 用来读写 TFRecords文件
- tf.train
- 这个模块提供了一些训练器,与 tf.nn 组合起来,实现一些网络的优化计算。
- tf.nn
- 这个模块提供了一些构建神经网络的底层函数。 TensorFlow 构建网络的核心模块。其中包含了添加各种层的函数,比如添加卷积层、池化层等。
9.2 高级API
- tf.keras
- Keras 本来是一个独立的深度学习库,tensorflow将其学习过来,增加这部分模块在于快速构建模型。
- tf.layers
- 高级 API,以更高级的概念层来定义一个模型。类似tf.keras。
- tf.contrib
- tf.contrib.layers提供够将计算图中的 网络层、正则化、摘要操作、是构建计算图的高级操作,但是tf.contrib包含不稳定和实验代码,有可能以后API会改变。
- tf.estimator
- 一个 Estimator 相当于 Model + Training + Evaluate 的合体。在模块中,已经实现了几种简单的分类器和回归器,包括:Baseline,Learning 和 DNN。这里的 DNN 的网络,只是全连接网络,没有提供卷积之类的。
10 TensorFlow训练稀疏模型
- 所有提出的优化算法都会产生一个密集的模型,这意味着大多数参数都是非零的。 如果我们需要一个在运行时速度非常快速的模型,或者需要模型占用较少的内存,我们可能更喜欢用一个稀疏模型来代替。
- 训练一个稀疏模型的一个小方法就是像平常一样训练模型,然后摆脱值小的权重(将它们设置为 0)。另一个方法是在训练过程中应用强 l1 正则化,因为它会推动优化器尽可能多地消除权重。
- 但是,在某些情况下,这些技术可能仍然不足。最后一个方法时应用对偶平均,通常称为FTRL(Follow The Regularized Leader)。当与 l1 正则化一起使用时,这种技术通常会导出一个非常稀疏的模型。 TensorFlow 在FTRLOptimizer类中实现称为 FTRL-Proximal 的 FTRL 变体。
TensorFlow 中的 layers 模块
TensorFlow 中的 layers 模块提供用于深度学习的更高层次封装的 API,利用它我们可以轻松地构建模型,这一节我们就来看下这个模块的 API 的具体用法。
11 TensorFlow中的layers模块
11.1 概览
layers 模块的路径写法为 tf.layers,这个模块定义在 tensorflow/python/layers/layers.py,其官方文档地址为:https://www.tensorflow.org/api_docs/python/tf/layers,TensorFlow 版本为 1.5。
这里面提供了多个类和方法以供使用,下面我们分别予以介绍。
11.2 方法
tf.layers 模块提供的方法有:
- Input(…): 用于实例化一个输入 Tensor,作为神经网络的输入。
- average_pooling1d(…): 一维平均池化层
- average_pooling2d(…): 二维平均池化层
- average_pooling3d(…): 三维平均池化层
- batch_normalization(…): 批量标准化层
- conv1d(…): 一维卷积层
- conv2d(…): 二维卷积层
- conv2d_transpose(…): 二维反卷积层
- conv3d(…): 三维卷积层
- conv3d_transpose(…): 三维反卷积层
- dense(…): 全连接层
- dropout(…): Dropout层
- flatten(…): Flatten层,即把一个 Tensor 展平
- max_pooling1d(…): 一维最大池化层
- max_pooling2d(…): 二维最大池化层
- max_pooling3d(…): 三维最大池化层
- separable_conv2d(…): 二维深度可分离卷积层
11.2.1 tf.layers.Input
tf.layers.Input() 这个方法是用于输入数据的方法,其实类似于 tf.placeholder,相当于一个占位符的作用,当然也可以通过传入 tensor 参数来进行赋值。
Input(
shape=None,
batch_size=None,
name=None,
dtype=tf.float32,
sparse=False,
tensor=None
)
参数说明如下:
- shape:可选,默认 None,是一个数字组成的元组或列表,但是这个 shape 比较特殊,它不包含 batch_size,比如传入的 shape 为 [32],那么它会将 shape 转化为 [?, 32],这里一定需要注意。
- batch_size:可选,默认 None,代表输入数据的 batch size,可以是数字或者 None。
- name:可选,默认 None,输入层的名称。
- dtype:可选,默认 tf.float32,元素的类型。
- sparse:可选,默认 False,指定是否以稀疏矩阵的形式来创建 placeholder。
- tensor:可选,默认 None,如果指定,那么创建的内容便不再是一个 placeholder,会用此 Tensor 初始化。
返回值: 返回一个包含历史 Meta Data 的 Tensor。
我们用一个实例来感受一下:
x = tf.layers.Input(shape=[32])
print(x)
y = tf.layers.dense(x, 16, activation=tf.nn.softmax)
print(y)
首先我们用 Input() 方法初始化了一个 placeholder,这时我们没有传入 tensor 参数,然后调用了 dense() 方法构建了一个全连接网络,激活函数使用 softmax,然后将二者输出,结果如下:
Tensor("input_layer_1:0", shape=(?, 32), dtype=float32)
Tensor("dense/Softmax:0", shape=(?, 16), dtype=float32)
这时我们发现,shape 它给我们做了转化,本来是 [32],结果它给转化成了 [?, 32],即第一维代表 batch_size,所以我们需要注意,在调用此方法的时候不需要去关心 batch_size 这一维。
如果我们在初始化的时候传入一个已有 Tensor,例如:
data = tf.constant([1, 2, 3])
x = tf.layers.Input(tensor=data)
print(x)
结果如下:
data = tf.constant([1, 2, 3])
x = tf.layers.Input(tensor=data)
print(x)
可以看到它可以自动计算出其 shape 和 dtype。
11.2.2 tf.layers.batch_normalization
此方法是批量标准化的方法,经过处理之后可以加速训练速度,其定义在 tensorflow/python/layers/normalization.py,论文可以参考:http://arxiv.org/abs/1502.03167 “Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift”。
batch_normalization(
inputs,
axis=-1,
momentum=0.99,
epsilon=0.001,
center=True,
scale=True,
beta_initializer=tf.zeros_initializer(),
gamma_initializer=tf.ones_initializer(),
moving_mean_initializer=tf.zeros_initializer(),
moving_variance_initializer=tf.ones_initializer(),
beta_regularizer=None,
gamma_regularizer=None,
beta_constraint=None,
gamma_constraint=None,
training=False,
trainable=True,
name=None,
reuse=None,
renorm=False,
renorm_clipping=None,
renorm_momentum=0.99,
fused=None,
virtual_batch_size=None,
adjustment=None
)
参数说明如下:
- inputs:必需,即输入数据。
- axis:可选,默认 -1,即进行标注化操作时操作数据的哪个维度。
- momentum:可选,默认 0.99,即动态均值的动量。
- epsilon:可选,默认 0.01,大于0的小浮点数,用于防止除0错误。
- center:可选,默认 True,若设为True,将会将 beta 作为偏置加上去,否则忽略参数 beta
- scale:可选,默认 True,若设为True,则会乘以gamma,否则不使用gamma。当下一层是线性的时,可以设False,因为scaling的操作将被下一层执行。
- beta_initializer:可选,默认 zeros_initializer,即 beta 权重的初始方法。
- gamma_initializer:可选,默认 ones_initializer,即 gamma 的初始化方法。
- moving_mean_initializer:可选,默认 zeros_initializer,即动态均值的初始化方法。
- moving_variance_initializer:可选,默认 ones_initializer,即动态方差的初始化方法。
- beta_regularizer: 可选,默认None,beta 的正则化方法。
- gamma_regularizer: 可选,默认None,gamma 的正则化方法。
- beta_constraint: 可选,默认None,加在 beta 上的约束项。
- gamma_constraint: 可选,默认None,加在 gamma 上的约束项。
- training:可选,默认 False,返回结果是 training 模式。
- trainable:可选,默认为 True,布尔类型,如果为 True,则将变量添加 GraphKeys.TRAINABLE_VARIABLES 中。
- name:可选,默认 None,层名称。
- reuse:可选,默认 None,根据层名判断是否重复利用。
- renorm:可选,默认 False,是否要用 Batch Renormalization (https://arxiv.org/abs/1702.03275)
- renorm_clipping:可选,默认 None,是否要用 rmax、rmin、dmax 来 scalar Tensor。
- renorm_momentum,可选,默认 0.99,用来更新动态均值和标准差的 Momentum 值。
- fused,可选,默认 None,是否使用一个更快的、融合的实现方法。
- virtual_batch_size,可选,默认 None,是一个 int 数字,指定一个虚拟 batch size。
- adjustment,可选,默认 None,对标准化后的结果进行适当调整的方法。
最后的一些参数说明不够详尽,更详细的用法参考:https://www.tensorflow.org/api_docs/python/tf/layers/batch_normalization。
其用法很简单,在输入数据后面加一层 batch_normalization() 即可:
x = tf.layers.Input(shape=[32])
x = tf.layers.batch_normalization(x)
y = tf.layers.dense(x, 20)
11.2.3 tf.layers.dense
dense,即全连接网络,layers 模块提供了一个 dense() 方法来实现此操作,定义在 tensorflow/python/layers/core.py 中,下面我们来说明一下它的用法。
dense(
inputs,
units,
activation=None,
use_bias=True,
kernel_initializer=None,
bias_initializer=tf.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None
)
参数说明如下:
- inputs:必需,即需要进行操作的输入数据。
- units:必须,即神经元的数量。
- activation:可选,默认为 None,如果为 None 则是线性激活。
- use_bias:可选,默认为 True,是否使用偏置。
- kernel_initializer:可选,默认为 None,即权重的初始化方法,如果为 None,则使用默认的 Xavier 初始化方法。
- bias_initializer:可选,默认为零值初始化,即偏置的初始化方法。
- kernel_regularizer:可选,默认为 None,施加在权重上的正则项。
- bias_regularizer:可选,默认为 None,施加在偏置上的正则项。
- activity_regularizer:可选,默认为 None,施加在输出上的正则项。
- kernel_constraint,可选,默认为 None,施加在权重上的约束项。
- bias_constraint,可选,默认为 None,施加在偏置上的约束项。
- trainable:可选,默认为 True,布尔类型,如果为 True,则将变量添加到 GraphKeys.TRAINABLE_VARIABLES 中。
- name:可选,默认为 None,卷积层的名称。
- reuse:可选,默认为 None,布尔类型,如果为 True,那么如果 name 相同时,会重复利用。
返回值: 全连接网络处理后的 Tensor。
下面我们用一个实例来感受一下它的用法:
x = tf.layers.Input(shape=[32])
print(x)
y1 = tf.layers.dense(x, 16, activation=tf.nn.relu)
print(y1)
y2 = tf.layers.dense(y1, 5, activation=tf.nn.sigmoid)
print(y2)
首先我们用 Input 定义了 [?, 32] 的输入数据,然后经过第一层全连接网络,此时指定了神经元个数为 16,激活函数为 relu,接着输出结果经过第二层全连接网络,此时指定了神经元个数为 5,激活函数为 sigmoid,最后输出,结果如下:
x = tf.layers.Input(shape=[32])
print(x)
y1 = tf.layers.dense(x, 16, activation=tf.nn.relu)
print(y1)
y2 = tf.layers.dense(y1, 5, activation=tf.nn.sigmoid)
print(y2)
可以看到输出结果的最后一维度就等于神经元的个数,这是非常容易理解的。
11.2.4 tf.layers.convolution
convolution,即卷积,这里提供了多个卷积方法,如 conv1d()、conv2d()、conv3d(),分别代表一维、二维、三维卷积,另外还有 conv2d_transpose()、conv3d_transpose(),分别代表二维和三维反卷积,还有 separable_conv2d() 方法代表二维深度可分离卷积。它们定义在 tensorflow/python/layers/convolutional.py 中,其用法都是类似的,在这里以 conv2d() 方法为例进行说明。
conv2d(
inputs,
filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format='channels_last',
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer=None,
bias_initializer=tf.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None
)
参数说明如下:
- inputs:必需,即需要进行操作的输入数据。
- filters:必需,是一个数字,代表了输出通道的个数,即 output_channels。
- kernel_size:必需,卷积核大小,必须是一个数字(高和宽都是此数字)或者长度为 2 的列表(分别代表高、宽)。
- strides:可选,默认为 (1, 1),卷积步长,必须是一个数字(高和宽都是此数字)或者长度为 2 的列表(分别代表高、宽)。
- padding:可选,默认为 valid,padding 的模式,有 valid 和 same 两种,大小写不区分。
- data_format:可选,默认 channels_last,分为 channels_last 和 channels_first 两种模式,代表了输入数据的维度类型,如果是 channels_last,那么输入数据的 shape 为 (batch, height, width, channels),如果是 channels_first,那么输入数据的 shape 为 (batch, channels, height, width)。
- dilation_rate:可选,默认为 (1, 1),卷积的扩张率,如当扩张率为 2 时,卷积核内部就会有边距,3×3 的卷积核就会变成 5×5。
- activation:可选,默认为 None,如果为 None 则是线性激活。
- use_bias:可选,默认为 True,是否使用偏置。
- kernel_initializer:可选,默认为 None,即权重的初始化方法,如果为 None,则使用默认的 Xavier 初始化方法。
- bias_initializer:可选,默认为零值初始化,即偏置的初始化方法。
- kernel_regularizer:可选,默认为 None,施加在权重上的正则项。
- bias_regularizer:可选,默认为 None,施加在偏置上的正则项。
- activity_regularizer:可选,默认为 None,施加在输出上的正则项。
- kernel_constraint,可选,默认为 None,施加在权重上的约束项。
- bias_constraint,可选,默认为 None,施加在偏置上的约束项。
- trainable:可选,默认为 True,布尔类型,如果为 True,则将变量添加到 GraphKeys.TRAINABLE_VARIABLES 中。
- name:可选,默认为 None,卷积层的名称。
- reuse:可选,默认为 None,布尔类型,如果为 True,那么如果 name 相同时,会重复利用。
返回值: 卷积后的 Tensor。
下面我们用实例感受一下它的用法:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2, padding='same')
print(y)
这里我们首先声明了一个 [?, 20, 20, 3] 的输入 x,然后将其传给 conv2d() 方法,filters 设定为 6,即输出通道为 6,kernel_size 为 2,即卷积核大小为 2 x 2,padding 方式设置为 same,那么输出结果的宽高和原来一定是相同的,但是输出通道就变成了 6,结果如下:
Tensor("conv2d/BiasAdd:0", shape=(?, 20, 20, 6), dtype=float32)
但如果我们将 padding 方式不传入,使用默认的 valid 模式,代码改写如下:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2)
print(y)
结果如下:
Tensor("conv2d/BiasAdd:0", shape=(?, 19, 19, 6), dtype=float32)
结果就变成了 [?, 19, 19, 6],这是因为步长默认为 1,卷积核大小为 2 x 2,所以得到的结果的高宽即为 (20 – (2 – 1)) x (20 – (2 – 1)) = 19 x 19。
当然卷积核我们也可以变换大小,传入一个列表形式:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=[2, 3])
print(y)
这时我们的卷积核大小变成了 2 x 3,即高为 2,宽为 3,结果就变成了 [?, 19, 18, 6],这是因为步长默认为 1,卷积核大小为 2 x 2,所以得到的结果的高宽即为 (20 – (2 – 1)) x (20 – (3 – 1)) = 19 x 18。
如果我们将步长也设置一下,也传入列表形式:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=[2, 3], strides=[2, 2])
print(y)
这时卷积核大小变成了 2 x 3,步长变成了 2 x 2,所以结果的高宽为 ceil(20 – (2- 1)) / 2 x ceil(20 – (3- 1)) / 2 = 10 x 9,得到的结果即为 [?, 10, 9, 6]。
运行结果如下:
Tensor("conv2d_4/BiasAdd:0", shape=(?, 10, 9, 6), dtype=float32)
另外我们还可以传入激活函数,或者禁用 bias 等操作,实例如下:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2, activation=tf.nn.relu, use_bias=False)
print(y)
这样我们就将激活函数改成了 relu,同时禁用了 bias,运行结果如下:
Tensor("conv2d_5/Relu:0", shape=(?, 19, 19, 6), dtype=float32)
另外还有反卷积操作,反卷积顾名思义即卷积的反向操作,即输入卷积的结果,得到卷积前的结果,其参数用法是完全一样的,例如:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d_transpose(x, filters=6, kernel_size=2, strides=2)
print(y)
例如此处输入的图像高宽为 20 x 20,经过卷积核为 2,步长为 2 的反卷积处理,得到的结果高宽就变为了 40 x 40,结果如下:
Tensor("conv2d_transpose/BiasAdd:0", shape=(?, 40, 40, 6), dtype=float32)
11.2.5 tf.layers.pooling
pooling,即池化,layers 模块提供了多个池化方法,这几个池化方法都是类似的,包括 max_pooling1d()、max_pooling2d()、max_pooling3d()、average_pooling1d()、average_pooling2d()、average_pooling3d(),分别代表一维二维三维最大和平均池化方法,它们都定义在 tensorflow/python/layers/pooling.py 中,这里以 max_pooling2d() 方法为例进行介绍。
max_pooling2d(
inputs,
pool_size,
strides,
padding='valid',
data_format='channels_last',
name=None
)
参数说明如下:
- inputs: 必需,即需要池化的输入对象,必须是 4 维的。
- pool_size:必需,池化窗口大小,必须是一个数字(高和宽都是此数字)或者长度为 2 的列表(分别代表高、宽)。
- strides:必需,池化步长,必须是一个数字(高和宽都是此数字)或者长度为 2 的列表(分别代表高、宽)。
- padding:可选,默认 valid,padding 的方法,valid 或者 same,大小写不区分。
- data_format:可选,默认 channels_last,分为 channels_last 和 channels_first 两种模式,代表了输入数据的维度类型,如果是 channels_last,那么输入数据的 shape 为 (batch, height, width, channels),如果是 channels_first,那么输入数据的 shape 为 (batch, channels, height, width)。
- name:可选,默认 None,池化层的名称。
返回值: 经过池化处理后的 Tensor。
下面我们用一个实例来感受一下:
x = tf.layers.Input(shape=[20, 20, 3])
print(x)
y = tf.layers.conv2d(x, filters=6, kernel_size=3, padding='same')
print(y)
p = tf.layers.max_pooling2d(y, pool_size=2, strides=2)
print(p)
在这里我们首先指定了输入 x,shape 为 [20, 20, 3],然后对其进行了卷积计算,然后池化,最后得到池化后的结果。结果如下:
Tensor("input_layer_1:0", shape=(?, 20, 20, 3), dtype=float32)
Tensor("conv2d/BiasAdd:0", shape=(?, 20, 20, 6), dtype=float32)
Tensor("max_pooling2d/MaxPool:0", shape=(?, 10, 10, 6), dtype=float32)
可以看到这里池化窗口用的是 2,步长也是 2,所以原本卷积后 shape 为 [?, 20, 20, 6] 的结果就变成了 [?, 10, 10, 6]。
11.2.6 tf.layers.dropout
dropout 是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃,可以用来防止过拟合,layers 模块中提供了 dropout() 方法来实现这一操作,定义在 tensorflow/python/layers/core.py。下面我们来说明一下它的用法。
dropout(
inputs,
rate=0.5,
noise_shape=None,
seed=None,
training=False,
name=None
)
参数说明如下:
- inputs:必须,即输入数据。
- rate:可选,默认为 0.5,即 dropout rate,如设置为 0.1,则意味着会丢弃 10% 的神经元。
- noise_shape:可选,默认为 None,int32 类型的一维 Tensor,它代表了 dropout mask 的 shape,dropout mask 会与 inputs 相乘对 inputs 做转换,例如 inputs 的 shape 为 (batch_size, timesteps, features),但我们想要 droput mask 在所有 timesteps 都是相同的,我们可以设置 noise_shape=[batch_size, 1, features]。
- seed:可选,默认为 None,即产生随机熟的种子值。
- training:可选,默认为 False,布尔类型,即代表了是否标志位 training 模式。
- name:可选,默认为 None,dropout 层的名称。
返回: 经过 dropout 层之后的 Tensor。
我们用一个实例来感受一下:
x = tf.layers.Input(shape=[32])
print(x)
y = tf.layers.dense(x, 16, activation=tf.nn.softmax)
print(y)
d = tf.layers.dropout(y, rate=0.2)
print(d)
运行结果:
Tensor("input_layer_1:0", shape=(?, 32), dtype=float32)
Tensor("dense/Softmax:0", shape=(?, 16), dtype=float32)
Tensor("dropout/Identity:0", shape=(?, 16), dtype=float32)
在这里我们使用 dropout() 方法实现了 droput 操作,并制定 dropout rate 为 0.2,最后输出结果的 shape 和原来是一致的。
11.2.7 tf.layers.flatten
flatten() 方法可以对 Tensor 进行展平操作,定义在 tensorflow/python/layers/core.py。
flatten(
inputs,
name=None
)
参数说明如下:
- inputs:必需,即输入数据。
- name:可选,默认为 None,即该层的名称。
返回结果: 展平后的 Tensor。
下面我们用一个实例来感受一下:
x = tf.layers.Input(shape=[5, 6])
print(x)
y = tf.layers.flatten(x)
print(y)
运行结果:
Tensor("input_layer_1:0", shape=(?, 5, 6), dtype=float32)
Tensor("flatten/Reshape:0", shape=(?, 30), dtype=float32)
这里输入数据的 shape 为 [?, 5, 6],经过 flatten 层之后,就会变成 [?, 30],即将除了第一维的数据维度相乘,对原 Tensor 进行展平。
假如第一维是一个已知的数的话,它依然还是同样的处理,示例如下:
Tensor("input_layer_1:0", shape=(?, 5, 6), dtype=float32)
Tensor("flatten/Reshape:0", shape=(?, 30), dtype=float32)
结果如下:
Tensor("input_layer_1:0", shape=(?, 5, 6), dtype=float32)
Tensor("flatten/Reshape:0", shape=(?, 30), dtype=float32)
12 TensorFlow 计算交叉熵错误问题解决
sparse_softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, name=None)
在使用tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels)语句时产生如下错误:
import tensorflow as tf
labels = [[0.2,0.3,0.5],
[0.1,0.6,0.3]]
logits = [[2,0.5,1],
[0.1,1,3]]result1 = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits)如果这样用就会报这个错ValueError: Rank mismatch: Rank of labels (received 2) should equal rank of logits minus 1 (received 2).
- 原因是logits和labels在使用时有labels应该少一维的限制。
比如一个tensorflow的分类问题,logits应该是batch×classes的一个矩阵,classes为类别数量labels应该是长batch的一个数组,当logits判断图片为某一类时,对应classes的位置为1- 例如:
- 例子,比如猫狗大战吧。当你一批次训练10张图片时,batch为10,猫狗总共两种分类。logits就应该是类似这样的一个10×2的矩阵:
[
0 1
1 0
0 1
0 1
0 1
1 0
1 0
1 0
1 0
0 1
] - 10是10张图片, 每行第一个位置如果为1,那么这一张图片是狗,每行第二个位置如果为1,那么这一张图片是猫。而此时的label应该是一个这样的一维矩阵:[2 1 2 2 2 1 1 1 1 2]logits和label满足这种形式时使用tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels)语句是正确的。
- 例子,比如猫狗大战吧。当你一批次训练10张图片时,batch为10,猫狗总共两种分类。logits就应该是类似这样的一个10×2的矩阵:
- 例如:
下面演示一下,两个方法的正确用法:
import tensorflow as tf
labels = [[0.2,0.3,0.5],
[0.1,0.6,0.3]]
logits = [[2,0.5,1],
[0.1,1,3]]result1 = tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits)
import tensorflow as tf
labels = [0,2]
logits = [[2,0.5,1],
[0.1,1,3]]
result1 = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits)
总结:
- sparse_softmax_cross_entropy_with_logits 主要用于结果是排他唯一性的计算, 如果是非唯一的需要用到 softmax_cross_entropy_with_logits。
- sparse_softmax_cross_entropy的logits通常是最后的全连接层的输出结果,labels是具体哪一类的标签,这个函数是直接使用标签数据的,而不是采用one-hot编码形式。
- .softmax_cross_entropy使用标签数据的one-hot编码形式。
tf.nn.in_top_k函数的用法
- tf.nn.in_top_k主要是用于计算预测的结果和实际结果的是否相等,返回一个bool类型的张量
- tf.nn.in_top_k(predictions,targets,k,name=None)函数
-
这输出了一个batch_size bool数组,如果目标类的预测是所有预测(例如i)中的前k个预测,则条目out[i]为true.请注意,InTopK的行为在处理关系时与TopK操作不同;如果多个类具有相同的预测值并跨越top-k边界,则所有这些类都被认为是在前k个.
-
参数:
- predictions:float32类型的Tensor.batch_size x classes张量.
- targets:一个Tensor,必须是以下类型之一:int32,int64.类ids的batch_size向量.
- k:int类型,要计算精度的顶级元素数.
- name:操作的名称(可选).
-
返回:
- bool类型的Tensor.以bool Tensor计算k处的精度.
'''
predictions: 你的预测结果(一般也就是你的网络输出值)大小是预测样本的数量乘以输出的维度
target: 实际样本类别的标签,大小是样本数量的个数
k: 每个样本中前K个最大的数里面(序号)是否包含对应target中的值'''
import tensorflow as tf
A = tf.Variable([[0.8, 0.4, 0.5, 0.6],[0.1, 0.9, 0.2, 0.4],[0.1, 0.9, 0.4, 0.2]])
B = tf.Variable([1, 1, 2])
result = tf.nn.in_top_k(A, B, 2)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(A))
print(sess.run(B))
print(sess.run(result))k=1 [False True False]
k=2 [False True True]
'''
解释:
k取1的时候:
因为A中第一个元素的最大值为0.8,索引(序号)是0,而B是1,不包含B,所以返回False.
A中第二个元素的最大值为0.9,索引(序号)是1,而B是1,包含B,所以返回True.
A中第三个元素的最大值为0.9,索引(序号)是1,而B是2,不包含B,所以返回False.
k取2的时候:
因为A中前两个元素的最大值为0.8,0.6,索引(序号)是0,3,而B是1,不包含B,所以返回False.
A中前两个元素的最大值为0.9,0.4,索引(序号)是1,3,而B是1,包含B,所以返回True.
A中前两个元素的最大值为0.9,0.4,索引(序号)是1,2,而B是2,包含B,所以返回True.
'''
-
案例:TensorFlow1实现全连接神经网络识别手势数据集
"""
一天下午,我们和一些朋友决定教我们的电脑破译手语。我们花了几个小时在白色的墙壁前拍照,于是就有了了以下数据集。现在,你的任务是建立一个算法,使有语音障碍的人与不懂手语的人交流。
训练集:有从0到5的数字的1080张图片(64x64像素),每个数字拥有180张图片。
测试集:有从0到5的数字的120张图片(64x64像素),每个数字拥有5张图片。
需要注意的是这是完整数据集的一个子集,完整的数据集包含更多的符号。
下面是每个数字的样本,以及我们如何表示标签的解释。这些都是原始图片,我们实际上用的是64 * 64像素的图片。
"""
import numpy as np
import h5py
import matplotlib.pyplot as plt
import time
import math
import tensorflow as tf
# %matplotlib inline #如果你使用的是jupyter notebook取消注释
np.random.seed(1)
class GestureSymbolRecognition(object):
def __init__(self):
self.learning_rate = 0.0001
self.num_epochs = 1500
self.minibatch_size = 32
self.regularazion_rate = 0.001
self.n_inputs = 12288
self.n_hidden1 = 100
self.n_hidden2 = 100
self.n_hidden3 = 100
self.n_outputs = 6
self.batch_norm_momentum = 0.9
def load_data(self):
"""
加载数据集
:return:
"""
# 从文件中读取训练集数据
train_dataset = h5py.File('datasets/train_signs.h5', "r")
# 从训练集数据中提取特征值与标签值数据
train_set_x_orig = np.array(train_dataset["train_set_x"][:])
train_set_y_orig = np.array(train_dataset["train_set_y"][:])
# 从文件中读取测试集数据
test_dataset = h5py.File('datasets/test_signs.h5', "r")
# 从测试集数据中提取特征值与标签值数据
test_set_x_orig = np.array(test_dataset["test_set_x"][:])
test_set_y_orig = np.array(test_dataset["test_set_y"][:])
classes = np.array(test_dataset["list_classes"][:]) # 类别列表
classes_num = len(classes) # 数据集标签分为几类
train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
X_train_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1) # 每一列就是一个样本
X_test_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1)
# 归一化数据
X_train = X_train_flatten / 255
X_test = X_test_flatten / 255
# 转换为one-hot编码矩阵
Y_train = self.convert_to_one_hot(train_set_y_orig, len(classes)).T
Y_test = self.convert_to_one_hot(test_set_y_orig, len(classes)).T
return X_train, Y_train, X_test, Y_test, classes_num
def convert_to_one_hot(self, Y, C):
"""
实现one-hot编码
:param Y:标签矩阵
:param C:分类数量
:return:one_hot:one-hot矩阵
"""
one_hot = np.eye(C)[Y.reshape(-1)].T
return one_hot
def random_mini_batches(self, X, Y, mini_batch_size, seed=0):
"""
从数据集中创建一个随机的mini-batch列表
:param X: 输入数据,维度为(输入节点数量,样本数量)
:param Y: 对应的目标值,维度为(1,样本数量)
:param mini_batch_size: 每个mini-batch的样本数量
:param seed: 随机数种子
:return: mini_batches:一个同步列表,维度为(mini_batch_X, mini_batch_Y)
"""
# 指定随机数种子
np.random.seed(seed)
# 获取样本数
m = X.shape[0]
# print('样本数量',m)
# 创建一个同步列表
mini_batches = []
# 第一步:打乱样本的顺序
permutation = list(np.random.permutation(m)) # 返回一个长度为m的随机数组,且里面的数是0到m-1
shuffled_X = X[permutation, :] # 将每一列特征值数据按permutation的顺序来重新排列
shuffled_Y = Y[permutation, :] # .reshape((Y.shape[0], m)) # 将每一列的目标值数据按permutation的顺序来重新排列
# 第二步:分割训练集数据
num_complete_minibatches = math.floor(m / mini_batch_size) # 迭代完成整个训练集的次数
for k in range(0, num_complete_minibatches):
mini_batch_X = shuffled_X[k * mini_batch_size:(k + 1) * mini_batch_size, :]
mini_batch_Y = shuffled_Y[k * mini_batch_size:(k + 1) * mini_batch_size, :]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
# 如果训练集的大小不是mini_batch_size的整数倍,那么最后肯定会剩下一些,取剩余的数据
if m % mini_batch_size != 0:
# 获取剩余部分的数据
mini_batch_X = shuffled_X[mini_batch_size * num_complete_minibatches:, :]
mini_batch_Y = shuffled_Y[mini_batch_size * num_complete_minibatches:, :]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
# 返回同步列表
return mini_batches
def train(self, print_cost=True, is_plot=True):
"""
实现一个三层的TensorFlow神经网络训练逻辑:LINEAR->RELU->LINEAR->RELU->LINEAR->SOFTMAX
参数:
print_cost - 是否打印成本,每100代打印一次
is_plot - 是否绘制曲线图
"""
# 加载数据集
x_train, y_train, x_test, y_test, label_classes_num = self.load_data()
print("训练集样本数 = " + str(x_train.shape[0]))
print("测试集样本数 = " + str(x_test.shape[0]))
print("X_train.shape: " + str(x_train.shape))
print("Y_train.shape: " + str(y_train.shape))
print("X_test.shape: " + str(x_test.shape))
print("Y_test.shape: " + str(y_test.shape))
# 设置图级别的随机数种子
tf.set_random_seed(1)
# 设置操作级别的随机数种子
seed = 3
# 获取输入节点数量和样本数
(m, n_x) = x_train.shape
# 成本函数值列表
costs = []
# 给X和Y创建占位符节点
X = tf.placeholder(tf.float32, shape=(None, self.n_inputs), name="X")
Y = tf.placeholder(tf.int32, shape=(None, label_classes_num), name="y")
# 定义神经网络全连接层
with tf.name_scope("dnn"):
# 使用He权重初始化方法初始化权重
he_init = tf.variance_scaling_initializer(mode="fan_avg")
# 定义全连接层,使用elu激活函数
hidden1 = tf.layers.dense(X, self.n_hidden1, name="hidden1",
kernel_initializer=he_init,
activation=tf.nn.elu, )
hidden2 = tf.layers.dense(hidden1, self.n_hidden2, name="hidden2",
kernel_initializer=he_init,
activation=tf.nn.elu)
hidden3 = tf.layers.dense(hidden2, self.n_hidden3, name="hidden3",
kernel_initializer=he_init,
activation=tf.nn.elu)
outputs = tf.layers.dense(hidden3, self.n_outputs, name="outputs")
# 输出预测的类别
y_proba = tf.nn.softmax(outputs)
# 定义损失函数计算损失
with tf.name_scope('loss'):
# 前向传播要在Z3处停止,因为在TensorFlow中最后的线性输出层的输出作为计算损失函数的输入,所以不需要A3.
xentropy = tf.nn.softmax_cross_entropy_with_logits(labels=Y,
logits=outputs)
loss = tf.reduce_mean(xentropy, name="loss")
# 定义优化器
with tf.name_scope("train_optimizer"):
optimizer = tf.train.AdamOptimizer(self.learning_rate)
training_op = optimizer.minimize(loss)
# # 使用梯度裁剪
# threshold = 1.0 # 设置梯度阈值
# # 定义优化器
# optimizer = tf.train.AdamOptimizer(self.learning_rate)
# # 调用优化器的compute_gradients()方法计算梯度
# grads_and_vars = optimizer.compute_gradients(loss)
# # 使用tf.clip_by_value()函数创建一个裁剪梯度的操作
# capped_gvs = [(tf.clip_by_value(grad, -threshold, threshold), var) for grad, var in grads_and_vars]
# # 创建一个节点使用优化器的apply_gradients()函数应用裁剪的梯度
# training_op = optimizer.apply_gradients(capped_gvs)
# 评估模型,使用准确性作为我们的绩效指标
with tf.name_scope("eval"):
# 计算当前的预测结果
# 检测使用了滑动平静模型的神经网络前向传播是否正确。tf.argmax(average_y,1)
# 计算每一个样例的预测答案。其中average_y是一个batch_size*10的二维数组,每
# 一行表示案例向前传播的结果。tf.argmax的第二个参数为1,表示选取最大值的
# 操作只在第一个维度上进行(x轴上),也就是说只在每一行选取最大值对应的下标
# 于是得到的结果是一个长度为batch的一维数组,这个一维数组中的值就表示了每
# 一个数字对应的样例识别的结果.tf.equal()判断每个Tensor的每一维度是否相同
# 如果相等返回True,否则返回False.
correct_prediction = tf.equal(tf.argmax(y_proba, 1), tf.argmax(Y, 1))
# 计算准确率
# 这个运算首先将一个布尔型的值转换为实数型,然后计算平均值。这一个平均值
# 就代表模型在这一组数据上的正确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 创建初始化所有变量的节点
init = tf.global_variables_initializer()
# 创建一个保存模型的saveOP,设置要保留的最近检测点的模型的数量
saver = tf.train.Saver(max_to_keep=1)
# 开始会话并计算
with tf.Session() as sess:
# 初始化所有变量
sess.run(init)
# 加载模型,从模型中找出与当前训练的模型代码当中(名字一样的OP操作),覆盖原来的值
checkpoin = tf.train.latest_checkpoint('./ckpt/')
# 如果模型已经保存则加载模型
if checkpoin:
saver.restore(sess, checkpoin)
# 计算模型预测的准确率
train_acc = accuracy.eval({X: x_train[100:200], Y: y_train[100:200]})
test_acc = accuracy.eval({X: x_test[100:200], Y: y_test[100:200]})
print("训练集的准确率:", train_acc)
print("测试集的准确率:", test_acc)
else: # 模型未保存,则开始训练模型
# 正常训练模型
for epoch in range(self.num_epochs):
epoch_cost = 0 # 每轮的成本函数值
num_minibatches = math.floor(m / self.minibatch_size) # 分成多少个mini-batch组
seed = seed + 1
minibatches = self.random_mini_batches(x_train, y_train, self.minibatch_size, seed)
for minibatch in minibatches:
# 选择一个minibatch
(minibatch_X, minibatch_Y) = minibatch
# 数据已经准备好了,开始运行session
_, minibatch_cost = sess.run([training_op, loss], feed_dict={X: minibatch_X, Y: minibatch_Y})
# 计算这个minibatch在这一代中所占的误差
epoch_cost = epoch_cost + minibatch_cost / num_minibatches
if epoch % 5 == 0:
# 将每轮迭代的代价函数值存入列表
costs.append(epoch_cost)
# 打印每轮迭代的代价函数值:
if print_cost and epoch % 100 == 0:
print("epoch = " + str(epoch) + " epoch_cost = " + str(epoch_cost))
# 保存学习后的参数
saver.save(sess, './ckpt/my_test1_model.ckpt')
# 绘制代价函数值的学习曲线
if is_plot:
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(self.learning_rate))
plt.show()
# 计算模型学习完成后的训练集及测试集准确率
train_acc = accuracy.eval({X: x_train, Y: y_train})
test_acc = accuracy.eval({X: x_test, Y: y_test})
print("训练集的准确率:", train_acc)
print("测试集的准确率:", test_acc)
if __name__ == '__main__':
# 开始时间
start_time = time.clock()
# 开始训练
nn = GestureSymbolRecognition()
nn.train()
# 结束时间
end_time = time.clock()
# 计算时差
print("CPU的执行时间 = " + str(end_time - start_time) + " 秒")