— 到目前为止,对于模型的设计和训练,读者可能已经较为熟悉,如果读者已经能够使用设计出的模型进行训练并取得较好的结果,那么,恭喜你,你对Tensorflowd程序的编写已经可以说更上了一层台阶。
— 但是在实际工程或者商业使用中,模型的训练并不都是由程序设计人员独立训练,而是通过复用已有的神经网络模型,导入已训练好的权重数据,从而实现图像的分解的目的。
— VGGNet是最常用的深度学习模型,在各种图片分类和更深一步的语义识别、图像分割上都有好的表现,因此作为最常用的深度学习基础模型被大量采用。
对于复用的VGG模型(在imagenet进行训练的模型),首先第一步是要获得相应的权重文件和对应的分类文件,读者可以在以下地址下载相应的文件。
权重文件:http://www.cs.toronto.edu/~frossard/vgg16/vgg16_weights.npz
分类文件:http://www.cs.toronto.edu/~frossard/vgg16/imagenet_classes.py
对于下载下的vgg16_weights.npz文件的说明,需要了解npz文件格式是Numpy包中自带的一种专用的二进制文件存储格式,并且Numpy提供了很多种存取其内容的文件操作函数,可通过自带的load函数对其进行载入,之后将其作为字典赋值给vgg_dict变量,而对其读取可以使用类似字典的方式进行。
import numpy as np
vgg_dict = np.load('./vgg16_weights.npz')
print(vgg_dict.keys())
print(vgg_dict["conv1_1_W"])
对于复用模型来说,最关键的一步就是复用其中已训练好的权重参数,而通过load方式,可以将其中所包含的数据以字典的形式读出,之后根据参数的不同予以载入。
1. 第一步:定义VGGNet的复用类
首先是对于类的定义,前面已经说过,如果想要复用已经训练完毕的权重参数,则需要在模型中将其作为参数输入。而类中输入参数的方式就是将参数在整个类中共享,故在类的初始化中加入一个全局列表,将所需要共享的参数加载至类中。代码如下:
def __init__(self, imgs):
self.parameters = [] ##关键语句
self.imgs = imgs
self.convlayers()
self.fc_layers()
self.probs = tf.nn.softmax(self.fc8)
init中定义的参数与自训练的类中的相同,但是多设置了一个parameter列表,其作用就是将各个层产生的数据以列表元素的方式加载至其中。
def conv(self,name, input_data, out_channel):
in_channel = input_data.get_shape()[-1]
with tf.variable_scope(name):
kernel = tf.get_variable("weights", [3, 3, in_channel, out_channel], dtype=tf.float32)
biases = tf.get_variable("biases", [out_channel], dtype=tf.float32)
conv_res = tf.nn.conv2d(input_data, kernel, [1, 1, 1, 1], padding="SAME")
res = tf.nn.bias_add(conv_res, biases)
out = tf.nn.relu(res, name=name)
self.parameters += [kernel, biases] ##关键语句
return out
同样在卷积层方法定义时,在所有参数定义后,需要一个将参数加载到对应的列表中的方法,即使用self.parameters += [kernel, biases]将定义的卷积核和偏置值输入值参数列表中,对于全连接层的定义也一样。
def fc(self,name,input_data,out_channel):
shape = input_data.get_shape().as_list()
if len(shape) == 4:
size = shape[-1] * shape[-2] * shape[-3]
else:size = shape[1]
input_data_flat = tf.reshape(input_data,[-1,size])
with tf.variable_scope(name):
weights = tf.get_variable(name="weights",shape=[size,out_channel],dtype=tf.float32)
biases = tf.get_variable(name="biases",shape=[out_channel],dtype=tf.float32)
res = tf.matmul(input_data_flat,weights)
out = tf.nn.relu(tf.nn.bias_add(res,biases))
self.parameters += [weights, biases] ##关键语句
return out
而池化层和saver方法对于类的定义没有影响,因此使用之前定义即可,即:
def saver(self):
return tf.train.Saver()
def maxpool(self,name,input_data):
out = tf.nn.max_pool(input_data,[1,2,2,1],[1,2,2,1],padding="SAME",name=name)
return out
最后一个非常重要的方法就是将获取的权重参数重载入VGGNet模型中,代码如下:
def load_weights(self, weight_file, sess):
weights = np.load(weight_file)
keys = sorted(weights.keys())
for i, k in enumerate(keys):
sess.run(self.parameters[i].assign(weights[k]))
print("-----------all done---------------")
这里首先使用np.load方法载入权重文件,之后对获得的字典值进行排序,之后使用一个enumerate方法将数据迭代出,这里迭代的结果是序号以及key值,之后执行一个赋值操作将对应的权重值赋值到参数列表中。
完整代码
保存为VGG16_model.py文件
import numpy as np
import tensorflow as tf
import global_variable
class vgg16:
def __init__(self, imgs):
self.parameters = []
self.imgs = imgs
self.convlayers()
self.fc_layers()
self.probs = tf.nn.softmax(self.fc8)
def saver(self):
return tf.train.Saver()
def maxpool(self,name,input_data):
out = tf.nn.max_pool(input_data,[1,2,2,1],[1,2,2,1],padding="SAME",name=name)
return out
def conv(self,name, input_data, out_channel):
in_channel = input_data.get_shape()[-1]
with tf.variable_scope(name):
kernel = tf.get_variable("weights", [3, 3, in_channel, out_channel], dtype=tf.float32)
biases = tf.get_variable("biases", [out_channel], dtype=tf.float32)
conv_res = tf.nn.conv2d(input_data, kernel, [1, 1, 1, 1], padding="SAME")
res = tf.nn.bias_add(conv_res, biases)
out = tf.nn.relu(res, name=name)
self.parameters += [kernel, biases]
return out
def fc(self,name,input_data,out_channel):
shape = input_data.get_shape().as_list()
if len(shape) == 4:
size = shape[-1] * shape[-2] * shape[-3]
else:size = shape[1]
input_data_flat = tf.reshape(input_data,[-1,size])
with tf.variable_scope(name):
weights = tf.get_variable(name="weights",shape=[size,out_channel],dtype=tf.float32)
biases = tf.get_variable(name="biases",shape=[out_channel],dtype=tf.float32)
res = tf.matmul(input_data_flat,weights)
out = tf.nn.relu(tf.nn.bias_add(res,biases))
self.parameters += [weights, biases]
return out
def convlayers(self):
# zero-mean input
#conv1
self.conv1_1 = self.conv("conv1re_1",self.imgs,64)
self.conv1_2 = self.conv("conv1_2",self.conv1_1,64)
self.pool1 = self.maxpool("poolre1",self.conv1_2)
#conv2
self.conv2_1 = self.conv("conv2_1",self.pool1,128)
self.conv2_2 = self.conv("convwe2_2",self.conv2_1,128)
self.pool2 = self.maxpool("pool2",self.conv2_2)
#conv3
self.conv3_1 = self.conv("conv3_1",self.pool2,256)
self.conv3_2 = self.conv("convrwe3_2",self.conv3_1,256)
self.conv3_3 = self.conv("convrew3_3",self.conv3_2,256)
self.pool3 = self.maxpool("poolre3",self.conv3_3)
#conv4
self.conv4_1 = self.conv("conv4_1",self.pool3,512)
self.conv4_2 = self.conv("convrwe4_2",self.conv4_1,512)
self.conv4_3 = self.conv("conv4rwe_3",self.conv4_2,512)
self.pool4 = self.maxpool("pool4",self.conv4_3)
#conv5
self.conv5_1 = self.conv("conv5_1",self.pool4,512)
self.conv5_2 = self.conv("convrwe5_2",self.conv5_1,512)
self.conv5_3 = self.conv("conv5_3",self.conv5_2,512)
self.pool5 = self.maxpool("poorwel5",self.conv5_3)
def fc_layers(self):
self.fc6 = self.fc("fc1", self.pool5, 4096)
self.fc7 = self.fc("fc2", self.fc6, 4096)
self.fc8 = self.fc("fc3", self.fc7, 1000)
def load_weights(self, weight_file, sess):
weights = np.load(weight_file)
keys = sorted(weights.keys())
for i, k in enumerate(keys):
sess.run(self.parameters[i].assign(weights[k]))
print("-----------all done---------------")
程序中各个层次的定义和模型中设计相同,self.imgs是输入数据,而self.fc8是结果的最终输出值。
2. 第二步:定义模型的使用和权重的载入
对于模型的使用,可以直接通过实现类中所定义的方法进行。由于无需对模型进行重新定义,因此可以通过对数据的输入而直接获得结果。test.py文件,代码如下:
import numpy as np
import tensorflow as tf
from scipy.misc import imread, imresize
import VGG16_model as model
from imagenet_classes import class_names
if __name__ == '__main__':
imgs = tf.placeholder(tf.float32, [None, 224, 224, 3])
vgg = model.vgg16(imgs)
prob = vgg.probs
sess = tf.Session()
vgg.load_weights("./vgg16_weights.npz", sess)
img1 = imread('001.jpg', mode='RGB')
img1 = imresize(img1, (224, 224))
prob = sess.run(vgg.probs, feed_dict={vgg.imgs: [img1]})[0]
preds = (np.argsort(prob)[::-1])[0:5]
for p in preds:
print(class_names[p], prob[p])
首先获得了VGGNet模型的初始化,在初始化过程中就将需要分辨的图载入,之后获取模型的预测值,最后是对分类结果的打印,计算结果生成一系列key值和概率的对应表,笔者取前5个最大可信的概率。
这是一张波斯猫的图片,将其输入到模型中,其最终验证结果如下:
可以看到图中的波斯猫的概率被确认为0.999703,因此可以确定VGGNet在此的复用是有效的。
如果对使用后的VGGNet模型进行保存,则需要获得相应的Saver类。这里读者可能已经注意到,在设计VGGNet类的时候,已经在其中定义了Saver类,因此可以在其中直接载入数据后对模型进行保存即可。
import tensorflow as tf
import VGG16_model as model
import global_variable
if __name__ == '__main__':
imgs = tf.placeholder(tf.float32, [None, 224, 224, 3])
vgg = model.vgg16(imgs)
prob = vgg.probs
saver = vgg.saver()
sess = tf.Session()
vgg.load_weights("./vgg16_weights.npz", sess)
saver.save(sess, global_variable.save_path)
而对于复用已经保存好的Tensorflow格式,则可以使用重新定义类之后在其中对整个模型图进行载入的方法进行,其代码如下:
import numpy as np
import tensorflow as tf
import global_variable
import VGG16_model as model
from imagenet_classes import class_names
from scipy.misc import imread, imresize
imgs = tf.placeholder(tf.float32, [None, 224, 224, 3])
vgg = model.vgg16(imgs)
saver = vgg.saver()
sess = tf.Session()
saver.restore(sess, global_variable.save_path)
img1 = imread('001.jpg', mode='RGB')
img1 = imresize(img1, (224, 224))
prob = sess.run(vgg.probs, feed_dict={vgg.imgs: [img1]})[0]
preds = (np.argsort(prob)[::-1])[0:5]
for p in preds:
print(class_names[p], prob[p])
具体用法与之前的用法一样,这里不再赘述。