参考博客:https://cosx.org/2017/10/transfer-learning/
下面的例子中将示范如何将一个图像识别的深度卷积网络,VGG,迁移到识别花朵类型的新任务上,在原先的任务中,VGG 只能识别花,但是迁移学习可以让模型不但能识别花,还能识别花的具体品种。
VGG 介绍
VGG 的两个版本分别是 16 层网络版和 19 层网络版。VGG 的输入数据格式是 224 * 224 * 3 的像素数据,经过一系列的卷积神经网络和池化网络处理之后,输出的是一个 4096 维的特征数据,然后再通过 3 层全连接的神经网络处理,最终由 softmax 规范化得到分类结果。VGG模型是一个. npy 文件,本质上是一个巨大的 numpy 对象,包含了 VGG16 模型中的所有参数,该文件大约有 500M,所以可见如果是从头训练这样一个模型是非常耗时的,借助于迁移学习的思想,我们可以直接在这个模型的基础上进行训练。
在接下来的迁移学习实践中,我们会采用稍微简单的一些的 VGG16,他和 VGG19 有几乎完全一样的准确度,但是运算起来更快一些。
VGG 的结构图如下:
搭建属于自己的识花网络
整体思路:
首先我们会将所有的图片交给 VGG16,利用 VGG16 的深度网络结构中的五轮卷积网络层和池化层,对每张图片得到一个 4096 维的特征向量,然后我们直接用这个特征向量替代原来的图片,再加若干层全连接的神经网络,对花朵数据集进行训练。因此本质上,我们是将 VGG16 作为一个图片特征提取器,然后在此基础上再进行一次普通的神经网络学习,这样就将原先的 224 * 224 * 3 维度的数据转化为了 4096 维的,而每一维度的信息量大大提高,从而大大降低了计算资源的消耗,实现了把学习物体识别中得到的知识应用到特殊的花朵分类问题上。
代码存放位置:/home/wei/MyPaperCode/recognizeFlowersBaseOnDeeplearningAndTransferLearning
#导入需要用的 python 模块 import os import numpy as np import tensorflow as tf #从文件夹中导入文件 from tensorflow_vgg import vgg16 from tensorflow_vgg import utils #加载识花数据集。接下来我们将 flower_photos 文件夹中的花朵图片都载入到进来,并且用图片所在的子文件夹作为标签值。 data_dir = 'flower_photos/' #返回文件夹中,子文件及子文件夹 名称的列表,contents为['tulips', 'dandelion', 'LICENSE.txt', 'roses', 'sunflowers', 'daisy'] contents = os.listdir(data_dir) #去掉子文件的名称,只保留子文件夹的名称 classes = [each for each in contents if os.path.isdir(data_dir + each)] #classes即为['tulips', 'dandelion', 'roses', 'sunflowers', 'daisy']
#利用 VGG16 计算得到特征值 # 首先设置计算batch的值,如果运算平台的内存越大,这个值可以设置得越高 batch_size = 10 # 用codes_list来存储特征值 codes_list = [] # 用labels来存储花的类别 labels = [] # batch数组用来临时存储图片数据 batch = [] codes = None with tf.Session() as sess: # 构建VGG16模型对象 vgg = vgg16.Vgg16() #调用vgg16文件中的Vgg16() input_ = tf.placeholder(tf.float32, [None, 224, 224, 3]) with tf.name_scope("content_vgg"): # 载入VGG16模型 vgg.build(input_) # 对每个不同种类的花分别用VGG16计算特征值 for each in classes: print("Starting {} images".format(each)) class_path = data_dir + each files = os.listdir(class_path) for ii, file in enumerate(files, 1): # 载入图片并放入batch数组中 img = utils.load_image(os.path.join(class_path, file)) #此时224*224*3 batch.append(img.reshape((1, 224, 224, 3))) #添加到batch数组中 labels.append(each) # 如果图片数量到了batch_size则开始具体的运算 if ii % batch_size == 0 or ii == len(files): images = np.concatenate(batch)#沿现有轴加入数组序列 feed_dict = {input_: images} # 计算特征值 codes_batch = sess.run(vgg.relu6, feed_dict=feed_dict) # 将结果放入到codes数组中 if codes is None: codes = codes_batch else: codes = np.concatenate((codes, codes_batch)) # 清空数组准备下一个batch的计算 batch = [] print('{} images processed'.format(ii)) #以上我们就可以得到一个 codes 数组,和一个 labels 数组,分别存储了所有花朵的特征值和类别 #用如下的代码将这两个数组保存到硬盘上 with open('codes', 'w') as f: codes.tofile(f) import csv with open('labels', 'w') as f: writer = csv.writer(f, delimiter='\n') writer.writerow(labels)
以上代码讲解:
myfiles = ['A', 'B', 'C'] for nn in enumerate(myfiles, 1): #1表示序号从1开始 print(nn) for mm, myfile in enumerate(myfiles, 5): #5表示序号从5开始 print(mm) print(myfile)
输出为:
(1, 'A') (2, 'B') (3, 'C') 5 A 6 B 7 C
os.path.join()函数
语法: os.path.join(path1[,path2[,......]])
返回值:将多个路径组合后返回
注:第一个绝对路径之前的参数将被忽略
os.path.join(class_path, file)表示的是路径,比如class_path = flower_photos/tulips, file = 111.jpg时其表示为flower_photos/tulips/111.jpg
print('{} images processed'.format(ii))
往字符串'{} images processed'中传入数据,ii为50时输出为 50 images processed
#准备训练集,验证集和测试集 #首先把 labels 数组中的分类标签用 One Hot Encode 的方式替换。 from sklearn.preprocessing import LabelBinarizer lb = LabelBinarizer() lb.fit(labels) labels_vecs = lb.transform(labels)
labels为 ['tulips', 'tulips', 'tulips',......., 'daisy', 'daisy']
labels_vecs为
[[0 0 0 0 1] [0 0 0 0 1] [0 0 0 0 1] ... [1 0 0 0 0] [1 0 0 0 0] [1 0 0 0 0]]
# 接下来就是抽取数据,因为不同类型的花的数据数量并不是完全一样的,而且 labels 数组中的数据也还没有被打乱,所 # 以最合适的方法是使用 StratifiedShuffleSplit 方法来进行分层随机划分。假设我们使用训练集:验证集:测试集 # = 8:1:1,那么代码如下: from sklearn.model_selection import StratifiedShuffleSplit ss = StratifiedShuffleSplit(n_splits=1, test_size=0.2) train_idx, val_idx = next(ss.split(codes, labels)) half_val_len = int(len(val_idx)/2) val_idx, test_idx = val_idx[:half_val_len], val_idx[half_val_len:] train_x, train_y = codes[train_idx], labels_vecs[train_idx] val_x, val_y = codes[val_idx], labels_vecs[val_idx] test_x, test_y = codes[test_idx], labels_vecs[test_idx] print("Train shapes (x, y):", train_x.shape, train_y.shape)#x表示特征图,y表示labels print("Validation shapes (x, y):", val_x.shape, val_y.shape) print("Test shapes (x, y):", test_x.shape, test_y.shape)
输出为:
('Train shapes (x, y):', (2936, 4096), (2936, 5))
('Validation shapes (x, y):', (367, 4096), (367, 5))
('Test shapes (x, y):', (367, 4096), (367, 5))
next()函数: 返回迭代器的下一个项目。
# 分好了数据集之后,就可以开始对数据集进行训练了,假设我们使用一个 256 维的全连接层,一个 5 维的全连接层 #(因为我们要分类五种不同类的花朵),和一个 softmax 层。当然,这里的网络结构可以任意修改,你可以不断尝试 # 其他的结构以找到合适的结构。 # 输入数据的维度 inputs_ = tf.placeholder(tf.float32, shape=[None, codes.shape[1]]) # 标签数据的维度 labels_ = tf.placeholder(tf.int64, shape=[None, labels_vecs.shape[1]]) # 加入一个256维的全连接的层 fc = tf.contrib.layers.fully_connected(inputs_, 256) # 加入一个5维的全连接层 logits = tf.contrib.layers.fully_connected(fc, labels_vecs.shape[1], activation_fn=None) # 计算cross entropy值 cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=labels_, logits=logits) # 计算损失函数 cost = tf.reduce_mean(cross_entropy) # 采用用得最广泛的AdamOptimizer优化器 optimizer = tf.train.AdamOptimizer().minimize(cost) # 得到最后的预测分布 predicted = tf.nn.softmax(logits) # 计算准确度 correct_pred = tf.equal(tf.argmax(predicted, 1), tf.argmax(labels_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
print(shape(l))#输出l的行列值
print(l.shape[0])#输出l的行数值
print(l.shape[1])#输出l的列数值
tf.contrib.layers.fully_connected(inputs, num_outputs, activation_fn=None)这个函数就是全链接成层,inputs是输入,num_outputs是下一层单元的个数,activation_fn是激活函数