目录
拓展知识2 tf.image.convert_image_dtype
拓展知识3 set_printoptions 设置数字输出格式
拓展知识1 Python的可视化包 – Matplotlib
拓展知识 2 安装的matplotlib 只能解析PNG格式图片
拓展知识3 matplotlib的常用的两种方式以及pylab
拓展知识6 matplotlib.pyplot.figure函数
拓展知识8 matplotlib.pyplot.imshow()官方使用说明
拓展知识1 理解图像中基本概念:色调、色相、饱和度、对比度、亮度
拓展知识2 python(基础):一行写不下,如何换行继续写
拓展知识1 CNN网络架构演进:从LeNet到DenseNet
拓展知识3 关于tensorflow中的softmax_cross_entropy_with_logits_v2函数的区别
ImageNet,这个包含图像标注信息的数据库见证了近年来计算机视觉和深度学习的日益流行。ImageNet网站每年都会举办一
场大规模视觉识别挑战赛(英文缩写为ILSVRC),要求参与者基于ImageNet所提供的数据库构建能够完成目标的自动检测和分
类任务的系统。在2012年,有一个名为SuperVision的参赛团队提交了一个富有创造性的神经网络架构的解决方案。在以往的
ILSVRC提交结果中并不乏具备创新性的解决方案,但SuperVision在图像分类任务中表现出的空前准确性使其异常出众。
SuperVision为计算机视觉的准确率建立了全新的标准,并激发了人们对一种名为卷积神经网络的深度学习技术的极大兴趣。
人们给予卷积神经网络(CNN)的关注一直在持续得到加强。这类网络结构主要用于计算机视觉相关任务,但处理对象并
不局限于图像。CNN可用于能够表示为张量(各个分量与其相关的分量有序排列在一个多维网格中)的任意类型的数据。微软
研究院于2014年公开了一篇利用CNN进行语音识别的文章,其中输入张量为一个按录音时间顺序排列的声音频率的单行网格。
对于图像,张量中的分量为依据图像的宽和高的次序排列在一个网格中的像素
本篇文章将重点介绍如何在TensorFlow中利用CNN处理图像。目标是利用TensorFlow基于ImageNet数据库的一个子集构建一个用
于图像分类的CNN模型。训练CNN模型需要使用TensorFlow处理图像,并理解卷积神经网络的使用方法。本章的绝大部分篇幅都
将结合TensorFlow介绍计算机视觉中的重要概念。
本章中用于训练CNN模型的数据集Stanford(斯坦福)的Dogs Dataset是ImageNet的一个子集。从其名称可以看出,该数据集中包含了不同品种的狗的图像以及图像中所出现的狗的品种标签。所构建的模型的目标是当给定一幅图像时,它能够精确地预测其中包含的狗的品种
当将上图中的某一幅送入模型时,模型的输出应为标签“Siberian Husky”。仅利用这些示例图像无法公正地对该模型的准确率进行评价,因为它们都来自训练集。为找到一个能够度量模型准确率的公正指标,需要使用大量不曾在训练集中出现的图像,并用它们创建一个独立的测试集
这里之所以要强调图像的公正性,是因为它是将数据集划分为独立的测试集、训练集和验证集的重要考虑因素。在处理输入时,必须要做的一件事是用数据中的大部分来训练一个网络。这种划分使得对模型进行盲测成为可能。如果用参与过训练的图像对模型进行测试,则很可能会得到一个与训练图像过分精确匹配的模型,而使其无法对新的输入产生令人满意的预测结果。测试集的用途是了解模型对不曾出现在训练集中的数据表现如何。随着训练时间的延长和迭代次数的增加,虽然为提升准确率所做的调整有可能导致这样的后果:模型在测试集上的表现越来越好,但面对真实数据时性能却较差。一种比较好的方式是利用一个交叉验证集检查最终的模型,以对模型的准确率做出更为客观的估计。对于图像数据,最好是在进行预处理(如对比度调整或裁剪)时对原始数据集进行划分,并保证对各个划分应用完全相同的输入流水线
1 卷积神经网络
从技术角度看,卷积神经网络是一种至少包含一个层(tf.nn.conv2d)的神经网络,该层的功能是计算其输入f与一组可配置的卷积核g的卷积,以生成该层的输出。可用一种比较简明的定义描述卷积:卷积的目的是将卷积核(滤波器)应用到某个张量的所有点上,并通过将卷积核在输入张量上滑动而生成经过滤波处理的张量
图像处理中的边缘检测便是滤波输出的一个典型例子。一个特殊的卷积核被应用到图像中的每个像素,而输出为一个刻画了所有边缘的新图像。在这种情形下,输入张量是一幅图像,而张量中的每个点都对应于一个像素所包含的红色值、绿色值和蓝色值。卷积核会遍历图像中的每个像素,任何位于边缘的像素对应的卷积输出值都会增大
简单的CNN架构通常包含卷积层(tf.nn.conv2d)、非线性变换层(tf.nn.relu)、池化层(tf.nn.max_pool)及全连接层(tf.nn.matmul)。如果没有这些层,模型便很难与复杂的模式匹配,因为网络将被填充过多的信息。一个设计良好的CNN架构会突出那些重要的信息,而将噪声忽略。本章稍后的内容将详细介绍这些层如何协同工作
这个架构的图像输入遵循一种复杂的格式,以支持图像的批量加载。批量加载图像使得可同时对多幅图像进行处理,但也相应地要求使用更为复杂的数据结构。所使用的数据结构中包含了与一批图像进行卷积运算所需的全部信息。TensorFlow的输入流水线(用于读取和解码文件)拥有一种为使用一个批数据中的多幅图像而设计的专门格式,它包括了任意一幅图像所需的信息([image_batch_size,image_height,image_width,image_channels])。利用下列示例代码,便有可能在TensorFlow中使用图像时检查样例输入的结构
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.constant([
[ # 第一幅图像
[[0, 255, 0], [0, 255, 0], [0, 255, 0]],
[[0, 255, 0], [0, 255, 0], [0, 255, 0]]
],
[ # 第二幅图像
[[0, 255, 0], [0, 255, 0], [0, 255, 0]],
[[0, 255, 0], [0, 255, 0], [0, 255, 0]]
]
])
print(a.get_shape())
这段示例代码创建了一个包含两幅图像的图像批数据。每幅图像的高为2个像素,宽为3个像素,且颜色空间为RGB。执行后的输出的第1组维度表明了图像数量,第2组维度对应图像的高度,第3组维度表明了图像的宽度,而颜色通道数量对应于最后一组维度
获取tensor的形状的,tensor.get_shape()
有一点值得注意,每个像素的索引都会映射到图像的宽和高这两个维度上。若要获取第1幅图像的第1个像素,需要用下列方式访问每一个维度。
变量a并不会从磁盘直接加载图像,而是将自身当成作为输入流水线的一部分而被加载的图像。使用输入流水线从磁盘加载的图像拥有相同的格式和行为。一种常见的做法是创建一些与上述a实例相似的假数据对CNN的输入和输出进行测试。这种简化的输入会使诊断和调试一些简单问题更加容易。简化调试过程非常重要,因为CNN架构极为复杂,训练经常需要耗费数日
CNN架构的第一个复杂性体现在卷积层的工作机理上。任何图像被加载和处理后,卷积层通常是网络的第一层。这第一个卷积层非常有用,因为它可简化网络的其余部分,并用于调试。下一节将重点介绍卷积层的工作机理,以及如何在TensorFlow中使用它们。
2、卷积
从名称可以看出,卷积运算是卷积神经网络的重要组成。CNN与各种模式精确匹配的能力可归功于卷积运算的使用。这些运算要求复杂的输入,这一点已在上一节说明过。本节将对卷积运算及其参数进行实验。
卷积运算对两个输入张量(输入和卷积核)进行卷积,并输出一个代表来自每个输入的信息的张量
2.1 输入和卷积核
通常,TensorFlow中的卷积运算是通过tf.nn.conv2d完成的。对于一些特定用例,TensorFlow还提供了其他卷积运算。在开始实验卷积运算时,推荐使用tf.nn.conv2d。例如,可试验计算两个张量的卷积,并查看结果
若input代表一幅图像,它拥有一个通道。在这种情形下,它将被视为一幅灰度图像。该张量中的每个元素都表示这幅图像中的一个像素。下述代码中卷积核其实为两个1*1的核。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
input = tf.constant([
[ # 第一幅图像
[[0.0], [1.0]],
[[2.0], [3.0]]
],
[ # 第二幅图像
[[2.0], [4.0]],
[[6.0], [8.0]]
]
])
kernel = tf.constant([
[
[[1.0, 2.0]]
]
])
conv2d = tf.nn.conv2d(input, kernel, strides=[1, 1, 1, 1], padding='SAME')
print(sess.run(conv2d))
执行上述代码后,结果如下:
input代表一幅图像,它拥有一个通道。在这种情形下,它将被视为一幅灰度图像。该张量中的每个元素都表示这幅图像中的一个像素。 上述代码中卷积核的作用是返回一个其第1个通道等于原始输入值,第2个通道等于原始输入值两倍的张量。故得到上述结果
可将卷积运算tf.nn.conv2d视为图像(用input_batch表示)和卷积核张量kernel的组合。这两个张量的卷积会生成一幅特征图(feature map)。特征图是一个比较宽泛的术语,但在计算机视觉中,它与使用图像卷积核的运算的输出相关,而现在,特征图通过为输出添加新层代表了这些张量的卷积。
拓展知识1 tf.nn.conv2d
https://www.cnblogs.com/qggg/p/6832342.html
tf.nn.conv2d(input,filter,strides,padding,use_cudnn_on_gpu=True,data_format='NHWC',dilations=[1, 1, 1, 1],name=None)
除去name参数用以指定该操作的name,与方法有关的一共七个参数:
第一个参数input:指需要做卷积的输入图像,它要求是一个Tensor,具有[batch, in_height, in_width, in_channels]这样的shape,具体含义是[训练时一个batch的图片数量, 图片高度, 图片宽度, 图像通道数],注意这是一个4维的Tensor,要求类型为float32和float64其中之一
第二个参数filter:相当于CNN中的卷积核,它要求是一个Tensor,具有[filter_height, filter_width, in_channels, out_channels]这样的shape,具体含义是[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数],要求类型与参数input相同,有一个地方需要注意,第三维in_channels,就是参数input的第四维
第三个参数strides:卷积时在图像每一维的步长,这是一个一维的向量,长度4,具有[batch, in_height, in_width, in_channels]这样的shape
第四个参数padding:string类型的量,只能是"SAME","VALID"其中之一,这个值决定了不同的卷积方式(后面会介绍)
第五个参数:use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认为true
第六个参数:data_format:该参数可取为“NHWC”或“NCHW”,默认值为“NHWC”,用于指定输入和输出数据的格式。当取默认格式“NHWC”时,数据的存储顺序为[batch,in_height,in_width,in_channels]。若该参数取为“NCHW”,数据存储顺序为[batch,in_channels,in_height,in_width]
第七个参数:扩张:一个可选的整数列表。 默认为[1,1,1,1]。 长度的1-D张量4.输入的每个维度的膨胀系数。 如果设置为k> 1,则该维度上的每个元素之间将有k-1个跳过的单元格。 维度顺序由data_format的值确定。 批次和深度尺寸的扩张必须为1。
结果返回一个Tensor,这个输出,就是我们常说的feature map,shape仍然是[batch, height, width, channels]这种形式。
2.2 跨度strides
在计算机视觉中,卷积的价值体现在对输入(本例中为图像)降维的能力上。一幅2D图像的维数包括其宽度、高度和通道数。如果图像具有较高的维数,则意味着神经网络扫描所有图像以判断各像素的重要性所需的时间呈指数级增长。利用卷积运算对图像降维是通过修改卷积核的strides(跨度)参数实现的
参数strides使得卷积核可跳过图像中的一些像素,从而在输出中不包含它们。实际上,说这些像素“被跳过”并不十分准确,因为它们仍然会对输出产生影响。strides参数指定了当图像维数较高,且使用了较为复杂的卷积核时,卷积运算应如何进行。当卷积运算用卷积核遍历输入时,它利用这个跨度参数来修改遍历输入的方式。strides参数使得卷积核无需遍历输入的每个元素,而是可以直接跳过某些元素
例如,假设需要计算一幅较大的图像和一个较大的卷积核之间的卷积运算。在这个例子中,图像的高度为6个像素,宽度为6个像素,而深度为1个通道(6×6×1),卷积核尺寸为(3×3×1)。
设置跨度为【1,1,1,1】时
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
input = tf.constant([
[
[[0.0], [1.0], [2.0], [3.0], [4.0], [5.0]],
[[0.1], [1.1], [2.1], [3.1], [4.1], [5.1]],
[[0.2], [1.2], [2.2], [3.2], [4.2], [5.2]],
[[0.3], [1.3], [2.3], [3.3], [4.3], [5.3]],
[[0.4], [1.4], [2.4], [3.4], [4.4], [5.4]],
[[0.5], [1.5], [2.5], [3.5], [4.5], [5.5]]
]
])
kernel = tf.constant([
[[[0.0]], [[0.5]], [[0.0]]],
[[[0.0]], [[1.0]], [[0.0]]],
[[[0.0]], [[0.5]], [[0.0]]]
])
# 修改跨度只需修改strides后面的参数,
conv2d = tf.nn.conv2d(input, kernel, strides=[1, 1, 1, 1], padding='SAME')
result = sess.run(conv2d)
print(result)
上述代码运行结果如下:跨度strides依次为[1,1,1,1],[1,2,2,1],[1,3,3,1]
通过将kernel在input上滑动,同时跨过(或跳过)某些元素,input与kernel便结合在一起。kernel每次移动时,都将inputh的一个元素作为中心。然后,位置重叠的值相乘,再将这些乘积相加得到卷积的结果。卷积就是通过这种逐点相乘的方式将两个输入整合在一起的。利用下图可更容易地将卷积运算可视化。下图中strides为【1,3,3,1】
上图所体现的是与之前的代码完全相同的逻辑。两个张量进行了卷积运算,但卷积核会跳过输入中的一些固定数目的元素。strides显著降低了输出的维数,而卷积核允许卷积使用所有的输入值。在输入数据中,没有任何元素在被跳过时被移除,但它仍然变成了一个形状更小的张量。
设置跨度是一种调整输入张量维数的方法。降维可减少所需的运算量,并可避免创建一些完全重叠的感受域。strides参数的格式与输入向量相同,即(image_batch_size_stride、image_height_stride、image_width_stride、image_channels_stride)。第1个和最后一个跨度参数通常很少修改,因为它们会在tf.nn.conv2d运算中跳过一些数据,从而不将这部分数据予以考虑。如果希望降低输入的维数,可修改image_height_stride和image_width_stride参数
在对输入使用跨度参数时,所面临的一个挑战是如何应对那些不是恰好在输入的边界到达尽头的跨度值。非均匀的跨越通常在图像尺寸和卷积核尺寸与跨度参数不匹配时出现。如果图像尺寸、卷积核尺寸和strides参数都无法改变,则可采取对图像填充边界的方法来处理那些非均匀区域。
2.3 边界填充
当卷积核与图像重叠时,它应当落在图像的边界内。有时,两者尺寸可能不匹配,一种较好的补救策略是对图像缺失的区域进行填充,即边界填充。TensorFlow会用0进行边界填充,或当卷积核与图像尺寸不匹配,但又不允许卷积核跨越图像边界时,会引发一个错误。tf.nn.conv2d的零填充数量或错误状态是由参数padding控制的,它的取值可以是SAME或VALID
·SAME:卷积输出与输入的尺寸相同。这里在计算如何跨越图像时,并不考虑滤波器的尺寸。选用该设置时,缺失的像素将用0填充,卷积核扫过的像素数将超过图像的实际像素数。
·VALID :在计算卷积核如何在图像上跨越时,需要考虑滤波器的尺寸。这会使卷积核尽量不越过图像的边界。在某些情形下,可能边界也会被填充
在计算卷积时,最好能够考虑图像的尺寸,如果边界填充是必要的,则TensorFlow会有一些内置选项。在大多数比较简单的情形下,SAME都是一个不错的选择。当指定跨度参数后,如果输入和卷积核能够很好地工作,则推荐使用VALID。
2.4 数据格式
tf.nn.conv2d还有另外一个参数data_format未在上述例程中使用。tf.nn.conv2d文档详细解释了如何修改数据格式,以使input、kernel和strides遵循某种与到目前为止所使用的格式不同的格式。如果有某个输入张量未遵循[batch_size,height,width,channel]标准,则修改该格式便非常有用。除了修改输入的格式,使之与标准匹配外,也可修改data_format参数以使用一种不同的布局
data_format:该参数可取为“NHWC”或“NCHW”,默认值为“NHWC”,用于指定输入和输出数据的格式。当取默认式“NHWC”时,数据的存储顺序为[batch,in_height,in_width,in_channels]。若该参数取为“NCHW”,数据存储顺序为[batch,in_channels,in_height,in_width]
2.5 深入探讨卷积核
在TensorFlow中,滤波器参数用于指定与输入进行卷积运算的卷积核。滤波器通常用于摄影中以调整图片的属性,如允许到达摄像机透镜的光通量。在摄影中,摄影者可借助滤波器对所拍摄的图片做出大幅度的修改。摄影者之所以能够利用滤波器对图片进行修改,是因为滤波器能够识别到达透镜的光线的特定属性。例如,红色透镜滤波器会吸收(或阻止)不同于红色的每种频率的光,使得只有红色光可通过该滤波器。
在计算机视觉中,卷积核(滤波器)常用于识别数字图像中的重要属性。当某些滤波器感兴趣的特征在图像中存在时,滤波器会使用特定模式突出这些特征。若将除红色外的所有颜色值减小,则可得到一个红色滤波器的卷积核。在这种情形下,红色值将保持不变,而其他任何匹配的颜色值将被减小
边缘检测卷积核在计算机视觉应用中极为常见,它可用基本的TensorFlow运算和一个tf.nn.conv2d运算实现
3 常见层
一个神经网络架构要成为CNN,必须至少包含一个卷积层(tf.nn.conv2d)。单层CNN的一种实际用途是检测边缘。对于图像识别和分类任务而言,更常见的情形是使用不同的层类型支持某个卷积层。这些层有助于减少过拟合,并可加速训练过程和降低内存占用率。本章所涵盖的层主要集中于那些在CNN架构中经常使用的层上。CNN可使用的层并非只局限于这些层,它们完全可以与为其他网络架构设计的层混合使用
3.1 卷积层
我们已经对一种类型的卷积层——tf.nn.conv2d进行了详细介绍,但对高级用户,还有一些注意事项需要说明。TensorFlow中的卷积层所完成的并非真正的卷积, 实际上,卷积与TensorFlow所采用的运算的差异主要体现在性能上。
TensorFlow采用了一种可对所有不同类型的卷积层中的卷积运算进行加速的技术。每种类型的卷积层都有一些用例,但tf.nn.conv2d是一个较好的切入点。其他类型的卷积也十分有用,但在构建能够完成目标识别和分类任务的网络时,并不需要它们。下面对这些卷积类型做一简要概括
1. tf.nn.depthwise_conv2d
当需要将一个卷积层的输出连接到另一个卷积层的输入时,可使用这种卷积。一种高级用例是利用tf.nn.depthwise_conv2d创建一个遵循Inception架构的网络(参见https://arxiv.org/abs/1512.00567 )。
2. tf.nn.separable_conv2d
它与tf.nn.conv2d类似,但并非后者的替代品。对于规模较大的模型,它可在不牺牲准确率的前提下实现训练的加速。对于规模较小的模型,它能够快速收敛,但准确率较低。
3. tf.nn.conv2d_transpose
它将一个卷积核应用于一个新的特征图,后者的每一部分都填充了与卷积核相同的值。当该卷积核遍历新图像时,任何重叠的部分都相加在一起。这就很好地解释了斯坦福大学课程CS231n Winter 2016:Lecture 13中关于如何将tf.nn.conv2d_transpose用于可学习的降采样的问题
3.2 激活函数
这些函数与其他层的输出联合使用可生成特征图。它们用于对某些运算的结果进行平滑(或微分)。其目标是为神经网络引入非线性。非线性意味着输入和输出的关系是一条曲线,而非直线。曲线能够刻画输入中更为复杂的变化。例如,非线性映射能够描述那些大部分时间值都很小,但在某个单点会周期性地出现极值的输入。为神经网络引入非线性可使其对在数据中发现的复杂模式进行训练。
TensorFlow提供了多种激活函数。在CNN中,人们之所以主要使用tf.nn.relu,是因为它虽然会带来一些信息损失,但性能较为突出。开始设计模型时,推荐使用tf.nn.relu,但高级用户也可创建自己的激活函数。评价某个激活函数是否有用时,可考虑下列为数不多的几个主要因素。
1)该函数应是单调 的 ,这样输出便会随着输入的增长而增长,从而使利用梯度下降法寻找局部极值点成为可能。
2)该函数应是可微分的 ,以保证该函数定义域内的任意一点上导数都存在,从而使得梯度下降法能够正常使用来自这类激活函数的输出。
任何满足这些条件的函数都可用作激活函数。在TensorFlow中,有少量激活函数值得一提,它们在各种CNN架构中都极为常见。下面给出这些激活函数的简要介绍,并通过一些示例代码片段来说明其用法
1.tf.nn.relu
在某些文档中,修正线性单元也被称为斜坡函数,因为它的图形与滑板的斜坡非常相似。ReLU是分段线性的,当输入为非负时,输出将与输入相同;而当输入为负时,输出均为0。它的优点在于不受“梯度消失”的影响,且取值范围为 ;其缺点在于当使用了较大的学习速率时,易受达到饱和的神经元的影响
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.range(-2, 3)
b = tf.nn.relu(a)
print(sess.run(b))
writer = tf.summary.FileWriter('./hh', sess.graph)
writer.close()
sess.close()
执行结果如下
在上面这个例子中,输入为一个由[-2,3]内的整数构成的秩1张量(向量)。tf.nn.relu会将那些小于0的分量置为0,而保持其余分量不变
拓展知识1 tf.range函数:创建数字序列
range(limit, delta=1, dtype=None, name='range')
range(start, limit, delta=1, dtype=None, name='range')
创建一个数字序列,该数字开始于 start 并且将 delta 增量扩展到不包括 limit 的序列
参数:
start:0-D Tensor(标量)。若 limit 不是 None,则作为该范围中的第一个条目;否则,作为范围限制,并且第一项默认为0。
limit:0-D Tensor(标量)。序列上限,具有排他性。如果没有,则默认值为 start,但是该范围的第一个条目默认为0。
delta:0-D Tensor(标量)。递增 start 的数字。默认为1。
dtype:结果张量的元素的类型。
name:操作的名称。默认为“range”。
返回:dtype 类型的一维 Tensor。
拓展知识2 tf.nn.relu
tf.nn.relu(features, name = None)
这个函数的作用是计算激活函数 relu,即 max(features, 0)。即将矩阵中每行的非最大值置0。
2.tf.sigmoid
sigmoid函数的返回值位于区间[0.0,1.0]中。当输入值较大时,tf.sigmoid将返回一个接近于1.0的值,而输入值较小时,返回值将接近于0.0。对于在那些真实输出位于[0.0,1.0]的样本上训练的神经网络,sigmoid函数可将输出保持在[0.0,1.0]内的能力非常有用。当输入接近饱和或变化剧烈时,对输出范围的这种缩减往往会带来一些不利影响。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.to_float(tf.range(-2, 3)) # 因为sigmoid现在 仅可接收浮点值,所以需要进行转换
b = tf.sigmoid(a, name='ling')
print(sess.run(b))
writer = tf.summary.FileWriter('./hh', sess.graph)
writer.close()
sess.close()
运行结果如下
在本例中,一组整数被转化为浮点类型(1变为1.0),并传入一个sigmoid函数。当输入为0时,sigmoid函数的输出为0.5,即sigmoid函数值域的中间点
3.tf.tanh
双曲正切函数(tanh)与tf.sigmoid非常接近,且与后者具有类似的优缺点。tf.sigmoid和tf.tanh的主要区别在于后者的值域为[-1.0,1.0]。在某些特定的网络架构中,能够输出负值的能力可能会非常有用
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.to_float(tf.range(-2, 3)) # 因为tanh现在 仅可接收浮点值,所以需要进行转换
b = tf.tanh(a, name='ling')
print(sess.run(b))
writer = tf.summary.FileWriter('./hh', sess.graph)
writer.close()
sess.close()
函数执行结果如下
在本例中,所有的设置均与上述tf.sigmoid例子相同,但输出却存在重要的差异。tf.tanh值域的中间点为0.0。当网络中的下一层期待输入为负值或0.0时,这可能会引发一些问题
4.tf.nn.dropout
依据某个可配置的概率将输出设为0.0。当引入少量随机性有助于训练时,这个层会有很好的表现。一种适合的场景是:当要学习的一些模式与其近邻特征耦合过强时。这种层会为所学习到的输出添加少量噪声。
注意:这种层应当只在训练阶段使用。如果在测试阶段使用该层,它所引入的随机噪声将对结果产生误导
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.to_float(tf.range(-2, 3))
b = tf.nn.dropout(a, keep_prob=0.2, name='ling')
print(sess.run(b))
writer = tf.summary.FileWriter('./hh', sess.graph)
writer.close()
sess.close()
执行结果如下
def dropout(x, keep_prob, noise_shape=None, seed=None, name=None)
tensorflow中的dropout就是:使输入tensor中某些元素变为0,其它没变0的元素变为原来的1/keep_prob大小!
每次执行该层时,都将得到不同的输出(带有一些随机性)。当某个输出被丢弃时,它的值被设为0.0
3.3 池化层
池化层能够减少过拟合,并通过减小输入的尺寸来提高性能。它们可用于对输入降采样,但会为后续层保留重要的信息。只使用tf.nn.conv2d来减小输入的尺寸也是可以的,但池化层的效率更高
1. tf.nn.max_pool
跳跃遍历某个张量,并从被卷积核覆盖的元素中找出最大的数值作为卷积结果。当输入数据的灰度与图像中的重要性相关时,这种池化方式非常有用。
最大池化(max-pooling)通常是利用2×2的接受域(高度和宽度均为2的卷积核)完成的,它通常也被称为“2×2的最大池化运算”。使用2×2的接受域的原因之一在于它是在单个通路上能够实施的最小数量的降采样。如果使用1×1的接受域,则输出将与输入相同
def max_pool(value, ksize, strides, padding, data_format="NHWC", name=None):
value:池化的输入,一般池化层接在卷积层的后面,所以输出通常为feature map。feature map依旧是[batch, in_height, in_width, in_channels]这样的参数。
ksize:池化窗口的大小,参数为四维向量,通常取[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1。
stries:步长,同样是一个四维向量。
padding:填充方式同样只有两种不重复了
2.tf.nn.avg_pool
跳跃遍历一个张量,并将被卷积核覆盖的各深度值取平均。当整个卷积核都非常重要时,若需实现值的缩减,平均池化是非常有用的,例如输入张量宽度和高度很大,但深度很小的情况
3.4 归一化
归一化层并非CNN所独有。在使用tf.nn.relu时,考虑输出的归一化是有价值的。由于ReLU是无界函数,利用某些形式的归一化来识别那些高频特征通常是十分有用的。
层的输入格式为[batch,image_height,image_width,image_channels]。归一化会将输出调整到区间[-1.0,1.0]中。归一化层tf.nn.relu会将其无界的输出调整到相同的范围内。
3.5 高级层
为使标准层的定义在创建时更加简单,TensorFlow引入了一些高级网络层。这些层不是必需的,但它们有助于减少代码冗余,同时遵循最佳的实践。开始时,这些层需要为数据流图添加大量非核心的节点。在使用这些层之前,投入一些精力了解相关基础知识是非常值得的
1.tf.contrib.layers.convolution2d【已经替换为tf.layers.conv2d】
convolution2d层与tf.nn.conv2d的逻辑相同,但还包括权值初始化、偏置初始化、可训练的变量输出、偏置相加以及添加激活函数的功能。CNN中的这些步骤有许多目前尚未介绍,但应熟练掌握。每个卷积核都是一个可训练的变量(CNN的目标是训练该变量),权值初始化用于在卷积核首次运行时,为其进行值的填充(tf.truncated_normal)。其余参数与之前使用过的类似,只是使用了缩写的版本。无需声明完整的卷积核,它采用简单的元组形式(1,1)表示卷积核的高度和宽度
def 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=init_ops.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None):
https://blog.csdn.net/gqixf/article/details/80519912
inputs
:Tensor 输入
filters
:整数,表示输出空间的维数(即卷积过滤器的数量)
kernel_size
:一个整数,或者包含了两个整数的元组/队列,表示卷积窗的高和宽。如果是一个整数,则宽高相等。
strides
:一个整数,或者包含了两个整数的元组/队列,表示卷积的纵向和横向的步长。如果是一个整数,则横纵步长相等。另外, strides
不等于1 和 dilation_rate
不等于1 这两种情况不能同时存在。
padding
:"valid"
或者 "same"
(不区分大小写)。"valid"
表示不够卷积核大小的块就丢弃,"same"
表示不够卷积核大小的块就补0。 "valid"
的输出形状为"valid"
的输出形状为其中, 为输入的 size(高或宽), 为 filter 的 size, 为 strides 的大小, 为向上取整。
data_format
:channels_last
或者 channels_first
,表示输入维度的排序。
dilation_rate
:一个整数,或者包含了两个整数的元组/队列,表示使用扩张卷积时的扩张率。如果是一个整数,则所有方向的扩张率相等。另外, strides
不等于1 和 dilation_rate
不等于1 这两种情况不能同时存在。
activation
:激活函数。如果是None
则为线性函数。
use_bias
:Boolean
类型,表示是否使用偏差向量。
kernel_initializer
:卷积核的初始化。默认为initializers.xavier_initializer()函数
bias_initializer
:偏差向量的初始化。如果是None
,则使用默认的初始值。默认为init_ops.zeros_initializer()函数。
kernel_regularizer
:卷积核的正则项
bias_regularizer
:偏差向量的正则项
activity_regularizer
:输出的正则函数
kernel_constraint
:映射函数,当核被Optimizer
更新后应用到核上。Optimizer
用来实现对权重矩阵的范数约束或者值约束。映射函数必须将未被影射的变量作为输入,且一定输出映射后的变量(有相同的大小)。做异步的分布式训练时,使用约束可能是不安全的。
bias_constraint
:映射函数,当偏差向量被Optimizer
更新后应用到偏差向量上。
trainable
:Boolean
类型。是否可训练,如作为训练节点,必须设置为True,默认即可。如果我们是微调网络,有时候需要冻结某一层的参数,则设置为False
name
:字符串,层的名字。
reuse
:Boolean
类型,表示是否可以重复使用具有相同名字的前一层的权重。
该函数使用示例如下
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
image_input = tf.constant([
[
[[0., 0., 0.], [255., 255., 255.], [254., 0., 0.]],
[[0., 191., 0.], [3., 108., 233.], [0., 191., 0.]],
[[254., 0., 0.], [255., 255., 255.], [0., 0., 0.]]
]
])
conv2d = tf.layers.conv2d(image_input, filters=4, kernel_size=[1, 1], activation=tf.nn.relu, strides=[1, 1], trainable=True)
# 注意:函数tf.layers.conv2d函数内有卷积核等variables类型变量,故必须执行以下一步,否则会报错
sess.run(tf.global_variables_initializer())
print(sess.run(conv2d))
执行结果如下
这个例子设置了一个与由单幅图像构成的批数据的完整卷积,所有的参数都基于本章所介绍的各步骤。主要的差异在于tf.layers.conv2d需要完成大量设置,而一旦设置完成,便无需再次编写。对于高级用户而言,该层可帮助他们节省大量时间。
注意:当输入为一幅图像时,不应使用tf.to_float,而应使用tf.image.convert_image_dtype,该方法将以恰当的方式调整各分量以表示颜色值。在这段示例代码中,使用了浮点值255.0,这并不是TensorFlow用浮点值表示图像所期望的方式。TensorFlow要求用浮点型描述图像颜色时,应当将各颜色分量控制在[0,1]范围内。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
with tf.Session() as sess:
a = tf.constant([
[
[[0, 0, 0], [255, 255, 255], [254, 0, 0]],
[[0, 191, 0], [3, 108, 233], [0, 191, 0]],
[[254, 0, 0], [255, 255, 255], [0, 0, 0]]
]
], dtype=tf.int32)
image_input = tf.image.convert_image_dtype(a, dtype=tf.float32)
print(sess.run(image_input))
print(a)
执行结果如下
拓展知识1 科学计数法的E是什么
https://zhidao.baidu.com/question/81692142.html?qq-pf-to=pcqq.c2c
科学记数:此格式用指数表示法显示数字,以 E+n 替换部分数字,其中 E(代表指数)表示将前面的数字乘以 10 的 n 次幂。科学记数法的形式是由两个数的乘积组成的。表示为a×10^b(aEb),其中一个因数为a(1≤|a|<10),另一个因数为10^n
拓展知识2 tf.image.convert_image_dtype
https://blog.csdn.net/wc781708249/article/details/78392754
将图像转换为dtype,如果需要,缩放其值。convert_image_dtype(image,dtype,saturate=False,name=None)
1.如果image的数值类型是int,dtype=float时会将数值缩放到[0,1)范围
2.如果image原本数据类型为unit8,而转成unit16,dtype=float,会发现缩放后的数值存在很大的问题
3.如果image的数值类型是float,dtype=float 数值不会缩放到[0,1)范围
上述三个情况的效果如下:
拓展知识3 set_printoptions 设置数字输出格式
set_printoptions(precision=None, threshold=None, edgeitems=None,
linewidth=None, suppress=None, nanstr=None, infstr=None,
formatter=None, sign=None, floatmode=None, **kwarg):
https://blog.csdn.net/weixin_41043240/article/details/79721114
precision : int, optional,float输出的精度,即小数点后维数,默认8( Number of digits of precision for floating point output (default 8))
threshold : int, optional,当数组数目过大时,设置显示几个数字,其余用省略号threshold=np.nan意思是输出数组的时候完全输出,不需要省略号将中间数据省略
edgeitems : int, optional,边缘数目,default 3 linewidth default 75 suppress : bool, optional,是否压缩由科学计数法表示的浮点数,default False)
注意,此函数在numpy包内,故使用此函数前要导入该包 import numpy as np https://blog.csdn.net/dajiyi1998/article/details/80938587
python 使程序输出不是科学计数法的形式,而是浮点型的数据输出。 np.set_printoptions(suppress=True)
2.tf.contrib.layers.fully_connected
在全连接层中,每个输入与每个输出之间都存在连接。在许多架构中,这个层都极为常见。对于CNN,最后一层通常都是全连接层。tf.contrib.layers.funlly_connected层提供了大量创建这个最后层的捷径,同时遵循了最佳实践原则。
通常,TensorFlow中的全连接层的格式是tf.matmul(features,weight)+bias,其中feature、weight和bias均为张量。该层完成的也是相同的任务,但同时也会对由管理张量weight和bias所引发的复杂性加以考虑
https://blog.csdn.net/JNingWei/article/details/78091466?fps=1&locationNum=8
tf.contrib.layers.fully_connected (inputs, num_outputs, activation_fn=tf.nn.relu)
其中默认进行了 convolution 和 activation 。convolution 中,只对输入的最后一维求平均,且阶数不变。即 ‘weights:0’.shape=[inputs.shape[-1], num_outputs])。‘weights:0’.shape 永远是二维的。
num_outputs 是 ‘weights:0’ 第二维(即第-1维)的参数值;经过fn计算后,也变成了 结果输出的tensor 的最后一维(即第-1维)的参数值。如果设置 activation_fn=None,则输出结果 不经过激活,依然可能包含负数值。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
features = tf.constant([
[
[[1.2], [3.4]]
]
])
fc = tf.contrib.layers.fully_connected(features, num_outputs=2)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(fc))
这个例子创建了一个全连接层,并将输入张量与输出层中的每个神经元建立了连接。对于不同的全连接层,还有大量其他参数需要调整,执行结果如下:
3. 输入层
在CNN架构中,每一层都有特定的意图。(至少)从高层来理解它们是非常重要的,但如果不具体实践,是很容易遗忘的。在任何神经网络中,输入层都至关重要。无论是训练还是测试,原始输入都需要传递给输入层。对于目标识别与分类,输入层为tf.nn.conv2d,它负责接收图像。接下来的步骤是在训练中使用真实图像,而非tf.constant或tf.range变量形式的样例输入。
4 图像与TensorFlow
TensorFlow在设计时,就考虑了给将图像作为神经网络的输入提供支持。TensorFlow支持加载常见的图像格式(JPG,PNG)可在不同的颜色空间(RGB、RGBA)中工作,并能够完成常见的图像操作任务。虽然TensorFlow使得图像操作变得容易,但仍然面临一些挑战。使用图像时,所面临的最大挑战便是最终需要加载的张量的尺寸。每幅图像都需要用一个与图像尺寸(height*width*channel)相同的张量表示。再次提醒,通道是用一个包含每个通道中颜色数量的标量的秩1张量表示。
在TensorFlow中,一个红色的RGB像素可用如下张量表示:
每个标量都可修改,以使像素值为另一个颜色值或一些颜色值的混合。对于RGB颜色空间,像素对应的秩1张量的格式为[red,green,blue]。一幅图像中的所有像素都存储在磁盘文件中,它们都需要被加载到内存中,以便TensorFlow对其进行操作
4.1 加载图像
TensorFlow在设计时便以能够从磁盘快速加载文件为目标。图像的加载与其他大型二进制文件的加载是相同的,只是图像的内容需要解码。加载下列3×3的JPG格式的示例图像的过程与加载任何其他类型的文件完全一致
在上篇文章https://mp.csdn.net/postedit/86318162中讲述过最新的tf.data API读取数据的知识,该文章中仅详细介绍了csv文件读取,现在重温一下
一个基于 tf.data API的读取数据的步骤包含下列几步
1、根据被读入数据的类型选择相应的读入函数来建立数据库
如果您的所有输入数据都适合存储在内存中【NumPy 数组】,则根据输入数据创建 Dataset
的最简单方法是将它们转换为 tf.Tensor
对象,并使用 Dataset.from_tensor_slices();
通过 tf.data.TFRecordDataset
类,您可以将一个或多个 TFRecord 文件的内容作为输入管道的一部分进行流式传输
tf.data.FixedLengthRecordDataset
提供了一种从一个或多个二进制文件中读取数据的简单方法
tf.data.TextLineDataset
提供了一种从一个或多个文本文件中(csv文件)提取行的简单方法。给定一个或多个文件名,TextLineDataset
会为这些文件的每行生成一个字符串值元素
2、根据需要对读入的数据进行一系列预处理(map,batch,shuffle,repeat,skip)
map接收一个函数,Dataset中的每个元素都会被当作这个函数的输入,并将函数返回值作为新的Dataset
batch就是将多个元素组合成batch,其实就是一种组合,tf.data.TextLineDataset
函数会为文件的每行生成一个字符串值元素,当使用迭代器取出时是一个元素一个元素的取出,即一行一行的取出,而batch就是将多个元素合为一个元素,本人理解:一般csv文件一行是一个样本,而batch组合后调用迭代器可以一次取出多个样本。
shuffle的功能为打乱dataset中的元素,它有一个参数buffersize,表示打乱时使用的buffer的大小,该值应该取一个较大的数值,如10000
repeat的功能就是将整个序列重复多次,主要用来处理机器学习中的epoch,假设原先的数据是一个epoch,使用repeat(5)就可以将之变成5个epoch:如果直接调用repeat()的话,生成的序列就会无限重复下去,没有结束,因此也不会抛出tf.errors.OutOfRangeError异常
skip的功能是读取文件时跳过前面几行
注意:上述5个预处理操作和读入函数tf.data.TextLineDataset
写在一行,直接获得需要的数据集
针对我们本章的数据——图片,采用 Dataset.from_tensor_slices()建立数据库
首先需要明白Dataset.from_tensor_slices(),tf.data.TFRecordDataset,tf.data.FixedLengthRecordDataset,tf.data.TextLineDataset四个建立数据库的区别,除了上面说的它们四个针对四种不同的数据外,Dataset.from_tensor_slices()和它们最终要的区别就是它只是对输入的tensor对象进行切片处理【比如输入[filename1, filename2,filename3]则该函数会依次返回如下的值【filename1】【filename2】【filename3】,即返回文件名而已】,而
其他三个建立数据库的方法则会直接返回文件名所对应的文件的具体内容。
其次,上述四个建立数据库的方法还有一个区别就是采用的映射函数的内容不同,Dataset.from_tensor_slices()若传入的tensor为图片文件名,则采用的映射函数一般会先读取该文件名对应的文件内容,然后调用不同图片类型所对应的解码函数。
读取文件的函数有两个tf.gfile.GFile和tf.read_file,该函数具体知识见下面的拓展知识。
tensorflow里面提供解码的函数有两个,tf.image.decode_jepg和tf.image.decode_png分别用于解码jpg格式和png格式的图像进行解码,得到图像的像素值,这个像素值可以用于显示图像。如果没有解码,读取的图像是一个字符串,没法显示。
注意:图像预处理操作时最好将map放在最前面
使用tf.read_file一次只能读取一个文件,若想批量读取图片文件,可结合Dataset.from_tensor_slices()建立数据库,然后使用该数据库的预处理方法map将文件批量读入,使用如下
import tensorflow as tf
import os
import matplotlib.pyplot as plt
import numpy as np
np.set_printoptions(threshold=np.nan)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
def pic_map(image_name): # 此函数是数据集的映射函数
# 当前目录为工程目录untitled,脚本文件中文件查找的范围也是该文件夹下的直接文件,而我们的图片是放在工程目录下的pic文件夹内
# 传入的参数image_name是pic目录下面的图片名字,如‘1.jpg’
# 函数tf.read_file需要传入图片的绝对或者相对(此处的相对就是相对与工程目录untitled目录)路
径,本人使用相对路径,即‘./pic/1.jpg’
# 传入图片名字为‘1.jpg’,而我们需要的是‘./pic/1.jpg’,故对于传入的图片名字的字符串需要添加‘./pic/’
image_name = './pic/'+image_name
image = tf.read_file(image_name)
image1 = tf.image.decode_jpeg(image)
return image1
with tf.Session() as sess:
# 列出该目录下所有图片文件的名字,即['1.jpg','2.jpg','3.jpg']
image_quene = tf.gfile.ListDirectory('./pic')
# 首先创建包含['1.jpg','2.jpg','3.jpg']的数据集
# 然后将包含字符串的数据集经过映射获得解码后的图片tensor的数据集
# 最后将上述数据集无限重复后批处理为2
image_dateset = tf.data.Dataset.from_tensor_slices(image_quene).map(pic_map).repeat().batch(2)
# 创建关联上述数据集的迭代器die
die = image_dateset.make_one_shot_iterator()
# 创建获取迭代器指向值的节点
next_picture = die.get_next()
plt.figure(1)
plt.imshow(sess.run(next_picture)[0]) # 将第一次运行后的结果的第一张图画在画布上
plt.figure(2)
plt.imshow(sess.run(next_picture)[1]) # 将第二次运行后的结果的第二张图画在画布上
plt.figure(3)
plt.imshow(sess.run(next_picture)[1]) # 将第三次运行后的结果的第二张图画在画布上
plt.show()
writer = tf.summary.FileWriter('./h', sess.graph)
writer.close()
执行结果如下:
注意:文件夹中图片的样式如下
那么执行结果和图片在文件夹中图片顺序为什么不一样呢?
输出image_quene = tf.gfile.ListDirectory('./pic')后获得的image_quene ,可得到如下结果
那么如何将读入图片的顺序和我们图片的名字一致的?做法如下:只需将上述代码获取的图片名列表进行就地排序。 注意:不能将 image_quene.sort() 的值再次赋值给 image_quene,因为sort函数是就地修改,返回值为none
经过排序后我们之前显示图片的程序改为如下
拓展知识1 Python的可视化包 – Matplotlib
http://python.jobbole.com/85106/ http://python.jobbole.com/87471/ https://blog.csdn.net/qq_27825451/article/details/81630839
Matplotlib是Python中最常用的可视化工具之一,可以非常方便地创建海量类型地2D图表和一些基本的3D图表。Matplotlib最早是为了可视化癫痫病人的脑皮层电图相关的信号而研发,因为在函数的设计上参考了MATLAB,所以叫做Matplotlib。Matplotlib首次发表于2007年,在开源和社区的推动下,现在在基于Python的各个科学计算领域都得到了广泛应用。Matplotlib的原作者John D. Hunter博士是一名神经生物学家,2012年不幸因癌症去世,感谢他创建了这样一个伟大的库。
1.1 什么是rcParams
pylot有默认的配置信息存在配置文件中,既然是配置文件,它也是一个文件,这个文件存在于matplotlib的安装文件夹之下,比如我的在以下文件夹:/homeng/anaconda3/envs/tensorflowb/python3.6/site-packages/matplotlib/mpl-data
该配置文件打开如下:
从上图发现,里面的内容都是“ 键-值 ”的形式,这也就是为什么我们可以通过mpl.rcParams['font.sans-serif'] = ['SimHei'] 这种形式加以配置了。那么如何查看修改默认配置信息呢?
1.2 如何查看默认配置信息——可以通过matplotlib的相关属性以及方法加以操作
1.2.1 查看默认配置的方法
方法一:直接打开matplotlibrc文件,在1.1节已经作出示例
方式二:下述这三者是等价的,示例如下
print(matplotlib.rc_params()) print(matplotlib.rcParamsDefault) print(matplotlib.rcParams)
1.2.2 设置相关的配置
修改方式一 mpl.rcParams['lines.linewidth'] = 2 mpl.rcParams['lines.color'] = 'r'
修改方式二 mpl.rc('lines', linewidth=4, color='g') # 线条颜色最好不要使用这个,貌似不能修改
恢复默认参数 mpl.rcdefaults()
从已有的文件更新 mpl.rc_file()
示例如下
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
matplotlib.rcParams['lines.linewidth'] = 20 # 更改划线默认宽度
plt.figure('1')
plt.plot(x, y, label='sin')
plt.figure('2')
matplotlib.rcdefaults() # 恢复划线默认宽度
plt.plot(x, y, label='sin')
plt.show()
1.3 2D图表
Matplotlib中最基础的模块是pyplot。先从最简单的点图和线图开始,比如我们有一组数据,还有一个拟合模型,通过下面的代码图来可视化:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 通过rcParams设置全局横纵轴字体大小
print(mpl.rc_params())
mpl.rcParams['xtick.labelsize'] = 10
mpl.rcParams['ytick.labelsize'] = 24
np.random.seed(42)
# x轴的采样点
x = np.linspace(0, 5, 100)
# 通过下面曲线加上噪声生成数据,所以拟合模型就用y了……
y = 2*np.sin(x) + 0.3*x**2
y_data = y + np.random.normal(scale=0.3, size=100)
# figure()指定图表名称
plt.figure('data')
# '.'标明画散点图,每个散点的形状是个圆
plt.plot(x, y_data, '.')
# 画模型的图,plot函数默认画连线图
plt.figure('model')
plt.plot(x, y)
plt.savefig('result1.png')
# 两个图画一起
plt.figure('data & model')
# 通过'k'指定线的颜色,lw指定线的宽度
# 第三个参数除了颜色也可以指定线形,比如'r--'表示红色虚线
plt.plot(x, y, 'k', lw=3)
plt.plot(x, y_data, '.')
# 将当前figure的图保存到文件result.png
plt.savefig('result.png')
# 一定要加上这句才能让画好的图显示在屏幕上
plt.show()
执行结果如下:
点和线图表只是最基本的用法,有的时候我们获取了分组数据要做对比,柱状或饼状类型的图或许更合适:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams['axes.titlesize'] = 20
mpl.rcParams['xtick.labelsize'] = 16
mpl.rcParams['ytick.labelsize'] = 16
mpl.rcParams['axes.labelsize'] = 16
mpl.rcParams['xtick.major.size'] = 0
mpl.rcParams['ytick.major.size'] = 0
# 包含了狗,猫和猎豹的最高奔跑速度,还有对应的可视化颜色
speed_map = {
'dog': (48, '#7199cf'),
'cat': (45, '#4fc4aa'),
'cheetah': (120, '#e1a7a2')
}
# 整体图的标题
fig = plt.figure('Bar chart & Pie chart')
# 在整张图上加入一个子图,121的意思是在一个1行2列的子图中的第一张
ax = fig.add_subplot(121)
ax.set_title('Running speed - bar chart')
# 生成x轴每个元素的位置
xticks = np.arange(3)
# 定义柱状图每个柱的宽度
bar_width = 0.5
# 动物名称
animals = speed_map.keys()
# 奔跑速度
speeds = [x[0] for x in speed_map.values()]
# 对应颜色
colors = [x[1] for x in speed_map.values()]
# 画柱状图,横轴是动物标签的位置,纵轴是速度,定义柱的宽度,同时设置柱的边缘为透明
bars = ax.bar(xticks, speeds, width=bar_width, edgecolor='none')
# 设置y轴的标题
ax.set_ylabel('Speed(km/h)')
# x轴每个标签的具体位置,设置为每个柱的中央
ax.set_xticks(xticks+bar_width/2)
# 设置每个标签的名字
ax.set_xticklabels(animals)
# 设置x轴的范围
ax.set_xlim([bar_width/2-0.5, 3-bar_width/2])
# 设置y轴的范围
ax.set_ylim([0, 125])
# 给每个bar分配指定的颜色
for bar, color in zip(bars, colors):
bar.set_color(color)
# 在122位置加入新的图
ax = fig.add_subplot(122)
ax.set_title('Running speed - pie chart')
# 生成同时包含名称和速度的标签
labels = ['{}\n{} km/h'.format(animal, speed) for animal, speed in zip(animals, speeds)]
# 画饼状图,并指定标签和对应颜色
ax.pie(speeds, labels=labels, colors=colors)
plt.show()
在这段代码中又出现了一个新的东西叫做,一个用ax命名的对象。在Matplotlib中,画图时有两个常用概念,一个是平时画图蹦出的一个窗口,这叫一个figure。Figure相当于一个大的画布,在每个figure中,又可以存在多个子图,这种子图叫做axes。顾名思义,有了横纵轴就是一幅简单的图表。在上面代码中,先把figure定义成了一个一行两列的大画布,然后通过fig.add_subplot()加入两个新的子图。subplot的定义格式很有趣,数字的前两位分别定义行数和列数,最后一位定义新加入子图的所处顺序,当然想写明确些也没问题,用逗号分开即可。上面这段代码产生的图像如下:
1.4 图像显示
Matplotlib也支持图像的存取和显示,并且和OpenCV一类的接口比起来,对于一般的二维矩阵的可视化要方便很多,来看例子:
确定坐标范围
plt.axis([xmin, xmax, ymin, ymax]) 上面例子里的axis()命令给定了坐标范围。xlim(xmin, xmax)和ylim(ymin, ymax)来调整x,y坐标范围,使用示例如下:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-5.0, 5.0, 0.02)
y1 = np.sin(x)
plt.figure(1)
plt.subplot(211)
plt.plot(x, y1)
plt.subplot(212)
plt.axis([-2.5, 2.5, -1, 1])
plt.plot(x, y1)
plt.show()
使用xlim(xmin, xmax)和ylim(ymin, ymax)示例如下
from pylab import *
x = np.arange(-5.0, 5.0, 0.02)
y1 = np.sin(x)
plt.figure(1)
plt.subplot(211)
plt.plot(x, y1)
plt.subplot(212)
# 设置x轴范围
xlim(-2.5, 2.5)
# 设置y轴范围
ylim(-1, 1)
plt.plot(x, y1)
plt.show()
上述两种方法的执行结果一致,如下
叠加图
用一条指令画多条不同格式的线。
import numpy as np
import matplotlib.pyplot as plt
# evenly sampled time at 200ms intervals
t = np.arange(0., 5., 0.2)
# red dashes, blue squares and green triangles
plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
plt.show()
运行结果如下:
补充线条属性标记颜色
plt.figure()
你可以多次使用figure命令来产生多个图,其中,图片号按顺序增加。这里,要注意一个概念当前图和当前坐标。所有绘图操作仅对当前图和当前坐标有效。通常,你并不需要考虑这些事,下面的这个例子为大家演示这一细节。figure感觉就是给图像ID,之后可以索引定位到它。
import matplotlib.pyplot as plt
plt.figure(1) # 第一张图
plt.subplot(211) # 第一张图中的第一张子图
import matplotlib.pyplot as plt
plt.figure(1) # 第一张图
plt.subplot(211) # 第一张图中的第一张子图
plt.plot([1, 2, 3])
plt.subplot(212) # 第一张图中的第二张子图
plt.plot([4, 5, 6])
plt.figure(2) # 第二张图
plt.plot([4, 5, 6]) # 默认创建子图subplot(111)
plt.figure(1) # 切换到figure 1 ; 子图subplot(212)仍旧是当前图
plt.title('ling') # 添加subplot 211 的标题
plt.subplot(211) # 令子图subplot(211)成为figure1的当前图
plt.title('Easy as 1,2,3') # 添加subplot 211 的标题
plt.show()
执行结果如下:
plt.text()添加文字说明
text()可以在图中的任意位置添加文字,并支持LaTex语法【注意:该函数只能在预先固定的位置显示文字】;xlable(), ylable()用于添加x轴和y轴标签;title()用于添加图的题目;grid(true)用于显示网格【grid(false)就是不显示网格】
mport matplotlib.pyplot as plt
plt.figure(1)
plt.plot([1, 2, 3])
plt.xlabel('Smarts')
plt.ylabel('Probability')
# 添加标题
plt.title('ling')
# 添加文字
plt.text(1.0, 2.0, 'hello') # 注意此处,添加的文字的坐标为(1.0,2.0),此处如果写(1,2)也可以
plt.grid(True)
plt.show()
执行结果如下:
plt.annotate()文本注释【annotate中文翻译为注释】
在数据可视化的过程中,图片中的文字经常被用来注释图中的一些特征。使用annotate()方法可以很方便地添加此类注释。在使用annotate时,要考虑两个点的坐标:被注释的地方xy(x, y)和插入文本的地方xytext(x, y)。annotate(s='str' ,xy=(x,y) ,xytext=(l1,l2) ,..)
annotate函数必填参数说明:s 为注释文本内容 ,xy 为被注释的坐标点,xytext 为注释文字的坐标位置
annotate函数可选参数说明如下:
在该示例中,xy
(箭头尖端)和xytext
位置(文本位置)都以数据坐标为单位。 有多种可以选择的其他坐标系 - 你可以使用xycoords
和textcoords
以及下列字符串之一(默认为data
)指定xy
和xytext
的坐标系。
| 参数 | 坐标系 |
| 'figure points'
| 距离图形左下角的点数量 |
| 'figure pixels'
| 距离图形左下角的像素数量 |
| 'figure fraction'
| 0,0 是图形左下角,1,1 是右上角 |
| 'axes points'
| 距离轴域左下角的点数量 |
| 'axes pixels'
| 距离轴域左下角的像素数量 |
| 'axes fraction'
| 0,0 是轴域左下角,1,1 是右上角 |
| 'data'
| 使用轴域数据坐标系 |
你可以通过在可选关键字参数arrowprops
中提供箭头属性字典来绘制从文本到注释点的箭头。箭头参数,参数类型为字典dict
此函数使用示例如下
import numpy as np
import matplotlib.pyplot as plt
ax = plt.subplot(111)
t = np.arange(0.0, 5.0, 0.01)
s = np.cos(2 * np.pi * t)
line, = plt.plot(t, s, lw=2)
plt.annotate('local max', xy=(2, 1), xytext=(3, 1.5),
arrowprops=dict(facecolor='black', shrink=0.001),
)
plt.ylim(-2, 2)
plt.show()
执行结果如下
plt.xticks()/plt.yticks()设置轴记号
人为设置坐标轴的刻度显示的值。
import numpy as np
import matplotlib.pyplot as plt
# 创建一个 8 * 6 点(point)的图,并设置分辨率为 80
plt.figure(figsize=(8, 6), dpi=80)
# 创建一个新的 1 * 1 的子图,接下来的图样绘制在其中的第 1 块(也是唯一的一块)
plt.subplot(1, 1, 1)
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C, S = np.cos(X), np.sin(X)
# 绘制余弦曲线,使用蓝色的、连续的、宽度为 1 (像素)的线条
plt.plot(X, C, color="blue", linewidth=1.0, linestyle="-")
# 绘制正弦曲线,使用绿色的、连续的、宽度为 1 (像素)的线条
plt.plot(X, S, color="r", lw=4.0, linestyle="-")
plt.axis([-4, 4, -1.2, 1.2])
# 设置轴记号
plt.xticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi],
[r'$-pi$', r'$-pi/2$', r'$0$', r'$+pi/2$', r'$+pi$'])
plt.yticks([-1, 0, +1],
[r'$-1$', r'$0$', r'$+1$'])
# 在屏幕上显示
plt.show()
上述代码执行结果如下
plt.legend()添加图例
import numpy as np
import matplotlib.pyplot as plt
# 创建一个 8 * 6 点(point)的图,并设置分辨率为 80
plt.figure(figsize=(8, 6), dpi=80)
# 创建一个新的 1 * 1 的子图,接下来的图样绘制在其中的第 1 块(也是唯一的一块)
plt.subplot(1, 1, 1)
X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
C, S = np.cos(X), np.sin(X)
# 绘制余弦曲线,使用蓝色的、连续的、宽度为2.5(像素)的线条
plt.plot(X, C, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.legend(loc='upper left')
# 绘制正弦曲线,使用绿色的、连续的、宽度为 2.5 (像素)的线条
plt.plot(X, S, color="red", linewidth=2.5, linestyle="-", label="sine")
plt.legend(loc='upper left')
plt.axis([-4, 4, -1.2, 1.2])
# 设置轴记号
plt.xticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi],
[r'$-pi$', r'$-pi/2$', r'$0$', r'$+pi/2$', r'$+pi$'])
plt.yticks([-1, 0, +1],
[r'$-1$', r'$0$', r'$+1$'])
# 在屏幕上显示
plt.show()
执行结果如下:
plt.subplot()
plt.subplot(2,3,1)
表示把图标分割成2*3的网格。也可以简写plt.subplot(231)
。其中,第一个参数是行数,第二个参数是列数,第三个参数表示图形的标号。
plt.axes()
我们先来看什么是Figure和Axes对象。在matplotlib中,整个图像为一个Figure对象。在Figure对象中可以包含一个,或者多个Axes对象。每个Axes对象都是一个拥有自己坐标系统的绘图区域。其逻辑关系如下34:
拓展知识 2 安装的matplotlib 只能解析PNG格式图片
https://blog.csdn.net/kaizao/article/details/78625780
ValueError: Only know how to handle extensions: ['png']; with Pillow installed matplotlib can handle more images
上述错误原因:安装的matplotlib 只能解析PNG格式图片【未安装Pillow库导致不能加载更多格式的图片】
解决方法:https://blog.csdn.net/lzp1510681927/article/details/78254675 sudo pacman -S python-pillow
拓展知识3 matplotlib的常用的两种方式以及pylab
https://blog.csdn.net/you_are_my_dream/article/details/53438983
pyplot:matplotlib模块中包含的经典高层封装,最常用;简单易用,交互时使用方便,可以根据命令实时作图,但底层定制能力不足
pylab:将matplotlib和numpy合并的模块,模拟matlab的编程环境。完全封装,环境最接近matlab,
推荐使用pyplot和numpy显式导入的方式
拓展知识4 像素和分辨率的关系
图像分辨率指的是图像中存储的信息量,是每英寸图像内有多少个像素点,分辨率的单位为PPI(Pixels Per Inch),通常叫做像素每英寸。像素是指基本原色素及其灰度的基本编码。 像素是构成数码影像的基本单元。【一英尺=30.48厘米】【1英寸=2.5399999961392厘米】
Pixels Per Inch也叫像素密度,所表示的是每英寸所拥有的像素数量。因此PPI数值越高,即代表显示屏能够以越高的密度显示图像。当然,显示的密度越高,拟真度就越高
Pixels Per Inch是像素的密度单位,就像PPI值越高,画面的细节就会越丰富,所以数码相机拍出来的图片因品牌或生产时间不同可能有所不同,常见的有72PPI,180PPI和300PPI,默认出来就是这么多(A710拍出的是180PPI)。 DPI(Dots Per Inch)是指输出分辨,针对于输出设备而言的,一般的激光打印机的输出分辨率是300DPI-600DPI,印刷的照排机达到1200DPI-2400DPI,常见的冲印一般在150DPI到300DPI之间。
这里首先要提到另外一个概念,尺寸。一般照片的长度大小是以寸来表示。而尺寸和像素和分辨率有直接的关系。我们要搞清楚一件事情,一张照片是否清晰不是由照片的像素决定的,而是取决于相片像素的点密度,也就是说分辨率决定照片的清晰程度,很自然的我们可以知道一张照片的分辨率大小是由照片的尺寸和像素多少决定的。
举个例子来说。为什么相同的一张照片放在手机里面看会比在电脑里面看更加清晰?原因就在于电脑呈现出来的照片尺寸更大,而像素点的数量却没有变化,也就是说像素在电脑画面中的密度更低这个“密度”实际上就是分辨率。反过来说,像素个数的多少决定你照片尺寸的大小。
我们在查看照片的时候经常见到的“640x480”这样格式的数字,它表示横向640像素和纵向480像素,所以这张照片的像素总量为640 × 480 = 307,200像素。若果我们知道它的尺寸大小,也可以推算出这张照片的分辨率,假如照片的长度为3.556英寸,宽为2.667英寸,那么它的长分辨率为640/3.556=180(像素/英寸),同理其宽的分辨也是也宽度的像素点除以宽度尺寸,得到宽的分辨率。
拓展知识5 python np.linspace
https://www.cnblogs.com/xiaohua92/p/5525871.html
linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
该函数的作用为:在规定的时间内,返回固定间隔的数据。他将返回“num”个等间距的样本,在区间[`start`, `stop`]中。其中,区间的结束端点可以被排除在外。
参数:
start : scalar, 队列的开始值
stop : scalar, 队列的结束值。当‘endpoint=False’时,不包含该点。在这种情况下,队列包含除了“num+1"以外的所有等间距的样本。要注意的是,当‘endpoint=False’时,步长会发生改变。
num : int, optional 要生成的样本数。默认是50,必须要非负。
endpoint : bool, optional 如果是True,’stop'是最后的样本。否则,'stop'将不会被包含。默认为true
retstep : bool, optional If True, return (`samples`, `step`), where `step` is the spacing between samples.
返回值:
samples : ndarray ,在闭区间[start, stop]或者是半开区间[start, stop)中有num个等间距的样本
step : float ,仅仅当”retstep=True“时候会返回。样本之间间距的大小
拓展知识6 matplotlib.pyplot.figure函数
https://blog.csdn.net/henni_719/article/details/77479912
matplotlib.pyplot.figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True, FigureClass=<class 'matplotlib.figure.Figure'>, clear=False, **kwargs)
该函数作用:创建一个新的画布(figure)。
输入参数:
num:整型或者字符串,可选参数,默认:None。如果不提供该参数,一个新的画布(figure)将被创建而且画布数量将会增加。如果提供该参数,带有id的画布是已经存在的,激活该画布并返回该画布的引用。如果这个画布不存在,创建并返回画布实例。如果num是字符串,窗口标题就是该字符串,否则窗口标题将被设置为该图的数字。
figsize:整型元组,可选参数 ,默认:None。每英寸的宽度和高度。如果不提供,默认值是figure.figsize。
dpi:整型,可选参数,默认:None。每英寸像素点。如果不提供,默认是figure.dpi。
facecolor:背景色。如果不提供,默认值:figure.facecolor。
edgecolor:边界颜色。如果不提供,默认值:figure.edgecolor。
framemon:布尔类型,可选参数,默认值:True。如果是False,禁止绘制画图框。
FigureClass:源于matplotlib.figure.Figure的类。(可选)使用自定义图实例。
clear:布尔类型,可选参数,默认值:False。如果为True和figure已经存在时,这是清理掉改图。
拓展知识7 tf.gfile
1、gfile模块是什么
tf.gfile模块的主要角色是:提供一个接近Python文件对象的API,以及提供基于TensorFlow C ++ FileSystem API的实现。
C ++ FileSystem API支持多种文件系统实现,包括本地文件,谷歌云存储(以gs://开头)和HDFS(以hdfs:/开头)。 TensorFlow将它们导出为tf.gfile,以便我们可以使用这些实现来保存和加载检查点,编写TensorBoard log以及访问训练数据(以及其他用途)。但是,如果所有文件都是本地文件,则可以使用常规的Python文件API而不会造成任何问题。
tf.gfile主要包括2个类和12个函数。两个类为:
class GFile :tf.gfile.GFile(filename, mode)获取文本操作句柄,类似于python提供的文本操作open()函数,filename是要打开的文件名,mode是以何种方式去读写,将会返回一个文本操作句柄。tf.gfile.Open()是该接口的同名,可任意使用其中一个!
class FastGFile: tf.gfile.FastGFile(filename, mode)该函数与tf.gfile.GFile的差别仅仅在于“无阻塞”,即该函数会无阻赛以较快的方式获取文本操作句柄。
文件打开模式mode的可取值如下:https://jingyan.baidu.com/article/b7001fe1b10cd80e7282dd26.html
# r 只读文件。
# rb 二进制读文件。
# r+ 可读可写,不会创建不存在的文件 从头部开始写,会覆盖之前此位置的内容 。
# rb+ 二进制格式读写文件。文件指针将会放在文件的开头。
# w 只写文件,如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
# wb 二进制写文件。
# w+ 读写文件。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
# wb+ 二进制读写文件。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
# a 只追写文件。从文件底部添加内容 不存在则创建 。
# ab 二进制追写文件。 从文件顶部读取内容 从文件底部添加内容 不存在则创建。
# a+ 追读写文件。从文件顶部读取内容 从文件底部添加内容 不存在则创建。
# ab+ 追读写二进制。从文件顶部读取内容 从文件底部添加内容 不存在则创建。
下面将分别介绍12个gfile 函数。
1、tf.gfile.Copy(oldpath, newpath, overwrite=False)
拷贝源文件并创建目标文件,无返回,
其形参说明如下: oldpath:带路径名字的拷贝源文件;newpath:带路径名字的拷贝目标文件;
overwrite:目标文件已经存在时是否要覆盖,默认为false,如果目标文件已经存在则会报错
2、tf.gfile.MkDir(dirname)
创建一个目录,dirname为目录名字,无返回
3、tf.gfile.Remove(filename)
删除文件,filename即文件名,无返回。
4、tf.gfile.DeleteRecursively(dirname)
递归删除所有目录及其文件,dirname即目录名,无返回。
判断目录或文件是否存在,filename可为目录路径或带文件名的路径,有该目录则返回True,否则False。
查找匹配pattern的文件并以列表的形式返回,filename可以是一个具体的文件名,也可以是包含通配符的正则表达式。
7、tf.gfile.IsDirectory(dirname)
判断所给目录是否存在,如果存在则返回True,否则返回False,dirname是目录名。
8、tf.gfile.ListDirectory(dirname)
罗列dirname目录下的所有文件并以列表形式返回,dirname必须是目录名。
以递归方式建立父目录及其子目录,如果目录已存在且是可覆盖则会创建成功,否则报错,无返回。
10、tf.gfile.Rename(oldname, newname, overwrite=False)
重命名或移动一个文件或目录,无返回,
其形参说明如下:oldname:旧目录或旧文件;newname:新目录或新文件;overwrite:默认为false,如果新目录或新文件已经存在则会报错,否则重命名或移动成功。
返回目录的统计数据,该函数会返回FileStatistics数据结构,可以用dir(tf.gfile.Stat(filename))获取返回数据的属性。
12、tf.gfile.Walk(top, in_order=True)
递归获取目录信息生成器,top是目录名,in_order默认为True指示顺序遍历目录,否则将无序遍历,每次生成返回如下格式信息(dirname, [subdirname, subdirname, ...], [filename, filename, ...])。
拓展知识8 matplotlib.pyplot.imshow()官方使用说明
matplotlib.pyplot.imshow(作用:将一个image显示在二维坐标轴上。
X,
cmap=None,
norm=None,
aspect=None,
interplotation=None,
alpha=None,
Vmin=None,
vmax= None,
origin=None,
extent=None,
shape=None,
filternorm=1,
filterrad=4.0,
imlim=None,
resample=None,
url=None,
hold=None,
data=None, **kwargs)
拓展知识9 tensorflow读取图片的两种方式
方式1.使用gfile读图片,该函数输出常量,decode输出是Tensor,eval(或sess.run)后是ndarray,示例如下
import tensorflow as tf
import os
import numpy as np
np.set_printoptions(threshold=np.nan) # 此语句设置输出的数字不省略
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
image_name = '1.jpg'
image = tf.gfile.GFile(image_name , 'rb').read() # 注意:文件打开mode为rb,二进制方式读入
image1 = tf.image.decode_jpeg(image)
with tf.Session() as sess:
print(image)
print(image1)
print(sess.run(image1))
writer = tf.summary.FileWriter('./h', sess.graph)
writer.close()
上述代码执行结果和流程图如下
从上述模型图中分析可知:读入图片的函数 tf.gfile.GFile(image_name, 'rb').read()在该流程图中没有显式作为节点,该函数返回值赋给image,此时image是静态常量const,而不属于tensor变量,故而代码中无需将其加入sess.run就可以直接通过print(image)输出image的具体内容。而解码函数image1 = tf.image.decode_jpeg(image)则是模型图中的一个节点,故而直接print(image1)只能输出tensor变量的形状等信息,只有将其作为sess.run里面才能将该tensor的内容输出
注意:上述代码中读入图片的mode为‘rb’,即以二进制方式读入图片文件,当mode为‘r'时,报错如下:
https://blog.csdn.net/u013555719/article/details/77991010
出现这种问题绝大部分情况是因为文件不是 UTF8 编码的(例如,可能是 GBK 编码的),而系统默认采用 UTF8 解码。解决方法是改为对应的解码方式。解决方案就是将’r’改为’rb’的形式
方式2.使用WholeFileReader输入queue,decode输出是Tensor,eval后是ndarray
此方法即将被tensorflow移除,故不再探索,tensorflow官方对于此方法的替代方法是方式3
3.使用read_file,该函数输出是Tensor,decode输出是Tensor,eval后是ndarray
import tensorflow as tf
import os
import numpy as np
np.set_printoptions(threshold=np.nan)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 设置输出等级,此时不输出警告
image_name = './pic/1.jpg'
image = tf.read_file(image_name)
image1 = tf.image.decode_jpeg(image)
with tf.Session() as sess:
print(image)
print(image1)
print(sess.run(image1))
writer = tf.summary.FileWriter('./h', sess.graph)
writer.close()
执行结果和模型图如下
从上述模型图中分析得知:读入图片的函数image = tf.read_file(image_name)在流程图中被作为节点,该函数返回值赋给image,此时image是tensor变量,故通过print(image)时只能输出image的属性,只有通过sess.run才可以输出该tensor变量的具体值
方法1和方法3结果差不多,为了统一性,最好使用方法3,这样会显式的将输如作为节点,从而可以通过sess对其进行管理
4.2 图像格式
考虑图像的各个方面以及它们如何对模型造成影响非常重要。当使用来自一台RED Weapon摄像机的单帧图像训练一个网络时,考虑会发生什么。在笔者撰写本书时,这种摄像机的分辨率为6144×3160像素。这样的一帧图像需要用包含19415040个带有3个维度的颜色信息的一阶张量表示。
实际上,这种尺寸的输入将占用个大量系统内存。训练一个CNN需要大量时间,加载非常大的文件会进一步增加训练所需的时间。即便增加的时间在可接受的范围内,单幅图像的尺寸也很难存放在大多数系统的GPU显存中。
输入图像尺寸过大也会为大多数CNN模型的训练产生不利影响。CNN总是试图找到图像中的本征属性,虽然这些属性有一定的独特性,但也需要推广到其他具有类似结果的图像上。使用尺寸过大的输入会使网络中充斥大量无关信息,从而影响模型的泛化能力。
在Stanford Dogs数据集中,哈巴狗类别中存在两幅外观迥异的图像。虽然非常可爱,但这些图像中充斥了大量会在训练中对网络造成误导的无用信息。例如,文件n02110958_4030.jpg中的哈巴狗所戴的帽子不是CNN为匹配哈巴狗所需要学习的特征。大多数哈巴狗喜欢海盗帽,因此图像中有小丑帽实际上是在训练网络去匹配一个大多数哈巴狗都没戴着的帽子
图像中的重要信息是通过按照某种恰当的文件格式存储并处理得以强调的。在使用图像时,不同的格式可用于解决不同的问题。
4.2.1 JPEG与PNG
TensorFlow拥有两种可对图像数据解码的格式,一种是tf.image.decode_jpeg,另一种是tf.image.decode_png。在计算机视觉应用中,这些都是常见的文件格式,因为将其他格式转换为这两种格式非常容易
值得注意的是,JPEG图像不会存储任何alpha通道的信息,但PNG图像会。如果在训练模型时需要利用alpha信息(透明度),则这一点非常重要。一种应用场景是当用户手工切除图像的一些区域,如狗所戴的不相关的小丑帽。将这些区域置为黑色会使它们与该图像中的其他黑色区域看起来有相似的重要性。若将所移除的帽子对应的区域的alpha值设为0,则有助于标识该区域是被移除的区域
使用JPEG图像时,不要进行过于频繁的操作,因为这样会留下一些伪影(artifact)。在进行任何必要的操作时,获取图像的原始数据,并将它们导出为JPEG文件。为了节省训练时间,请试着尽量在图像加载之前完成对它们的操作。
如果一些操作是必要的,PNG图像可以很好地工作。PNG格式采用的是无损压缩,因此它会保留原始文件(除非被缩放或降采样)中的全部信息。PNG格式的缺点在于文件体积相比JPEG要大一些
4.2 2.TFRecord
为将二进制数据和标签(训练的类别标签)数据存储在同一个文件中,TensorFlow设计了一种内置文件格式,该格式被称为TFRecord,它要求在模型训练之前通过一个预处理步骤将图像转换为TFRecord格式。该格式的最大优点是将每幅输入图像和与之关联的标签放在同一文件中。
从技术角度讲,TFRecord文件是protobuf格式的文件。作为一种经过预处理的格式,它们是非常有用的。由于它们不对数据进行压缩,所以可被快速加载到内存中。在下面这个例子中,我们将一幅图像及其标签写入一个新的TFRecord格式的文件中。
# step0:获取图片的标签和图像数据的二进制表达
image_label = b'\x01'
image_before = tf.read_file('./pic/1.jpg')
image = tf.image.decode_jpeg(image_before)
sess = tf.Session()
image_really = sess.run(image)
image_bytes = image_really.tobytes()
# step1:创建写TFRecord格式的对象
writer = tf.python_io.TFRecordWriter('./image.tfrecord')
# step2:生成tf.train.Int64List, tf.train.BytesList, tf.train.FloatList三种类型的数据
feature_bytes_list = tf.train.BytesList(value=[image_bytes])
label_bytes_list = tf.train.BytesList(value=[image_label]) # image_label是解码后的图像数据
# step3:生成feature类型的数据
image_feature = tf.train.Feature(bytes_list=feature_bytes_list)
image_label = tf.train.Feature(bytes_list=label_bytes_list)
# step4:生成features类型的数据
features = tf.train.Features(feature={'labels:': image_label, 'image:': image_feature})
# step5:生成例子,注意:样本文件中不保存图像的宽度、高度或 通道数,以节省空间,所以之后读取该文件时需要重塑图像的形状
emaple = tf.train.Example(features=features)
# step6:将emaple数据写入TFRecord文件
writer.write(emaple.SerializeToString())
# step7:关闭
writer.close()
标签的格式被称为独热编码(one-hotencoding),这是一种用于多类分类的有标签数据的常见表示方法。Stanford Dogs数据集之所以被视为多类分类数据,是因为狗会被分类为单一品种,而非多个品种的混合。在现实世界中,当预测狗的品种时,多标签解决方案通常较为有效,因为它们能够匹配同时属于多个品种的狗。
在这段示例代码中,图像被加载到内存中并被转换为字节数组。之后,这些字节被添加到tf.train.Example文件中,而后者在被保存到磁盘之前先通过SerializeToString序列化为二进制字符串。序列化是一种将内存对象转换为某种可安全传输到某个文件的格式。上面序列化的样本现在被保存为一种可被加载的格式,并可被反序列化为这里的样本格式。
由于图像被保存为TFRecord文件,所以可被再次加载(从TFRecord文件加载,而非从图像文件加载)。在训练阶段,加载图像及其标签是必需的。这样相比将图像及其标签分开加载会节省一些时间。
TFRecord文件的读和写的具体代码如下:
import tensorflow as tf
import matplotlib.pyplot as plt # 为直观显式图片而引入
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
# 将jpg图像转换为TFRecord格式存储下来
def write_tfrecord(sess):
# step0:获取图片的标签和图像数据的二进制表达
image_label = b'\x01'
image_before = tf.read_file('./pic/1.jpg')
image = tf.image.decode_jpeg(image_before)
image_really = sess.run(image)
imagesize = image_really.shape
image_bytes = image_really.tobytes()
# step1:创建写TFRecord格式的对象
filename_tfrecord = '.ng.tfrecord'
writer = tf.python_io.TFRecordWriter(filename_tfrecord)
# step2:生成tf.train.Int64List, tf.train.BytesList, tf.train.FloatList三种类型的数据
feature_bytes_list = tf.train.BytesList(value=[image_bytes])
label_bytes_list = tf.train.BytesList(value=[image_label]) # image_label是解码后的图像数据
# step3:生成feature类型的数据
image_feature = tf.train.Feature(bytes_list=feature_bytes_list)
image_label = tf.train.Feature(bytes_list=label_bytes_list)
# step4:生成features类型的数据
features = tf.train.Features(feature={'labels': image_label, 'image': image_feature})
# step5:生成例子,注意:样本文件中不保存图像的宽度、高度或 通道数,以节省空间,所以之后读取该文件时需要重塑图像的形状
example = tf.train.Example(features=features)
# step6:将emaple数据 ,注意:样本文件中不保存图像的宽度、高度或 通道数,以节省空间,所以之后读取该文件时需要重塑图像的形状写入TFRecord文件
writer.write(example.SerializeToString())
# step7:关闭
writer.close()
return imagesize, filename_tfrecord
# 读取TFRecord文件时的映射函数
def tfrecord_map(emaple):
# class tf.FixedLenFeature 解析定长的输入特征feature相关配置
features = {'image': tf.FixedLenFeature([], tf.string),
'labels': tf.FixedLenFeature([], tf.string)}
# parse_single_example解析一个单独的Example proto,将Example protos解析为tensor的字典形式
# 解析serialized所给予的序列化的一些Example protos
# 返回一个由特征keys映射Tensor和SparseTensor值的字典
# 此函数解析出来的是二进制数据的字符串形式
parsed_features = tf.parse_single_example(emaple, features)
# decode_raw将bytes转换为一个数字向量表示,bytes为一个字符串类型的tensor
tf_record_image = tf.decode_raw(parsed_features['image'], tf.uint8)
tf_record_label = tf.decode_raw(parsed_features['labels'], tf.uint8)
return tf_record_image, tf_record_label
# 读取TFRecord文件,即将TFRecord解析为一般图像数据
def read_tfrecord(image_size, filename):
# step1: 创建TFRecord文件数据库
dataset1 = tf.data.TFRecordDataset([filename]).map(tfrecord_map).repeat()
# step2:定义数据库的迭代器
iter1 = dataset1.make_one_shot_iterator()
next_value = iter1.get_next()
# step3:将迭代器获取的图像数据形状进行调整,decode_raw之后得到的是一维向量,故需要将其转换为对应的图片维度
tf_record_image, tf_record_label = next_value[0], next_value[1]
tf_record_image_size = tf.reshape(tf_record_image, [image_size[0], image_size[1], image_size[2]])
return tf_record_image_size, tf_record_label
sess = tf.Session()
image_size, filename = write_tfrecord(sess)
final_image, final_label = read_tfrecord(image_size, filename)
# 以下部分均为测试使用,无必要性
print(sess.run(final_image))
print(sess.run(final_label).dtype)
plt.figure(1)
plt.imshow(sess.run(final_image))
plt.show()
拓展知识1 Tensorflow的基本概念与常用函数
tf的各种函数详解 https://www.cnblogs.com/focusonepoint/p/7544369.html
该文件被加载后,为使其布局符合tf.nn.conv2d的要求,即[image_height,image_width,image_channels],需要对形状进行调整(tf.reshape)。为将batch_size维添加到input_batch中,需要对维数进行扩展(tf.expand)。
4.3 图像操作
当给定大量不同质量的训练数据时,CNN往往能够很好地工作。图像能够通过可视化的方式传达复杂场景所蕴涵的某种目标主题。在Stanford Dogs数据集中,很重要的一点是图像能够以可视化的方式突出图片中狗的重要性。一幅狗位于画面中心的图像会被认为比狗作为背景的图像更有价值
并非所有数据集都拥有最有价值的图像。下面所示的两幅图像都来自Stanford Dogs数据集,按照假设,该数据集本应突出不同的狗的品种。
左图n02113978_3480.jpg突出的是一条典型的墨西哥无毛犬的重要属性,而右图n02113978_1030.jpg强调的是两个参加聚会的人在逗一条墨西哥无毛犬。右图中充斥了大量的无关信息,这可能会导致所训练的CNN模型对参加聚会的人的面部信息更为关注,而非墨西哥无毛犬。类似这样的图像中可能会包含狗,我们可对其进行操作,使狗而非人成为真正被突出的对象
在大多数场景中,对图像的操作最好能在预处理阶段完成。预处理包括对图像裁剪、缩放以及灰度调整等。另一方面,在训练时对图像进行操作有一个重要的用例。当一幅图像被加载后,可对其做翻转或扭曲处理,以使输入给网络的训练信息多样化。虽然这个步骤会进一步增加处理时间,但却有助于缓解过拟合现象
TensorFlow并未设计成一个图像处理框架。与TensorFlow相比,有一些Python库(如PIL和OpenCV)支持更丰富的图像操作。对于TensorFlow,可将那些对训练CNN十分有用的图像处理方法总结如下
4.3.1 裁剪
裁剪会将图像中的某些区域移除,将其中的信息完全丢弃。裁剪与tf.slice类似,后者是将一个张量中的一部分从完整的张量中移除。当沿某个维度存在多余的输入时,为CNN对输入图像进行裁剪便是十分有用的。例如,为减少输入的尺寸,可对狗位于图像中心的图片进行裁剪
这段示例代码利用了tf.image.central_crop将图像中10%的区域抠出,并将其返回。该方法总是会基于所使用的图像的中心返回结果。裁剪通常在预处理阶段使用,但在训练阶段,若背景也有用时,它也可派上用场。当背景有用时,可随机化裁剪区域起始位置到图像中心的偏移量来实现裁剪
bounding_crop = tf.image.crop_to_bounding_box(final_image, offset_height=0, offset_width=0, target_height=2000, target_width=2000)
为从位于(0,0)的图像的左上角像素开始对图像裁剪,这段示例代码使用了tf.image.crop_to_bounding_box
注意:此函数必须保证原有图像的高度final_image.height>= offset_height+ target_height,且原有图像的宽度final_image.weight>=offset_width+target_width
4.3.2 边界填充
为使输入图像符合期望的尺寸,可用0进行边界填充。可利用tf.pad函数完成该操作,但对于尺寸过大或过小的图像,TensorFlow还提供了另外一个非常有用的尺寸调整方法。对于尺寸过小的图像,该方法会围绕该图像的边界填充一些灰度值为0的像素。通常,该方法用于调整小图像的尺寸,因为任何其他调整尺寸的方法都会使图像的内容产生扭曲。
pad = tf.image.pad_to_bounding_box(final_image, offset_height=500, offset_width=500, target_height=2000, target_width=2000)
注意:此函数使用条件必须保证原有图像的高度final_image.height<= target_height-offset_height,且原有图像的宽度final_image.weight<=target_width-offset_width
所增加的新像素的灰度值均为0。对于尺寸过小的图像,这种边界填充方式是非常有用的。如果训练集中的图像存在多种不同的长宽比,便需要这样的处理方法。对于那些长宽比不一致的图像,TensorFlow还提供了一种组合了pad和crop的尺寸调整的便捷方法
crop_pad = tf.image.resize_image_with_crop_or_pad(final_image, target_height=500, target_width=2000)
这个操作的效果其实就是,首先建立一张【target_height,target_width】的全黑图片,然后将原图像final_image居中放在这张黑布上面
4.3.3 翻转
翻转操作的含义与其字面意思一致,即每个像素的位置都沿水平或垂直方向翻转。从技术角度讲,翻转是在沿垂直方向翻转时所采用的术语。利用TensorFlow对图像执行翻转操作是非常有用的,这样可以为同一幅训练图像赋予不同的视角。例如,一幅左耳卷曲的澳大利亚牧羊犬图像如果经过了翻转,便有可能与其他的图像中右耳卷曲的狗匹配。
TensorFlow有一些函数可实现垂直翻转、水平翻转,用户可随意选择。随机翻转一幅图像的能力对于防止模型对图像的翻转版本产生过拟合非常有用
top_bottom = tf.image.flip_up_down(final_image)
left_right = tf.image.flip_left_right(final_image)
tf.image.flip_left_right和tf.image.flip_up_down都可对张量进行操作,而非仅限于图像。这些函数对图像的翻转具有确定性(确定性就是知道图像经过翻转函数肯定得到相应的效果),要想实现对图像随机翻转,可利用另一组函数。
top_bottom = tf.image.random_flip_up_down(final_image)
left_right = tf.image.random_flip_left_right(final_image)
上述两个函数sess.run之后图像可能翻转可能不翻转
于本例中的输出是随机的。这个例程每次运行时,都会得到不同的输出。有一个名称为seed的参数可控制翻转发生的随机性
4.3.4 饱和与平衡
可在互联网上找到的图像通常都事先经过了编辑。例如,Stanford Dogs数据集中的许多图像都具有过高的饱和度(大量颜色)。当将编辑过的图像用于训练时,可能会误导CNN模型去寻找那些与编辑过的图像有关的模式,而非图像本身所呈现的内容
拓展知识1 理解图像中基本概念:色调、色相、饱和度、对比度、亮度
https://www.cnblogs.com/taoshengyujiu/articles/4308501.html
对比度:对比度指不同颜色之间的差别。对比度越大,不同颜色之间的反差越大,即所谓黑白分明,对比度过大,图像就会显得很刺眼。对比度越小,不同颜色之间的反差就越小。
亮度:亮度指照射在景物或图像上光线的明暗程度。图像亮度增加时,就会显得耀眼或刺眼,亮度越小时,图像就会显得灰暗。
色调: 色调是各种图像色彩模式下原色的明暗程度,级别范围从0到255,共256级色调。例如对灰度图像,当色调级别为255时,就是白色,当级别为0时,就是黑色,中间是各种程度不同的灰色。在RGB模式中,色调代表红、绿、蓝三种原色的明暗程度,对绿色就有淡绿、浅绿、深绿等不同的色调。色调是指色彩外观的基本倾向。在明度、纯度、色相这三个要素中,某种因素起主导作有用,可以称之为某种色调
色相:色相就是颜色,调整色相就是调整景物的颜色,例如,彩虹由红、橙、黄、绿、青、蓝、紫七色组成,那么它就有七种色相。顾名思义即各类色彩的相貌称谓,如大红、普蓝、柠檬黄等。色相是色彩的首要特征,是区别各种不同色彩的最准确的标准。事实上任何黑白灰以外的颜色都有色相的属性,而色相也就是由原色、间色和复色来构成的
饱和度:饱和度是指图像颜色的浓度。饱和度越高,颜色越饱满,即所谓的青翠欲滴的感觉。饱和度越低,颜色就会显得越陈旧、惨淡,饱和度为0时,图像就为灰度图像。可以通过调整电视机的饱和度来进一步理解饱和度的概念。
为向在图像数据上的训练提供帮助,TensorFlow实现了一些通过修改饱和度、色调、对比度和亮度的函数。利用这些函数可对这些图像属性进行简单的操作和随机修改。对训练而言,这种随机修改是非常有用的,原因与图像的随机翻转类似。对属性的随机修改能够使CNN精确匹配经过编辑的或不同光照条件下的图像的某种特征
bright = tf.image.adjust_brightness(image, 0.2)
contrast = tf.image.adjust_contrast(image, -0.5)
将对比度调整了-0.5,这将生成一个识别度相当差的新图像。调节对比度时,最好选择一个较小的增量,以避免对图像造成“过曝”。这里的“过曝”的含义与神经元出现饱和类似,即达到了最大值而无法恢复。当对比度变化时,图像中的像素可能会呈现出全白和全黑的情形
4.4 颜色
CNN通常使用具有单一颜色的图像来训练。当一幅图像只有单一颜色时,我们称它使用了灰度颜色空间,即单颜色通道。对大多数计算机视觉相关任务而言,使用灰度值是合理的,因为要了解图像的形状无须借助所有的颜色信息。缩减颜色空间可加速训练过程。为描述图像中的灰度,仅需一个单个分量的秩1张量即可,而无须像RGB图像那样使用含3个分量的秩1张量
虽然只使用灰度信息有一些优点,但也必须考虑那些需要利用颜色的区分性的应用。在大多数计算机视觉任务中,如何使用图像中的颜色都颇具挑战性,因为很难从数学上定义两个RGB颜色之间的相似度。为在CNN训练中使用颜色,对图像进行颜色空间变换有时是非常有用的。
4.4.1 灰度
灰度图具有单个分量,且其取值范围与RGB图像中的颜色一样,也是[0,255]。
tf.image.rgb_to_grayscale(final_image)函数将rgb图像转换为灰度图像
gray = tf.image.rgb_to_grayscale(final_image)
4.4.2 HSV空间
色度、饱和度和灰度值构成了HSV颜色空间。与RGB空间类似,这个颜色空间也是用含3个分量的秩1张量表示的。HSV空间所度量的内容与RGB空间不同,它所度量的是图像的一些更为贴近人类感知的属性。有时HSV也被称为HSB,其中字母B表示亮度值。
hsv = tf.image.rgb_to_hsv(tf.image.convert_image_dtype(final_image, tf.float32))
注意:tf.image.rgb_to_hsv(image)函数中传入的图像参数image必须是数据类型为float的图像
4.4.3 RGB空间
到目前为止,所有的示例代码中使用的都是RGB颜色空间。它对应于一个含3个分量的秩1张量,其中红、绿和蓝的取值范围均为[0,255]。大多数图像本身就位于RGB颜色空间中,但考虑到有些图像可能会来自其他颜色空间,TensorFlow也提供了一些颜色空间转换的内置函数。
rgb1 = tf.image.hsv_to_rgb(hsv)
rgb2 = tf.image.grayscale_to_rgb(gray)
RGB图像需要三种颜色,而灰度图像只需要一种颜色。当转换(灰度到RGB)发生时,RGB中每个像素的各通道都将被与灰度图中对应像素的灰度值填充,如下图所示,由灰度图转换出的rgb图中各通道的值都相同,都等于灰度图中的值
4.4.4 LAB空间
TensorFlow并未为LAB颜色空间提供原生支持。它是一种有用的颜色空间,因为与RGB相比,它能够映射大量可感知的颜色。虽然TensorFlow并未为它提供原生支持,但它却是一种经常在专业场合使用的颜色空间。Python库python-colormath为LAB和其他本书未提及的颜色空间提供了转换支持。
使用LAB颜色空间最大的好处在于与RGB或HSV空间相比,它对颜色差异的映射更贴近人类的感知。在LAB颜色空间中,两个颜色的欧氏距离在某种程度上能够反映人类所感受到的这两种颜色的差异
4.4.5.图像数据类型转换
在这些例子中,为说明如何修改图像的数据类型,tf.to_float被多次用到。对于某些例子,使用这种方式是可以的,但TensorFlow还提供了一个内置函数,用于当图像数据类型发生变化时恰当地对像素值进行比例变换。 tf.image.convert_iamge_dtype(image,dtype,saturate=False)是将图像的数据类型从tf.uint8更改为tf.float的便捷方法。
5、CNN的实现
利用TensorFlow实现目标识别与分类要求对卷积(对CNN)、常见层(非线性、池化、全连接等)、图像加载、图像操作和颜色空间相关的基础知识有所了解。当掌握了这些内容后,便有可能利用TensorFlow构建一个用于图像识别与分类的CNN模型。在这种情况下,训练数据来自于Stanford的一个包含了许多狗及其品种标签的数据集。我们需要依据这些图像训练一个网络,然后再评估它对狗的品种的预测准确性。
我们的网络架构采取了AlexKrizhevsky的AlexNet的简化版本,但并未使用AlexNet的所有层。AlexNet架构在本章最开始已做过介绍,它是ILSVRC2012挑战赛的冠军。这个网络使用了本章介绍过的层和技术,与TensorFlow提供的CNN入门教程也非常类似。
本节所介绍的网络包含每层之后的输出TensorShape。这些层按照自左向右、自上向下的顺序被依次读取,且存在关联的层被分为一组。当输入经过网络时,其高度和宽度都会减小,而其深度会增加。深度值的增加减少了使用该网络所需的计算量
5.1 Stanford Dogs数据集
用于训练该模型的数据集可从Stanford的计算机视觉站点http://vision.stanford.edu/aditya86/ImageNetDogs/ 下载。训练模型时需要事先下载相关的数据。下载完包含所有图像的压缩文件后,需要将其解压至一个新的名为imagenet-dogs的目录下,该目录应当与用于构建模型的代码位于同一路径下。
由Stanford提供的压缩文件包含被组织为120个不同品种的狗的图像。该模型的目标是将这个数据集中80%的图像用于训练,而用其余20%做测试。如果这是一个产品模型,则还应预留一些原始数据做交叉验证。为验证模型的准确性,交叉验证是一个有用的步骤,但该模型的设计初衷只是为说明这个过程,而非出于完整性的考虑。
这个数据压缩包遵循了ImageNet的实践原则。每个狗品种都对应一个类似于n02085620-Chihuahua的文件夹,其中目录名称的后一半对应于狗品种的英语表述(Chihuahua)。在每个目录中,都有大量属于该品种的狗的图像,每幅图像都为JPEG格式(RGB)且尺寸各异。各图像尺寸不一是一种挑战,因为TensorFlow希望各张量都具有相同的维数
5.2 将图像转为TFRecord文件
被组织在某个目录中的原始图像无法直接用于训练,因为这些图像尺寸不一,且相应的品种标签也不在图像文件中。将图像提前转换为TFRecord文件将有助于加速训练,并简化与图像标签的匹配。另一个好处是与训练和测试有关的图像可以事先分离,这样当训练开始时,就可利用检查点文件对模型进行不间断的测试。
转换图像数据格式时需要将它们的颜色空间变为灰度空间,将图像尺寸修改为统一尺寸,并将标签依附于每幅图像。在训练开始前,这种转换应当仅进行一次,通常它会花费较长的时间。
# 预处理阶段,将jpg格式样本转化为tfrecord
# 获取数据集
def get_dataset(image_dir):
# step0:获取所有图片的相对文件名# step0:获取所有图片的相对文件名
# image_filenames = tf.gfile.Glob('./Images/*/*.jpg')
image_filenames = tf.gfile.Glob(image_dir)
# 相当于创建空的字典,当要查询的建对应的值不存在时赋值为[],此时的training_dataset和testing_dataset都是字典,字典的值都是列表
training_dataset = defaultdict(list)
testing_dataset = defaultdict(list)
# 文件名类似于'./Images/n02093647-Bedlington_terrier/n02093647_1558.jpg',使用split切割后的第3个元素就是n02093647-Bedlington_terrier
# 此时的image_filename_with_breed的每一项相当于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
image_filename_with_breed = map(lambda filename: (filename.split('/')[2], filename), image_filenames)
# 注意:此时的dog_breed是分组后的组名,即狗的品种,而breed_images是分组后该组的所有项,每项都是分组前image_filename_with_breed中的数据项,
# 即类似于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
# 此段处理结束后,testing_dataset和training_dataset的键都是类似于'n02111889-Samoyed'的狗的品种名,值为包含多个类似于'./Images/n02111889-Samoyed/n02111889_11502.jpg'的图片路径的列表
for dog_breed, breed_images in groupby(image_filename_with_breed, lambda x: x[0]):
for i, breed_image in enumerate(breed_images):
if i % 5 == 0:
# breed_image是类似于# 即类似于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
testing_dataset[dog_breed].append(breed_image[1])
else:
training_dataset[dog_breed].append(breed_image[1])
breed_training_count = len(training_dataset[dog_breed]) # 获取该品种训练和测试的样本数,确保比例
breed_testing_count = len(testing_dataset[dog_breed])
assert round(breed_testing_count/(breed_training_count+breed_testing_count), 2) > 0.18, 'not enought testing images'
return training_dataset, testing_dataset
# 将训练和测试样本写入tfrecord文件
def write_tfrecord_file(breed_dir, sess, dataset, record_location):
# 注意:此部分的breed_to_num 是狗狗种类和数字的对应关系(即存放图片目录下种类的存放顺序)
breed_to_num = dict()
total_breed = tf.gfile.ListDirectory(breed_dir)
for x in total_breed:
ser = total_breed.index(x)
ser1 = sess.run(tf.cast(ser, tf.uint8))
breed_to_num[x] = ser1
# 将图片写入tfrecord文件
writer = None
current_index = 0
# 注意:使用for x in dict获取到的x只是字典dict的键,而无法获取其值,要想直接获取键值对,可以使用字典的items函数
for breed, image_filenames in dataset.items():
for image_filename in image_filenames:
if current_index % 100 == 0:
if writer:
writer.close()
record_name = '{record_location}-{current_index}.tfrecords'.format(record_location=record_location,
current_index=current_index)
writer = tf.python_io.TFRecordWriter(record_name)
current_index += 1
image_file = tf.read_file(image_filename)
try:
image = tf.image.decode_jpeg(image_file)
except:
print(image_filename)
continue
# 转换为灰度图可以减少处理的计算量和内存占用,但这不是必须的
gray_image = tf.image.rgb_to_grayscale(image)
resize_image = tf.image.resize_images(gray_image, [image_height, image_weight])
# 将此样本写入tfrecord文件
# step1:获取图像和标签的bytes格式
uint8_image = tf.cast(resize_image, tf.uint8)
image_bytes = sess.run(uint8_image).tobytes()
image_label = breed_to_num[breed].tobytes()
# step2: 创建bytes类型区域
image_bytes_list = tf.train.BytesList(value=[image_bytes])
label_bytes_list = tf.train.BytesList(value=[image_label])
# step3 :创建feature区域
image_feature = tf.train.Feature(bytes_list=image_bytes_list)
label_feature = tf.train.Feature(bytes_list=label_bytes_list)
# step4 :创建features区域
features = {'image': image_feature, 'label': label_feature}
example_feature = tf.train.Features(feature=features)
# step5:创建example区域
example = tf.train.Example(features=example_feature)
writer.write(example.SerializeToString())
writer.close()
return breed_to_num
5.3 加载图像
一旦测试集和训练集被转换为TFRecord格式,便可按照TFRecord文件而非JPEG文件进行读取。我们的目标是每次加载少量图像及相应的标签
# 加载图像
# 数据库的映射函数,即将tfrecord文件进行解码
def tfrecord_map(item):
# step1:对tfrecord文件的example格式进行解码
features = {'image': tf.FixedLenFeature([], tf.string), 'label': tf.FixedLenFeature([], tf.string)}
parsed_features = tf.parse_single_example(item, features)
tf_record_image = tf.decode_raw(parsed_features['image'], tf.uint8) # parsed_features是一堆二进制格式的字符串
tf_record_label = tf.decode_raw(parsed_features['label'], tf.uint8)
# step2:重塑图片形状【此时tf_record_image就是一个列表(维度为1)】
reshape_image = tf.reshape(tf_record_image, [image_height, image_weight, 1])
float_image = tf.image.convert_image_dtype(reshape_image, tf.float32)
# step3:对标签进行处理,之后计算交叉熵的时候要求标签是int32或int64
label_int64 = tf.cast(tf_record_label, tf.int64)
return float_image, label_int64
# 读入输入函数,形参filedir为tfrecord文件的路径,batch_size为批处理图片的数量
def input_image(filedir):
tfrecord_dataset = tf.data.TFRecordDataset(filedir).map(tfrecord_map).shuffle(buffer_size=10000).repeat().batch(batch_size)
image_iter = tfrecord_dataset.make_one_shot_iterator()
next_image = image_iter.get_next()
# 注意:迭代器获取的图片即为标准的图片格式(4维度),但是标签却是两个维度,但是希望得到的维度是一维,故需要降维
# 即:若batch_size=2,则一次性获取两个图片的标签,若两幅图的标签分别为1和2,那么此时得到的label的形式为【【1】,【2】】
image, label = next_image[0], next_image[1]
# 对图像格式进行整理,确保形状符合要求,此步很重要,可以确保使用此图片和标签的操作都能得到确定的形状,防止出现形状为问号的情况
image_really = tf.reshape(image, [batch_size, image_height, image_weight, 1])
label_downdim = tf.reshape(label, [batch_size]) # 降维
return image_really, label_downdim
这段示例代码通过匹配所有在训练集所在目录下找到的TFRecord文件而加载训练图像。每个TFRecord文件中都包含了多幅图像,但tf.parse_single_example将只从批处理时,必须要满足的条件是系统拥有足够的内存。当可用的图像都加载到内存中后,接下来的步骤便是创建用于训练和测试的模型
5.4 模型
这里所使用的模型与前面的MNIST卷积网络的例子非常类似,也常出现在介绍卷积神经网络的TensorFlow入门教程中。该模型的架构虽然简单,但对于解释图像分类与识别中所使用的不同技术却非常有价值。更复杂的模型可参考AlexNet的设计,它引入了更多的卷积层
# 搭建cnn模型
def cnn_model(x):
# 第一层:32个【5,5】的卷积核进行卷积,采用relu非线性化,采用最大池化
with tf.name_scope('layer_one'):
conv2d_layer_one = tf.layers.conv2d(inputs=x, filters=32,
kernel_size=[5, 5], strides=[2, 2],
padding='SAME', activation=tf.nn.relu,
trainable=True, name='conv2d_one')
pool_layer_one = tf.nn.max_pool(value=conv2d_layer_one, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding='SAME')
# 第二层:64个【5,5】的卷积核进行卷积、采用relu进行非线性化,采用最大池化
with tf.name_scope('layer_two'):
conv2d_layer_two = tf.layers.conv2d(inputs=pool_layer_one, filters=64, kernel_size=[5, 5], strides=[1, 1],
padding='SAME', activation=tf.nn.relu,
trainable=True, name='conv2d_two')
pool_layer_two = tf.nn.max_pool(value=conv2d_layer_two, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding='SAME')
# 第三层:全连接层
with tf.name_scope('fully_connect_one'):
# 将每幅图片重塑形状为一维向量
flatten_layer_two = tf.reshape(pool_layer_two, [batch_size, -1])
# 高级全连接层
fully_connect_layer0 = tf.layers.dense(flatten_layer_two, units=512,
kernel_initializer=lambda shape, dtype, partition_info:
tf.truncated_normal(shape, stddev=0.1, dtype=dtype))
# drop,扔到一部分数据,便于加速训练
drop_layer_three = tf.nn.dropout(fully_connect_layer0, 0.1)
# 第四层:全连接层,因为狗狗的种类为120,故此全连接层的单元数为120
with tf.name_scope('fully_connect_two'):
fin= tf.layers.dense(drop_layer_three, units=120,
kernel_initializer=lambda shape, dtype, partition_info:
tf.truncated_normal(shape, stddev=0.1, dtype=dtype))
# 注意:其实此处只需返回fin即可,返回其他的只是为了查看其他模块是否工作正常
# return conv2d_layer_one, pool_layer_one, conv2d_layer_two, pool_layer_two, fully_connect_layer0, fin
return fin
与第1层相比,第2层改动很小,唯一的区别在于滤波器的深度。现在滤波器的数量变为第一层的2倍,同时减小了图像的高度和宽度。多个卷积和池化层连续地减少了输入的高度和宽度,同时进一步增加了深度。
此时,可进一步增加卷积和池化步骤。在许多架构中,卷积层和池化层都超过5层。最复杂的架构需要的训练和调试时间也更长,但它们能够匹配更多更复杂的模式。在本例中,为解释卷积网络的基本原理,使用两个卷积层和池化已经足够了。
被处理的张量仍然相当复杂,接下来的步骤是将图像中的每个点都与输出神经元建立全连接。由于在本例中,后面要使用softmax,因此全连接层需要修改为二阶张量。张量的第1维将用于区分每幅图像,而第2维对应于每个输入张量的秩1张量
tf.reshape拥有一个特殊值,其可用于指示和使用其余所有维。在这段示例代码中,-1用于将最后一个池化层调整为一个巨大的秩1张量。池化层展开后,便可与将网络当前状态与所预测的狗的品种关联的两个全连接层进行整合。
5.5 训练
一旦模型做好了训练的准备,最后的步骤便与本书前面的章节所讨论的过程完全一致。依据模型对输入到训练优化器(作用是优化每层的权值)的训练数据的真实标签和模型的预测结果计算模型的损失。这个优化过程会经历数次迭代,每次迭代时都试图提升模型的准确率。对于该模型,有一点需要注意,那就是在训练过程中,大部分分类函数(tf.nn.softmax)都要求标签为数值类型。在介绍从TFRecord文件加载图像的那一节中已经强调过这一点。在本例中,每个标签都是一个类似于n02085620-Chihuahua的字符串。由于tf.nn.softmax无法直接使用这些字符串,所以需要将每个标签转换为一个独一无二的数字。将这些标签转换为整数表示应当在预处理阶段进行。
# 训练
def loss(x, y):
total_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits
(logits=cnn_model(x), labels=y))
return total_loss
# 训练
def train(totalloss, learn_rate):
op = tf.train.GradientDescentOptimizer(learn_rate).minimize(totalloss)
return op
with tf.Session() as sess:
# step0:预处理阶段,将图片和标签写入tfrecord文件
'''train_dataset, test_dataset = get_dataset('./Images/*/*.jpg')
breed_dir = './Images'
breed_to_num = write_tfrecord_file(breed_dir, sess, train_dataset, './output/training-images/training-image')
write_tfrecord_file(sess, test_dataset, './output/testing-images/testing-image')'''
# step1:建图
filedir = './output/training-images/training-image-300.tfrecords'
x, y = input_image(filedir)
total_loss = loss(x, y)
learn_rate = 0.01
op = train(total_loss, learn_rate)
sess.run(tf.global_variables_initializer())
# step2 训练
train_step = 1000
for i in range(train_step):
sess.run(op)
if i % 10 == 0:
print(sess.run(total_loss))
sess.close()
5.6 神经元函数及优化方法
本节主要介绍 TensorFlow 中构建神经网络所需的神经元函数,包括各种激活函数、卷积函数、池化函数、损失函数、优化器等。读者阅读时,务必把本节介绍的常用 API 记熟,这有利于在“实战篇”轻轻松松地构建神经网络进行训练。
5.6.1 激活函数
激活函数(activation function)运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层的神经网络。神经网络之所以能解决非线性问题(如语音、图像识别),本质上就是激活函数加入了非线性因素,弥补了线性模型的表达力,把“激活的神经元的特征”通过函数保留并映射到下一层。
因为神经网络的数学基础是处处可微的,所以选取的激活函数要能保证数据输入与输出也是可微的。那么激活函数在 TensorFlow 中是如何表达的呢?
激活函数不会更改输入数据的维度,也就是输入和输出的维度是相同的。TensorFlow 中有如下激活函数,它们定义在 tensorflow-1.1.0/tensorflow/python/ops/nn.py 文件中,这里包括平滑非线性的激活函数,如 sigmoid、tanh、elu、softplus 和 softsign,也包括连续但不是处处可微的函数 relu、relu6、crelu 和 relu_x,以及随机正则化函数 dropout:
tf.nn.relu()
tf.nn.sigmoid()
tf.nn.tanh()
tf.nn.elu()
tf.nn.bias_add()
tf.nn.crelu()
tf.nn.relu6()
tf.nn.softplus()
tf.nn.softsign()
tf.nn.dropout() # 防止过拟合,用来舍弃某些神经元
上述激活函数的输入均为要计算的 x(一个张量),输出均为与 x 数据类型相同的张量。常见的激活函数有 sigmoid、tanh、relu 和 softplus 这 4 种。下面我们就来逐一讲解
(1)sigmoid 函数。这是传统神经网络中最常用的激活函数之一(另一个是 tanh)
sigmoid 函数的优点在于,它的输出映射在(0,1)内,单调连续,非常适合用作输出层,并且求导比较容易。但是,它也有缺点,因为软饱和性,一旦输入落入饱和区,f'(x)就会变得接近于 0,很容易产生梯度消失
软饱和是指激活函数 h(x)在取值趋于无穷大时,它的一阶导数趋于 0。硬饱和是指当|x| > c 时,其中 c 为常数,f '(x)=0。
relu 就是一类左侧硬饱和激活函数。
梯度消失是指在更新模型参数时采用链式求导法则反向求导,越往前梯度越小。最终的结果是到达一定深度后梯度对模型的更新就没有任何贡献了
(2)tanh 函数。
tanh 函数也具有软饱和性。因为它的输出以 0 为中心,收敛速度比 sigmoid 要很。但是仍无法解决梯度消失的问题
(3)relu 函数是目前最受欢迎的激活函数。softplus可以看作是 ReLU 的平滑版本。relu 定义为 f(x)=max(x,0)。
softplus 定义为 f(x)=log(1+exp(x))。
由上图 可见,relu 在 x<0 时硬饱和。由于 x>0 时导数为 1,所以,relu 能够在 x>0 时保持梯度不衰减,从而缓解梯度消失问题,还能够更很地收敛,并提供了神经网络的稀疏表达能力。但是,随着训练的进行,部分输入会落到硬饱和区,导致对应的权重无法更新,称为“神经元死亡”
除了 relu 本身外,TensorFlow 还定义了 relu6,也就是定义在 min(max(features, 0), 6)的tf.nn.relu6(features, name=None),以及 crelu,也就是 tf.nn.crelu(features, name=None)。
(4)dropout 函数。一个神经元将以概率 keep_prob 决定是否被抑制。如果被抑制,该神经元的输出就为 0;如果不被抑制,那么该神经元的输出值将被放大到原来的 1/keep_prob 倍。①在默认情况下,每个神经元是否被抑制是相互独立的。但是否被抑制也可以通过 noise_shape 来调节。当 noise_shape[i] == shape(x)[i]时,x 中的元素是相互独立的。如果 shape(x) = [k, l, m, n],x 中的维度的顺序分别为批、行、列和通道,如果 noise_shape = [k, 1, 1, n],那么每个批和通道都是相互独立的,但是每行和每列的数据都是关联的,也就是说,要不都为 0,要不都还是原来的值。
dropout 在论文中最我被提出时是这么做的:在训练的时候用概率 p 丢弃,然后在预测的时候,所有参数按比例缩小,也就是乘以 p。在各种深度学习框架(如 Keras、TensorFlow)的实现中,都是用反向 ropout 来代替 dropout。也就是这里所说的,在训练的时候一边 dropout,然后再按比例放大,也就是乘以 1/p,然后在预测的时候,不做任务处理
当输入数据特征相差明显时,用 tanh 的效果会很好,且在循环过程中会不断扩大特征效果并显示出来。当特征相差不明显时,sigmoid 效果比较好。同时,用 sigmoid 和 tanh 作为激活函数时,需要对输入进行规范化,否则激活后的值全部都进入平坦区,隐层的输出会全部趋同,丧失原有的特征表达。而 relu 会好很多,有时可以不需要输入规范化来避免上述情况。因此,现在大部分的卷积神经网络都采用 relu 作为激活函数。我估计大概有 85%~90%的神经网络会采用 ReLU,10%~15%的神经网络会采用 tanh,尤其用在自然语言处理上
5.6.2 卷积函数
卷积函数是构建神经网络的重要支架,是在一批图像上扫描的二维过滤器。卷积函数定义在 tensorflow1.1.0/tensorflow/python/ops 下的 nn_impl.py 和n_ops.py 文件中。
tf.nn.convolution(input, filter, padding, strides=None,
dilation_rate=None, name=None, data_format=None)
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None,
data_format= None, name=None)
tf.nn.depthwise_conv2d (input, filter, strides, padding, rate=None, name=None,
data_format=None)
tf.nn.separable_conv2d (input, depthwise_filter, pointwise_filter, strides, padding,
rate=None, name=None, data_format=None)
tf.nn.atrous_conv2d(value, filters, rate, padding, name=None)
tf.nn.conv2d_transpose(value, filter, output_shape, strides, padding='SAME',
data_format='NHWC', name=None)
tf.nn.conv1d(value, filters, stride, padding, use_cudnn_on_gpu=None,
data_format= None, name=None)
tf.nn.conv3d(input, filter, strides, padding, name=None)
tf.nn.conv3d_transpose(value, filter, output_shape, strides, padding='SAME', name=None)
下面就分别加以说明。
(1)tf.nn.convolution(input, filter, padding, strides=None, dilation_rate=None, name=None, data_format =None)这个函数计算 N 维卷积的和。
(2)tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)这个函数的作用是对一个四维的输入数据 input 和四维的卷积核 filter 进行操作,然后对输入数据进行一个二维的卷积操作,最后得到卷积之后的结果。
def conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None,
data_format=None, name=None)
# 输入:
# input:一个 Tensor。数据类型必须是 float32 或者 float64
# filter:一个 Tensor。数据类型必须是 input 相同
# strides:一个长度是 4 的一维整数类型数组,每一维度对应的是 input 中每一维的对应移动步数,
# 比如,strides[1]对应 input[1]的移动步数
# padding:一个字符串,取值为 SAME 或者 VALID
# padding='SAME':仅适用于全尺寸操作,即输入数据维度和输出数据维度相同
# padding='VALID:适用于部分窗口,即输入数据维度和输出数据维度不同
# use_cudnn_on_gpu:一个可选布尔值,默认情况下是 True
# name:(可选)为这个操作取一个名字
# 输出:一个 Tensor,数据类型是 input 相同
(3)tf.nn.depthwise_conv2d (input, filter, strides, padding, rate=None, name=None,data_format= None) 这个函数输入张量的数据维度是[batch, in_height, in_width, in_channels],卷积核的维度是[filter_height, filter_width, in_channels, channel_multiplier],在通道 in_channels 上面的卷积深度是1,depthwise_conv2d 函数将不同的卷积核独立地应用在 in_channels 的每个通道上(从通道 1到通道 channel_multiplier),然后把所以的结果进行汇总。最后输出通道的总数是 in_channels * channel_multiplier。
(4)tf.nn.separable_conv2d (input, depthwise_filter, pointwise_filter, strides, padding, rate=None, name=None, data_format=None)是利用几个分离的卷积核去做卷积。在这个 API 中,将应用一个二维的卷积核,在每个通道上,以深度 channel_multiplier 进行卷积
def separable_conv2d (input, depthwise_filter, pointwise_filter, strides, padding,rate=None, name=None, data_format=None)
# 特殊参数:
# depthwise_filter:一个张量。数据维度是四维[filter_height, filter_width, in_channels,
# channel_multiplier]。其中,in_channels 的卷积深度是 1
# pointwise_filter:一个张量。数据维度是四维[1, 1, channel_multiplier * in_channels,
# out_channels]。其中,pointwise_filter 是在 depthwise_filter 卷积之后的混合卷积
(5)tf.nn.atrous_conv2d(value, filters, rate, padding, name=None)计算 Atrous 卷积,又称孔卷积或者扩张卷积。
(6)tf.nn.conv2d_transpose(value, filter, output_shape, strides, padding='SAME', data_format='NHWC', name=None)①
在解卷积网络(deconvolutional network)中有时称为“反卷积”,但实际上是 conv2d的转置,而不是实际的反卷积。
def conv2d_transpose(value, filter, output_shape, strides, padding='SAME',
data_format='NHWC', name=None)
# 特殊参数:
# output_shape:一维的张量,表示反卷积运算后输出的形状
# 输出:和 value 一样维度的 Tensor
(7)tf.nn.conv1d(value, filters, stride, padding, use_cudnn_on_gpu=None, data_format=None, name=None)和二维卷积类似。这个函数是用来计算给定三维的输入和过滤器的情况下的一维卷积。不同的是,它的输入是三维,如[batch, in_width, in_channels]。卷积核的维度也是三维,少了一维 filter_height,如 [filter_width, in_channels, out_channels]。stride 是一个正整数,代表卷积核向右移动每一步的长度。
(8)tf.nn.conv3d(input, filter, strides, padding, name=None)和二维卷积类似。这个函数用来计算给定五维的输入和过滤器的情况下的三维卷积。和二维卷积相对比:
input 的 shape 中多了一维 in_depth,形状为 Shape[batch, in_depth, in_height, in_width,
in_channels];
● filter 的 shape 中多了一维 filter_depth,由 filter_depth, filter_height, filter_width 构成了卷
积核的大小;
● strides 中多了一维,变为[strides_batch, strides_depth, strides_height, strides_width,
strides_channel],必须保证 strides[0] = strides[4] = 1。
(9)tf.nn.conv3d_transpose(value, filter, output_shape, strides, padding='SAME', name=None)和二维反卷积类似,
5.6.3 池化函数
在神经网络中,池化函数一般跟在卷积函数的下一层,它们也被定义在 tensorflow-1.1.0/ tensorflow/python/ops 下的 nn.py 和 gen_nn_ops.py 文件中
tf.nn.avg_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
tf.nn.max_pool_with_argmax(input, ksize, strides, padding, Targmax=None, name=None)
tf.nn.avg_pool3d(input, ksize, strides, padding, name=None)
tf.nn.max_pool3d(input, ksize, strides, padding, name=None)
tf.nn.fractional_avg_pool(value, pooling_ratio, pseudo_random=None, overlapping=None,
deterministic=None, seed=None, seed2=None, name=None)
tf.nn.fractional_max_pool(value, pooling_ratio, pseudo_random=None, overlapping=None,
deterministic=None, seed=None, seed2=None, name=None)
tf.nn.pool(input, window_shape, pooling_type, padding, dilation_rate=None, strides=None,
name=None, data_format=None)
池化操作是利用一个矩阵窗口在张量上进行扫描,将每个矩阵窗口中的值通过取最大值或平均值来减少元素个数。每个池化操作的矩阵窗口大小是由 ksize 指定的,并且根据步长 strides决定移动步长。下面就分别来说明。
(1)tf.nn.avg_pool(value, ksize, strides, padding, data_format='NHWC', name=None)。这个函数计算池化区域中元素的平均值
def avg_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
# 输入:
# value:一个四维的张量。数据维度是[batch, height, width, channels]
# ksize:一个长度不小于 4 的整型数组。每一位上的值对应于输入数据张量中每一维的窗口对应值
# strides:一个长度不小于 4 的整型数组。该参数指定滑动窗口在输入数据张量每一维上的步长
# padding:一个字符串,取值为 SAME 或者 VALID
# data_format: 'NHWC'代表输入张量维度的顺序,N 为个数,H 为高度,W 为宽度,C 为通道数(RGB 三
# 通道或者灰度单通道)
# name(可选):为这个操作取一个名字
# 输出:一个张量,数据类型和 value 相同
(2)tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)。这个函数是计算池化区域中元素的最大值。
(3)tf.nn.max_pool_with_argmax(input, ksize, strides, padding, Targmax = None, name=None)。这个函数的作用是计算池化区域中元素的最大值和该最大值所在的位置
在计算位置 argmax 的时候,我们将 input 铺平了进行计算,所以,如果 input = [b, y, x, c],那么索引位置是(( b * height + y) * width + x) * channels + c。使用示例如下,该函数只能在 GPU 下运行,在 CPU 下没有对应的函数实现:
返回结果是一个张量组成的元组(output, argmax),output 表示池化区域的最大值;argmax的数据类型是 Targmax,维度是四维
(4)tf.nn.avg_pool3d()和 tf.nn.max_pool3d()分别是在三维下的平均池化和最大池化。
(5)tf.nn.fractional_avg_pool()和tf.nn.fractional_max_pool()分别是在三维下的平均池化和最大池化。
(6)tf.nn.pool(input, window_shape, pooling_type, padding, dilation_rate=None, strides=None, name=None, data_format=None)这个函数执行一个 N 维的池化操作。
5.6.4 分类函数
TensorFlow 中常见的分类函数主要有 sigmoid_cross_entropy_with_logits、softmax、log_softmax、softmax_cross_entropy_with_logits 等,它们也主要定义在 tensorflow-1.1.0/tensorflow/python/ops 的nn.py 和 nn_ops.py 文件中
tf.nn.sigmoid_cross_entropy_with_logits(logits, targets, name=None)
tf.nn.softmax(logits, dim=-1, name=None)
tf.nn.log_softmax(logits, dim=-1, name=None)
tf.nn.softmax_cross_entropy_with_logits(logits, labels, dim=-1, name=None)
tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels, name=None)
(1)tf.nn.sigmoid_cross_entropy_with_logits(logits, targets, name=None):
def sigmoid_cross_entropy_with_logits(logits, targets, name=None):
# 输入:logits:[batch_size, num_classes],targets:[batch_size, size].logits 用最后一
# 层的输入即可
# 最后一层不需要进行 sigmoid 运算,此函数内部进行了 sigmoid 操作
# 输出:loss [batch_size, num_classes]
这个函数的输入要格外注意,如果采用此函数作为损失函数,在神经网络的最后一层不需要进行 sigmoid 运算
(2)tf.nn.softmax(logits, dim=-1, name=None)计算 Softmax 激活,也就是 softmax = exp(logits) /reduce_sum(exp(logits), dim)
(3)tf.nn.log_softmax(logits, dim=-1, name=None)计算 log softmax 激活,也就是 logsoftmax = logits - log(reduce_sum(exp(logits), dim))。
(4)tf.nn.softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, dim=-1, name =None):
def softmax_cross_entropy_with_logits(logits, targets, dim=-1, name=None):
# 输入:logits and labels 均为[batch_size, num_classes]
# 输出: loss:[batch_size], 里面保存的是 batch 中每个样本的交叉熵
(5)tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels, name=None)
def sparse_softmax_cross_entropy_with_logits(logits, labels, name=None):
# logits 是神经网络最后一层的结果
# 输入:logits: [batch_size, num_classes] labels: [batch_size],必须在[0, num_classes]
# 输出:loss [batch_size],里面保存是 batch 中每个样本的交叉熵
5.6.5 优化方法
如何加速神经网络的训练呢?目前加速训练的优化方法基本都是基于梯度下降的,只是细节上有些差异。梯度下降是求函数极值的一种方法,学习到最后就是求损失函数的极值问题
TensorFlow 提供了很多优化器(optimizer),我们重点介绍下面这 8 个
class tf.train.GradientDescentOptimizer
class tf.train.AdadeltaOptimizer
class tf.train.AdagradOptimizer
class tf.train.AdagradDAOptimizer
class tf.train.MomentumOptimizer
class tf.train.AdamOptimizer
class tf.train.FtrlOptimizer
class tf.train.RMSPropOptimizer
这 8 个优化器对应 8 种优化方法,分别是梯度下降法(BGD 和 SGD)、Adadelta 法、Adagrad法(Adagrad 和 AdagradDAO)、Momentum 法(Momentum 和 Nesterov Momentum)、Adam、Ftrl 法和 RMSProp 法,其中 BGD、SGD、Momentum 和 Nesterov Momentum 是手动指定学习率的,其余算法能够自动调节学习率
下面就介绍其中几种优化方法。
1.BGD 法
BGD 的全称是 batch gradient descent,即批梯度下降。这种方法是利用现有参数对训练集中的每一个输入生成一个估计输出 yi,然后跟实际输出 yi比较,统计所有误差,求平均以后得到平均误差,以此作为更新参数的依据。它的迭代过程为:
(1)提取训练集中的所有内容{x1, …, xn},以及相关的输出 yi;
(2)计算梯度和误差并更新参数。
这种方法的优点是,使用所有训练数据计算,能够保证收敛,并且不需要逐渐减少学习率;缺点是,每一步都需要使用所有的训练数据,随着训练的进行,速度会越来越慢。那么,如果将训练数据拆分成一个个批次(batch),每次抽取一批数据来更新参数,是不
是会加速训练呢?这就是最常用的 SGD。
2.SGD 法
SGD 的全称是 stochastic gradient descent,即随机梯度下降。因为这种方法的主要思想是将数据集拆分成一个个批次(batch)随机抽取一个批次来计算并更新参数,所以也称为 MBGD(minibatch gradient descent)。
SGD 在每一次迭代计算 mini-batch 的梯度,然后对参数进行更新。与 BGD 相比,SGD 在训练数据集很大时,仍能以较很的速度收敛。但是,它仍然会有下面两个缺点。
(1)由于抽取不可避免地梯度会有误差,需要手动调整学习率(learning rate),但是选择合适的学习率又比较困难。尤其在训练时,我们常常想对常出现的特征更新速度狠一些,而对不常出现的特征更新速度慢一些,而 SGD 在更新参数时对所有参数采用一样的学习率,因此无法满足要求。
(2)SGD 容易收敛到局部最优,并且在某些情况下可能被困在鞍点。为了解决学习率固定的问题,又引入了 Momentum 法。
3.Momentum 法
Momentum 是模拟物理学中动量的概念,更新时在一定程度上保留之前的更新方向,利用当前的批次再微调本次的更新参数,因此引入了一个新的变量 v(速度),作为前几次梯度的累加。因此,Momentum 能够更新学习率,在下降初期,前后梯度方向一致时,能够加速学习;在下降的中后期,在局部最小值的附近来回震荡时,能够抑制震荡,加很收敛
4.Nesterov Momentum 法
Nesterov Momentum 法由 Ilya Sutskever 在 Nesterov 工作的启发下提出的,是对传统 Momentum法的一项改进
标准 Momentum 法首先计算一个梯度(短的 1 号线),然后在加速更新梯度的方向进行一个大的跳跃(长的 1 号线);Nesterov 项首先在原来加速的梯度方向进行一个大的跳跃(2 号线),然后在该位置计算梯度值(3 号线),然后用这个梯度值修正最终的更新方向(4 号线)。上面介绍的优化方法都需要我们自己设定学习率,接下来介绍几种自适应学习率的优化方法
5.Adagrad 法
Adagrad 法能够自适应地为各个参数分配不同的学习率,能够控制每个维度的梯度方向。这种方法的优点是能够实现学习率的自动更改:如果本次更新时梯度大,学习率就衰减得很一些;如果这次更新时梯度小,学习率衰减得就慢一些。
6.Adadelta 法
Adagrad 法仍然存在一些问题:其学习率单调递减,在训练的后期学习率非常小,并且需要手动设置一个全局的初始学习率。Adadelta 法用一阶的方法,近似模拟二阶牛顿法,解决了这些问题
7.RMSprop 法
RMSProp 法与 Momentum 法类似,通过引入一个衰减系数,使每一回合都衰减一定比例。在实践中,对循环神经网络(RNN)效果很好。
8.Adam 法
Adam 的名称来源于自适应矩估计①(adaptive moment estimation)。Adam 法根据损失函数针对每个参数的梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率
拓展知识1 Python glob使用
https://blog.csdn.net/u010472607/article/details/76857493
glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,类似于Windows下的文件搜索,支持通配符操作,,?,[]这三个通配符,代表0个或多个字符,?代表一个字符,[]匹配指定范围内的字符,如[0-9]匹配数字。两个主要方法如下
1. glob方法:
glob模块的主要方法就是glob,该方法返回所有匹配的文件路径列表(list);该方法需要一个参数用来指定匹配的路径字符串(字符串可以为绝对路径也可以为相对路径),其返回的文件名只包括当前目录里的文件名,不包括子文件夹里的文件。
2. iglob方法:
获取一个迭代器( iterator )对象,使用它可以逐个获取匹配的文件路径名。与glob.glob()的区别是:glob.glob同时获取所有的匹配路径,而 glob.iglob一次只获取一个匹配路径
注意:tf模块也有功能类似的函数,即tf.gfile.Glob
拓展知识2 python(基础):一行写不下,如何换行继续写
https://blog.csdn.net/qq_40229981/article/details/83587503
在一行末尾加上‘\’【如果是以逗号连接的,可以不加‘\’】
拓展知识3 Python enumerate() 函数
http://www.runoob.com/python/python-func-enumerate.html
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
拓展知识4 python中defaultdict方法的使用
https://www.cnblogs.com/jidongdeatao/p/6930325.html
defaultdict类就好像是一个dict,但是它是使用一个类型来初始化的
glob模块可枚举指定路径下的目录,从而显示出数据集中的文件结构。文件名中的8个数字对应于ImageNet中每个类别的
WordNet ID。ImageNet网站拥有一个可依据WordNet ID查询图像细节的浏览器。例如,要查看Chihuahua(吉娃娃)品种的样本,可通过下列网址访问http://www.imagenet.org/synset?wnid=n02085620
将jpg格式的图像转换为tfrecord格式的完整代码如下:
import tensorflow as tf
import numpy as np
from collections import defaultdict
from itertools import groupby
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
np.set_printoptions(threshold=np.nan)
# 获取数据集
def get_dataset(image_dir):
# step0:获取所有图片的相对文件名
# image_filenames = tf.gfile.Glob('./Images/*/*.jpg')
image_filenames = tf.gfile.Glob(image_dir)
# 相当于创建空的字典,当要查询的建对应的值不存在时赋值为[],此时的training_dataset和testing_dataset都是字典,字典的值都是列表
training_dataset = defaultdict(list)
testing_dataset = defaultdict(list)
# 文件名类似于'./Images/n02093647-Bedlington_terrier/n02093647_1558.jpg',使用split切割后的第3个元素就是n02093647-Bedlington_terrier
# 此时的image_filename_with_breed的每一项相当于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
image_filename_with_breed = map(lambda filename: (filename.split('/')[2], filename), image_filenames)
# 注意:此时的dog_breed是分组后的组名,即狗的品种,而breed_images是分组后该组的所有项,每项都是分组前image_filename_with_breed中的数据项,
# 即类似于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
# 此段处理结束后,testing_dataset和training_dataset的键都是类似于'n02111889-Samoyed'的狗的品种名,值为包含多个类似于'./Images/n02111889-Samoyed/n02111889_11502.jpg'的图片路径的列表
for dog_breed, breed_images in groupby(image_filename_with_breed, lambda x: x[0]):
for i, breed_image in enumerate(breed_images):
if i % 5 == 0:
# breed_image是类似于# 即类似于('n02111889-Samoyed', './Images/n02111889-Samoyed/n02111889_11502.jpg')样式
testing_dataset[dog_breed].append(breed_image[1])
else:
training_dataset[dog_breed].append(breed_image[1])
breed_training_count = len(training_dataset[dog_breed]) # 获取该品种训练和测试的样本数,确保比例
breed_testing_count = len(testing_dataset[dog_breed])
assert round(breed_testing_count/(breed_training_count+breed_testing_count), 2) > 0.18, 'not enought testing images'
return training_dataset, testing_dataset
# 将训练和测试样本写入tfrecord文件
def write_tfrecord_file(sess, dataset, record_location):
writer = None
current_index = 0
# 注意:使用for x in dict获取到的x只是字典dict的键,而无法获取其值,要想直接获取键值对,可以使用字典的items函数
for breed, image_filenames in dataset.items():
for image_filename in image_filenames:
if current_index % 100 == 0:
if writer:
writer.close()
record_name = '{record_location}-{current_index}.tfrecords'.format(record_location=record_location,
current_index=current_index)
writer = tf.python_io.TFRecordWriter(record_name)
current_index += 1
image_file = tf.read_file(image_filename)
try:
image = tf.image.decode_jpeg(image_file)
except:
print(image_filename)
continue
# 转换为灰度图可以减少处理的计算量和内存占用,但这不是必须的
gray_image = tf.image.rgb_to_grayscale(image)
resize_image = tf.image.resize_images(gray_image, [250, 151])
# 将此样本写入tfrecord文件
# step1:获取图像和标签的bytes格式
uint8_image = tf.cast(resize_image, tf.uint8)
image_bytes = sess.run(uint8_image).tobytes()
image_label = breed.encode('utf-8')
# step2: 创建bytes类型区域
image_bytes_list = tf.train.BytesList(value=[image_bytes])
label_bytes_list = tf.train.BytesList(value=[image_label])
# step3 :创建feature区域
image_feature = tf.train.Feature(bytes_list=image_bytes_list)
label_feature = tf.train.Feature(bytes_list=label_bytes_list)
# step4 :创建features区域
features = {'image': image_feature, 'label': label_feature}
example_feature = tf.train.Features(feature=features)
# step5:创建example区域
example = tf.train.Example(features=example_feature)
writer.write(example.SerializeToString())
writer.close()
return
with tf.Session() as sess:
train_dataset, test_dataset = get_dataset('./Images/*/*.jpg')
write_tfrecord_file(sess, test_dataset, './output/testing-images/testing-image')
write_tfrecord_file(sess, train_dataset, './output/training-images/training-image')
这段示例代码完成的任务包括:打开每幅图像,将其转换为灰度图,调整其尺寸,然后将其添加到一个TFRecord文件中。这个逻辑与之前的例子基本一致,唯一的区别是这里使用了tf.image.resize_images函数。这个尺寸调整方法会将所有图像变为相同的尺寸,即便会有扭曲发生。例如,假设有一幅纵向的图像和一幅横向的图像,若用这段代码调整两者的尺寸,则横向图像的输出将会产生扭曲。这种扭曲之所以发生,是因为tf.image.resize_images并不考虑图像的长宽比(宽度与高度的比值)。为了对一组图像进行恰当的尺寸调整,裁剪或边界填充是一种推荐的方法,因为这些方式能够保持图像的纵横比,不至于使图像产生扭曲
5.3 加载图像
一旦测试集和训练集被转换为TFRecord格式,便可按照TFRecord文件而非JPEG文件进行读取。我们的目标是每次加载少量图像及相应的标签
拓展知识1 CNN网络架构演进:从LeNet到DenseNet
http://www.cnblogs.com/skyfsm/p/8451834.html
卷积神经网络可谓是现在深度学习领域中大红大紫的网络框架,尤其在计算机视觉领域更是一枝独秀。CNN从90年代的LeNet开始,21世纪初沉寂了10年,直到12年AlexNet开始又再焕发第二春,从ZF Net到VGG,GoogLeNet再到ResNet和最近的DenseNet,网络越来越深,架构越来越复杂,解决反向传播时梯度消失的方法也越来越巧妙。
开山之作:LeNet
闪光点:定义了CNN的基本组件,是CNN的鼻祖。
LeNet是卷积神经网络的祖师爷LeCun在1998年提出,用于解决手写数字识别的视觉任务。自那时起,CNN的最基本的架构就定下来了:卷积层、池化层、全连接层。如今各大深度学习框架中所使用的LeNet都是简化改进过的LeNet-5(-5表示具有5个层),和原始的LeNet有些许不同,比如把激活函数改为了现在很常用的ReLu
王者归来:AlexNet
AlexNet在2012年ImageNet竞赛中以超过第二名10.9个百分点的绝对优势一举夺冠,从此深度学习和卷积神经网络名声鹊起,深度学习的研究如雨后春笋般出现,AlexNet的出现可谓是卷积神经网络的王者归来。
闪光点:更深的网络、数据增广、ReLU、dropout、LRN
稳步前行:ZF-Net
ZFNet是2013ImageNet分类任务的冠军,其网络结构没什么改进,只是调了调参,性能较Alex提升了不少。ZF-Net只是将AlexNet第一层卷积核由11变成7,步长由4变为2,第3,4,5卷积层转变为384,384,256。这一年的ImageNet还是比较平静的一届,其冠军ZF-Net的名堂也没其他届的经典网络架构响亮
越走越深:VGG-Nets
VGG-Nets是由牛津大学VGG(Visual Geometry Group)提出,是2014年ImageNet竞赛定位任务的第一名和分类任务的第二名的中的基础网络。VGG可以看成是加深版本的AlexNet. 都是conv layer + FC layer,在当时看来这是一个非常深的网络了,因为层数高达十多层,我们从其论文名字就知道了(《Very Deep Convolutional Networks for Large-Scale Visual Recognition》),当然以现在的目光看来VGG真的称不上是一个very deep的网络。
VGG采用的是一种Pre-training的方式,这种方式在经典的神经网络中经常见得到,就是先训练一部分小网络,然后再确保这部分网络稳定之后,再在这基础上逐渐加深。表1从左到右体现的就是这个过程,并且当网络处于D阶段的时候,效果是最优的,因此D阶段的网络也就是VGG-16了!E阶段得到的网络就是VGG-19了!VGG-16的16指的是conv+fc的总层数是16,是不包括max pool的层数!
下面这个图就是VGG-16的网络结构。
由上图看出,VGG-16的结构非常整洁,深度较AlexNet深得多,里面包含多个conv->conv->max_pool这类的结构,VGG的卷积层都是same的卷积,即卷积过后的输出图像的尺寸与输入是一致的,它的下采样完全是由max pooling来实现。
VGG网络后接3个全连接层,filter的个数(卷积后的输出通道数)从64开始,然后没接一个pooling后其成倍的增加,128、512,VGG的注意贡献是使用小尺寸的filter,及有规则的卷积-池化操作。
闪光点:卷积层使用更小的filter尺寸和间隔
大浪推手:GoogLeNet
GoogLeNet在2014的ImageNet分类任务上击败了VGG-Nets夺得冠军,其实力肯定是非常深厚的,GoogLeNet跟AlexNet,VGG-Nets这种单纯依靠加深网络结构进而改进网络性能的思路不一样,它另辟幽径,在加深网络的同时(22层),也在网络结构上做了创新,引入Inception结构代替了单纯的卷积+激活的传统操作(这思路最早由Network in Network提出)。GoogLeNet进一步把对卷积神经网络的研究推上新的高度。
具体内容可见https://my.oschina.net/u/876354/blog/1637819
闪光点:引入Inception结构、中间层的辅助LOSS单元、后面的全连接层全部替换为简单的全局平均pooling
里程碑式创新:ResNet
2015年何恺明推出的ResNet在ISLVRC和COCO上横扫所有选手,获得冠军。ResNet在网络结构上做了大创新,而不再是简单的堆积层数,ResNet在卷积神经网络的新思路,绝对是深度学习发展历程上里程碑式的事件
闪光点:层数非常深,已经超过百层、引入残差单元来解决退化问题
继往开来:DenseNet
自Resnet提出以后,ResNet的变种网络层出不穷,都各有其特点,网络性能也有一定的提升。本文介绍的最后一个网络是CVPR 2017最佳论文DenseNet,论文中提出的DenseNet(Dense Convolutional Network)主要还是和ResNet及Inception网络做对比,思想上有借鉴,但却是全新的结构,网络结构并不复杂,却非常有效,在CIFAR指标上全面超越ResNet。可以说DenseNet吸收了ResNet最精华的部分,并在此上做了更加创新的工作,使得网络性能进一步提升。
闪光点:密集连接:缓解梯度消失问题,加强特征传播,鼓励特征复用,极大的减少了参数量
具体见:https://www.sohu.com/a/161639222_114877?qq-pf-to=pcqq.c2c
拓展知识2 : TensorFlow layers模块用法
http://ju.outofmemory.cn/entry/344970
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(…): 二维深度可分离卷积层
- pooling,即池化,layers 模块提供了多个池化方法,这几个池化方法都是类似的
拓展知识3 关于tensorflow中的softmax_cross_entropy_with_logits_v2函数的区别
https://blog.csdn.net/tsyccnh/article/details/81069308
tf.nn.softmax_cross_entropy_with_logits(记为f1) 和 tf.nn.sparse_softmax_cross_entropy_with_logits(记为f3),以及
tf.nn.softmax_cross_entropy_with_logits_v2(记为f2) 之间的区别。
f1和f3对于参数logits的要求都是一样的,即未经处理的,直接由神经网络输出的数值, 比如 [3.5,2.1,7.89,4.4]。两个函数不一样的地方在于labels格式的要求,f1的要求labels的格式和logits类似,比如[0,0,1,0]。而f3的要求labels是一个数值,这个数值记录着ground truth所在的索引。以[0,0,1,0]为例,这里真值1的索引为2。所以f3要求labels的输入为数字2(tensor)。一般可以用tf.argmax()来从[0,0,1,0]中取得真值的索引。
f1和f2之间很像,实际上官方文档已经标记出f1已经是deprecated 状态,推荐使用f2。两者唯一的区别在于f1在进行反向传播的时候,只对logits进行反向传播,labels保持不变。而f2在进行反向传播的时候,同时对logits和labels都进行反向传播,如果将labels传入的tensor设置为stop_gradients,就和f1一样了。
那么问题来了,一般我们在进行监督学习的时候,labels都是标记好的真值,什么时候会需要改变label?f2存在的意义是什么?实际上在应用中labels并不一定都是人工手动标注的,有的时候还可能是神经网络生成的,一个实际的例子就是对抗生成网络(GAN)。