9.3 Inception模型文件导入与卷积层分析

> 导入ImageNet图像分类模型

对经典的卷积神经网络已经有了大致的了解后,接下来来学习如何导入模型,也就是把这些已经训练好的经典模型加载到我们的案例中。

> 模型的加载

TensorFlow提供了两种方式来存储和加载模型,其中一种方式是检查点文件(checkpoint file),这在前面的课程已经学习过了,这里就不再赘述,它的扩展名一般为.ckpt,通过在tf.train.Saver对象上调用Saver.save()生成,通过saver.restore()来加载。另一种方式是生成图协议文件(graph proto file),这是一个二进制文件,它的扩展名一般为.bp,用tf.train.write_graph()来保存,然后使用tf.import.graph_def()来加载图。

> 图的保存与加载

首先导入tensorflow库,然后对图进行保存。

添加一个变量v,然后启动一个会话。在这个会话里,通过tf.train.write_graph()函数写入一个图文件,“./tfmodel”是所保存的图文件的路径,“test_pb”是它的名称,扩展名是.pb,执行之后,有一个名为tfmodel的文件夹。

点进去之后,可以看到test_pb这样一个图文件:

在对图进行保存后,可以对已经保存好的图进行加载,这里打开另外一个会话,并且通过“tf.import_graph_def(graph_def,name=’tf.graph’)”语句把已经保存好的图文件导入进来,然后通过print检查是否导入了图模型。在print出来的信息里,可以看到结点(node)、结点操作(op)、值(value)、值的类型(type)。这个值和值的类型指的就是在之前的图里所定义的变量v的值和类型。

> 导入Inception模型

Deep Dream模型需要优化ImageNet模型的卷积层的某个通道或者若干个通道的激活值。因此,先在TensorFlow里导入一个ImageNet的图像识别模型。在这个案例里,以Inception模型为例进行介绍,通过图文件的方式来导入模型。

首先,需要导入一些常用的语句和基本模块。然后,再创建基本的图和会话:

以上都是一些基本的准备工作,下面开始正式地导入Inception模型。

TensorFlow提供了一种以“.pb”为扩展名的文件,即图文件,可以事先将模型导入到pb文件中,再在需要的时候进行导出。

对于Inception模型,对应的pb文件是“tensorflow_Inception_graph.pb”这个pb文件可以在下面这个网站上进行下载:

https://storage.googleapis.com/download.tensorflow.org/models/Inception5h.zip

下载好这个图文件后,把它放到根目下就可以了,然后对这个文件进行一个解析。

在导入时,需要给网络指定一个输入的图像,所以需要设置一个占位符t_input,在后面的程序中,就会把图像传递给t_input。

这里需要注意两点:

第一是图像预处理——减均值。这里的减均值是指为图像减去一个像素的均值。

为什么要减去一个像素的均值呢?因为在训练Inception模型的时候,对图像都进行了一个减均值的预处理,这里将Inception作为预训练模型,所以也应该对输入的图像进行和训练Inception的时候同样的预处理操作,也就是减去这个均值。那么这里采用Inception作为预训练模型,所以在处理输入的时候,也要与Inception保持一致,对所有的像素点都减去一个117的均值。

第二是要对图像进行增加维度的预处理。我们知道图像的数据格式一般都是像素的高度、宽度以及通道数。虽然图像的格式是这样的,但Inception模型的输入格式是四维的,他比图像多了一维batch,batch指的是一个批次里的样本数量。

为什么要给它增加一维呢?因为三维的格式只能表示一张图片,但在训练神经网络的时候,往往都需要同时输入多张图片,所以需要在前面增加一维来表示多张图片。怎样才能在原始的输入前面增加一维呢?这里用到了一个非常常用的函数——tf.expand_dims()。这个函数的第一个参数是需要增加维度的输入,第二个参数是需要增加维度的位置。这个函数的返回值是一个张量,这个张量跟input是同类型的,并且它的数据也是一样的,只不过它的shape增加了一个额外的维度,这个维度的大小为1。

可以来看一个例子:

首先定义一个t,对于这个数组t,它的shape是(2,3)。现在,以这个t作为输入,若dim=0,得到t1的shape是(1,2,3),也就是在原来shape第一个位置上增加了一个维度1。若dim=1,在中间这个位置上增加了一个维度1。若dim=2,在最后一个位置上增加了一个维度1。如果dim=-1呢?这个维度1插入到最后一个维度的地方。所以dim就表示需要插入维度1的地方,0对应第一个位置,1对应第二个位置,-1对应最后一个位置。

回到案例中,通过一个减均值的预处理以及一个增加维度的预处理之后,得到了t_preprocessed。这是最后真正送入网络的输入图像。然后通过“tf.import_graph_def(graph_def,{‘input’:t_preprocessed})”导入图模型。

在导入模型之后,需要找出模型中所有的卷积层,或是需要可视化的卷积层。

> 找到卷积层

graph.get_operations()函数是对图进行操作的函数。

> 图的基本操作

之前已经学习如何建立图、获得默认图以及重置默认图。分别通过tf.Graph()建立图、tf.get_default_graph()获得默认图、tf.reset_default_graph()重置默认图。

首先通过tf.Graph()创建新的图并把这个图设为默认图。然后在这个图中定义一个常量c1并输出c1所在的图。可以通过“变量.graph()”获得它所在的图。c1是一个张量,c1所在的图是“0x0000000004EADD8”。

