美文网首页tensorflow实战
ResNet-V1-50卷积神经网络迁移学习进行不同品种的花的分

ResNet-V1-50卷积神经网络迁移学习进行不同品种的花的分

作者: YinliX | 来源:发表于2018-12-12 14:40 被阅读0次

    运行环境

    python3.6.3、tensorflow1.10.0
    Intel@AIDevCloud:Intel Xeon Gold 6128 processors集群

    数据和模型来源

    数据集:http://download.tensorflow.org/example_images/flower_photos.tgz
    模型:http://download.tensorflow.org/models/resnet_v1_50_2016_08_28.tar.gz

    思路

    数据集分析及处理

    数据集文件解压后共有五个文件夹,每个文件夹都包含一定数量的花的图片,一个文件夹对应一个品种,图片各种尺寸都有,均为jpg格式,均为彩色图片。这里利用tensorflow提供的图片处理工具将所有图片转为300×300×3的格式,然后将所有图片的80%当作训练集,10%当作验证集,10%当作测试集,并且将训练集进行随机打乱,将得到的数据存在一个numpy文件中,以待后续训练使用。

    模型构建

    这里采用了ResNet-V1-50卷积神经网络来进行训练,模型结构在slim中都提供好了,另外采用官方已经训练好的参数进行迁移学习,只是在模型的最后根据问题的实际需要再定义一层输出层,只训练最后的自定义的全连接输出层的参数,训练500次,每次batch样本数取32,学习率取0.0001。

    源代码

    load_data.py
    # -*- coding: UTF-8 -*-
    #Author:Yinli
    
    import glob
    import os.path
    import numpy as np
    import tensorflow as tf
    from tensorflow.python.platform import gfile
    
    #定义输入文件夹和数据存储文件名
    INPUT_DATA = 'flower_photos'
    OUTPUT_FILE = 'flower_processed_data.npy'
    
    #设定验证集和测试集的百分比
    VALIDATION_PERCENTAGE = 10
    TEST_PERCENTAGE = 10
    
    def create_image_list(sess, testing_percentage, validation_percentage):
    
        #列出输入文件夹下的所有子文件夹,此时sub_dirs里面除了有子文件夹还有它自身,在第一个
        sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]
        #设置一个bool值,指定第一次循环的时候跳过母文件夹
        is_root_dir = True
        #print(sub_dirs)
    
        #初始化数据矩阵
        training_images = []
        training_labels = []
        testing_images = []
        testing_labels = []
        validation_images = []
        validation_labels= []
        current_label = 0
    
        #分别处理每个子文件夹
        for sub_dir in sub_dirs:
            #跳过第一个值,即跳过母文件夹
            if is_root_dir:
                is_root_dir = False
                continue
    
            #获取子目录中的所有图片文件
            extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
            #用列表记录所有图片文件
            file_list = []
            #获取此子目录的名字比如daisy
            dir_name = os.path.basename(sub_dir)
            #对此子目录中所有图片后缀的文件
            for extension in extensions:
                #获取每种图片的所有正则表达式
                file_glob = os.path.join(INPUT_DATA, dir_name, '*.' + extension)
                print(file_glob)
                #将所有符合正则表达式的文件名加入文件列表
                file_list.extend(glob.glob(file_glob))
            print(file_list)
            #如果没有文件跳出循环
            if not file_list:
                continue
            print("processing ", dir_name)
    
            i = 0
            #对于每张图片
            for file_name in file_list:
                i+=1
                #打开图片文件
                #print("process num : ",i,"   processing", file_name, file=f)
                image_raw_data = gfile.FastGFile(file_name,'rb').read()
                #解码
                image = tf.image.decode_jpeg(image_raw_data)
                #如果图片格式不是float32则转为float32
                if image.dtype != tf.float32:
                    image = tf.image.convert_image_dtype(image, dtype=tf.float32)
                #将图片源数据转为299*299
                image = tf.image.resize_images(image, [300,300])
                #得到此图片的数据
                image_value = sess.run(image)
                print(np.shape(image_value))
    
                #生成一个100以内的数
                chance = np.random.randint(100)
                #按概率随机分到三个数据集中
                if chance < validation_percentage:
                    validation_images.append(image_value)
                    validation_labels.append(current_label)
                elif chance < (testing_percentage + validation_percentage):
                    testing_images.append(image_value)
                    testing_labels.append(current_label)
                else:
                    training_images.append(image_value)
                    training_labels.append(current_label)
                if i%200 == 0:
                    print("processing...")
            #处理完此种品种就将标签+1
            current_label += 1
    
        #将训练数据和标签以同样的方式打乱
        state = np.random.get_state()
        np.random.shuffle(training_images)
        np.random.set_state(state)
        np.random.shuffle(training_labels)
    
        #返回所有数据
        return np.asarray([training_images, training_labels,
                           validation_images, validation_labels, testing_images, testing_labels])
    
    
    def main():
        with tf.Session() as sess:
            processed_data = create_image_list(sess, TEST_PERCENTAGE, VALIDATION_PERCENTAGE)
            #将数据存到文件中
            np.save(OUTPUT_FILE, processed_data)
    
    if __name__ == "__main__":
        main()
    
    resnet.py
    # -*- coding: UTF-8 -*-
    # Author:Yinli
    
    import numpy as np
    import tensorflow as tf
    import tensorflow.contrib.slim as slim
    # 加载通过slim定义好的resnet_v1模型
    import tensorflow.contrib.slim.python.slim.nets.resnet_v1 as resnet_v1
    
    # 数据文件
    INPUT_DATA = "./flower_processed_data.npy"
    # 保存训练好的模型
    TRAIN_FILE = "./save_model/my_model"
    # 提供的已经训练好的模型
    CKPT_FILE = "./resnet_v1_50.ckpt"
    
    # 定义训练所用参数
    LEARNING_RATE = 0.0001
    STEPS = 500
    BATCH = 32
    N_CLASSES = 5
    
    # 这里指出了不需要从训练好的模型中加载的参数,就是最后的自定义的全连接层
    CHECKPOINT_EXCLUDE_SCOPES = 'Logits'
    # 指定最后的全连接层为可训练的参数
    TRAINABLE_SCOPES = 'Logits'
    
    
    # 加载所有需要从训练好的模型加载的参数
    def get_tuned_variables():
        ##不需要加载的范围
        exclusions = [scope.strip() for scope in CHECKPOINT_EXCLUDE_SCOPES.split(",")]
        # 初始化需要加载的参数
        variables_to_restore = []
    
        # 遍历模型中的所有参数
        for var in slim.get_model_variables():
            # 先指定为不需要移除
            excluded = False
            # 遍历exclusions,如果在exclusions中,就指定为需要移除
            for exclusion in exclusions:
                if var.op.name.startswith(exclusion):
                    excluded = True
                    break
            # 如果遍历完后还是不需要移除,就把参数加到列表里
            if not excluded:
                variables_to_restore.append(var)
        return variables_to_restore
    
    
    # 获取所有需要训练的参数
    def get_trainable_variables():
        # 同上
        scopes = [scope.strip() for scope in TRAINABLE_SCOPES.split(",")]
        variables_to_train = []
        # 枚举所有需要训练的参数的前缀,并找到这些前缀的所有参数
        for scope in scopes:
            variables = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope)
            variables_to_train.extend(variables)
        return variables_to_train
    
    
    def main():
        # 加载数据
        processed_data = np.load(INPUT_DATA)
        training_images = processed_data[0]
        n_training_example = len(training_images)
        training_labels = processed_data[1]
        validation_images = processed_data[2]
        validation_labels = processed_data[3]
        testing_images = processed_data[4]
        testing_labels = processed_data[5]
    
        print("there is %d training examples, %d validation examples, %d testing examples" %
              (n_training_example, len(validation_labels), len(testing_labels)))
    
        # 定义数据格式
        images = tf.placeholder(tf.float32, [None, 300, 300, 3], name='input_images')
        labels = tf.placeholder(tf.int64, [None], name='labels')
    
        # 定义模型,因为给出的只有参数,并没有模型,这里需要指定模型的具体结构
        with slim.arg_scope(resnet_v1.resnet_arg_scope()):
            # logits就是最后预测值,images就是输入数据,指定num_classes=None是为了使resnet模型最后的输出层禁用
            logits, _ = resnet_v1.resnet_v1_50(images, num_classes=None)
    
        #自定义的输出层
        with tf.variable_scope("Logits"):
            #将原始模型的输出数据去掉维度为2和3的维度,最后只剩维度1的batch数和维度4的300*300*3
            #也就是将原来的二三四维度全部压缩到第四维度
            net = tf.squeeze(logits, axis=[1,2])
            #加入一层dropout层
            net = slim.dropout(net, keep_prob=0.5,scope='dropout_scope')
            #加入一层全连接层,指定最后输出大小
            logits = slim.fully_connected(net, num_outputs=N_CLASSES, scope='fc')
    
    
        # 获取需要训练的变量
        trainable_variables = get_trainable_variables()
    
        # 定义损失,模型定义的时候已经考虑了正则化了
        tf.losses.softmax_cross_entropy(tf.one_hot(labels, N_CLASSES), logits, weights=1.0)
        # 定义训练过程
        train_step = tf.train.RMSPropOptimizer(LEARNING_RATE).minimize(tf.losses.get_total_loss())
    
        # 定义测试和验证过程
        with tf.name_scope('evaluation'):
            correct_prediction = tf.equal(tf.argmax(logits, 1), labels)
            evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    
        # 定义加载模型的函数,就是重新定义load_fn函数,从文件中获取参数,获取指定的变量,忽略缺省值
        load_fn = slim.assign_from_checkpoint_fn(CKPT_FILE, get_tuned_variables(), ignore_missing_vars=True)
    
        # 定义保存新的训练好的模型的函数
        saver = tf.train.Saver()
        with tf.Session() as sess:
            # 初始化没有加载进来的变量,一定要在模型加载之前,否则会将训练好的参数重新赋值
            init = tf.global_variables_initializer()
            sess.run(init)
    
            # 加载训练好的模型
            print("加载谷歌训练好的模型...")
            load_fn(sess)
    
            start = 0
            end = BATCH
            for i in range(STEPS):
                # 训练...
                sess.run(train_step, feed_dict={images: training_images[start:end],
                                                labels: training_labels[start:end]})
                # 间断地保存模型,并在验证集上验证
                if i % 50 == 0 or i + 1 == STEPS:
                    saver.save(sess, TRAIN_FILE, global_step=i)
                    validation_accuracy = sess.run(evaluation_step, feed_dict={images: validation_images,
                                                                               labels: validation_labels})
                    print("经过%d次训练后,在验证集上的正确率为%.3f" % (i, validation_accuracy))
    
                # 更新起始和末尾
                start = end
                if start == n_training_example:
                    start = 0
                end = start + BATCH
                if end > n_training_example:
                    end = n_training_example
    
            # 训练完了在测试集上测试正确率
            testing_accuracy = sess.run(evaluation_step, feed_dict={images: testing_images,
                                                                    labels: testing_labels})
            print("最后在测试集上的正确率为%.3f" % testing_accuracy)
    
    
    if __name__ == '__main__':
        main()
    

    运行结果

    result.png

    结果分析

    从结果中可以看到,利用已经训练好的复杂模型的参数,再根据问题加上一层自定义的输出层,可以在短时间内利用较少的资源将模型迁移到不同的问题上,在200次训练的时候就可以在这个问题上达到90%的正确率,经过500次训练后可以在测试集上达到接近95%的正确率,验证了目前的主流卷积神经网络具有很好的普适性和迁移性。

    相关文章

      网友评论

        本文标题:ResNet-V1-50卷积神经网络迁移学习进行不同品种的花的分

        本文链接:https://www.haomeiwen.com/subject/yrbdhqtx.html