『TensorFlow』迁移学习_他山之石,可以攻玉_V2
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『TensorFlow』迁移学习_他山之石,可以攻玉_V2相关的知识,希望对你有一定的参考价值。
絮絮叨叨
开学以来课业繁重(虽然也不怎么认真听讲...)一直没怎么闲着,一直没写什么长的代码,正巧好几件事赶在一块了:导师催着我把超新星检测的神经网络实现,我不想用现有的demo(当然一时间也没有合适的,之前写的AlexNet不太喜欢,毕竟是个老掉牙的网络架构,很多结构今天看来并不漂亮),而且分类之后需要考虑扩充为检测网络;和武师兄一起参加了个遥感影像识别的比赛,由于我对遥感是一窍不通而且也没什么兴趣学(逃),所以基本上要做的也是利用tensorflow搭建DL网络了,正好也需要一个图片处理的DL架构,不过这里需要我去额外学习一下语义分割了;这次的十一假期真的挺长,天文系的筒子们都回家了,新班级除了宿舍的熟人也不多,正是宅学校的好时机。于是就利用这个假期自己写了个迁移学习的类,蛮大的架构(原谅我见识少)完全自己来设计机会还真是不多,搭建的速度很慢,而且遇到了不少问题,当然过程是曲折的,结果算是圆满的,至此,分类部分是搭建完了,还是有蛮多体会的,对tensorflow以及编程思想什么的(玄学大师),记录一下,算是不愧对这个假期233
简介
1.这暂时是个基于vgg16(可更换模型)的迁移学习的分类网络,之后会考虑升级语义分割部分以适应之后的任务,不过应该是很久以后了(因为太难...)。
2.除了分类外,它可以做一部分图像预处理的活,暂时是批量图片尺寸调整(特意升级成了三线程加速,飞一般感觉),以后可能会升级为图像增强模块(因为现在的数据已经被师兄做了扩充,所以暂时用不到增强,也就没加)。
3.这是个人的一点执念了,前几天开始写的时候我对它的期望是(程序本体)简洁,(调用方式)简单,现在我发现基本不可能同时实现了...,我选择了前者,虽然是一个class,实际上各个方法之间基本是线性的,交叉调用很少,数据流基本也就只有一个方向,函数也写的简单易懂(当然和我菜也有关系),相对的,main的调用部分看起来又臭又长,不过套路模式很固定,我感觉还是不错的。
思路是这样的,对于一批图片,
- 我们首先批量裁剪,裁剪为网络需要的大小
- 然后送入预训练好的模型中,完成特征提取(实际就是个数据降维),写入新的二进制文件
- 自写一个分类头(实际就是个单层的神经网络),使用降维特征及标签进行训练
整个流程中我们使用字典来标记数据和标签,作为各个方法之间交流的格式。
代码
对于我的二分类问题,下面的程序会生成如下的目录架构,
其中vgg16文件夹和image文件夹以及其中文件(预训练模型和训练数据)是需要自己事先准备的(废话),其他的都由程序自己生成。
程序如下,
import os import glob import pprint import datetime import threading import numpy as np import tensorflow as tf import matplotlib.pyplot as plt os.environ[‘TF_CPP_MIN_LOG_LEVEL‘] = ‘2‘ class image_process(): def __init__(self, train_path = ‘./image/SNRDetect/train‘, test_path=‘./image/SNRDetect/val‘): # train0_path = ‘./image/SNRDetect/train/0‘, # train1_path = ‘./image/SNRDetect/train/1‘, # val0_path = ‘./image/SNRDetect/val/0‘, # val1_path = ‘./image/SNRDetect/val/1‘): ‘‘‘ 读入图片名称,每个类别图片名称以一维numpy数组形式存储 为了保证扩展性,本class下各方法尽量使用文件名array作为参数,而不去直接调用内置属性 目录给到分类一级(即path的下一级目录是类别名,里面是该类的文件) ‘‘‘ # self.dict = {} # self.dict[‘train0_name‘] = [train0_path + ‘/‘ + name for name in np.squeeze([name[2] for name in os.walk(train0_path)])] # self.dict[‘train1_name‘] = [train1_path + ‘/‘ + name for name in np.squeeze([name[2] for name in os.walk(train1_path)])] # self.dict[‘val0_name‘] = [val0_path + ‘/‘ + name for name in np.squeeze([name[2] for name in os.walk(val0_path)])] # self.dict[‘val1_name‘] = [val1_path + ‘/‘ + name for name in np.squeeze([name[2] for name in os.walk(val1_path)])] # map(np.random.shuffle, [self.dict[‘train0_name‘], self.dict[‘train1_name‘], self.dict[‘val0_name‘], self.dict[‘val1_name‘]]) # np.random.shuffle(self.dict[‘train0_name‘]) # np.random.shuffle(self.dict[‘train1_name‘]) # np.random.shuffle(self.dict[‘val0_name‘]) # np.random.shuffle(self.dict[‘val1_name‘]) dir_train = [f[0] for f in [file for file in os.walk(train_path)][1:]] file_train = [f[2] for f in [file for file in os.walk(train_path)][1:]] self.train_dict = {} for k,v in zip(dir_train,file_train): np.random.shuffle(v) self.train_dict[k.split(‘\\\\‘)[-1]] = v dir_test = [f[0] for f in [file for file in os.walk(test_path)][1:]] file_test = [f[2] for f in [file for file in os.walk(test_path)][1:]] self.test_dict = {} for k,v in zip(dir_test,file_test): np.random.shuffle(v) self.test_dict[k.split(‘\\\\‘)[-1]] = v def reload_dict(self, train_path = ‘./image/SNRDetect/train‘, test_path=‘./image/SNRDetect/val‘): dir_train = [f[0] for f in [file for file in os.walk(train_path)][1:]] file_train = [f[2] for f in [file for file in os.walk(train_path)][1:]] self.train_dict = {} for k,v in zip(dir_train,file_train): np.random.shuffle(v) self.train_dict[k.split(‘\\\\‘)[-1]] = v dir_test = [f[0] for f in [file for file in os.walk(test_path)][1:]] file_test = [f[2] for f in [file for file in os.walk(test_path)][1:]] self.test_dict = {} for k,v in zip(dir_test,file_test): np.random.shuffle(v) self.test_dict[k.split(‘\\\\‘)[-1]] = v # def re_load(self, # train0_path = ‘./image/SNRDetect/train/0‘, # train1_path = ‘./image/SNRDetect/train/1‘, # val0_path = ‘./image/SNRDetect/val/0‘, # val1_path = ‘./image/SNRDetect/val/1‘): # self.dict[‘train0_name‘] = [train0_path + ‘/‘ + name for name in # np.squeeze([name[2] for name in os.walk(train0_path)])] # self.dict[‘train1_name‘] = [train1_path + ‘/‘ + name for name in # np.squeeze([name[2] for name in os.walk(train1_path)])] # self.dict[‘val0_name‘] = [val0_path + ‘/‘ + name for name in # np.squeeze([name[2] for name in os.walk(val0_path)])] # self.dict[‘val1_name‘] = [val1_path + ‘/‘ + name for name in # np.squeeze([name[2] for name in os.walk(val1_path)])] # map(np.random.shuffle, # [self.dict[‘train0_name‘],self.dict[‘train1_name‘],self.dict[‘val0_name‘],self.dict[‘val1_name‘]]) def resize_img(self, name_list, img_path=‘./image/SNRDetect/train/0‘, file_name=‘test/0‘, size=(224,224)): self._name_list = name_list def resize_img_thread(): print(‘图片尺寸调整子线程(代号{})启动‘.format(threading.current_thread().name)) if not os.path.exists(‘resize_img‘): os.mkdir(‘./resize_img‘) if not os.path.exists(‘resize_img/train‘): os.mkdir(‘./resize_img/train‘) if not os.path.exists(‘resize_img/test‘): os.mkdir(‘./resize_img/test‘) # if not os.path.exists(‘./resize_img/{}‘.format(file_name.split(‘/‘)[-1])): if not os.path.exists(‘./resize_img/{}‘.format(file_name)): os.mkdir(‘./resize_img/{}‘.format(file_name)) with tf.Session() as sess: for img in self._name_list: self._name_list.remove(img) print(‘\\r‘+img) print(os.path.join(img_path,img)) img_raw = tf.gfile.FastGFile(os.path.join(img_path,img), ‘rb‘).read() img_0 = tf.image.decode_jpeg(img_raw) img_1 = tf.image.resize_images(img_0, size, method=3) re_img = np.asarray(sess.run(img_1),dtype=‘uint8‘) # print(img.split(‘/‘)[-1].split(‘.‘)) with tf.gfile.FastGFile(‘./resize_img/{2}/{0}_{1}.jpeg‘.format( img.split(‘/‘)[-1].split(‘.‘)[0], size[0], file_name) ,‘wb‘) as f: f.write(sess.run(tf.image.encode_jpeg(re_img))) f.flush() # 多线程调整图片尺寸 threads = [] t1 = threading.Thread(target=resize_img_thread, name=‘resize_img_thread:0‘) threads.append(t1) t2 = threading.Thread(target=resize_img_thread, name=‘resize_img_thread:1‘) threads.append(t2) t3 = threading.Thread(target=resize_img_thread, name=‘resize_img_thread:2‘) threads.append(t3) for t in threads: t.start() for t in threads: t.join() def bottlenecks_tensor_write(self, name_list, img_path=‘./image/SNRDetect/train/0‘, file_name = ‘train/0‘, model_name = ‘vgg16/vgg16.tfmodel‘): ‘‘‘ 张量瓶颈层文件写入方法,路径为:bottleneck/类名/文件名 :param name_list: 图片名称列表 :param img_path: 图片路径(对应上面的列表) :param file_name: 保存张量的文件名称 :param model_dir: 模型文件夹 :param model_name: 模型文件名 :return: ‘‘‘ self._name_list = name_list if not os.path.exists(‘bottleneck‘): os.mkdir(‘./bottleneck‘) if not os.path.exists(‘bottleneck/train‘): os.mkdir(‘./bottleneck/train‘) if not os.path.exists(‘bottleneck/test‘): os.mkdir(‘./bottleneck/test‘) if not os.path.exists(‘./bottleneck/{}‘.format(file_name)): os.mkdir(‘./bottleneck/{}‘.format(file_name)) def bottlenecks_tensor_get_thread(): print(‘瓶颈层张量获取子线程(代号{})启动‘.format(threading.current_thread().name)) Pp = pprint.PrettyPrinter() g = tf.Graph() with g.as_default(): with open(os.path.join(model_name),‘rb‘) as f: # 阅读器上下文 graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) # 获取输入层tensor和瓶颈层tensor image_input_tensor, bottleneck_tensor = tf.import_graph_def(graph_def, return_elements=[‘images:0‘, ‘MatMul:0‘]) #‘conv5_3/conv5_3:0‘]) # Pp.pprint(g.get_operations()) with tf.Session() as sess: Pp.pprint(self._name_list) for img in self._name_list: self._name_list.remove(img) print(‘\\r‘ + os.path.join(img_path,img)) image_data = open(os.path.join(img_path,img),‘rb‘).read() # 防止array被压缩 np.set_printoptions(threshold=np.nan) # 张量对象是不能作为feed对象的,而且维度必须是4维 bottleneck_values = sess.run(bottleneck_tensor, feed_dict={image_input_tensor:sess.run(tf.expand_dims( tf.image.decode_jpeg(image_data),axis=0))}) # bottleneck_string = ‘,‘.join(str(x) for x in bottleneck_values) # with open(‘./bottleneck/{1}/{0}.txt‘.format( # img.split(‘/‘)[-1].split(‘.‘)[0], file_name) ,‘w‘) as f: # f.write(bottleneck_string) # f.flush() print(‘./bottleneck/{1}/{0}‘.format(img.split(‘/‘)[-1].split(‘.‘)[0], file_name)) np.save(‘./bottleneck/{1}/{0}‘.format(img.split(‘/‘)[-1].split(‘.‘)[0], file_name),bottleneck_values) # 多线程书写张量文件 threads = [] t1 = threading.Thread(target=bottlenecks_tensor_get_thread, name=‘bottlenecks_tensor_get_thread:0‘) threads.append(t1) t2 = threading.Thread(target=bottlenecks_tensor_get_thread, name=‘bottlenecks_tensor_get_thread:1‘) threads.append(t2) t3 = threading.Thread(target=bottlenecks_tensor_get_thread, name=‘bottlenecks_tensor_get_thread:2‘) threads.append(t3) for t in threads: t.start() for t in threads: t.join() def bottlenecks_dict_get(self, path = ‘C:\\Projects\\python3_5\\SNR_DET\\\\bottleneck‘): ‘‘‘ 生成bottlenecks文件字典{类名:[文件名]} :param path: :return: ‘‘‘ dir = [f[0] for f in [file for file in os.walk(path)][1:]] file = [f[2] for f in [file for file in os.walk(path)][1:]] self.bottlenecks_dict = {} for k,v in zip(dir, file): np.random.shuffle(v) if v != []: self.bottlenecks_dict[k.split(‘\\\\‘)[-1]] = v def bottlenecks_tensor_get(self, dict, batch_size, train=True, base_path=‘./‘): ‘‘‘ 随机获取batch_size的瓶颈层张量 :param dict: :param batch_size: :param base_path: :return: 两个list,张量list和标签list[array(one_hot编码)] ‘‘‘ bottlenecks = [] ground_truths = [] n_class = len(dict) for i in range(batch_size): label_index = np.random.randint(n_class) label_name = list(dict.keys())[label_index] bottlenecks_tensor_name = np.random.choice(dict[label_name]) if train == True: train_or_test = ‘train‘ else: train_or_test = ‘test‘ bottleneck_string = np.load(os.path.join(base_path, ‘bottleneck‘, train_or_test, label_name, bottlenecks_tensor_name)) bottlenecks.append(bottleneck_string) ground_truth = np.zeros(n_class,dtype=np.float32) ground_truth[label_index] = 1.0 ground_truths.append(ground_truth) return bottlenecks, ground_truths def class_head(self, n_class=2,batch_size=100,steps=20000): ‘‘‘ 分类头 :param n_class: :return: ‘‘‘ # 添加占位符 X = tf.placeholder(tf.float32,[None,None],name=‘BottleneckInputPlaceholder‘) y = tf.placeholder(tf.float32,[None,n_class],name=‘label‘) # 全连接层 with tf.name_scope(‘final_train_ops‘): Weights = tf.Variable(tf.truncated_normal([4096,n_class],stddev=0.001)) biases = tf.Variable(tf.zeros([n_class])) logits = tf.matmul(X,Weights) + biases final_tensor = tf.nn.softmax(logits) # 交叉熵损失函数 cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=y)) # 优化器 train_step = tf.train.GradientDescentOptimizer(0.001).minimize(cross_entropy) # 准确率计算 with tf.name_scope(‘evaluation‘): correct_prediction = tf.equal(tf.argmax(final_tensor,1),tf.argmax(y,1)) evaluation_step = tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) self.bottlenecks_dict_get() for i in range(steps): batch_X, batch_y = self.bottlenecks_tensor_get(self.bottlenecks_dict, batch_size, base_path=‘./‘) sess.run(train_step,feed_dict={X:np.squeeze(np.asarray(batch_X)), y:batch_y}) if i % 500 == 0: print(‘当前为第{0}轮,\\r误差值为:‘.format(i), sess.run(cross_entropy,feed_dict={X:np.squeeze(np.asarray(batch_X)), y:batch_y})) print(‘准确率为:{:.2f}%‘.format( sess.run(evaluation_step,feed_dict={X:np.squeeze(np.asarray(batch_X))*100, y:batch_y}))) def graph_view(self, log_dir = ‘logs‘, model_name = ‘vgg16/vgg16.tfmodel‘): ‘‘‘可视化图,并输出节点‘‘‘ Pp = pprint.PrettyPrinter() g = tf.Graph() with g.as_default(): with open(model_name,‘rb‘) as f: # 阅读器上下文 graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def) Pp.pprint(g.get_operations()) with tf.Session() as sess: summary_writer = tf.summary.FileWriter(log_dir,sess.graph) if __name__==‘__main__‘: SNR = image_process() start_time = datetime.datetime.now() ‘‘‘ 图片裁剪 图片裁剪时建议注释掉本部分之后的程序,在裁剪完成后注释掉本部分单独运行后面的瓶颈张量部分 另注,本方法保存目录一级名称必须是train或者test中的一个,二级目录自取,建议使用类名 ‘‘‘ SNR.resize_img(SNR.train_dict[‘0‘],img_path=‘./image/SNRDetect/train/0‘,file_name=‘train/0‘, size=(224,224)) SNR.resize_img(SNR.train_dict[‘1‘],img_path=‘./image/SNRDetect/train/1‘,file_name=‘train/1‘, size=(224,224)) SNR.resize_img(SNR.test_dict[‘0‘],img_path=‘./image/SNRDetect/val/0‘,file_name=‘test/0‘, size=(224,224)) SNR.resize_img(SNR.test_dict[‘1‘],img_path=‘./image/SNRDetect/test/1‘,file_name=‘test/1‘, size=(224,224)) ‘‘‘ 瓶颈层张量获取 ‘‘‘ # 重载裁剪后的图片 # SNR.reload_dict(train_path=‘C:/Projects/python3_5/SNR_DET/resize_img/train‘, # test_path=‘C:/Projects/python3_5/SNR_DET/resize_img/test‘) # 瓶颈层张量文件写入 # SNR.bottlenecks_tensor_write( # SNR.train_dict[‘0‘], # img_path=‘./resize_img/train/0‘, # file_name=‘train/0‘, # model_name=‘vgg16/vgg16.tfmodel‘) # SNR.bottlenecks_tensor_write( # SNR.train_dict[‘1‘], # img_path=‘./resize_img/train/1‘, # file_name=‘train/1‘, # model_name=‘vgg16/vgg16.tfmodel‘) # SNR.bottlenecks_tensor_write( # SNR.test_dict[‘0‘], # img_path=‘./resize_img/test/0‘, # file_name=‘test/0‘, # model_name=‘vgg16/vgg16.tfmodel‘) # SNR.bottlenecks_tensor_write( # SNR.test_dict[‘1‘], # img_path=‘./resize_img/test/1‘, # file_name=‘test/1‘, # model_name=‘vgg16/vgg16.tfmodel‘) # 瓶颈层张量文件字典获取 # SNR.bottlenecks_dict_get() # 利用瓶颈层张量文件和字典训练分类器 # SNR.class_head(n_class=2) end_time = datetime.datetime.now() time = end_time - start_time print(‘\\n总计耗时{}‘.format(time))
注意一下,main部分中大块的注释不是废弃代码,是因为我pycharm的设置,导致不能一次运行全部,实际我的操作是先运行图片裁剪(后面的注释掉,如上面所示),然后注释掉裁剪部分,取消注释下面的部分,重新运行,训练分类器。
简单介绍一下几个之前用的不太熟的函数
os.walk()
[i for i in os.walk(‘C:\\Projects\\python3_5\\Gephi‘)] Out[6]: [(‘C:\\\\Projects\\\\python3_5\\\\Gephi‘, [‘.ipynb_checkpoints‘], [‘17级学硕导师情况.csv‘, ‘17级学硕导师情况.xlsx‘, ‘bear.py‘, ‘bear.txt‘, ‘csv_init.py‘, ‘EuroSiS Generale Pays.gexf‘, ‘kmeans.py‘, ‘lesmiserables.gml‘, ‘network_x.py‘, ‘pd_nx_test.py‘, ‘result.csv‘, ‘result.txt‘, ‘Untitled.ipynb‘, ‘西游记.csv‘]), (‘C:\\\\Projects\\\\python3_5\\\\Gephi\\\\.ipynb_checkpoints‘, [], [‘Untitled-checkpoint.ipynb‘])]
首先,它返回一个迭代器,其次,每一层(tuple)有三个元素(list),如下:
[
([本层目录名],
[本目录下的目录],
[本目录下的文件]),
... ...
]
os.mkdir()
这个函数一次只能建立一级目录,所以想要建立多级目录需要连用,
os.mkdir(‘./file1‘)
os.mkdir(‘./file1/file2‘)
另外经常和os.path.exists()连用。
更新v1
好吧,我得承认,我愚蠢了,实际上os.makedirs()是可以同时创建多级目录的,
os.makedirs(‘./1/2‘)
以上是关于『TensorFlow』迁移学习_他山之石,可以攻玉_V2的主要内容,如果未能解决你的问题,请参考以下文章
美团云TensorFlow 迁移学习识花实战案例(Transfer Learning)