接着通过tf.reset_default_graph()重置默认图。之前g是默认图,对默认图进行重置后,g2就是当前的默认图,输出后可以看到当前默认图的信息。

> 获取张量

首先输出一下之前定义的c1张量的名字,是Const:0。然后把这个张量的名字放到get_tensor_by_name()函数中,这个函数通过指定的名字来获取张量,所以需要把Const:0放到函数中。输出t后得到的信息和c1的信息是一样的,说明通过这个函数所获取的张量t就是前面定义的张量c1。

注意:不必花太多精力去关注TensorFlow中默认的命名规则。一般在需要使用名字时,都会在定义的同时为它指定好固定的名字,如果像之前添加张量c1时那样,没有给它指定名字的话,也可以通过print这个张量的name来获得它的名字,得到名字后,把它回填到代码中,作为函数的输入参数,运行之后就可以得到这个张量了。

> 获取节点操作

获取节点操作的方法跟获取张量的方法非常相似,都是先把OP的名字打印出来,然后把名字回填到函数的输入参数中,运行一下就可以了。

这里把获取张量和获取节点操作放在一起进行比较,是为了说明一个非常容易混淆的点。

首先定义a、b、tensor1,tensor1是a和b的叉乘。输出tensor1以及它的名字后可以看到,tensor1是一个张量,易混淆的地方在于大家有可能会误认为“tf.matmul(a,b,name=’example_op’)”是一个操作,其实它是一个张量。那什么是操作呢?它描述的是张量中的一种运算关系。在这里,操作节点是叉乘,而不是这条表达式,操作节点需要通过访问张量的属性找到。比如tensor1.op.name,先打印OP的名字,然后把这个名字回填到get_operation_by_name()函数中,得到了g2中的操作节点。真正的操作节点是一种运算关系,在这里就是矩阵乘法。

经过对图的基本操作的了解后,回到找出卷积层的问题中。通过get_operations()找到这个操作节点的类型是二维卷积所有的卷积层。

输出的层数是59,即找到了59个卷积层,59是layers这个列表的长度。如果想知道有哪59个卷积层,也可以通过“print(layers)”把它们输出。

还可以对其中的卷积层进行输出,来获得更详细的信息。

这里指定了两个卷积层,对它们的shape分别进行输出。

第一个卷积层的shape是(?,?,?,144)。问号代表什么呢?卷积层的格式一般是批量中样本的数量、像素的高、像素的宽以及通道数。因为现在还没有真正输入图像,所以并不清楚输入图像的个数以及它的尺寸,就用问号表示。这里的通道数(144)是固定的,因为Inception是已经预训练好的模型,所以它的结构、通道数都是固定的。除了输出这两个卷积层之外,还可以尝试打印其他卷积层的形状。

下面就以name1的这个卷积层为例,最大化它的某一通道以及所有通道的平均值来达到生成DeepDream图像的目的。

> 示例代码

图的保存与加载.py
import tensorflow as tf

# 图的保存
v = tf.Variable(1.0, name='my_variable')
with tf.Session() as sess:
    tf.train.write_graph(sess.graph_def, './tfmodel', 'test_pb.pb' , as_text=False)
    
# 图的加载
with tf.Session() as sess:
    with tf.gfile.FastGFile( './tfmodel/test_pb.pb', 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
        sess.graph.as_default()
        tf.import_graph_def(graph_def,name='tf.graph')
        print(graph_def)
tf.expand_dims()使用示例.py
import tensorflow as tf
import numpy as np

#tf.expand_dims(input, dim, name=None)
t = [[2,3,3],[1,5,5]]
print("t_shape:",np.shape(t))

t1 = tf.expand_dims(t, 0)
print("t1_shape:",np.shape(t1))

t2 = tf.expand_dims(t, 1)
print("t2_shape:",np.shape(t2))

t3 = tf.expand_dims(t, 2)
print("t3_shape:",np.shape(t3))

t4 = tf.expand_dims(t, -1)
print("t4_shape:",np.shape(t4))
创建图、重置和获得默认图.py
import numpy as np
import tensorflow as tf 

g = tf.Graph() # 创建新的图
with  g.as_default(): 
  c1 = tf.constant(0.0) # 在新图中添加变量
  print(c1)
  print("c1.graph:", c1.graph) # 可通过变量的'.graph'可获得其所在的图
  
tf.reset_default_graph() # 重置默认图
g2 =  tf.get_default_graph() # 获得默认图
print("g2:", g2)
获取张量.py
#先获取张量的名字
print(c1.name) 

# 然后将张量名字放到tf.Graph().get_tensor_by_name(name = "")中
t = g.get_tensor_by_name(name = "Const:0") 
# 通过打印t验证get_tensor_by_name所获取的张量就是前面定义的张量c1
print(t)
获取节点操作.py
a = tf.constant([[1.0, 2.0]])
b = tf.constant([[1.0], [3.0]])

tensor1 = tf.matmul(a, b, name='example_op')
print(tensor1) 
print(tensor1.name)

#先将op的名字打印出来
print(tensor1.op.name)

#然后使用get_operation_by_name函数
test_op = g2.get_operation_by_name("example_op")
print(test_op)

Last updated